2 from __future__
import annotations
5 from pathlib
import Path
6 from typing
import ClassVar
10 from ..
import pinocchio_pywrap_default
as pin
11 from ..deprecation
import DeprecatedWarning
12 from ..utils
import npToTuple
13 from .
import BaseVisualizer
17 import meshcat.geometry
as mg
19 import_meshcat_succeed =
False
21 import_meshcat_succeed =
True
26 import xml.etree.ElementTree
as Et
27 from typing
import Any
30 MsgType =
"dict[str, Union[str, bytes, bool, float, 'MsgType']]"
35 WITH_HPP_FCL_BINDINGS =
True
37 WITH_HPP_FCL_BINDINGS =
False
39 DEFAULT_COLOR_PROFILES = {
40 "gray": ([0.98, 0.98, 0.98], [0.8, 0.8, 0.8]),
41 "white": ([1.0, 1.0, 1.0], [1.0, 1.0, 1.0]),
43 COLOR_PRESETS = DEFAULT_COLOR_PROFILES.copy()
45 FRAME_AXIS_POSITIONS = (
46 np.array([[0, 0, 0], [1, 0, 0], [0, 0, 0], [0, 1, 0], [0, 0, 0], [0, 0, 1]])
51 np.array([[1, 0, 0], [1, 0.6, 0], [0, 1, 0], [0.6, 1, 0], [0, 0, 1], [0, 0.6, 1]])
58 assert color
is not None
59 color = np.asarray(color)
60 assert color.shape == (3,)
61 return color.clip(0.0, 1.0)
65 """Check whether the geometry object contains a Mesh supported by MeshCat"""
66 if geometry_object.meshPath ==
"":
69 file_extension = Path(geometry_object.meshPath).suffix
70 if file_extension.lower()
in [
".dae",
".obj",
".stl"]:
77 homogeneous_transform: np.ndarray, scale: np.ndarray
79 assert homogeneous_transform.shape == (4, 4)
80 assert scale.size == 3
81 scale = np.array(scale).flatten()
82 S = np.diag(np.concatenate((scale, [1.0])))
83 return homogeneous_transform @ S
86 if import_meshcat_succeed:
89 """A cone of the given height and radius. By Three.js convention, the axis
90 of rotational symmetry is aligned with the y-axis.
97 radialSegments: float = 32,
98 openEnded: bool =
False,
106 def lower(self, object_data: Any) -> MsgType:
109 "type":
"ConeGeometry",
117 def __init__(self, dae_path: str, cache: set[str] |
None =
None) ->
None:
118 """Load Collada files with texture images.
120 https://gist.github.com/danzimmerman/a392f8eadcf1166eb5bd80e3922dbdc5
125 dae_path = Path(dae_path)
133 dae_dir = dae_path.parent
134 with dae_path.open()
as text_file:
138 img_resource_paths: list[Path] = []
139 img_lib_element = Et.parse(dae_path).find(
140 "{http://www.collada.org/2005/11/COLLADASchema}library_images"
143 img_resource_paths = [
145 for e
in img_lib_element.iter()
146 if e.tag.count(
"init_from")
150 self.img_resources: dict[str, str] = {}
151 for img_path
in img_resource_paths:
152 img_key = str(img_path)
154 if cache
is not None:
155 if img_path
in cache:
156 self.img_resources[img_key] =
""
161 img_path_abs: Path = img_path
162 if not img_path.is_absolute():
163 img_path_abs = (dae_dir / img_path_abs).resolve()
164 if not img_path_abs.is_file():
165 raise UserWarning(f
"Texture '{img_path}' not found.")
166 with img_path_abs.open(
"rb")
as img_file:
167 img_data = base64.b64encode(img_file.read())
168 img_uri = f
"data:image/png;base64,{img_data.decode('utf-8')}"
169 self.img_resources[img_key] = img_uri
172 """Pack data into a dictionary of the format that must be passed to
173 `Visualizer.window.send`.
176 "type":
"set_object",
179 "metadata": {
"version": 4.5,
"type":
"Object"},
184 "type":
"_meshfile_object",
187 "resources": self.img_resources,
202 """A plane of the given width and height."""
208 widthSegments: float = 1,
209 heightSegments: float = 1,
217 def lower(self, object_data: Any) -> MsgType:
220 "type":
"PlaneGeometry",
229 WITH_HPP_FCL_BINDINGS
230 and tuple(map(int, hppfcl.__version__.split(
"."))) >= (3, 0, 0)
231 and hppfcl.WITH_OCTOMAP
235 boxes = octree.toBoxes()
239 bs = boxes[0][3] / 2.0
240 num_boxes = len(boxes)
242 box_corners = np.array(
255 all_points = np.empty((8 * num_boxes, 3))
256 all_faces = np.empty((12 * num_boxes, 3), dtype=int)
258 for box_id, box_properties
in enumerate(boxes):
259 box_center = box_properties[:3]
261 corners = box_corners + box_center
262 point_range = range(box_id * 8, (box_id + 1) * 8)
263 all_points[point_range, :] = corners
274 all_faces[face_id] = np.array([C, D, B])
275 all_faces[face_id + 1] = np.array([B, A, C])
276 all_faces[face_id + 2] = np.array([A, B, F])
277 all_faces[face_id + 3] = np.array([F, E, A])
278 all_faces[face_id + 4] = np.array([E, F, H])
279 all_faces[face_id + 5] = np.array([H, G, E])
280 all_faces[face_id + 6] = np.array([G, H, D])
281 all_faces[face_id + 7] = np.array([D, C, G])
283 all_faces[face_id + 8] = np.array([A, E, G])
284 all_faces[face_id + 9] = np.array([G, C, A])
286 all_faces[face_id + 10] = np.array([B, H, F])
287 all_faces[face_id + 11] = np.array([H, B, D])
291 colors = np.empty((all_points.shape[0], 3))
292 colors[:] = np.ones(3)
293 mesh = mg.TriangularMeshGeometry(all_points, all_faces, colors)
299 raise NotImplementedError(
"loadOctree need hppfcl with octomap support")
302 if WITH_HPP_FCL_BINDINGS:
305 if isinstance(mesh, (hppfcl.HeightFieldOBBRSS, hppfcl.HeightFieldAABB)):
306 heights = mesh.getHeights()
307 x_grid = mesh.getXGrid()
308 y_grid = mesh.getYGrid()
309 min_height = mesh.getMinHeight()
311 X, Y = np.meshgrid(x_grid, y_grid)
316 num_cells = (nx) * (ny) * 2 + (nx + ny) * 4 + 2
318 num_vertices = X.size
321 faces = np.empty((num_tris, 3), dtype=int)
322 vertices = np.vstack(
326 X.reshape(num_vertices),
327 Y.reshape(num_vertices),
328 heights.reshape(num_vertices),
334 X.reshape(num_vertices),
335 Y.reshape(num_vertices),
336 np.full(num_vertices, min_height),
344 for y_id
in range(ny):
345 for x_id
in range(nx):
346 p0 = x_id + y_id * (nx + 1)
351 faces[face_id] = np.array([p0, p3, p1])
353 faces[face_id] = np.array([p3, p2, p1])
357 p0_low = p0 + num_vertices
358 p1_low = p1 + num_vertices
360 faces[face_id] = np.array([p0, p1_low, p0_low])
362 faces[face_id] = np.array([p0, p1, p1_low])
366 p2_low = p2 + num_vertices
367 p3_low = p3 + num_vertices
369 faces[face_id] = np.array([p3, p3_low, p2_low])
371 faces[face_id] = np.array([p3, p2_low, p2])
375 p0_low = p0 + num_vertices
376 p3_low = p3 + num_vertices
378 faces[face_id] = np.array([p0, p3_low, p3])
380 faces[face_id] = np.array([p0, p0_low, p3_low])
384 p1_low = p1 + num_vertices
385 p2_low = p2 + num_vertices
387 faces[face_id] = np.array([p1, p2_low, p2])
389 faces[face_id] = np.array([p1, p1_low, p2_low])
395 p2 = 2 * num_vertices - 1
398 faces[face_id] = np.array([p0, p1, p2])
400 faces[face_id] = np.array([p0, p2, p3])
403 elif isinstance(mesh, (hppfcl.Convex, hppfcl.BVHModelBase)):
404 if isinstance(mesh, hppfcl.BVHModelBase):
405 num_vertices = mesh.num_vertices
406 num_tris = mesh.num_tris
408 call_triangles = mesh.tri_indices
409 call_vertices = mesh.vertices
411 elif isinstance(mesh, hppfcl.Convex):
412 num_vertices = mesh.num_points
413 num_tris = mesh.num_polygons
415 call_triangles = mesh.polygons
416 call_vertices = mesh.points
418 faces = np.empty((num_tris, 3), dtype=int)
419 for k
in range(num_tris):
420 tri = call_triangles(k)
421 faces[k] = [tri[i]
for i
in range(3)]
423 vertices = call_vertices()
424 vertices = vertices.astype(np.float32)
427 mesh = mg.TriangularMeshGeometry(vertices, faces)
431 vertices.T, color=np.repeat(np.ones((3, 1)), num_vertices, axis=1)
433 mg.PointsMaterial(size=0.002),
441 raise NotImplementedError(
"loadMesh need hppfcl")
445 import meshcat.geometry
as mg
450 [1.0, 0.0, 0.0, 0.0],
451 [0.0, 0.0, -1.0, 0.0],
452 [0.0, 1.0, 0.0, 0.0],
453 [0.0, 0.0, 0.0, 1.0],
456 RotatedCylinder =
type(
457 "RotatedCylinder", (mg.Cylinder,), {
"intrinsic_transform":
lambda self: R}
460 geom = geometry_object.geometry
462 if WITH_HPP_FCL_BINDINGS
and isinstance(geom, hppfcl.ShapeBase):
463 if isinstance(geom, hppfcl.Capsule):
464 if hasattr(mg,
"TriangularMeshGeometry"):
467 obj = RotatedCylinder(2.0 * geom.halfLength, geom.radius)
468 elif isinstance(geom, hppfcl.Cylinder):
469 obj = RotatedCylinder(2.0 * geom.halfLength, geom.radius)
470 elif isinstance(geom, hppfcl.Cone):
471 obj = RotatedCylinder(2.0 * geom.halfLength, 0, geom.radius, 0)
472 elif isinstance(geom, hppfcl.Box):
473 obj = mg.Box(
npToTuple(2.0 * geom.halfSide))
474 elif isinstance(geom, hppfcl.Sphere):
475 obj = mg.Sphere(geom.radius)
476 elif isinstance(geom, hppfcl.ConvexBase):
480 msg = f
"Unsupported geometry type for {geometry_object.name} ({type(geom)})"
481 warnings.warn(msg, category=UserWarning, stacklevel=2)
487 nbv = np.array([
max(radial_resolution, 4),
max(cap_resolution, 4)])
491 vertices = np.zeros((nbv[0] * (2 * nbv[1]) + 2, 3))
492 for j
in range(nbv[0]):
493 phi = (2 * np.pi * j) / nbv[0]
494 for i
in range(nbv[1]):
495 theta = (np.pi / 2 * i) / nbv[1]
496 vertices[position + i, :] = np.array(
498 np.cos(theta) * np.cos(phi) * r,
499 np.cos(theta) * np.sin(phi) * r,
500 -h / 2 - np.sin(theta) * r,
503 vertices[position + i + nbv[1], :] = np.array(
505 np.cos(theta) * np.cos(phi) * r,
506 np.cos(theta) * np.sin(phi) * r,
507 h / 2 + np.sin(theta) * r,
510 position += nbv[1] * 2
511 vertices[-2, :] = np.array([0, 0, -h / 2 - r])
512 vertices[-1, :] = np.array([0, 0, h / 2 + r])
513 indexes = np.zeros((nbv[0] * (4 * (nbv[1] - 1) + 4), 3))
516 last = nbv[0] * (2 * nbv[1]) + 1
517 for j
in range(nbv[0]):
518 j_next = (j + 1) % nbv[0]
519 indexes[index + 0] = np.array(
521 j_next * stride + nbv[1],
526 indexes[index + 1] = np.array(
529 j_next * stride + nbv[1],
533 indexes[index + 2] = np.array(
535 j * stride + nbv[1] - 1,
536 j_next * stride + nbv[1] - 1,
540 indexes[index + 3] = np.array(
542 j_next * stride + 2 * nbv[1] - 1,
543 j * stride + 2 * nbv[1] - 1,
547 for i
in range(nbv[1] - 1):
548 indexes[index + 4 + i * 4 + 0] = np.array(
551 j_next * stride + i + 1,
555 indexes[index + 4 + i * 4 + 1] = np.array(
557 j_next * stride + i + 1,
562 indexes[index + 4 + i * 4 + 2] = np.array(
564 j_next * stride + nbv[1] + i + 1,
565 j_next * stride + nbv[1] + i,
566 j * stride + nbv[1] + i,
569 indexes[index + 4 + i * 4 + 3] = np.array(
571 j_next * stride + nbv[1] + i + 1,
572 j * stride + nbv[1] + i,
573 j * stride + nbv[1] + i + 1,
576 index += 4 * (nbv[1] - 1) + 4
577 return mg.TriangularMeshGeometry(vertices, indexes)
581 """A Pinocchio display using Meshcat"""
584 FRAME_VEL_COLOR = 0x00FF00
585 CAMERA_PRESETS: ClassVar = {
590 "preset1": [np.zeros(3), [1.0, 1.0, 1.0]],
591 "preset2": [[0.0, 0.0, 0.6], [0.8, 1.0, 1.2]],
592 "acrobot": [[0.0, 0.1, 0.0], [0.5, 0.0, 0.2]],
593 "cam_ur": [[0.4, 0.6, -0.2], [1.0, 0.4, 1.2]],
594 "cam_ur2": [[0.4, 0.3, 0.0], [0.5, 0.1, 1.4]],
595 "cam_ur3": [[0.4, 0.3, 0.0], [0.6, 1.3, 0.3]],
596 "cam_ur4": [[-1.0, 0.3, 0.0], [1.3, 0.1, 1.2]],
597 "cam_ur5": [[-1.0, 0.3, 0.0], [-0.05, 1.5, 1.2]],
598 "talos": [[0.0, 1.2, 0.0], [1.5, 0.3, 1.5]],
599 "talos2": [[0.0, 1.1, 0.0], [1.2, 0.6, 1.5]],
605 collision_model=
None,
612 if not import_meshcat_succeed:
614 "Error while importing the viewer client.\n"
615 "Check whether meshcat is properly installed "
616 "(pip install --user meshcat)."
618 raise ImportError(msg)
632 """Return the name of the geometry object inside the viewer."""
633 if geometry_type
is pin.GeometryType.VISUAL:
635 elif geometry_type
is pin.GeometryType.COLLISION:
638 def initViewer(self, viewer=None, open=False, loadModel=False, zmq_url=None):
639 """Start a new MeshCat server and client.
640 Note: the server can also be started separately using the "meshcat-server"
641 command in a terminal:
642 this enables the server to remain active after the current script ends.
645 self.
viewer = meshcat.Visualizer(zmq_url)
if viewer
is None else viewer
672 """Set the background."""
673 if col_top
is not None:
677 assert preset_name
in COLOR_PRESETS.keys()
678 col_top, col_bot = COLOR_PRESETS[preset_name]
683 self.
viewer.set_cam_target(target)
686 self.
viewer.set_cam_pos(position)
689 """Set the camera angle and position using a given preset."""
690 assert preset_key
in self.CAMERA_PRESETS
691 cam_val = self.CAMERA_PRESETS[preset_key]
697 elt.set_property(
"zoom", zoom)
709 import meshcat.geometry
as mg
712 basic_three_js_transform = np.array(
714 [1.0, 0.0, 0.0, 0.0],
715 [0.0, 0.0, -1.0, 0.0],
716 [0.0, 1.0, 0.0, 0.0],
717 [0.0, 0.0, 0.0, 1.0],
720 RotatedCylinder =
type(
723 {
"intrinsic_transform":
lambda self: basic_three_js_transform},
728 geom = geometry_object.geometry
730 if WITH_HPP_FCL_BINDINGS
and isinstance(geom, hppfcl.ShapeBase):
731 if isinstance(geom, hppfcl.Capsule):
732 if hasattr(mg,
"TriangularMeshGeometry"):
735 obj = RotatedCylinder(2.0 * geom.halfLength, geom.radius)
736 elif isinstance(geom, hppfcl.Cylinder):
737 obj = RotatedCylinder(2.0 * geom.halfLength, geom.radius)
738 elif isinstance(geom, hppfcl.Cone):
739 obj = RotatedCylinder(2.0 * geom.halfLength, 0, geom.radius, 0)
740 elif isinstance(geom, hppfcl.Box):
741 obj = mg.Box(
npToTuple(2.0 * geom.halfSide))
742 elif isinstance(geom, hppfcl.Sphere):
743 obj = mg.Sphere(geom.radius)
744 elif isinstance(geom, hppfcl.Plane):
746 To[:3, 3] = geom.d * geom.n
747 TranslatedPlane =
type(
750 {
"intrinsic_transform":
lambda self: To},
752 sx = geometry_object.meshScale[0] * 10
753 sy = geometry_object.meshScale[1] * 10
754 obj = TranslatedPlane(sx, sy)
755 elif isinstance(geom, hppfcl.Ellipsoid):
756 obj = mg.Ellipsoid(geom.radii)
757 elif isinstance(geom, (hppfcl.Plane, hppfcl.Halfspace)):
758 plane_transform: pin.SE3 = pin.SE3.Identity()
760 plane_transform.rotation = pin.Quaternion.FromTwoVectors(
763 TransformedPlane =
type(
766 {
"intrinsic_transform":
lambda self: plane_transform.homogeneous},
768 obj = TransformedPlane(1000, 1000)
769 elif isinstance(geom, hppfcl.ConvexBase):
773 msg = f
"Unsupported geometry type for {geometry_object.name} ({type(geom)})"
774 warnings.warn(msg, category=UserWarning, stacklevel=2)
781 if geometry_object.meshPath ==
"":
783 "Display of geometric primitives is supported only if "
784 "pinocchio is build with HPP-FCL bindings."
786 warnings.warn(msg, category=UserWarning, stacklevel=2)
790 file_extension = Path(geometry_object.meshPath).suffix
791 if file_extension.lower() ==
".dae":
793 elif file_extension.lower() ==
".obj":
794 obj = mg.ObjMeshGeometry.from_file(geometry_object.meshPath)
795 elif file_extension.lower() ==
".stl":
796 obj = mg.StlMeshGeometry.from_file(geometry_object.meshPath)
798 msg = f
"Unknown mesh file format: {geometry_object.meshPath}."
799 warnings.warn(msg, category=UserWarning, stacklevel=2)
805 """Load a single geometry object"""
807 meshcat_node = self.
viewer[node_name]
811 if WITH_HPP_FCL_BINDINGS:
812 if isinstance(geometry_object.geometry, hppfcl.ShapeBase):
815 tuple(map(int, hppfcl.__version__.split(
"."))) >= (3, 0, 0)
816 and hppfcl.WITH_OCTOMAP
817 and isinstance(geometry_object.geometry, hppfcl.OcTree)
823 geometry_object.geometry,
826 hppfcl.HeightFieldOBBRSS,
827 hppfcl.HeightFieldAABB,
830 obj =
loadMesh(geometry_object.geometry)
835 "The geometry object named "
836 + geometry_object.name
837 +
" is not supported by Pinocchio/MeshCat for vizualization."
839 warnings.warn(msg, category=UserWarning, stacklevel=2)
841 except Exception
as e:
843 "Error while loading geometry object: "
844 f
"{geometry_object.name}\nError message:\n{e}"
846 warnings.warn(msg, category=UserWarning, stacklevel=2)
849 if isinstance(obj, mg.Object):
850 meshcat_node.set_object(obj)
851 elif isinstance(obj, (mg.Geometry, mg.ReferenceSceneElement)):
852 material = mg.MeshPhongMaterial()
856 def to_material_color(rgba) -> int:
857 """Convert rgba color as list into rgba color as int"""
859 int(rgba[0] * 255) * 256**2
860 + int(rgba[1] * 255) * 256
865 meshColor = geometry_object.meshColor
869 material.color = to_material_color(meshColor)
871 if float(meshColor[3]) != 1.0:
872 material.transparent =
True
873 material.opacity = float(meshColor[3])
875 geom_material = geometry_object.meshMaterial
876 if geometry_object.overrideMaterial
and isinstance(
879 material.emissive = to_material_color(geom_material.meshEmissionColor)
880 material.specular = to_material_color(geom_material.meshSpecularColor)
881 material.shininess = geom_material.meshShininess * 100.0
883 if isinstance(obj, DaeMeshGeometry):
884 obj.path = meshcat_node.path
885 if geometry_object.overrideMaterial:
886 obj.material = material
887 meshcat_node.window.send(obj)
889 meshcat_node.set_object(obj, material)
893 rootNodeName="pinocchio",
895 collision_color=None,
898 """Load the robot in a MeshCat viewer.
900 rootNodeName: name to give to the robot in the viewer
901 color: deprecated and optional, color to give to both the collision
902 and visual models of the robot. This setting overwrites any color
903 specified in the robot description. Format is a list of four
904 RGBA floating-point numbers (between 0 and 1)
905 collision_color: optional, color to give to the collision model of
906 the robot. Format is a list of four RGBA floating-point numbers
908 visual_color: optional, color to give to the visual model of
909 the robot. Format is a list of four RGBA floating-point numbers
912 if color
is not None:
914 "The 'color' argument is deprecated and will be removed in a "
915 "future version of Pinocchio. Consider using "
916 "'collision_color' and 'visual_color' instead.",
917 category=DeprecatedWarning,
919 collision_color = color
928 if self.collision_model
is not None:
929 for collision
in self.collision_model.geometryObjects:
931 collision, pin.GeometryType.COLLISION, collision_color
937 if self.visual_model
is not None:
938 for visual
in self.visual_model.geometryObjects:
940 visual, pin.GeometryType.VISUAL, visual_color
948 def reload(self, new_geometry_object, geometry_type=None):
949 """Reload a geometry_object given by its name and its type"""
950 if geometry_type == pin.GeometryType.VISUAL:
951 geom_model = self.visual_model
953 geom_model = self.collision_model
954 geometry_type = pin.GeometryType.COLLISION
956 geom_id = geom_model.getGeometryId(new_geometry_object.name)
957 geom_model.geometryObjects[geom_id] = new_geometry_object
959 self.
delete(new_geometry_object, geometry_type)
960 visual = geom_model.geometryObjects[geom_id]
966 def delete(self, geometry_object, geometry_type):
972 Display the robot at configuration q in the viewer by placing all the bodies
987 if geometry_type == pin.GeometryType.VISUAL:
988 geom_model = self.visual_model
989 geom_data = self.visual_data
991 geom_model = self.collision_model
992 geom_data = self.collision_data
995 for visual
in geom_model.geometryObjects:
998 M = geom_data.oMg[geom_model.getGeometryId(visual.name)]
1001 geom = visual.geometry
1002 if WITH_HPP_FCL_BINDINGS
and isinstance(
1003 geom, (hppfcl.Plane, hppfcl.Halfspace)
1006 T.translation += M.rotation @ (geom.d * geom.n)
1013 self.
viewer[visual_name].set_transform(T)
1017 M: pin.SE3 = visual.placement
1020 self.
viewer[visual_name].set_transform(T)
1023 """Add a visual GeometryObject to the viewer, with an optional color."""
1028 if not hasattr(self.
viewer,
"get_image"):
1030 "meshcat.Visualizer does not have the get_image() method."
1031 " You need meshcat >= 0.2.0 to get this feature."
1035 """Capture an image from the Meshcat viewer and return an RGB array."""
1036 if w
is not None or h
is not None:
1038 img = self.
viewer.get_image(w, h)
1040 img = self.
viewer.get_image()
1041 img_arr = np.asarray(img)
1045 """Set whether to display collision objects or not."""
1046 if self.collision_model
is None:
1056 """Set whether to display visual objects or not."""
1057 if self.visual_model
is None:
1066 def displayFrames(self, visibility, frame_ids=None, axis_length=0.2, axis_width=2):
1067 """Set whether to display frames or not."""
1074 """Initializes the frame objects for display."""
1075 import meshcat.geometry
as mg
1080 for fid, frame
in enumerate(self.model.frames):
1081 if frame_ids
is None or fid
in frame_ids:
1082 frame_viz_name = f
"{self.viewerFramesGroupName}/{frame.name}"
1083 self.
viewer[frame_viz_name].set_object(
1086 position=axis_length * FRAME_AXIS_POSITIONS,
1087 color=FRAME_AXIS_COLORS,
1089 mg.LineBasicMaterial(
1090 linewidth=axis_width,
1099 Updates the frame visualizations with the latest transforms from model data.
1103 frame_name = self.model.frames[fid].name
1104 frame_viz_name = f
"{self.viewerFramesGroupName}/{frame_name}"
1105 self.
viewer[frame_viz_name].set_transform(self.data.oMf[fid].homogeneous)
1110 self.model, self.data, frame_id, pin.LOCAL_WORLD_ALIGNED
1112 line_group_name = f
"ee_vel/{frame_id}"
1114 [v_scale * vFr.linear], [frame_id], [line_group_name], [color]
1119 vecs: list[np.ndarray],
1120 frame_ids: list[int],
1121 vec_names: list[str],
1124 """Draw vectors extending from given frames."""
1125 import meshcat.geometry
as mg
1127 if len(vecs) != len(frame_ids)
or len(vecs) != len(vec_names):
1129 "Number of vectors and frames IDs or names is inconsistent."
1131 for i, (fid, v)
in enumerate(zip(frame_ids, vecs)):
1132 frame_pos = self.data.oMf[fid].translation
1133 vertices = np.array([frame_pos, frame_pos + v]).astype(np.float32).T
1135 geometry = mg.PointsGeometry(position=vertices)
1136 geom_object = mg.LineSegments(
1137 geometry, mg.LineBasicMaterial(color=colors[i])
1140 self.
viewer[prefix].set_object(geom_object)
1143 __all__ = [
"MeshcatVisualizer"]