1414
1515
1616class AbstractRobotModelArtist (object ):
17-
1817 def transform (self , geometry , transformation ):
1918 """Transforms a CAD-specific geometry using a Transformation.
2019
@@ -75,10 +74,28 @@ def __init__(self, model):
7574 super (RobotModelArtist , self ).__init__ ()
7675 self .model = model
7776 self .create ()
78- self .scale_factor = 1.
79- self .attached_tool_model = None
77+ self .scale_factor = 1.0
78+ self .attached_tool_models = {}
8079 self .attached_items = {}
8180
81+ @property
82+ def attached_tool_model (self ):
83+ """
84+ For backwards compatibility. Returns the tool attached to the first end effector link or,
85+ if not available, the first tool from the dictionary.
86+ Returns None if no tool are attached.
87+
88+ Returns
89+ -------
90+ :class: `~compas.robots.model.ToolModel`
91+
92+ """
93+ tool_model = None
94+ if self .attached_tool_models :
95+ link_name = self .model .get_end_effector_link ()
96+ tool_model = self .attached_tool_models .get (link_name ) or list (self .attached_tool_models .values ())[0 ]
97+ return tool_model
98+
8299 def attach_tool_model (self , tool_model ):
83100 """Attach a tool to the robot artist for visualization.
84101
@@ -88,35 +105,48 @@ def attach_tool_model(self, tool_model):
88105 The tool that should be attached to the robot's flange.
89106
90107 """
91- self .attached_tool_model = tool_model
92- self .create (tool_model .root , 'attached_tool' )
108+ self .create (tool_model .root , "attached_tool" )
93109
94110 if not tool_model .link_name :
95111 link = self .model .get_end_effector_link ()
96112 tool_model .link_name = link .name
97113 else :
98114 link = self .model .get_link_by_name (tool_model .link_name )
99115
116+ # don't attach twice on the same link
117+ self .attached_tool_models [tool_model .link_name ] = tool_model
118+
100119 ee_frame = link .parent_joint .origin .copy ()
101120 initial_transformation = Transformation .from_frame_to_frame (Frame .worldXY (), ee_frame )
102121
103122 sample_geometry = link .collision [0 ] if link .collision else link .visual [0 ] if link .visual else None
104123
105- if hasattr (sample_geometry , ' current_transformation' ):
124+ if hasattr (sample_geometry , " current_transformation" ):
106125 relative_transformation = sample_geometry .current_transformation
107126 else :
108127 relative_transformation = Transformation ()
109128
110129 transformation = relative_transformation .concatenated (initial_transformation )
111130
112- self .update_tool (transformation = transformation )
131+ self .update_tool (tool = tool_model , transformation = transformation )
113132
114133 tool_model .parent_joint_name = link .parent_joint .name
115134
116- def detach_tool_model (self ):
117- """Detach the tool.
135+ def detach_tool_model (self , tool_model = None ):
118136 """
119- self .attached_tool_model = None
137+ Detach tool_model from this robot model.
138+ If tool_model is None, all attached tools are detached.
139+
140+ Parameters
141+ ----------
142+ tool_model : :class:`~compas.robots.ToolModel`
143+ The tool that should be detached from the robot's flange.
144+ If None, all attached tools tools are removed.
145+ """
146+ if tool_model :
147+ del self .attached_tool_models [tool_model .link_name ]
148+ else :
149+ self .attached_tool_models .clear ()
120150
121151 def attach_mesh (self , mesh , name , link = None , frame = None ):
122152 """Rigidly attaches a compas mesh to a given link for visualization.
@@ -201,17 +231,17 @@ def create(self, link=None, context=None):
201231 meshes = Geometry ._get_item_meshes (item )
202232
203233 if meshes :
204- is_visual = hasattr (item , ' get_color' )
234+ is_visual = hasattr (item , " get_color" )
205235 color = item .get_color () if is_visual else None
206236
207237 native_geometry = []
208238 for i , mesh in enumerate (meshes ):
209- mesh_type = ' visual' if is_visual else ' collision'
239+ mesh_type = " visual" if is_visual else " collision"
210240 if not context :
211241 mesh_name_components = [self .model .name , mesh_type , link .name , str (i )]
212242 else :
213243 mesh_name_components = [self .model .name , mesh_type , context , link .name , str (i )]
214- mesh_name = '.' .join (mesh_name_components )
244+ mesh_name = "." .join (mesh_name_components )
215245 native_mesh = self .create_geometry (mesh , name = mesh_name , color = color )
216246
217247 self .transform (native_mesh , item .init_transformation )
@@ -301,8 +331,8 @@ def scale_link(self, link, transformation):
301331 """
302332 self ._scale_link_helper (link , transformation )
303333
304- if self .attached_tool_model :
305- self ._scale_link_helper (self . attached_tool_model .root , transformation )
334+ for tool in self .attached_tool_models . values () :
335+ self ._scale_link_helper (tool .root , transformation )
306336
307337 def _scale_link_helper (self , link , transformation ):
308338 for item in itertools .chain (link .visual , link .collision ):
@@ -333,7 +363,7 @@ def _apply_transformation_on_transformed_link(self, item, transformation):
333363 None
334364
335365 """
336- if getattr (item , ' current_transformation' ):
366+ if getattr (item , " current_transformation" ):
337367 relative_transformation = transformation * item .current_transformation .inverse ()
338368 else :
339369 relative_transformation = transformation
@@ -359,9 +389,9 @@ def update(self, joint_state, visual=True, collision=True):
359389
360390 """
361391 _ = self ._update (self .model , joint_state , visual , collision )
362- if self .attached_tool_model :
363- frame = self .model .forward_kinematics (joint_state , link_name = self . attached_tool_model .link_name )
364- self .update_tool (visual = visual , collision = collision , transformation = Transformation .from_frame_to_frame (Frame .worldXY (), frame ))
392+ for tool in self .attached_tool_models . values () :
393+ frame = self .model .forward_kinematics (joint_state , link_name = tool .link_name )
394+ self .update_tool (tool = tool , visual = visual , collision = collision , transformation = Transformation .from_frame_to_frame (Frame .worldXY (), frame ))
365395
366396 def _update (self , model , joint_state , visual = True , collision = True , parent_transformation = None ):
367397 transformations = model .compute_transformations (joint_state , parent_transformation = parent_transformation )
@@ -380,7 +410,7 @@ def _transform_link_geometry(self, link, transformation, collision=True):
380410 for item in self .attached_items .get (link .name , {}).values ():
381411 self ._apply_transformation_on_transformed_link (item , transformation )
382412
383- def update_tool (self , joint_state = None , visual = True , collision = True , transformation = None ):
413+ def update_tool (self , tool , joint_state = None , visual = True , collision = True , transformation = None ):
384414 """Triggers the update of the robot geometry of the tool.
385415
386416 Parameters
@@ -403,12 +433,12 @@ def update_tool(self, joint_state=None, visual=True, collision=True, transformat
403433
404434 """
405435 joint_state = joint_state or {}
406- if self . attached_tool_model :
407- if transformation is None :
408- transformation = self . attached_tool_model .current_transformation
409- self ._transform_link_geometry (self . attached_tool_model .root , transformation , collision )
410- self ._update (self . attached_tool_model , joint_state , visual , collision , transformation )
411- self . attached_tool_model .current_transformation = transformation
436+
437+ if transformation is None :
438+ transformation = tool .current_transformation
439+ self ._transform_link_geometry (tool .root , transformation , collision )
440+ self ._update (tool , joint_state , visual , collision , transformation )
441+ tool .current_transformation = transformation
412442
413443 def draw_visual (self ):
414444 """Draws all visual geometry of the robot model.
@@ -420,10 +450,10 @@ def draw_visual(self):
420450
421451 """
422452 visual = []
423- for native_geometry in self ._iter_geometry (self .model , ' visual' ):
453+ for native_geometry in self ._iter_geometry (self .model , " visual" ):
424454 visual .append (native_geometry )
425- if self .attached_tool_model :
426- for native_geometry in self ._iter_geometry (self . attached_tool_model , ' visual' ):
455+ for tool in self .attached_tool_models . values () :
456+ for native_geometry in self ._iter_geometry (tool , " visual" ):
427457 visual .append (native_geometry )
428458 return visual
429459
@@ -437,11 +467,12 @@ def draw_collision(self):
437467
438468 """
439469 visual = []
440- for native_geometry in self ._iter_geometry (self .model , ' collision' ):
470+ for native_geometry in self ._iter_geometry (self .model , " collision" ):
441471 visual .append (native_geometry )
442- if self .attached_tool_model :
443- for native_geometry in self ._iter_geometry (self . attached_tool_model , ' collision' ):
472+ for tool in self .attached_tool_models . values () :
473+ for native_geometry in self ._iter_geometry (tool , " collision" ):
444474 visual .append (native_geometry )
475+
445476 return visual
446477
447478 def draw_attached_meshes (self ):
0 commit comments