antioch-py 2.2.4__py3-none-any.whl → 3.0.0__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.

Files changed (94) hide show
  1. antioch/__init__.py +101 -0
  2. antioch/{module/execution.py → execution.py} +1 -1
  3. antioch/{module/input.py → input.py} +2 -4
  4. antioch/{module/module.py → module.py} +17 -34
  5. antioch/{module/node.py → node.py} +17 -16
  6. {antioch_py-2.2.4.dist-info → antioch_py-3.0.0.dist-info}/METADATA +8 -11
  7. antioch_py-3.0.0.dist-info/RECORD +61 -0
  8. {antioch_py-2.2.4.dist-info → antioch_py-3.0.0.dist-info}/WHEEL +1 -1
  9. antioch_py-3.0.0.dist-info/licenses/LICENSE +21 -0
  10. common/ark/__init__.py +6 -16
  11. common/ark/ark.py +23 -62
  12. common/ark/hardware.py +1 -1
  13. common/ark/kinematics.py +1 -1
  14. common/ark/module.py +22 -0
  15. common/ark/node.py +46 -3
  16. common/ark/scheduler.py +2 -29
  17. common/ark/sim.py +1 -1
  18. {antioch/module → common/ark}/token.py +17 -0
  19. common/assets/rigging.usd +0 -0
  20. common/constants.py +63 -5
  21. common/core/__init__.py +37 -24
  22. common/core/auth.py +87 -112
  23. common/core/container.py +261 -0
  24. common/core/registry.py +131 -152
  25. common/core/rome.py +251 -0
  26. common/core/telemetry.py +176 -0
  27. common/core/types.py +219 -0
  28. common/message/__init__.py +19 -5
  29. common/message/annotation.py +174 -23
  30. common/message/array.py +25 -1
  31. common/message/camera.py +23 -1
  32. common/message/color.py +32 -6
  33. common/message/detection.py +40 -0
  34. common/message/foxglove.py +20 -0
  35. common/message/frame.py +71 -7
  36. common/message/image.py +58 -9
  37. common/message/imu.py +24 -4
  38. common/message/joint.py +69 -10
  39. common/message/log.py +52 -7
  40. common/message/pir.py +23 -8
  41. common/message/plot.py +57 -0
  42. common/message/point.py +55 -6
  43. common/message/point_cloud.py +55 -19
  44. common/message/pose.py +59 -19
  45. common/message/quaternion.py +105 -92
  46. common/message/radar.py +195 -29
  47. common/message/twist.py +34 -0
  48. common/message/types.py +40 -5
  49. common/message/vector.py +180 -245
  50. common/sim/__init__.py +49 -0
  51. common/{session/config.py → sim/objects.py} +97 -27
  52. common/sim/state.py +11 -0
  53. common/utils/comms.py +30 -12
  54. common/utils/logger.py +26 -7
  55. antioch/message.py +0 -87
  56. antioch/module/__init__.py +0 -53
  57. antioch/session/__init__.py +0 -152
  58. antioch/session/ark.py +0 -500
  59. antioch/session/asset.py +0 -65
  60. antioch/session/error.py +0 -80
  61. antioch/session/objects/__init__.py +0 -40
  62. antioch/session/objects/animation.py +0 -162
  63. antioch/session/objects/articulation.py +0 -180
  64. antioch/session/objects/basis_curve.py +0 -180
  65. antioch/session/objects/camera.py +0 -65
  66. antioch/session/objects/collision.py +0 -46
  67. antioch/session/objects/geometry.py +0 -58
  68. antioch/session/objects/ground_plane.py +0 -48
  69. antioch/session/objects/imu.py +0 -53
  70. antioch/session/objects/joint.py +0 -49
  71. antioch/session/objects/light.py +0 -123
  72. antioch/session/objects/pir_sensor.py +0 -102
  73. antioch/session/objects/radar.py +0 -62
  74. antioch/session/objects/rigid_body.py +0 -197
  75. antioch/session/objects/xform.py +0 -119
  76. antioch/session/record.py +0 -158
  77. antioch/session/scene.py +0 -1544
  78. antioch/session/session.py +0 -211
  79. antioch/session/task.py +0 -309
  80. antioch_py-2.2.4.dist-info/RECORD +0 -85
  81. antioch_py-2.2.4.dist-info/entry_points.txt +0 -2
  82. common/core/agent.py +0 -324
  83. common/core/task.py +0 -36
  84. common/message/velocity.py +0 -11
  85. common/rome/__init__.py +0 -9
  86. common/rome/client.py +0 -435
  87. common/rome/error.py +0 -16
  88. common/session/__init__.py +0 -31
  89. common/session/environment.py +0 -31
  90. common/session/sim.py +0 -129
  91. common/utils/usd.py +0 -12
  92. /antioch/{module/clock.py → clock.py} +0 -0
  93. {antioch_py-2.2.4.dist-info → antioch_py-3.0.0.dist-info}/top_level.txt +0 -0
  94. /common/message/{base.py → message.py} +0 -0
