rod 0.4.2.dev3__tar.gz → 0.4.2.dev7__tar.gz

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.
Files changed (57) hide show
  1. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/PKG-INFO +1 -1
  2. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/src/rod/urdf/exporter.py +120 -82
  3. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/src/rod.egg-info/PKG-INFO +1 -1
  4. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/tests/test_urdf_exporter.py +64 -0
  5. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/.gitattributes +0 -0
  6. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/.github/CODEOWNERS +0 -0
  7. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/.github/release.yml +0 -0
  8. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/.github/workflows/ci_cd.yml +0 -0
  9. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/.gitignore +0 -0
  10. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/.pre-commit-config.yaml +0 -0
  11. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/LICENSE +0 -0
  12. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/README.md +0 -0
  13. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/environment.yml +0 -0
  14. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/pixi.lock +0 -0
  15. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/pyproject.toml +0 -0
  16. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/setup.cfg +0 -0
  17. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/setup.py +0 -0
  18. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/src/rod/__init__.py +0 -0
  19. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/src/rod/__main__.py +0 -0
  20. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/src/rod/builder/__init__.py +0 -0
  21. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/src/rod/builder/primitive_builder.py +0 -0
  22. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/src/rod/builder/primitives.py +0 -0
  23. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/src/rod/kinematics/__init__.py +0 -0
  24. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/src/rod/kinematics/kinematic_tree.py +0 -0
  25. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/src/rod/kinematics/tree_transforms.py +0 -0
  26. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/src/rod/logging.py +0 -0
  27. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/src/rod/pretty_printer.py +0 -0
  28. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/src/rod/sdf/__init__.py +0 -0
  29. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/src/rod/sdf/collision.py +0 -0
  30. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/src/rod/sdf/common.py +0 -0
  31. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/src/rod/sdf/element.py +0 -0
  32. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/src/rod/sdf/geometry.py +0 -0
  33. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/src/rod/sdf/joint.py +0 -0
  34. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/src/rod/sdf/link.py +0 -0
  35. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/src/rod/sdf/material.py +0 -0
  36. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/src/rod/sdf/model.py +0 -0
  37. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/src/rod/sdf/physics.py +0 -0
  38. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/src/rod/sdf/scene.py +0 -0
  39. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/src/rod/sdf/sdf.py +0 -0
  40. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/src/rod/sdf/visual.py +0 -0
  41. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/src/rod/sdf/world.py +0 -0
  42. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/src/rod/tree/__init__.py +0 -0
  43. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/src/rod/tree/directed_tree.py +0 -0
  44. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/src/rod/tree/tree_elements.py +0 -0
  45. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/src/rod/urdf/__init__.py +0 -0
  46. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/src/rod/utils/__init__.py +0 -0
  47. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/src/rod/utils/frame_convention.py +0 -0
  48. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/src/rod/utils/gazebo.py +0 -0
  49. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/src/rod/utils/resolve_frames.py +0 -0
  50. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/src/rod/utils/resolve_uris.py +0 -0
  51. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/src/rod.egg-info/SOURCES.txt +0 -0
  52. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/src/rod.egg-info/dependency_links.txt +0 -0
  53. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/src/rod.egg-info/requires.txt +0 -0
  54. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/src/rod.egg-info/top_level.txt +0 -0
  55. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/tests/test_meshbuilder.py +0 -0
  56. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/tests/test_urdf_parsing.py +0 -0
  57. {rod-0.4.2.dev3 → rod-0.4.2.dev7}/tests/utils_models.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rod
3
- Version: 0.4.2.dev3
3
+ Version: 0.4.2.dev7
4
4
  Summary: The ultimate Python tool for RObot Descriptions processing.
5
5
  Author-email: Diego Ferigo <dgferigo@gmail.com>
6
6
  Maintainer-email: Filippo Luca Ferretti <filippo.ferretti@iit.it>, Carlotta Sartore <carlotta.sartore@iit.it>
