cartesian-tree 0.3.0__tar.gz → 0.4.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.
- {cartesian_tree-0.3.0 → cartesian_tree-0.4.0}/Cargo.lock +17 -1
- {cartesian_tree-0.3.0 → cartesian_tree-0.4.0}/Cargo.toml +13 -7
- {cartesian_tree-0.3.0 → cartesian_tree-0.4.0}/PKG-INFO +5 -4
- {cartesian_tree-0.3.0 → cartesian_tree-0.4.0}/README.md +2 -1
- {cartesian_tree-0.3.0 → cartesian_tree-0.4.0}/pyproject.toml +1 -1
- cartesian_tree-0.4.0/python/cartesian_tree/_cartesian_tree.pyi +95 -0
- {cartesian_tree-0.3.0 → cartesian_tree-0.4.0}/python/cartesian_tree/angles.py +10 -1
- {cartesian_tree-0.3.0 → cartesian_tree-0.4.0}/python/cartesian_tree/base_types.py +61 -4
- {cartesian_tree-0.3.0 → cartesian_tree-0.4.0}/python/cartesian_tree/lazy_access.py +19 -1
- {cartesian_tree-0.3.0 → cartesian_tree-0.4.0}/python/cartesian_tree/lib.py +59 -13
- {cartesian_tree-0.3.0 → cartesian_tree-0.4.0}/python/cartesian_tree/quaternion.py +21 -1
- {cartesian_tree-0.3.0 → cartesian_tree-0.4.0}/src/bindings/frame.rs +15 -9
- {cartesian_tree-0.3.0 → cartesian_tree-0.4.0}/src/bindings/lazy_access.rs +2 -2
- {cartesian_tree-0.3.0 → cartesian_tree-0.4.0}/src/bindings/pose.rs +1 -1
- {cartesian_tree-0.3.0 → cartesian_tree-0.4.0}/src/bindings/utils.rs +7 -7
- {cartesian_tree-0.3.0 → cartesian_tree-0.4.0}/src/errors.rs +10 -4
- cartesian_tree-0.4.0/src/frame.rs +1956 -0
- {cartesian_tree-0.3.0 → cartesian_tree-0.4.0}/src/pose.rs +68 -43
- {cartesian_tree-0.3.0 → cartesian_tree-0.4.0}/src/rotation.rs +36 -3
- cartesian_tree-0.4.0/src/tree/mod.rs +5 -0
- cartesian_tree-0.3.0/src/frame.rs +0 -1241
- cartesian_tree-0.3.0/src/tree/mod.rs +0 -5
- {cartesian_tree-0.3.0 → cartesian_tree-0.4.0}/license-mit +0 -0
- {cartesian_tree-0.3.0 → cartesian_tree-0.4.0}/python/cartesian_tree/__init__.py +0 -0
- {cartesian_tree-0.3.0 → cartesian_tree-0.4.0}/python/cartesian_tree/py.typed +0 -0
- {cartesian_tree-0.3.0 → cartesian_tree-0.4.0}/src/bindings/mod.rs +0 -0
- {cartesian_tree-0.3.0 → cartesian_tree-0.4.0}/src/lazy_access.rs +0 -0
- {cartesian_tree-0.3.0 → cartesian_tree-0.4.0}/src/lib.rs +0 -0
- {cartesian_tree-0.3.0 → cartesian_tree-0.4.0}/src/tree/traits.rs +0 -0
|
@@ -4,13 +4,14 @@ version = 4
|
|
|
4
4
|
|
|
5
5
|
[[package]]
|
|
6
6
|
name = "CartesianTree"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.0"
|
|
8
8
|
dependencies = [
|
|
9
9
|
"approx",
|
|
10
10
|
"nalgebra",
|
|
11
11
|
"pyo3",
|
|
12
12
|
"serde",
|
|
13
13
|
"serde_json",
|
|
14
|
+
"slotmap",
|
|
14
15
|
"thiserror",
|
|
15
16
|
"uuid",
|
|
16
17
|
]
|
|
@@ -383,6 +384,15 @@ dependencies = [
|
|
|
383
384
|
"wide",
|
|
384
385
|
]
|
|
385
386
|
|
|
387
|
+
[[package]]
|
|
388
|
+
name = "slotmap"
|
|
389
|
+
version = "1.1.1"
|
|
390
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
391
|
+
checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038"
|
|
392
|
+
dependencies = [
|
|
393
|
+
"version_check",
|
|
394
|
+
]
|
|
395
|
+
|
|
386
396
|
[[package]]
|
|
387
397
|
name = "syn"
|
|
388
398
|
version = "2.0.101"
|
|
@@ -449,6 +459,12 @@ dependencies = [
|
|
|
449
459
|
"wasm-bindgen",
|
|
450
460
|
]
|
|
451
461
|
|
|
462
|
+
[[package]]
|
|
463
|
+
name = "version_check"
|
|
464
|
+
version = "0.9.5"
|
|
465
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
466
|
+
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
|
467
|
+
|
|
452
468
|
[[package]]
|
|
453
469
|
name = "wasi"
|
|
454
470
|
version = "0.14.7+wasi-0.2.4"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
authors = ["Kn0g"]
|
|
3
3
|
name = "CartesianTree"
|
|
4
|
-
version = "0.
|
|
4
|
+
version = "0.4.0"
|
|
5
5
|
edition = "2024"
|
|
6
6
|
description = "Build hierarchical Cartesian coordinate systems to easily transform poses."
|
|
7
7
|
homepage = "https://github.com/Kn0g/cartesian-tree"
|
|
@@ -13,16 +13,19 @@ keywords = ["Cartesian", "coordinate-systems", "transform", "poses"]
|
|
|
13
13
|
categories = ["mathematics", "data-structures"]
|
|
14
14
|
|
|
15
15
|
[lints.clippy]
|
|
16
|
-
all = "warn"
|
|
17
|
-
style = "warn"
|
|
18
|
-
pedantic = "warn"
|
|
19
|
-
cargo = "warn"
|
|
20
|
-
nursery = "warn"
|
|
16
|
+
all = { level = "warn", priority = -1 }
|
|
17
|
+
style = { level = "warn", priority = -1 }
|
|
18
|
+
pedantic = { level = "warn", priority = -1 }
|
|
19
|
+
cargo = { level = "warn", priority = -1 }
|
|
20
|
+
nursery = { level = "warn", priority = -1 }
|
|
21
21
|
missing_panics_doc = "warn"
|
|
22
22
|
missing_errors_doc = "warn"
|
|
23
23
|
missing_safety_doc = "warn"
|
|
24
24
|
empty_line_after_outer_attr = "warn"
|
|
25
25
|
empty_line_after_doc_comments = "warn"
|
|
26
|
+
# Guards in this crate are already scoped as tightly as the borrow checker allows;
|
|
27
|
+
# this nursery lint's suggestions frequently do not compile here.
|
|
28
|
+
significant_drop_tightening = "allow"
|
|
26
29
|
|
|
27
30
|
[dependencies]
|
|
28
31
|
nalgebra = { version = "0.33.2", features = ["serde-serialize"] }
|
|
@@ -30,9 +33,12 @@ pyo3 = { version = "0.25.0", features = ["extension-module"] , optional = true}
|
|
|
30
33
|
thiserror = "2.0.12"
|
|
31
34
|
serde = { version = "1.0", features = ["derive"] }
|
|
32
35
|
serde_json = "1.0"
|
|
33
|
-
|
|
36
|
+
slotmap = "1"
|
|
34
37
|
uuid = { version = "1", features = ["v4"] }
|
|
35
38
|
|
|
39
|
+
[dev-dependencies]
|
|
40
|
+
approx = "0.5.1"
|
|
41
|
+
|
|
36
42
|
[features]
|
|
37
43
|
bindings = ["dep:pyo3"]
|
|
38
44
|
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cartesian-tree
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Classifier: Programming Language :: Python
|
|
5
|
+
Requires-Dist: maturin==1.8.6 ; extra == 'build'
|
|
5
6
|
Requires-Dist: cartesian-tree[lint] ; extra == 'dev'
|
|
6
7
|
Requires-Dist: cartesian-tree[test] ; extra == 'dev'
|
|
7
8
|
Requires-Dist: cartesian-tree[build] ; extra == 'dev'
|
|
8
9
|
Requires-Dist: ruff==0.12.4 ; extra == 'lint'
|
|
9
10
|
Requires-Dist: mypy==1.17.0 ; extra == 'lint'
|
|
10
11
|
Requires-Dist: pytest==8.4.1 ; extra == 'test'
|
|
11
|
-
|
|
12
|
+
Provides-Extra: build
|
|
12
13
|
Provides-Extra: dev
|
|
13
14
|
Provides-Extra: lint
|
|
14
15
|
Provides-Extra: test
|
|
15
|
-
Provides-Extra: build
|
|
16
16
|
License-File: license-mit
|
|
17
17
|
Summary: Build hierarchical Cartesian coordinate systems to easily transform poses.
|
|
18
18
|
Keywords: Cartesian,coordinate systems,transform,poses
|
|
@@ -31,6 +31,7 @@ cartesian-tree is a library for managing and transforming poses in a tree-struct
|
|
|
31
31
|
- Poses and Transformations: A Pose represents a position + orientation in a frame. Easily transform poses between frames of the same tree.
|
|
32
32
|
- Unified Rotations: Supports quaternions and roll-pitch-yaw (RPY) interchangeably.
|
|
33
33
|
- Lazy Operations: Quick adjustments like translations (`+ x(1.0)`) or rotations (`* rz(PI/4)`).
|
|
34
|
+
- Thread Safety: Frames and poses can be shared across threads; every operation sees a consistent snapshot of the tree.
|
|
34
35
|
|
|
35
36
|
## Installation
|
|
36
37
|
The project can be found on [crates.io](https://crates.io/crates/cartesiantree) and [pypi.org](https://pypi.org/project/cartesian-tree/#description)
|
|
@@ -60,7 +61,7 @@ fn main() {
|
|
|
60
61
|
// Transform the pose to the child frame
|
|
61
62
|
let pose_in_child = pose.in_frame(&child).unwrap();
|
|
62
63
|
|
|
63
|
-
println!("Pose in child: {:?}",
|
|
64
|
+
println!("Pose in child: {:?}", pose_in_child.transformation());
|
|
64
65
|
```
|
|
65
66
|
Python:
|
|
66
67
|
|
|
@@ -7,6 +7,7 @@ cartesian-tree is a library for managing and transforming poses in a tree-struct
|
|
|
7
7
|
- Poses and Transformations: A Pose represents a position + orientation in a frame. Easily transform poses between frames of the same tree.
|
|
8
8
|
- Unified Rotations: Supports quaternions and roll-pitch-yaw (RPY) interchangeably.
|
|
9
9
|
- Lazy Operations: Quick adjustments like translations (`+ x(1.0)`) or rotations (`* rz(PI/4)`).
|
|
10
|
+
- Thread Safety: Frames and poses can be shared across threads; every operation sees a consistent snapshot of the tree.
|
|
10
11
|
|
|
11
12
|
## Installation
|
|
12
13
|
The project can be found on [crates.io](https://crates.io/crates/cartesiantree) and [pypi.org](https://pypi.org/project/cartesian-tree/#description)
|
|
@@ -36,7 +37,7 @@ fn main() {
|
|
|
36
37
|
// Transform the pose to the child frame
|
|
37
38
|
let pose_in_child = pose.in_frame(&child).unwrap();
|
|
38
39
|
|
|
39
|
-
println!("Pose in child: {:?}",
|
|
40
|
+
println!("Pose in child: {:?}", pose_in_child.transformation());
|
|
40
41
|
```
|
|
41
42
|
Python:
|
|
42
43
|
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Type stubs for the Rust extension module `cartesian_tree._cartesian_tree`.
|
|
2
|
+
# Keep in sync with the pymethods in src/bindings/.
|
|
3
|
+
|
|
4
|
+
class Vector3:
|
|
5
|
+
def __init__(self, x: float, y: float, z: float) -> None: ...
|
|
6
|
+
@property
|
|
7
|
+
def x(self) -> float: ...
|
|
8
|
+
@property
|
|
9
|
+
def y(self) -> float: ...
|
|
10
|
+
@property
|
|
11
|
+
def z(self) -> float: ...
|
|
12
|
+
def to_tuple(self) -> tuple[float, float, float]: ...
|
|
13
|
+
|
|
14
|
+
class Rotation:
|
|
15
|
+
@classmethod
|
|
16
|
+
def from_quaternion(cls, x: float, y: float, z: float, w: float) -> Rotation: ...
|
|
17
|
+
@classmethod
|
|
18
|
+
def from_rpy(cls, roll: float, pitch: float, yaw: float) -> Rotation: ...
|
|
19
|
+
@classmethod
|
|
20
|
+
def identity(cls) -> Rotation: ...
|
|
21
|
+
def as_quaternion(self) -> tuple[float, float, float, float]: ...
|
|
22
|
+
def as_rpy(self) -> tuple[float, float, float]: ...
|
|
23
|
+
|
|
24
|
+
class Isometry:
|
|
25
|
+
@classmethod
|
|
26
|
+
def identity(cls) -> Isometry: ...
|
|
27
|
+
@classmethod
|
|
28
|
+
def from_translation(cls, translation: Vector3) -> Isometry: ...
|
|
29
|
+
@classmethod
|
|
30
|
+
def from_rotation(cls, rotation: Rotation) -> Isometry: ...
|
|
31
|
+
@classmethod
|
|
32
|
+
def from_parts(cls, translation: Vector3, rotation: Rotation) -> Isometry: ...
|
|
33
|
+
def decompose(self) -> tuple[Vector3, Rotation]: ...
|
|
34
|
+
def translation(self) -> Vector3: ...
|
|
35
|
+
def rotation(self) -> Rotation: ...
|
|
36
|
+
def inverse(self) -> Isometry: ...
|
|
37
|
+
def __mul__(self, other: Isometry) -> Isometry: ...
|
|
38
|
+
|
|
39
|
+
class LazyTranslation: ...
|
|
40
|
+
class LazyRotation: ...
|
|
41
|
+
|
|
42
|
+
class Frame:
|
|
43
|
+
def __init__(self, name: str) -> None: ...
|
|
44
|
+
@property
|
|
45
|
+
def name(self) -> str: ...
|
|
46
|
+
@property
|
|
47
|
+
def depth(self) -> int: ...
|
|
48
|
+
@property
|
|
49
|
+
def position(self) -> Vector3: ...
|
|
50
|
+
@property
|
|
51
|
+
def orientation(self) -> Rotation: ...
|
|
52
|
+
def add_child(self, name: str, position: Vector3, orientation: Rotation) -> Frame: ...
|
|
53
|
+
def remove_child(self, name: str) -> None: ...
|
|
54
|
+
def calibrate_child(
|
|
55
|
+
self,
|
|
56
|
+
name: str,
|
|
57
|
+
desired_position: Vector3,
|
|
58
|
+
desired_orientation: Rotation,
|
|
59
|
+
reference_pose: Pose,
|
|
60
|
+
) -> Frame: ...
|
|
61
|
+
def add_pose(self, position: Vector3, orientation: Rotation) -> Pose: ...
|
|
62
|
+
def transformation(self) -> tuple[Vector3, Rotation]: ...
|
|
63
|
+
def set(self, position: Vector3, orientation: Rotation) -> None: ...
|
|
64
|
+
def apply_in_parent_frame(self, isometry: Isometry) -> None: ...
|
|
65
|
+
def apply_in_local_frame(self, isometry: Isometry) -> None: ...
|
|
66
|
+
def to_json(self) -> str: ...
|
|
67
|
+
def apply_config(self, json: str) -> None: ...
|
|
68
|
+
def root(self) -> Frame: ...
|
|
69
|
+
def parent(self) -> Frame | None: ...
|
|
70
|
+
def children(self) -> list[Frame]: ...
|
|
71
|
+
def __add__(self, other: LazyTranslation) -> Frame: ...
|
|
72
|
+
def __sub__(self, other: LazyTranslation) -> Frame: ...
|
|
73
|
+
def __mul__(self, other: LazyRotation) -> Frame: ...
|
|
74
|
+
|
|
75
|
+
class Pose:
|
|
76
|
+
def frame(self) -> Frame | None: ...
|
|
77
|
+
@property
|
|
78
|
+
def position(self) -> Vector3: ...
|
|
79
|
+
@property
|
|
80
|
+
def orientation(self) -> Rotation: ...
|
|
81
|
+
def transformation(self) -> tuple[Vector3, Rotation]: ...
|
|
82
|
+
def set(self, position: Vector3, orientation: Rotation) -> None: ...
|
|
83
|
+
def apply_in_parent_frame(self, isometry: Isometry) -> None: ...
|
|
84
|
+
def apply_in_local_frame(self, isometry: Isometry) -> None: ...
|
|
85
|
+
def in_frame(self, target_frame: Frame) -> Pose: ...
|
|
86
|
+
def __add__(self, other: LazyTranslation) -> Pose: ...
|
|
87
|
+
def __sub__(self, other: LazyTranslation) -> Pose: ...
|
|
88
|
+
def __mul__(self, other: LazyRotation) -> Pose: ...
|
|
89
|
+
|
|
90
|
+
def x(value: float) -> LazyTranslation: ...
|
|
91
|
+
def y(value: float) -> LazyTranslation: ...
|
|
92
|
+
def z(value: float) -> LazyTranslation: ...
|
|
93
|
+
def rx(value: float) -> LazyRotation: ...
|
|
94
|
+
def ry(value: float) -> LazyRotation: ...
|
|
95
|
+
def rz(value: float) -> LazyRotation: ...
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from cartesian_tree import _cartesian_tree as _core
|
|
5
|
+
from cartesian_tree import _cartesian_tree as _core
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class RPY:
|
|
@@ -56,6 +56,15 @@ class RPY:
|
|
|
56
56
|
"""
|
|
57
57
|
return self._core_rotation.as_rpy()
|
|
58
58
|
|
|
59
|
+
def __eq__(self, other: object) -> bool:
|
|
60
|
+
"""Exact component-wise equality of the angle values (no angle wrapping)."""
|
|
61
|
+
if not isinstance(other, RPY):
|
|
62
|
+
return NotImplemented
|
|
63
|
+
return self.as_tuple() == other.as_tuple()
|
|
64
|
+
|
|
65
|
+
def __hash__(self) -> int:
|
|
66
|
+
return hash(self.as_tuple())
|
|
67
|
+
|
|
59
68
|
@classmethod
|
|
60
69
|
def _from_rust(cls, rust_rotation: _core.Rotation) -> RPY:
|
|
61
70
|
instance = cls.__new__(cls)
|
|
@@ -6,7 +6,7 @@ from typing import Any
|
|
|
6
6
|
|
|
7
7
|
from .angles import RPY
|
|
8
8
|
from .quaternion import Quaternion
|
|
9
|
-
from cartesian_tree import _cartesian_tree as _core
|
|
9
|
+
from cartesian_tree import _cartesian_tree as _core
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class Rotation:
|
|
@@ -14,10 +14,23 @@ class Rotation:
|
|
|
14
14
|
|
|
15
15
|
_core_rotation: _core.Rotation
|
|
16
16
|
|
|
17
|
+
def __init__(self) -> None:
|
|
18
|
+
"""Rotations cannot be constructed directly.
|
|
19
|
+
|
|
20
|
+
Use one of the classmethods instead: from_quaternion, from_rpy, or identity.
|
|
21
|
+
|
|
22
|
+
Raises:
|
|
23
|
+
TypeError: Always.
|
|
24
|
+
"""
|
|
25
|
+
msg = "Rotation cannot be constructed directly; use from_quaternion(), from_rpy(), or identity() instead."
|
|
26
|
+
raise TypeError(msg)
|
|
27
|
+
|
|
17
28
|
@classmethod
|
|
18
29
|
def from_quaternion(cls, x: float, y: float, z: float, w: float) -> Rotation:
|
|
19
30
|
"""Initializes the rotation from quaternion values.
|
|
20
31
|
|
|
32
|
+
Note, the quaternion does not need to be normalized; it is normalized internally.
|
|
33
|
+
|
|
21
34
|
Args:
|
|
22
35
|
x: The x value.
|
|
23
36
|
y: The y value.
|
|
@@ -26,6 +39,9 @@ class Rotation:
|
|
|
26
39
|
|
|
27
40
|
Returns:
|
|
28
41
|
The initialized instance.
|
|
42
|
+
|
|
43
|
+
Raises:
|
|
44
|
+
ValueError: If the quaternion's norm is too close to zero to normalize.
|
|
29
45
|
"""
|
|
30
46
|
instance = cls.__new__(cls)
|
|
31
47
|
instance._core_rotation = _core.Rotation.from_quaternion(x, y, z, w)
|
|
@@ -70,6 +86,15 @@ class Rotation:
|
|
|
70
86
|
"""
|
|
71
87
|
return RPY._from_rust(self._core_rotation)
|
|
72
88
|
|
|
89
|
+
def __eq__(self, other: object) -> bool:
|
|
90
|
+
"""Whether both represent the same rotation, regardless of representation."""
|
|
91
|
+
if not isinstance(other, Rotation):
|
|
92
|
+
return NotImplemented
|
|
93
|
+
return self.as_quaternion() == other.as_quaternion()
|
|
94
|
+
|
|
95
|
+
def __hash__(self) -> int:
|
|
96
|
+
return hash(self.as_quaternion())
|
|
97
|
+
|
|
73
98
|
@property
|
|
74
99
|
def _binding_structure(self) -> Any:
|
|
75
100
|
return self._core_rotation
|
|
@@ -124,6 +149,15 @@ class Vector3:
|
|
|
124
149
|
"""The z value."""
|
|
125
150
|
return self._core_vector.z
|
|
126
151
|
|
|
152
|
+
def __eq__(self, other: object) -> bool:
|
|
153
|
+
"""Exact component-wise equality."""
|
|
154
|
+
if not isinstance(other, Vector3):
|
|
155
|
+
return NotImplemented
|
|
156
|
+
return self.as_tuple() == other.as_tuple()
|
|
157
|
+
|
|
158
|
+
def __hash__(self) -> int:
|
|
159
|
+
return hash(self.as_tuple())
|
|
160
|
+
|
|
127
161
|
@property
|
|
128
162
|
def _binding_structure(self) -> Any:
|
|
129
163
|
return self._core_vector
|
|
@@ -156,12 +190,26 @@ class Isometry:
|
|
|
156
190
|
|
|
157
191
|
_core_isometry: _core.Isometry
|
|
158
192
|
|
|
193
|
+
def __init__(self) -> None:
|
|
194
|
+
"""Isometries cannot be constructed directly.
|
|
195
|
+
|
|
196
|
+
Use one of the classmethods instead: identity, from_translation, from_rotation, or from_parts.
|
|
197
|
+
|
|
198
|
+
Raises:
|
|
199
|
+
TypeError: Always.
|
|
200
|
+
"""
|
|
201
|
+
msg = (
|
|
202
|
+
"Isometry cannot be constructed directly; "
|
|
203
|
+
"use identity(), from_translation(), from_rotation(), or from_parts() instead."
|
|
204
|
+
)
|
|
205
|
+
raise TypeError(msg)
|
|
206
|
+
|
|
159
207
|
@classmethod
|
|
160
208
|
def identity(cls) -> Isometry:
|
|
161
|
-
"""Initializes the identity isometry.
|
|
209
|
+
"""Initializes the identity isometry (no translation, no rotation).
|
|
162
210
|
|
|
163
|
-
|
|
164
|
-
|
|
211
|
+
Returns:
|
|
212
|
+
The identity isometry.
|
|
165
213
|
"""
|
|
166
214
|
instance = cls.__new__(cls)
|
|
167
215
|
instance._core_isometry = _core.Isometry.identity()
|
|
@@ -252,6 +300,15 @@ class Isometry:
|
|
|
252
300
|
def __mul__(self, other: Isometry) -> Isometry:
|
|
253
301
|
return Isometry._from_rust(self._core_isometry.__mul__(other._binding_structure))
|
|
254
302
|
|
|
303
|
+
def __eq__(self, other: object) -> bool:
|
|
304
|
+
"""Whether translation and rotation are equal (rotation compared representation-independently)."""
|
|
305
|
+
if not isinstance(other, Isometry):
|
|
306
|
+
return NotImplemented
|
|
307
|
+
return self.translation() == other.translation() and self.rotation() == other.rotation()
|
|
308
|
+
|
|
309
|
+
def __hash__(self) -> int:
|
|
310
|
+
return hash((self.translation(), self.rotation()))
|
|
311
|
+
|
|
255
312
|
@property
|
|
256
313
|
def _binding_structure(self) -> Any:
|
|
257
314
|
return self._core_isometry
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from cartesian_tree import _cartesian_tree as _core
|
|
5
|
+
from cartesian_tree import _cartesian_tree as _core
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class LazyTranslation:
|
|
@@ -10,6 +10,15 @@ class LazyTranslation:
|
|
|
10
10
|
|
|
11
11
|
inner: _core.LazyTranslation
|
|
12
12
|
|
|
13
|
+
def __init__(self) -> None:
|
|
14
|
+
"""Lazy translations cannot be constructed directly; use x(), y(), or z() instead.
|
|
15
|
+
|
|
16
|
+
Raises:
|
|
17
|
+
TypeError: Always.
|
|
18
|
+
"""
|
|
19
|
+
msg = "LazyTranslation cannot be constructed directly; use x(), y(), or z() instead."
|
|
20
|
+
raise TypeError(msg)
|
|
21
|
+
|
|
13
22
|
@classmethod
|
|
14
23
|
def _from_rust(cls, rust_lazy_access: _core.LazyTranslation) -> LazyTranslation:
|
|
15
24
|
instance = cls.__new__(cls)
|
|
@@ -61,6 +70,15 @@ class LazyRotation:
|
|
|
61
70
|
|
|
62
71
|
inner: _core.LazyRotation
|
|
63
72
|
|
|
73
|
+
def __init__(self) -> None:
|
|
74
|
+
"""Lazy rotations cannot be constructed directly; use rx(), ry(), or rz() instead.
|
|
75
|
+
|
|
76
|
+
Raises:
|
|
77
|
+
TypeError: Always.
|
|
78
|
+
"""
|
|
79
|
+
msg = "LazyRotation cannot be constructed directly; use rx(), ry(), or rz() instead."
|
|
80
|
+
raise TypeError(msg)
|
|
81
|
+
|
|
64
82
|
@classmethod
|
|
65
83
|
def _from_rust(cls, rust_lazy_access: _core.LazyRotation) -> LazyRotation:
|
|
66
84
|
instance = cls.__new__(cls)
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
"""Defines
|
|
1
|
+
"""Defines the main library classes."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from typing import TYPE_CHECKING, Any
|
|
6
6
|
|
|
7
7
|
from .base_types import Isometry, Rotation, Vector3
|
|
8
|
-
from cartesian_tree import _cartesian_tree as _core
|
|
8
|
+
from cartesian_tree import _cartesian_tree as _core
|
|
9
9
|
|
|
10
10
|
if TYPE_CHECKING:
|
|
11
11
|
from .lazy_access import LazyRotation, LazyTranslation
|
|
@@ -16,6 +16,15 @@ class Frame:
|
|
|
16
16
|
|
|
17
17
|
Each frame can have one parent and multiple children. The frame stores its
|
|
18
18
|
transformation (position and orientation) relative to its parent.
|
|
19
|
+
|
|
20
|
+
All frames of a tree share ownership of the tree: keeping any Frame (or Pose)
|
|
21
|
+
alive keeps the whole tree alive, so a leaf reference is always enough to reach
|
|
22
|
+
the root. Frames removed via remove_child become stale, and operations on stale
|
|
23
|
+
frames raise a ValueError.
|
|
24
|
+
|
|
25
|
+
Frames can be shared freely across threads: all operations are synchronized
|
|
26
|
+
through a tree-wide read/write lock, and every operation sees a consistent
|
|
27
|
+
snapshot of the tree.
|
|
19
28
|
"""
|
|
20
29
|
|
|
21
30
|
def __init__(self, name: str) -> None:
|
|
@@ -38,13 +47,21 @@ class Frame:
|
|
|
38
47
|
|
|
39
48
|
@property
|
|
40
49
|
def position(self) -> Vector3:
|
|
41
|
-
"""The position of the frame relative to its parent.
|
|
50
|
+
"""The position of the frame relative to its parent.
|
|
51
|
+
|
|
52
|
+
Raises:
|
|
53
|
+
ValueError: If the frame has been removed from its tree.
|
|
54
|
+
"""
|
|
42
55
|
binding_position = self._core_frame.position
|
|
43
56
|
return Vector3(*binding_position.to_tuple())
|
|
44
57
|
|
|
45
58
|
@property
|
|
46
59
|
def orientation(self) -> Rotation:
|
|
47
|
-
"""The orientation of the frame relative to its parent.
|
|
60
|
+
"""The orientation of the frame relative to its parent.
|
|
61
|
+
|
|
62
|
+
Raises:
|
|
63
|
+
ValueError: If the frame has been removed from its tree.
|
|
64
|
+
"""
|
|
48
65
|
binding_orientation = self._core_frame.orientation
|
|
49
66
|
return Rotation._from_rust(binding_orientation)
|
|
50
67
|
|
|
@@ -65,6 +82,20 @@ class Frame:
|
|
|
65
82
|
binding_frame = self._core_frame.add_child(name, position._binding_structure, orientation._binding_structure)
|
|
66
83
|
return Frame._from_rust(binding_frame)
|
|
67
84
|
|
|
85
|
+
def remove_child(self, name: str) -> None:
|
|
86
|
+
"""Removes the child with the given name and its entire subtree from the tree.
|
|
87
|
+
|
|
88
|
+
Existing Frame objects referring to removed frames become stale and raise a
|
|
89
|
+
ValueError when used.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
name: The name of the child frame to remove.
|
|
93
|
+
|
|
94
|
+
Raises:
|
|
95
|
+
ValueError: If no child with the given name exists.
|
|
96
|
+
"""
|
|
97
|
+
self._core_frame.remove_child(name)
|
|
98
|
+
|
|
68
99
|
def calibrate_child(
|
|
69
100
|
self, name: str, desired_position: Vector3, desired_orientation: Rotation, reference_pose: Pose
|
|
70
101
|
) -> Frame:
|
|
@@ -168,15 +199,16 @@ class Frame:
|
|
|
168
199
|
def apply_config(self, config_json: str) -> None:
|
|
169
200
|
"""Applies a JSON config to update matching transforms in the tree.
|
|
170
201
|
|
|
171
|
-
Deserializes the JSON to a temporary structure, then recursively updates transforms
|
|
172
|
-
|
|
202
|
+
Deserializes the JSON to a temporary structure, then recursively updates the transforms
|
|
203
|
+
of frames whose names match (partial apply; unmatched frames in the config are ignored).
|
|
173
204
|
It is assumed this frame is the root.
|
|
174
205
|
|
|
175
206
|
Args:
|
|
176
207
|
config_json: The JSON string to apply.
|
|
177
208
|
|
|
178
209
|
Raises:
|
|
179
|
-
ValueError: On deserialization or mismatch errors (e.g. if this frame is not the root)
|
|
210
|
+
ValueError: On deserialization or mismatch errors (e.g. if this frame is not the root),
|
|
211
|
+
or if an orientation in the config cannot be normalized.
|
|
180
212
|
"""
|
|
181
213
|
self._core_frame.apply_config(config_json)
|
|
182
214
|
|
|
@@ -239,9 +271,26 @@ class Pose:
|
|
|
239
271
|
|
|
240
272
|
_core_pose: _core.Pose
|
|
241
273
|
|
|
242
|
-
def
|
|
243
|
-
"""
|
|
244
|
-
|
|
274
|
+
def __init__(self) -> None:
|
|
275
|
+
"""Poses cannot be constructed directly; use Frame.add_pose instead.
|
|
276
|
+
|
|
277
|
+
Raises:
|
|
278
|
+
TypeError: Always.
|
|
279
|
+
"""
|
|
280
|
+
msg = "Pose cannot be constructed directly; use Frame.add_pose() instead."
|
|
281
|
+
raise TypeError(msg)
|
|
282
|
+
|
|
283
|
+
def frame(self) -> Frame | None:
|
|
284
|
+
"""Returns the frame of the pose.
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
The frame the pose is attached to, or None if the frame has been removed
|
|
288
|
+
from the tree.
|
|
289
|
+
"""
|
|
290
|
+
binding_frame = self._core_pose.frame()
|
|
291
|
+
if binding_frame is None:
|
|
292
|
+
return None
|
|
293
|
+
return Frame._from_rust(binding_frame)
|
|
245
294
|
|
|
246
295
|
def transformation(self) -> tuple[Vector3, Rotation]:
|
|
247
296
|
"""Returns the transformation of the pose to its parent frame.
|
|
@@ -291,9 +340,6 @@ class Pose:
|
|
|
291
340
|
Args:
|
|
292
341
|
isometry: The isometry (describing a motion in this frame) to apply to the current
|
|
293
342
|
transformation.
|
|
294
|
-
|
|
295
|
-
Raises:
|
|
296
|
-
ValueError: If the frame has no parent.
|
|
297
343
|
"""
|
|
298
344
|
self._core_pose.apply_in_local_frame(isometry._binding_structure)
|
|
299
345
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from cartesian_tree import _cartesian_tree as _core
|
|
5
|
+
from cartesian_tree import _cartesian_tree as _core
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class Quaternion:
|
|
@@ -18,6 +18,9 @@ class Quaternion:
|
|
|
18
18
|
y: The y value.
|
|
19
19
|
z: The z value.
|
|
20
20
|
w: The w value.
|
|
21
|
+
|
|
22
|
+
Raises:
|
|
23
|
+
ValueError: If the quaternion's norm is too close to zero to normalize.
|
|
21
24
|
"""
|
|
22
25
|
self._core_rotation = _core.Rotation.from_quaternion(x, y, z, w)
|
|
23
26
|
|
|
@@ -72,6 +75,23 @@ class Quaternion:
|
|
|
72
75
|
"""
|
|
73
76
|
return self._core_rotation.as_quaternion()
|
|
74
77
|
|
|
78
|
+
def _canonical(self) -> tuple[float, float, float, float]:
|
|
79
|
+
"""Returns the components with the sign chosen such that q and -q map to the same tuple."""
|
|
80
|
+
q = self.as_tuple()
|
|
81
|
+
for component in q:
|
|
82
|
+
if component != 0.0:
|
|
83
|
+
return q if component > 0.0 else (-q[0], -q[1], -q[2], -q[3])
|
|
84
|
+
return q
|
|
85
|
+
|
|
86
|
+
def __eq__(self, other: object) -> bool:
|
|
87
|
+
"""Exact component-wise equality, treating q and -q as the same rotation."""
|
|
88
|
+
if not isinstance(other, Quaternion):
|
|
89
|
+
return NotImplemented
|
|
90
|
+
return self._canonical() == other._canonical()
|
|
91
|
+
|
|
92
|
+
def __hash__(self) -> int:
|
|
93
|
+
return hash(self._canonical())
|
|
94
|
+
|
|
75
95
|
@classmethod
|
|
76
96
|
def _from_rust(cls, rust_rotation: _core.Rotation) -> Quaternion:
|
|
77
97
|
instance = cls.__new__(cls)
|
|
@@ -10,7 +10,7 @@ use crate::{
|
|
|
10
10
|
tree::{HasChildren, HasParent, Walking},
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
-
#[pyclass(name = "Frame"
|
|
13
|
+
#[pyclass(name = "Frame")]
|
|
14
14
|
#[derive(Clone)]
|
|
15
15
|
pub struct PyFrame {
|
|
16
16
|
pub(crate) rust_frame: RustFrame,
|
|
@@ -46,6 +46,12 @@ impl PyFrame {
|
|
|
46
46
|
})
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
#[pyo3(signature = (name))]
|
|
50
|
+
fn remove_child(&self, name: &str) -> PyResult<()> {
|
|
51
|
+
self.rust_frame.remove_child(name)?;
|
|
52
|
+
Ok(())
|
|
53
|
+
}
|
|
54
|
+
|
|
49
55
|
#[pyo3(signature = (name, desired_position, desired_orientation, reference_pose))]
|
|
50
56
|
fn calibrate_child(
|
|
51
57
|
&self,
|
|
@@ -86,17 +92,17 @@ impl PyFrame {
|
|
|
86
92
|
}
|
|
87
93
|
|
|
88
94
|
#[getter]
|
|
89
|
-
fn position(&self) -> PyVector3 {
|
|
90
|
-
PyVector3 {
|
|
91
|
-
inner: self.rust_frame.position()
|
|
92
|
-
}
|
|
95
|
+
fn position(&self) -> PyResult<PyVector3> {
|
|
96
|
+
Ok(PyVector3 {
|
|
97
|
+
inner: self.rust_frame.position()?,
|
|
98
|
+
})
|
|
93
99
|
}
|
|
94
100
|
|
|
95
101
|
#[getter]
|
|
96
|
-
fn orientation(&self) -> PyRotation {
|
|
97
|
-
PyRotation {
|
|
98
|
-
rust_rotation: self.rust_frame.orientation()
|
|
99
|
-
}
|
|
102
|
+
fn orientation(&self) -> PyResult<PyRotation> {
|
|
103
|
+
Ok(PyRotation {
|
|
104
|
+
rust_rotation: self.rust_frame.orientation()?,
|
|
105
|
+
})
|
|
100
106
|
}
|
|
101
107
|
|
|
102
108
|
#[pyo3(signature = (position, orientation))]
|
|
@@ -9,12 +9,12 @@ use crate::lazy_access::x as rust_x;
|
|
|
9
9
|
use crate::lazy_access::y as rust_y;
|
|
10
10
|
use crate::lazy_access::z as rust_z;
|
|
11
11
|
|
|
12
|
-
#[pyclass(name = "LazyTranslation"
|
|
12
|
+
#[pyclass(name = "LazyTranslation")]
|
|
13
13
|
#[derive(Clone)]
|
|
14
14
|
pub struct PyLazyTranslation {
|
|
15
15
|
pub(crate) inner: LazyTranslation,
|
|
16
16
|
}
|
|
17
|
-
#[pyclass(name = "LazyRotation"
|
|
17
|
+
#[pyclass(name = "LazyRotation")]
|
|
18
18
|
#[derive(Clone)]
|
|
19
19
|
pub struct PyLazyRotation {
|
|
20
20
|
pub(crate) inner: LazyRotation,
|