meshcat_visualizer.py
Go to the documentation of this file.
1 from .. import pinocchio_pywrap as pin
2 from ..utils import npToTuple
3 
4 from . import BaseVisualizer
5 
6 import os
7 import warnings
8 import numpy as np
9 from distutils.version import LooseVersion
10 
11 try:
12  import hppfcl
13  WITH_HPP_FCL_BINDINGS = True
14 except ImportError:
15  WITH_HPP_FCL_BINDINGS = False
16 
17 DEFAULT_COLOR_PROFILES = {
18  "gray": ([0.98, 0.98, 0.98], [0.8, 0.8, 0.8]),
19  "white": (np.ones(3),),
20 }
21 COLOR_PRESETS = DEFAULT_COLOR_PROFILES.copy()
22 
23 
24 def isMesh(geometry_object):
25  """Check whether the geometry object contains a Mesh supported by MeshCat"""
26  if geometry_object.meshPath == "":
27  return False
28 
29  _, file_extension = os.path.splitext(geometry_object.meshPath)
30  if file_extension.lower() in [".dae", ".obj", ".stl"]:
31  return True
32 
33  return False
34 
35 
36 def loadMesh(mesh):
37  import meshcat.geometry as mg
38 
39  if isinstance(mesh, hppfcl.HeightFieldOBBRSS):
40  heights = mesh.getHeights()
41  x_grid = mesh.getXGrid()
42  y_grid = mesh.getYGrid()
43  min_height = mesh.getMinHeight()
44 
45  X, Y = np.meshgrid(x_grid, y_grid)
46 
47  nx = len(x_grid) - 1
48  ny = len(y_grid) - 1
49 
50  num_cells = (nx) * (ny) * 2 + (nx + ny) * 4 + 2
51 
52  num_vertices = X.size
53  num_tris = num_cells
54 
55  faces = np.empty((num_tris, 3), dtype=int)
56  vertices = np.vstack(
57  (
58  np.stack(
59  (
60  X.reshape(num_vertices),
61  Y.reshape(num_vertices),
62  heights.reshape(num_vertices),
63  ),
64  axis=1,
65  ),
66  np.stack(
67  (
68  X.reshape(num_vertices),
69  Y.reshape(num_vertices),
70  np.full(num_vertices, min_height),
71  ),
72  axis=1,
73  ),
74  )
75  )
76 
77  face_id = 0
78  for y_id in range(ny):
79  for x_id in range(nx):
80  p0 = x_id + y_id * (nx + 1)
81  p1 = p0 + 1
82  p2 = p1 + nx + 1
83  p3 = p2 - 1
84 
85  faces[face_id] = np.array([p0, p3, p1])
86  face_id += 1
87  faces[face_id] = np.array([p3, p2, p1])
88  face_id += 1
89 
90  if y_id == 0:
91  p0_low = p0 + num_vertices
92  p1_low = p1 + num_vertices
93 
94  faces[face_id] = np.array([p0, p1_low, p0_low])
95  face_id += 1
96  faces[face_id] = np.array([p0, p1, p1_low])
97  face_id += 1
98 
99  if y_id == ny - 1:
100  p2_low = p2 + num_vertices
101  p3_low = p3 + num_vertices
102 
103  faces[face_id] = np.array([p3, p3_low, p2_low])
104  face_id += 1
105  faces[face_id] = np.array([p3, p2_low, p2])
106  face_id += 1
107 
108  if x_id == 0:
109  p0_low = p0 + num_vertices
110  p3_low = p3 + num_vertices
111 
112  faces[face_id] = np.array([p0, p3_low, p3])
113  face_id += 1
114  faces[face_id] = np.array([p0, p0_low, p3_low])
115  face_id += 1
116 
117  if x_id == nx - 1:
118  p1_low = p1 + num_vertices
119  p2_low = p2 + num_vertices
120 
121  faces[face_id] = np.array([p1, p2_low, p2])
122  face_id += 1
123  faces[face_id] = np.array([p1, p1_low, p2_low])
124  face_id += 1
125 
126  # Last face
127  p0 = num_vertices
128  p1 = p0 + nx
129  p2 = 2 * num_vertices - 1
130  p3 = p2 - nx
131 
132  faces[face_id] = np.array([p0, p1, p2])
133  face_id += 1
134  faces[face_id] = np.array([p0, p2, p3])
135  face_id += 1
136 
137  elif isinstance(mesh, (hppfcl.Convex, hppfcl.BVHModelBase)):
138  if isinstance(mesh, hppfcl.BVHModelBase):
139  num_vertices = mesh.num_vertices
140  num_tris = mesh.num_tris
141 
142  call_triangles = mesh.tri_indices
143  call_vertices = mesh.vertices
144 
145  elif isinstance(mesh, hppfcl.Convex):
146  num_vertices = mesh.num_points
147  num_tris = mesh.num_polygons
148 
149  call_triangles = mesh.polygons
150  call_vertices = mesh.points
151 
152  faces = np.empty((num_tris, 3), dtype=int)
153  for k in range(num_tris):
154  tri = call_triangles(k)
155  faces[k] = [tri[i] for i in range(3)]
156 
157  if LooseVersion(hppfcl.__version__) >= LooseVersion("1.7.7"):
158  vertices = call_vertices()
159  else:
160  vertices = np.empty((num_vertices, 3))
161  for k in range(num_vertices):
162  vertices[k] = call_vertices(k)
163 
164  vertices = vertices.astype(np.float32)
165 
166  if num_tris > 0:
167  mesh = mg.TriangularMeshGeometry(vertices, faces)
168  else:
169  mesh = mg.Points(
170  mg.PointsGeometry(
171  vertices.T, color=np.repeat(np.ones((3, 1)), num_vertices, axis=1)
172  ),
173  mg.PointsMaterial(size=0.002),
174  )
175 
176  return mesh
177 
178 
179 def loadPrimitive(geometry_object):
180 
181  import meshcat.geometry as mg
182 
183  # Cylinders need to be rotated
184  R = np.array(
185  [
186  [1.0, 0.0, 0.0, 0.0],
187  [0.0, 0.0, -1.0, 0.0],
188  [0.0, 1.0, 0.0, 0.0],
189  [0.0, 0.0, 0.0, 1.0],
190  ]
191  )
192  RotatedCylinder = type(
193  "RotatedCylinder", (mg.Cylinder,), {"intrinsic_transform": lambda self: R}
194  )
195 
196  geom = geometry_object.geometry
197  if isinstance(geom, hppfcl.Capsule):
198  if hasattr(mg, "TriangularMeshGeometry"):
199  obj = createCapsule(2.0 * geom.halfLength, geom.radius)
200  else:
201  obj = RotatedCylinder(2.0 * geom.halfLength, geom.radius)
202  elif isinstance(geom, hppfcl.Cylinder):
203  obj = RotatedCylinder(2.0 * geom.halfLength, geom.radius)
204  elif isinstance(geom, hppfcl.Cone):
205  obj = RotatedCylinder(2.0 * geom.halfLength, 0, geom.radius, 0)
206  elif isinstance(geom, hppfcl.Box):
207  obj = mg.Box(npToTuple(2.0 * geom.halfSide))
208  elif isinstance(geom, hppfcl.Sphere):
209  obj = mg.Sphere(geom.radius)
210  elif isinstance(geom, hppfcl.ConvexBase):
211  obj = loadMesh(geom)
212  else:
213  msg = "Unsupported geometry type for %s (%s)" % (
214  geometry_object.name,
215  type(geom),
216  )
217  warnings.warn(msg, category=UserWarning, stacklevel=2)
218  obj = None
219 
220  return obj
221 
222 
223 def createCapsule(length, radius, radial_resolution=30, cap_resolution=10):
224  nbv = np.array([max(radial_resolution, 4), max(cap_resolution, 4)])
225  h = length
226  r = radius
227  position = 0
228  vertices = np.zeros((nbv[0] * (2 * nbv[1]) + 2, 3))
229  for j in range(nbv[0]):
230  phi = (2 * np.pi * j) / nbv[0]
231  for i in range(nbv[1]):
232  theta = (np.pi / 2 * i) / nbv[1]
233  vertices[position + i, :] = np.array(
234  [
235  np.cos(theta) * np.cos(phi) * r,
236  np.cos(theta) * np.sin(phi) * r,
237  -h / 2 - np.sin(theta) * r,
238  ]
239  )
240  vertices[position + i + nbv[1], :] = np.array(
241  [
242  np.cos(theta) * np.cos(phi) * r,
243  np.cos(theta) * np.sin(phi) * r,
244  h / 2 + np.sin(theta) * r,
245  ]
246  )
247  position += nbv[1] * 2
248  vertices[-2, :] = np.array([0, 0, -h / 2 - r])
249  vertices[-1, :] = np.array([0, 0, h / 2 + r])
250  indexes = np.zeros((nbv[0] * (4 * (nbv[1] - 1) + 4), 3))
251  index = 0
252  stride = nbv[1] * 2
253  last = nbv[0] * (2 * nbv[1]) + 1
254  for j in range(nbv[0]):
255  j_next = (j + 1) % nbv[0]
256  indexes[index + 0] = np.array(
257  [j_next * stride + nbv[1], j_next * stride, j * stride]
258  )
259  indexes[index + 1] = np.array(
260  [j * stride + nbv[1], j_next * stride + nbv[1], j * stride]
261  )
262  indexes[index + 2] = np.array(
263  [j * stride + nbv[1] - 1, j_next * stride + nbv[1] - 1, last - 1]
264  )
265  indexes[index + 3] = np.array(
266  [j_next * stride + 2 * nbv[1] - 1, j * stride + 2 * nbv[1] - 1, last]
267  )
268  for i in range(nbv[1] - 1):
269  indexes[index + 4 + i * 4 + 0] = np.array(
270  [j_next * stride + i, j_next * stride + i + 1, j * stride + i]
271  )
272  indexes[index + 4 + i * 4 + 1] = np.array(
273  [j_next * stride + i + 1, j * stride + i + 1, j * stride + i]
274  )
275  indexes[index + 4 + i * 4 + 2] = np.array(
276  [
277  j_next * stride + nbv[1] + i + 1,
278  j_next * stride + nbv[1] + i,
279  j * stride + nbv[1] + i,
280  ]
281  )
282  indexes[index + 4 + i * 4 + 3] = np.array(
283  [
284  j_next * stride + nbv[1] + i + 1,
285  j * stride + nbv[1] + i,
286  j * stride + nbv[1] + i + 1,
287  ]
288  )
289  index += 4 * (nbv[1] - 1) + 4
290  import meshcat.geometry
291 
292  return meshcat.geometry.TriangularMeshGeometry(vertices, indexes)
293 
294 
295 class MeshcatVisualizer(BaseVisualizer):
296  """A Pinocchio display using Meshcat"""
297 
298  FORCE_SCALE = 0.06
299  FRAME_VEL_COLOR = 0x00FF00
300  CAMERA_PRESETS = {
301  "preset0": [
302  np.zeros(3), # target
303  [3.0, 0.0, 1.0], # anchor point (x, z, -y) lhs coords
304  ],
305  "preset1": [np.zeros(3), [1.0, 1.0, 1.0]],
306  "preset2": [[0.0, 0.0, 0.6], [0.8, 1.0, 1.2]],
307  "acrobot": [[0.0, 0.1, 0.0], [0.5, 0.0, 0.2]],
308  "cam_ur": [[0.4, 0.6, -0.2], [1.0, 0.4, 1.2]],
309  "cam_ur2": [[0.4, 0.3, 0.0], [0.5, 0.1, 1.4]],
310  "cam_ur3": [[0.4, 0.3, 0.0], [0.6, 1.3, 0.3]],
311  "cam_ur4": [[-1.0, 0.3, 0.0], [1.3, 0.1, 1.2]], # x>0 to x<0
312  "cam_ur5": [[-1.0, 0.3, 0.0], [-0.05, 1.5, 1.2]],
313  "talos": [[0.0, 1.2, 0.0], [1.5, 0.3, 1.5]],
314  "talos2": [[0.0, 1.1, 0.0], [1.2, 0.6, 1.5]],
315  }
316 
317  def getViewerNodeName(self, geometry_object, geometry_type):
318  """Return the name of the geometry object inside the viewer."""
319  if geometry_type is pin.GeometryType.VISUAL:
320  return self.viewerVisualGroupName + "/" + geometry_object.name
321  elif geometry_type is pin.GeometryType.COLLISION:
322  return self.viewerCollisionGroupName + "/" + geometry_object.name
323 
324  def initViewer(self, viewer=None, open=False, loadModel=False):
325  """Start a new MeshCat server and client.
326  Note: the server can also be started separately using the "meshcat-server" command in a terminal:
327  this enables the server to remain active after the current script ends.
328  """
329 
330  import meshcat
331 
332  self.viewer = meshcat.Visualizer() if viewer is None else viewer
333 
334  self._node_default_cam = self.viewer["/Cameras/default"]
335  self._node_background = self.viewer["/Background"]
336  self._rot_cam_key = "rotated/object"
337  self.static_objects = []
338 
340 
341  if open:
342  self.viewer.open()
343 
344  if loadModel:
345  self.loadViewerModel()
346 
347  def setBackgroundColor(
348  self, preset_name="gray"
349  ): # pylint: disable=arguments-differ
350  """Set the background."""
351  col_top, col_bot = COLOR_PRESETS[preset_name]
352  self._node_background.set_property("top_color", col_top)
353  self._node_background.set_property("bottom_color", col_bot)
354 
355  def setCameraTarget(self, target):
356  self.viewer.set_cam_target(target)
357 
358  def setCameraPosition(self, position):
359  self.viewer.set_cam_pos(position)
360 
361  def setCameraPreset(self, preset_key):
362  """Set the camera angle and position using a given preset."""
363  cam_val = self.CAMERA_PRESETS[preset_key]
364  self.setCameraTarget(cam_val[0])
365  self.setCameraPosition(cam_val[1])
366 
367  def setCameraZoom(self, zoom):
368  elt = self._node_default_cam[self._rot_cam_key]
369  elt.set_property("zoom", zoom)
370 
371  def setCameraPose(self, pose=np.eye(4)):
372  self._node_default_cam.set_transform(pose)
373 
375  self.setCameraPosition([0, 0, 0])
376 
378  self.setCameraPosition([3, 0, 1])
379 
380  def loadMesh(self, geometry_object):
381 
382  import meshcat.geometry
383 
384  # Mesh path is empty if Pinocchio is built without HPP-FCL bindings
385  if geometry_object.meshPath == "":
386  msg = "Display of geometric primitives is supported only if pinocchio is build with HPP-FCL bindings."
387  warnings.warn(msg, category=UserWarning, stacklevel=2)
388  return None
389 
390  # Get file type from filename extension.
391  _, file_extension = os.path.splitext(geometry_object.meshPath)
392  if file_extension.lower() == ".dae":
393  obj = meshcat.geometry.DaeMeshGeometry.from_file(geometry_object.meshPath)
394  elif file_extension.lower() == ".obj":
395  obj = meshcat.geometry.ObjMeshGeometry.from_file(geometry_object.meshPath)
396  elif file_extension.lower() == ".stl":
397  obj = meshcat.geometry.StlMeshGeometry.from_file(geometry_object.meshPath)
398  else:
399  msg = "Unknown mesh file format: {}.".format(geometry_object.meshPath)
400  warnings.warn(msg, category=UserWarning, stacklevel=2)
401  obj = None
402 
403  return obj
404 
405  def loadViewerGeometryObject(self, geometry_object, geometry_type, color=None):
406  """Load a single geometry object"""
407  import meshcat.geometry
408 
409  viewer_name = self.getViewerNodeName(geometry_object, geometry_type)
410 
411  is_mesh = False
412  try:
413  if WITH_HPP_FCL_BINDINGS and isinstance(
414  geometry_object.geometry, hppfcl.ShapeBase
415  ):
416  obj = loadPrimitive(geometry_object)
417  elif isMesh(geometry_object):
418  obj = self.loadMesh(geometry_object)
419  is_mesh = True
420  elif WITH_HPP_FCL_BINDINGS and isinstance(
421  geometry_object.geometry,
422  (hppfcl.BVHModelBase, hppfcl.HeightFieldOBBRSS),
423  ):
424  obj = loadMesh(geometry_object.geometry)
425  else:
426  msg = (
427  "The geometry object named "
428  + geometry_object.name
429  + " is not supported by Pinocchio/MeshCat for vizualization."
430  )
431  warnings.warn(msg, category=UserWarning, stacklevel=2)
432  return
433  if obj is None:
434  return
435  except Exception as e:
436  msg = "Error while loading geometry object: %s\nError message:\n%s" % (
437  geometry_object.name,
438  e,
439  )
440  warnings.warn(msg, category=UserWarning, stacklevel=2)
441  return
442 
443  if isinstance(obj, meshcat.geometry.Object):
444  self.viewer[viewer_name].set_object(obj)
445  elif isinstance(obj, meshcat.geometry.Geometry):
446  material = meshcat.geometry.MeshPhongMaterial()
447  # Set material color from URDF, converting for triplet of doubles to a single int.
448  if color is None:
449  meshColor = geometry_object.meshColor
450  else:
451  meshColor = color
452  material.color = (
453  int(meshColor[0] * 255) * 256**2
454  + int(meshColor[1] * 255) * 256
455  + int(meshColor[2] * 255)
456  )
457  # Add transparency, if needed.
458  if float(meshColor[3]) != 1.0:
459  material.transparent = True
460  material.opacity = float(meshColor[3])
461  self.viewer[viewer_name].set_object(obj, material)
462 
463  if is_mesh: # Apply the scaling
464  scale = list(np.asarray(geometry_object.meshScale).flatten())
465  self.viewer[viewer_name].set_property("scale", scale)
466 
467  def loadViewerModel(self, rootNodeName="pinocchio", color=None):
468  """Load the robot in a MeshCat viewer.
469  Parameters:
470  rootNodeName: name to give to the robot in the viewer
471  color: optional, color to give to the robot. This overwrites the color present in the urdf.
472  Format is a list of four RGBA floats (between 0 and 1)
473  """
474 
475  # Set viewer to use to gepetto-gui.
476  self.viewerRootNodeName = rootNodeName
477 
478  # Collisions
479  self.viewerCollisionGroupName = self.viewerRootNodeName + "/" + "collisions"
480 
481  for collision in self.collision_model.geometryObjects:
482  self.loadViewerGeometryObject(collision, pin.GeometryType.COLLISION, color)
483  self.displayCollisions(False)
484 
485  # Visuals
486  self.viewerVisualGroupName = self.viewerRootNodeName + "/" + "visuals"
487 
488  for visual in self.visual_model.geometryObjects:
489  self.loadViewerGeometryObject(visual, pin.GeometryType.VISUAL, color)
490  self.displayVisuals(True)
491 
492  def reload(self, new_geometry_object, geometry_type=None):
493  """Reload a geometry_object given by its name and its type"""
494  if geometry_type == pin.GeometryType.VISUAL:
495  geom_model = self.visual_model
496  else:
497  geom_model = self.collision_model
498  geometry_type = pin.GeometryType.COLLISION
499 
500  geom_id = geom_model.getGeometryId(new_geometry_object.name)
501  geom_model.geometryObjects[geom_id] = new_geometry_object
502 
503  self.delete(new_geometry_object, geometry_type)
504  visual = geom_model.geometryObjects[geom_id]
505  self.loadViewerGeometryObject(visual, geometry_type)
506 
507  def clean(self):
508  self.viewer.delete()
509 
510  def delete(self, geometry_object, geometry_type):
511  viewer_name = self.getViewerNodeName(geometry_object, geometry_type)
512  self.viewer[viewer_name].delete()
513 
514  def display(self, q=None):
515  """Display the robot at configuration q in the viewer by placing all the bodies."""
516  if q is not None:
517  pin.forwardKinematics(self.model, self.data, q)
518 
519  if self.display_collisions:
520  self.updatePlacements(pin.GeometryType.COLLISION)
521 
522  if self.display_visuals:
523  self.updatePlacements(pin.GeometryType.VISUAL)
524 
525  def updatePlacements(self, geometry_type):
526  if geometry_type == pin.GeometryType.VISUAL:
527  geom_model = self.visual_model
528  geom_data = self.visual_data
529  else:
530  geom_model = self.collision_model
531  geom_data = self.collision_data
532 
533  pin.updateGeometryPlacements(self.model, self.data, geom_model, geom_data)
534  for visual in geom_model.geometryObjects:
535  visual_name = self.getViewerNodeName(visual, geometry_type)
536  # Get mesh pose.
537  M = geom_data.oMg[geom_model.getGeometryId(visual.name)]
538  # Manage scaling: force scaling even if this should be normally handled by MeshCat (but there is a bug here)
539  if isMesh(visual):
540  scale = np.asarray(visual.meshScale).flatten()
541  S = np.diag(np.concatenate((scale, [1.0])))
542  T = np.array(M.homogeneous).dot(S)
543  else:
544  T = M.homogeneous
545 
546  # Update viewer configuration.
547  self.viewer[visual_name].set_transform(T)
548 
549  for visual in self.static_objects:
550  visual_name = self.getViewerNodeName(visual, pin.GeometryType.VISUAL)
551  M = visual.placement
552  T = M.homogeneous
553  self.viewer[visual_name].set_transform(T)
554 
555  def addGeometryObject(self, obj, color=None):
556  """Add a visual GeometryObject to the viewer, with an optional color."""
557  self.loadViewerGeometryObject(obj, pin.GeometryType.VISUAL, color)
558  self.static_objects.append(obj)
559 
561  if not hasattr(self.viewer, "get_image"):
562  warnings.warn(
563  "meshcat.Visualizer does not have the get_image() method."
564  " You need meshcat >= 0.2.0 to get this feature."
565  )
566 
567  def captureImage(self, w=None, h=None):
568  """Capture an image from the Meshcat viewer and return an RGB array."""
569  if w is not None or h is not None:
570  # pass arguments when either is not None
571  img = self.viewer.get_image(w, h)
572  else:
573  img = self.viewer.get_image()
574  img_arr = np.asarray(img)
575  return img_arr
576 
577  def displayCollisions(self, visibility):
578  """Set whether to display collision objects or not."""
579  if self.collision_model is None:
580  self.display_collisions = False
581  else:
582  self.display_collisions = visibility
583  self.viewer[self.viewerCollisionGroupName].set_property("visible", visibility)
584 
585  if visibility:
586  self.updatePlacements(pin.GeometryType.COLLISION)
587 
588  def displayVisuals(self, visibility):
589  """Set whether to display visual objects or not."""
590  if self.visual_model is None:
591  self.display_visuals = False
592  else:
593  self.display_visuals = visibility
594  self.viewer[self.viewerVisualGroupName].set_property("visible", visibility)
595 
596  if visibility:
597  self.updatePlacements(pin.GeometryType.VISUAL)
598 
600  self, frame_id, v_scale=0.2, color=FRAME_VEL_COLOR
601  ): # pylint: disable=arguments-differ
602  pin.updateFramePlacement(self.model, self.data, frame_id)
603  vFr = pin.getFrameVelocity(
604  self.model, self.data, frame_id, pin.LOCAL_WORLD_ALIGNED
605  )
606  line_group_name = "ee_vel/{}".format(frame_id)
608  [v_scale * vFr.linear], [frame_id], [line_group_name], [color]
609  )
610 
612  self,
613  vecs,
614  frame_ids,
615  vec_names,
616  colors,
617  ):
618  """Draw vectors extending from given frames."""
619  import meshcat.geometry as mg
620 
621  if len(vecs) != len(frame_ids) or len(vecs) != len(vec_names):
622  return ValueError(
623  "Number of vectors and frames IDs or names is inconsistent."
624  )
625  for i, (fid, v) in enumerate(zip(frame_ids, vecs)):
626  frame_pos = self.data.oMf[fid].translation
627  vertices = np.array([frame_pos, frame_pos + v]).astype(np.float32).T
628  name = vec_names[i]
629  geometry = mg.PointsGeometry(position=vertices)
630  geom_object = mg.LineSegments(
631  geometry, mg.LineBasicMaterial(color=colors[i])
632  )
633  prefix = self.viewerVisualGroupName + "/lines/" + name
634  self.viewer[prefix].set_object(geom_object)
635 
636 
637 __all__ = ["MeshcatVisualizer"]
def drawFrameVelocities(self, frame_id, v_scale=0.2, color=FRAME_VEL_COLOR)
result_of::push_front< V const, T >::type append(T const &t, V const &v)
Append the element T at the front of boost fusion vector V.
Definition: fusion.hpp:24
def getViewerNodeName(self, geometry_object, geometry_type)
def reload(self, new_geometry_object, geometry_type=None)
AD< Scalar > max(const AD< Scalar > &x, const AD< Scalar > &y)
def delete(self, geometry_object, geometry_type)
def _draw_vectors_from_frame(self, vecs, frame_ids, vec_names, colors)
def loadViewerGeometryObject(self, geometry_object, geometry_type, color=None)
def createCapsule(length, radius, radial_resolution=30, cap_resolution=10)
def loadViewerModel(self, rootNodeName="pinocchio", color=None)
def initViewer(self, viewer=None, open=False, loadModel=False)


pinocchio
Author(s):
autogenerated on Fri Jun 23 2023 02:38:32