common/message/vector.py CHANGED
@@ -1,15 +1,14 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING, Any
3
+ from typing import Any
4
4
 
5
5
  import numpy as np
6
- from pydantic import model_validator
6
+ from foxglove.schemas import Vector2 as FoxgloveVector2, Vector3 as FoxgloveVector3
7
+ from pydantic import Field, model_validator
7
8
 
8
- from common.message.base import Message
9
+ from common.message.message import Message
9
10
  from common.message.point import Point2, Point3
10
-
11
- if TYPE_CHECKING:
12
- from common.message.quaternion import Quaternion
11
+ from common.message.quaternion import Quaternion
13
12
 
14
13
  try:
15
14
  from pxr import Gf # type: ignore
@@ -23,37 +22,32 @@ class Vector2(Message):
23
22
  """
24
23
  A 2D vector (x, y).
25
24
 
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.
25
+ Supports multiple construction styles:
26
+ - Vector2(x=1.0, y=2.0) - keyword args
27
+ - Vector2.from_any([1.0, 2.0]) - from list/tuple
28
+ - Vector2.from_any((1.0, 2.0)) - from tuple
43
29
 
44
- :return: The x component.
45
- """
30
+ Example:
31
+ ```python
32
+ from common.message import Vector2
46
33
 
47
- return self.data[0]
34
+ # Create a 2D vector
35
+ v = Vector2(x=3.0, y=4.0)
48
36
 
49
- @property
50
- def y(self) -> float:
51
- """Get the y component.
37
+ # Vector operations
38
+ magnitude = v.magnitude() # 5.0
39
+ normalized = v.normalize()
52
40
 
53
- :return: The y component.
54
- """
41
+ # Arithmetic operations
42
+ v2 = Vector2(x=1.0, y=2.0)
43
+ sum_v = v + v2
44
+ scaled = v * 2.0
45
+ ```
46
+ """
55
47
 
56
- return self.data[1]
48
+ _type = "antioch/vector2"
49
+ x: float = Field(default=0.0, description="X component")
50
+ y: float = Field(default=0.0, description="Y component")
57
51
 
58
52
  def __len__(self) -> int:
59
53
  """
@@ -71,7 +65,7 @@ class Vector2(Message):
71
65
  :return: Iterator over [x, y].
72
66
  """
73
67
 
74
- return iter(self.data)
68
+ return iter((self.x, self.y))
75
69
 
76
70
  def __getitem__(self, index: int) -> float:
77
71
  """
@@ -81,7 +75,7 @@ class Vector2(Message):
81
75
  :return: The component value.
82
76
  """
83
77
 
84
- return self.data[index]
78
+ return (self.x, self.y)[index]
85
79
 
86
80
  def __eq__(self, other: object) -> bool:
87
81
  """
@@ -93,7 +87,7 @@ class Vector2(Message):
93
87
 
94
88
  if not isinstance(other, Vector2):
95
89
  return False
96
- return self.data == other.data
90
+ return self.x == other.x and self.y == other.y
97
91
 
98
92
  def __repr__(self) -> str:
