1818import threading
1919
2020# Thirdparty
21- import torch # noqa: TID253
2221import warp as wp
2322
2423# Kamino
@@ -250,12 +249,14 @@ def __init__(
250249 collapse_fixed_joints : bool = False ,
251250 terrain_fn : callable | None = None ,
252251 scene_callback : callable | None = None ,
252+ use_torch : bool = True ,
253253 ):
254254 # ----- Device setup -----
255255 self ._device = wp .get_device (device )
256256 self ._torch_device : str = "cuda" if self ._device .is_cuda else "cpu"
257257 self ._use_cuda_graph = use_cuda_graph
258258 self ._sim_dt = sim_dt
259+ self ._use_torch = use_torch
259260
260261 # ----- Video recording -----
261262 self ._record_video = record_video
@@ -428,7 +429,7 @@ def apply_shape_colors(shape_colors: dict[int, Color3]):
428429 # ------------------------------------------------------------------
429430
430431 def _make_rl_interface (self ):
431- """Create zero-copy PyTorch views of simulator state, control and contact arrays ."""
432+ """Create Warp views and, when requested, zero-copy PyTorch views ."""
432433 nw = self .sim .model .size .num_worlds
433434 njc = self .sim .model .size .max_of_num_joint_coords
434435 njd = self .sim .model .size .max_of_num_joint_dofs
@@ -439,6 +440,38 @@ def _make_rl_interface(self):
439440 assert self .sim .model .size .sum_of_num_joint_coords == nw * njc
440441 assert self .sim .model .size .sum_of_num_joint_dofs == nw * njd
441442
443+ # Warp state/control views are always available. This lets ONNX/Warp
444+ # examples run without importing PyTorch at all.
445+ self ._q_j_wp = self .sim .state .q_j .reshape ((nw , njc ))
446+ self ._dq_j_wp = self .sim .state .dq_j .reshape ((nw , njd ))
447+ self ._q_i_wp = self .sim .state .q_i .reshape ((nw , nb ))
448+ self ._u_i_wp = self .sim .state .u_i .reshape ((nw , nb ))
449+ self ._q_j_ref_wp = self .sim .control .q_j_ref .reshape ((nw , njc ))
450+ self ._dq_j_ref_wp = self .sim .control .dq_j_ref .reshape ((nw , njd ))
451+ self ._tau_j_ref_wp = self .sim .control .tau_j_ref .reshape ((nw , njd ))
452+
453+ # World mask and reset buffers are native Warp arrays.
454+ self ._world_mask_wp = wp .zeros ((nw ,), dtype = wp .bool , device = self ._device )
455+ self ._reset_base_q_wp = wp .zeros (nw , dtype = wp .transformf , device = self ._device )
456+ self ._reset_base_u_wp = wp .zeros (nw , dtype = wp .spatial_vectorf , device = self ._device )
457+ self ._reset_q_j_wp = wp .zeros (nw * njc , dtype = wp .float32 , device = self ._device )
458+ self ._reset_dq_j_wp = wp .zeros (nw * njd , dtype = wp .float32 , device = self ._device )
459+
460+ # Contact aggregation is required by the simulator step in either mode.
461+ self ._contact_aggregation = ContactAggregation (model = self .sim .model , contacts = self .sim .contacts )
462+
463+ if not self ._use_torch :
464+ self ._body_pair_contact_flag = None
465+ self ._update_q_j = False
466+ self ._update_dq_j = False
467+ self ._update_base_q = False
468+ self ._update_base_u = False
469+ return
470+
471+ # Import lazily: the Warp-only replay path must work without Torch installed.
472+ import torch
473+
474+ self ._torch = torch
442475 # State tensors (read-only views into simulator)
443476 # q_j uses generalized coordinates (njc), dq_j uses DOFs (njd)
444477 self ._q_j = wp .to_torch (self .sim .state .q_j ).reshape (nw , njc )
@@ -452,15 +485,9 @@ def _make_rl_interface(self):
452485 self ._dq_j_ref = wp .to_torch (self .sim .control .dq_j_ref ).reshape (nw , njd )
453486 self ._tau_j_ref = wp .to_torch (self .sim .control .tau_j_ref ).reshape (nw , njd )
454487
455- # World mask for selective resets
456- self ._world_mask_wp = wp .zeros ((nw ,), dtype = wp .bool , device = self ._device )
457488 self ._world_mask = wp .to_torch (self ._world_mask_wp )
458489
459490 # Reset buffers
460- self ._reset_base_q_wp = wp .zeros (nw , dtype = wp .transformf , device = self ._device )
461- self ._reset_base_u_wp = wp .zeros (nw , dtype = wp .spatial_vectorf , device = self ._device )
462- self ._reset_q_j_wp = wp .zeros (nw * njc , dtype = wp .float32 , device = self ._device )
463- self ._reset_dq_j_wp = wp .zeros (nw * njd , dtype = wp .float32 , device = self ._device )
464491 self ._reset_base_q = wp .to_torch (self ._reset_base_q_wp ).reshape (nw , 7 )
465492 self ._reset_base_u = wp .to_torch (self ._reset_base_u_wp ).reshape (nw , 6 )
466493 self ._reset_q_j = wp .to_torch (self ._reset_q_j_wp ).reshape (nw , njc )
@@ -472,8 +499,6 @@ def _make_rl_interface(self):
472499 self ._update_base_q = False
473500 self ._update_base_u = False
474501
475- # Contact aggregation
476- self ._contact_aggregation = ContactAggregation (model = self .sim .model , contacts = self .sim .contacts )
477502 self ._contact_flags = wp .to_torch (self ._contact_aggregation .body_contact_flag ).reshape (nw , nb )
478503 self ._ground_contact_flags = wp .to_torch (self ._contact_aggregation .body_static_contact_flag ).reshape (nw , nb )
479504 self ._net_contact_forces = wp .to_torch (self ._contact_aggregation .body_net_force ).reshape (nw , nb , 3 )
@@ -502,11 +527,11 @@ def _extract_metadata(self):
502527
503528 # Read per-joint metadata from the Kamino model (first world only)
504529 joint_labels = [lbl .rsplit ("/" , 1 )[- 1 ] for lbl in self .sim .model .joints .label [:max_joints ]]
505- joint_num_coords = wp . to_torch ( self .sim .model .joints .num_coords )[:max_joints ].tolist ()
506- joint_num_dofs = wp . to_torch ( self .sim .model .joints .num_dofs )[:max_joints ].tolist ()
507- joint_act_type = wp . to_torch ( self .sim .model .joints .act_type )[:max_joints ].tolist ()
508- joint_q_j_min = wp . to_torch ( self .sim .model .joints .q_j_min )
509- joint_q_j_max = wp . to_torch ( self .sim .model .joints .q_j_max )
530+ joint_num_coords = self .sim .model .joints .num_coords . numpy ( )[:max_joints ].tolist ()
531+ joint_num_dofs = self .sim .model .joints .num_dofs . numpy ( )[:max_joints ].tolist ()
532+ joint_act_type = self .sim .model .joints .act_type . numpy ( )[:max_joints ].tolist ()
533+ joint_q_j_min = self .sim .model .joints .q_j_min . numpy ( )
534+ joint_q_j_max = self .sim .model .joints .q_j_max . numpy ( )
510535
511536 # Joint names and actuated indices
512537 self ._joint_names : list [str ] = []
@@ -528,12 +553,15 @@ def _extract_metadata(self):
528553 coord_offset += ncoords
529554 dof_offset += ndofs
530555
531- self ._actuated_coord_indices_tensor = torch .tensor (
532- self ._actuated_coord_indices , device = self ._torch_device , dtype = torch .long
533- )
534- self ._actuated_dof_indices_tensor = torch .tensor (
535- self ._actuated_dof_indices , device = self ._torch_device , dtype = torch .long
536- )
556+ self ._actuated_coord_indices_wp = wp .array (self ._actuated_coord_indices , dtype = wp .int32 , device = self ._device )
557+ self ._actuated_dof_indices_wp = wp .array (self ._actuated_dof_indices , dtype = wp .int32 , device = self ._device )
558+ if self ._use_torch :
559+ self ._actuated_coord_indices_tensor = self ._torch .tensor (
560+ self ._actuated_coord_indices , device = self ._torch_device , dtype = self ._torch .long
561+ )
562+ self ._actuated_dof_indices_tensor = self ._torch .tensor (
563+ self ._actuated_dof_indices , device = self ._torch_device , dtype = self ._torch .long
564+ )
537565
538566 msg .info (f"Actuated joints ({ self .num_actuated } ): { self ._actuated_joint_names } " )
539567
@@ -589,9 +617,9 @@ def step(self):
589617
590618 def reset (self ):
591619 """Full reset of all worlds to initial state."""
592- self ._world_mask .fill_ (1 )
620+ self ._world_mask_wp .fill_ (True )
593621 self ._reset_worlds ()
594- self ._world_mask .zero_ ()
622+ self ._world_mask_wp .zero_ ()
595623
596624 def apply_resets (self ):
597625 """Apply pending selective resets staged via :meth:`set_dof` / :meth:`set_root`.
@@ -604,7 +632,7 @@ def apply_resets(self):
604632 wp .capture_launch (self ._reset_graph )
605633 else :
606634 self ._reset_worlds ()
607- self ._world_mask .zero_ ()
635+ self ._world_mask_wp .zero_ ()
608636 self ._update_q_j = False
609637 self ._update_dq_j = False
610638 self ._update_base_q = False
@@ -853,6 +881,44 @@ def set_root(
853881 # State properties (zero-copy torch views)
854882 # ------------------------------------------------------------------
855883
884+ @property
885+ def q_j_wp (self ):
886+ """Joint positions as a zero-copy Warp array."""
887+ return self ._q_j_wp
888+
889+ @property
890+ def dq_j_wp (self ):
891+ """Joint velocities as a zero-copy Warp array."""
892+ return self ._dq_j_wp
893+
894+ @property
895+ def q_i_wp (self ):
896+ """Body poses as a zero-copy Warp transform array."""
897+ return self ._q_i_wp
898+
899+ @property
900+ def u_i_wp (self ):
901+ """Body twists as a zero-copy Warp spatial-vector array."""
902+ return self ._u_i_wp
903+
904+ @property
905+ def q_j_ref_wp (self ):
906+ """Joint position references as a zero-copy Warp array."""
907+ return self ._q_j_ref_wp
908+
909+ @property
910+ def dq_j_ref_wp (self ):
911+ """Joint velocity references as a zero-copy Warp array."""
912+ return self ._dq_j_ref_wp
913+
914+ @property
915+ def actuated_coord_indices_wp (self ):
916+ return self ._actuated_coord_indices_wp
917+
918+ @property
919+ def actuated_dof_indices_wp (self ):
920+ return self ._actuated_dof_indices_wp
921+
856922 @property
857923 def q_j (self ) -> torch .Tensor :
858924 """Joint positions ``(num_worlds, num_joint_coords)``."""
0 commit comments