rod 0.3.4.dev2__tar.gz → 0.3.4.dev9__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 (52) hide show
  1. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/.github/workflows/ci_cd.yml +2 -2
  2. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/PKG-INFO +1 -1
  3. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/builder/primitive_builder.py +3 -2
  4. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/builder/primitives.py +46 -26
  5. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/sdf/link.py +41 -0
  6. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod.egg-info/PKG-INFO +1 -1
  7. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/tests/test_meshbuilder.py +10 -2
  8. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/.github/workflows/style.yml +0 -0
  9. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/.gitignore +0 -0
  10. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/LICENSE +0 -0
  11. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/README.md +0 -0
  12. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/pyproject.toml +0 -0
  13. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/setup.cfg +0 -0
  14. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/setup.py +0 -0
  15. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/__init__.py +0 -0
  16. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/builder/__init__.py +0 -0
  17. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/kinematics/__init__.py +0 -0
  18. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/kinematics/kinematic_tree.py +0 -0
  19. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/kinematics/tree_transforms.py +0 -0
  20. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/logging.py +0 -0
  21. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/pretty_printer.py +0 -0
  22. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/sdf/__init__.py +0 -0
  23. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/sdf/collision.py +0 -0
  24. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/sdf/common.py +0 -0
  25. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/sdf/element.py +0 -0
  26. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/sdf/geometry.py +0 -0
  27. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/sdf/joint.py +0 -0
  28. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/sdf/material.py +0 -0
  29. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/sdf/model.py +0 -0
  30. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/sdf/physics.py +0 -0
  31. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/sdf/scene.py +0 -0
  32. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/sdf/sdf.py +0 -0
  33. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/sdf/visual.py +0 -0
  34. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/sdf/world.py +0 -0
  35. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/tree/__init__.py +0 -0
  36. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/tree/directed_tree.py +0 -0
  37. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/tree/tree_elements.py +0 -0
  38. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/urdf/__init__.py +0 -0
  39. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/urdf/exporter.py +0 -0
  40. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/utils/__init__.py +0 -0
  41. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/utils/frame_convention.py +0 -0
  42. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/utils/gazebo.py +0 -0
  43. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/utils/resolve_frames.py +0 -0
  44. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/utils/resolve_uris.py +0 -0
  45. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod.egg-info/SOURCES.txt +0 -0
  46. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod.egg-info/dependency_links.txt +0 -0
  47. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod.egg-info/not-zip-safe +0 -0
  48. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod.egg-info/requires.txt +0 -0
  49. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod.egg-info/top_level.txt +0 -0
  50. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/tests/test_urdf_exporter.py +0 -0
  51. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/tests/test_urdf_parsing.py +0 -0
  52. {rod-0.3.4.dev2 → rod-0.3.4.dev9}/tests/utils_models.py +0 -0
@@ -165,6 +165,8 @@ jobs:
165
165
  name: Publish to PyPI
166
166
  needs: test
167
167
  runs-on: ubuntu-latest
168
+ permissions:
169
+ id-token: write
168
170
 
169
171
  steps:
170
172
 
@@ -184,6 +186,4 @@ jobs:
184
186
  (github.event_name == 'release' && github.event.action == 'published'))
185
187
  uses: pypa/gh-action-pypi-publish@release/v1
186
188
  with:
187
- user: __token__
188
- password: ${{ secrets.PYPI_TOKEN }}
189
189
  skip_existing: true
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: rod
3
- Version: 0.3.4.dev2
3
+ Version: 0.3.4.dev9
4
4
  Summary: The ultimate Python tool for RObot Descriptions processing.
5
5
  Home-page: https://github.com/ami-iit/rod
6
6
  Author: Diego Ferigo
@@ -12,8 +12,9 @@ from rod import logging
12
12
 
13
13
  @dataclasses.dataclass
14
14
  class PrimitiveBuilder(abc.ABC):
15
- name: str
16
- mass: float
15
+
16
+ name: str = dataclasses.field(kw_only=True)
17
+ mass: float = dataclasses.field(kw_only=True)
17
18
 
