jaxsim 0.2.1.dev17__py3-none-any.whl → 0.2.1.dev38__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
jaxsim/_version.py CHANGED
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.2.1.dev17'
16
- __version_tuple__ = version_tuple = (0, 2, 1, 'dev17')
15
+ __version__ = version = '0.2.1.dev38'
16
+ __version_tuple__ = version_tuple = (0, 2, 1, 'dev38')
jaxsim/api/data.py CHANGED
@@ -417,7 +417,7 @@ class JaxSimModelData(common.ModelDataWithVelocityRepresentation):
417
417
  def generalized_position(self) -> tuple[jtp.Matrix, jtp.Vector]:
418
418
  r"""
419
419
  Get the generalized position
420
- :math:`\\mathbf{q} = ({}^W \\mathbf{H}_B, \\mathbf{s}) \\in \text{SO}(3) \times \\mathbb{R}^n`.
420
+ :math:`\mathbf{q} = ({}^W \mathbf{H}_B, \mathbf{s}) \in \text{SO}(3) \times \mathbb{R}^n`.
421
421
 
422
422
  Returns:
423
423
  A tuple containing the base transform and the joint positions.
@@ -429,7 +429,7 @@ class JaxSimModelData(common.ModelDataWithVelocityRepresentation):
429
429
  def generalized_velocity(self) -> jtp.Vector:
430
430
  r"""
431
431
  Get the generalized velocity
432
- :math:`\boldsymbol{\nu} = (\boldsymbol{v}_{W,B};\\, \boldsymbol{\\omega}_{W,B};\\, \\mathbf{s}) \\in \\mathbb{R}^{6+n}`
432
+ :math:`\boldsymbol{\nu} = (\boldsymbol{v}_{W,B};\, \boldsymbol{\omega}_{W,B};\, \mathbf{s}) \in \mathbb{R}^{6+n}`
433
433
 
434
434
  Returns:
435
435
  The generalized velocity in the active representation.
jaxsim/api/model.py CHANGED
@@ -1,9 +1,10 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import copy
3
4
  import dataclasses
4
5
  import functools
5
6
  import pathlib
6
- from typing import Any
7
+ from typing import Any, Sequence
7
8
 
8
9
  import jax
9
10
  import jax.numpy as jnp
@@ -55,7 +56,7 @@ class JaxSimModel(JaxsimDataclass):
55
56
  *,
56
57
  terrain: jaxsim.terrain.Terrain | None = None,
57
58
  is_urdf: bool | None = None,
58
- considered_joints: list[str] | None = None,
59
+ considered_joints: Sequence[str] | None = None,
59
60
  ) -> JaxSimModel:
60
61
  """
61
62
  Build a Model object from a model description.
@@ -257,24 +258,50 @@ class JaxSimModel(JaxsimDataclass):
257
258
  # =====================
258
259
 
259
260
 
260
- def reduce(model: JaxSimModel, considered_joints: tuple[str, ...]) -> JaxSimModel:
261
+ def reduce(
262
+ model: JaxSimModel,
263
+ considered_joints: tuple[str, ...],
264
+ locked_joint_positions: dict[str, jtp.Float] | None = None,
265
+ ) -> JaxSimModel:
261
266
  """
262
267
  Reduce the model by lumping together the links connected by removed joints.
263
268
 
264
269
  Args:
265
270
  model: The model to reduce.
266
271
  considered_joints: The sequence of joints to consider.
267
-
268
- Note:
269
- If considered_joints contains joints not existing in the model, the method
270
- will raise an exception. If considered_joints is empty, the method will
271
- return a copy of the input model.
272
+ locked_joint_positions:
273
+ A dictionary containing the positions of the joints to be considered
274
+ in the reduction process. The removed joints in the reduced model
275
+ will have their position locked to their value in this dictionary.
276
+ If a joint is not part of the dictionary, its position is set to zero.
272
277
  """
273
278
 
279
+ locked_joint_positions = (
280
+ locked_joint_positions if locked_joint_positions is not None else {}
281
+ )
282
+
283
+ # If locked joints are passed, make sure that they are valid.
284
+ if not set(locked_joint_positions).issubset(model.joint_names()):
285
+ new_joints = set(model.joint_names()) - set(locked_joint_positions)
286
+ raise ValueError(f"Passed joints not existing in the model: {new_joints}")
287
+
288
+ # Copy the model description with a deep copy of the joints.
289
+ intermediate_description = dataclasses.replace(
290
+ model.description.get(), joints=copy.deepcopy(model.description.get().joints)
291
+ )
292
+
293
+ # Update the initial position of the joints.
294
+ # This is necessary to compute the correct pose of the link pairs connected
295
+ # to removed joints.
296
+ for joint_name in set(model.joint_names()) - set(considered_joints):
297
+ j = intermediate_description.joints_dict[joint_name]
298
+ with j.mutable_context():
299
+ j.initial_position = float(locked_joint_positions.get(joint_name, 0.0))
300
+
274
301
  # Reduce the model description.
275
- # If considered_joints contains joints not existing in the model, the method
276
- # will raise an exception.
277
- reduced_intermediate_description = model.description.obj.reduce(
302
+ # If `considered_joints` contains joints not existing in the model,
303
+ # the method will raise an exception.
304
+ reduced_intermediate_description = intermediate_description.reduce(
278
305
  considered_joints=list(considered_joints)
279
306
  )
280
307
 
@@ -15,6 +15,7 @@ from jaxsim.parsers.descriptions import (
15
15
  JointType,
16
16
  ModelDescription,
17
17
  )
18
+ from jaxsim.parsers.kinematic_graph import KinematicGraphTransforms
18
19
 
19
20
  from .rotation import Rotation
20
21
 
@@ -87,21 +88,19 @@ class JointModel:
87
88
  # w.r.t. the implicit __model__ SDF frame is not the identity).
88
89
  suc_H_i = suc_H_i.at[0].set(ordered_links[0].pose)
89
90
 
91
+ # Create the object to compute forward kinematics.
92
+ fk = KinematicGraphTransforms(graph=description)
93
+
90
94
  # Compute the parent-to-predecessor and successor-to-child transforms for
91
95
  # each joint belonging to the model.
92
96
  # Note that the joint indices starts from i=1 given our joint model,
93
97
  # therefore the entries at index 0 are not updated.
94
98
  for joint in ordered_joints:
95
99
  λ_H_pre = λ_H_pre.at[joint.index].set(
96
- description.relative_transform(
97
- relative_to=joint.parent.name,
98
- name=joint.name,
99
- )
100
+ fk.relative_transform(relative_to=joint.parent.name, name=joint.name)
100
101
  )
101
102
  suc_H_i = suc_H_i.at[joint.index].set(
102
- description.relative_transform(
103
- relative_to=joint.name, name=joint.child.name
104
- )
103
+ fk.relative_transform(relative_to=joint.name, name=joint.child.name)
105
104
  )
106
105
 
107
106
  # Define the DoFs of the base link.
@@ -243,16 +242,16 @@ def supported_joint_motion(
243
242
  """
