Skip to content

Commit 355795f

Browse files
Move the Meshcat Visualizer form iDynTree to this repo
1 parent 29201e0 commit 355795f

File tree

3 files changed

+272
-1
lines changed

3 files changed

+272
-1
lines changed

app.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from file_reader.signal_provider import SignalProvider
1010

1111
# Meshcat
12-
from idyntree.visualize import MeshcatVisualizer
12+
from robot_visualizer.meshcat_visualizer import MeshcatVisualizer
1313

1414

1515
def get_model_path(robot_name='iCubGazeboV3'):

robot_visualizer/__init__.py

Whitespace-only changes.
+271
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
"""
2+
Copyright (C) 2021 Fondazione Istituto Italiano di Tecnologia
3+
4+
Licensed under either the GNU Lesser General Public License v3.0 :
5+
https://www.gnu.org/licenses/lgpl-3.0.html
6+
or the GNU Lesser General Public License v2.1 :
7+
https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
8+
at your option.
9+
"""
10+
11+
import os
12+
import idyntree.bindings as idyn
13+
import numpy as np
14+
import warnings
15+
16+
17+
class MeshcatVisualizer:
18+
"""
19+
A simple wrapper to the meshcat visualizer. The MeshcatVisualizer class is highly inspired by the Pinocchio version
20+
of the MeshCat visualizer
21+
https://github.com/stack-of-tasks/pinocchio/blob/b134b25f1409f5bf036105b996da2d29c1a66a12/bindings/python/pinocchio/visualize/meshcat_visualizer.py
22+
"""
23+
24+
def __init__(self, zmq_url=None):
25+
import meshcat
26+
27+
if zmq_url is not None:
28+
print("Connecting to meshcat-server at zmq_url=" + zmq_url + ".")
29+
30+
self.viewer = meshcat.Visualizer(zmq_url=zmq_url)
31+
self.traversal = dict()
32+
self.model = dict()
33+
self.link_pos = dict()
34+
35+
def __is_mesh(self, geometry_object) -> bool:
36+
37+
if not geometry_object.isExternalMesh():
38+
return False
39+
mesh_path = geometry_object.asExternalMesh().getFileLocationOnLocalFileSystem()
40+
41+
# Check whether the geometry object contains a Mesh supported by MeshCat
42+
if mesh_path == "":
43+
return False
44+
45+
_, file_extension = os.path.splitext(mesh_path)
46+
if file_extension.lower() in [".dae", ".obj", ".stl"]:
47+
return True
48+
49+
return False
50+
51+
def __load_mesh(self, geometry_object):
52+
53+
import meshcat
54+
55+
mesh_path = geometry_object.asExternalMesh().getFileLocationOnLocalFileSystem()
56+
57+
# try to import the mesh
58+
if mesh_path == "":
59+
return None
60+
61+
_, file_extension = os.path.splitext(mesh_path)
62+
63+
basename = os.path.basename(mesh_path)
64+
file_name = os.path.splitext(basename)[0]
65+
66+
geometry_object.asExternalMesh().setName(file_name)
67+
68+
if file_extension.lower() == ".dae":
69+
obj = meshcat.geometry.DaeMeshGeometry.from_file(mesh_path)
70+
elif file_extension.lower() == ".obj":
71+
obj = meshcat.geometry.ObjMeshGeometry.from_file(mesh_path)
72+
elif file_extension.lower() == ".stl":
73+
obj = meshcat.geometry.StlMeshGeometry.from_file(mesh_path)
74+
else:
75+
msg = "The following mesh cannot be loaded: {}.".format(mesh_path)
76+
warnings.warn(msg, category=UserWarning, stacklevel=2)
77+
obj = None
78+
79+
return obj
80+
81+
def __apply_transform(self, world_H_frame, solid_shape, viewer_name):
82+
world_H_geometry = (world_H_frame * solid_shape.getLink_H_geometry()).asHomogeneousTransform().toNumPy()
83+
scale = list(solid_shape.asExternalMesh().getScale().toNumPy().flatten())
84+
extended_scale = np.diag(np.concatenate((scale, [1.0])))
85+
world_H_geometry_scaled = np.array(world_H_geometry).dot(extended_scale)
86+
87+
# Update viewer configuration.
88+
self.viewer[viewer_name].set_transform(world_H_geometry_scaled)
89+
90+
def __model_exists(self, model_name):
91+
92+
if model_name in self.model.keys():
93+
return True
94+
95+
if model_name in self.traversal.keys():
96+
return True
97+
98+
if model_name in self.link_pos.keys():
99+
return True
100+
101+
return False
102+
103+
def __add_model_geometry_to_viewer(self, model, model_geometry: idyn.ModelSolidShapes,
104+
model_name, color):
105+
import meshcat
106+
107+
if not self.__model_exists(model_name):
108+
msg = "The model named: " + model_name + " does not exist."
109+
warnings.warn(msg, category=UserWarning, stacklevel=2)
110+
return
111+
112+
# Solve forward kinematics
113+
joint_pos = idyn.VectorDynSize(self.model[model_name].getNrOfJoints())
114+
joint_pos.zero()
115+
idyn.ForwardPositionKinematics(self.model[model_name], self.traversal[model_name],
116+
idyn.Transform.Identity(), joint_pos,
117+
self.link_pos[model_name])
118+
119+
link_solid_shapes = model_geometry.getLinkSolidShapes()
120+
121+
for link_index in range(0, self.model[model_name].getNrOfLinks()):
122+
123+
world_H_frame = self.link_pos[model_name](link_index)
124+
link_name = self.model[model_name].getLinkName(link_index)
125+
126+
is_mesh = False
127+
for geom in range(0, len(link_solid_shapes[link_index])):
128+
solid_shape = model_geometry.getLinkSolidShapes()[link_index][geom]
129+
if self.__is_mesh(solid_shape):
130+
obj = self.__load_mesh(solid_shape)
131+
is_mesh = True
132+
else:
133+
134+
msg = "The geometry object named \"" \
135+
+ solid_shape.getName() \
136+
+ "\" is not supported by iDynTree/MeshCat for visualization."
137+
warnings.warn(msg, category=UserWarning, stacklevel=2)
138+
continue
139+
140+
if obj is None:
141+
msg = "The geometry object named " + solid_shape.asExternalMesh().getName() + " is not valid."
142+
warnings.warn(msg, category=UserWarning, stacklevel=2)
143+
continue
144+
145+
viewer_name = model_name + "/" + link_name + "/" + solid_shape.asExternalMesh().getName()
146+
147+
if isinstance(obj, meshcat.geometry.Object):
148+
self.viewer[viewer_name].set_object(obj)
149+
elif isinstance(obj, meshcat.geometry.Geometry):
150+
material = meshcat.geometry.MeshPhongMaterial()
151+
# Set material color from URDF, converting for triplet of doubles to a single int.
152+
if color is None:
153+
mesh_color = solid_shape.getMaterial().color()
154+
else:
155+
mesh_color = color
156+
157+
material.color = int(mesh_color[0] * 255) * 256 ** 2 + \
158+
int(mesh_color[1] * 255) * 256 + \
159+
int(mesh_color[2] * 255)
160+
161+
# Add transparency, if needed.
162+
if float(mesh_color[3]) != 1.0:
163+
material.transparent = True
164+
material.opacity = float(mesh_color[3])
165+
166+
self.viewer[viewer_name].set_object(obj, material)
167+
168+
if is_mesh:
169+
self.__apply_transform(world_H_frame, solid_shape, viewer_name)
170+
171+
def display(self, base_position, base_rotation, joint_value, model_name='iDynTree'):
172+
"""Display the robot at given configuration."""
173+
174+
if not self.__model_exists(model_name):
175+
msg = "The model named: " + model_name + " does not exist."
176+
warnings.warn(msg, category=UserWarning, stacklevel=2)
177+
return
178+
179+
base_rotation_idyn = idyn.Rotation()
180+
base_position_idyn = idyn.Position()
181+
base_pose_idyn = idyn.Transform()
182+
183+
for i in range(0, 3):
184+
base_position_idyn.setVal(i, base_position[i])
185+
for j in range(0, 3):
186+
base_rotation_idyn.setVal(i, j, base_rotation[i, j])
187+
188+
base_pose_idyn.setRotation(base_rotation_idyn)
189+
base_pose_idyn.setPosition(base_position_idyn)
190+
191+
if len(joint_value) != self.model[model_name].getNrOfJoints():
192+
msg = "The size of the joint_values is different from the model DoFs"
193+
warnings.warn(msg, category=UserWarning, stacklevel=2)
194+
return
195+
196+
joint_pos_idyn = idyn.VectorDynSize(self.model[model_name].getNrOfJoints())
197+
for i in range(0, self.model[model_name].getNrOfJoints()):
198+
joint_pos_idyn.setVal(i, joint_value[i])
199+
200+
# Solve forward kinematics
201+
idyn.ForwardPositionKinematics(self.model[model_name], self.traversal[model_name], base_pose_idyn,
202+
joint_pos_idyn, self.link_pos[model_name])
203+
204+
# Update the visual shapes
205+
model_geometry = self.model[model_name].visualSolidShapes()
206+
link_solid_shapes = model_geometry.getLinkSolidShapes()
207+
208+
for link_index in range(0, self.model[model_name].getNrOfLinks()):
209+
210+
link_name = self.model[model_name].getLinkName(link_index)
211+
for geom in range(0, len(link_solid_shapes[link_index])):
212+
solid_shape = model_geometry.getLinkSolidShapes()[link_index][geom]
213+
if self.__is_mesh(solid_shape):
214+
viewer_name = model_name + "/" + link_name + "/" + solid_shape.asExternalMesh().getName()
215+
self.__apply_transform(self.link_pos[model_name](link_index), solid_shape, viewer_name)
216+
217+
def open(self):
218+
self.viewer.open()
219+
220+
def jupyter_cell(self):
221+
return self.viewer.jupyter_cell()
222+
223+
def set_model_from_file(self, model_path: str, considered_joints=None, model_name='iDynTree'):
224+
225+
if self.__model_exists(model_name):
226+
msg = "The model named: " + model_name + " already exists."
227+
warnings.warn(msg, category=UserWarning, stacklevel=2)
228+
return
229+
230+
231+
model_loader = idyn.ModelLoader()
232+
if considered_joints is None:
233+
ok = model_loader.loadModelFromFile(model_path)
234+
else:
235+
considered_joints_idyn = idyn.StringVector()
236+
for joint in considered_joints:
237+
considered_joints_idyn.push_back(joint)
238+
239+
ok = model_loader.loadReducedModelFromFile(model_path, considered_joints_idyn)
240+
241+
if not ok:
242+
msg = "Unable to load the model named: " + model_name + " from the file: " + model_path + "."
243+
warnings.warn(msg, category=UserWarning, stacklevel=2)
244+
return
245+
246+
self.model[model_name] = model_loader.model().copy()
247+
self.traversal[model_name] = idyn.Traversal()
248+
self.link_pos[model_name] = idyn.LinkPositions()
249+
250+
self.model[model_name].computeFullTreeTraversal(self.traversal[model_name])
251+
self.link_pos[model_name].resize(self.model[model_name])
252+
253+
def set_model(self, model: idyn.Model, model_name='iDynTree'):
254+
255+
if self.__model_exists(model_name):
256+
msg = "The model named: " + model_name + " already exists."
257+
warnings.warn(msg, category=UserWarning, stacklevel=2)
258+
return
259+
260+
self.model[model_name] = model.copy()
261+
self.traversal[model_name] = idyn.Traversal()
262+
self.link_pos[model_name] = idyn.LinkPositions()
263+
264+
self.model[model_name].computeFullTreeTraversal(self.traversal[model_name])
265+
self.link_pos[model_name].resize(self.model[model_name])
266+
267+
def load_model(self, model_name='iDynTree', color=None):
268+
self.__add_model_geometry_to_viewer(self.model,
269+
self.model[model_name].visualSolidShapes(),
270+
model_name,
271+
color)

0 commit comments

Comments
 (0)