Skip to content

Commit 897f3f1

Browse files
authored
Merge pull request #25 from compas-dev/feature/robot_model_meshes
Added 4 methods for extracting meshes from links in `RobotModel`
2 parents 3284fbc + eeb6662 commit 897f3f1

File tree

3 files changed

+198
-4
lines changed

3 files changed

+198
-4
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12+
* Added new functions to extract Visual and Collision meshes from `RobotModel`.
13+
* Added `RobotModel.get_link_visual_meshes` and `RobotModel.get_link_collision_meshes`
14+
for extracting meshes from a specific link.
15+
Added `RobotModel.get_link_visual_meshes_joined` and `RobotModel.get_link_collision_meshes_joined`
16+
for extracting a single joined mesh from a specific link.
17+
1218
### Changed
1319

1420
* Fixed bug in `compas_viewer` due to import of `RobotModelObject` inside registration function.

src/compas_robots/model/robot.py

Lines changed: 189 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import itertools
66
import random
77

8+
from compas import IPY
89
from compas.colors import Color
910
from compas.data import Data
1011
from compas.datastructures import Mesh
@@ -34,6 +35,13 @@
3435
from .link import Link
3536
from .link import Visual
3637

38+
if not IPY:
39+
from typing import TYPE_CHECKING
40+
41+
if TYPE_CHECKING:
42+
from typing import List # noqa: F401
43+
from typing import Optional # noqa: F401
44+
3745

