antioch-py 2.0.6__py3-none-any.whl → 3.0.12__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 (109) 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.0.6.dist-info → antioch_py-3.0.12.dist-info}/METADATA +8 -11
  7. antioch_py-3.0.12.dist-info/RECORD +61 -0
  8. {antioch_py-2.0.6.dist-info → antioch_py-3.0.12.dist-info}/WHEEL +1 -1
  9. antioch_py-3.0.12.dist-info/licenses/LICENSE +21 -0
  10. common/ark/__init__.py +6 -16
  11. common/ark/ark.py +23 -60
  12. common/ark/hardware.py +13 -37
  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 +83 -4
  21. common/core/__init__.py +37 -24
  22. common/core/auth.py +87 -114
  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 -3
  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 +22 -5
  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/sim/objects.py +460 -0
  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 -150
  58. antioch/session/ark.py +0 -504
  59. antioch/session/asset.py +0 -65
  60. antioch/session/error.py +0 -80
  61. antioch/session/record.py +0 -158
  62. antioch/session/scene.py +0 -1521
  63. antioch/session/session.py +0 -220
  64. antioch/session/task.py +0 -323
  65. antioch/session/views/__init__.py +0 -40
  66. antioch/session/views/animation.py +0 -189
  67. antioch/session/views/articulation.py +0 -245
  68. antioch/session/views/basis_curve.py +0 -186
  69. antioch/session/views/camera.py +0 -92
  70. antioch/session/views/collision.py +0 -75
  71. antioch/session/views/geometry.py +0 -74
  72. antioch/session/views/ground_plane.py +0 -63
  73. antioch/session/views/imu.py +0 -73
  74. antioch/session/views/joint.py +0 -64
  75. antioch/session/views/light.py +0 -175
  76. antioch/session/views/pir_sensor.py +0 -140
  77. antioch/session/views/radar.py +0 -73
  78. antioch/session/views/rigid_body.py +0 -282
  79. antioch/session/views/xform.py +0 -119
  80. antioch_py-2.0.6.dist-info/RECORD +0 -99
  81. antioch_py-2.0.6.dist-info/entry_points.txt +0 -2
  82. common/core/agent.py +0 -296
  83. common/core/task.py +0 -36
  84. common/rome/__init__.py +0 -9
  85. common/rome/client.py +0 -430
  86. common/rome/error.py +0 -16
  87. common/session/__init__.py +0 -54
  88. common/session/environment.py +0 -31
  89. common/session/sim.py +0 -240
  90. common/session/views/__init__.py +0 -263
  91. common/session/views/animation.py +0 -73
  92. common/session/views/articulation.py +0 -184
  93. common/session/views/basis_curve.py +0 -102
  94. common/session/views/camera.py +0 -147
  95. common/session/views/collision.py +0 -59
  96. common/session/views/geometry.py +0 -102
  97. common/session/views/ground_plane.py +0 -41
  98. common/session/views/imu.py +0 -66
  99. common/session/views/joint.py +0 -81
  100. common/session/views/light.py +0 -96
  101. common/session/views/pir_sensor.py +0 -115
  102. common/session/views/radar.py +0 -82
  103. common/session/views/rigid_body.py +0 -236
  104. common/session/views/viewport.py +0 -21
  105. common/session/views/xform.py +0 -39
  106. common/utils/usd.py +0 -12
  107. /antioch/{module/clock.py → clock.py} +0 -0
  108. {antioch_py-2.0.6.dist-info → antioch_py-3.0.12.dist-info}/top_level.txt +0 -0
  109. /common/message/{base.py → message.py} +0 -0
common/message/joint.py CHANGED
@@ -1,4 +1,6 @@
1
- from common.message.base import Message
1
+ from pydantic import Field
2
+
3
+ from common.message.message import Message
2
4
 
3
5
 
4
6
  class JointState(Message):
@@ -7,12 +9,27 @@ class JointState(Message):
7
9
 
8
10
  Represents the complete physical state of a joint including its position,
9
11
  velocity, and measured effort (force/torque).
12
+
13
+ Example:
14
+ ```python
15
+ from common.message import JointState
16
+
17
+ # Create a joint state
18
+ state = JointState(
19
+ name="shoulder_pan",
20
+ position=1.57,
21
+ velocity=0.1,
22
+ effort=5.0,
23
+ )
24
+ print(f"{state.name}: pos={state.position:.2f} rad")
25
+ ```
10
26
  """
11
27
 
12
28
  _type = "antioch/joint_state"
13
- position: float
14
- velocity: float
15
- effort: float
29
+ name: str = Field(description="Name of the joint")
30
+ position: float = Field(description="Joint position in radians")
31
+ velocity: float = Field(description="Joint velocity in radians per second")
32
+ effort: float = Field(description="Joint effort (force/torque) in Nm")
16
33
 
17
34
 
18
35
  class JointTarget(Message):
@@ -20,28 +37,70 @@ class JointTarget(Message):
20
37
  Control target for a single joint.
21
38
 
22
39
  Specifies desired position, velocity, and/or effort targets for a joint's
23
- PD controller. All fields are optional - omitted values are not controlled.
40
+ PD controller. The name field is required to identify which joint is being
41
+ targeted. Position, velocity, and effort are optional - omitted values are
42
+ not controlled.
43
+
44
+ Example:
45
+ ```python
46
+ from common.message import JointTarget
47
+
48
+ # Position control target
49
+ target = JointTarget(name="elbow", position=0.5)
50
+
51
+ # Velocity control target
52
+ vel_target = JointTarget(name="wrist", velocity=0.2)
53
+
54
+ # Combined position and velocity target
55
+ combined = JointTarget(name="shoulder", position=1.0, velocity=0.1)
56
+ ```
24
57
  """
25
58
 
26
59
  _type = "antioch/joint_target"
27
- position: float | None = None
28
- velocity: float | None = None
29
- effort: float | None = None
60
+ name: str = Field(description="Name of the joint to control")
61
+ position: float | None = Field(default=None, description="Target position in radians")
62
+ velocity: float | None = Field(default=None, description="Target velocity in radians per second")
63
+ effort: float | None = Field(default=None, description="Target effort (force/torque) in Nm")
30
64
 
31
65
 
32
66
  class JointStates(Message):
33
67
  """
34
68
  Collection of joint states for an actuator group.
69
+
70
+ Example:
71
+ ```python
72
+ from common.message import JointStates, JointState
73
+
74
+ # Create states for a robot arm
75
+ states = JointStates(states=[
76
+ JointState(name="joint1", position=0.0, velocity=0.0, effort=0.0),
77
+ JointState(name="joint2", position=1.57, velocity=0.1, effort=2.5),
78
+ ])
79
+
80
+ for state in states.states:
81
+ print(f"{state.name}: {state.position:.2f} rad")
82
+ ```
35
83
  """
36
84
 
37
85
  _type = "antioch/joint_states"
38
- states: list[JointState]
86
+ states: list[JointState] = Field(description="List of joint states")
39
87
 
40
88
 
41
89
  class JointTargets(Message):
42
90
  """
43
91
  Collection of joint targets for an actuator group.
92
+
93
+ Example:
94
+ ```python
95
+ from common.message import JointTargets, JointTarget
96
+
97
+ # Create targets for multiple joints
98
+ targets = JointTargets(targets=[
99
+ JointTarget(name="joint1", position=0.5),
100
+ JointTarget(name="joint2", position=1.0),
101
+ ])
102
+ ```
44
103
  """
45
104
 
46
105
  _type = "antioch/joint_targets"
47
- targets: list[JointTarget]
106
+ targets: list[JointTarget] = Field(description="List of joint targets")
common/message/log.py CHANGED
@@ -1,14 +1,24 @@
1
1
  import time
2
2
  from enum import Enum
3
3
 
4
+ from foxglove.schemas import Log as FoxgloveLog, LogLevel as FoxgloveLogLevel
4
5
  from pydantic import Field
5
6
 
6
- from common.message.base import Message
7
+ from common.message.message import Message
7
8
 
8
9
 
9
10
  class LogLevel(str, Enum):
10
11
  """
11
12
  Log level.
13
+
14
+ Example:
15
+ ```python
16
+ from common.message import LogLevel
17
+
18
+ level = LogLevel.INFO
19
+ if level == LogLevel.ERROR:
20
+ print("Error occurred")
21
+ ```
12
22
  """
13
23
 
14
24
  DEBUG = "debug"
@@ -20,12 +30,47 @@ class LogLevel(str, Enum):
20
30
  class Log(Message):
21
31
  """
22
32
  Log entry structure.
33
+
34
+ Example:
35
+ ```python
36
+ from common.message import Log, LogLevel
37
+
38
+ # Create a log entry
39
+ log = Log(
40
+ let_us=1000000,
41
+ level=LogLevel.INFO,
42
+ message="Robot initialized successfully",
43
+ channel="robot.init",
44
+ )
45
+ ```
23
46
  """
24
47
 
25
48
  _type = "antioch/log"
26
- timestamp_us: int = Field(default_factory=lambda: int(time.time_ns() // 1000))
27
- let_us: int
28
- level: LogLevel
29
- message: str | None = None
30
- channel: str | None = None
31
- telemetry: bytes | None = None
49
+ timestamp_us: int = Field(default_factory=lambda: int(time.time_ns() // 1000), description="Wall clock timestamp in microseconds")
50
+ let_us: int = Field(description="Logical event time in microseconds")
51
+ level: LogLevel = Field(description="Log severity level")
52
+ message: str | None = Field(default=None, description="Log message text")
53
+ channel: str | None = Field(default=None, description="Log channel/category")
54
+ telemetry: bytes | None = Field(default=None, description="Optional serialized telemetry data")
55
+
56
+ def to_foxglove(self) -> FoxgloveLog:
57
+ """
58
+ Convert to Foxglove Log for telemetry.
59
+
60
+ :return: Foxglove Log schema.
61
+ """
62
+
63
+ level_map = {
64
+ LogLevel.DEBUG: FoxgloveLogLevel.Debug,
65
+ LogLevel.INFO: FoxgloveLogLevel.Info,
66
+ LogLevel.WARNING: FoxgloveLogLevel.Warning,
67
+ LogLevel.ERROR: FoxgloveLogLevel.Error,
68
+ }
69
+ return FoxgloveLog(
70
+ timestamp=None,
71
+ level=level_map[self.level],
72
+ message=self.message or "",
73
+ name=self.channel or "",
74
+ file="",
75
+ line=0,
76
+ )
common/message/pir.py CHANGED
@@ -1,16 +1,33 @@
1
1
  from pydantic import Field
2
2
 
3
- from common.message.base import Message
3
+ from common.message.message import Message
4
4
 
5
5
 
6
6
  class PirStatus(Message):
7
7
  """
8
8
  PIR sensor status containing detection state and signal information.
9
+
10
+ Passive Infrared (PIR) sensors detect motion by measuring changes in
11
+ infrared radiation in their field of view.
12
+
13
+ Example:
14
+ ```python
15
+ from common.message import PirStatus
16
+
17
+ # Create a PIR status
18
+ status = PirStatus(
19
+ is_detected=True,
20
+ signal_strength=0.85,
21
+ threshold=0.5,
22
+ )
23
+
24
+ # Check for motion
25
+ if status.is_detected:
26
+ print(f"Motion detected! Signal: {status.signal_strength}")
27
+ ```
9
28
  """
10
29
 
11
30
  _type = "antioch/pir_status"
12
31
  is_detected: bool = Field(description="Whether motion is currently detected")
13
- signal_strength: float = Field(description="Current analog signal value after filtering")
14
- threshold: float = Field(description="Current detection threshold")
15
- element_flux: list[float] = Field(default_factory=list, description="Raw accumulated IR flux per element")
16
- element_signal: list[float] = Field(default_factory=list, description="Pyroelectric voltage signal per element (proportional to dT/dt)")
32
+ signal_strength: float = Field(description="Analog signal strength [0.0, 1.0]")
33
+ threshold: float = Field(description="Detection threshold [0.0, 1.0]")
common/message/plot.py ADDED
@@ -0,0 +1,57 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from pydantic import Field
6
+
7
+ from common.message.message import Message
8
+
9
+
10
+ class PlotData(Message):
11
+ """
12
+ Simple X/Y plot data for Foxglove visualization.
13
+
14
+ Use with Foxglove's Plot panel by selecting:
15
+ - X: .x[:]
16
+ - Y: .y[:]
17
+
18
+ Example:
19
+ ```python
20
+ from common.message import PlotData
21
+ import numpy as np
22
+
23
+ distances = np.linspace(0, 10, 128)
24
+ values = np.random.rand(128)
25
+
26
+ plot = PlotData.from_arrays(distances, values)
27
+ sim.logger.telemetry("radar/mti/radar_left", plot)
28
+ ```
29
+ """
30
+
31
+ _type = "antioch/plot_data"
32
+
33
+ x: list[float] = Field(default_factory=list, description="X-axis values")
34
+ y: list[float] = Field(default_factory=list, description="Y-axis values")
35
+
36
+ @classmethod
37
+ def from_arrays(cls, x: Any, y: Any) -> PlotData:
38
+ """
39
+ Create PlotData from arrays or lists.
40
+
41
+ :param x: X values (numpy array or list).
42
+ :param y: Y values (numpy array or list).
43
+ :return: PlotData instance.
44
+ """
45
+
46
+ x_list = x.tolist() if hasattr(x, "tolist") else list(x)
47
+ y_list = y.tolist() if hasattr(y, "tolist") else list(y)
48
+ return cls(x=x_list, y=y_list)
49
+
50
+ def to_foxglove(self) -> dict[str, Any]:
51
+ """
52
+ Convert to Foxglove-compatible dict.
53
+
54
+ :return: Dict with x and y arrays.
55
+ """
56
+
57
+ return {"x": self.x, "y": self.y}
common/message/point.py CHANGED
@@ -1,6 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
- from common.message.base import Message
3
+ from foxglove.schemas import Point2 as FoxglovePoint2, Point3 as FoxglovePoint3
4
+ from pydantic import Field
5
+
6
+ from common.message.message import Message
4
7
 
5
8
 
6
9
  class Point2(Message):
@@ -8,11 +11,25 @@ class Point2(Message):
8
11
  A point representing a position in 2D space.
9
12
 
10
13
  Used in image annotations and 2D coordinate systems.
14
+
15
+ Example:
16
+ ```python
17
+ from common.message import Point2
18
+
19
+ # Create a 2D point
20
+ point = Point2(x=100.0, y=200.0)
21
+
22
+ # Create using factory method
23
+ origin = Point2.zero()
24
+
25
+ # Convert to Foxglove for visualization
26
+ foxglove_point = point.to_foxglove()
27
+ ```
11
28
  """
12
29
 
13
30
  _type = "antioch/point2"
14
- x: float
15
- y: float
31
+ x: float = Field(description="X coordinate")
32
+ y: float = Field(description="Y coordinate")
16
33
 
17
34
  def __repr__(self) -> str:
18
35
  """
@@ -54,18 +71,41 @@ class Point2(Message):
54
71
 
55
72
  return cls(x=0.0, y=0.0)
56
73
 
74
+ def to_foxglove(self) -> FoxglovePoint2:
75
+ """
76
+ Convert to Foxglove Point2 for telemetry.
77
+
78
+ :return: Foxglove Point2 schema.
79
+ """
80
+
81
+ return FoxglovePoint2(x=self.x, y=self.y)
82
+
57
83
 
58
84
  class Point3(Message):
59
85
  """
60
86
  A point representing a position in 3D space.
61
87
 
62
88
  Used in 3D graphics and spatial coordinate systems.
89
+
90
+ Example:
91
+ ```python
92
+ from common.message import Point3
93
+
94
+ # Create a 3D point
95
+ point = Point3(x=1.0, y=2.0, z=3.0)
96
+
97
+ # Create at origin
98
+ origin = Point3.zero()
99
+
100
+ # Create using factory method
101
+ p = Point3.new(x=0.5, y=1.0, z=0.0)
102
+ ```
63
103
  """
64
104
 
65
105
  _type = "antioch/point3"
66
- x: float
67
- y: float
68
- z: float
106
+ x: float = Field(description="X coordinate")
107
+ y: float = Field(description="Y coordinate")
108
+ z: float = Field(description="Z coordinate")
69
109
 
70
110
  def __repr__(self) -> str:
71
111
  """
@@ -107,3 +147,12 @@ class Point3(Message):
107
147
  """
108
148
 
109
149
  return cls(x=0.0, y=0.0, z=0.0)
150
+
151
+ def to_foxglove(self) -> FoxglovePoint3:
152
+ """
153
+ Convert to Foxglove Point3 for telemetry.
154
+
155
+ :return: Foxglove Point3 schema.
156
+ """
157
+
158
+ return FoxglovePoint3(x=self.x, y=self.y, z=self.z)
@@ -1,25 +1,50 @@
1
1
  import struct
2
2
 
3
+ from foxglove.schemas import (
4
+ PackedElementField,
5
+ PackedElementFieldNumericType as NumericType,
6
+ PointCloud as FoxglovePointCloud,
7
+ Pose as FoxglovePose,
8
+ Quaternion as FoxgloveQuaternion,
9
+ Vector3 as FoxgloveVector3,
10
+ )
3
11
  from pydantic import Field, model_validator
4
12
 
5
- from common.message.base import Message
13
+ from common.message.message import Message
6
14
 
7
15
 
8
16
  class PointCloud(Message):
9
17
  """
10
18
  A collection of 3D points.
11
19
 
12
- :param frame_id: Frame of reference.
13
- :param x: X coordinates of points.
14
- :param y: Y coordinates of points.
15
- :param z: Z coordinates of points.
20
+ Point clouds are used for representing 3D sensor data such as LiDAR
21
+ scans or depth camera point clouds.
22
+
23
+ Example:
24
+ ```python
25
+ from common.message import PointCloud
26
+
27
+ # Create a point cloud
28
+ cloud = PointCloud(
29
+ frame_id="lidar_link",
30
+ x=[1.0, 2.0, 3.0],
31
+ y=[0.5, 1.0, 1.5],
32
+ z=[0.1, 0.2, 0.3],
33
+ )
34
+
35
+ # Convert to Foxglove for visualization
36
+ foxglove_cloud = cloud.to_foxglove()
37
+
38
+ # Get packed bytes for transmission
39
+ data = cloud.to_bytes()
40
+ ```
16
41
  """
17
42
 
18
43
  _type = "antioch/point_cloud"
19
- frame_id: str = Field(default="", description="Frame of reference")
20
- x: list[float] = Field(description="X coordinates of points")
21
- y: list[float] = Field(description="Y coordinates of points")
22
- z: list[float] = Field(description="Z coordinates of points")
44
+ frame_id: str = Field(default="", description="Frame of reference for the point cloud")
45
+ x: list[float] = Field(description="X coordinates of points in meters")
46
+ y: list[float] = Field(description="Y coordinates of points in meters")
47
+ z: list[float] = Field(description="Z coordinates of points in meters")
23
48
 
24
49
  @model_validator(mode="after")
25
50
  def validate_array_lengths(self) -> "PointCloud":
@@ -47,17 +72,28 @@ class PointCloud(Message):
47
72
 
48
73
  return bytes(data)
49
74
 
50
- @staticmethod
51
- def combine(point_clouds: list["PointCloud"], frame_id: str = "") -> "PointCloud":
75
+ def to_foxglove(self) -> FoxglovePointCloud:
52
76
  """
53
- Combine multiple point clouds into a single point cloud.
77
+ Convert to Foxglove PointCloud for telemetry.
54
78
 
55
- :param point_clouds: List of point clouds to combine.
56
- :param frame_id: Frame of reference for the combined point cloud.
57
- :return: Combined point cloud with all points.
79
+ :return: Foxglove PointCloud schema.
58
80
  """
59
81
 
60
- all_x = sum((pc.x for pc in point_clouds), [])
61
- all_y = sum((pc.y for pc in point_clouds), [])
62
- all_z = sum((pc.z for pc in point_clouds), [])
63
- return PointCloud(frame_id=frame_id, x=all_x, y=all_y, z=all_z)
82
+ # Pack point data as interleaved x, y, z floats
83
+ data = self.to_bytes()
84
+
85
+ return FoxglovePointCloud(
86
+ timestamp=None,
87
+ frame_id=self.frame_id,
88
+ pose=FoxglovePose(
89
+ position=FoxgloveVector3(x=0, y=0, z=0),
90
+ orientation=FoxgloveQuaternion(x=0, y=0, z=0, w=1),
91
+ ),
92
+ point_stride=12, # 3 floats * 4 bytes
93
+ fields=[
94
+ PackedElementField(name="x", offset=0, type=NumericType.Float32),
95
+ PackedElementField(name="y", offset=4, type=NumericType.Float32),
96
+ PackedElementField(name="z", offset=8, type=NumericType.Float32),
97
+ ],
98
+ data=data,
99
+ )
common/message/pose.py CHANGED
@@ -3,9 +3,10 @@ from __future__ import annotations
3
3
  from typing import Any
4
4
 
5
5
  import numpy as np
6
+ from foxglove.schemas import Pose as FoxglovePose, Quaternion as FoxgloveQuaternion, Vector3 as FoxgloveVector3
6
7
  from pydantic import Field, field_validator, model_validator
7
8
 
8
- from common.message.base import Message
9
+ from common.message.message import Message
9
10
  from common.message.vector import Vector3
10
11
 
11
12
 
@@ -19,11 +20,31 @@ class Pose(Message):
19
20
  Automatically converts from common Python types:
20
21
  - Lists/tuples: Pose(position=[1.0, 2.0, 3.0], orientation=[0.0, 0.0, 1.57])
21
22
  - Dicts: Pose(position={"x": 1.0, "y": 2.0, "z": 3.0}, orientation={"x": 0.0, "y": 0.0, "z": 1.57})
23
+
24
+ Example:
25
+ ```python
26
+ from common.message import Pose, Vector3
27
+
28
+ # Create a pose with position and orientation
29
+ pose = Pose(
30
+ position=[1.0, 2.0, 0.5],
31
+ orientation=[0.0, 0.0, 1.57], # 90 degrees yaw
32
+ )
33
+
34
+ # Create from identity
35
+ identity = Pose.identity()
36
+
37
+ # Translate a pose
38
+ translated = pose.translate(Vector3(x=1.0, y=0.0, z=0.0))
39
+
40
+ # Access position components
41
+ print(f"Position: ({pose.position.x}, {pose.position.y}, {pose.position.z})")
42
+ ```
22
43
  """
23
44
 
24
45
  _type = "antioch/pose"
25
- position: Vector3 = Field(default_factory=lambda: Vector3.zeros())
26
- orientation: Vector3 = Field(default_factory=lambda: Vector3.zeros())
46
+ position: Vector3 = Field(default_factory=lambda: Vector3.zeros(), description="Position in 3D space as (x, y, z)")
47
+ orientation: Vector3 = Field(default_factory=lambda: Vector3.zeros(), description="Orientation as (roll, pitch, yaw) in radians")
27
48
 
28
49
  def __add__(self, other: Vector3) -> Pose:
29
50
  """
@@ -71,7 +92,7 @@ class Pose(Message):
71
92
  """
72
93
 
73
94
  if isinstance(v, (list, tuple, np.ndarray)):
74
- return Vector3.convert_iterables.__func__(Vector3, v)
95
+ return Vector3._convert_input.__func__(Vector3, v)
75
96
  return v
76
97
 
77
98
  @field_validator("orientation", mode="before")
@@ -82,29 +103,34 @@ class Pose(Message):
82
103
  """
83
104
 
84
105
  if isinstance(v, (list, tuple, np.ndarray)):
85
- return Vector3.convert_iterables.__func__(Vector3, v)
106
+ return Vector3._convert_input.__func__(Vector3, v)
86
107
  return v
87
108
 
88
109
  @classmethod
89
- def from_any(cls, data: Any) -> Pose:
110
+ def from_any(cls, value: Pose | dict | tuple | list | None) -> Pose:
90
111
  """
91
- Create Pose from a dict or Pose instance.
112
+ Create Pose from any compatible type.
113
+
114
+ Accepts:
115
+ - Pose instance (returned as-is)
116
+ - Dict with 'position' and/or 'orientation' keys
117
+ - Tuple/list of 3 floats (treated as position only)
118
+ - None (returns identity pose)
92
119
 
93
- :param data: Dict with position/orientation fields, or a Pose instance.
120
+ :param value: Any compatible input type.
94
121
  :return: A Pose instance.
95
- :raises ValueError: If conversion fails.
96
122
  """
97
123
 
98
- # Already a Pose - return as-is
99
- if isinstance(data, cls):
100
- return data
101
-
102
- try:
103
- if isinstance(data, dict):
104
- return cls(**data)
105
- raise ValueError("Pose requires a dict with position and orientation fields")
106
- except Exception as e:
107
- raise ValueError(f"Cannot convert to Pose: {e}") from None
124
+ if value is None:
125
+ return cls.identity()
126
+ if isinstance(value, cls):
127
+ return value
128
+ if isinstance(value, dict):
129
+ return cls.model_validate(value)
130
+ if isinstance(value, (tuple, list)):
131
+ # Treat as position only
132
+ return cls(position=Vector3.from_any(value))
133
+ raise ValueError(f"Cannot convert {type(value).__name__} to Pose")
108
134
 
109
135
  @classmethod
110
136
  def identity(cls) -> Pose:
@@ -146,3 +172,17 @@ class Pose(Message):
146
172
  """
147
173
 
148
174
  return Pose(position=self.position, orientation=self.orientation + rotation)
175
+
176
+ def to_foxglove(self) -> FoxglovePose:
177
+ """
178
+ Convert to Foxglove Pose for telemetry.
179
+
180
+ :return: Foxglove Pose schema.
181
+ """
182
+
183
+ # Convert RPY orientation to quaternion for Foxglove
184
+ quat = self.orientation.to_quat()
185
+ return FoxglovePose(
186
+ position=FoxgloveVector3(x=self.position.x, y=self.position.y, z=self.position.z),
187
+ orientation=FoxgloveQuaternion(x=quat.x, y=quat.y, z=quat.z, w=quat.w),
188
+ )