antioch-py 2.0.6__py3-none-any.whl
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.
Potentially problematic release.
This version of antioch-py might be problematic. Click here for more details.
- antioch/__init__.py +0 -0
- antioch/message.py +87 -0
- antioch/module/__init__.py +53 -0
- antioch/module/clock.py +62 -0
- antioch/module/execution.py +278 -0
- antioch/module/input.py +127 -0
- antioch/module/module.py +218 -0
- antioch/module/node.py +357 -0
- antioch/module/token.py +42 -0
- antioch/session/__init__.py +150 -0
- antioch/session/ark.py +504 -0
- antioch/session/asset.py +65 -0
- antioch/session/error.py +80 -0
- antioch/session/record.py +158 -0
- antioch/session/scene.py +1521 -0
- antioch/session/session.py +220 -0
- antioch/session/task.py +323 -0
- antioch/session/views/__init__.py +40 -0
- antioch/session/views/animation.py +189 -0
- antioch/session/views/articulation.py +245 -0
- antioch/session/views/basis_curve.py +186 -0
- antioch/session/views/camera.py +92 -0
- antioch/session/views/collision.py +75 -0
- antioch/session/views/geometry.py +74 -0
- antioch/session/views/ground_plane.py +63 -0
- antioch/session/views/imu.py +73 -0
- antioch/session/views/joint.py +64 -0
- antioch/session/views/light.py +175 -0
- antioch/session/views/pir_sensor.py +140 -0
- antioch/session/views/radar.py +73 -0
- antioch/session/views/rigid_body.py +282 -0
- antioch/session/views/xform.py +119 -0
- antioch_py-2.0.6.dist-info/METADATA +115 -0
- antioch_py-2.0.6.dist-info/RECORD +99 -0
- antioch_py-2.0.6.dist-info/WHEEL +5 -0
- antioch_py-2.0.6.dist-info/entry_points.txt +2 -0
- antioch_py-2.0.6.dist-info/top_level.txt +2 -0
- common/__init__.py +0 -0
- common/ark/__init__.py +60 -0
- common/ark/ark.py +128 -0
- common/ark/hardware.py +121 -0
- common/ark/kinematics.py +31 -0
- common/ark/module.py +85 -0
- common/ark/node.py +94 -0
- common/ark/scheduler.py +439 -0
- common/ark/sim.py +33 -0
- common/assets/__init__.py +3 -0
- common/constants.py +47 -0
- common/core/__init__.py +52 -0
- common/core/agent.py +296 -0
- common/core/auth.py +305 -0
- common/core/registry.py +331 -0
- common/core/task.py +36 -0
- common/message/__init__.py +59 -0
- common/message/annotation.py +89 -0
- common/message/array.py +500 -0
- common/message/base.py +517 -0
- common/message/camera.py +91 -0
- common/message/color.py +139 -0
- common/message/frame.py +50 -0
- common/message/image.py +171 -0
- common/message/imu.py +14 -0
- common/message/joint.py +47 -0
- common/message/log.py +31 -0
- common/message/pir.py +16 -0
- common/message/point.py +109 -0
- common/message/point_cloud.py +63 -0
- common/message/pose.py +148 -0
- common/message/quaternion.py +273 -0
- common/message/radar.py +58 -0
- common/message/types.py +37 -0
- common/message/vector.py +786 -0
- common/rome/__init__.py +9 -0
- common/rome/client.py +430 -0
- common/rome/error.py +16 -0
- common/session/__init__.py +54 -0
- common/session/environment.py +31 -0
- common/session/sim.py +240 -0
- common/session/views/__init__.py +263 -0
- common/session/views/animation.py +73 -0
- common/session/views/articulation.py +184 -0
- common/session/views/basis_curve.py +102 -0
- common/session/views/camera.py +147 -0
- common/session/views/collision.py +59 -0
- common/session/views/geometry.py +102 -0
- common/session/views/ground_plane.py +41 -0
- common/session/views/imu.py +66 -0
- common/session/views/joint.py +81 -0
- common/session/views/light.py +96 -0
- common/session/views/pir_sensor.py +115 -0
- common/session/views/radar.py +82 -0
- common/session/views/rigid_body.py +236 -0
- common/session/views/viewport.py +21 -0
- common/session/views/xform.py +39 -0
- common/utils/__init__.py +4 -0
- common/utils/comms.py +571 -0
- common/utils/logger.py +123 -0
- common/utils/time.py +42 -0
- common/utils/usd.py +12 -0
common/message/vector.py
ADDED
|
@@ -0,0 +1,786 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
from pydantic import model_validator
|
|
7
|
+
|
|
8
|
+
from common.message.base import Message
|
|
9
|
+
from common.message.point import Point2, Point3
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from common.message.quaternion import Quaternion
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
from pxr import Gf # type: ignore
|
|
16
|
+
|
|
17
|
+
HAS_PXR = True
|
|
18
|
+
except ImportError:
|
|
19
|
+
HAS_PXR = False
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Vector2(Message):
|
|
23
|
+
"""
|
|
24
|
+
A 2D vector (x, y).
|
|
25
|
+
|
|
26
|
+
Serializes as a tuple [x, y] for space efficiency.
|
|
27
|
+
Automatically converts from common Python types:
|
|
28
|
+
- Lists: Vector2.from_any([1.0, 2.0])
|
|
29
|
+
- Tuples: Vector2.from_any((1.0, 2.0))
|
|
30
|
+
- Dicts: Vector2.from_any({"x": 1.0, "y": 2.0})
|
|
31
|
+
- Numpy arrays: Vector2.from_any(np.array([1.0, 2.0]))
|
|
32
|
+
|
|
33
|
+
When used as a field in Messages, conversion happens automatically:
|
|
34
|
+
pose = SomeMessage(position_2d=[10.0, 20.0]) # List auto-converts!
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
_type = "antioch/vector2"
|
|
38
|
+
data: tuple[float, float]
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def x(self) -> float:
|
|
42
|
+
"""Get the x component.
|
|
43
|
+
|
|
44
|
+
:return: The x component.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
return self.data[0]
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def y(self) -> float:
|
|
51
|
+
"""Get the y component.
|
|
52
|
+
|
|
53
|
+
:return: The y component.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
return self.data[1]
|
|
57
|
+
|
|
58
|
+
def __len__(self) -> int:
|
|
59
|
+
"""
|
|
60
|
+
Get the length of the vector (always 2).
|
|
61
|
+
|
|
62
|
+
:return: The length of the vector.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
return 2
|
|
66
|
+
|
|
67
|
+
def __iter__(self):
|
|
68
|
+
"""
|
|
69
|
+
Iterate over vector components.
|
|
70
|
+
|
|
71
|
+
:return: Iterator over [x, y].
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
return iter(self.data)
|
|
75
|
+
|
|
76
|
+
def __getitem__(self, index: int) -> float:
|
|
77
|
+
"""
|
|
78
|
+
Get vector component by index.
|
|
79
|
+
|
|
80
|
+
:param index: Index (0=x, 1=y).
|
|
81
|
+
:return: The component value.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
return self.data[index]
|
|
85
|
+
|
|
86
|
+
def __eq__(self, other: object) -> bool:
|
|
87
|
+
"""
|
|
88
|
+
Check equality with another Vector2.
|
|
89
|
+
|
|
90
|
+
:param other: Another Vector2.
|
|
91
|
+
:return: True if equal.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
if not isinstance(other, Vector2):
|
|
95
|
+
return False
|
|
96
|
+
return self.data == other.data
|
|
97
|
+
|
|
98
|
+
def __repr__(self) -> str:
|
|
99
|
+
"""
|
|
100
|
+
Return a readable string representation.
|
|
101
|
+
|
|
102
|
+
:return: String representation.
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
return f"Vector2(x={self.x}, y={self.y})"
|
|
106
|
+
|
|
107
|
+
def __str__(self) -> str:
|
|
108
|
+
"""
|
|
109
|
+
Return a readable string representation.
|
|
110
|
+
|
|
111
|
+
:return: String representation.
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
return f"Vector2(x={self.x}, y={self.y})"
|
|
115
|
+
|
|
116
|
+
def __add__(self, other: Vector2) -> Vector2:
|
|
117
|
+
"""
|
|
118
|
+
Add two vectors component-wise.
|
|
119
|
+
|
|
120
|
+
:param other: Another Vector2.
|
|
121
|
+
:return: The sum vector.
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
return Vector2(data=(self.x + other.x, self.y + other.y))
|
|
125
|
+
|
|
126
|
+
def __sub__(self, other: Vector2) -> Vector2:
|
|
127
|
+
"""
|
|
128
|
+
Subtract two vectors component-wise.
|
|
129
|
+
|
|
130
|
+
:param other: Another Vector2.
|
|
131
|
+
:return: The difference vector.
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
return Vector2(data=(self.x - other.x, self.y - other.y))
|
|
135
|
+
|
|
136
|
+
def __mul__(self, scalar: float) -> Vector2:
|
|
137
|
+
"""
|
|
138
|
+
Multiply vector by a scalar.
|
|
139
|
+
|
|
140
|
+
:param scalar: The scalar value.
|
|
141
|
+
:return: The scaled vector.
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
return Vector2(data=(self.x * scalar, self.y * scalar))
|
|
145
|
+
|
|
146
|
+
def __rmul__(self, scalar: float) -> Vector2:
|
|
147
|
+
"""
|
|
148
|
+
Multiply vector by a scalar (reversed operands).
|
|
149
|
+
|
|
150
|
+
:param scalar: The scalar value.
|
|
151
|
+
:return: The scaled vector.
|
|
152
|
+
"""
|
|
153
|
+
|
|
154
|
+
return self.__mul__(scalar)
|
|
155
|
+
|
|
156
|
+
def __truediv__(self, scalar: float) -> Vector2:
|
|
157
|
+
"""
|
|
158
|
+
Divide vector by a scalar.
|
|
159
|
+
|
|
160
|
+
:param scalar: The scalar value.
|
|
161
|
+
:return: The scaled vector.
|
|
162
|
+
:raises ZeroDivisionError: If scalar is zero.
|
|
163
|
+
"""
|
|
164
|
+
|
|
165
|
+
if scalar == 0:
|
|
166
|
+
raise ZeroDivisionError("Cannot divide vector by zero")
|
|
167
|
+
return Vector2(data=(self.x / scalar, self.y / scalar))
|
|
168
|
+
|
|
169
|
+
def __neg__(self) -> Vector2:
|
|
170
|
+
"""
|
|
171
|
+
Negate the vector.
|
|
172
|
+
|
|
173
|
+
:return: The negated vector.
|
|
174
|
+
"""
|
|
175
|
+
|
|
176
|
+
return Vector2(data=(-self.x, -self.y))
|
|
177
|
+
|
|
178
|
+
@model_validator(mode="before")
|
|
179
|
+
@classmethod
|
|
180
|
+
def convert_iterables(cls, data: Any) -> Any:
|
|
181
|
+
"""
|
|
182
|
+
Automatically convert lists, tuples, dicts, and numpy arrays to Vector2 format.
|
|
183
|
+
"""
|
|
184
|
+
|
|
185
|
+
# Already a Vector2 instance
|
|
186
|
+
if isinstance(data, cls):
|
|
187
|
+
return {"data": data.data}
|
|
188
|
+
|
|
189
|
+
# Tuple format - already correct
|
|
190
|
+
if isinstance(data, tuple) and len(data) == 2:
|
|
191
|
+
return {"data": (float(data[0]), float(data[1]))}
|
|
192
|
+
|
|
193
|
+
# Dict format with x, y - convert to tuple
|
|
194
|
+
if isinstance(data, dict):
|
|
195
|
+
if "data" in data:
|
|
196
|
+
return data
|
|
197
|
+
if "x" in data and "y" in data:
|
|
198
|
+
return {"data": (float(data["x"]), float(data["y"]))}
|
|
199
|
+
raise ValueError("Dict must have either 'data' or 'x' and 'y' fields")
|
|
200
|
+
|
|
201
|
+
# Numpy array - convert
|
|
202
|
+
if isinstance(data, np.ndarray):
|
|
203
|
+
if data.shape != (2,):
|
|
204
|
+
raise ValueError(f"Vector2 array must have shape (2,), got {data.shape}")
|
|
205
|
+
return {"data": (float(data[0]), float(data[1]))}
|
|
206
|
+
|
|
207
|
+
# List - convert
|
|
208
|
+
if isinstance(data, list):
|
|
209
|
+
if len(data) != 2:
|
|
210
|
+
raise ValueError(f"Vector2 requires 2 values, got {len(data)}")
|
|
211
|
+
return {"data": (float(data[0]), float(data[1]))}
|
|
212
|
+
|
|
213
|
+
return data
|
|
214
|
+
|
|
215
|
+
@classmethod
|
|
216
|
+
def new(cls, x: float, y: float) -> Vector2:
|
|
217
|
+
"""
|
|
218
|
+
Create a new 2D vector.
|
|
219
|
+
|
|
220
|
+
:param x: The x component.
|
|
221
|
+
:param y: The y component.
|
|
222
|
+
:return: A 2D vector.
|
|
223
|
+
"""
|
|
224
|
+
|
|
225
|
+
return cls(data=(x, y))
|
|
226
|
+
|
|
227
|
+
@classmethod
|
|
228
|
+
def zeros(cls) -> Vector2:
|
|
229
|
+
"""
|
|
230
|
+
Create a zero vector.
|
|
231
|
+
|
|
232
|
+
:return: A zero vector.
|
|
233
|
+
"""
|
|
234
|
+
|
|
235
|
+
return cls(data=(0.0, 0.0))
|
|
236
|
+
|
|
237
|
+
@classmethod
|
|
238
|
+
def ones(cls) -> Vector2:
|
|
239
|
+
"""
|
|
240
|
+
Create a ones vector.
|
|
241
|
+
|
|
242
|
+
:return: A ones vector.
|
|
243
|
+
"""
|
|
244
|
+
|
|
245
|
+
return cls(data=(1.0, 1.0))
|
|
246
|
+
|
|
247
|
+
@classmethod
|
|
248
|
+
def from_any(cls, data: Any) -> Vector2:
|
|
249
|
+
"""
|
|
250
|
+
Create Vector2 from any iterable (list, tuple, numpy array).
|
|
251
|
+
|
|
252
|
+
:param data: Iterable with 2 values, dict, or a Vector2 instance.
|
|
253
|
+
:return: A Vector2 instance.
|
|
254
|
+
:raises ValueError: If conversion fails.
|
|
255
|
+
"""
|
|
256
|
+
|
|
257
|
+
# Already a Vector2 - return as-is
|
|
258
|
+
if isinstance(data, cls):
|
|
259
|
+
return data
|
|
260
|
+
|
|
261
|
+
try:
|
|
262
|
+
# Will be handled by validator
|
|
263
|
+
return cls.model_validate(data)
|
|
264
|
+
except Exception as e:
|
|
265
|
+
raise ValueError(f"Cannot convert to Vector2: {e}") from None
|
|
266
|
+
|
|
267
|
+
@classmethod
|
|
268
|
+
def from_numpy(cls, array: np.ndarray) -> Vector2:
|
|
269
|
+
"""
|
|
270
|
+
Create from a numpy array.
|
|
271
|
+
|
|
272
|
+
:param array: The numpy array (must have shape (2,)).
|
|
273
|
+
:return: A Vector2.
|
|
274
|
+
:raises ValueError: If array shape is not (2,).
|
|
275
|
+
"""
|
|
276
|
+
|
|
277
|
+
if array.shape != (2,):
|
|
278
|
+
raise ValueError(f"Vector2 array must have shape (2,), got {array.shape}")
|
|
279
|
+
return cls(data=(float(array[0]), float(array[1])))
|
|
280
|
+
|
|
281
|
+
@classmethod
|
|
282
|
+
def from_list(cls, values: list[float]) -> Vector2:
|
|
283
|
+
"""
|
|
284
|
+
Create from a list of 2 values.
|
|
285
|
+
|
|
286
|
+
:param values: List of 2 float values.
|
|
287
|
+
:return: A Vector2.
|
|
288
|
+
:raises ValueError: If list does not have exactly 2 values.
|
|
289
|
+
"""
|
|
290
|
+
|
|
291
|
+
if len(values) != 2:
|
|
292
|
+
raise ValueError(f"Vector2 requires 2 values, got {len(values)}")
|
|
293
|
+
return cls(data=(values[0], values[1]))
|
|
294
|
+
|
|
295
|
+
def dot(self, other: Vector2) -> float:
|
|
296
|
+
"""
|
|
297
|
+
Compute dot product with another vector.
|
|
298
|
+
|
|
299
|
+
:param other: Another Vector2.
|
|
300
|
+
:return: The dot product.
|
|
301
|
+
"""
|
|
302
|
+
|
|
303
|
+
return self.x * other.x + self.y * other.y
|
|
304
|
+
|
|
305
|
+
def magnitude(self) -> float:
|
|
306
|
+
"""
|
|
307
|
+
Compute the magnitude (length) of the vector.
|
|
308
|
+
|
|
309
|
+
:return: The magnitude.
|
|
310
|
+
"""
|
|
311
|
+
|
|
312
|
+
return (self.x**2 + self.y**2) ** 0.5
|
|
313
|
+
|
|
314
|
+
def magnitude_squared(self) -> float:
|
|
315
|
+
"""
|
|
316
|
+
Compute the squared magnitude of the vector.
|
|
317
|
+
|
|
318
|
+
:return: The squared magnitude.
|
|
319
|
+
"""
|
|
320
|
+
|
|
321
|
+
return self.x**2 + self.y**2
|
|
322
|
+
|
|
323
|
+
def normalize(self) -> Vector2:
|
|
324
|
+
"""
|
|
325
|
+
Return a normalized (unit length) version of this vector.
|
|
326
|
+
|
|
327
|
+
:return: The normalized vector.
|
|
328
|
+
:raises ValueError: If the vector has zero magnitude.
|
|
329
|
+
"""
|
|
330
|
+
|
|
331
|
+
mag = self.magnitude()
|
|
332
|
+
if mag == 0:
|
|
333
|
+
raise ValueError("Cannot normalize zero vector")
|
|
334
|
+
return self / mag
|
|
335
|
+
|
|
336
|
+
def to_numpy(self) -> np.ndarray:
|
|
337
|
+
"""
|
|
338
|
+
Convert to a numpy array.
|
|
339
|
+
|
|
340
|
+
:return: The numpy array.
|
|
341
|
+
"""
|
|
342
|
+
|
|
343
|
+
return np.array(self.data, dtype=np.float32)
|
|
344
|
+
|
|
345
|
+
def to_list(self) -> list[float]:
|
|
346
|
+
"""
|
|
347
|
+
Convert to a list.
|
|
348
|
+
|
|
349
|
+
:return: The list of values.
|
|
350
|
+
"""
|
|
351
|
+
|
|
352
|
+
return list(self.data)
|
|
353
|
+
|
|
354
|
+
def to_point(self) -> Point2:
|
|
355
|
+
"""
|
|
356
|
+
Convert to a Point2.
|
|
357
|
+
|
|
358
|
+
:return: A Point2 with the same x, y coordinates.
|
|
359
|
+
"""
|
|
360
|
+
|
|
361
|
+
return Point2(x=self.x, y=self.y)
|
|
362
|
+
|
|
363
|
+
def as_array(self) -> list[float]:
|
|
364
|
+
"""
|
|
365
|
+
Convert to a list (alias for to_list for Rust compatibility).
|
|
366
|
+
|
|
367
|
+
:return: The list of values.
|
|
368
|
+
"""
|
|
369
|
+
|
|
370
|
+
return self.to_list()
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
class Vector3(Message):
|
|
374
|
+
"""
|
|
375
|
+
A 3D vector (x, y, z).
|
|
376
|
+
|
|
377
|
+
Serializes as a tuple [x, y, z] for space efficiency.
|
|
378
|
+
Automatically converts from common Python types:
|
|
379
|
+
- Lists: Vector3.from_any([1.0, 2.0, 3.0])
|
|
380
|
+
- Tuples: Vector3.from_any((1.0, 2.0, 3.0))
|
|
381
|
+
- Dicts: Vector3.from_any({"x": 1.0, "y": 2.0, "z": 3.0})
|
|
382
|
+
- Numpy arrays: Vector3.from_any(np.array([1.0, 2.0, 3.0]))
|
|
383
|
+
|
|
384
|
+
When used as a field in Messages, conversion happens automatically:
|
|
385
|
+
pose = SomeMessage(position=[10.0, 20.0, 30.0]) # List auto-converts!
|
|
386
|
+
"""
|
|
387
|
+
|
|
388
|
+
_type = "antioch/vector3"
|
|
389
|
+
data: tuple[float, float, float]
|
|
390
|
+
|
|
391
|
+
@property
|
|
392
|
+
def x(self) -> float:
|
|
393
|
+
"""Get the x component.
|
|
394
|
+
|
|
395
|
+
:return: The x component.
|
|
396
|
+
"""
|
|
397
|
+
|
|
398
|
+
return self.data[0]
|
|
399
|
+
|
|
400
|
+
@property
|
|
401
|
+
def y(self) -> float:
|
|
402
|
+
"""Get the y component.
|
|
403
|
+
|
|
404
|
+
:return: The y component.
|
|
405
|
+
"""
|
|
406
|
+
|
|
407
|
+
return self.data[1]
|
|
408
|
+
|
|
409
|
+
@property
|
|
410
|
+
def z(self) -> float:
|
|
411
|
+
"""Get the z component.
|
|
412
|
+
|
|
413
|
+
:return: The z component.
|
|
414
|
+
"""
|
|
415
|
+
|
|
416
|
+
return self.data[2]
|
|
417
|
+
|
|
418
|
+
def __len__(self) -> int:
|
|
419
|
+
"""
|
|
420
|
+
Get the length of the vector (always 3).
|
|
421
|
+
|
|
422
|
+
:return: The length of the vector.
|
|
423
|
+
"""
|
|
424
|
+
|
|
425
|
+
return 3
|
|
426
|
+
|
|
427
|
+
def __iter__(self):
|
|
428
|
+
"""
|
|
429
|
+
Iterate over vector components.
|
|
430
|
+
|
|
431
|
+
:return: Iterator over [x, y, z].
|
|
432
|
+
"""
|
|
433
|
+
|
|
434
|
+
return iter(self.data)
|
|
435
|
+
|
|
436
|
+
def __getitem__(self, index: int) -> float:
|
|
437
|
+
"""
|
|
438
|
+
Get vector component by index.
|
|
439
|
+
|
|
440
|
+
:param index: Index (0=x, 1=y, 2=z).
|
|
441
|
+
:return: The component value.
|
|
442
|
+
"""
|
|
443
|
+
|
|
444
|
+
return self.data[index]
|
|
445
|
+
|
|
446
|
+
def __eq__(self, other: object) -> bool:
|
|
447
|
+
"""
|
|
448
|
+
Check equality with another Vector3.
|
|
449
|
+
|
|
450
|
+
:param other: Another Vector3.
|
|
451
|
+
:return: True if equal.
|
|
452
|
+
"""
|
|
453
|
+
|
|
454
|
+
if not isinstance(other, Vector3):
|
|
455
|
+
return False
|
|
456
|
+
return self.data == other.data
|
|
457
|
+
|
|
458
|
+
def __repr__(self) -> str:
|
|
459
|
+
"""
|
|
460
|
+
Return a readable string representation.
|
|
461
|
+
|
|
462
|
+
:return: String representation.
|
|
463
|
+
"""
|
|
464
|
+
|
|
465
|
+
return f"Vector3(x={self.x}, y={self.y}, z={self.z})"
|
|
466
|
+
|
|
467
|
+
def __str__(self) -> str:
|
|
468
|
+
"""
|
|
469
|
+
Return a readable string representation.
|
|
470
|
+
|
|
471
|
+
:return: String representation.
|
|
472
|
+
"""
|
|
473
|
+
|
|
474
|
+
return f"Vector3(x={self.x}, y={self.y}, z={self.z})"
|
|
475
|
+
|
|
476
|
+
def __add__(self, other: Vector3) -> Vector3:
|
|
477
|
+
"""
|
|
478
|
+
Add two vectors component-wise.
|
|
479
|
+
|
|
480
|
+
:param other: Another Vector3.
|
|
481
|
+
:return: The sum vector.
|
|
482
|
+
"""
|
|
483
|
+
|
|
484
|
+
return Vector3(data=(self.x + other.x, self.y + other.y, self.z + other.z))
|
|
485
|
+
|
|
486
|
+
def __sub__(self, other: Vector3) -> Vector3:
|
|
487
|
+
"""
|
|
488
|
+
Subtract two vectors component-wise.
|
|
489
|
+
|
|
490
|
+
:param other: Another Vector3.
|
|
491
|
+
:return: The difference vector.
|
|
492
|
+
"""
|
|
493
|
+
|
|
494
|
+
return Vector3(data=(self.x - other.x, self.y - other.y, self.z - other.z))
|
|
495
|
+
|
|
496
|
+
def __mul__(self, scalar: float) -> Vector3:
|
|
497
|
+
"""
|
|
498
|
+
Multiply vector by a scalar.
|
|
499
|
+
|
|
500
|
+
:param scalar: The scalar value.
|
|
501
|
+
:return: The scaled vector.
|
|
502
|
+
"""
|
|
503
|
+
|
|
504
|
+
return Vector3(data=(self.x * scalar, self.y * scalar, self.z * scalar))
|
|
505
|
+
|
|
506
|
+
def __rmul__(self, scalar: float) -> Vector3:
|
|
507
|
+
"""
|
|
508
|
+
Multiply vector by a scalar (reversed operands).
|
|
509
|
+
|
|
510
|
+
:param scalar: The scalar value.
|
|
511
|
+
:return: The scaled vector.
|
|
512
|
+
"""
|
|
513
|
+
|
|
514
|
+
return self.__mul__(scalar)
|
|
515
|
+
|
|
516
|
+
def __truediv__(self, scalar: float) -> Vector3:
|
|
517
|
+
"""
|
|
518
|
+
Divide vector by a scalar.
|
|
519
|
+
|
|
520
|
+
:param scalar: The scalar value.
|
|
521
|
+
:return: The scaled vector.
|
|
522
|
+
:raises ZeroDivisionError: If scalar is zero.
|
|
523
|
+
"""
|
|
524
|
+
|
|
525
|
+
if scalar == 0:
|
|
526
|
+
raise ZeroDivisionError("Cannot divide vector by zero")
|
|
527
|
+
return Vector3(data=(self.x / scalar, self.y / scalar, self.z / scalar))
|
|
528
|
+
|
|
529
|
+
def __neg__(self) -> Vector3:
|
|
530
|
+
"""
|
|
531
|
+
Negate the vector.
|
|
532
|
+
|
|
533
|
+
:return: The negated vector.
|
|
534
|
+
"""
|
|
535
|
+
|
|
536
|
+
return Vector3(data=(-self.x, -self.y, -self.z))
|
|
537
|
+
|
|
538
|
+
@model_validator(mode="before")
|
|
539
|
+
@classmethod
|
|
540
|
+
def convert_iterables(cls, data: Any) -> Any:
|
|
541
|
+
"""
|
|
542
|
+
Automatically convert lists, tuples, dicts, and numpy arrays to Vector3 format.
|
|
543
|
+
"""
|
|
544
|
+
|
|
545
|
+
# Already a Vector3 instance
|
|
546
|
+
if isinstance(data, cls):
|
|
547
|
+
return {"data": data.data}
|
|
548
|
+
|
|
549
|
+
# Tuple format - already correct
|
|
550
|
+
if isinstance(data, tuple) and len(data) == 3:
|
|
551
|
+
return {"data": (float(data[0]), float(data[1]), float(data[2]))}
|
|
552
|
+
|
|
553
|
+
# Dict format with x, y, z - convert to tuple
|
|
554
|
+
if isinstance(data, dict):
|
|
555
|
+
if "data" in data:
|
|
556
|
+
return data
|
|
557
|
+
if "x" in data and "y" in data and "z" in data:
|
|
558
|
+
return {"data": (float(data["x"]), float(data["y"]), float(data["z"]))}
|
|
559
|
+
raise ValueError("Dict must have either 'data' or 'x', 'y', and 'z' fields")
|
|
560
|
+
|
|
561
|
+
# Numpy array - convert
|
|
562
|
+
if isinstance(data, np.ndarray):
|
|
563
|
+
if data.shape != (3,):
|
|
564
|
+
raise ValueError(f"Vector3 array must have shape (3,), got {data.shape}")
|
|
565
|
+
return {"data": (float(data[0]), float(data[1]), float(data[2]))}
|
|
566
|
+
|
|
567
|
+
# List - convert
|
|
568
|
+
if isinstance(data, list):
|
|
569
|
+
if len(data) != 3:
|
|
570
|
+
raise ValueError(f"Vector3 requires 3 values, got {len(data)}")
|
|
571
|
+
if all(item is None for item in data):
|
|
572
|
+
return {"data": (0.0, 0.0, 0.0)}
|
|
573
|
+
return {"data": (float(data[0]), float(data[1]), float(data[2]))}
|
|
574
|
+
|
|
575
|
+
return data
|
|
576
|
+
|
|
577
|
+
@classmethod
|
|
578
|
+
def new(cls, x: float, y: float, z: float) -> Vector3:
|
|
579
|
+
"""
|
|
580
|
+
Create a new 3D vector.
|
|
581
|
+
|
|
582
|
+
:param x: The x component.
|
|
583
|
+
:param y: The y component.
|
|
584
|
+
:param z: The z component.
|
|
585
|
+
:return: A 3D vector.
|
|
586
|
+
"""
|
|
587
|
+
|
|
588
|
+
return cls(data=(x, y, z))
|
|
589
|
+
|
|
590
|
+
@classmethod
|
|
591
|
+
def zeros(cls) -> Vector3:
|
|
592
|
+
"""
|
|
593
|
+
Create a zero vector.
|
|
594
|
+
|
|
595
|
+
:return: A zero vector.
|
|
596
|
+
"""
|
|
597
|
+
|
|
598
|
+
return cls(data=(0.0, 0.0, 0.0))
|
|
599
|
+
|
|
600
|
+
@classmethod
|
|
601
|
+
def ones(cls) -> Vector3:
|
|
602
|
+
"""
|
|
603
|
+
Create a ones vector.
|
|
604
|
+
|
|
605
|
+
:return: A ones vector.
|
|
606
|
+
"""
|
|
607
|
+
|
|
608
|
+
return cls(data=(1.0, 1.0, 1.0))
|
|
609
|
+
|
|
610
|
+
@classmethod
|
|
611
|
+
def from_any(cls, data: Any) -> Vector3:
|
|
612
|
+
"""
|
|
613
|
+
Create Vector3 from any iterable (list, tuple, numpy array).
|
|
614
|
+
|
|
615
|
+
:param data: Iterable with 3 values, dict, or a Vector3 instance.
|
|
616
|
+
:return: A Vector3 instance.
|
|
617
|
+
:raises ValueError: If conversion fails.
|
|
618
|
+
"""
|
|
619
|
+
|
|
620
|
+
# Already a Vector3 - return as-is
|
|
621
|
+
if isinstance(data, cls):
|
|
622
|
+
return data
|
|
623
|
+
|
|
624
|
+
try:
|
|
625
|
+
# Will be handled by validator
|
|
626
|
+
return cls.model_validate(data)
|
|
627
|
+
except Exception as e:
|
|
628
|
+
raise ValueError(f"Cannot convert to Vector3: {e}") from None
|
|
629
|
+
|
|
630
|
+
@classmethod
|
|
631
|
+
def from_numpy(cls, array: np.ndarray) -> Vector3:
|
|
632
|
+
"""
|
|
633
|
+
Create from a numpy array.
|
|
634
|
+
|
|
635
|
+
:param array: The numpy array (must have shape (3,)).
|
|
636
|
+
:return: A Vector3.
|
|
637
|
+
:raises ValueError: If array shape is not (3,).
|
|
638
|
+
"""
|
|
639
|
+
|
|
640
|
+
if array.shape != (3,):
|
|
641
|
+
raise ValueError(f"Vector3 array must have shape (3,), got {array.shape}")
|
|
642
|
+
return cls(data=(float(array[0]), float(array[1]), float(array[2])))
|
|
643
|
+
|
|
644
|
+
@classmethod
|
|
645
|
+
def from_list(cls, values: list[float]) -> Vector3:
|
|
646
|
+
"""
|
|
647
|
+
Create from a list of 3 values.
|
|
648
|
+
|
|
649
|
+
:param values: List of 3 float values.
|
|
650
|
+
:return: A Vector3.
|
|
651
|
+
:raises ValueError: If list does not have exactly 3 values.
|
|
652
|
+
"""
|
|
653
|
+
|
|
654
|
+
if len(values) != 3:
|
|
655
|
+
raise ValueError(f"Vector3 requires 3 values, got {len(values)}")
|
|
656
|
+
return cls(data=(values[0], values[1], values[2]))
|
|
657
|
+
|
|
658
|
+
def dot(self, other: Vector3) -> float:
|
|
659
|
+
"""
|
|
660
|
+
Compute dot product with another vector.
|
|
661
|
+
|
|
662
|
+
:param other: Another Vector3.
|
|
663
|
+
:return: The dot product.
|
|
664
|
+
"""
|
|
665
|
+
|
|
666
|
+
return self.x * other.x + self.y * other.y + self.z * other.z
|
|
667
|
+
|
|
668
|
+
def cross(self, other: Vector3) -> Vector3:
|
|
669
|
+
"""
|
|
670
|
+
Compute cross product with another vector.
|
|
671
|
+
|
|
672
|
+
:param other: Another Vector3.
|
|
673
|
+
:return: The cross product vector.
|
|
674
|
+
"""
|
|
675
|
+
|
|
676
|
+
return Vector3(
|
|
677
|
+
data=(
|
|
678
|
+
self.y * other.z - self.z * other.y,
|
|
679
|
+
self.z * other.x - self.x * other.z,
|
|
680
|
+
self.x * other.y - self.y * other.x,
|
|
681
|
+
)
|
|
682
|
+
)
|
|
683
|
+
|
|
684
|
+
def magnitude(self) -> float:
|
|
685
|
+
"""
|
|
686
|
+
Compute the magnitude (length) of the vector.
|
|
687
|
+
|
|
688
|
+
:return: The magnitude.
|
|
689
|
+
"""
|
|
690
|
+
|
|
691
|
+
return (self.x**2 + self.y**2 + self.z**2) ** 0.5
|
|
692
|
+
|
|
693
|
+
def magnitude_squared(self) -> float:
|
|
694
|
+
"""
|
|
695
|
+
Compute the squared magnitude of the vector.
|
|
696
|
+
|
|
697
|
+
:return: The squared magnitude.
|
|
698
|
+
"""
|
|
699
|
+
|
|
700
|
+
return self.x**2 + self.y**2 + self.z**2
|
|
701
|
+
|
|
702
|
+
def normalize(self) -> Vector3:
|
|
703
|
+
"""
|
|
704
|
+
Return a normalized (unit length) version of this vector.
|
|
705
|
+
|
|
706
|
+
:return: The normalized vector.
|
|
707
|
+
:raises ValueError: If the vector has zero magnitude.
|
|
708
|
+
"""
|
|
709
|
+
|
|
710
|
+
mag = self.magnitude()
|
|
711
|
+
if mag == 0:
|
|
712
|
+
raise ValueError("Cannot normalize zero vector")
|
|
713
|
+
return self / mag
|
|
714
|
+
|
|
715
|
+
def to_numpy(self) -> np.ndarray:
|
|
716
|
+
"""
|
|
717
|
+
Convert to a numpy array.
|
|
718
|
+
|
|
719
|
+
:return: The numpy array.
|
|
720
|
+
"""
|
|
721
|
+
|
|
722
|
+
return np.array(self.data, dtype=np.float32)
|
|
723
|
+
|
|
724
|
+
def to_list(self) -> list[float]:
|
|
725
|
+
"""
|
|
726
|
+
Convert to a list.
|
|
727
|
+
|
|
728
|
+
:return: The list of values.
|
|
729
|
+
"""
|
|
730
|
+
|
|
731
|
+
return list(self.data)
|
|
732
|
+
|
|
733
|
+
def to_gf_vec3f(self) -> Gf.Vec3f:
|
|
734
|
+
"""
|
|
735
|
+
Convert to a Gf.Vec3f.
|
|
736
|
+
|
|
737
|
+
:return: The Gf.Vec3f.
|
|
738
|
+
:raises ImportError: If pxr is not installed.
|
|
739
|
+
"""
|
|
740
|
+
|
|
741
|
+
if not HAS_PXR:
|
|
742
|
+
raise ImportError("pxr is not installed")
|
|
743
|
+
return Gf.Vec3f(self.x, self.y, self.z)
|
|
744
|
+
|
|
745
|
+
def to_gf_vec3d(self) -> Gf.Vec3d:
|
|
746
|
+
"""
|
|
747
|
+
Convert to a Gf.Vec3d (double precision).
|
|
748
|
+
|
|
749
|
+
:return: The Gf.Vec3d.
|
|
750
|
+
:raises ImportError: If pxr is not installed.
|
|
751
|
+
"""
|
|
752
|
+
|
|
753
|
+
if not HAS_PXR:
|
|
754
|
+
raise ImportError("pxr is not installed")
|
|
755
|
+
return Gf.Vec3d(self.x, self.y, self.z)
|
|
756
|
+
|
|
757
|
+
def to_quat(self) -> "Quaternion":
|
|
758
|
+
"""
|
|
759
|
+
Convert RPY angles to quaternion.
|
|
760
|
+
|
|
761
|
+
Assumes this Vector3 contains roll-pitch-yaw angles in radians.
|
|
762
|
+
|
|
763
|
+
:return: Quaternion representation.
|
|
764
|
+
"""
|
|
765
|
+
|
|
766
|
+
from common.message.quaternion import Quaternion
|
|
767
|
+
|
|
768
|
+
return Quaternion.from_rpy(self.x, self.y, self.z)
|
|
769
|
+
|
|
770
|
+
def to_point(self) -> Point3:
|
|
771
|
+
"""
|
|
772
|
+
Convert to a Point3.
|
|
773
|
+
|
|
774
|
+
:return: A Point3 with the same x, y, z coordinates.
|
|
775
|
+
"""
|
|
776
|
+
|
|
777
|
+
return Point3(x=self.x, y=self.y, z=self.z)
|
|
778
|
+
|
|
779
|
+
def as_array(self) -> list[float]:
|
|
780
|
+
"""
|
|
781
|
+
Convert to a list (alias for to_list for Rust compatibility).
|
|
782
|
+
|
|
783
|
+
:return: The list of values.
|
|
784
|
+
"""
|
|
785
|
+
|
|
786
|
+
return self.to_list()
|