244
243
 
245
244
  if isinstance(joint_type, JointType):
246
- code = joint_type
245
+ type_enum = joint_type
247
246
  elif isinstance(joint_type, JointDescriptor):
248
- code = joint_type.code
247
+ type_enum = joint_type.joint_type
249
248
  else:
250
249
  raise ValueError(joint_type)
251
250
 
252
251
  # Prepare the joint position
253
252
  s = jnp.array(joint_position).astype(float)
254
253
 
255
- match code:
254
+ match type_enum:
256
255
 
257
256
  case JointType.R:
258
257
  joint_type: JointGenericAxis
@@ -276,58 +275,8 @@ def supported_joint_motion(
276
275
  S = jnp.vstack(jnp.hstack([joint_type.axis.squeeze(), jnp.zeros(3)]))
277
276
 
278
277
  case JointType.F:
279
- raise ValueError("Fixed joints shouldn't be here")
280
-
281
- case JointType.Rx:
282
-
283
- pre_H_suc = jaxlie.SE3.from_rotation(
284
- rotation=jaxlie.SO3.from_x_radians(theta=s)
285
- )
286
-
287
- S = jnp.vstack([0, 0, 0, 1.0, 0, 0])
288
-
289
- case JointType.Ry:
290
-
291
- pre_H_suc = jaxlie.SE3.from_rotation(
292
- rotation=jaxlie.SO3.from_y_radians(theta=s)
293
- )
294
-
295
- S = jnp.vstack([0, 0, 0, 0, 1.0, 0])
296
-
297
- case JointType.Rz:
298
-
299
- pre_H_suc = jaxlie.SE3.from_rotation(
300
- rotation=jaxlie.SO3.from_z_radians(theta=s)
301
- )
302
-
303
- S = jnp.vstack([0, 0, 0, 0, 0, 1.0])
304
-
305
- case JointType.Px:
306
-
307
- pre_H_suc = jaxlie.SE3.from_rotation_and_translation(
308
- rotation=jaxlie.SO3.identity(),
309
- translation=jnp.array([s, 0.0, 0.0]),
310
- )
311
-
312
- S = jnp.vstack([1.0, 0, 0, 0, 0, 0])
313
-
314
- case JointType.Py:
315
-
316
- pre_H_suc = jaxlie.SE3.from_rotation_and_translation(
317
- rotation=jaxlie.SO3.identity(),
318
- translation=jnp.array([0.0, s, 0.0]),
319
- )
320
-
321
- S = jnp.vstack([0, 1.0, 0, 0, 0, 0])
322
-
323
- case JointType.Pz:
324
-
325
- pre_H_suc = jaxlie.SE3.from_rotation_and_translation(
326
- rotation=jaxlie.SO3.identity(),
327
- translation=jnp.array([0.0, 0.0, s]),
328
- )
329
-
330
- S = jnp.vstack([0, 0, 1.0, 0, 0, 0])
278
+ pre_H_suc = jaxlie.SE3.identity()
279
+ S = jnp.zeros(shape=(6, 1))
331
280
 
332
281
  case _:
333
282
  raise ValueError(joint_type)
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import dataclasses
2
4
  import enum
3
5
  from typing import Tuple, Union
@@ -6,79 +8,60 @@ import jax_dataclasses
6
8
  import numpy as np
7
9
  import numpy.typing as npt
8
10
 
11
+ import jaxsim.typing as jtp
9
12
  from jaxsim.utils import JaxsimDataclass, Mutability
10
13
 
11
14
  from .link import LinkDescription
12
15
 
13
16
 
17
+ @enum.unique
14
18
  class JointType(enum.IntEnum):
15
19
  """
16
- Enumeration of joint types for robot joints.
17
-
18
- Args:
19
- F: Fixed joint (no movement).
20
- R: Revolute joint (rotation).
21
- P: Prismatic joint (translation).
22
- Rx: Revolute joint with rotation about the X-axis.
23
- Ry: Revolute joint with rotation about the Y-axis.
24
- Rz: Revolute joint with rotation about the Z-axis.
25
- Px: Prismatic joint with translation along the X-axis.
26
- Py: Prismatic joint with translation along the Y-axis.
27
- Pz: Prismatic joint with translation along the Z-axis.
20
+ Type of supported joints.
28
21
  """
29
22
 
30
- F = enum.auto() # Fixed
31
- R = enum.auto() # Revolute
32
- P = enum.auto() # Prismatic
23
+ @staticmethod
24
+ def _generate_next_value_(name, start, count, last_values):
25
+ # Start auto Enum value from 0 instead of 1
26
+ return count
27
+
28
+ #: Fixed joint.
29
+ F = enum.auto()
33
30
 
34
- # Revolute joints, single axis
35
- Rx = enum.auto()
36
- Ry = enum.auto()
37
- Rz = enum.auto()
31
+ #: Revolute joint (1 DoF around axis).
32
+ R = enum.auto()
38
33
 
39
- # Prismatic joints, single axis
40
- Px = enum.auto()
41
- Py = enum.auto()
42
- Pz = enum.auto()
34
+ #: Prismatic joint (1 DoF along axis).
35
+ P = enum.auto()
43
36
 
44
37
 
45
- @dataclasses.dataclass
38
+ @jax_dataclasses.pytree_dataclass
46
39
  class JointDescriptor:
47
40
  """
48
- Description of a joint type with a specific code.
49
-
50
- Args:
51
- code (JointType): The code representing the joint type.
52
-
41
+ Base class for joint types requiring to store additional metadata.
53
42
  """
54
43
 
55
- code: JointType
44
+ #: The joint type.
45
+ joint_type: JointType
56
46
 
57
- def __hash__(self) -> int:
58
- return hash(self.__repr__())
59
47
 
60
-
61
- @dataclasses.dataclass
48
+ @jax_dataclasses.pytree_dataclass
62
49
  class JointGenericAxis(JointDescriptor):
63
50
  """
64
- Description of a joint type with a generic axis.
65
-
66
- Attributes:
67
- axis (npt.NDArray): The axis of rotation or translation for the joint.
68
-
51
+ A joint requiring the specification of a 3D axis.
69
52
  """
70
53
 
71
- axis: npt.NDArray
54
+ #: The axis of rotation or translation of the joint (must have norm 1).
55
+ axis: jtp.Vector
72
56
 
73
- def __post_init__(self):
74
- if np.allclose(self.axis, 0.0):
75
- raise ValueError(self.axis)
57
+ def __hash__(self) -> int:
58
+ return hash((self.joint_type, tuple(np.array(self.axis).tolist())))
76
59
 
77
- def __eq__(self, other):
78
- return super().__eq__(other) and np.allclose(self.axis, other.axis)
60
+ def __eq__(self, other: JointGenericAxis) -> bool:
61
+ if not isinstance(other, JointGenericAxis):
62
+ return False
79
63
 
80
- def __hash__(self) -> int:
81
- return hash(self.__repr__())
64
+ return hash(self) == hash(other)
82
65
 
83
66
 
84
67
  @jax_dataclasses.pytree_dataclass
@@ -4,7 +4,7 @@ from typing import List
4
4
 
5
5
  from jaxsim import logging
6
6
 
7
- from ..kinematic_graph import KinematicGraph, RootPose
7
+ from ..kinematic_graph import KinematicGraph, KinematicGraphTransforms, RootPose
8
8
  from .collision import CollidablePoint, CollisionShape
9
9
  from .joint import JointDescription
10
10
  from .link import LinkDescription
@@ -75,6 +75,9 @@ class ModelDescription(KinematicGraph):
75
75
  considered_joints=considered_joints
76
76
  )