@@ -56,6 +56,125 @@ class UrdfExporter(abc.ABC):
56
56
  gazebo_preserve_fixed_joints=gazebo_preserve_fixed_joints,
57
57
  ).to_urdf_string(sdf=sdf)
58
58
 
59
+ @staticmethod
60
+ def _get_urdf_joint_type(joint: rod.Joint) -> str:
61
+ """
62
+ Get the URDF joint type, converting revolute joints with infinite limits to continuous.
63
+
64
+ sdformat converts URDF continuous joints to SDF revolute joints with infinite limits,
65
+ so we need to convert them back to continuous when exporting to URDF.
66
+ """
67
+ if (
68
+ joint.type == "revolute"
69
+ and joint.axis is not None
70
+ and joint.axis.limit is not None
71
+ and (joint.axis.limit.lower is None or np.isinf(joint.axis.limit.lower))
72
+ and (joint.axis.limit.upper is None or np.isinf(joint.axis.limit.upper))
73
+ ):
74
+ return "continuous"
75
+ return joint.type
76
+
77
+ @staticmethod
78
+ def _joint_to_urdf_dict(joint: rod.Joint) -> dict[str, Any]:
79
+ """
80
+ Convert a ROD joint to a URDF joint dictionary.
81
+
82
+ Args:
83
+ joint: The ROD joint to convert.
84
+
85
+ Returns:
86
+ A dictionary representing the joint in URDF format.
87
+ """
88
+ # Compute the corrected joint type once
89
+ urdf_joint_type = UrdfExporter._get_urdf_joint_type(joint)
90
+
91
+ return {
92
+ "@name": joint.name,
93
+ "@type": urdf_joint_type,
94
+ "origin": {
95
+ "@xyz": " ".join(map(str, joint.pose.xyz)),
96
+ "@rpy": " ".join(map(str, joint.pose.rpy)),
97
+ },
98
+ "parent": {"@link": joint.parent},
99
+ "child": {"@link": joint.child},
100
+ **(
101
+ {"axis": {"@xyz": " ".join(map(str, joint.axis.xyz.xyz))}}
102
+ if joint.axis is not None
103
+ and joint.axis.xyz is not None
104
+ and urdf_joint_type != "fixed"
105
+ else {}
106
+ ),
107
+ # calibration: does not have any SDF corresponding element
108
+ **(
109
+ {
110
+ "dynamics": {
111
+ **(
112
+ {"@damping": joint.axis.dynamics.damping}
113
+ if joint.axis.dynamics.damping is not None
114
+ else {}
115
+ ),
116
+ **(
117
+ {"@friction": joint.axis.dynamics.friction}
118
+ if joint.axis.dynamics.friction is not None
119
+ else {}
120
+ ),
121
+ }
122
+ }
123
+ if joint.axis is not None
124
+ and joint.axis.dynamics is not None
125
+ and {joint.axis.dynamics.damping, joint.axis.dynamics.friction}
126
+ != {None}
127
+ and urdf_joint_type != "fixed"
128
+ else {}
129
+ ),
130
+ **(
131
+ {
132
+ "limit": {
133
+ **(
134
+ {"@effort": joint.axis.limit.effort}
135
+ if joint.axis.limit.effort is not None
136
+ else (
137
+ {"@effort": np.finfo(np.float32).max}
138
+ if urdf_joint_type
139
+ in {"revolute", "prismatic", "continuous"}
140
+ else {}
141
+ )
142
+ ),
143
+ **(
144
+ {"@velocity": joint.axis.limit.velocity}
145
+ if joint.axis.limit.velocity is not None
146
+ else (
147
+ {"@velocity": np.finfo(np.float32).max}
148
+ if urdf_joint_type
149
+ in {"revolute", "prismatic", "continuous"}
150
+ else {}
151
+ )
152
+ ),
153
+ **(
154
+ {"@lower": joint.axis.limit.lower}
155
+ if joint.axis.limit.lower is not None
156
+ and not np.isinf(joint.axis.limit.lower)
157
+ and urdf_joint_type in {"revolute", "prismatic"}
158
+ else {}
159
+ ),
160
+ **(
161
+ {"@upper": joint.axis.limit.upper}
162
+ if joint.axis.limit.upper is not None
163
+ and not np.isinf(joint.axis.limit.upper)
164
+ and urdf_joint_type in {"revolute", "prismatic"}
165
+ else {}
166
+ ),
167
+ },
168
+ }
169
+ if joint.axis is not None
170
+ and joint.axis.limit is not None
171
+ and urdf_joint_type != "fixed"
172
+ else {}
173
+ ),
174
+ # mimic: does not have any SDF corresponding element
175
+ # safety_controller: does not have any SDF corresponding element
176
+ }
177
+
59
178
  def to_urdf_string(self, sdf: rod.Sdf | rod.Model) -> str:
