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.
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/.github/workflows/ci_cd.yml +2 -2
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/PKG-INFO +1 -1
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/builder/primitive_builder.py +3 -2
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/builder/primitives.py +46 -26
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/sdf/link.py +41 -0
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod.egg-info/PKG-INFO +1 -1
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/tests/test_meshbuilder.py +10 -2
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/.github/workflows/style.yml +0 -0
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/.gitignore +0 -0
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/LICENSE +0 -0
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/README.md +0 -0
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/pyproject.toml +0 -0
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/setup.cfg +0 -0
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/setup.py +0 -0
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/__init__.py +0 -0
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/builder/__init__.py +0 -0
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/kinematics/__init__.py +0 -0
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/kinematics/kinematic_tree.py +0 -0
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/kinematics/tree_transforms.py +0 -0
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/logging.py +0 -0
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/pretty_printer.py +0 -0
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/sdf/__init__.py +0 -0
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/sdf/collision.py +0 -0
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/sdf/common.py +0 -0
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/sdf/element.py +0 -0
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/sdf/geometry.py +0 -0
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/sdf/joint.py +0 -0
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/sdf/material.py +0 -0
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/sdf/model.py +0 -0
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/sdf/physics.py +0 -0
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/sdf/scene.py +0 -0
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/sdf/sdf.py +0 -0
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/sdf/visual.py +0 -0
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/sdf/world.py +0 -0
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/tree/__init__.py +0 -0
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/tree/directed_tree.py +0 -0
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/tree/tree_elements.py +0 -0
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/urdf/__init__.py +0 -0
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/urdf/exporter.py +0 -0
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/utils/__init__.py +0 -0
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/utils/frame_convention.py +0 -0
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/utils/gazebo.py +0 -0
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/utils/resolve_frames.py +0 -0
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod/utils/resolve_uris.py +0 -0
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod.egg-info/SOURCES.txt +0 -0
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod.egg-info/dependency_links.txt +0 -0
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod.egg-info/not-zip-safe +0 -0
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod.egg-info/requires.txt +0 -0
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/src/rod.egg-info/top_level.txt +0 -0
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/tests/test_urdf_exporter.py +0 -0
- {rod-0.3.4.dev2 → rod-0.3.4.dev9}/tests/test_urdf_parsing.py +0 -0
- {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
|
|
@@ -12,8 +12,9 @@ from rod import logging
|
|
|
12
12
|
|
|
13
13
|
@dataclasses.dataclass
|
|
14
14
|
class PrimitiveBuilder(abc.ABC):
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
66
|
-
|
|
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
|
-
|
|
79
|
-
self.
|
|
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
|
-
|
|
85
|
-
|
|
91
|
+
# Resolve the mesh URI.
|
|
92
|
+
mesh_path = resolve_robotics_uri_py.resolve_robotics_uri(uri=str(self.mesh_uri))
|
|
86
93
|
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|