99
93
  """
@@ -102,7 +96,7 @@ class Vector2(Message):
102
96
  :return: String representation.
103
97
  """
104
98
 
105
- return f"Vector2(x={self.x}, y={self.y})"
99
+ return f"Vector2({self.x}, {self.y})"
106
100
 
107
101
  def __str__(self) -> str:
108
102
  """
@@ -111,7 +105,7 @@ class Vector2(Message):
111
105
  :return: String representation.
112
106
  """
113
107
 
114
- return f"Vector2(x={self.x}, y={self.y})"
108
+ return f"Vector2({self.x}, {self.y})"
115
109
 
116
110
  def __add__(self, other: Vector2) -> Vector2:
117
111
  """
@@ -121,7 +115,7 @@ class Vector2(Message):
121
115
  :return: The sum vector.
122
116
  """
123
117
 
124
- return Vector2(data=(self.x + other.x, self.y + other.y))
118
+ return Vector2(x=self.x + other.x, y=self.y + other.y)
125
119
 
126
120
  def __sub__(self, other: Vector2) -> Vector2:
127
121
  """
@@ -131,7 +125,7 @@ class Vector2(Message):
131
125
  :return: The difference vector.
132
126
  """
133
127
 
134
- return Vector2(data=(self.x - other.x, self.y - other.y))
128
+ return Vector2(x=self.x - other.x, y=self.y - other.y)
135
129
 
136
130
  def __mul__(self, scalar: float) -> Vector2:
137
131
  """
@@ -141,7 +135,7 @@ class Vector2(Message):
141
135
  :return: The scaled vector.
142
136
  """
143
137
 
144
- return Vector2(data=(self.x * scalar, self.y * scalar))
138
+ return Vector2(x=self.x * scalar, y=self.y * scalar)
145
139
 
146
140
  def __rmul__(self, scalar: float) -> Vector2:
147
141
  """
@@ -164,7 +158,7 @@ class Vector2(Message):
164
158
 
165
159
  if scalar == 0:
166
160
  raise ZeroDivisionError("Cannot divide vector by zero")
167
- return Vector2(data=(self.x / scalar, self.y / scalar))
161
+ return Vector2(x=self.x / scalar, y=self.y / scalar)
168
162
 
169
163
  def __neg__(self) -> Vector2:
170
164
  """
@@ -173,56 +167,7 @@ class Vector2(Message):
173
167
  :return: The negated vector.
174
168
  """
175
169
 
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))
170
+ return Vector2(x=-self.x, y=-self.y)
226
171
 
227
172
  @classmethod
228
173
  def zeros(cls) -> Vector2:
@@ -232,7 +177,7 @@ class Vector2(Message):
232
177
  :return: A zero vector.
233
178
  """
234
179
 
235
- return cls(data=(0.0, 0.0))
180
+ return cls(x=0.0, y=0.0)
236
181
 
237
182
  @classmethod
238
183
  def ones(cls) -> Vector2:
@@ -242,27 +187,29 @@ class Vector2(Message):
242
187
  :return: A ones vector.
243
188
  """
244
189
 
245
- return cls(data=(1.0, 1.0))
190
+ return cls(x=1.0, y=1.0)
246
191
 
247
192
  @classmethod
248
- def from_any(cls, data: Any) -> Vector2:
193
+ def from_any(cls, value: Vector2 | tuple | list | dict | np.ndarray | None) -> Vector2:
249
194
  """
250
- Create Vector2 from any iterable (list, tuple, numpy array).
195
+ Create Vector2 from any compatible type.
196
+
197
+ Accepts:
198
+ - Vector2 instance (returned as-is)
199
+ - Tuple/list of 2 floats: (x, y)
200
+ - Dict with 'x' and 'y' keys
201
+ - Numpy array of shape (2,)
202
+ - None (returns zero vector)
251
203
 
252
- :param data: Iterable with 2 values, dict, or a Vector2 instance.
204
+ :param value: Any compatible input type.
253
205
  :return: A Vector2 instance.
