33@author Micah Huth
44"""
55
6+ import importlib
67from roboticstoolbox .backend .Connector import Connector
7-
88from roboticstoolbox .robot .DHLink import DHLink
99from roboticstoolbox .robot .Robot import Robot as r
1010
11- from roboticstoolbox .backend .VPython .canvas import GraphicsCanvas3D , \
12- GraphicsCanvas2D
13- from roboticstoolbox .backend .VPython .graphicalrobot import \
14- GraphicalRobot
15- from roboticstoolbox .backend .VPython .common_functions import \
16- close_localhost_session
11+ GraphicsCanvas3D = None
12+ GraphicsCanvas2D = None
13+ GraphicalRobot = None
14+ close_localhost_session = None
15+
16+
17+ def _imports ():
18+ global GraphicsCanvas3D
19+ global GraphicsCanvas2D
20+ global GraphicalRobot
21+ global close_localhost_session
22+
23+ try :
24+ canvas = importlib .import_module (
25+ 'roboticstoolbox.backend.VPython.canvas' )
26+ GraphicsCanvas3D = canvas .GraphicsCanvas3D
27+ GraphicsCanvas2D = canvas .GraphicsCanvas2D
28+
29+ graphicalrobot = importlib .import_module (
30+ 'roboticstoolbox.backend.VPython.graphicalrobot' )
31+ GraphicalRobot = graphicalrobot .GraphicalRobot
32+
33+ common_functions = importlib .import_module (
34+ 'roboticstoolbox.backend.VPython.common_functions' )
35+ close_localhost_session = common_functions .close_localhost_session
36+
37+ except ImportError :
38+ print (
39+ '\n You must install the VPython component of the toolbox, do: \n '
40+ 'pip install roboticstoolbox[vpython]\n \n ' )
1741
1842
19- class VPython (Connector ):
43+ class VPython (Connector ): # pragma nocover
2044 """
2145 Graphical backend using VPython
2246
2347 VPython is a Python API that connects to a JavaScript/WebGL 3D graphics
24- engine in a browser tab. It supports many 3D graphical primitives including
25- meshes, boxes, ellipsoids and lines. It can not render in full color.
48+ engine in a browser tab. It supports many 3D graphical primitives
49+ including meshes, boxes, ellipsoids and lines. It can not render in
50+ full color.
2651
2752 Example:
2853
@@ -41,7 +66,7 @@ class VPython(Connector):
4166 :references:
4267
4368 - https://vpython.org
44-
69+
4570 """
4671 # TODO be able to add ellipsoids (vellipse, fellipse)
4772 # TODO be able add lines (for end-effector paths)
@@ -53,14 +78,19 @@ def __init__(self):
5378 """
5479 super (VPython , self ).__init__ ()
5580
81+ _imports ()
82+
5683 # Init vars
5784 self .canvases = []
58- self .canvas_settings = [] # 2D array of [is_3d, height, width, title, caption, grid] per canvas
85+ # 2D array of [is_3d, height, width, title, caption, grid] per canvas
86+ self .canvas_settings = []
5987 self .robots = []
6088
6189 self ._create_empty_session ()
6290
63- def launch (self , is_3d = True , height = 500 , width = 888 , title = '' , caption = '' , grid = True ):
91+ def launch (
92+ self , is_3d = True , height = 500 , width = 888 ,
93+ title = '' , caption = '' , grid = True ):
6494 """
6595 Launch a graphical backend in a browser tab
6696
@@ -71,13 +101,16 @@ def launch(self, is_3d=True, height=500, width=888, title='', caption='', grid=T
71101
72102 super ().launch ()
73103
74- self .canvas_settings .append ([is_3d , height , width , title , caption , grid ])
104+ self .canvas_settings .append (
105+ [is_3d , height , width , title , caption , grid ])
75106
76107 # Create the canvas with the given information
77108 if is_3d :
78- self .canvases .append (GraphicsCanvas3D (height , width , title , caption , grid ))
109+ self .canvases .append (
110+ GraphicsCanvas3D (height , width , title , caption , grid ))
79111 else :
80- self .canvases .append (GraphicsCanvas2D (height , width , title , caption , grid ))
112+ self .canvases .append (
113+ GraphicsCanvas2D (height , width , title , caption , grid ))
81114
82115 def step (self , id , q = None , fig_num = 0 ):
83116 """
@@ -88,41 +121,44 @@ def step(self, id, q=None, fig_num=0):
88121 :type id: :class:`~roboticstoolbox.robot.DHRobot.DHRobot`,
89122 :class:`roboticstoolbox.backend.VPython.graphics_robot.GraphicalRobot`
90123 :param q: The joint angles/configuration of the robot (Optional, if not
91- supplied will use the stored q values).
92- :type q: float ndarray(n)
93- :param fig_num: The canvas index to delete the robot from, defaults to the
94- initial one
95- :type fig_num: int, optional
124+ supplied will use the stored q values).
125+ :type q: float ndarray(n)
126+ :param fig_num: The canvas index to delete the robot from, defaults to
127+ the initial one
128+ :type fig_num: int, optional
96129 :raises ValueError: Figure number must be between 0 and total number of
97- canvases
130+ canvases
98131 :raises TypeError: Input must be a DHLink or GraphicalRobot
99132
100133 ``env.step(args)`` triggers an update of the 3D scene in the browser
101134 window referenced by ``env``.
102135
103- .. note::
136+ .. note::
104137
105138 - Each robot in the scene is updated based on
106139 their control type (position, velocity, acceleration, or torque).
107140 - Upon acting, the other three of the four control types will be
108- updated in the internal state of the robot object.
109- - The control type is defined by the robot object, and not all robot
110- objects support all control types.
141+ updated in the internal state of the robot object.
142+ - The control type is defined by the robot object, and not all
143+ robot objects support all control types.
111144 - Execution is blocked for the specified interval
112145
113146 """
114147
115148 super ().step ()
116149
117150 if fig_num < 0 or fig_num >= len (self .canvases ):
118- raise ValueError ("Figure number must be between 0 and total number of canvases" )
151+ raise ValueError (
152+ "Figure number must be between 0 and total number of canvases" )
119153
120154 # If DHRobot given
121155 if isinstance (id , r .Robot ):
122156 robot = None
123157 # Find first occurrence of it that is in the correct canvas
124158 for i in range (len (self .robots )):
125- if self .robots [i ].robot is id and self .canvases [fig_num ].is_robot_in_canvas (self .robots [i ]):
159+ if self .robots [i ].robot is id and \
160+ self .canvases [fig_num ].is_robot_in_canvas (
161+ self .robots [i ]):
126162 robot = self .robots [i ]
127163 break
128164 if robot is None :
@@ -138,7 +174,9 @@ def step(self, id, q=None, fig_num=0):
138174 id .set_joint_poses (poses )
139175 # Else
140176 else :
141- raise TypeError ("Input must be a Robot (or subclass) or GraphicalRobot, given {0}" .format (type (id )))
177+ raise TypeError (
178+ "Input must be a Robot (or subclass) or "
179+ "GraphicalRobot, given {0}" .format (type (id )))
142180
143181 def reset (self ):
144182 """
@@ -168,9 +206,13 @@ def reset(self):
168206 for settings in self .canvas_settings :
169207 # Create the canvas with the given information
170208 if settings [0 ]:
171- self .canvases .append (GraphicsCanvas3D (settings [1 ], settings [2 ], settings [3 ], settings [4 ], settings [5 ]))
209+ self .canvases .append (GraphicsCanvas3D (
210+ settings [1 ], settings [2 ], settings [3 ],
211+ settings [4 ], settings [5 ]))
172212 else :
173- self .canvases .append (GraphicsCanvas2D (settings [1 ], settings [2 ], settings [3 ], settings [4 ], settings [5 ]))
213+ self .canvases .append (GraphicsCanvas2D (
214+ settings [1 ], settings [2 ], settings [3 ],
215+ settings [4 ], settings [5 ]))
174216
175217 def restart (self ):
176218 """
@@ -184,16 +226,6 @@ def restart(self):
184226
185227 super ().restart ()
186228
187- # self.close()
188- # self._create_empty_session()
189- # for settings in self.canvas_settings:
190- # # Create the canvas with the given information
191- # if settings[0]:
192- # self.canvases.append(GraphicsCanvas3D(settings[1], settings[2], settings[3], settings[4], settings[5]))
193- # else:
194- # self.canvases.append(GraphicsCanvas2D(settings[1], settings[2], settings[3], settings[4], settings[5]))
195-
196- # Program on close terminates execution, so just run reset
197229 self .reset ()
198230
199231 def close (self ):
@@ -222,21 +254,23 @@ def add(self, fig_num, name, dhrobot):
222254 """
223255 Add a robot to the graphical scene
224256
225- :param fig_num: The canvas number to place the robot in
257+ :param fig_num: The canvas number to place the robot in
226258 :type fig_num: int
227- :param name: The name of the robot
228- :type name: `str`
229- :param dhrobot: The ``DHRobot`` object (if applicable)
230- :type dhrobot: class:`~roboticstoolbox.robot.DHRobot.DHRobot`, None
231- :raises ValueError: Figure number must be between 0 and number of figures created
259+ :param name: The name of the robot
260+ :type name: `str`
261+ :param dhrobot: The ``DHRobot`` object (if applicable)
262+ :type dhrobot: class:`~roboticstoolbox.robot.DHRobot.DHRobot`, None
263+ :raises ValueError: Figure number must be between 0 and number of
264+ figures created
232265 :return: object id within visualizer
233266 :rtype: int
234267
235- ``id = env.add(robot)`` adds the ``robot`` to the graphical environment.
268+ ``id = env.add(robot)`` adds the ``robot`` to the graphical
269+ environment.
236270
237271 .. note::
238272
239- - ``robot`` must be of an appropriate class.
273+ - ``robot`` must be of an appropriate class.
240274 - Adds the robot object to a list of robots which will be updated
241275 when the ``step()`` method is called.
242276
@@ -251,42 +285,48 @@ def add(self, fig_num, name, dhrobot):
251285
252286 # Sanity check input
253287 if fig_num < 0 or fig_num > len (self .canvases ) - 1 :
254- raise ValueError ("Figure number must be between 0 and number of figures created" )
288+ raise ValueError (
289+ "Figure number must be between 0 and number "
290+ "of figures created" )
255291
256292 # Add robot to canvas
257- self .robots .append (GraphicalRobot (self .canvases [fig_num ], name , dhrobot ))
293+ self .robots .append (
294+ GraphicalRobot (self .canvases [fig_num ], name , dhrobot ))
258295 # self.canvases[fig_num].add_robot(self.robots[len(self.robots)-1])
259296
260297 def remove (self , id , fig_num = 0 ):
261298 """
262299 Remove a robot to the graphical scene
263300
264- :param id: The id of the robot to remove. Can be either the DHLink or
301+ :param id: The id of the robot to remove. Can be either the DHLink or
265302 GraphicalRobot
266303 :type id: class:`~roboticstoolbox.robot.DHRobot.DHRobot`,
267304 class:`roboticstoolbox.backend.VPython.graphics_robot.GraphicalRobot`
268305 :param fig_num: The canvas index to delete the robot from, defaults to
269306 the initial one
270307 :type fig_num: int, optional
271- :raises ValueError: Figure number must be between 0 and total number
308+ :raises ValueError: Figure number must be between 0 and total number
272309 of canvases
273310 :raises TypeError: Input must be a DHLink or GraphicalRobot
274311
275- ``env.remove(robot)`` removes the ``robot`` from the graphical environment.
312+ ``env.remove(robot)`` removes the ``robot`` from the graphical
313+ environment.
276314
277315 """
278316
279317 super ().remove ()
280318
281319 if fig_num < 0 or fig_num >= len (self .canvases ):
282- raise ValueError ("Figure number must be between 0 and total number of canvases" )
320+ raise ValueError (
321+ "Figure number must be between 0 and total number of canvases" )
283322
284323 # If DHLink given
285324 if isinstance (id , DHLink ):
286325 robot = None
287326 # Find first occurrence of it that is in the correct canvas
288327 for i in range (len (self .robots )):
289- if self .robots [i ].seriallink .equal (id ) and self .canvases [fig_num ].is_robot_in (self .robots [i ]):
328+ if self .robots [i ].seriallink .equal (id ) and \
329+ self .canvases [fig_num ].is_robot_in (self .robots [i ]):
290330 robot = self .robots [i ]
291331 break
292332 if robot is None :
0 commit comments