Skip to content

Commit 31dc3e1

Browse files
authored
Merge pull request #1046 from compas-dev/robot_artist_tools
Robot artist tools
2 parents ddd806b + 08d040c commit 31dc3e1

File tree

2 files changed

+64
-32
lines changed

2 files changed

+64
-32
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3535
* Moved from `autopep8` to `black`
3636
* Fixed bug in `compas.utilities.linspace` for number series with high precision start and stop values.
3737
* Fixed uncentered viewbox in `Plotter.zoom_extents()`
38+
* Changed `RobotModelArtists.atteched_tool_models` to dictionary to support multiple tools.
3839

3940
### Removed
4041

src/compas/artists/robotmodelartist.py

Lines changed: 63 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515

1616
class 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

Comments
 (0)