77
77
 
78
+ # Create the object to compute forward kinematics.
79
+ fk = KinematicGraphTransforms(graph=kinematic_graph)
80
+
78
81
  # Store here the final model collisions
79
82
  final_collisions: List[CollisionShape] = []
80
83
 
@@ -121,7 +124,7 @@ class ModelDescription(KinematicGraph):
121
124
  # relative pose
122
125
  moved_cp = cp.change_link(
123
126
  new_link=real_parent_link_of_shape,
124
- new_H_old=kinematic_graph.relative_transform(
127
+ new_H_old=fk.relative_transform(
125
128
  relative_to=real_parent_link_of_shape.name,
126
129
  name=cp.parent_link.name,
127
130
  ),
@@ -139,7 +142,9 @@ class ModelDescription(KinematicGraph):
139
142
  root=kinematic_graph.root,
140
143
  joints=kinematic_graph.joints,
141
144
  frames=kinematic_graph.frames,
145
+ _joints_removed=kinematic_graph._joints_removed,
142
146
  )
147
+
143
148
  assert kinematic_graph.root.name == base_link_name, kinematic_graph.root.name
144
149
 
145
150
  return model
@@ -158,15 +163,12 @@ class ModelDescription(KinematicGraph):
158
163
  ValueError: If the specified joints are not part of the model.
159
164
  """
160
165
 
161
- msg = "The model reduction logic assumes that removed joints have zero angles"
162
- logging.info(msg=msg)
163
-
164
166
  if len(set(considered_joints) - set(self.joint_names())) != 0:
165
167
  extra_joints = set(considered_joints) - set(self.joint_names())
166
168
  msg = f"Found joints not part of the model: {extra_joints}"
167
169
  raise ValueError(msg)
168
170
 
169
- return ModelDescription.build_model_from(
171
+ reduced_model_description = ModelDescription.build_model_from(
170
172
  name=self.name,
171
173
  links=list(self.links_dict.values()),
172
174
  joints=self.joints,
@@ -177,6 +179,12 @@ class ModelDescription(KinematicGraph):
177
179
  considered_joints=considered_joints,
178
180
  )
179
181
 
182
+ # Include the unconnected/removed joints from the original model.
183
+ for joint in self._joints_removed:
184
+ reduced_model_description._joints_removed.append(joint)
185
+
186
+ return reduced_model_description
187
+
180
188
  def update_collision_shape_of_link(self, link_name: str, enabled: bool) -> None:
181
189
  """
182
190
  Enable or disable collision shapes associated with a link.
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import copy
2
4
  import dataclasses
3
5
  import functools
@@ -9,6 +11,7 @@ from typing import (
9
11
  List,
10
12
  NamedTuple,
11
13
  Optional,
14
+ Sequence,
12
15
  Tuple,
13
16
  Union,
14
17
  )
@@ -41,7 +44,7 @@ class RootPose(NamedTuple):
41
44
 
42
45
 
43
46
  @dataclasses.dataclass(frozen=True)
44
- class KinematicGraph:
47
+ class KinematicGraph(Sequence[descriptions.LinkDescription]):
45
48
  """
46
49
  Represents a kinematic graph of links and joints.
47
50
 
@@ -76,6 +79,12 @@ class KinematicGraph:
76
79
  repr=False, compare=False, default_factory=dict
77
80
  )
78
81
 
82
+ # Private attribute storing the unconnected joints from the parsed model and
83
+ # the joints removed after model reduction.
84
+ _joints_removed: list[descriptions.JointDescription] = dataclasses.field(
85
+ default_factory=list, repr=False, compare=False
86
+ )
87
+
79
88
  @functools.cached_property
80
89
  def links_dict(self) -> Dict[str, descriptions.LinkDescription]:
81
90
  return {l.name: l for l in iter(self)}
@@ -153,15 +162,24 @@ class KinematicGraph:
153
162
  # Couple links and joints and create the graph of links.
154
163
  # Note that the pose of the frames is not updated; it's the caller's
155
164
  # responsibility to update their pose if they want to use them.
156
- graph_root_node, graph_joints, graph_frames = KinematicGraph.create_graph(
157
- links=links, joints=joints, root_link_name=root_link_name
165
+ graph_root_node, graph_joints, graph_frames, unconnected_joints = (
166
+ KinematicGraph.create_graph(
167
+ links=links, joints=joints, root_link_name=root_link_name
168
+ )
158
169
  )
159
170
 
160
171
  for frame in graph_frames:
161
172
  logging.warning(msg=f"Ignoring unconnected link / frame: '{frame.name}'")
162
173
 
174
+ for joint in unconnected_joints:
175
+ logging.warning(msg=f"Ignoring unconnected joint: '{joint.name}'")
176
+
163
177
  return KinematicGraph(
164
- root=graph_root_node, joints=graph_joints, frames=[], root_pose=root_pose
178
+ root=graph_root_node,
179
+ joints=graph_joints,
180
+ frames=[],
181
+ root_pose=root_pose,
182
+ _joints_removed=unconnected_joints,
165
183
  )
166
184
 
167
185
  @staticmethod
@@ -173,9 +191,10 @@ class KinematicGraph:
173
191
  descriptions.LinkDescription,
174
192
  List[descriptions.JointDescription],
175
193
  List[descriptions.LinkDescription],
194
+ list[descriptions.JointDescription],
176
195
  ]:
177
196
  """
178
- Create a kinematic graph from lists of links and joints.
197
+ Create a kinematic graph from the lists of parsed links and joints.
179
198
 
180
199
  Args:
181
200
  links (List[descriptions.LinkDescription]): A list of link descriptions.
@@ -183,8 +202,9 @@ class KinematicGraph:
183
202
  root_link_name (str): The name of the root link.
184
203
 
185
204
  Returns:
186
- Tuple[descriptions.LinkDescription, List[descriptions.JointDescription], List[descriptions.LinkDescription]]:
187
- A tuple containing the root link, list of joints, and list of frames in the graph.
205
+ A tuple containing the root node with the full kinematic graph as child nodes,
206
+ the list of joints associated to graph nodes, the list of frames rigidly
207
+ attached to graph nodes, and the list of joints not part of the graph.
188
208
  """
189
209
 
190
210
  # Create a dict that maps link name to the link, for easy retrieval
@@ -246,18 +266,25 @@ class KinematicGraph:
246
266
  links_dict[root_link_name].mutable(mutable=False),
247
267
  list(set(joints) - set(removed_joints)),
248
268
  frames,
269
+ list(set(removed_joints)),
249
270
  )
250
271
 
251
- def reduce(self, considered_joints: List[str]) -> "KinematicGraph":
272
+ def reduce(self, considered_joints: List[str]) -> KinematicGraph:
252
273
  """
253
- Reduce the kinematic graph by removing specified joints and lumping the mass and inertia of removed links into their parent links.
274
+ Reduce the kinematic graph by removing unspecified joints.
275
+
276
+ When a joint is removed, the mass and inertia of its child link are lumped
277
+ with those of its parent link, obtaining a new link that combines the two.
278
+ The description of the removed joint specifies the default angle (usually 0)
279
+ that is considered when the joint is removed.
254
280
 
255
281
  Args:
256
- considered_joints (List[str]): A list of joint names to consider.
282
+ considered_joints: A list of joint names to consider.
257
283
 
258
284
  Returns:
259
- KinematicGraph: The reduced kinematic graph.
285
+ The reduced kinematic graph.
260
286
  """
287
+
261
288
  # The current object represents the complete kinematic graph
262
289
  full_graph = self
263
290
 
@@ -281,6 +308,9 @@ class KinematicGraph:
281
308
  links_dict = copy.deepcopy(full_graph.links_dict)
282
309
  joints_dict = copy.deepcopy(full_graph.joints_dict)
283
310
 
311
+ # Create the object to compute forward kinematics.
312
+ fk = KinematicGraphTransforms(graph=full_graph)
313
+
284
314
  # The following steps are implemented below in order to create the reduced graph:
285
315
  #
286
316
  # 1. Lump the mass of the removed links into their parent
@@ -329,7 +359,7 @@ class KinematicGraph:
329
359
  # Lump the link
330
360
  lumped_link = parent_of_link_to_remove.lump_with(
331
361
  link=link_to_remove,
332
- lumped_H_removed=full_graph.relative_transform(
362
+ lumped_H_removed=fk.relative_transform(
333
363
  relative_to=parent_of_link_to_remove.name, name=link_to_remove.name
334
364
  ),
335
365
  )
@@ -370,7 +400,7 @@ class KinematicGraph:
370
400
  # Update the pose. Note that after the lumping process, the dict entry
371
401
  # links_dict[joint.parent.name] contains the final lumped link
372
402
  with joint.mutable_context(mutability=Mutability.MUTABLE):
373
- joint.pose = full_graph.relative_transform(
403
+ joint.pose = fk.relative_transform(
374
404
  relative_to=links_dict[joint.parent.name].name, name=joint.name
375
405
  )
376
406
  with joint.mutable_context(mutability=Mutability.MUTABLE_NO_VALIDATION):
@@ -396,18 +426,25 @@ class KinematicGraph:
396
426
 
397
427
  # Create the reduced graph data. We pass the full list of links so that those
398
428
  # that are not part of the graph will be returned as frames.
399
- reduced_root_node, reduced_joints, reduced_frames = KinematicGraph.create_graph(
400
- links=list(full_graph_links_dict.values()),
401
- joints=[joints_dict[joint_name] for joint_name in considered_joints],
402
- root_link_name=full_graph.root.name,
429
+ reduced_root_node, reduced_joints, reduced_frames, unconnected_joints = (
430
+ KinematicGraph.create_graph(
431
+ links=list(full_graph_links_dict.values()),
432
+ joints=[joints_dict[joint_name] for joint_name in considered_joints],
433
+ root_link_name=full_graph.root.name,
434
+ )
403
435
  )
404
436
 
405
437
  # Create the reduced graph
406
438
  reduced_graph = KinematicGraph(
407
439
  root=reduced_root_node,
408
440
  joints=reduced_joints,
409
- frames=reduced_frames,
441
+ frames=self.frames + reduced_frames,
410
442
  root_pose=full_graph.root_pose,
443
+ _joints_removed=(
444
+ self._joints_removed
445
+ + unconnected_joints
446
+ + [joints_dict[name] for name in joint_names_to_remove]
447
+ ),
411
448
  )
412
449
 
413
450
  # ================================================================
@@ -424,7 +461,7 @@ class KinematicGraph:
424
461
 
425
462
  # Update the connection of the frame
426
463
  frame.parent = new_parent_link
427
- frame.pose = full_graph.relative_transform(
464
+ frame.pose = fk.relative_transform(
428
465
  relative_to=new_parent_link.name, name=frame.name
429
466
  )
430
467
 
@@ -462,65 +499,6 @@ class KinematicGraph:
462
499
  """
463
500
  return list(self.frames_dict.keys())
464
501
 
465
- def transform(self, name: str) -> npt.NDArray:
466
- """
467
- Compute the transformation matrix for a given link, joint, or frame.
468
-
469
- Args:
470
- name (str): The name of the link, joint, or frame.
471
-
472
- Returns:
473
- npt.NDArray: The transformation matrix.
474
- """
475
- if name in self.transform_cache:
476
- return self.transform_cache[name]
477
-
478
- if name in self.joint_names():
479
- joint = self.joints_dict[name]
480
-
481
- if joint.initial_position != 0.0:
482
- msg = f"Ignoring unsupported initial position of joint '{name}'"
483
- logging.warning(msg=msg)
484
-
485
- transform = self.transform(name=joint.parent.name) @ joint.pose
486
- self.transform_cache[name] = transform
487
- return self.transform_cache[name]
488
-
489
- if name in self.link_names():
490
- link = self.links_dict[name]
491
-
492
- if link.name == self.root.name:
493
- return link.pose
494
-
495
- parent_joint = self.joints_connection_dict[(link.parent.name, link.name)]
496
- transform = self.transform(name=parent_joint.name) @ link.pose
497
- self.transform_cache[name] = transform
498
- return self.transform_cache[name]
499
-
500
- # It can only be a plain frame
501
- if name not in self.frame_names():
502
- raise ValueError(name)
503
-
504
- frame = self.frames_dict[name]
505
- transform = self.transform(name=frame.parent.name) @ frame.pose
506
- self.transform_cache[name] = transform
507
- return self.transform_cache[name]
508
-
509
- def relative_transform(self, relative_to: str, name: str) -> npt.NDArray:
510
- """
511
- Compute the relative transformation matrix between two elements in the kinematic graph.
512
-
513
- Args:
514
- relative_to (str): The name of the reference element.
515
- name (str): The name of the element to compute the relative transformation for.
516
-
517
- Returns:
518
- npt.NDArray: The relative transformation matrix.
519
- """
520
- return np.linalg.inv(self.transform(name=relative_to)) @ self.transform(
521
- name=name
522
- )
523
-
524
502
  def print_tree(self) -> None:
525
503
  """
526
504
  Print the tree structure of the kinematic graph.
@@ -574,6 +552,10 @@ class KinematicGraph:
574
552
 
575
553
  yield child
576
554
 
555
+ # =================
556
+ # Sequence protocol
557
+ # =================
558
+
577
559
  def __iter__(self) -> Iterable[descriptions.LinkDescription]:
578
560
  yield from KinematicGraph.breadth_first_search(root=self.root)
579
561
 
@@ -606,3 +588,188 @@ class KinematicGraph:
606
588
  return list(iter(self))[key]
607
589
 
608
590
  raise TypeError(type(key).__name__)
591
+
592
+ def count(self, value: descriptions.LinkDescription) -> int:
593
+ return list(iter(self)).count(value)
594
+
595
+ def index(
596
+ self, value: descriptions.LinkDescription, start: int = 0, stop: int = -1
597
+ ) -> int:
598
+ return list(iter(self)).index(value, start, stop)
599
+
600
+
601
+ # ====================
602
+ # Other useful classes
603
+ # ====================
604
+
605
+
606
+ @dataclasses.dataclass(frozen=True)
607
+ class KinematicGraphTransforms:
608
+
609
+ graph: KinematicGraph
610
+
611
+ _transform_cache: dict[str, npt.NDArray] = dataclasses.field(
612
+ default_factory=dict, init=False, repr=False, compare=False
613
+ )
614
+
615
+ _initial_joint_positions: dict[str, float] = dataclasses.field(
616
+ init=False, repr=False, compare=False
617
+ )
618
+
619
+ def __post_init__(self) -> None:
620
+
621
+ super().__setattr__(
622
+ "_initial_joint_positions",
623
+ {joint.name: joint.initial_position for joint in self.graph.joints},
624
+ )
625
+
626
+ @property
627
+ def initial_joint_positions(self) -> npt.NDArray:
628
+
629
+ return np.atleast_1d(
630
+ np.array(list(self._initial_joint_positions.values()))
631
+ ).astype(float)
632
+
633
+ @initial_joint_positions.setter
634
+ def initial_joint_positions(
635
+ self,
636
+ positions: npt.NDArray | Sequence,
637
+ joint_names: Sequence[str] | None = None,
638
+ ) -> None:
639
+
640
+ joint_names = (
641
+ joint_names
642
+ if joint_names is not None
643
+ else list(self._initial_joint_positions.keys())
644
+ )
645
+
646
+ s = np.atleast_1d(np.array(positions).squeeze())
647
+
648
+ if s.size != len(joint_names):
649
+ raise ValueError(s.size, len(joint_names))
650
+
651
+ for joint_name in joint_names:
652
+ if joint_name not in self._initial_joint_positions:
653
+ raise ValueError(joint_name)
654
+
655
+ # Clear transform cache.
656
+ self._transform_cache.clear()
657
+
658
+ # Update initial joint positions.
659
+ for joint_name, position in zip(joint_names, s):
660
+ self._initial_joint_positions[joint_name] = position
661
+
662
+ def transform(self, name: str) -> npt.NDArray:
663
+ """
664
+ Compute the SE(3) transform of elements belonging to the kinematic graph.
665
+
666
+ Args:
667
+ name: The name of a link, a joint, or a frame.
668
+
669
+ Returns:
670
+ The 4x4 transform matrix of the element w.r.t. the model frame.
671
+ """
672
+
673
+ # If the transform was already computed, return it.
674
+ if name in self._transform_cache:
675
+ return self._transform_cache[name]
676
+
677
+ # If the name is a joint, compute M_H_J transform.
678
+ if name in self.graph.joint_names():
679
+
680
+ # Get the joint.
681
+ joint = self.graph.joints_dict[name]
682
+
683
+ # Get the transform of the parent link.
684
+ M_H_L = self.transform(name=joint.parent.name)
685
+
686
+ # Rename the pose of the predecessor joint frame w.r.t. its parent link.
687
+ L_H_pre = joint.pose
688
+
689
+ # Compute the joint transform from the predecessor to the successor frame.
690
+ pre_H_J = self.pre_H_suc(
691
+ joint_type=joint.jtype,
692
+ joint_position=self._initial_joint_positions[joint.name],
693
+ )
694
+
695
+ # Compute the M_H_J transform.
696
+ self._transform_cache[name] = M_H_L @ L_H_pre @ pre_H_J
697
+ return self._transform_cache[name]
698
+
699
+ # If the name is a link, compute M_H_L transform.
700
+ if name in self.graph.link_names():
701
+
702
+ # Get the link.
703
+ link = self.graph.links_dict[name]
704
+
705
+ # Handle the pose between the __model__ frame and the root link.
706
+ if link.name == self.graph.root.name:
707
+ M_H_B = link.pose
708
+ return M_H_B
709
+
710
+ # Get the joint between the link and its parent.
711
+ parent_joint = self.graph.joints_connection_dict[
712
+ (link.parent.name, link.name)
713
+ ]
714
+
715
+ # Get the transform of the parent joint.
716
+ M_H_J = self.transform(name=parent_joint.name)
717
+
718
+ # Rename the pose of the link w.r.t. its parent joint.
719
+ J_H_L = link.pose
720
+
721
+ # Compute the M_H_L transform.
722
+ self._transform_cache[name] = M_H_J @ J_H_L
723
+ return self._transform_cache[name]
724
+
725
+ # It can only be a plain frame.
726
+ if name not in self.graph.frame_names():
727
+ raise ValueError(name)
728
+
729
+ # Get the frame.
730
+ frame = self.graph.frames_dict[name]
731
+
732
+ # Get the transform of the parent link.
733
+ M_H_L = self.transform(name=frame.parent.name)
734
+
735
+ # Rename the pose of the frame w.r.t. its parent link.
736
+ L_H_F = frame.pose
737
+
738
+ # Compute the M_H_F transform.
739
+ self._transform_cache[name] = M_H_L @ L_H_F
740
+ return self._transform_cache[name]
741
+
742
+ def relative_transform(self, relative_to: str, name: str) -> npt.NDArray:
743
+ """
744
+ Compute the SE(3) relative transform of elements belonging to the kinematic graph.
745
+
746
+ Args:
747
+ relative_to: The name of the reference element.
748
+ name: The name of a link, a joint, or a frame.
749
+
750
+ Returns:
751
+ The 4x4 transform matrix of the element w.r.t. the desired frame.
752
+ """
753
+
754
+ import jaxsim.math
755
+
756
+ M_H_target = self.transform(name=name)
757
+ M_H_R = self.transform(name=relative_to)
758
+
759
+ # Compute the relative transform R_H_target, where R is the reference frame,
760
+ # and i the frame of the desired link|joint|frame.
761
+ return np.array(jaxsim.math.Transform.inverse(M_H_R)) @ M_H_target
762
+
763
+ @staticmethod
764
+ def pre_H_suc(
765
+ joint_type: descriptions.JointType | descriptions.JointDescriptor,
766
+ joint_position: float | None = None,
767
+ ) -> npt.NDArray:
768
+
769
+ import jaxsim.math
770
+
771
+ return np.array(
772
+ jaxsim.math.supported_joint_motion(
773
+ joint_type=joint_type, joint_position=joint_position
774
+ )[0]
775
+ )
@@ -134,7 +134,7 @@ def extract_model_data(
134
134
  name=j.name,
135
135
  parent=world_link,
136
136
  child=links_dict[j.child],
137
- jtype=utils.axis_to_jtype(axis=j.axis, type=j.type),
137
+ jtype=utils.joint_to_joint_type(joint=j),
138
138
  axis=(
139
139
  np.array(j.axis.xyz.xyz)
140
140
  if j.axis is not None
@@ -201,7 +201,7 @@ def extract_model_data(
201
201
  name=j.name,
202
202
  parent=links_dict[j.parent],
203
203
  child=links_dict[j.child],
204
- jtype=utils.axis_to_jtype(axis=j.axis, type=j.type),
204
+ jtype=utils.joint_to_joint_type(joint=j),
205
205
  axis=(
206
206
  np.array(j.axis.xyz.xyz)
207
207
  if j.axis is not None
@@ -1,5 +1,4 @@
1
1
  import os
2
- from typing import Union
3
2
 
4
3
  import jaxlie
5
4
  import numpy as np
@@ -60,57 +59,43 @@ def from_sdf_inertial(inertial: rod.Inertial) -> jtp.Matrix:
60
59
  return M_L.astype(dtype=float)
61
60
 
62
61
 
63
- def axis_to_jtype(
64
- axis: rod.Axis, type: str
65
- ) -> Union[descriptions.JointType, descriptions.JointDescriptor]:
62
+ def joint_to_joint_type(
63
+ joint: rod.Joint,
64
+ ) -> descriptions.JointType | descriptions.JointDescriptor:
66
65
  """
67
- Convert an SDF axis to a joint type.
66
+ Extract the joint type from an SDF joint.
68
67
 
69
68
  Args:
70
- axis: The SDF axis.
71
- type: The SDF joint type.
69
+ joint: The parsed SDF joint.
72
70
 
73
71
  Returns:
74
72
  The corresponding joint type description.
75
73
  """
76
74
 
77
- if type == "fixed":
75
+ axis = joint.axis
76
+ joint_type = joint.type
77
+
78
+ if joint_type == "fixed":
78
79
  return descriptions.JointType.F
79
80
 
80
81
  if not (axis.xyz is not None and axis.xyz.xyz is not None):
81
82
  raise ValueError("Failed to read axis xyz data")
82
83
 
83
- axis_xyz = np.array(axis.xyz.xyz)
84
-
85
- if np.allclose(axis_xyz, [1, 0, 0]) and type in {"revolute", "continuous"}:
86
- return descriptions.JointType.Rx
87
-
88
- if np.allclose(axis_xyz, [0, 1, 0]) and type in {"revolute", "continuous"}:
89
- return descriptions.JointType.Ry
90
-
91
- if np.allclose(axis_xyz, [0, 0, 1]) and type in {"revolute", "continuous"}:
92
- return descriptions.JointType.Rz
93
-
94
- if np.allclose(axis_xyz, [1, 0, 0]) and type == "prismatic":
95
- return descriptions.JointType.Px
96
-
97
- if np.allclose(axis_xyz, [0, 1, 0]) and type == "prismatic":
98
- return descriptions.JointType.Py
99
-
100
- if np.allclose(axis_xyz, [0, 0, 1]) and type == "prismatic":
101
- return descriptions.JointType.Pz
84
+ # Make sure that the axis is a unary vector.
85
+ axis_xyz = np.array(axis.xyz.xyz).astype(float)
86
+ axis_xyz = axis_xyz / np.linalg.norm(axis_xyz)
102
87
 
103
- if type == "revolute":
88
+ if joint_type in {"revolute", "continuous"}:
104
89
  return descriptions.JointGenericAxis(
105
- code=descriptions.JointType.R, axis=np.array(axis_xyz, dtype=float)
90
+ joint_type=descriptions.JointType.R, axis=axis_xyz
106
91
  )
107
92
 
108
- if type == "prismatic":
93
+ if joint_type == "prismatic":
109
94
  return descriptions.JointGenericAxis(
110
- code=descriptions.JointType.P, axis=np.array(axis_xyz, dtype=float)
95
+ joint_type=descriptions.JointType.P, axis=axis_xyz
111
96
  )
112
97
 
113
- raise ValueError("Joint not supported", axis_xyz, type)
98
+ raise ValueError("Joint not supported", axis_xyz, joint_type)
114
99
 
115
100
 
116
101
  def create_box_collision(
@@ -50,7 +50,9 @@ class JaxsimDataclass(abc.ABC):
50
50
 
51
51
  @contextlib.contextmanager
52
52
  def mutable_context(
53
- self: Self, mutability: Mutability, restore_after_exception: bool = True
53
+ self: Self,
54
+ mutability: Mutability = Mutability.MUTABLE,
55
+ restore_after_exception: bool = True,
54
56
  ) -> Iterator[Self]:
55
57
  """
56
58
  Context manager to temporarily change the mutability of the object.
@@ -86,7 +88,7 @@ class JaxsimDataclass(abc.ABC):
86
88
  setattr(self, f.name, getattr(self_copy, f.name))
87
89
 
88
90
  try:
89
- self.set_mutability(mutability)
91
+ self.set_mutability(mutability=mutability)
90
92
  yield self
91
93
 
92
94
  if mutability is not Mutability.MUTABLE_NO_VALIDATION:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: jaxsim
3
- Version: 0.2.1.dev17
3
+ Version: 0.2.1.dev38
4
4
  Home-page: https://github.com/ami-iit/jaxsim
5
5
  Author: Diego Ferigo
6
6
  Author-email: diego.ferigo@iit.it
@@ -43,7 +43,7 @@ Provides-Extra: all
43
43
  Requires-Dist: black[jupyter] ~=24.0 ; extra == 'all'
44
44
  Requires-Dist: isort ; extra == 'all'
45
45
  Requires-Dist: pre-commit ; extra == 'all'
46
- Requires-Dist: idyntree ; extra == 'all'
46
+ Requires-Dist: idyntree >=12.2.1 ; extra == 'all'
47
47
  Requires-Dist: pytest >=6.0 ; extra == 'all'
48
48
  Requires-Dist: pytest-icdiff ; extra == 'all'
49
49
  Requires-Dist: robot-descriptions ; extra == 'all'
@@ -55,7 +55,7 @@ Requires-Dist: black[jupyter] ~=24.0 ; extra == 'style'
55
55
  Requires-Dist: isort ; extra == 'style'
56
56
  Requires-Dist: pre-commit ; extra == 'style'
57
57
  Provides-Extra: testing
58
- Requires-Dist: idyntree ; extra == 'testing'
58
+ Requires-Dist: idyntree >=12.2.1 ; extra == 'testing'
59
59
  Requires-Dist: pytest >=6.0 ; extra == 'testing'
60
60
  Requires-Dist: pytest-icdiff ; extra == 'testing'
61
61
  Requires-Dist: robot-descriptions ; extra == 'testing'
@@ -1,16 +1,16 @@
1
1
  jaxsim/__init__.py,sha256=OcrfoYS1DGcmAGqu2AqlCTiUVxcpi-IsVwcr_16x74Q,1789
2
- jaxsim/_version.py,sha256=E6AF09CLGzT7X3HBCAUGv8Fgy4Lo-CauUZTQ3aY6D5k,426
2
+ jaxsim/_version.py,sha256=Cl_kuTOEO0aVEiwkm9tH1bjrDIciBhU4mo1dsIZmCdM,426
3
3
  jaxsim/logging.py,sha256=c4zhwBKf9eAYAHVp62kTEllqdsZgh0K-kPKVy8L3elU,1584
4
4
  jaxsim/typing.py,sha256=MeuOCQtLAr-sPkvB_sU8FtwGNRirz1auCwIgRC-QZl8,646
5
5
  jaxsim/api/__init__.py,sha256=fNTCPUeDfOAizRd4RsW3Epv0sLTu0KJGoFRSEsi75VM,162
6
6
  jaxsim/api/com.py,sha256=Qtm_6qpiK4WtDVn6JMUHa8DySgBl9CjgKCybJqZ58Lc,7379
7
7
  jaxsim/api/common.py,sha256=DV-WZG28sikXopNv458aYvpLjmiAtFr5LRscOwXusuk,6640
8
8
  jaxsim/api/contact.py,sha256=Ve4ZOWkLEBRgK3KhtICxKY7YzsxYvc3lO-pPRBjqSnY,8659
9
- jaxsim/api/data.py,sha256=cPFy6TTm0rNpDk1O0ZxOpvBwWVPu6V14XLRMeC7x81Q,26786
9
+ jaxsim/api/data.py,sha256=GXSCfq4_PsRZuUwsmcl18e2Ppdtu4ykYs0oWih8J2ZI,26775
10
10
  jaxsim/api/joint.py,sha256=q31Kp3Cqv-yTcxijjzbj_QADFnGQyjb2al9fYZtzedo,4763
11
11
  jaxsim/api/kin_dyn_parameters.py,sha256=G4mtSi8fElYe0yttLgsxSOPf7vcK-yqTu06Aa5SSrYg,26012
12
12
  jaxsim/api/link.py,sha256=rypTwkMf9HJ5UuAtHRJh0LqqdJWcLKTtTjWcjduEsF0,9842
13
- jaxsim/api/model.py,sha256=UwjZctpQR_3F8_Cm-NmFQb14Wtw_vscs3Yg-ULpQyrs,52269
13
+ jaxsim/api/model.py,sha256=Ci7hl4M1gbnHNQAO5GLjxOdvJe8w3dhvGB_7x2gdpaU,53540
14
14
  jaxsim/api/ode.py,sha256=6l-6i2YHagsQvR8Ac-_fmO6P0hBVT6NkHhwXnrdITEg,9785
15
15
  jaxsim/api/ode_data.py,sha256=dwRFVPJ30XMmdUbPXEu7YxsQ97jZP4L4fd5ZzhrO5Ys,22184
16
16
  jaxsim/api/references.py,sha256=Lvskf17r619KKxwCJP7hAAty2kaXgDXJX1uKqoDIDgo,15483
@@ -22,7 +22,7 @@ jaxsim/math/__init__.py,sha256=inJ9nRFkqstuGa8OyFkfWVudo5U9Ug4WgDBuKva8AIA,337
22
22
  jaxsim/math/adjoint.py,sha256=DT21izjVW497GRrgNfx8tv0ZeWW5QncWMGMhI0acUNw,4425
23
23
  jaxsim/math/cross.py,sha256=U7yEx_l75mSy5g6O-jsjBztApvxC3WaV4MpkS5tThu4,1330
24
24
  jaxsim/math/inertia.py,sha256=UAB7ym4gXFanejcs_ovZMpteHCc6poWYmt-mLmd5hhk,1640
25
- jaxsim/math/joint_model.py,sha256=LKLB26VMz6vx9JLdFUWhGyrElYFEQV-bJiQO5kaZUGY,10896
25
+ jaxsim/math/joint_model.py,sha256=LSZM94oLpdc0z8DJr9lrN10PHYyScjvMZ1BVORUMkhk,9618
26
26
  jaxsim/math/quaternion.py,sha256=X9b8jHf0QemKUjIZSnXRJc3DdMr42CBhBy_mi9_X_AM,5068
27
27
  jaxsim/math/rotation.py,sha256=Z90daUjGpuNEVLfWB3SVtM9EtwAIaneVj9A9UpWXqhA,2182
28
28
  jaxsim/math/skew.py,sha256=oOGSSR8PUGROl6IJFlrmu6K3gPH-u16hUPfKIkcVv9o,1177
@@ -33,15 +33,15 @@ jaxsim/mujoco/loaders.py,sha256=-z6TIsems7DDogueDBwYLS2JuoWalNfv6sV1F45H63k,2102
33
33
  jaxsim/mujoco/model.py,sha256=5-4KTbEbU19zjrSuvUVdLo3noWxTvlCNsFIs3rQTNDY,13506
34
34
  jaxsim/mujoco/visualizer.py,sha256=YlteqcCbeB1B6saAHKBz1IJad3N5Rp163reZrzKLAys,5065
35
35
  jaxsim/parsers/__init__.py,sha256=sonYi-bBWAoB04kp1mxT4uIORxjb7SdZ0ukGPmVx98Y,44
36
- jaxsim/parsers/kinematic_graph.py,sha256=_ZJr-u1m64qPCeY6BHTI56dSOWhOGfbg2HN49XbTXhA,23784
36
+ jaxsim/parsers/kinematic_graph.py,sha256=pzjY-2O9QDY5wUXHAhuTyWHs1Te238gkzlHnyKeA5T0,28935
37
37
  jaxsim/parsers/descriptions/__init__.py,sha256=EbTfnrK3oCxA3pNv--YUwllJ6uICENvFgAdRbYtS9ts,238
38
38
  jaxsim/parsers/descriptions/collision.py,sha256=HUWwuRgI9KznY29FFw1_zU3bGigDEezrcPOJSxSJGNU,3382
39
- jaxsim/parsers/descriptions/joint.py,sha256=hpH0ANvIhbEQk-NGRmWIvPv3lXW385TBIMWNgz5rzM4,4106
39
+ jaxsim/parsers/descriptions/joint.py,sha256=vozAx4sr3q-9IroN90iLP0BPpY_5BJ6VME3DKPrBIoY,3600
40
40
  jaxsim/parsers/descriptions/link.py,sha256=hqLLitrAXnouia6ULk1BPOIEfRxrXwHmoPsi306IZW8,2859
41
- jaxsim/parsers/descriptions/model.py,sha256=wenuDrjoBf6prkzm9WyYT0nFWc0l6WBpKNjLoRUDPxo,8937
41
+ jaxsim/parsers/descriptions/model.py,sha256=hapbpO1VpC7f5ELkGH1ZtM2I3dpmYLz8Em5cb3DdiaA,9264
42
42
  jaxsim/parsers/rod/__init__.py,sha256=G2vqlLajBLUc4gyzXwsEI2Wsi4TMOIF9bLDFeT6KrGU,92
43
- jaxsim/parsers/rod/parser.py,sha256=mFi1baSJte6EMmWLpVjVuCpicfAAF48aFUzoKzYzPpo,12555
44
- jaxsim/parsers/rod/utils.py,sha256=xoxNdb4IgOePFFnblNo6UrgYCRm53mB3No3yXM6qlpw,6471
43
+ jaxsim/parsers/rod/parser.py,sha256=83s8Z33LSS1ddYjjm0lOqf_fQPBgeFnaayuMgc2KRXk,12533
44
+ jaxsim/parsers/rod/utils.py,sha256=SJg9t-nnItmo_gfxkfQ6uILNYN_BddcAHckyQkTDAjY,5902
45
45
  jaxsim/rbda/__init__.py,sha256=HLwxeU-IxaRpFGUCSQv-LDv20JHTt3Xj7ELiRbRieS8,319
46
46
  jaxsim/rbda/aba.py,sha256=0OoCzHhf1v-qqr1y5PIrD7_mPwAlid0fjXxUrIa5E_s,9118
47
47
  jaxsim/rbda/collidable_points.py,sha256=4ZNJbEj2nEi15jBLR-GNbdaqKgkN58FBgqd_TXupEgg,4948
@@ -55,10 +55,10 @@ jaxsim/terrain/__init__.py,sha256=f7lVX-iNpH_wkkjef9Qpjh19TTAUOQw76EiLYJDVizc,78
55
55
  jaxsim/terrain/terrain.py,sha256=q0xkWqEShVq-p1j2abTLZq8sEhjyJwquxQKm80PaHhM,2161
56
56
  jaxsim/utils/__init__.py,sha256=tnQq1_CavdfeKaLYt3pmO7Jk4MU2RwwQU_qICkjyoTY,197
57
57
  jaxsim/utils/hashless.py,sha256=bFIwKeo9KiWwsY8QM55duEGGQOyyJ4jQyPcuqTLEp5k,297
58
- jaxsim/utils/jaxsim_dataclass.py,sha256=T8nZolb563mYJD7YvTmYW-z-hpkEiaK3cDbYHqwtqRc,11347
58
+ jaxsim/utils/jaxsim_dataclass.py,sha256=h26timZ_XrBL_Q_oymv-DkQd-EcUiHn8QexAaZXBY9c,11396
59
59
  jaxsim/utils/tracing.py,sha256=KDMoyVPlu2NJvFkhtZwq5AkqMMgajt3munvJom-vEjQ,650
60
- jaxsim-0.2.1.dev17.dist-info/LICENSE,sha256=eaYdFmdeMbiIoIiPzEK0MjP1S9wtFXjXNR5er49uLR0,1546
61
- jaxsim-0.2.1.dev17.dist-info/METADATA,sha256=ZuAZDrgEcUOjS01EwlNvXEUG2D04PJqtR2wvC6QnXeA,9726
62
- jaxsim-0.2.1.dev17.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
63
- jaxsim-0.2.1.dev17.dist-info/top_level.txt,sha256=LxGMA8FLtXjQ6oI7N5gd_R_oSUHxpXxUEOfT1xS_ni0,7
64
- jaxsim-0.2.1.dev17.dist-info/RECORD,,
60
+ jaxsim-0.2.1.dev38.dist-info/LICENSE,sha256=eaYdFmdeMbiIoIiPzEK0MjP1S9wtFXjXNR5er49uLR0,1546
61
+ jaxsim-0.2.1.dev38.dist-info/METADATA,sha256=63Xh0podujOMHsBQHfMyNCFYIXQPovXz6I6oWQ-IGd4,9744
62
+ jaxsim-0.2.1.dev38.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
63
+ jaxsim-0.2.1.dev38.dist-info/top_level.txt,sha256=LxGMA8FLtXjQ6oI7N5gd_R_oSUHxpXxUEOfT1xS_ni0,7
64
+ jaxsim-0.2.1.dev38.dist-info/RECORD,,