254
- :raises ValueError: If conversion fails.
255
206
  """
256
207
 
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
208
+ if value is None:
209
+ return cls(x=0.0, y=0.0)
210
+ if isinstance(value, cls):
211
+ return value
212
+ return cls.model_validate(value)
266
213
 
267
214
  @classmethod
268
215
  def from_numpy(cls, array: np.ndarray) -> Vector2:
@@ -276,7 +223,7 @@ class Vector2(Message):
276
223
 
277
224
  if array.shape != (2,):
278
225
  raise ValueError(f"Vector2 array must have shape (2,), got {array.shape}")
279
- return cls(data=(float(array[0]), float(array[1])))
226
+ return cls(x=float(array[0]), y=float(array[1]))
280
227
 
281
228
  @classmethod
282
229
  def from_list(cls, values: list[float]) -> Vector2:
@@ -290,7 +237,7 @@ class Vector2(Message):
290
237
 
291
238
  if len(values) != 2:
292
239
  raise ValueError(f"Vector2 requires 2 values, got {len(values)}")
293
- return cls(data=(values[0], values[1]))
240
+ return cls(x=values[0], y=values[1])
294
241
 
295
242
  def dot(self, other: Vector2) -> float:
296
243
  """
@@ -340,7 +287,7 @@ class Vector2(Message):
340
287
  :return: The numpy array.
341
288
  """
342
289
 
343
- return np.array(self.data, dtype=np.float32)
290
+ return np.array([self.x, self.y], dtype=np.float32)
344
291
 
345
292
  def to_list(self) -> list[float]:
346
293
  """
@@ -349,7 +296,16 @@ class Vector2(Message):
349
296
  :return: The list of values.
350
297
  """
351
298
 
352
- return list(self.data)
299
+ return [self.x, self.y]
300
+
301
+ def to_tuple(self) -> tuple[float, float]:
302
+ """
303
+ Convert to a tuple.
304
+
305
+ :return: The tuple of values.
306
+ """
307
+
308
+ return (self.x, self.y)
353
309
 
354
310
  def to_point(self) -> Point2:
355
311
  """
@@ -360,60 +316,67 @@ class Vector2(Message):
360
316
 
361
317
  return Point2(x=self.x, y=self.y)
362
318
 
363
- def as_array(self) -> list[float]:
319
+ def to_foxglove(self) -> FoxgloveVector2:
364
320
  """
365
- Convert to a list (alias for to_list for Rust compatibility).
321
+ Convert to Foxglove Vector2 for telemetry.
366
322
 
367
- :return: The list of values.
323
+ :return: Foxglove Vector2 schema.
368
324
  """
369
325
 
370
- return self.to_list()
326
+ return FoxgloveVector2(x=self.x, y=self.y)
371
327
 
328
+ @model_validator(mode="before")
329
+ @classmethod
330
+ def _convert_input(cls, data: Any) -> Any:
331
+ """
332
+ Convert various input types to Vector2 format.
333
+ """
372
334
 
373
- class Vector3(Message):
374
- """
375
- A 3D vector (x, y, z).
335
+ if isinstance(data, cls):
336
+ return {"x": data.x, "y": data.y}
337
+ if isinstance(data, (tuple, list)) and len(data) == 2:
338
+ return {"x": float(data[0]), "y": float(data[1])}
339
+ if isinstance(data, np.ndarray) and data.shape == (2,):
340
+ return {"x": float(data[0]), "y": float(data[1])}
341
+ if isinstance(data, dict) and "x" in data and "y" in data:
342
+ return {"x": float(data["x"]), "y": float(data["y"])}
343
+ return data
376
344
 
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
345
 
384
- When used as a field in Messages, conversion happens automatically:
385
- pose = SomeMessage(position=[10.0, 20.0, 30.0]) # List auto-converts!
346
+ class Vector3(Message):
386
347
  """