3846
class RobotModel(Data):
3947
"""RobotModel is the root element of the model.
@@ -822,6 +830,10 @@ def scale(self, factor, link=None):
822830

823831
self._scale_factor = factor
824832

833+
# --------------------------------------------------------------------------
834+
# Methods for computing frames and axes from the Robot Model
835+
# --------------------------------------------------------------------------
836+
825837
def compute_transformations(self, joint_state, link=None, parent_transformation=None):
826838
"""Recursive function to calculate the transformations of each joint.
827839
@@ -874,7 +886,9 @@ def compute_transformations(self, joint_state, link=None, parent_transformation=
874886
return transformations
875887

876888
def transformed_frames(self, joint_state):
877-
"""Returns the transformed frames based on the joint_state.
889+
"""Returns the transformed Joint frames (relative to the Robot Coordinate Frame) based on the joint_state (:class:`~compas_robots.Configuration`).
890+
891+
The order of the frames is the same as the order returned by :meth:`iter_joints`.
878892
879893
Parameters
880894
----------
@@ -990,6 +1004,180 @@ def _check_link_name(self, name):
9901004
if name in all_link_names:
9911005
raise ValueError("Link name '%s' already used in chain." % name)
9921006

1007+
# --------------------------------------------------------------------------
1008+
# Methods for accessing the visual and collision geometry
1009+
# --------------------------------------------------------------------------
1010+
1011+
def _extract_link_meshes(self, link_elements):
1012+
# type: (List[Visual] | List[Collision]) -> List[Mesh]
1013+
"""Extracts the list of compas meshes from a link's Visual or Collision elements.
1014+
1015+
Note that each link may have multiple visual or collision nodes, each can have
1016+
a different origin frame. Therefore, the returned meshes are transformed
1017+
according to the ``.origin`` frame of each visual or collision element.
1018+
1019+
This transformation is time consuming for large meshes and should be done only once.
1020+
This transformation is automatically skipped if the origin is None or the identity frame.
1021+
1022+
Parameters
1023+
----------
1024+
link_elements : list of :class:`~compas_robots.model.Visual` or :class:`~compas_robots.model.Collision`
1025+
The list of Visual or Collision elements of a link.
1026+
1027+
Returns
1028+
-------
1029+
list of :class:`~compas.datastructures.Mesh`
1030+
A list of meshes belonging to the link elements.
1031+
If there are no meshes, an empty list is returned.
1032+
1033+
"""
1034+
meshes = []
1035+
# Note: Each Link can have multiple visual nodes
1036+
for element in link_elements:
1037+
# Some elements may have a non-identity origin frame
1038+
t_origin = None
1039+
if element.origin:
1040+
origin = element.origin if isinstance(element.origin, Frame) else element.origin._proxied_object
1041+
if Frame.worldXY() != origin:
1042+
t_origin = Transformation.from_frame(element.origin)
1043+
1044+
# Note: the MeshDescriptor.meshes object supports a list of compas meshes.
1045+
# There can be multiple mesh in a single MeshDescriptor
1046+
for mesh in LinkGeometry._get_item_meshes(element):
1047+
# Transform the mesh
1048+
if t_origin:
1049+
meshes.append(mesh.transformed(t_origin))
1050+
else:
1051+
meshes.append(mesh)
1052+
1053+
return meshes
1054+
1055+
def get_link_visual_meshes(self, link):
1056+
# type: (Link) -> List[Mesh]
1057+
"""Get a list of visual meshes from a Link.
1058+
1059+
The origin of the visual meshes are transformed according to the `element.origin`
1060+
of the Visual nodes. This means that the returned mesh will match with the link's origin frame.
1061+
1062+
Parameters
1063+
----------
1064+
link : :class:`~compas_robots.model.Link`
1065+
The link to extract the visual meshes from.
1066+
1067+
Returns
1068+
-------
1069+
list of :class:`~compas.datastructures.Mesh`
1070+
A list of visual meshes belonging to the link
1071+
The list is empty if no visual meshes are found.
1072+
1073+
Notes
1074+
-----
1075+
Only MeshDescriptor in `element.geometry.shape` is supported. Other shapes are ignored.
1076+
"""
1077+
visual_meshes = self._extract_link_meshes(link.visual, True)
1078+
return visual_meshes
1079+
1080+
def get_link_visual_meshes_joined(self, link, weld=False, weld_precision=None):
1081+
# type: (Link, Optional[bool], Optional[int]) -> Mesh | None
1082+
"""Get the visual meshes of a Link joined into a single mesh.
1083+
1084+
The origin of the visual meshes are transformed according to the `element.origin`
1085+
of the Visual nodes. This means that the returned mesh will match with the link's origin frame.
1086+
1087+
Parameters
1088+
----------
1089+
link : :class:`~compas_robots.model.Link`
1090+
The link to extract the visual meshes from.
1091+
weld : bool, optional
1092+
If True, weld close vertices after joining. Defaults to False.
1093+
weld_precision : int, optional
1094+
The precision used for welding the mesh.
1095+
Default is :attr:`TOL.precision`.
1096+
1097+
Returns
1098+
-------
1099+
:class:`~compas.datastructures.Mesh` | None
1100+
A single mesh representing the visual meshes of the link.
1101+
None if no visual meshes are found.
1102+
1103+
Notes
1104+
-----
1105+
Only MeshDescriptor in `element.geometry.shape` is supported. Other shapes are ignored.
1106+
"""
1107+
visual_meshes = self._extract_link_meshes(link.visual, True)
1108+
if not visual_meshes:
1109+
return None
1110+
1111+
joined_mesh = Mesh()
1112+
for mesh in visual_meshes:
1113+
joined_mesh.join(mesh, weld, weld_precision)
1114+
return joined_mesh
1115+
1116+
def get_link_collision_meshes(self, link):
1117+
# type: (Link) -> List[Mesh]
1118+
"""Get the list of collision meshes of a link.
1119+
1120+
The origin of the visual meshes are transformed according to the `element.origin`
1121+
of the Visual nodes. This means that the returned mesh will match with the link's origin frame.
1122+
1123+
Parameters
1124+
----------
1125+
link : :class:`~compas_robots.model.Link`
1126+
The link to extract the collision meshes from.
1127+
1128+
Returns
1129+
-------
1130+
list of :class:`~compas.datastructures.Mesh`
1131+
A list of collision meshes belonging to the link
1132+
The list is empty if no collision meshes are found.
1133+
1134+
Notes
1135+
-----
1136+
Only MeshDescriptor in `element.geometry.shape` is supported. Other shapes are ignored.
1137+
"""
1138+
collision_meshes = self._extract_link_meshes(link.collision, True)
1139+
return collision_meshes
1140+
1141+
def get_link_collision_meshes_joined(self, link, weld=False, weld_precision=None):
1142+
# type: (Link, Optional[bool], Optional[int]) -> Mesh | None
1143+
"""Get the collision meshes of a Link joined into a single mesh.
1144+
1145+
The origin of the visual meshes are transformed according to the `element.origin`
1146+
of the Visual nodes. This means that the returned mesh will match with the link's origin frame.
1147+
1148+
Parameters
1149+
----------
1150+
link : :class:`~compas_robots.model.Link`
1151+
The link to extract the collision meshes from.
1152+
weld : bool, optional
1153+
If True, weld close vertices after joining. Defaults to False.
1154+
weld_precision : int, optional
1155+
The precision used for welding the mesh.
1156+
Default is :attr:`TOL.precision`.
1157+
1158+
Returns
1159+
-------
1160+
:class:`~compas.datastructures.Mesh` | None
1161+
A single mesh representing the collision meshes of the link.
1162+
None if no collision meshes are found.
1163+
1164+
Notes
1165+
-----
1166+
Only MeshDescriptor in `element.geometry.shape` is supported. Other shapes are ignored.
1167+
"""
1168+
collision_meshes = self._extract_link_meshes(link.collision, True)
1169+
if not collision_meshes:
1170+
return None
1171+
1172+
joined_mesh = Mesh()
1173+
for mesh in collision_meshes:
1174+
joined_mesh.join(mesh, weld, weld_precision)
1175+
return joined_mesh
1176+
1177+
# --------------------------------------------------------------------------
1178+
# Methods for modifying the Robot Model structure
1179+
# --------------------------------------------------------------------------
1180+
9931181
def add_link(self, name, visual_meshes=None, visual_color=None, collision_meshes=None, **kwargs):
9941182
"""Adds a link to the robot model.
9951183

src/compas_robots/rhino/scene/robotmodelobject.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ def _enter_layer(self):
9393

9494
if self.layer:
9595
if not rs.IsLayer(self.layer):
96-
compas_rhino.create_layers_from_path(self.layer)
96+
compas_rhino.layers.create_layers_from_path(self.layer)
9797
self._previous_layer = rs.CurrentLayer(self.layer)
9898

9999
rs.EnableRedraw(False)
@@ -208,9 +208,9 @@ def clear_layer(self):
208208
209209
"""
210210
if self.layer:
211-
compas_rhino.clear_layer(self.layer)
211+
compas_rhino.layers.clear_layer(self.layer)
212212
else:
213-
compas_rhino.clear_current_layer()
213+
compas_rhino.layers.clear_current_layer()
214214

215215
def _add_mesh_to_doc(self, mesh):
216216
guid = sc.doc.Objects.AddMesh(mesh)

0 commit comments

Comments
 (0)