18
19
  element: rod.Model | rod.Link | rod.Inertial | rod.Collision | rod.Visual = (
19
20
  dataclasses.field(
@@ -1,10 +1,13 @@
1
1
  import dataclasses
2
2
  import pathlib
3
3
 
4
+ import numpy as np
5
+ import numpy.typing as npt
6
+ import resolve_robotics_uri_py
4
7
  import trimesh
5
- from numpy.typing import NDArray
6
8
 
7
9
  import rod
10
+ from rod import logging
8
11
  from rod.builder.primitive_builder import PrimitiveBuilder
9
12
 
10
13
 
@@ -62,8 +65,13 @@ class CylinderBuilder(PrimitiveBuilder):
62
65
 
63
66
  @dataclasses.dataclass
64
67
  class MeshBuilder(PrimitiveBuilder):
65
- mesh_path: str | pathlib.Path
66
- scale: NDArray
68
+
69
+ mesh_uri: str | pathlib.Path
70
+
71
+ scale: npt.NDArray = dataclasses.field(default_factory=lambda: np.ones(3))
72
+
73
+ mass: float | None = dataclasses.field(default=None, kw_only=True)
74
+ inertia_tensor: npt.NDArray | None = dataclasses.field(default=None, kw_only=True)
67
75
 
68
76
  def __post_init__(self) -> None:
69
77
  """
@@ -74,34 +82,46 @@ class MeshBuilder(PrimitiveBuilder):
74
82
  AssertionError: If the scale is not a 3D vector.
75
83
  TypeError: If the mesh_path is not a str or pathlib.Path.
76
84
  """
85
+ # Adjust the shape of the scale.
86
+ self.scale = self.scale.squeeze()
77
87
 
78
- mesh_path = (
79
- self.mesh_path
80
- if isinstance(self.mesh_path, pathlib.Path)
81
- else pathlib.Path(self.mesh_path)
82
- )
88
+ if self.scale.shape != (3,):
89
+ raise RuntimeError(f"Scale must be a 3D vector, got '{self.scale.shape}'")
83
90
 
84
- if not mesh_path.is_file():
85
- raise FileNotFoundError(f"Mesh file not found at {self.mesh_path}")
91
+ # Resolve the mesh URI.
92
+ mesh_path = resolve_robotics_uri_py.resolve_robotics_uri(uri=str(self.mesh_uri))
86
93
 
87
- self.mesh: trimesh.base.Trimesh = trimesh.load_mesh(
88
- file_obj=mesh_path,
89
- )
94
+ # Build the trimesh object from the mesh path.
95
+ self.mesh: trimesh.base.Trimesh = trimesh.load_mesh(file_obj=mesh_path)
96
+
97
+ # Populate the mass from the mesh if it was not provided externally.
98
+ if self.mass is None:
99
+
100
+ if self.mesh.is_watertight:
101
+ self.mass = self.mesh.mass
102
+
103
+ else:
104
+ msg = "Mesh '{}' is not watertight. Using a dummy mass value."
105
+ logging.warning(msg.format(self.mesh_uri))
106
+ self.mass = 1.0
90
107
 
91
- assert self.scale.shape == (
92
- 3,
93
- ), f"Scale must be a 3D vector, got {self.scale.shape}"
108
+ # Populate the inertia tensor from the mesh if it was not provided externally.
109
+ if self.inertia_tensor is None:
110
+
111
+ if self.mesh.is_watertight:
112
+ self.inertia_tensor = self.mesh.moment_inertia
113
+
114
+ else:
115
+ msg = "Mesh '{}' is not watertight. Using a dummy inertia tensor."
116
+ logging.warning(msg.format(self.mesh_uri))
117
+ self.inertia_tensor = np.eye(3)
94
118
 
95
119
  def _inertia(self) -> rod.Inertia:
96
- inertia = self.mesh.moment_inertia
97
- return rod.Inertia(
98
- ixx=inertia[0, 0],
99
- ixy=inertia[0, 1],
100
- ixz=inertia[0, 2],
101
- iyy=inertia[1, 1],
102
- iyz=inertia[1, 2],
103
- izz=inertia[2, 2],
104
- )
120
+
121
+ return rod.Inertia.from_inertia_tensor(inertia_tensor=self.inertia_tensor)
105
122
 
106
123
  def _geometry(self) -> rod.Geometry:
107
- return rod.Geometry(mesh=rod.Mesh(uri=str(self.mesh_path), scale=self.scale))
124
+
125
+ return rod.Geometry(
126
+ mesh=rod.Mesh(uri=str(self.mesh_uri), scale=self.scale.tolist())
127
+ )
@@ -1,9 +1,13 @@
1
+ from __future__ import annotations
2
+
1
3
  import dataclasses
2
4
 
3
5
  import mashumaro
4
6
  import numpy as np
5
7
  import numpy.typing as npt
6
8
 
9
+ from rod import logging
10
+
7
11
  from .collision import Collision
8
12
  from .common import Pose
9
13
  from .element import Element
@@ -12,6 +16,7 @@ from .visual import Visual
12
16
 
13
17
  @dataclasses.dataclass
14
18
  class Inertia(Element):
19
+
15
20
  ixx: float = dataclasses.field(
16
21
  default=1.0,
17
22
  metadata=mashumaro.field_options(serialize=Element.serialize_float),
@@ -42,6 +47,42 @@ class Inertia(Element):
42
47
  metadata=mashumaro.field_options(serialize=Element.serialize_float),
43
48
  )
44
49
 
50
+ @staticmethod
51
+ def from_inertia_tensor(
52
+ inertia_tensor: npt.NDArray, validate: bool = True
53
+ ) -> Inertia:
54
+
55
+ inertia_tensor = inertia_tensor.squeeze()
56
+
57
+ if inertia_tensor.shape != (3, 3):
58
+ raise ValueError(f"Expected shape (3, 3), got {inertia_tensor.shape}")
59
+
60
+ # Extract the diagonal terms.
61
+ I1, I2, I3 = np.diag(inertia_tensor)
62
+
63
+ # Check if the inertia tensor meets the triangular inequality.
64
+ valid = True
65
+ valid = valid and I1 + I2 >= I3
66
+ valid = valid and I1 + I3 >= I2
67
+ valid = valid and I2 + I3 >= I1
68
+
69
+ if not valid:
70
+ msg = "Inertia tensor does not meet the triangular inequality"
71
+
72
+ if not validate:
73
+ logging.warning(msg)
74
+ else:
75
+ raise ValueError(msg)
76
+
77
+ return Inertia(
78
+ ixx=float(inertia_tensor[0, 0]),
79
+ ixy=float(inertia_tensor[0, 1]),
80
+ ixz=float(inertia_tensor[0, 2]),
81
+ iyy=float(inertia_tensor[1, 1]),
82
+ iyz=float(inertia_tensor[1, 2]),
83
+ izz=float(inertia_tensor[2, 2]),
84
+ )
85
+
45
86
  def matrix(self) -> npt.NDArray:
46
87
  return np.array(
47
88
  [
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: rod
3
- Version: 0.3.4.dev2
3
+ Version: 0.3.4.dev9
4
4
  Summary: The ultimate Python tool for RObot Descriptions processing.
5
5
  Home-page: https://github.com/ami-iit/rod
6
6
  Author: Diego Ferigo
@@ -1,6 +1,7 @@
1
1
  import tempfile
2
2
 
3
3
  import numpy as np
4
+ import pytest
4
5
  import trimesh
5
6
 
6
7
  from rod.builder.primitives import MeshBuilder
@@ -20,11 +21,18 @@ def test_builder_creation():
20
21
 
21
22
  builder = MeshBuilder(
22
23
  name="test_mesh",
23
- mesh_path=fp.name,
24
+ mesh_uri=fp.name,
24
25
  mass=1.0,
25
26
  scale=np.array([1.0, 1.0, 1.0]),
26
27
  )
27
28
 
29
+ # Check that the builder can build a correct link.
30
+ # Note that the URI is not valid since it's a temporary file.
31
+ link = builder.build_link().add_inertial().add_visual().add_collision().build()
32
+ assert link.collision is not None
33
+ assert link.collision.geometry.mesh is not None
34
+ assert link.collision.geometry.mesh.scale == pytest.approx([1, 1, 1])
35
+
28
36
  assert (
29
37
  builder.mesh.vertices.shape == mesh.vertices.shape
30
38
  ), f"{builder.mesh.vertices.shape} != {mesh.vertices.shape}"
@@ -56,7 +64,7 @@ def test_builder_creation_custom_mesh():
56
64
 
57
65
  builder = MeshBuilder(
58
66
  name="test_mesh",
59
- mesh_path=fp.name,
67
+ mesh_uri=fp.name,
60
68
  mass=1.0,
61
69
  scale=np.array([1.0, 1.0, 1.0]),
62
70
  )
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