348
+ A 3D vector (x, y, z).
387
349
 
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]
350
+ Supports multiple construction styles:
351
+ - Vector3(x=1.0, y=2.0, z=3.0) - keyword args
352
+ - Vector3.from_any([1.0, 2.0, 3.0]) - from list
353
+ - Vector3.from_any((1.0, 2.0, 3.0)) - from tuple
399
354
 
400
- @property
401
- def y(self) -> float:
402
- """Get the y component.
355
+ Example:
356
+ ```python
357
+ from common.message import Vector3
403
358
 
404
- :return: The y component.
405
- """
359
+ # Create a 3D vector
360
+ v = Vector3(x=1.0, y=2.0, z=3.0)
406
361
 
407
- return self.data[1]
362
+ # Vector operations
363
+ magnitude = v.magnitude()
364
+ normalized = v.normalize()
408
365
 
409
- @property
410
- def z(self) -> float:
411
- """Get the z component.
366
+ # Cross and dot products
367
+ v2 = Vector3(x=0.0, y=1.0, z=0.0)
368
+ cross = v.cross(v2)
369
+ dot = v.dot(v2)
412
370
 
413
- :return: The z component.
414
- """
371
+ # Convert to numpy
372
+ arr = v.to_numpy()
373
+ ```
374
+ """
415
375
 
416
- return self.data[2]
376
+ _type = "antioch/vector3"
377
+ x: float = Field(default=0.0, description="X component")
378
+ y: float = Field(default=0.0, description="Y component")
379
+ z: float = Field(default=0.0, description="Z component")
417
380
 
418
381
  def __len__(self) -> int:
419
382
  """
@@ -431,7 +394,7 @@ class Vector3(Message):
431
394
  :return: Iterator over [x, y, z].
432
395
  """
433
396
 
434
- return iter(self.data)
397
+ return iter((self.x, self.y, self.z))
435
398
 
436
399
  def __getitem__(self, index: int) -> float:
437
400
  """
@@ -441,7 +404,7 @@ class Vector3(Message):
441
404
  :return: The component value.
442
405
  """
443
406
 
444
- return self.data[index]
407
+ return (self.x, self.y, self.z)[index]
445
408
 
446
409
  def __eq__(self, other: object) -> bool:
447
410
  """
@@ -453,7 +416,7 @@ class Vector3(Message):
453
416
 
454
417
  if not isinstance(other, Vector3):
455
418
  return False
456
- return self.data == other.data
419
+ return self.x == other.x and self.y == other.y and self.z == other.z
457
420
 
458
421
  def __repr__(self) -> str:
459
422
  """
@@ -462,7 +425,7 @@ class Vector3(Message):
462
425
  :return: String representation.
463
426
  """
464
427
 
465
- return f"Vector3(x={self.x}, y={self.y}, z={self.z})"
428
+ return f"Vector3({self.x}, {self.y}, {self.z})"
466
429
 
467
430
  def __str__(self) -> str:
468
431
  """
@@ -471,7 +434,7 @@ class Vector3(Message):
471
434
  :return: String representation.
472
435
  """
473
436
 
474
- return f"Vector3(x={self.x}, y={self.y}, z={self.z})"
437
+ return f"Vector3({self.x}, {self.y}, {self.z})"
475
438
 
476
439
  def __add__(self, other: Vector3) -> Vector3:
477
440
  """
@@ -481,7 +444,7 @@ class Vector3(Message):
481
444
  :return: The sum vector.
482
445
  """
483
446
 
484
- return Vector3(data=(self.x + other.x, self.y + other.y, self.z + other.z))
447
+ return Vector3(x=self.x + other.x, y=self.y + other.y, z=self.z + other.z)
485
448
 
486
449
  def __sub__(self, other: Vector3) -> Vector3:
487
450
  """
@@ -491,7 +454,7 @@ class Vector3(Message):
491
454
  :return: The difference vector.
492
455
  """
493
456
 
494
- return Vector3(data=(self.x - other.x, self.y - other.y, self.z - other.z))
457
+ return Vector3(x=self.x - other.x, y=self.y - other.y, z=self.z - other.z)
495
458
 
496
459
  def __mul__(self, scalar: float) -> Vector3:
497
460
  """
@@ -501,7 +464,7 @@ class Vector3(Message):
501
464
  :return: The scaled vector.
502
465
  """
503
466
 
504
- return Vector3(data=(self.x * scalar, self.y * scalar, self.z * scalar))
467
+ return Vector3(x=self.x * scalar, y=self.y * scalar, z=self.z * scalar)
505
468
 
506
469
  def __rmul__(self, scalar: float) -> Vector3:
507
470
  """
@@ -524,7 +487,7 @@ class Vector3(Message):
524
487
 
525
488
  if scalar == 0:
526
489
  raise ZeroDivisionError("Cannot divide vector by zero")
527
- return Vector3(data=(self.x / scalar, self.y / scalar, self.z / scalar))
490
+ return Vector3(x=self.x / scalar, y=self.y / scalar, z=self.z / scalar)
528
491
 
529
492
  def __neg__(self) -> Vector3:
530
493
  """
@@ -533,59 +496,7 @@ class Vector3(Message):
533
496
  :return: The negated vector.
534
497
  """
535
498
 
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))
499
+ return Vector3(x=-self.x, y=-self.y, z=-self.z)
589
500
 
590
501
  @classmethod
591
502
  def zeros(cls) -> Vector3:
@@ -595,7 +506,7 @@ class Vector3(Message):
595
506
  :return: A zero vector.
596
507
  """
597
508
 
598
- return cls(data=(0.0, 0.0, 0.0))
509
+ return cls(x=0.0, y=0.0, z=0.0)
599
510
 
600
511
  @classmethod
601
512
  def ones(cls) -> Vector3:
@@ -605,27 +516,29 @@ class Vector3(Message):
605
516
  :return: A ones vector.
606
517
  """
607
518
 
608
- return cls(data=(1.0, 1.0, 1.0))
519
+ return cls(x=1.0, y=1.0, z=1.0)
609
520
 
610
521
  @classmethod
611
- def from_any(cls, data: Any) -> Vector3:
522
+ def from_any(cls, value: Vector3 | tuple | list | dict | np.ndarray | None) -> Vector3:
612
523
  """
613
- Create Vector3 from any iterable (list, tuple, numpy array).
524
+ Create Vector3 from any compatible type.
525
+
526
+ Accepts:
527
+ - Vector3 instance (returned as-is)
528
+ - Tuple/list of 3 floats: (x, y, z)
529
+ - Dict with 'x', 'y', and 'z' keys
530
+ - Numpy array of shape (3,)
531
+ - None (returns zero vector)
614
532
 
615
- :param data: Iterable with 3 values, dict, or a Vector3 instance.
533
+ :param value: Any compatible input type.
616
534
  :return: A Vector3 instance.
617
- :raises ValueError: If conversion fails.
618
535
  """
619
536
 
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
537
+ if value is None:
538
+ return cls(x=0.0, y=0.0, z=0.0)
539
+ if isinstance(value, cls):
540
+ return value
541
+ return cls.model_validate(value)
629
542
 
630
543
  @classmethod
631
544
  def from_numpy(cls, array: np.ndarray) -> Vector3:
@@ -639,7 +552,7 @@ class Vector3(Message):
639
552
 
640
553
  if array.shape != (3,):
641
554
  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])))
555
+ return cls(x=float(array[0]), y=float(array[1]), z=float(array[2]))
643
556
 
644
557
  @classmethod
645
558
  def from_list(cls, values: list[float]) -> Vector3:
@@ -653,7 +566,7 @@ class Vector3(Message):
653
566
 
654
567
  if len(values) != 3:
655
568
  raise ValueError(f"Vector3 requires 3 values, got {len(values)}")
656
- return cls(data=(values[0], values[1], values[2]))
569
+ return cls(x=values[0], y=values[1], z=values[2])
657
570
 
658
571
  def dot(self, other: Vector3) -> float:
659
572
  """
@@ -674,11 +587,9 @@ class Vector3(Message):
674
587
  """
675
588
 
676
589
  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
- )
590
+ x=self.y * other.z - self.z * other.y,
591
+ y=self.z * other.x - self.x * other.z,
592
+ z=self.x * other.y - self.y * other.x,
682
593
  )
683
594
 
684
595
  def magnitude(self) -> float:
@@ -719,7 +630,7 @@ class Vector3(Message):
719
630
  :return: The numpy array.
720
631
  """
721
632
 
722
- return np.array(self.data, dtype=np.float32)
633
+ return np.array([self.x, self.y, self.z], dtype=np.float32)
723
634
 
724
635
  def to_list(self) -> list[float]:
725
636
  """
@@ -728,7 +639,16 @@ class Vector3(Message):
728
639
  :return: The list of values.
729
640
  """
730
641
 
731
- return list(self.data)
642
+ return [self.x, self.y, self.z]
643
+
644
+ def to_tuple(self) -> tuple[float, float, float]:
645
+ """
646
+ Convert to a tuple.
647
+
648
+ :return: The tuple of values.
649
+ """
650
+
651
+ return (self.x, self.y, self.z)
732
652
 
733
653
  def to_gf_vec3f(self) -> Gf.Vec3f:
734
654
  """
@@ -738,8 +658,8 @@ class Vector3(Message):
738
658
  :raises ImportError: If pxr is not installed.
739
659
  """
740
660
 
741
- if not HAS_PXR:
742
- raise ImportError("pxr is not installed")
661
+ from pxr import Gf
662
+
743
663
  return Gf.Vec3f(self.x, self.y, self.z)
744
664
 
745
665
  def to_gf_vec3d(self) -> Gf.Vec3d:
@@ -750,11 +670,11 @@ class Vector3(Message):
750
670
  :raises ImportError: If pxr is not installed.
751
671
  """
752
672
 
753
- if not HAS_PXR:
754
- raise ImportError("pxr is not installed")
673
+ from pxr import Gf
674
+
755
675
  return Gf.Vec3d(self.x, self.y, self.z)
756
676
 
757
- def to_quat(self) -> "Quaternion":
677
+ def to_quat(self) -> Quaternion:
758
678
  """
759
679
  Convert RPY angles to quaternion.
760
680
 
@@ -763,8 +683,6 @@ class Vector3(Message):
763
683
  :return: Quaternion representation.
764
684
  """
765
685
 
766
- from common.message.quaternion import Quaternion
767
-
768
686
  return Quaternion.from_rpy(self.x, self.y, self.z)
769
687
 
770
688
  def to_point(self) -> Point3:
@@ -776,11 +694,28 @@ class Vector3(Message):
776
694
 
777
695
  return Point3(x=self.x, y=self.y, z=self.z)
778
696
 
779
- def as_array(self) -> list[float]:
697
+ def to_foxglove(self) -> FoxgloveVector3:
780
698
  """
781
- Convert to a list (alias for to_list for Rust compatibility).
699
+ Convert to Foxglove Vector3 for telemetry.
782
700
 
783
- :return: The list of values.
701
+ :return: Foxglove Vector3 schema.
784
702
  """
785
703
 
786
- return self.to_list()
704
+ return FoxgloveVector3(x=self.x, y=self.y, z=self.z)
705
+
706
+ @model_validator(mode="before")
707
+ @classmethod
708
+ def _convert_input(cls, data: Any) -> Any:
709
+ """
710
+ Convert various input types to Vector3 format.
711
+ """
712
+
713
+ if isinstance(data, cls):
714
+ return {"x": data.x, "y": data.y, "z": data.z}
715
+ if isinstance(data, (tuple, list)) and len(data) == 3:
716
+ return {"x": float(data[0]), "y": float(data[1]), "z": float(data[2])}
717
+ if isinstance(data, np.ndarray) and data.shape == (3,):
718
+ return {"x": float(data[0]), "y": float(data[1]), "z": float(data[2])}
719
+ if isinstance(data, dict) and "x" in data and "y" in data and "z" in data:
720
+ return {"x": float(data["x"]), "y": float(data["y"]), "z": float(data["z"])}
721
+ return data