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.

Files changed (99) hide show
  1. antioch/__init__.py +0 -0
  2. antioch/message.py +87 -0
  3. antioch/module/__init__.py +53 -0
  4. antioch/module/clock.py +62 -0
  5. antioch/module/execution.py +278 -0
  6. antioch/module/input.py +127 -0
  7. antioch/module/module.py +218 -0
  8. antioch/module/node.py +357 -0
  9. antioch/module/token.py +42 -0
  10. antioch/session/__init__.py +150 -0
  11. antioch/session/ark.py +504 -0
  12. antioch/session/asset.py +65 -0
  13. antioch/session/error.py +80 -0
  14. antioch/session/record.py +158 -0
  15. antioch/session/scene.py +1521 -0
  16. antioch/session/session.py +220 -0
  17. antioch/session/task.py +323 -0
  18. antioch/session/views/__init__.py +40 -0
  19. antioch/session/views/animation.py +189 -0
  20. antioch/session/views/articulation.py +245 -0
  21. antioch/session/views/basis_curve.py +186 -0
  22. antioch/session/views/camera.py +92 -0
  23. antioch/session/views/collision.py +75 -0
  24. antioch/session/views/geometry.py +74 -0
  25. antioch/session/views/ground_plane.py +63 -0
  26. antioch/session/views/imu.py +73 -0
  27. antioch/session/views/joint.py +64 -0
  28. antioch/session/views/light.py +175 -0
  29. antioch/session/views/pir_sensor.py +140 -0
  30. antioch/session/views/radar.py +73 -0
  31. antioch/session/views/rigid_body.py +282 -0
  32. antioch/session/views/xform.py +119 -0
  33. antioch_py-2.0.6.dist-info/METADATA +115 -0
  34. antioch_py-2.0.6.dist-info/RECORD +99 -0
  35. antioch_py-2.0.6.dist-info/WHEEL +5 -0
  36. antioch_py-2.0.6.dist-info/entry_points.txt +2 -0
  37. antioch_py-2.0.6.dist-info/top_level.txt +2 -0
  38. common/__init__.py +0 -0
  39. common/ark/__init__.py +60 -0
  40. common/ark/ark.py +128 -0
  41. common/ark/hardware.py +121 -0
  42. common/ark/kinematics.py +31 -0
  43. common/ark/module.py +85 -0
  44. common/ark/node.py +94 -0
  45. common/ark/scheduler.py +439 -0
  46. common/ark/sim.py +33 -0
  47. common/assets/__init__.py +3 -0
  48. common/constants.py +47 -0
  49. common/core/__init__.py +52 -0
  50. common/core/agent.py +296 -0
  51. common/core/auth.py +305 -0
  52. common/core/registry.py +331 -0
  53. common/core/task.py +36 -0
  54. common/message/__init__.py +59 -0
  55. common/message/annotation.py +89 -0
  56. common/message/array.py +500 -0
  57. common/message/base.py +517 -0
  58. common/message/camera.py +91 -0
  59. common/message/color.py +139 -0
  60. common/message/frame.py +50 -0
  61. common/message/image.py +171 -0
  62. common/message/imu.py +14 -0
  63. common/message/joint.py +47 -0
  64. common/message/log.py +31 -0
  65. common/message/pir.py +16 -0
  66. common/message/point.py +109 -0
  67. common/message/point_cloud.py +63 -0
  68. common/message/pose.py +148 -0
  69. common/message/quaternion.py +273 -0
  70. common/message/radar.py +58 -0
  71. common/message/types.py +37 -0
  72. common/message/vector.py +786 -0
  73. common/rome/__init__.py +9 -0
  74. common/rome/client.py +430 -0
  75. common/rome/error.py +16 -0
  76. common/session/__init__.py +54 -0
  77. common/session/environment.py +31 -0
  78. common/session/sim.py +240 -0
  79. common/session/views/__init__.py +263 -0
  80. common/session/views/animation.py +73 -0
  81. common/session/views/articulation.py +184 -0
  82. common/session/views/basis_curve.py +102 -0
  83. common/session/views/camera.py +147 -0
  84. common/session/views/collision.py +59 -0
  85. common/session/views/geometry.py +102 -0
  86. common/session/views/ground_plane.py +41 -0
  87. common/session/views/imu.py +66 -0
  88. common/session/views/joint.py +81 -0
  89. common/session/views/light.py +96 -0
  90. common/session/views/pir_sensor.py +115 -0
  91. common/session/views/radar.py +82 -0
  92. common/session/views/rigid_body.py +236 -0
  93. common/session/views/viewport.py +21 -0
  94. common/session/views/xform.py +39 -0
  95. common/utils/__init__.py +4 -0
  96. common/utils/comms.py +571 -0
  97. common/utils/logger.py +123 -0
  98. common/utils/time.py +42 -0
  99. common/utils/usd.py +12 -0