60
179
  """
61
180
  Convert an in-memory SDF model to a URDF string.
@@ -298,88 +417,7 @@ class UrdfExporter(abc.ABC):
298
417
  + extra_links_from_frames,
299
418
  # http://wiki.ros.org/urdf/XML/joint
300
419
  "joint": [
301
- {
302
- "@name": j.name,
303
- "@type": j.type,
304
- "origin": {
305
- "@xyz": " ".join(map(str, j.pose.xyz)),
306
- "@rpy": " ".join(map(str, j.pose.rpy)),
307
- },
308
- "parent": {"@link": j.parent},
309
- "child": {"@link": j.child},
310
- **(
311
- {"axis": {"@xyz": " ".join(map(str, j.axis.xyz.xyz))}}
312
- if j.axis is not None
313
- and j.axis.xyz is not None
314
- and j.type != "fixed"
315
- else {}
316
- ),
317
- # calibration: does not have any SDF corresponding element
318
- **(
319
- {
320
- "dynamics": {
321
- **(
322
- {"@damping": j.axis.dynamics.damping}
323
- if j.axis.dynamics.damping is not None
324
- else {}
325
- ),
326
- **(
327
- {"@friction": j.axis.dynamics.friction}
328
- if j.axis.dynamics.friction is not None
329
- else {}
330
- ),
331
- }
332
- }
333
- if j.axis is not None
334
- and j.axis.dynamics is not None
335
- and {j.axis.dynamics.damping, j.axis.dynamics.friction}
336
- != {None}
337
- and j.type != "fixed"
338
- else {}
339
- ),
340
- **(
341
- {
342
- "limit": {
343
- **(
344
- {"@effort": j.axis.limit.effort}
345
- if j.axis.limit.effort is not None
346
- else (
347
- {"@effort": np.finfo(np.float32).max}
348
- if j.type in {"revolute", "prismatic"}
349
- else {}
350
- )
351
- ),
352
- **(
353
- {"@velocity": j.axis.limit.velocity}
354
- if j.axis.limit.velocity is not None
355
- else (
356
- {"@velocity": np.finfo(np.float32).max}
357
- if j.type in {"revolute", "prismatic"}
358
- else {}
359
- )
360
- ),
361
- **(
362
- {"@lower": j.axis.limit.lower}
363
- if j.axis.limit.lower is not None
364
- and j.type in {"revolute", "prismatic"}
365
- else {}
366
- ),
367
- **(
368
- {"@upper": j.axis.limit.upper}
369
- if j.axis.limit.upper is not None
370
- and j.type in {"revolute", "prismatic"}
371
- else {}
372
- ),
373
- },
374
- }
375
- if j.axis is not None
376
- and j.axis.limit is not None
377
- and j.type != "fixed"
378
- else {}
379
- ),
380
- # mimic: does not have any SDF corresponding element
381
- # safety_controller: does not have any SDF corresponding element
382
- }
420
+ UrdfExporter._joint_to_urdf_dict(j)
383
421
  for j in model.joints()
384
422
  if j.type in UrdfExporter.SupportedSdfJointTypes
385
423
  ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rod
3
- Version: 0.4.2.dev3
3
+ Version: 0.4.2.dev7
4
4
  Summary: The ultimate Python tool for RObot Descriptions processing.
5
5
  Author-email: Diego Ferigo <dgferigo@gmail.com>
6
6
  Maintainer-email: Filippo Luca Ferretti <filippo.ferretti@iit.it>, Carlotta Sartore <carlotta.sartore@iit.it>
@@ -124,3 +124,67 @@ def test_urdf_exporter(robot: Robot) -> None:
124
124
  )
125
125
 
126
126
  assert locked_inertia_exported == pytest.approx(locked_inertia_original, abs=1e-6)
127
+
128
+
129
+ def test_continuous_joint_urdf_roundtrip() -> None:
130
+ """Test that continuous joints in URDF are preserved through SDF conversion."""
131
+ import xmltodict
132
+
133
+ # Create a URDF with a continuous joint
134
+ urdf_string = """<?xml version="1.0" encoding="utf-8"?>
135
+ <robot name="test_continuous">
136
+ <link name="base_link">
137
+ <inertial>
138
+ <origin xyz="0 0 0" rpy="0 0 0"/>
139
+ <mass value="1.0"/>
140
+ <inertia ixx="1.0" ixy="0.0" ixz="0.0" iyy="1.0" iyz="0.0" izz="1.0"/>
141
+ </inertial>
142
+ </link>
143
+ <link name="rotating_link">
144
+ <inertial>
145
+ <origin xyz="0 0 0" rpy="0 0 0"/>
146
+ <mass value="0.5"/>
147
+ <inertia ixx="1.0" ixy="0.0" ixz="0.0" iyy="1.0" iyz="0.0" izz="1.0"/>
148
+ </inertial>
149
+ </link>
150
+ <joint name="continuous_joint" type="continuous">
151
+ <origin xyz="0 0 0" rpy="0 0 0"/>
152
+ <parent link="base_link"/>
153
+ <child link="rotating_link"/>
154
+ <axis xyz="0 0 1"/>
155
+ <limit effort="100.0" velocity="10.0"/>
156
+ </joint>
157
+ </robot>
158
+ """
159
+
160
+ # Load the URDF (it gets converted to SDF internally by sdformat)
161
+ sdf = rod.Sdf.load(sdf=urdf_string, is_urdf=True)
162
+
163
+ # Export back to URDF
164
+ exporter = rod.urdf.exporter.UrdfExporter(pretty=True)
165
+ exported_urdf_string = exporter.to_urdf_string(sdf=sdf)
166
+
167
+ # Parse the exported URDF to check the joint properties
168
+ urdf_dict = xmltodict.parse(exported_urdf_string)
169
+ joint = urdf_dict["robot"]["joint"]
170
+
171
+ # Verify the joint type is continuous (not revolute)
172
+ assert (
173
+ joint["@type"] == "continuous"
174
+ ), f"Expected joint type 'continuous' after roundtrip, got '{joint['@type']}'"
175
+
176
+ # Verify NO upper/lower limits are present
177
+ assert (
178
+ "@upper" not in joint["limit"]
179
+ ), "Continuous joint should NOT have an upper position limit after roundtrip"
180
+ assert (
181
+ "@lower" not in joint["limit"]
182
+ ), "Continuous joint should NOT have a lower position limit after roundtrip"
183
+
184
+ # Verify effort and velocity limits are still present
185
+ assert (
186
+ "@effort" in joint["limit"]
187
+ ), "Continuous joint should still have an effort limit after roundtrip"
188
+ assert (
189
+ "@velocity" in joint["limit"]
190
+ ), "Continuous joint should still have a velocity limit after roundtrip"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes