riggen 0.2.1.dev0__tar.gz → 0.6.0__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.
- {riggen-0.2.1.dev0 → riggen-0.6.0}/Cargo.lock +6 -6
- {riggen-0.2.1.dev0 → riggen-0.6.0}/Cargo.toml +1 -1
- {riggen-0.2.1.dev0 → riggen-0.6.0}/PKG-INFO +22 -10
- {riggen-0.2.1.dev0 → riggen-0.6.0}/README.md +21 -9
- {riggen-0.2.1.dev0 → riggen-0.6.0}/crates/riggen-core/src/command.rs +313 -12
- {riggen-0.2.1.dev0 → riggen-0.6.0}/crates/riggen-core/src/file.rs +11 -0
- {riggen-0.2.1.dev0 → riggen-0.6.0}/crates/riggen-core/src/robot.rs +47 -0
- {riggen-0.2.1.dev0 → riggen-0.6.0}/crates/riggen-core/src/validate.rs +831 -11
- {riggen-0.2.1.dev0 → riggen-0.6.0}/crates/riggen-export/src/export.rs +25 -1
- {riggen-0.2.1.dev0 → riggen-0.6.0}/crates/riggen-export/src/import.rs +12 -0
- {riggen-0.2.1.dev0 → riggen-0.6.0}/crates/riggen-export/src/mjcf.rs +48 -18
- {riggen-0.2.1.dev0 → riggen-0.6.0}/crates/riggen-export/src/mjcf_in.rs +5 -0
- {riggen-0.2.1.dev0 → riggen-0.6.0}/crates/riggen-export/src/resolve.rs +338 -22
- {riggen-0.2.1.dev0 → riggen-0.6.0}/crates/riggen-export/src/urdf_in.rs +1 -1
- {riggen-0.2.1.dev0 → riggen-0.6.0}/crates/riggen-py/src/robot.rs +20 -3
- {riggen-0.2.1.dev0 → riggen-0.6.0}/python/riggen/_riggen.pyi +7 -1
- {riggen-0.2.1.dev0 → riggen-0.6.0}/python/riggen/robot.py +20 -4
- {riggen-0.2.1.dev0 → riggen-0.6.0}/LICENSE-APACHE +0 -0
- {riggen-0.2.1.dev0 → riggen-0.6.0}/LICENSE-MIT +0 -0
- {riggen-0.2.1.dev0 → riggen-0.6.0}/crates/riggen-core/Cargo.toml +0 -0
- {riggen-0.2.1.dev0 → riggen-0.6.0}/crates/riggen-core/src/fk.rs +0 -0
- {riggen-0.2.1.dev0 → riggen-0.6.0}/crates/riggen-core/src/history.rs +0 -0
- {riggen-0.2.1.dev0 → riggen-0.6.0}/crates/riggen-core/src/ids.rs +0 -0
- {riggen-0.2.1.dev0 → riggen-0.6.0}/crates/riggen-core/src/inertial.rs +0 -0
- {riggen-0.2.1.dev0 → riggen-0.6.0}/crates/riggen-core/src/lib.rs +0 -0
- {riggen-0.2.1.dev0 → riggen-0.6.0}/crates/riggen-core/src/pose.rs +0 -0
- {riggen-0.2.1.dev0 → riggen-0.6.0}/crates/riggen-export/Cargo.toml +0 -0
- {riggen-0.2.1.dev0 → riggen-0.6.0}/crates/riggen-export/src/fk_samples.rs +0 -0
- {riggen-0.2.1.dev0 → riggen-0.6.0}/crates/riggen-export/src/lib.rs +0 -0
- {riggen-0.2.1.dev0 → riggen-0.6.0}/crates/riggen-export/src/mesh_store.rs +0 -0
- {riggen-0.2.1.dev0 → riggen-0.6.0}/crates/riggen-export/src/mjcf_compose.rs +0 -0
- {riggen-0.2.1.dev0 → riggen-0.6.0}/crates/riggen-export/src/sdf.rs +0 -0
- {riggen-0.2.1.dev0 → riggen-0.6.0}/crates/riggen-export/src/test_util.rs +0 -0
- {riggen-0.2.1.dev0 → riggen-0.6.0}/crates/riggen-export/src/urdf.rs +0 -0
- {riggen-0.2.1.dev0 → riggen-0.6.0}/crates/riggen-export/src/xml.rs +0 -0
- {riggen-0.2.1.dev0 → riggen-0.6.0}/crates/riggen-mesh/Cargo.toml +0 -0
- {riggen-0.2.1.dev0 → riggen-0.6.0}/crates/riggen-mesh/src/aabb.rs +0 -0
- {riggen-0.2.1.dev0 → riggen-0.6.0}/crates/riggen-mesh/src/decomp.rs +0 -0
- {riggen-0.2.1.dev0 → riggen-0.6.0}/crates/riggen-mesh/src/error.rs +0 -0
- {riggen-0.2.1.dev0 → riggen-0.6.0}/crates/riggen-mesh/src/feature.rs +0 -0
- {riggen-0.2.1.dev0 → riggen-0.6.0}/crates/riggen-mesh/src/fit.rs +0 -0
- {riggen-0.2.1.dev0 → riggen-0.6.0}/crates/riggen-mesh/src/hull.rs +0 -0
- {riggen-0.2.1.dev0 → riggen-0.6.0}/crates/riggen-mesh/src/lib.rs +0 -0
- {riggen-0.2.1.dev0 → riggen-0.6.0}/crates/riggen-mesh/src/mass.rs +0 -0
- {riggen-0.2.1.dev0 → riggen-0.6.0}/crates/riggen-mesh/src/msh.rs +0 -0
- {riggen-0.2.1.dev0 → riggen-0.6.0}/crates/riggen-mesh/src/obj.rs +0 -0
- {riggen-0.2.1.dev0 → riggen-0.6.0}/crates/riggen-mesh/src/ray.rs +0 -0
- {riggen-0.2.1.dev0 → riggen-0.6.0}/crates/riggen-mesh/src/stl.rs +0 -0
- {riggen-0.2.1.dev0 → riggen-0.6.0}/crates/riggen-mesh/src/tri_mesh.rs +0 -0
- {riggen-0.2.1.dev0 → riggen-0.6.0}/crates/riggen-py/Cargo.toml +0 -0
- {riggen-0.2.1.dev0 → riggen-0.6.0}/crates/riggen-py/src/doc.rs +0 -0
- {riggen-0.2.1.dev0 → riggen-0.6.0}/crates/riggen-py/src/errors.rs +0 -0
- {riggen-0.2.1.dev0 → riggen-0.6.0}/crates/riggen-py/src/lib.rs +0 -0
- {riggen-0.2.1.dev0 → riggen-0.6.0}/pyproject.toml +0 -0
- {riggen-0.2.1.dev0 → riggen-0.6.0}/python/riggen/__init__.py +0 -0
- {riggen-0.2.1.dev0 → riggen-0.6.0}/python/riggen/__main__.py +0 -0
- {riggen-0.2.1.dev0 → riggen-0.6.0}/python/riggen/errors.py +0 -0
- {riggen-0.2.1.dev0 → riggen-0.6.0}/python/riggen/py.typed +0 -0
- {riggen-0.2.1.dev0 → riggen-0.6.0}/python/riggen/show.py +0 -0
|
@@ -3223,7 +3223,7 @@ version = "0.0.1"
|
|
|
3223
3223
|
|
|
3224
3224
|
[[package]]
|
|
3225
3225
|
name = "riggen-app"
|
|
3226
|
-
version = "0.
|
|
3226
|
+
version = "0.6.0"
|
|
3227
3227
|
dependencies = [
|
|
3228
3228
|
"console_error_panic_hook",
|
|
3229
3229
|
"eframe",
|
|
@@ -3250,7 +3250,7 @@ dependencies = [
|
|
|
3250
3250
|
|
|
3251
3251
|
[[package]]
|
|
3252
3252
|
name = "riggen-core"
|
|
3253
|
-
version = "0.
|
|
3253
|
+
version = "0.6.0"
|
|
3254
3254
|
dependencies = [
|
|
3255
3255
|
"riggen-mesh",
|
|
3256
3256
|
"serde",
|
|
@@ -3259,7 +3259,7 @@ dependencies = [
|
|
|
3259
3259
|
|
|
3260
3260
|
[[package]]
|
|
3261
3261
|
name = "riggen-export"
|
|
3262
|
-
version = "0.
|
|
3262
|
+
version = "0.6.0"
|
|
3263
3263
|
dependencies = [
|
|
3264
3264
|
"quick-xml 0.36.2",
|
|
3265
3265
|
"riggen-core",
|
|
@@ -3271,7 +3271,7 @@ dependencies = [
|
|
|
3271
3271
|
|
|
3272
3272
|
[[package]]
|
|
3273
3273
|
name = "riggen-mesh"
|
|
3274
|
-
version = "0.
|
|
3274
|
+
version = "0.6.0"
|
|
3275
3275
|
dependencies = [
|
|
3276
3276
|
"glam 0.30.10",
|
|
3277
3277
|
"parry3d-f64",
|
|
@@ -3281,7 +3281,7 @@ dependencies = [
|
|
|
3281
3281
|
|
|
3282
3282
|
[[package]]
|
|
3283
3283
|
name = "riggen-py"
|
|
3284
|
-
version = "0.
|
|
3284
|
+
version = "0.6.0"
|
|
3285
3285
|
dependencies = [
|
|
3286
3286
|
"pyo3",
|
|
3287
3287
|
"riggen-core",
|
|
@@ -3293,7 +3293,7 @@ dependencies = [
|
|
|
3293
3293
|
|
|
3294
3294
|
[[package]]
|
|
3295
3295
|
name = "riggen-viewport"
|
|
3296
|
-
version = "0.
|
|
3296
|
+
version = "0.6.0"
|
|
3297
3297
|
dependencies = [
|
|
3298
3298
|
"bytemuck",
|
|
3299
3299
|
"egui",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: riggen
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Classifier: Development Status :: 3 - Alpha
|
|
5
5
|
Classifier: Intended Audience :: Science/Research
|
|
6
6
|
Classifier: Topic :: Scientific/Engineering
|
|
@@ -29,7 +29,7 @@ Project-URL: Repository, https://github.com/Divelix/riggen
|
|
|
29
29
|
Drop meshes in, get a simulation-ready MJCF, URDF or SDF out — in a
|
|
30
30
|
window, or from ten lines of Python.
|
|
31
31
|
|
|
32
|
-

|
|
32
|
+

|
|
33
33
|
|
|
34
34
|
## Try it in the browser
|
|
35
35
|
|
|
@@ -64,11 +64,21 @@ without a wheel, `pip install` builds the SDK from source with `cargo` on
|
|
|
64
64
|
tree on the left, the viewport with the robot. Drag with the left
|
|
65
65
|
button to orbit and with the right to pan (shift+left pans too, for a
|
|
66
66
|
trackpad; the middle button orbits and shift+middle pans), zoom with the
|
|
67
|
-
wheel, `Home` to frame everything. The
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
wheel, `Home` to frame everything. The **ViewCube** in the top-right
|
|
68
|
+
says which way you are looking, and the red, green and blue arms on its
|
|
69
|
+
corner are X, Y and Z: click a face, edge or corner to fly to that view,
|
|
70
|
+
drag it to orbit, click an arrow round it to turn the view 15°, click
|
|
71
|
+
the house to frame everything, and the button under it switches
|
|
72
|
+
perspective and orthographic. `W A S D E Q`
|
|
73
|
+
walk the camera *into* the assembly — the point it orbits around comes
|
|
74
|
+
with you, so a joint buried in a shell can be put in front of you and
|
|
75
|
+
then turned around locally. The arm stands on a **ground grid** at
|
|
76
|
+
z = 0, so a part at the origin reads as resting on something. The six
|
|
77
|
+
buttons left of the cube are the **visibility row**: ground, joints, joint
|
|
78
|
+
names, frames, links, collision geometry. Switching one off takes it
|
|
79
|
+
out of the picture *and* out of the cursor's reach, so with joints off
|
|
80
|
+
you get the robot and nothing else — the status bar says what is
|
|
81
|
+
hidden.
|
|
72
82
|
2. Pose it: drag a joint's bar in the tree, or turn the wheel over it —
|
|
73
83
|
or over the joint's glyph in the viewport, which steps it by 5° (1°
|
|
74
84
|
with shift) instead of zooming. A click on a glyph selects the joint;
|
|
@@ -85,7 +95,7 @@ without a wheel, `pip install` builds the SDK from source with `cargo` on
|
|
|
85
95
|
back.
|
|
86
96
|
4. **File › Export…**, tick the formats you want (all three by default),
|
|
87
97
|
choose a directory. The dialog lists anything that would stop the
|
|
88
|
-
export (a link with no mass, a joint with no axis) and writes
|
|
98
|
+
export (a moving link with no mass, a joint with no axis) and writes
|
|
89
99
|
`arm.xml`, `arm.urdf` and `arm.sdf` beside `meshes/*.stl` when there is
|
|
90
100
|
nothing.
|
|
91
101
|
5. Load it:
|
|
@@ -122,7 +132,8 @@ mount — which Move and Rotate land on a picked feature the same way.
|
|
|
122
132
|
`.sdf`, and the forward kinematics of both agree with riggen's (those
|
|
123
133
|
are CI jobs, not hopes).
|
|
124
134
|
- **Imports**: an existing URDF or MJCF — `package://` paths resolved
|
|
125
|
-
beside the file,
|
|
135
|
+
beside the file, and a package that is not found asks for its folder
|
|
136
|
+
(`--package NAME=DIR` on the command line), MuJoCo's `<default>` classes and degrees understood, a
|
|
126
137
|
model spelled across several files opened through its main one
|
|
127
138
|
(`<include>` and MuJoCo 3's `<frame>` are resolved on the way in) — to
|
|
128
139
|
fix and convert it. Whatever the file held that the document cannot is
|
|
@@ -136,7 +147,7 @@ mount — which Move and Rotate land on a picked feature the same way.
|
|
|
136
147
|
usage:
|
|
137
148
|
riggen [FILE...] open a .riggen document, or drop meshes (.stl, .obj) as links
|
|
138
149
|
riggen --example arm open the bundled sample arm
|
|
139
|
-
riggen --export mjcf|urdf|sdf|both|all --out DIR [--fk-samples] INPUT
|
|
150
|
+
riggen --export mjcf|urdf|sdf|both|all --out DIR [--fk-samples] [--package NAME=DIR]... INPUT
|
|
140
151
|
write INPUT's export to DIR without opening a window
|
|
141
152
|
|
|
142
153
|
options:
|
|
@@ -144,6 +155,7 @@ options:
|
|
|
144
155
|
--export FORMAT headless export of INPUT (.riggen, .urdf or .xml): mjcf, urdf, sdf, both or all
|
|
145
156
|
--out DIR where --export writes; created if missing
|
|
146
157
|
--fk-samples with --export: also write <name>.fk.json, five sampled joint configurations
|
|
158
|
+
--package NAME=DIR with --export: a .urdf's package://NAME/ meshes are under DIR; repeatable
|
|
147
159
|
--timing print the time from launch to the first frame on stderr
|
|
148
160
|
-h, --help print this help
|
|
149
161
|
-V, --version print the version and the git commit it was built from
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
Drop meshes in, get a simulation-ready MJCF, URDF or SDF out — in a
|
|
8
8
|
window, or from ten lines of Python.
|
|
9
9
|
|
|
10
|
-

|
|
10
|
+

|
|
11
11
|
|
|
12
12
|
## Try it in the browser
|
|
13
13
|
|
|
@@ -42,11 +42,21 @@ without a wheel, `pip install` builds the SDK from source with `cargo` on
|
|
|
42
42
|
tree on the left, the viewport with the robot. Drag with the left
|
|
43
43
|
button to orbit and with the right to pan (shift+left pans too, for a
|
|
44
44
|
trackpad; the middle button orbits and shift+middle pans), zoom with the
|
|
45
|
-
wheel, `Home` to frame everything. The
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
wheel, `Home` to frame everything. The **ViewCube** in the top-right
|
|
46
|
+
says which way you are looking, and the red, green and blue arms on its
|
|
47
|
+
corner are X, Y and Z: click a face, edge or corner to fly to that view,
|
|
48
|
+
drag it to orbit, click an arrow round it to turn the view 15°, click
|
|
49
|
+
the house to frame everything, and the button under it switches
|
|
50
|
+
perspective and orthographic. `W A S D E Q`
|
|
51
|
+
walk the camera *into* the assembly — the point it orbits around comes
|
|
52
|
+
with you, so a joint buried in a shell can be put in front of you and
|
|
53
|
+
then turned around locally. The arm stands on a **ground grid** at
|
|
54
|
+
z = 0, so a part at the origin reads as resting on something. The six
|
|
55
|
+
buttons left of the cube are the **visibility row**: ground, joints, joint
|
|
56
|
+
names, frames, links, collision geometry. Switching one off takes it
|
|
57
|
+
out of the picture *and* out of the cursor's reach, so with joints off
|
|
58
|
+
you get the robot and nothing else — the status bar says what is
|
|
59
|
+
hidden.
|
|
50
60
|
2. Pose it: drag a joint's bar in the tree, or turn the wheel over it —
|
|
51
61
|
or over the joint's glyph in the viewport, which steps it by 5° (1°
|
|
52
62
|
with shift) instead of zooming. A click on a glyph selects the joint;
|
|
@@ -63,7 +73,7 @@ without a wheel, `pip install` builds the SDK from source with `cargo` on
|
|
|
63
73
|
back.
|
|
64
74
|
4. **File › Export…**, tick the formats you want (all three by default),
|
|
65
75
|
choose a directory. The dialog lists anything that would stop the
|
|
66
|
-
export (a link with no mass, a joint with no axis) and writes
|
|
76
|
+
export (a moving link with no mass, a joint with no axis) and writes
|
|
67
77
|
`arm.xml`, `arm.urdf` and `arm.sdf` beside `meshes/*.stl` when there is
|
|
68
78
|
nothing.
|
|
69
79
|
5. Load it:
|
|
@@ -100,7 +110,8 @@ mount — which Move and Rotate land on a picked feature the same way.
|
|
|
100
110
|
`.sdf`, and the forward kinematics of both agree with riggen's (those
|
|
101
111
|
are CI jobs, not hopes).
|
|
102
112
|
- **Imports**: an existing URDF or MJCF — `package://` paths resolved
|
|
103
|
-
beside the file,
|
|
113
|
+
beside the file, and a package that is not found asks for its folder
|
|
114
|
+
(`--package NAME=DIR` on the command line), MuJoCo's `<default>` classes and degrees understood, a
|
|
104
115
|
model spelled across several files opened through its main one
|
|
105
116
|
(`<include>` and MuJoCo 3's `<frame>` are resolved on the way in) — to
|
|
106
117
|
fix and convert it. Whatever the file held that the document cannot is
|
|
@@ -114,7 +125,7 @@ mount — which Move and Rotate land on a picked feature the same way.
|
|
|
114
125
|
usage:
|
|
115
126
|
riggen [FILE...] open a .riggen document, or drop meshes (.stl, .obj) as links
|
|
116
127
|
riggen --example arm open the bundled sample arm
|
|
117
|
-
riggen --export mjcf|urdf|sdf|both|all --out DIR [--fk-samples] INPUT
|
|
128
|
+
riggen --export mjcf|urdf|sdf|both|all --out DIR [--fk-samples] [--package NAME=DIR]... INPUT
|
|
118
129
|
write INPUT's export to DIR without opening a window
|
|
119
130
|
|
|
120
131
|
options:
|
|
@@ -122,6 +133,7 @@ options:
|
|
|
122
133
|
--export FORMAT headless export of INPUT (.riggen, .urdf or .xml): mjcf, urdf, sdf, both or all
|
|
123
134
|
--out DIR where --export writes; created if missing
|
|
124
135
|
--fk-samples with --export: also write <name>.fk.json, five sampled joint configurations
|
|
136
|
+
--package NAME=DIR with --export: a .urdf's package://NAME/ meshes are under DIR; repeatable
|
|
125
137
|
--timing print the time from launch to the first frame on stderr
|
|
126
138
|
-h, --help print this help
|
|
127
139
|
-V, --version print the version and the git commit it was built from
|
|
@@ -47,7 +47,8 @@ pub enum Command {
|
|
|
47
47
|
/// Moves a joint's frame **without moving anything in the world**: the
|
|
48
48
|
/// new `origin` (the child link frame in the parent frame) and `axis`
|
|
49
49
|
/// (in the *new* child frame, since the joint frame is the child link
|
|
50
|
-
/// frame) are written, and the child's
|
|
50
|
+
/// frame) are written, and the child's visual and collision geom poses
|
|
51
|
+
/// (`Meshes` and `Primitives`), its own child joints'
|
|
51
52
|
/// origins, its frames and an `Override` inertial are all re-expressed
|
|
52
53
|
/// so no world pose in the zero configuration changes. Only the pivot
|
|
53
54
|
/// the joint turns about moves.
|
|
@@ -156,6 +157,13 @@ pub enum Command {
|
|
|
156
157
|
///
|
|
157
158
|
/// [`SetActuator`]: Command::SetActuator
|
|
158
159
|
SetActuators(Option<ActuatorSpec>),
|
|
160
|
+
/// Gives the material to every link [`Robot::unweighed_links`] names —
|
|
161
|
+
/// geometry, no material, and an inertial that needs a density it has
|
|
162
|
+
/// not got — in one history entry (ADR-0032 §5). The one-click answer to
|
|
163
|
+
/// an import full of links nothing weighs, shaped like `SetActuators`:
|
|
164
|
+
/// many links, one command, one undo. Refused for a material the
|
|
165
|
+
/// document has not got; a no-op when no link qualifies.
|
|
166
|
+
AssignMaterialToUnweighed(String),
|
|
159
167
|
}
|
|
160
168
|
|
|
161
169
|
/// What a command created, for the caller that selects it afterwards.
|
|
@@ -436,6 +444,25 @@ impl Command {
|
|
|
436
444
|
for geom in &mut link.visuals {
|
|
437
445
|
geom.pose = delta.compose(&geom.pose);
|
|
438
446
|
}
|
|
447
|
+
// Collision the document holds itself is in link frame
|
|
448
|
+
// too; the derived policies follow the visuals above.
|
|
449
|
+
match &mut link.collision {
|
|
450
|
+
CollisionPolicy::Meshes(geoms) => {
|
|
451
|
+
for geom in geoms {
|
|
452
|
+
geom.pose = delta.compose(&geom.pose);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
CollisionPolicy::Primitives(prims) => {
|
|
456
|
+
for prim in prims {
|
|
457
|
+
let pose = prim.pose_mut();
|
|
458
|
+
*pose = delta.compose(pose);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
CollisionPolicy::None
|
|
462
|
+
| CollisionPolicy::SameAsVisual
|
|
463
|
+
| CollisionPolicy::ConvexHull
|
|
464
|
+
| CollisionPolicy::ConvexDecomposition { .. } => {}
|
|
465
|
+
}
|
|
439
466
|
// A measured inertial is in link axes about `com`, and
|
|
440
467
|
// the link frame just moved under it (M3 has the UI).
|
|
441
468
|
if let InertialSpec::Override { com, inertia, .. } = &mut link.inertial {
|
|
@@ -651,6 +678,14 @@ impl Command {
|
|
|
651
678
|
);
|
|
652
679
|
}
|
|
653
680
|
}
|
|
681
|
+
Command::AssignMaterialToUnweighed(material) => {
|
|
682
|
+
if !robot.materials.contains_key(&material) {
|
|
683
|
+
return Err(EditError::UnknownMaterial(material));
|
|
684
|
+
}
|
|
685
|
+
for link in robot.unweighed_links() {
|
|
686
|
+
link_mut(robot, link)?.material = Some(material.clone());
|
|
687
|
+
}
|
|
688
|
+
}
|
|
654
689
|
}
|
|
655
690
|
Ok(None)
|
|
656
691
|
}
|
|
@@ -661,7 +696,7 @@ mod tests {
|
|
|
661
696
|
use super::*;
|
|
662
697
|
use crate::fk::{JointState, fk, frames};
|
|
663
698
|
use crate::ids::FrameId;
|
|
664
|
-
use crate::robot::{ActuatorSpec, Frame, JointKind, Limits, Mimic, TendonJoint};
|
|
699
|
+
use crate::robot::{ActuatorSpec, Frame, JointKind, Limits, Mimic, Primitive, TendonJoint};
|
|
665
700
|
use riggen_mesh::glam::{DQuat, DVec3};
|
|
666
701
|
use std::collections::BTreeMap;
|
|
667
702
|
use std::f64::consts::FRAC_PI_2;
|
|
@@ -758,19 +793,49 @@ mod tests {
|
|
|
758
793
|
(robot, [arm, hand, tip, tail])
|
|
759
794
|
}
|
|
760
795
|
|
|
761
|
-
///
|
|
762
|
-
|
|
763
|
-
|
|
796
|
+
/// Which piece of a link's geometry a world pose belongs to.
|
|
797
|
+
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
798
|
+
enum Slot {
|
|
799
|
+
Visual(GeomId),
|
|
800
|
+
Collision(GeomId),
|
|
801
|
+
Primitive(usize),
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
/// Every geom of every link at `q = 0` — visuals, collision meshes and
|
|
805
|
+
/// primitives — in world coordinates: what a frame move must leave alone.
|
|
806
|
+
fn world_geoms(robot: &Robot) -> Vec<(LinkId, Slot, Pose)> {
|
|
764
807
|
let world = fk(robot, &JointState::default());
|
|
765
808
|
let mut out = Vec::new();
|
|
766
809
|
for (&link, l) in &robot.links {
|
|
810
|
+
let at = |pose: &Pose| world[&link].compose(pose);
|
|
767
811
|
for geom in &l.visuals {
|
|
768
|
-
out.push((link, geom.id,
|
|
812
|
+
out.push((link, Slot::Visual(geom.id), at(&geom.pose)));
|
|
813
|
+
}
|
|
814
|
+
for geom in l.collision.geoms() {
|
|
815
|
+
out.push((link, Slot::Collision(geom.id), at(&geom.pose)));
|
|
816
|
+
}
|
|
817
|
+
if let CollisionPolicy::Primitives(prims) = &l.collision {
|
|
818
|
+
for (i, prim) in prims.iter().enumerate() {
|
|
819
|
+
out.push((link, Slot::Primitive(i), at(&prim.pose())));
|
|
820
|
+
}
|
|
769
821
|
}
|
|
770
822
|
}
|
|
771
823
|
out
|
|
772
824
|
}
|
|
773
825
|
|
|
826
|
+
/// Every pose in `before` is still where it was in `robot`'s world.
|
|
827
|
+
fn assert_world_geoms_kept(robot: &Robot, before: &[(LinkId, Slot, Pose)]) {
|
|
828
|
+
let now = world_geoms(robot);
|
|
829
|
+
assert_eq!(now.len(), before.len(), "no geom appears or vanishes");
|
|
830
|
+
for (link, slot, pose) in before {
|
|
831
|
+
let (_, _, now) = now
|
|
832
|
+
.iter()
|
|
833
|
+
.find(|(l, s, _)| l == link && s == slot)
|
|
834
|
+
.expect("the geom survives");
|
|
835
|
+
assert_pose_eq(now, pose);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
774
839
|
/// The `arm()` chain with a geom on every link, so a frame move has
|
|
775
840
|
/// geometry to re-express as well as child joints.
|
|
776
841
|
fn arm_with_geoms() -> (Robot, [LinkId; 4]) {
|
|
@@ -831,12 +896,109 @@ mod tests {
|
|
|
831
896
|
}
|
|
832
897
|
assert_pose_eq(&after[&hand], &origin_in_world(&robot, hand));
|
|
833
898
|
// Every geom, on the moved link and on its grandchildren, stays put.
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
899
|
+
assert_world_geoms_kept(&robot, &before_geoms);
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
#[test]
|
|
903
|
+
fn moving_a_joint_frame_leaves_collision_in_the_world() {
|
|
904
|
+
let (mut robot, [arm, hand, tip, _tail]) = arm_with_geoms();
|
|
905
|
+
let mesh = robot.add_asset(asset());
|
|
906
|
+
let posed = |i: f64| {
|
|
907
|
+
Pose::from_xyz_rpy(
|
|
908
|
+
DVec3::new(0.05 * i, 0.3, -0.1 * i),
|
|
909
|
+
DVec3::new(-0.3 * i, 0.5, 0.1 * i),
|
|
910
|
+
)
|
|
911
|
+
};
|
|
912
|
+
// `hand` holds its own collision meshes, `arm` all four primitives,
|
|
913
|
+
// `tip` a policy derived from its visuals.
|
|
914
|
+
let hull: GeomId = robot.next_id.alloc();
|
|
915
|
+
let commands = [
|
|
916
|
+
Command::SetCollision(
|
|
917
|
+
hand,
|
|
918
|
+
CollisionPolicy::Meshes(vec![Geom {
|
|
919
|
+
id: hull,
|
|
920
|
+
mesh,
|
|
921
|
+
pose: posed(1.0),
|
|
922
|
+
color: None,
|
|
923
|
+
}]),
|
|
924
|
+
),
|
|
925
|
+
Command::SetCollision(
|
|
926
|
+
arm,
|
|
927
|
+
CollisionPolicy::Primitives(vec![
|
|
928
|
+
Primitive::Box {
|
|
929
|
+
pose: posed(1.0),
|
|
930
|
+
size: DVec3::new(0.1, 0.2, 0.3),
|
|
931
|
+
},
|
|
932
|
+
Primitive::Cylinder {
|
|
933
|
+
pose: posed(2.0),
|
|
934
|
+
radius: 0.04,
|
|
935
|
+
length: 0.25,
|
|
936
|
+
},
|
|
937
|
+
Primitive::Sphere {
|
|
938
|
+
pose: posed(3.0),
|
|
939
|
+
radius: 0.07,
|
|
940
|
+
},
|
|
941
|
+
Primitive::Capsule {
|
|
942
|
+
pose: posed(4.0),
|
|
943
|
+
radius: 0.03,
|
|
944
|
+
length: 0.15,
|
|
945
|
+
},
|
|
946
|
+
]),
|
|
947
|
+
),
|
|
948
|
+
Command::SetCollision(tip, CollisionPolicy::SameAsVisual),
|
|
949
|
+
];
|
|
950
|
+
for command in commands {
|
|
951
|
+
apply(&mut robot, command).unwrap();
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
let mut history = crate::History::new();
|
|
955
|
+
for link in [arm, hand, tip] {
|
|
956
|
+
let joint = robot.parent_joint(link).unwrap();
|
|
957
|
+
let before = robot.clone();
|
|
958
|
+
let before_geoms = world_geoms(&robot);
|
|
959
|
+
// A pivot that both turns and translates.
|
|
960
|
+
let origin =
|
|
961
|
+
Pose::from_xyz_rpy(DVec3::new(0.7, -0.35, 0.2), DVec3::new(-0.6, 0.4, 1.1));
|
|
962
|
+
history
|
|
963
|
+
.apply(
|
|
964
|
+
&mut robot,
|
|
965
|
+
Command::MoveJointFrame {
|
|
966
|
+
joint,
|
|
967
|
+
origin,
|
|
968
|
+
axis: DVec3::X,
|
|
969
|
+
},
|
|
970
|
+
)
|
|
971
|
+
.unwrap();
|
|
972
|
+
|
|
973
|
+
assert_world_geoms_kept(&robot, &before_geoms);
|
|
974
|
+
// Only the poses were rewritten: every shape is bitwise as it was.
|
|
975
|
+
match (
|
|
976
|
+
&before.links[&link].collision,
|
|
977
|
+
&robot.links[&link].collision,
|
|
978
|
+
) {
|
|
979
|
+
(CollisionPolicy::Meshes(old), CollisionPolicy::Meshes(new)) => {
|
|
980
|
+
assert_eq!(old.len(), new.len());
|
|
981
|
+
for (old, new) in old.iter().zip(new) {
|
|
982
|
+
assert!((old.pose.t - new.pose.t).length() > EPS, "re-expressed");
|
|
983
|
+
assert_eq!((old.id, old.mesh, old.color), (new.id, new.mesh, new.color));
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
(CollisionPolicy::Primitives(old), CollisionPolicy::Primitives(new)) => {
|
|
987
|
+
assert_eq!(old.len(), new.len());
|
|
988
|
+
for (old, new) in old.iter().zip(new) {
|
|
989
|
+
assert!((old.pose().t - new.pose().t).length() > EPS, "re-expressed");
|
|
990
|
+
let mut shape = new.clone();
|
|
991
|
+
*shape.pose_mut() = old.pose();
|
|
992
|
+
assert_eq!(&shape, old);
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
(old, new) => assert_eq!(old, new, "a derived policy is stored as it was"),
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// One undo is the whole move; redo it to move the next pivot on.
|
|
999
|
+
assert!(history.undo(&mut robot));
|
|
1000
|
+
assert_eq!(robot, before);
|
|
1001
|
+
assert!(history.redo(&mut robot));
|
|
840
1002
|
}
|
|
841
1003
|
}
|
|
842
1004
|
|
|
@@ -1264,6 +1426,17 @@ mod tests {
|
|
|
1264
1426
|
let moved = Pose::from_translation(DVec3::Z);
|
|
1265
1427
|
apply(&mut robot, Command::SetGeomPose(arm, gid, moved)).unwrap();
|
|
1266
1428
|
assert_eq!(robot.links[&arm].visuals[0].pose, moved);
|
|
1429
|
+
// A NaN is refused by the edit that makes it, and changes nothing.
|
|
1430
|
+
let before = robot.clone();
|
|
1431
|
+
let nan = Pose::from_translation(DVec3::new(f64::NAN, 0.0, 0.0));
|
|
1432
|
+
assert_eq!(
|
|
1433
|
+
apply(&mut robot, Command::SetGeomPose(arm, gid, nan)),
|
|
1434
|
+
Err(ValidationError::NonFinite {
|
|
1435
|
+
what: format!("pose of geom {gid} of link {arm}")
|
|
1436
|
+
}
|
|
1437
|
+
.into())
|
|
1438
|
+
);
|
|
1439
|
+
assert_eq!(robot, before);
|
|
1267
1440
|
assert_eq!(
|
|
1268
1441
|
apply(
|
|
1269
1442
|
&mut robot,
|
|
@@ -1279,6 +1452,34 @@ mod tests {
|
|
|
1279
1452
|
assert!(robot.links[&arm].visuals.is_empty());
|
|
1280
1453
|
}
|
|
1281
1454
|
|
|
1455
|
+
/// What the SDK's `set_geom_pose({"t": …, "r": [0, 0, 0, 0]})` hands
|
|
1456
|
+
/// the command: finite, and no rotation. Refused, so no writer
|
|
1457
|
+
/// normalises it into NaN, and the document is left as it was.
|
|
1458
|
+
#[test]
|
|
1459
|
+
fn a_zero_length_rotation_from_the_sdk_is_refused() {
|
|
1460
|
+
let (mut robot, [arm, ..]) = arm();
|
|
1461
|
+
let mesh = robot.add_asset(asset());
|
|
1462
|
+
let gid: GeomId = robot.next_id.alloc();
|
|
1463
|
+
let geom = Geom {
|
|
1464
|
+
id: gid,
|
|
1465
|
+
mesh,
|
|
1466
|
+
pose: Pose::IDENTITY,
|
|
1467
|
+
color: None,
|
|
1468
|
+
};
|
|
1469
|
+
apply(&mut robot, Command::AddGeom(arm, geom)).unwrap();
|
|
1470
|
+
let pose: Pose =
|
|
1471
|
+
serde_json::from_str(r#"{"t": [0.0, 0.0, 0.5], "r": [0.0, 0.0, 0.0, 0.0]}"#).unwrap();
|
|
1472
|
+
let before = robot.clone();
|
|
1473
|
+
assert_eq!(
|
|
1474
|
+
apply(&mut robot, Command::SetGeomPose(arm, gid, pose)),
|
|
1475
|
+
Err(ValidationError::DegenerateRotation {
|
|
1476
|
+
what: format!("pose of geom {gid} of link {arm}")
|
|
1477
|
+
}
|
|
1478
|
+
.into())
|
|
1479
|
+
);
|
|
1480
|
+
assert_eq!(robot, before);
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1282
1483
|
#[test]
|
|
1283
1484
|
fn set_joint_keeps_the_endpoints() {
|
|
1284
1485
|
let (mut robot, [arm, _, _, tail]) = arm();
|
|
@@ -1775,6 +1976,106 @@ mod tests {
|
|
|
1775
1976
|
assert!(robot.actuators.is_empty());
|
|
1776
1977
|
}
|
|
1777
1978
|
|
|
1979
|
+
/// `AssignMaterialToUnweighed` (ADR-0032 §5): every link with geometry,
|
|
1980
|
+
/// no material and an inertial that takes its density from one gets the
|
|
1981
|
+
/// material, in one history entry; a link with an `Override`, a
|
|
1982
|
+
/// material, a density override or no geometry is untouched; an unknown
|
|
1983
|
+
/// material is refused; and once nothing is unweighed the command
|
|
1984
|
+
/// changes nothing, so it records nothing.
|
|
1985
|
+
#[test]
|
|
1986
|
+
fn assign_material_to_unweighed_is_one_undo_over_every_unweighed_link() {
|
|
1987
|
+
use crate::history::History;
|
|
1988
|
+
let (mut robot, [arm, hand, tip, tail]) = arm_with_geoms();
|
|
1989
|
+
let empty = robot.root;
|
|
1990
|
+
let set = |robot: &mut Robot, link: LinkId, material: Option<&str>, spec: InertialSpec| {
|
|
1991
|
+
let l = robot.links.get_mut(&link).unwrap();
|
|
1992
|
+
l.material = material.map(Into::into);
|
|
1993
|
+
l.inertial = spec;
|
|
1994
|
+
};
|
|
1995
|
+
let unset = InertialSpec::Computed {
|
|
1996
|
+
density_override: None,
|
|
1997
|
+
};
|
|
1998
|
+
set(&mut robot, arm, None, unset.clone());
|
|
1999
|
+
set(&mut robot, hand, None, InertialSpec::Hybrid { mass: 0.3 });
|
|
2000
|
+
set(
|
|
2001
|
+
&mut robot,
|
|
2002
|
+
tip,
|
|
2003
|
+
None,
|
|
2004
|
+
InertialSpec::Override {
|
|
2005
|
+
mass: 1.0,
|
|
2006
|
+
com: DVec3::ZERO,
|
|
2007
|
+
inertia: riggen_mesh::glam::DMat3::IDENTITY,
|
|
2008
|
+
},
|
|
2009
|
+
);
|
|
2010
|
+
set(&mut robot, tail, Some("PLA"), unset.clone());
|
|
2011
|
+
set(&mut robot, empty, None, unset);
|
|
2012
|
+
let dense = add(
|
|
2013
|
+
&mut robot,
|
|
2014
|
+
tail,
|
|
2015
|
+
"dense",
|
|
2016
|
+
fixed("dense_joint", Pose::IDENTITY),
|
|
2017
|
+
);
|
|
2018
|
+
let geom = robot.links[&tail].visuals[0].clone();
|
|
2019
|
+
set(
|
|
2020
|
+
&mut robot,
|
|
2021
|
+
dense,
|
|
2022
|
+
None,
|
|
2023
|
+
InertialSpec::Computed {
|
|
2024
|
+
density_override: Some(1000.0),
|
|
2025
|
+
},
|
|
2026
|
+
);
|
|
2027
|
+
let id = robot.next_id.alloc();
|
|
2028
|
+
robot
|
|
2029
|
+
.links
|
|
2030
|
+
.get_mut(&dense)
|
|
2031
|
+
.unwrap()
|
|
2032
|
+
.visuals
|
|
2033
|
+
.push(Geom { id, ..geom });
|
|
2034
|
+
assert_eq!(robot.unweighed_links(), [arm, hand]);
|
|
2035
|
+
let before = robot.clone();
|
|
2036
|
+
|
|
2037
|
+
let mut history = History::new();
|
|
2038
|
+
assert_eq!(
|
|
2039
|
+
history.apply(
|
|
2040
|
+
&mut robot,
|
|
2041
|
+
Command::AssignMaterialToUnweighed("unobtainium".into())
|
|
2042
|
+
),
|
|
2043
|
+
Err(EditError::UnknownMaterial("unobtainium".into()))
|
|
2044
|
+
);
|
|
2045
|
+
assert_eq!(robot, before, "a refusal changes nothing");
|
|
2046
|
+
|
|
2047
|
+
history
|
|
2048
|
+
.apply(
|
|
2049
|
+
&mut robot,
|
|
2050
|
+
Command::AssignMaterialToUnweighed("aluminium".into()),
|
|
2051
|
+
)
|
|
2052
|
+
.unwrap();
|
|
2053
|
+
let material = |robot: &Robot, l: LinkId| robot.links[&l].material.clone();
|
|
2054
|
+
assert_eq!(material(&robot, arm).as_deref(), Some("aluminium"));
|
|
2055
|
+
assert_eq!(material(&robot, hand).as_deref(), Some("aluminium"));
|
|
2056
|
+
for l in [tip, empty, dense] {
|
|
2057
|
+
assert_eq!(material(&robot, l), None, "{l}");
|
|
2058
|
+
}
|
|
2059
|
+
assert_eq!(material(&robot, tail).as_deref(), Some("PLA"));
|
|
2060
|
+
assert!(robot.unweighed_links().is_empty());
|
|
2061
|
+
assert_eq!(history.undo_depth(), 1);
|
|
2062
|
+
|
|
2063
|
+
history
|
|
2064
|
+
.apply(
|
|
2065
|
+
&mut robot,
|
|
2066
|
+
Command::AssignMaterialToUnweighed("aluminium".into()),
|
|
2067
|
+
)
|
|
2068
|
+
.unwrap();
|
|
2069
|
+
assert_eq!(
|
|
2070
|
+
history.undo_depth(),
|
|
2071
|
+
1,
|
|
2072
|
+
"nothing qualified, nothing recorded"
|
|
2073
|
+
);
|
|
2074
|
+
|
|
2075
|
+
assert!(history.undo(&mut robot));
|
|
2076
|
+
assert_eq!(robot, before, "one undo reverts every link");
|
|
2077
|
+
}
|
|
2078
|
+
|
|
1778
2079
|
/// The table's own quartet, following `AddFrame` / `SetFrame` /
|
|
1779
2080
|
/// `RenameFrame` / `RemoveFrame` (ADR-0023): the id comes back from the
|
|
1780
2081
|
/// command, a dangling target is refused, and an actuator carries a
|
|
@@ -753,6 +753,17 @@ mod tests {
|
|
|
753
753
|
Err(FileError::Invalid { .. })
|
|
754
754
|
));
|
|
755
755
|
assert!(!target.exists());
|
|
756
|
+
// So is a NaN, rather than written as the `null` serde_json spells
|
|
757
|
+
// one — a file that would then not load.
|
|
758
|
+
let mut nan = robot.clone();
|
|
759
|
+
nan.links.get_mut(&root).unwrap().visuals[0].pose.t.x = f64::NAN;
|
|
760
|
+
assert!(matches!(
|
|
761
|
+
to_json(&nan, &target),
|
|
762
|
+
Err(FileError::Invalid {
|
|
763
|
+
source: ValidationError::NonFinite { .. },
|
|
764
|
+
..
|
|
765
|
+
})
|
|
766
|
+
));
|
|
756
767
|
assert!(matches!(
|
|
757
768
|
load(&dir.join("nope.riggen")),
|
|
758
769
|
Err(FileError::Io { .. })
|