@@ -0,0 +1,1521 @@
1
+ from typing import Literal, overload
2
+
3
+ from antioch.session.ark import Ark
4
+ from antioch.session.session import SessionContainer
5
+ from antioch.session.views import (
6
+ Animation,
7
+ Articulation,
8
+ BasisCurve,
9
+ Camera,
10
+ Geometry,
11
+ GroundPlane,
12
+ Imu,
13
+ Joint,
14
+ Light,
15
+ PirSensor,
16
+ Radar,
17
+ RigidBody,
18
+ XForm,
19
+ get_mesh_approximation,
20
+ has_collision,
21
+ remove_collision,
22
+ set_collision,
23
+ set_pir_material,
24
+ )
25
+ from common.core import ContainerSource, get_asset_path
26
+ from common.core.agent import Agent
27
+ from common.message import Pose, Vector3
28
+ from common.session.environment import SessionEnvironment
29
+ from common.session.sim import (
30
+ AddAsset,
31
+ GetPrimAttribute,
32
+ PrimAttributeValue,
33
+ QueryScene,
34
+ SceneQueryResponse,
35
+ SceneTarget,
36
+ SetPrimAttribute,
37
+ SetSimulationControls,
38
+ SimulationInfo,
39
+ SimulationTime,
40
+ Step,
41
+ ToggleUi,
42
+ )
43
+ from common.session.views.articulation import ArticulationConfig, ArticulationJointConfig
44
+ from common.session.views.camera import CameraConfig, CameraMode, DistortionModel
45
+ from common.session.views.geometry import GeometryConfig, GeometryType, MeshApproximation # noqa: F401
46
+ from common.session.views.ground_plane import GroundPlaneConfig
47
+ from common.session.views.imu import ImuConfig
48
+ from common.session.views.joint import JointAxis, JointConfig, JointType
49
+ from common.session.views.light import LightConfig, LightType
50
+ from common.session.views.pir_sensor import PirSensorConfig
51
+ from common.session.views.radar import RadarConfig
52
+ from common.session.views.rigid_body import BodyType, RigidBodyConfig
53
+ from common.session.views.viewport import SetActiveViewportCamera, SetCameraView
54
+
55
+
56
+ class Scene(SessionContainer):
57
+ """
58
+ Singleton wrapper for scene-level operations and view factory.
59
+
60
+ Uses a lazy singleton pattern - the first instantiation or call to get_current()
61
+ creates the instance, and all subsequent calls return the same instance.
62
+
63
+ Provides low-level scene operations (clear, step, status) and factory methods
64
+ for creating and retrieving views (add_*/get_* pattern).
65
+
66
+ Example:
67
+ scene = Scene() # or Scene.get_current()
68
+ scene.clear()
69
+ scene.toggle_ui(show_ui=False)
70
+ scene.set_speed(1.0)
71
+ scene.play()
72
+ scene.step(dt=0.01)
73
+
74
+ # Add views
75
+ geometry = scene.add_geometry(path="/World/box", ...)
76
+
77
+ # Get existing views
78
+ existing_geometry = scene.get_geometry(path="/World/box")
79
+
80
+ # Query scene hierarchy
81
+ from common.session.sim import SceneTarget
82
+ all_prims = scene.query_scene(root_path="/World")
83
+ cameras = scene.query_scene(target=SceneTarget.CAMERA)
84
+
85
+ # Add Ark from registry
86
+ ark = scene.add_ark(name="my_robot", version="1.0.0")
87
+
88
+ # Add asset from registry
89
+ scene.add_asset(path="/World/my_asset", name="asset_name", version="1.0.0")
90
+
91
+ # Or add asset from file path
92
+ scene.add_asset(path="/World/my_model", asset_file_path="/path/to/model.usdz")
93
+ """
94
+
95
+ _current: "Scene | None" = None
96
+ _ark: "Ark | None" = None
97
+
98
+ def __new__(cls) -> "Scene":
99
+ if cls._current is None:
100
+ cls._current = super().__new__(cls)
101
+ return cls._current
102
+
103
+ def __init__(self):
104
+ """
105
+ Initialize scene wrapper.
106
+ """
107
+
108
+ if not hasattr(self, "_initialized"):
109
+ super().__init__()
110
+ self._initialized = True
111
+ self._agent = Agent()
112
+ Scene._current = self
113
+
114
+ @classmethod
115
+ def get_current(cls) -> "Scene":
116
+ """
117
+ Get the current scene, creating it if it doesn't exist (lazy singleton).
118
+
119
+ :return: The current scene.
120
+ """
121
+
122
+ if cls._current is None:
123
+ cls._current = Scene()
124
+ return cls._current
125
+
126
+ @property
127
+ def info(self) -> SimulationInfo:
128
+ """
129
+ Get comprehensive simulation info.
130
+
131
+ :return: Info response with detailed simulation information.
132
+ """
133
+
134
+ return self._session.query_sim_rpc(
135
+ endpoint="get_info",
136
+ response_type=SimulationInfo,
137
+ )
138
+
139
+ @property
140
+ def time_us(self) -> int:
141
+ """
142
+ Get the current simulation time in microseconds.
143
+
144
+ :return: Current simulation time in microseconds.
145
+ """
146
+
147
+ return self._session.query_sim_rpc(
148
+ endpoint="get_time",
149
+ response_type=SimulationTime,
150
+ ).time_us
151
+
152
+ @property
153
+ def ark(self):
154
+ """
155
+ Get the current Ark if one exists.
156
+
157
+ :return: The current Ark or None.
158
+ """
159
+
160
+ return self._ark
161
+
162
+ def add_ark(
163
+ self,
164
+ name: str,
165
+ version: str,
166
+ path: str = "/World",
167
+ world_pose: Pose | dict | None = None,
168
+ local_pose: Pose | dict | None = None,
169
+ source: ContainerSource | None = None,
170
+ debug: bool = False,
171
+ timeout: float = 30.0,
172
+ ) -> "Ark":
173
+ """
174
+ Add an Ark container at the specified path.
175
+
176
+ This builds the entire Ark: loads definition, builds kinematics and hardware,
177
+ starts containers via agent, and initializes scheduling. Everything is ready to run.
178
+
179
+ :param name: Name of the Ark.
180
+ :param version: Version of the Ark.
181
+ :param path: USD path where the Ark will be built (default: "/World").
182
+ :param world_pose: Optional world pose.
183
+ :param local_pose: Optional local pose.
184
+ :param source: Container image source.
185
+ :param debug: Enable debug mode.
186
+ :param timeout: Timeout in seconds for the Ark to start.
187
+ :return: The fully initialized Ark container.
188
+ """
189
+
190
+ # Auto-select the container source based on session environment
191
+ if source is None:
192
+ source = ContainerSource.LOCAL if SessionEnvironment.check() == SessionEnvironment.LOCAL else ContainerSource.REMOTE
193
+
194
+ self._ark = Ark(
195
+ path=path,
196
+ scene=self,
197
+ name=name,
198
+ version=version,
199
+ world_pose=Pose.from_any(world_pose) if world_pose is not None else None,
200
+ local_pose=Pose.from_any(local_pose) if local_pose is not None else None,
201
+ source=source,
202
+ debug=debug,
203
+ timeout=timeout,
204
+ )
205
+
206
+ return self._ark
207
+
208
+ def play(self) -> None:
209
+ """
210
+ Start or resume playing the simulation.
211
+
212
+ Automatically starts the simulation if stopped, or resumes if paused.
213
+ After playing, background rendering is disabled and the viewport is only
214
+ updated with calls to step() or render().
215
+ """
216
+
217
+ self._session.query_sim_rpc(endpoint="play")
218
+
219
+ def pause(self) -> None:
220
+ """
221
+ Pause the simulation.
222
+
223
+ Freezes time but keeps physics initialized, enabling background rendering.
224
+ """
225
+
226
+ self._session.query_sim_rpc(endpoint="pause")
227
+
228
+ def render(self) -> None:
229
+ """
230
+ Force render the simulation.
231
+ """
232
+
233
+ self._session.query_sim_rpc(endpoint="render")
234
+
235
+ def step(self, dt_us: int = 1_000_000) -> None:
236
+ """
237
+ Step the simulation forward.
238
+
239
+ The simulation must be playing before calling this method. Call play() first.
240
+ If an Ark exists, delegates to Ark.step() which handles both node execution
241
+ and physics stepping. Otherwise steps physics directly.
242
+
243
+ :param dt_us: Amount of time to step in microseconds (default 1 second).
244
+ :raises SessionSimRpcClientError: If simulation is not playing.
245
+ """
246
+
247
+ # Delegate to Ark if present
248
+ if self._ark is not None:
249
+ self._ark.step(dt_us)
250
+ return
251
+
252
+ # No Ark, step physics directly
253
+ self._step_physics(dt_us)
254
+
255
+ def set_simulation_controls(
256
+ self,
257
+ max_physics_dt_us: int | None = None,
258
+ render_interval_us: int | None = None,
259
+ ) -> None:
260
+ """
261
+ Set simulation control parameters.
262
+
263
+ The max_physics_dt_us is the maximum physics step size in microseconds
264
+ that will be used by the simulation backend. Setting a larger value will
265
+ speed up the simulation, but may cause instability and lower the physics
266
+ fidelity. You should generally stick to values between 1ms and 20ms.
267
+
268
+ The render_interval_us is the interval in microseconds at which the viewport
269
+ will be rendered **in simulation time**. Setting a larger value will reduce the
270
+ number of renders and speed up the simulation, but may increase lag and jitter
271
+ in the viewport. You should generally stick to values between 10ms and 100ms.
272
+
273
+ :param max_physics_dt_us: Maximum physics timestep in microseconds.
274
+ :param render_interval_us: Render interval in microseconds.
275
+ :raises SessionSimRpcClientError: If parameters are invalid.
276
+ """
277
+
278
+ self._session.query_sim_rpc(
279
+ endpoint="set_simulation_controls",
280
+ payload=SetSimulationControls(
281
+ max_physics_dt_us=max_physics_dt_us,
282
+ render_interval_us=render_interval_us,
283
+ ),
284
+ )
285
+
286
+ def clear(self, timeout: float = 30.0) -> None:
287
+ """
288
+ Clear and completely reset the scene.
289
+
290
+ Stops any running Ark before clearing the scene. If a task is active,
291
+ restarts it to reset telemetry with the same configuration.
292
+
293
+ :param timeout: Timeout in seconds for stopping the Ark.
294
+ """
295
+
296
+ from antioch.session.task import Task
297
+
298
+ # Always try to stop the Ark via the agent (idempotent)
299
+ self._agent.stop_ark(timeout=timeout)
300
+ self._ark = None
301
+
302
+ # Clear the simulation scenne completely
303
+ self._session.query_sim_rpc(endpoint="clear")
304
+
305
+ # Clear any existing task
306
+ Task.get_current().clear()
307
+
308
+ def restart(self) -> None:
309
+ """
310
+ Restart the simulation container.
311
+
312
+ This endpoint only works in Kubernetes environments. It sends SIGKILL to PID 1
313
+ (the container entrypoint process), which will forcefully terminate the entire
314
+ container and trigger a restart.
315
+
316
+ Note: This function will not return as the process will be killed immediately.
317
+ """
318
+
319
+ from antioch.session.task import Task
320
+
321
+ # Always try to stop the Ark via the agent (idempotent)
322
+ self._agent.stop_ark()
323
+ self._ark = None
324
+
325
+ # Restart the RPC container
326
+ self._session.query_sim_rpc(endpoint="restart")
327
+
328
+ # Clear any existing task
329
+ Task.get_current().clear()
330
+
331
+ def toggle_ui(self, show_ui: bool) -> None:
332
+ """
333
+ Toggle the Isaac Sim UI visibility.
334
+
335
+ :param show_ui: Whether to show or hide the UI.
336
+ """
337
+
338
+ self._session.query_sim_rpc(
339
+ endpoint="toggle_ui",
340
+ payload=ToggleUi(show_ui=show_ui),
341
+ )
342
+
343
+ def set_camera_view(
344
+ self,
345
+ eye: Vector3 | list[float] | tuple[float, float, float],
346
+ target: Vector3 | list[float] | tuple[float, float, float],
347
+ camera_prim_path: str | None = None,
348
+ ) -> None:
349
+ """
350
+ Set the viewport camera view position and target.
351
+
352
+ :param eye: Eye position (camera location) in world coordinates.
353
+ :param target: Target position (look-at point) in world coordinates.
354
+ :param camera_prim_path: Optional USD path to the camera prim to configure.
355
+ """
356
+
357
+ self._session.query_sim_rpc(
358
+ endpoint="set_camera_view",
359
+ payload=SetCameraView(
360
+ eye=Vector3.from_any(eye),
361
+ target=Vector3.from_any(target),
362
+ camera_prim_path=camera_prim_path,
363
+ ),
364
+ )
365
+
366
+ def set_active_viewport_camera(self, camera_prim_path: str) -> None:
367
+ """
368
+ Set which camera is active in the viewport.
369
+
370
+ :param camera_prim_path: USD path to the camera prim to make active.
371
+ """
372
+
373
+ self._session.query_sim_rpc(
374
+ endpoint="set_active_viewport_camera",
375
+ payload=SetActiveViewportCamera(camera_prim_path=camera_prim_path),
376
+ )
377
+
378
+ def get_prim_attribute(self, path: str, attribute_name: str) -> float | int | str | bool | list[float]:
379
+ """
380
+ Get an attribute value from any prim.
381
+
382
+ Supports primitive and vector types: float, int, bool, string, Vec2/3/4, Quat.
383
+
384
+ :param path: USD path to the prim.
385
+ :param attribute_name: Name of the attribute to get.
386
+ :return: The attribute value (scalar or list for vectors/quaternions).
387
+ :raises ValueError: If prim or attribute doesn't exist, or type is unsupported.
388
+ """
389
+
390
+ return self._session.query_sim_rpc(
391
+ endpoint="get_prim_attribute_value",
392
+ response_type=PrimAttributeValue,
393
+ payload=GetPrimAttribute(path=path, attribute_name=attribute_name),
394
+ ).value
395
+
396
+ def set_prim_attribute(self, path: str, attribute_name: str, value: float | int | str | bool | list[float]) -> None:
397
+ """
398
+ Set an attribute value on any prim.
399
+
400
+ Supports primitive and vector types: float, int, bool, string, Vec2/3/4, Quat.
401
+ The attribute must already exist.
402
+
403
+ :param path: USD path to the prim.
404
+ :param attribute_name: Name of the attribute to set.
405
+ :param value: The value to set (scalar or list for vectors/quaternions).
406
+ :raises ValueError: If prim or attribute doesn't exist, or value is incompatible.
407
+ """
408
+
409
+ self._session.query_sim_rpc(
410
+ endpoint="set_prim_attribute_value",
411
+ payload=SetPrimAttribute(path=path, attribute_name=attribute_name, value=value),
412
+ )
413
+
414
+ def query_scene(self, root_path: str = "/World", target: SceneTarget | None = None) -> SceneQueryResponse:
415
+ """
416
+ Query the USD scene hierarchy for prims matching specific criteria.
417
+
418
+ Traverses the scene starting from root_path and finds all prims that match
419
+ the target type (or all applicable targets if target is None). This works
420
+ regardless of simulation state.
421
+
422
+ :param root_path: Root path to start the query from (default: "/World").
423
+ :param target: Specific target type to filter for (None returns all).
424
+ :return: Scene query response with matching prims and their applicable targets.
425
+ """
426
+
427
+ return self._session.query_sim_rpc(
428
+ endpoint="query_scene_hierarchy",
429
+ response_type=SceneQueryResponse,
430
+ payload=QueryScene(root_path=root_path, target=target),
431
+ )
432
+
433
+ def add_asset(
434
+ self,
435
+ path: str,
436
+ name: str | None = None,
437
+ version: str | None = None,
438
+ asset_file_path: str | None = None,
439
+ asset_prim_path: str | None = None,
440
+ remove_articulation: bool = True,
441
+ remove_rigid_body: bool = False,
442
+ remove_sensors: bool = False,
443
+ world_pose: Pose | dict | None = None,
444
+ local_pose: Pose | dict | None = None,
445
+ scale: Vector3 | list[float] | tuple[float, float, float] | None = None,
446
+ ) -> str:
447
+ """
448
+ Add and convert an asset file (FBX, OBJ, glTF, USD, etc) to USD.
449
+
450
+ Either provide asset_file_path directly OR provide both name and version to load from
451
+ the asset registry. Exactly one option must be used.
452
+
453
+ :param path: USD path where the asset will be added (e.g., "/World/my_model").
454
+ :param name: Name of the asset in the registry (requires version).
455
+ :param version: Version of the asset in the registry (requires name).
456
+ :param asset_file_path: Path to the asset file (FBX, OBJ, glTF, STL, USD, etc).
457
+ :param asset_prim_path: Full path to prim in the USD file to reference.
458
+ :param remove_articulation: Whether to remove articulation APIs.
459
+ :param remove_rigid_body: Whether to remove rigid body APIs.
460
+ :param remove_sensors: Whether to remove sensor and graph prims.
461
+ :param world_pose: Optional world pose as Pose (or dict with position/orientation lists).
462
+ :param local_pose: Optional local pose as Pose (or dict with position/orientation lists).
463
+ :param scale: Optional scale as Vector3 (or list/tuple of 3 floats).
464
+ :raises ValueError: If neither option or both options are provided.
465
+ """
466
+
467
+ # Validate that exactly one option is provided
468
+ has_file_path = asset_file_path is not None
469
+ has_name_version = name is not None and version is not None
470
+ has_partial_name_version = (name is not None) != (version is not None)
471
+ if has_partial_name_version:
472
+ raise ValueError("Both name and version must be provided together")
473
+ if not has_file_path and not has_name_version:
474
+ raise ValueError("Either asset_file_path or both name and version must be provided")
475
+ if has_file_path and has_name_version:
476
+ raise ValueError("Cannot provide both asset_file_path and name/version")
477
+
478
+ # Resolve asset file path from registry if name/version provided
479
+ if has_name_version:
480
+ if name is None or version is None:
481
+ raise ValueError("Name and version must be provided together")
482
+ asset_file_path = str(get_asset_path(name=name, version=version, assert_exists=True))
483
+
484
+ if not asset_file_path:
485
+ raise ValueError("Asset file path is required")
486
+ self._session.query_sim_rpc(
487
+ endpoint="add_asset",
488
+ payload=AddAsset(
489
+ path=path,
490
+ asset_file_path=asset_file_path,
491
+ asset_prim_path=asset_prim_path,
492
+ remove_articulation=remove_articulation,
493
+ remove_rigid_body=remove_rigid_body,
494
+ remove_sensors=remove_sensors,
495
+ world_pose=Pose.from_any(world_pose) if world_pose is not None else None,
496
+ local_pose=Pose.from_any(local_pose) if local_pose is not None else None,
497
+ scale=Vector3.from_any(scale) if scale is not None else None,
498
+ ),
499
+ )
500
+ return path
501
+
502
+ def add_geometry(
503
+ self,
504
+ path: str,
505
+ geometry_type: GeometryType,
506
+ radius: float | None = None,
507
+ height: float | None = None,
508
+ size: float | None = None,
509
+ color: Vector3 | list[float] | tuple[float, float, float] | None = None,
510
+ opacity: float = 1.0,
511
+ world_pose: Pose | dict | None = None,
512
+ local_pose: Pose | dict | None = None,
513
+ enable_collision: bool = True,
514
+ static_friction: float = 0.5,
515
+ dynamic_friction: float = 0.5,
516
+ restitution: float = 0.2,
517
+ mesh_file_path: str | None = None,
518
+ mesh_approximation: MeshApproximation = MeshApproximation.CONVEX_DECOMPOSITION,
519
+ contact_offset: float | None = None,
520
+ rest_offset: float | None = None,
521
+ torsional_patch_radius: float | None = None,
522
+ min_torsional_patch_radius: float | None = None,
523
+ ) -> Geometry:
524
+ """
525
+ Add geometry to the scene.
526
+
527
+ :param path: USD path for the geometry.
528
+ :param geometry_type: Type of geometry (sphere, cube, cylinder, cone, capsule, mesh).
529
+ :param name: Optional unique name for the view.
530
+ :param radius: Radius for sphere/cylinder/cone/capsule.
531
+ :param height: Height for cylinder/cone/capsule.
532
+ :param size: Size for cube (uniform).
533
+ :param color: RGB color as Vector3 (or list/tuple of 3 floats) with values 0-1.
534
+ :param opacity: Opacity from 0 (transparent) to 1 (opaque).
535
+ :param world_pose: Optional world pose as Pose (or dict with position/orientation lists).
536
+ :param local_pose: Optional local pose as Pose (or dict with position/orientation lists).
537
+ :param enable_collision: Whether to enable collision.
538
+ :param static_friction: Static friction coefficient.
539
+ :param dynamic_friction: Dynamic friction coefficient.
540
+ :param restitution: Restitution (bounciness).
541
+ :param mesh_file_path: Path to mesh file (FBX, OBJ, glTF, STL, etc.) - required for MESH type.
542
+ :param mesh_approximation: Collision mesh approximation method (for meshes).
543
+ :param contact_offset: Distance at which collision detection begins.
544
+ :param rest_offset: Minimum separation distance between objects.
545
+ :param torsional_patch_radius: Radius for torsional friction calculations.
546
+ :param min_torsional_patch_radius: Minimum radius for torsional friction.
547
+ :return: The geometry instance.
548
+ """
549
+
550
+ return Geometry.add(
551
+ path=path,
552
+ config=GeometryConfig(
553
+ geometry_type=geometry_type,
554
+ radius=radius,
555
+ height=height,
556
+ size=size,
557
+ color=Vector3.from_any(color) if color is not None else None,
558
+ opacity=opacity,
559
+ enable_collision=enable_collision,
560
+ static_friction=static_friction,
561
+ dynamic_friction=dynamic_friction,
562
+ restitution=restitution,
563
+ mesh_file_path=mesh_file_path,
564
+ mesh_approximation=mesh_approximation,
565
+ contact_offset=contact_offset,
566
+ rest_offset=rest_offset,
567
+ torsional_patch_radius=torsional_patch_radius,
568
+ min_torsional_patch_radius=min_torsional_patch_radius,
569
+ ),
570
+ world_pose=Pose.from_any(world_pose) if world_pose is not None else None,
571
+ local_pose=Pose.from_any(local_pose) if local_pose is not None else None,
572
+ )
573
+
574
+ def get_geometry(self, path: str) -> Geometry:
575
+ """
576
+ Get existing geometry from the scene.
577
+
578
+ :param path: USD path for the geometry.
579
+ :return: The geometry instance.
580
+ """
581
+
582
+ return Geometry(path)
583
+
584
+ def add_articulation(
585
+ self,
586
+ path: str,
587
+ joint_configs: list[ArticulationJointConfig] | None = None,
588
+ solver_position_iterations: int = 32,
589
+ solver_velocity_iterations: int = 1,
590
+ sleep_threshold: float = 0.005,
591
+ stabilization_threshold: float = 0.001,
592
+ enable_self_collisions: bool = False,
593
+ world_pose: Pose | dict | None = None,
594
+ local_pose: Pose | dict | None = None,
595
+ scale: Vector3 | list[float] | tuple[float, float, float] | None = None,
596
+ ) -> Articulation:
597
+ """
598
+ Add an articulation to the scene.
599
+
600
+ :param path: USD path for the articulation.
601
+ :param name: Optional unique name for the view.
602
+ :param joint_configs: Per-joint configurations (stiffness, damping, limits, etc).
603
+ :param solver_position_iterations: Number of position iterations for the solver.
604
+ :param solver_velocity_iterations: Number of velocity iterations for the solver.
605
+ :param sleep_threshold: Sleep threshold for the articulation.
606
+ :param stabilization_threshold: Stabilization threshold for the articulation.
607
+ :param enable_self_collisions: Whether to enable self-collisions.
608
+ :param world_pose: Optional world pose as Pose (or dict with position/orientation lists).
609
+ :param local_pose: Optional local pose as Pose (or dict with position/orientation lists).
610
+ :param scale: Optional scale as Vector3 (or list/tuple of 3 floats).
611
+ :return: The articulation instance.
612
+ """
613
+
614
+ return Articulation.add(
615
+ path=path,
616
+ config=ArticulationConfig(
617
+ solver_position_iterations=solver_position_iterations,
618
+ solver_velocity_iterations=solver_velocity_iterations,
619
+ sleep_threshold=sleep_threshold,
620
+ stabilization_threshold=stabilization_threshold,
621
+ enable_self_collisions=enable_self_collisions,
622
+ joint_configs=joint_configs or [],
623
+ ),
624
+ world_pose=Pose.from_any(world_pose) if world_pose is not None else None,
625
+ local_pose=Pose.from_any(local_pose) if local_pose is not None else None,
626
+ scale=Vector3.from_any(scale) if scale is not None else None,
627
+ )
628
+
629
+ def get_articulation(self, path: str) -> Articulation:
630
+ """
631
+ Get an existing articulation from the scene.
632
+
633
+ :param path: USD path for the articulation.
634
+ :return: The articulation instance.
635
+ """
636
+
637
+ return Articulation(path)
638
+
639
+ def add_joint(
640
+ self,
641
+ path: str,
642
+ parent_path: str,
643
+ child_path: str,
644
+ pose: Pose | dict | None = None,
645
+ joint_type: JointType = JointType.FIXED,
646
+ axis: JointAxis = JointAxis.X,
647
+ lower_limit: float | None = None,
648
+ upper_limit: float | None = None,
649
+ friction: float = 0.01,
650
+ armature: float = 0.1,
651
+ exclude_from_articulation: bool = False,
652
+ ) -> Joint:
653
+ """
654
+ Add a joint to the scene.
655
+
656
+ :param path: USD path for the joint.
657
+ :param parent_path: USD path to parent body.
658
+ :param child_path: USD path to child body.
659
+ :param name: Optional unique name for the view.
660
+ :param pose: Joint pose relative to parent (defaults to identity).
661
+ :param joint_type: Type of joint motion (FIXED, REVOLUTE, PRISMATIC).
662
+ :param axis: Axis of motion for non-fixed joints.
663
+ :param lower_limit: Lower motion limit (degrees for revolute, meters for prismatic).
664
+ :param upper_limit: Upper motion limit (degrees for revolute, meters for prismatic).
665
+ :param friction: Joint friction coefficient (unitless).
666
+ :param armature: Joint armature (kg for prismatic, kg-m^2 for revolute).
667
+ :param exclude_from_articulation: Whether to exclude this joint from articulation.
668
+ :return: The joint instance.
669
+ """
670
+
671
+ return Joint.add(
672
+ path=path,
673
+ config=JointConfig(
674
+ parent_path=parent_path,
675
+ child_path=child_path,
676
+ pose=Pose.from_any(pose) if pose is not None else Pose.identity(),
677
+ joint_type=joint_type,
678
+ axis=axis,
679
+ lower_limit=lower_limit,
680
+ upper_limit=upper_limit,
681
+ friction=friction,
682
+ armature=armature,
683
+ exclude_from_articulation=exclude_from_articulation,
684
+ ),
685
+ )
686
+
687
+ def get_joint(self, path: str) -> Joint:
688
+ """
689
+ Get an existing joint from the scene.
690
+
691
+ :param path: USD path for the joint.
692
+ :return: The joint instance.
693
+ """
694
+
695
+ return Joint(path)
696
+
697
+ def add_rigid_body(
698
+ self,
699
+ path: str,
700
+ body_type: BodyType = BodyType.DYNAMIC,
701
+ mass: float = 1.0,
702
+ density: float | None = None,
703
+ center_of_mass: Vector3 | list[float] | tuple[float, float, float] | None = None,
704
+ diagonal_inertia: Vector3 | list[float] | tuple[float, float, float] | None = None,
705
+ principal_axes: Vector3 | list[float] | tuple[float, float, float] | None = None,
706
+ sleep_threshold: float | None = None,
707
+ linear_velocity: Vector3 | list[float] | tuple[float, float, float] | None = None,
708
+ angular_velocity: Vector3 | list[float] | tuple[float, float, float] | None = None,
709
+ world_pose: Pose | dict | None = None,
710
+ local_pose: Pose | dict | None = None,
711
+ scale: Vector3 | list[float] | tuple[float, float, float] | None = None,
712
+ ) -> RigidBody:
713
+ """
714
+ Add rigid body physics to the scene.
715
+
716
+ :param path: USD path for the rigid body.
717
+ :param name: Optional unique name for the view.
718
+ :param body_type: Body type (dynamic or kinematic).
719
+ :param mass: Mass in kg.
720
+ :param density: Density in kg/m³ (alternative to mass).
721
+ :param center_of_mass: Center of mass offset as Vector3 (or list/tuple) in body frame.
722
+ :param diagonal_inertia: Diagonal inertia values as Vector3 (or list/tuple).
723
+ :param principal_axes: Principal axes orientation as RPY Vector3 (or list/tuple).
724
+ :param sleep_threshold: Mass-normalized kinetic energy threshold for sleeping.
725
+ :param linear_velocity: Initial linear velocity as Vector3 (or list/tuple).
726
+ :param angular_velocity: Initial angular velocity as Vector3 (or list/tuple).
727
+ :param world_pose: Optional world pose as Pose (or dict with position/orientation lists).
728
+ :param local_pose: Optional local pose as Pose (or dict with position/orientation lists).
729
+ :param scale: Optional scale as Vector3 (or list/tuple of 3 floats).
730
+ :return: The rigid body instance.
731
+ """
732
+
733
+ return RigidBody.add(
734
+ path=path,
735
+ config=RigidBodyConfig(
736
+ body_type=body_type,
737
+ mass=mass,
738
+ density=density,
739
+ center_of_mass=Vector3.from_any(center_of_mass) if center_of_mass is not None else None,
740
+ diagonal_inertia=Vector3.from_any(diagonal_inertia) if diagonal_inertia is not None else None,
741
+ principal_axes=Vector3.from_any(principal_axes) if principal_axes is not None else None,
742
+ sleep_threshold=sleep_threshold,
743
+ linear_velocity=Vector3.from_any(linear_velocity) if linear_velocity is not None else None,
744
+ angular_velocity=Vector3.from_any(angular_velocity) if angular_velocity is not None else None,
745
+ ),
746
+ world_pose=Pose.from_any(world_pose) if world_pose is not None else None,
747
+ local_pose=Pose.from_any(local_pose) if local_pose is not None else None,
748
+ scale=Vector3.from_any(scale) if scale is not None else None,
749
+ )
750
+
751
+ def get_rigid_body(self, path: str) -> RigidBody:
752
+ """
753
+ Get an existing rigid body from the scene.
754
+
755
+ :param path: USD path for the rigid body.
756
+ :return: The rigid body instance.
757
+ """
758
+
759
+ return RigidBody(path)
760
+
761
+ def add_light(
762
+ self,
763
+ path: str,
764
+ light_type: LightType = LightType.SPHERE,
765
+ intensity: float = 30000.0,
766
+ exposure: float = 10.0,
767
+ color: Vector3 | list[float] | tuple[float, float, float] | None = None,
768
+ radius: float = 0.1,
769
+ width: float | None = None,
770
+ height: float | None = None,
771
+ length: float | None = None,
772
+ angle: float | None = None,
773
+ texture_file: str | None = None,
774
+ world_pose: Pose | dict | None = None,
775
+ local_pose: Pose | dict | None = None,
776
+ ) -> Light:
777
+ """
778
+ Add a light to the scene.
779
+
780
+ :param path: USD path for the light.
781
+ :param name: Optional unique name for the view.
782
+ :param light_type: Type of light (sphere, rect, disk, cylinder, distant, dome).
783
+ :param intensity: Light intensity.
784
+ :param exposure: Light exposure value.
785
+ :param color: RGB color as Vector3 (or list/tuple of 3 floats) with values 0-1 (defaults to white).
786
+ :param radius: Light radius in meters (for sphere lights).
787
+ :param width: Width in meters (for rect lights).
788
+ :param height: Height in meters (for rect/cylinder lights).
789
+ :param length: Length in meters (for cylinder lights).
790
+ :param angle: Angle in degrees (for distant lights).
791
+ :param texture_file: Path to texture file (for dome lights).
792
+ :param world_pose: Optional world pose as Pose (or dict with position/orientation lists).
793
+ :param local_pose: Optional local pose as Pose (or dict with position/orientation lists).
794
+ :return: The light instance.
795
+ """
796
+
797
+ return Light.add(
798
+ path=path,
799
+ config=LightConfig(
800
+ light_type=light_type,
801
+ intensity=intensity,
802
+ exposure=exposure,
803
+ color=Vector3.from_any(color) if color is not None else Vector3(data=(1.0, 1.0, 1.0)),
804
+ radius=radius,
805
+ width=width,
806
+ height=height,
807
+ length=length,
808
+ angle=angle,
809
+ texture_file=texture_file,
810
+ ),
811
+ world_pose=Pose.from_any(world_pose) if world_pose is not None else None,
812
+ local_pose=Pose.from_any(local_pose) if local_pose is not None else None,
813
+ )
814
+
815
+ def get_light(self, path: str) -> Light:
816
+ """
817
+ Get an existing light from the scene.
818
+
819
+ :param path: USD path for the light.
820
+ :return: The light instance.
821
+ """
822
+
823
+ return Light(path)
824
+
825
+ def add_xform(
826
+ self,
827
+ path: str,
828
+ world_pose: Pose | dict | None = None,
829
+ local_pose: Pose | dict | None = None,
830
+ scale: Vector3 | list[float] | tuple[float, float, float] | None = None,
831
+ ) -> XForm:
832
+ """
833
+ Add an xform to the scene.
834
+
835
+ :param path: USD path for the xform.
836
+ :param name: Optional unique name for the view.
837
+ :param world_pose: Optional world pose as Pose (or dict with position/orientation lists).
838
+ :param local_pose: Optional local pose as Pose (or dict with position/orientation lists).
839
+ :param scale: Optional scale as Vector3 (or list/tuple of 3 floats).
840
+ :return: The xform instance.
841
+ """
842
+
843
+ return XForm.add(
844
+ path=path,
845
+ world_pose=Pose.from_any(world_pose) if world_pose is not None else None,
846
+ local_pose=Pose.from_any(local_pose) if local_pose is not None else None,
847
+ scale=Vector3.from_any(scale) if scale is not None else None,
848
+ )
849
+
850
+ def get_xform(self, path: str) -> XForm:
851
+ """
852
+ Get an existing xform from the scene.
853
+
854
+ :param path: USD path for the xform.
855
+ :return: The xform instance.
856
+ """
857
+
858
+ return XForm(path)
859
+
860
+ @overload
861
+ def add_animation(
862
+ self,
863
+ path: str,
864
+ *,
865
+ waypoints: list[Vector3],
866
+ loop: bool = True,
867
+ ) -> Animation: ...
868
+
869
+ @overload
870
+ def add_animation(
871
+ self,
872
+ path: str,
873
+ *,
874
+ basis_curve: str,
875
+ samples_per_segment: int = 10,
876
+ sort_by: Literal["X", "Y", "Z"] | None = None,
877
+ ascending: bool = True,
878
+ ) -> Animation: ...
879
+
880
+ def add_animation(
881
+ self,
882
+ path: str,
883
+ *,
884
+ waypoints: list[Vector3] | None = None,
885
+ basis_curve: str | None = None,
886
+ samples_per_segment: int = 10,
887
+ sort_by: Literal["X", "Y", "Z"] | None = None,
888
+ ascending: bool = True,
889
+ loop: bool = True,
890
+ ) -> Animation:
891
+ """
892
+ Add an animation to the scene.
893
+
894
+ :param path: USD path for the animation (must contain a skeleton root).
895
+ :param waypoints: List of waypoints for the animation path.
896
+ :param basis_curve: Path to the basis curve to use for the animation.
897
+ :param samples_per_segment: The number of samples per segment to use from the basis curve.
898
+ :param sort_by: The axis to sort the points by.
899
+ :param ascending: Whether to sort the points in ascending order.
900
+ :param loop: Whether to loop the animation.
901
+ :return: The animation instance.
902
+ :raises ValueError: If both waypoints and basis curve are provided.
903
+ :raises ValueError: If neither waypoints nor basis curve are provided.
904
+ """
905
+ if waypoints is not None and basis_curve is not None:
906
+ raise ValueError("Must specify either waypoints or basis curve")
907
+ if waypoints is not None:
908
+ return Animation.add(
909
+ path=path,
910
+ waypoints=waypoints,
911
+ loop=loop,
912
+ )
913
+ elif basis_curve is not None:
914
+ return Animation.add(
915
+ path=path,
916
+ basis_curve=basis_curve,
917
+ samples_per_segment=samples_per_segment,
918
+ sort_by=sort_by,
919
+ ascending=ascending,
920
+ )
921
+ else:
922
+ raise ValueError("Must specify either waypoints or basis curve")
923
+
924
+ def get_animation(self, path: str) -> Animation:
925
+ """
926
+ Get an existing animation from the scene.
927
+
928
+ :param path: USD path for the animation.
929
+ :return: The animation instance.
930
+ """
931
+
932
+ return Animation(path)
933
+
934
+ def add_ground_plane(
935
+ self,
936
+ path: str,
937
+ size: float = 5000.0,
938
+ z_position: float = 0.0,
939
+ color: Vector3 | list[float] | tuple[float, float, float] | None = None,
940
+ static_friction: float = 0.5,
941
+ dynamic_friction: float = 0.5,
942
+ restitution: float = 0.0,
943
+ ) -> GroundPlane:
944
+ """
945
+ Add a ground plane to the scene.
946
+
947
+ :param path: USD path for the ground plane (default: "/World/ground").
948
+ :param name: Optional unique name for the view.
949
+ :param size: Size of the ground plane in meters.
950
+ :param z_position: Z position of the ground plane.
951
+ :param color: RGB color as Vector3 (or list/tuple of 3 floats) with values 0-1 (defaults to gray).
952
+ :param static_friction: Friction when objects are not moving.
953
+ :param dynamic_friction: Friction when objects are sliding.
954
+ :param restitution: Bounciness of collisions (0=no bounce, 1=perfect bounce).
955
+ :return: The ground plane instance.
956
+ """
957
+
958
+ return GroundPlane.add(
959
+ path=path,
960
+ config=GroundPlaneConfig(
961
+ size=size,
962
+ z_position=z_position,
963
+ color=Vector3.from_any(color) if color is not None else Vector3(data=(0.5, 0.5, 0.5)),
964
+ static_friction=static_friction,
965
+ dynamic_friction=dynamic_friction,
966
+ restitution=restitution,
967
+ ),
968
+ )
969
+
970
+ def get_ground_plane(self, path: str) -> GroundPlane:
971
+ """
972
+ Get an existing ground plane from the scene.
973
+
974
+ :param path: USD path for the ground plane.
975
+ :return: The ground plane instance.
976
+ """
977
+
978
+ return GroundPlane(path)
979
+
980
+ def add_camera(
981
+ self,
982
+ path: str,
983
+ config: CameraConfig | None = None,
984
+ mode: CameraMode = CameraMode.RGB,
985
+ frequency: int = 30,
986
+ width: int = 640,
987
+ height: int = 480,
988
+ focal_length: float = 50.0,
989
+ sensor_width: float = 20.4,
990
+ sensor_height: float = 15.3,
991
+ near_clip: float = 0.1,
992
+ far_clip: float = 1000.0,
993
+ f_stop: float = 0.0,
994
+ focus_distance: float = 10.0,
995
+ principal_point_x: float = 0.0,
996
+ principal_point_y: float = 0.0,
997
+ distortion_model: DistortionModel = DistortionModel.PINHOLE,
998
+ distortion_coefficients: list[float] | None = None,
999
+ world_pose: Pose | dict | None = None,
1000
+ local_pose: Pose | dict | None = None,
1001
+ ) -> Camera:
1002
+ """
1003
+ Add a camera to the scene.
1004
+
1005
+ :param path: USD path for the camera.
1006
+ :param name: Optional unique name for the camera.
1007
+ :param config: Optional camera configuration (alternative to individual parameters).
1008
+ :param mode: Camera capture mode (RGB or depth).
1009
+ :param frequency: Camera update frequency in Hz.
1010
+ :param width: Image width in pixels.
1011
+ :param height: Image height in pixels.
1012
+ :param focal_length: Focal length in mm.
1013
+ :param sensor_width: Physical sensor width in mm.
1014
+ :param sensor_height: Physical sensor height in mm.
1015
+ :param near_clip: Near clipping plane in meters.
1016
+ :param far_clip: Far clipping plane in meters.
1017
+ :param f_stop: F-stop for depth of field.
1018
+ :param focus_distance: Focus distance in meters.
1019
+ :param principal_point_x: Principal point X offset in pixels.
1020
+ :param principal_point_y: Principal point Y offset in pixels.
1021
+ :param distortion_model: Lens distortion model.
1022
+ :param distortion_coefficients: Distortion coefficients.
1023
+ :param world_pose: Optional world pose.
1024
+ :param local_pose: Optional local pose.
1025
+ :return: The camera instance.
1026
+ """
1027
+
1028
+ if config is None:
1029
+ config = CameraConfig(
1030
+ mode=mode,
1031
+ frequency=frequency,
1032
+ width=width,
1033
+ height=height,
1034
+ focal_length=focal_length,
1035
+ sensor_width=sensor_width,
1036
+ sensor_height=sensor_height,
1037
+ near_clip=near_clip,
1038
+ far_clip=far_clip,
1039
+ f_stop=f_stop,
1040
+ focus_distance=focus_distance,
1041
+ principal_point_x=principal_point_x,
1042
+ principal_point_y=principal_point_y,
1043
+ distortion_model=distortion_model,
1044
+ distortion_coefficients=distortion_coefficients,
1045
+ )
1046
+
1047
+ return Camera.add(
1048
+ path=path,
1049
+ config=config,
1050
+ world_pose=Pose.from_any(world_pose) if world_pose is not None else None,
1051
+ local_pose=Pose.from_any(local_pose) if local_pose is not None else None,
1052
+ )
1053
+
1054
+ def get_camera(self, path: str) -> Camera:
1055
+ """
1056
+ Get existing camera from the scene.
1057
+
1058
+ :param path: USD path for the camera.
1059
+ :return: The camera instance.
1060
+ """
1061
+
1062
+ return Camera(path)
1063
+
1064
+ def add_imu(
1065
+ self,
1066
+ path: str,
1067
+ config: ImuConfig | None = None,
1068
+ frequency: int | None = None,
1069
+ linear_acceleration_filter_size: int = 10,
1070
+ angular_velocity_filter_size: int = 10,
1071
+ orientation_filter_size: int = 10,
1072
+ world_pose: Pose | dict | None = None,
1073
+ local_pose: Pose | dict | None = None,
1074
+ ) -> Imu:
1075
+ """
1076
+ Add an IMU to the scene.
1077
+
1078
+ :param path: USD path for the IMU.
1079
+ :param name: Optional unique name for the IMU.
1080
+ :param config: Optional IMU configuration (alternative to individual parameters).
1081
+ :param frequency: Sensor update frequency in Hz (optional, defaults to physics rate).
1082
+ :param linear_acceleration_filter_size: Filter window size for linear acceleration.
1083
+ :param angular_velocity_filter_size: Filter window size for angular velocity.
1084
+ :param orientation_filter_size: Filter window size for orientation.
1085
+ :param world_pose: Optional world pose.
1086
+ :param local_pose: Optional local pose.
1087
+ :return: The IMU instance.
1088
+ """
1089
+
1090
+ if config is None:
1091
+ config = ImuConfig(
1092
+ frequency=frequency,
1093
+ linear_acceleration_filter_size=linear_acceleration_filter_size,
1094
+ angular_velocity_filter_size=angular_velocity_filter_size,
1095
+ orientation_filter_size=orientation_filter_size,
1096
+ )
1097
+
1098
+ return Imu.add(
1099
+ path=path,
1100
+ config=config,
1101
+ world_pose=Pose.from_any(world_pose) if world_pose is not None else None,
1102
+ local_pose=Pose.from_any(local_pose) if local_pose is not None else None,
1103
+ )
1104
+
1105
+ def get_imu(self, path: str) -> Imu:
1106
+ """
1107
+ Get existing IMU from the scene.
1108
+
1109
+ :param path: USD path for the IMU.
1110
+ :return: The IMU instance.
1111
+ """
1112
+
1113
+ return Imu(path)
1114
+
1115
+ def add_radar(
1116
+ self,
1117
+ path: str,
1118
+ config: RadarConfig | None = None,
1119
+ frequency: int = 10,
1120
+ max_azimuth: float = 66.0,
1121
+ max_elevation: float = 20.0,
1122
+ max_range: float = 200.0,
1123
+ range_resolution: float = 0.4,
1124
+ azimuth_resolution: float = 1.3,
1125
+ elevation_resolution: float = 5.0,
1126
+ azimuth_noise: float = 0.0,
1127
+ range_noise: float = 0.0,
1128
+ world_pose: Pose | dict | None = None,
1129
+ local_pose: Pose | dict | None = None,
1130
+ ) -> Radar:
1131
+ """
1132
+ Add a radar to the scene.
1133
+
1134
+ :param path: USD path for the radar.
1135
+ :param name: Optional unique name for the radar.
1136
+ :param config: Optional radar configuration (alternative to individual parameters).
1137
+ :param frequency: Sensor update frequency in Hz.
1138
+ :param max_azimuth: Maximum azimuth angle in degrees (±FOV from center).
1139
+ :param max_elevation: Maximum elevation angle in degrees (±FOV from center).
1140
+ :param max_range: Maximum detection range in meters.
1141
+ :param range_resolution: Range resolution in meters.
1142
+ :param azimuth_resolution: Azimuth resolution at boresight in degrees.
1143
+ :param elevation_resolution: Elevation resolution at boresight in degrees.
1144
+ :param azimuth_noise: Azimuth measurement noise standard deviation in radians.
1145
+ :param range_noise: Range measurement noise standard deviation in meters.
1146
+ :param world_pose: Optional world pose.
1147
+ :param local_pose: Optional local pose.
1148
+ :return: The radar instance.
1149
+ """
1150
+
1151
+ if config is None:
1152
+ config = RadarConfig(
1153
+ frequency=frequency,
1154
+ max_azimuth=max_azimuth,
1155
+ max_elevation=max_elevation,
1156
+ max_range=max_range,
1157
+ range_resolution=range_resolution,
1158
+ azimuth_resolution=azimuth_resolution,
1159
+ elevation_resolution=elevation_resolution,
1160
+ azimuth_noise=azimuth_noise,
1161
+ range_noise=range_noise,
1162
+ )
1163
+
1164
+ return Radar.add(
1165
+ path=path,
1166
+ config=config,
1167
+ world_pose=Pose.from_any(world_pose) if world_pose is not None else None,
1168
+ local_pose=Pose.from_any(local_pose) if local_pose is not None else None,
1169
+ )
1170
+
1171
+ def add_basis_curve_semi_circle(
1172
+ self,
1173
+ path: str,
1174
+ center: Vector3 | list[float] | tuple[float, float, float] = Vector3.zeros(),
1175
+ radius: float = 1.0,
1176
+ min_angle_deg: float = 0.0,
1177
+ max_angle_deg: float = 180.0,
1178
+ ) -> BasisCurve:
1179
+ """
1180
+ Add a basis curve semi-circle to the scene.
1181
+
1182
+ :param path: USD path for the basis curve.
1183
+ :param center: Center of the basis curve.
1184
+ :param radius: Radius of the basis curve.
1185
+ :param min_angle_deg: Minimum angle of the basis curve in degrees.
1186
+ :param max_angle_deg: Maximum angle of the basis curve in degrees.
1187
+ :return: The basis curve instance.
1188
+ """
1189
+
1190
+ return BasisCurve.add(
1191
+ path=path,
1192
+ center=Vector3.from_any(center),
1193
+ radius=radius,
1194
+ min_angle_deg=min_angle_deg,
1195
+ max_angle_deg=max_angle_deg,
1196
+ )
1197
+
1198
+ @overload
1199
+ def add_basis_curve_line(
1200
+ self,
1201
+ path: str,
1202
+ *,
1203
+ start: Vector3 | list[float] | tuple[float, float, float],
1204
+ end: Vector3 | list[float] | tuple[float, float, float],
1205
+ ) -> BasisCurve: ...
1206
+
1207
+ @overload
1208
+ def add_basis_curve_line(
1209
+ self,
1210
+ path: str,
1211
+ *,
1212
+ start: Vector3 | list[float] | tuple[float, float, float],
1213
+ angle_deg: float,
1214
+ length: float,
1215
+ ) -> BasisCurve: ...
1216
+
1217
+ def add_basis_curve_line(
1218
+ self,
1219
+ path: str,
1220
+ *,
1221
+ start: Vector3 | list[float] | tuple[float, float, float],
1222
+ end: Vector3 | list[float] | tuple[float, float, float] | None = None,
1223
+ angle_deg: float | None = None,
1224
+ length: float | None = None,
1225
+ ) -> BasisCurve:
1226
+ """
1227
+ Add a basis curve line to the scene.
1228
+
1229
+ Supports two modes:
1230
+ - Cartesian: Provide start and end points directly
1231
+ - Polar: Provide start point, angle (degrees from +X axis in XY plane), and length
1232
+
1233
+ Examples:
1234
+ # Cartesian mode
1235
+ line = scene.add_basis_curve_line(
1236
+ "/World/line1",
1237
+ start=[0, 0, 0],
1238
+ end=[1, 1, 0],
1239
+ )
1240
+
1241
+ # Polar mode
1242
+ line = scene.add_basis_curve_line(
1243
+ "/World/line2",
1244
+ start=[0, 0, 0],
1245
+ angle_deg=45.0,
1246
+ length=2.0,
1247
+ )
1248
+
1249
+ :param path: USD path for the basis curve.
1250
+ :param start: Start point of the line.
1251
+ :param end: End point of the line (Cartesian mode).
1252
+ :param angle_deg: Angle in degrees from +X axis in XY plane (polar mode).
1253
+ :param length: Length of the line (polar mode).
1254
+ :return: The basis curve instance.
1255
+ :raises ValueError: If both modes are specified or neither mode is complete.
1256
+ """
1257
+ if end is not None and (angle_deg is not None or length is not None):
1258
+ raise ValueError("Cannot specify both end point and angle/length in Cartesian mode")
1259
+ if end is None and angle_deg is None and length is None:
1260
+ raise ValueError("Must specify either end point or angle/length")
1261
+ if length is not None and angle_deg is None:
1262
+ raise ValueError("Must specify angle when length is provided")
1263
+ if angle_deg is not None and length is None:
1264
+ raise ValueError("Must specify length when angle is provided")
1265
+
1266
+ return BasisCurve.add_line(
1267
+ path=path,
1268
+ start=Vector3.from_any(start),
1269
+ end=Vector3.from_any(end) if end is not None else None,
1270
+ angle_deg=angle_deg,
1271
+ length=length,
1272
+ )
1273
+
1274
+ def get_basis_curve(self, path: str) -> BasisCurve:
1275
+ """
1276
+ Get existing basis curve from the scene.
1277
+
1278
+ :param path: USD path for the basis curve.
1279
+ :return: The basis curve instance.
1280
+ """
1281
+
1282
+ return BasisCurve(path)
1283
+
1284
+ def get_basis_curve_extents(self, path: str) -> tuple[Vector3, Vector3]:
1285
+ """
1286
+ Get the extents of the basis curve.
1287
+
1288
+ :param path: USD path for the basis curve.
1289
+ :return: The extents of the basis curve.
1290
+ """
1291
+
1292
+ return BasisCurve(path).get_extents()
1293
+
1294
+ def get_basis_curve_points(
1295
+ self, path: str, samples_per_segment: int = 10, sort_by: Literal["X", "Y", "Z"] | None = None, ascending: bool = True
1296
+ ) -> list[Vector3]:
1297
+ """
1298
+ Get the points of the basis curve.
1299
+
1300
+ :param path: USD path for the basis curve.
1301
+ :param samples_per_segment: The number of samples per segment.
1302
+ :param sort_by: The axis to sort the points by.
1303
+ :param ascending: Whether to sort the points in ascending order.
1304
+ :return: The points of the basis curve.
1305
+ """
1306
+
1307
+ return BasisCurve(path).get_points(samples_per_segment=samples_per_segment, sort_by=sort_by, ascending=ascending)
1308
+
1309
+ def set_basis_curve_visibility(self, path: str, visible: bool) -> None:
1310
+ """
1311
+ Set the visibility of the basis curve.
1312
+
1313
+ :param path: USD path for the basis curve.
1314
+ :param visible: True to make visible, False to hide.
1315
+ """
1316
+
1317
+ BasisCurve(path).set_visibility(visible)
1318
+
1319
+ def get_radar(self, path: str) -> Radar:
1320
+ """
1321
+ Get existing radar from the scene.
1322
+
1323
+ :param path: USD path for the radar.
1324
+ :return: The radar instance.
1325
+ """
1326
+
1327
+ return Radar(path)
1328
+
1329
+ def add_pir_sensor(
1330
+ self,
1331
+ path: str,
1332
+ config: PirSensorConfig | None = None,
1333
+ update_rate_hz: float = 10.0,
1334
+ max_range: float = 12.0,
1335
+ horiz_fov_deg: float = 90.0,
1336
+ vert_fov_deg: float = 60.0,
1337
+ rays_per_h: int = 64,
1338
+ rays_per_v: int = 16,
1339
+ gain: float = 0.01,
1340
+ hp_corner_hz: float = 0.1,
1341
+ lp_corner_hz: float = 3.0,
1342
+ threshold: float | None = None,
1343
+ threshold_scale: float = 1.0,
1344
+ hold_time_s: float = 2.0,
1345
+ lens_transmission: float = 0.9,
1346
+ lens_segments_h: int = 6,
1347
+ vertical_gain_falloff: float = 0.5,
1348
+ element_fov_overlap_deg: float = 20.0,
1349
+ element_scatter_gain: float = 0.1,
1350
+ element_fov_transition_deg: float = 15.0,
1351
+ ambient_temp_c: float = 20.0,
1352
+ thermal_time_constant_s: float = 0.2,
1353
+ pyro_responsivity: float = 1.0,
1354
+ noise_amplitude: float = 0.0,
1355
+ target_delta_t: float = 10.0,
1356
+ target_distance: float = 5.0,
1357
+ target_emissivity: float = 0.98,
1358
+ target_velocity_mps: float = 1.0,
1359
+ world_pose: Pose | dict | None = None,
1360
+ local_pose: Pose | dict | None = None,
1361
+ ) -> PirSensor:
1362
+ """
1363
+ Add a PIR (Passive Infrared) sensor to the scene.
1364
+
1365
+ PIR sensors detect infrared radiation changes caused by moving warm objects.
1366
+ The sensor uses a dual-element design with interleaved zones for motion detection.
1367
+
1368
+ :param path: USD path for the PIR sensor.
1369
+ :param config: Optional PIR sensor configuration (alternative to individual parameters).
1370
+ :param update_rate_hz: Sensor update frequency in Hz.
1371
+ :param max_range: Maximum detection range in meters.
1372
+ :param horiz_fov_deg: Horizontal field of view in degrees.
1373
+ :param vert_fov_deg: Vertical field of view in degrees.
1374
+ :param rays_per_h: Number of rays in horizontal direction.
1375
+ :param rays_per_v: Number of rays in vertical direction.
1376
+ :param gain: Amplifier gain.
1377
+ :param hp_corner_hz: High-pass filter corner frequency in Hz.
1378
+ :param lp_corner_hz: Low-pass filter corner frequency in Hz.
1379
+ :param threshold: Detection threshold (auto-calibrated if None).
1380
+ :param threshold_scale: Scale factor applied to auto-calibrated threshold.
1381
+ :param hold_time_s: Detection hold time in seconds.
1382
+ :param lens_transmission: Lens transmission coefficient (0-1).
1383
+ :param lens_segments_h: Number of horizontal lens segments (sinusoidal sensitivity peaks).
1384
+ :param vertical_gain_falloff: Exponent for elevation-based gain reduction (0=uniform).
1385
+ :param element_fov_overlap_deg: Half-angle of center region where both elements see well (degrees).
1386
+ :param element_scatter_gain: Minimum gain for blocked element due to scatter/reflections (0-1).
1387
+ :param element_fov_transition_deg: Width of soft transition from full gain to scatter gain (degrees).
1388
+ :param ambient_temp_c: Ambient temperature in Celsius.
1389
+ :param thermal_time_constant_s: Pyroelectric element thermal time constant in seconds.
1390
+ :param pyro_responsivity: Pyroelectric responsivity scaling factor.
1391
+ :param noise_amplitude: Thermal/electronic noise amplitude.
1392
+ :param target_delta_t: Target temperature difference for threshold calibration in Celsius.
1393
+ :param target_distance: Target distance for threshold calibration in meters.
1394
+ :param target_emissivity: Target emissivity for threshold calibration.
1395
+ :param target_velocity_mps: Target velocity for threshold calibration in m/s.
1396
+ :param world_pose: Optional world pose.
1397
+ :param local_pose: Optional local pose.
1398
+ :return: The PIR sensor instance.
1399
+ """
1400
+
1401
+ if config is None:
1402
+ config = PirSensorConfig(
1403
+ update_rate_hz=update_rate_hz,
1404
+ max_range=max_range,
1405
+ horiz_fov_deg=horiz_fov_deg,
1406
+ vert_fov_deg=vert_fov_deg,
1407
+ rays_per_h=rays_per_h,
1408
+ rays_per_v=rays_per_v,
1409
+ gain=gain,
1410
+ hp_corner_hz=hp_corner_hz,
1411
+ lp_corner_hz=lp_corner_hz,
1412
+ threshold=threshold,
1413
+ threshold_scale=threshold_scale,
1414
+ hold_time_s=hold_time_s,
1415
+ lens_transmission=lens_transmission,
1416
+ lens_segments_h=lens_segments_h,
1417
+ vertical_gain_falloff=vertical_gain_falloff,
1418
+ element_fov_overlap_deg=element_fov_overlap_deg,
1419
+ element_scatter_gain=element_scatter_gain,
1420
+ element_fov_transition_deg=element_fov_transition_deg,
1421
+ ambient_temp_c=ambient_temp_c,
1422
+ thermal_time_constant_s=thermal_time_constant_s,
1423
+ pyro_responsivity=pyro_responsivity,
1424
+ noise_amplitude=noise_amplitude,
1425
+ target_delta_t=target_delta_t,
1426
+ target_distance=target_distance,
1427
+ target_emissivity=target_emissivity,
1428
+ target_velocity_mps=target_velocity_mps,
1429
+ )
1430
+
1431
+ return PirSensor.add(
1432
+ path=path,
1433
+ config=config,
1434
+ world_pose=Pose.from_any(world_pose) if world_pose is not None else None,
1435
+ local_pose=Pose.from_any(local_pose) if local_pose is not None else None,
1436
+ )
1437
+
1438
+ def get_pir_sensor(self, path: str) -> PirSensor:
1439
+ """
1440
+ Get existing PIR sensor from the scene.
1441
+
1442
+ :param path: USD path for the PIR sensor.
1443
+ :return: The PIR sensor instance.
1444
+ """
1445
+
1446
+ return PirSensor(path)
1447
+
1448
+ def set_collision(self, path: str, mesh_approximation: MeshApproximation | None = None) -> None:
1449
+ """
1450
+ Apply collision API to a prim.
1451
+
1452
+ :param path: USD path to the prim.
1453
+ :param mesh_approximation: Optional mesh approximation method for collision geometry.
1454
+ """
1455
+
1456
+ set_collision(path, mesh_approximation)
1457
+
1458
+ def remove_collision(self, path: str) -> None:
1459
+ """
1460
+ Remove collision API from a prim.
1461
+
1462
+ :param path: USD path to the prim.
1463
+ """
1464
+
1465
+ remove_collision(path)
1466
+
1467
+ def has_collision(self, path: str) -> bool:
1468
+ """
1469
+ Check if a prim has collision API applied.
1470
+
1471
+ :param path: USD path to the prim.
1472
+ :return: True if collision API is applied.
1473
+ """
1474
+
1475
+ return has_collision(path)
1476
+
1477
+ def get_mesh_approximation(self, path: str) -> MeshApproximation | None:
1478
+ """
1479
+ Get mesh collision approximation from a prim.
1480
+
1481
+ :param path: USD path to the prim.
1482
+ :return: Mesh approximation method, or None if not set.
1483
+ """
1484
+
1485
+ return get_mesh_approximation(path)
1486
+
1487
+ def set_pir_material(
1488
+ self,
1489
+ path: str,
1490
+ emissivity: float = 0.9,
1491
+ temperature_c: float | None = None,
1492
+ ) -> None:
1493
+ """
1494
+ Set PIR-specific thermal properties on a prim.
1495
+
1496
+ These properties define how the prim appears to PIR sensors:
1497
+ - emissivity: How well the surface emits infrared radiation (0-1)
1498
+ - temperature_c: Surface temperature in Celsius
1499
+
1500
+ Example:
1501
+ scene.set_pir_material("/World/person", emissivity=0.98, temperature_c=37.0)
1502
+
1503
+ :param path: USD path of the prim to configure.
1504
+ :param emissivity: Material emissivity (0-1, default 0.9).
1505
+ :param temperature_c: Surface temperature in Celsius (optional).
1506
+ """
1507
+
1508
+ set_pir_material(path, emissivity, temperature_c)
1509
+
1510
+ def _step_physics(self, dt_us: int) -> None:
1511
+ """
1512
+ Step physics by dt_us.
1513
+
1514
+ :param dt_us: Amount of time to step in microseconds.
1515
+ """
1516
+
1517
+ if dt_us > 0:
1518
+ self._session.query_sim_rpc(
1519
+ endpoint="step",
1520
+ payload=Step(dt_us=dt_us),
1521
+ )