# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
from __future__ import annotations
import logging
import warnings
from typing import Any
import numpy as np
import warp as wp
from isaaclab.assets.articulation import ordering_kernels
from isaaclab.assets.articulation.base_articulation_data import BaseArticulationData
from isaaclab.utils.buffers import TimestampedBufferWarp as TimestampedBuffer
from isaaclab.utils.buffers import reset_timestamps
from isaaclab.utils.warp import ProxyArray
from isaaclab.utils.warp.launch_cache import _WarpLaunchCache
from isaaclab_ov import tensor_types as TT
from isaaclab_ov.assets.kernels import (
_compose_root_com_pose,
_compute_heading,
_copy_first_body,
_fd_joint_acc_ordered,
_projected_gravity,
_world_vel_to_body_ang,
_world_vel_to_body_lin,
concat_body_pose_and_vel_to_state,
concat_root_pose_and_vel_to_state,
get_body_com_pose_from_body_link_pose,
get_body_link_vel_from_body_com_vel,
vec13f,
)
from isaaclab_ov.physics import OvPhysxManager
from isaaclab_ov.sim.views.ovphysx_view import OvPhysxView
from . import kernels as articulation_kernels
from .kernels import _fd_joint_acc
# import logger
logger = logging.getLogger(__name__)
[docs]
class ArticulationData(BaseArticulationData):
"""Data container for an articulation.
This class contains the data for an articulation in the simulation. The data includes the state of
the root rigid body, the state of all the bodies in the articulation, and the joint state. The data is
stored in the simulation world frame unless otherwise specified.
An articulation is comprised of multiple rigid bodies or links. For a rigid body, there are two frames
of reference that are used:
- Actor frame: The frame of reference of the rigid body prim. This typically corresponds to the Xform prim
with the rigid body schema.
- Center of mass frame: The frame of reference of the center of mass of the rigid body.
Depending on the settings, the two frames may not coincide with each other. In the robotics sense, the actor frame
can be interpreted as the link frame.
.. note::
**Pull-to-refresh model.** OVPhysX state properties are *not* automatically updated each
simulation step. Without ordering or joint-direction correction, first access per timestamp
refreshes the public buffer directly from the OVPhysX ``TensorBinding`` and caches it until
the next step. Otherwise, the getter normalizes a backend-order staging buffer into an owned
public-order shadow. Newton's solver-owned backend-order buffers
are refreshed automatically by the simulation, and its nonidentity public-order shadows are
published automatically once per simulation step.
.. note::
**CPU-only bindings.** OVPhysX exposes a subset of bindings (``BODY_MASS``, ``BODY_COM_POSE``,
``BODY_INERTIA``, and most ``DOF_*`` property bindings) on CPU only. These are routed through
pinned-host staging buffers via :meth:`_binding_read` so that GPU-resident consumers see the
data without per-step host allocations.
.. note::
**Recorded read commands.** OVPhysX reads into stable, pre-allocated destination buffers.
Outside CUDA graph capture, repeated Warp kernels that derive or reorder public data from
those buffers reuse recorded launch commands. Direct ``TensorBinding`` reads continue to use
:class:`OvPhysxView`'s object-identity cache. Recorded commands are discarded whenever ordering
buffers may be replaced or the data container is invalidated.
"""
__backend_name__: str = "ovphysx"
"""The name of the backend for the articulation data."""
[docs]
def __init__(self, view: OvPhysxView, device: str) -> None:
"""Initialize the articulation data container.
Args:
view: The :class:`~isaaclab_ov.sim.views.OvPhysxView` binding manager
for this articulation. All counts (instances, bodies, DOFs,
fixed/spatial tendons) are derived from the view metadata. Name lists
are assigned by
:meth:`~isaaclab_ov.assets.Articulation._initialize_impl` after
construction.
device: Simulation device string (e.g., ``"cuda:0"`` or ``"cpu"``).
"""
super().__init__(root_view=None, device=device)
self._view = view
# The view exposes the articulation metadata (instance count, dof_count,
# body_count, fixed/spatial tendon counts) read from any instantiated binding.
self.num_instances = view.count
self.num_bodies = view.body_count
self.num_joints = view.dof_count
self.num_fixed_tendons = view.fixed_tendon_count
self.num_spatial_tendons = view.spatial_tendon_count
# private aliases used throughout _create_buffers and property bodies
self._num_instances = self.num_instances
self._num_bodies = self.num_bodies
self._num_joints = self.num_joints
self._num_fixed_tendons = self.num_fixed_tendons
self._num_spatial_tendons = self.num_spatial_tendons
# Set initial time stamp
self._sim_timestamp: float = 0.0
self._fk_timestamp: float = 0.0
self._is_primed: bool = False
self._read_launch_cache = _WarpLaunchCache(device)
self._joint_dof_signs = wp.ones(self.num_joints, dtype=wp.int32, device=device)
self._has_reversed_joints = False
# pinned-host staging buffers for CPU-only bindings (keyed by tensor_type)
self._cpu_staging_buffers: dict[int, wp.array] = {}
# obtain gravity from the simulation configuration (fall back to standard
# gravity when the simulation has not been configured yet, e.g. in unit tests)
gravity = (0.0, 0.0, -9.81)
from isaaclab.physics import PhysicsManager
if PhysicsManager._sim is not None and hasattr(PhysicsManager._sim, "cfg"):
gravity = PhysicsManager._sim.cfg.gravity
gravity_np = np.array(gravity, dtype=np.float32)
gravity_mag = float(np.linalg.norm(gravity_np))
if gravity_mag == 0.0:
gravity_dir = np.array([0.0, 0.0, -1.0], dtype=np.float32)
else:
gravity_dir = gravity_np / gravity_mag
gravity_dir_tiled = np.tile(gravity_dir, (self._num_instances, 1))
forward_tiled = np.tile(np.array([1.0, 0.0, 0.0], dtype=np.float32), (self._num_instances, 1))
# Initialize constants
self.GRAVITY_VEC_W = ProxyArray(wp.from_numpy(gravity_dir_tiled, dtype=wp.vec3f, device=device))
self.FORWARD_VEC_B = ProxyArray(wp.from_numpy(forward_tiled, dtype=wp.vec3f, device=device))
self._create_buffers()
@property
def is_primed(self) -> bool:
"""Whether the articulation data is fully instantiated and ready to use."""
return self._is_primed
@is_primed.setter
def is_primed(self, value: bool) -> None:
"""Set whether the articulation data is fully instantiated and ready to use.
.. note::
Once this quantity is set to True, it cannot be changed.
Args:
value: The primed state.
Raises:
ValueError: If the articulation data is already primed.
"""
if self._is_primed:
raise ValueError("The articulation data is already primed.")
self._is_primed = True
def update(self, dt: float) -> None:
"""Updates the data for the articulation.
Args:
dt: The time step for the update. This must be a positive value.
"""
# update the simulation timestamp
self._sim_timestamp += dt
# FK is current after a sim step. Keep fk_timestamp in sync unless it was explicitly invalidated.
if self._fk_timestamp >= 0.0:
self._fk_timestamp = self._sim_timestamp
if not self._is_primed:
return
# Trigger a finite-difference refresh of the joint acceleration at step frequency. The
# property recomputes lazily when stale; reading it here keeps the FD cadence at one step.
self.joint_acc
def _ensure_fk_fresh(self) -> None:
"""Run forward kinematics if the joint / body state has changed since the last FK update.
Isaac Sim's articulation link transforms and velocities are recomputed by
``update_articulations_kinematic``. After a manual joint or root write that bypassed the sim
step (``write_*_to_sim_*``), ``_fk_timestamp`` is set to ``-1.0`` to force a refresh on the
next read of any property that depends on body poses or velocities. The physics instance is
absent under the mocked-interface tests, in which case the refresh is skipped.
"""
if self._fk_timestamp < self._sim_timestamp:
physx_instance = OvPhysxManager.get_physx_instance()
if physx_instance is not None:
physx_instance.update_articulations_kinematic()
self._fk_timestamp = self._sim_timestamp
def _reset_pose(self, from_link: bool = True) -> None:
"""Reset pose-dependent cached articulation properties.
Writing a root or joint pose moves the body kinematic chain, so every buffer derived from
body poses (the world-frame body poses and the composite root/body state buffers) goes stale.
Args:
from_link: Set ``True`` when the root link pose was written so the derived root
center-of-mass pose (:attr:`root_com_pose_w`) is also invalidated; set ``False`` when
the center-of-mass pose was written directly so it is not clobbered. Defaults to True.
"""
# The root com pose is derived from the root link pose, so only invalidate it when the link
# pose was the quantity written (otherwise we would clobber the freshly-written com pose).
# Body poses and the composite state buffers always go stale on a pose write.
reset_timestamps(
[
self._root_com_pose_w if from_link else None,
self._body_link_pose_w,
self._body_link_pose_w_backend,
self._body_com_pose_w,
self._root_link_vel_w,
self._body_link_vel_w,
self._body_com_vel_w,
self._body_com_vel_w_backend,
self._projected_gravity_b,
self._heading_w,
self._root_link_lin_vel_b,
self._root_link_ang_vel_b,
self._root_com_lin_vel_b,
self._root_com_ang_vel_b,
self._root_state_w_buf,
self._root_link_state_w_buf,
self._root_com_state_w_buf,
self._body_state_w_buf,
self._body_link_state_w_buf,
self._body_com_state_w_buf,
self._body_com_jacobian_w,
self._mass_matrix,
self._gravity_compensation_forces,
]
)
# Force a kinematic refresh on the next FK-dependent read.
self._fk_timestamp = -1.0
def _reset_velocity(self, from_com: bool = True) -> None:
"""Reset velocity-dependent cached articulation properties.
Writing a root or joint velocity changes the body velocities, so every buffer derived from
them (the body velocities and the composite root/body state buffers) goes stale.
Args:
from_com: Set ``True`` when the root center-of-mass velocity was written so the derived root
link velocity (:attr:`root_link_vel_w`) is also invalidated; set ``False`` when the link
velocity was written directly so it is not clobbered. Defaults to True.
"""
# The root link velocity is derived from the root com velocity, so only invalidate it when the
# com velocity was the quantity written (otherwise we would clobber the freshly-written value).
# Body velocities and the composite state buffers always go stale on a velocity write.
reset_timestamps(
[
self._root_link_vel_w if from_com else None,
self._body_com_vel_w,
self._body_com_vel_w_backend,
self._body_link_vel_w,
self._root_link_lin_vel_b,
self._root_link_ang_vel_b,
self._root_com_lin_vel_b,
self._root_com_ang_vel_b,
self._root_state_w_buf,
self._root_link_state_w_buf,
self._root_com_state_w_buf,
self._body_state_w_buf,
self._body_link_state_w_buf,
self._body_com_state_w_buf,
]
)
# Force a kinematic refresh on the next FK-dependent read.
self._fk_timestamp = -1.0
def _reset_dynamics(
self, *, body_com_jacobian: bool = False, mass_matrix: bool = False, gravity_compensation: bool = False
) -> None:
"""Reset selected computed-dynamics caches after same-timestamp model writes."""
reset_timestamps(
[
self._body_com_jacobian_w if body_com_jacobian else None,
self._mass_matrix if mass_matrix else None,
self._gravity_compensation_forces if gravity_compensation else None,
]
)
def _reset_body_com_pose_b_dependents(self) -> None:
"""Reset cached properties derived from body-frame center-of-mass offsets."""
reset_timestamps(
[
self._root_com_pose_w,
self._root_com_vel_w,
self._root_link_vel_w,
self._body_com_pose_w,
self._body_com_vel_w,
self._body_com_vel_w_backend,
self._body_link_vel_w,
self._root_link_lin_vel_b,
self._root_link_ang_vel_b,
self._root_com_lin_vel_b,
self._root_com_ang_vel_b,
self._root_state_w_buf,
self._root_link_state_w_buf,
self._root_com_state_w_buf,
self._body_state_w_buf,
self._body_link_state_w_buf,
self._body_com_state_w_buf,
]
)
self._reset_dynamics(body_com_jacobian=True, mass_matrix=True, gravity_compensation=True)
"""
Names.
"""
body_names: list[str] = None
"""Body names in public order (configured ordering when set, otherwise backend order)."""
joint_names: list[str] = None
"""Joint names in public order (configured ordering when set, otherwise backend order)."""
fixed_tendon_names: list[str] = None
"""Fixed tendon names in the order parsed by USD."""
spatial_tendon_names: list[str] = None
"""Spatial tendon names in the order parsed by USD."""
"""
Defaults - Initial state.
"""
@property
def default_root_pose(self) -> ProxyArray:
"""Default root pose ``[pos, quat]`` in local environment frame [m, -].
Shape is (num_instances,), dtype = wp.transformf.
In torch this resolves to (num_instances, 7).
Populated from :attr:`ArticulationCfg.init_state` during initialisation.
"""
if self._default_root_pose_ta is None:
self._default_root_pose_ta = ProxyArray(self._default_root_pose)
return self._default_root_pose_ta
@default_root_pose.setter
def default_root_pose(self, value: wp.array) -> None:
"""Set the default root pose.
Args:
value: The default root pose, shape (num_instances, 7).
Raises:
ValueError: If the articulation data is already primed.
"""
if self._is_primed:
raise ValueError("The articulation data is already primed.")
self._default_root_pose.assign(value)
@property
def default_root_vel(self) -> ProxyArray:
"""Default root velocity ``[lin_vel, ang_vel]`` in local environment frame [m/s, rad/s].
Shape is (num_instances,), dtype = wp.spatial_vectorf.
In torch this resolves to (num_instances, 6).
Populated from :attr:`ArticulationCfg.init_state` during initialisation.
"""
if self._default_root_vel_ta is None:
self._default_root_vel_ta = ProxyArray(self._default_root_vel)
return self._default_root_vel_ta
@default_root_vel.setter
def default_root_vel(self, value: wp.array) -> None:
"""Set the default root velocity.
Args:
value: The default root velocity, shape (num_instances, 6).
Raises:
ValueError: If the articulation data is already primed.
"""
if self._is_primed:
raise ValueError("The articulation data is already primed.")
self._default_root_vel.assign(value)
@property
def default_joint_pos(self) -> ProxyArray:
"""Default joint positions of all joints [m or rad, depending on joint type].
Shape is (num_instances, num_joints), dtype = wp.float32.
"""
if self._default_joint_pos_ta is None:
self._default_joint_pos_ta = ProxyArray(self._default_joint_pos)
return self._default_joint_pos_ta
@default_joint_pos.setter
def default_joint_pos(self, value: wp.array) -> None:
"""Set the default joint positions.
Args:
value: The default joint positions, shape (num_instances, num_joints).
Raises:
ValueError: If the articulation data is already primed.
"""
if self._is_primed:
raise ValueError("The articulation data is already primed.")
self._default_joint_pos.assign(value)
@property
def default_joint_vel(self) -> ProxyArray:
"""Default joint velocities of all joints [m/s or rad/s, depending on joint type].
Shape is (num_instances, num_joints), dtype = wp.float32.
"""
if self._default_joint_vel_ta is None:
self._default_joint_vel_ta = ProxyArray(self._default_joint_vel)
return self._default_joint_vel_ta
@default_joint_vel.setter
def default_joint_vel(self, value: wp.array) -> None:
"""Set the default joint velocities.
Args:
value: The default joint velocities, shape (num_instances, num_joints).
Raises:
ValueError: If the articulation data is already primed.
"""
if self._is_primed:
raise ValueError("The articulation data is already primed.")
self._default_joint_vel.assign(value)
"""
Joint commands -- Set into simulation.
"""
@property
def joint_pos_target(self) -> ProxyArray:
"""Joint position targets commanded by the user [m or rad, depending on joint type].
Shape is (num_instances, num_joints), dtype = wp.float32.
"""
if self._joint_pos_target_ta is None:
self._joint_pos_target_ta = ProxyArray(self._joint_pos_target)
return self._joint_pos_target_ta
@property
def joint_vel_target(self) -> ProxyArray:
"""Joint velocity targets commanded by the user [m/s or rad/s, depending on joint type].
Shape is (num_instances, num_joints), dtype = wp.float32.
"""
if self._joint_vel_target_ta is None:
self._joint_vel_target_ta = ProxyArray(self._joint_vel_target)
return self._joint_vel_target_ta
@property
def joint_effort_target(self) -> ProxyArray:
"""Joint effort targets commanded by the user [N or N*m, depending on joint type].
Shape is (num_instances, num_joints), dtype = wp.float32.
"""
if self._joint_effort_target_ta is None:
self._joint_effort_target_ta = ProxyArray(self._joint_effort_target)
return self._joint_effort_target_ta
"""
Joint commands -- Explicit actuators.
"""
@property
def computed_torque(self) -> ProxyArray:
"""Joint torques computed from the actuator model (before clipping) [N*m].
Shape is (num_instances, num_joints), dtype = wp.float32.
"""
if self._computed_torque_ta is None:
self._computed_torque_ta = ProxyArray(self._computed_torque)
return self._computed_torque_ta
@property
def applied_torque(self) -> ProxyArray:
"""Joint torques applied from the actuator model (after clipping) [N*m].
Shape is (num_instances, num_joints), dtype = wp.float32.
"""
if self._applied_torque_ta is None:
self._applied_torque_ta = ProxyArray(self._applied_torque)
return self._applied_torque_ta
"""
Joint properties
"""
@property
def joint_stiffness(self) -> ProxyArray:
"""Joint stiffness provided to the simulation [N*m/rad or N/m, depending on joint type].
Shape is (num_instances, num_joints), dtype = wp.float32.
Routed through pinned-host staging because ``DOF_STIFFNESS`` is a
CPU-only OVPhysX binding.
"""
self._read_joint_property_binding(TT.DOF_STIFFNESS, self._joint_stiffness, self._joint_stiffness_backend)
if self._joint_stiffness_ta is None:
self._joint_stiffness_ta = ProxyArray(self._joint_stiffness.data)
return self._joint_stiffness_ta
@property
def joint_damping(self) -> ProxyArray:
"""Joint damping provided to the simulation [N*m*s/rad or N*s/m, depending on joint type].
Shape is (num_instances, num_joints), dtype = wp.float32.
Routed through pinned-host staging because ``DOF_DAMPING`` is a
CPU-only OVPhysX binding.
"""
self._read_joint_property_binding(TT.DOF_DAMPING, self._joint_damping, self._joint_damping_backend)
if self._joint_damping_ta is None:
self._joint_damping_ta = ProxyArray(self._joint_damping.data)
return self._joint_damping_ta
@property
def joint_armature(self) -> ProxyArray:
"""Joint armature provided to the simulation [kg*m^2].
Shape is (num_instances, num_joints), dtype = wp.float32.
Routed through pinned-host staging because ``DOF_ARMATURE`` is a
CPU-only OVPhysX binding.
"""
self._read_joint_property_binding(TT.DOF_ARMATURE, self._joint_armature, self._joint_armature_backend)
if self._joint_armature_ta is None:
self._joint_armature_ta = ProxyArray(self._joint_armature.data)
return self._joint_armature_ta
@property
def joint_friction_coeff(self) -> ProxyArray:
"""Joint static friction coefficient [dimensionless].
Shape is (num_instances, num_joints), dtype = wp.float32.
Component ``[..., 0]`` of the ``DOF_FRICTION_PROPERTIES`` binding.
Routed through pinned-host staging because ``DOF_FRICTION_PROPERTIES``
is a CPU-only OVPhysX binding.
"""
self._read_joint_friction_binding()
if self._joint_friction_coeff_ta is None:
self._joint_friction_coeff_ta = ProxyArray(self._joint_friction_coeff)
return self._joint_friction_coeff_ta
@property
def joint_dynamic_friction_coeff(self) -> ProxyArray:
"""Joint dynamic friction coefficient [dimensionless].
Shape is (num_instances, num_joints), dtype = wp.float32.
Component ``[..., 1]`` of the ``DOF_FRICTION_PROPERTIES`` binding.
Routed through pinned-host staging because ``DOF_FRICTION_PROPERTIES``
is a CPU-only OVPhysX binding.
"""
self._read_joint_friction_binding()
if self._joint_dynamic_friction_coeff_ta is None:
self._joint_dynamic_friction_coeff_ta = ProxyArray(self._joint_dynamic_friction_coeff)
return self._joint_dynamic_friction_coeff_ta
@property
def joint_viscous_friction_coeff(self) -> ProxyArray:
"""Joint viscous friction coefficient [N*m*s/rad or N*s/m, depending on joint type].
Shape is (num_instances, num_joints), dtype = wp.float32.
Component ``[..., 2]`` of the ``DOF_FRICTION_PROPERTIES`` binding.
Routed through pinned-host staging because ``DOF_FRICTION_PROPERTIES``
is a CPU-only OVPhysX binding.
"""
self._read_joint_friction_binding()
if self._joint_viscous_friction_coeff_ta is None:
self._joint_viscous_friction_coeff_ta = ProxyArray(self._joint_viscous_friction_coeff)
return self._joint_viscous_friction_coeff_ta
@property
def joint_pos_limits(self) -> ProxyArray:
"""Joint position limits provided to the simulation [m or rad, depending on joint type].
Shape is (num_instances, num_joints), dtype = wp.vec2f.
In torch this resolves to (num_instances, num_joints, 2).
The limits are in the order :math:`[lower, upper]`. Routed through
pinned-host staging because ``DOF_LIMIT`` is a CPU-only OVPhysX binding.
"""
self._read_joint_property_binding(TT.DOF_LIMIT, self._joint_pos_limits, self._joint_pos_limits_backend)
if self._joint_pos_limits_ta is None:
self._joint_pos_limits_ta = ProxyArray(self._joint_pos_limits.data)
return self._joint_pos_limits_ta
@property
def joint_vel_limits(self) -> ProxyArray:
"""Joint maximum velocity provided to the simulation [m/s or rad/s, depending on joint type].
Shape is (num_instances, num_joints), dtype = wp.float32.
Routed through pinned-host staging because ``DOF_MAX_VELOCITY`` is a
CPU-only OVPhysX binding.
"""
self._read_joint_property_binding(TT.DOF_MAX_VELOCITY, self._joint_vel_limits, self._joint_vel_limits_backend)
if self._joint_vel_limits_ta is None:
self._joint_vel_limits_ta = ProxyArray(self._joint_vel_limits.data)
return self._joint_vel_limits_ta
@property
def joint_effort_limits(self) -> ProxyArray:
"""Joint maximum effort provided to the simulation [N or N*m, depending on joint type].
Shape is (num_instances, num_joints), dtype = wp.float32.
Routed through pinned-host staging because ``DOF_MAX_FORCE`` is a
CPU-only OVPhysX binding.
"""
self._read_joint_property_binding(
TT.DOF_MAX_FORCE, self._joint_effort_limits, self._joint_effort_limits_backend
)
if self._joint_effort_limits_ta is None:
self._joint_effort_limits_ta = ProxyArray(self._joint_effort_limits.data)
return self._joint_effort_limits_ta
"""
Joint properties - Custom.
"""
@property
def soft_joint_pos_limits(self) -> ProxyArray:
r"""Soft joint position limits for all joints [m or rad, depending on joint type].
Shape is (num_instances, num_joints), dtype = wp.vec2f.
In torch this resolves to (num_instances, num_joints, 2).
The limits are in the order :math:`[lower, upper]`.
"""
if self._soft_joint_pos_limits_ta is None:
self._soft_joint_pos_limits_ta = ProxyArray(self._soft_joint_pos_limits)
return self._soft_joint_pos_limits_ta
@property
def soft_joint_vel_limits(self) -> ProxyArray:
"""Soft joint velocity limits for all joints [m/s or rad/s, depending on joint type].
Shape is (num_instances, num_joints), dtype = wp.float32.
"""
if self._soft_joint_vel_limits_ta is None:
self._soft_joint_vel_limits_ta = ProxyArray(self._soft_joint_vel_limits)
return self._soft_joint_vel_limits_ta
@property
def gear_ratio(self) -> ProxyArray:
"""Gear ratio for relating motor torques to applied joint torques.
Shape is (num_instances, num_joints), dtype = wp.float32.
"""
if self._gear_ratio_ta is None:
self._gear_ratio_ta = ProxyArray(self._gear_ratio)
return self._gear_ratio_ta
"""
Fixed tendon properties.
"""
@property
def fixed_tendon_stiffness(self) -> ProxyArray:
"""Fixed-tendon stiffness gains [N*m/rad].
Shape is (num_instances, num_fixed_tendons), dtype = ``wp.float32``.
Routed through pinned-host staging (CPU-only binding).
"""
self._read_scalar_binding(TT.FIXED_TENDON_STIFFNESS, self._fixed_tendon_stiffness)
if self._fixed_tendon_stiffness_ta is None:
self._fixed_tendon_stiffness_ta = ProxyArray(self._fixed_tendon_stiffness.data)
return self._fixed_tendon_stiffness_ta
@property
def fixed_tendon_damping(self) -> ProxyArray:
"""Fixed-tendon damping coefficients [N*m*s/rad].
Shape is (num_instances, num_fixed_tendons), dtype = ``wp.float32``.
Routed through pinned-host staging (CPU-only binding).
"""
self._read_scalar_binding(TT.FIXED_TENDON_DAMPING, self._fixed_tendon_damping)
if self._fixed_tendon_damping_ta is None:
self._fixed_tendon_damping_ta = ProxyArray(self._fixed_tendon_damping.data)
return self._fixed_tendon_damping_ta
@property
def fixed_tendon_limit_stiffness(self) -> ProxyArray:
"""Fixed-tendon limit stiffness [N*m/rad].
Shape is (num_instances, num_fixed_tendons), dtype = ``wp.float32``.
Routed through pinned-host staging (CPU-only binding).
"""
self._read_scalar_binding(TT.FIXED_TENDON_LIMIT_STIFFNESS, self._fixed_tendon_limit_stiffness)
if self._fixed_tendon_limit_stiffness_ta is None:
self._fixed_tendon_limit_stiffness_ta = ProxyArray(self._fixed_tendon_limit_stiffness.data)
return self._fixed_tendon_limit_stiffness_ta
@property
def fixed_tendon_rest_length(self) -> ProxyArray:
"""Fixed-tendon rest lengths [m].
Shape is (num_instances, num_fixed_tendons), dtype = ``wp.float32``.
Routed through pinned-host staging (CPU-only binding).
"""
self._read_scalar_binding(TT.FIXED_TENDON_REST_LENGTH, self._fixed_tendon_rest_length)
if self._fixed_tendon_rest_length_ta is None:
self._fixed_tendon_rest_length_ta = ProxyArray(self._fixed_tendon_rest_length.data)
return self._fixed_tendon_rest_length_ta
@property
def fixed_tendon_offset(self) -> ProxyArray:
"""Fixed-tendon offsets [m].
Shape is (num_instances, num_fixed_tendons), dtype = ``wp.float32``.
Routed through pinned-host staging (CPU-only binding).
"""
self._read_scalar_binding(TT.FIXED_TENDON_OFFSET, self._fixed_tendon_offset)
if self._fixed_tendon_offset_ta is None:
self._fixed_tendon_offset_ta = ProxyArray(self._fixed_tendon_offset.data)
return self._fixed_tendon_offset_ta
@property
def fixed_tendon_pos_limits(self) -> ProxyArray:
"""Fixed tendon position limits provided to the simulation [m or rad].
Shape is (num_instances, num_fixed_tendons), dtype = ``wp.vec2f``.
In torch this resolves to (num_instances, num_fixed_tendons, 2).
.. deprecated::
Use :attr:`fixed_tendon_limit` (shape ``(N, T, 2)``, dtype
``wp.float32``) instead. This alias is kept for backwards
compatibility and reads the same underlying data.
"""
self._read_scalar_binding(TT.FIXED_TENDON_LIMIT, self._fixed_tendon_pos_limits)
if self._fixed_tendon_pos_limits_ta is None:
self._fixed_tendon_pos_limits_ta = ProxyArray(self._fixed_tendon_pos_limits.data)
return self._fixed_tendon_pos_limits_ta
"""
Spatial tendon properties.
"""
@property
def spatial_tendon_stiffness(self) -> ProxyArray:
"""Spatial-tendon stiffness gains [N/m].
Shape is (num_instances, num_spatial_tendons), dtype = ``wp.float32``.
Routed through pinned-host staging (CPU-only binding).
"""
self._read_scalar_binding(TT.SPATIAL_TENDON_STIFFNESS, self._spatial_tendon_stiffness)
if self._spatial_tendon_stiffness_ta is None:
self._spatial_tendon_stiffness_ta = ProxyArray(self._spatial_tendon_stiffness.data)
return self._spatial_tendon_stiffness_ta
@property
def spatial_tendon_damping(self) -> ProxyArray:
"""Spatial-tendon damping coefficients [N*s/m].
Shape is (num_instances, num_spatial_tendons), dtype = ``wp.float32``.
Routed through pinned-host staging (CPU-only binding).
"""
self._read_scalar_binding(TT.SPATIAL_TENDON_DAMPING, self._spatial_tendon_damping)
if self._spatial_tendon_damping_ta is None:
self._spatial_tendon_damping_ta = ProxyArray(self._spatial_tendon_damping.data)
return self._spatial_tendon_damping_ta
@property
def spatial_tendon_limit_stiffness(self) -> ProxyArray:
"""Spatial-tendon limit stiffness [N/m].
Shape is (num_instances, num_spatial_tendons), dtype = ``wp.float32``.
Routed through pinned-host staging (CPU-only binding).
"""
self._read_scalar_binding(TT.SPATIAL_TENDON_LIMIT_STIFFNESS, self._spatial_tendon_limit_stiffness)
if self._spatial_tendon_limit_stiffness_ta is None:
self._spatial_tendon_limit_stiffness_ta = ProxyArray(self._spatial_tendon_limit_stiffness.data)
return self._spatial_tendon_limit_stiffness_ta
@property
def spatial_tendon_offset(self) -> ProxyArray:
"""Spatial-tendon offsets [m].
Shape is (num_instances, num_spatial_tendons), dtype = ``wp.float32``.
Routed through pinned-host staging (CPU-only binding).
"""
self._read_scalar_binding(TT.SPATIAL_TENDON_OFFSET, self._spatial_tendon_offset)
if self._spatial_tendon_offset_ta is None:
self._spatial_tendon_offset_ta = ProxyArray(self._spatial_tendon_offset.data)
return self._spatial_tendon_offset_ta
"""
Root state properties.
"""
@property
def root_link_pose_w(self) -> ProxyArray:
"""Root link pose ``[pos, quat]`` in simulation world frame [m, -].
Shape is (num_instances,), dtype = wp.transformf.
In torch this resolves to (num_instances, 7).
This quantity is the pose of the articulation root's actor frame relative to the world.
The orientation is provided in (x, y, z, w) format.
"""
self._read_transform_binding(TT.ROOT_POSE, self._root_link_pose_w)
if self._root_link_pose_w_ta is None:
self._root_link_pose_w_ta = ProxyArray(self._root_link_pose_w.data)
return self._root_link_pose_w_ta
@property
def root_pose_w(self) -> ProxyArray:
"""Alias for :attr:`root_link_pose_w` matching Newton's convention.
Shape is (num_instances,), dtype = wp.transformf.
In torch this resolves to (num_instances, 7).
"""
return self.root_link_pose_w
@property
def root_link_vel_w(self) -> ProxyArray:
"""Root link velocity ``[lin_vel, ang_vel]`` in simulation world frame [m/s, rad/s].
Shape is (num_instances,), dtype = wp.spatial_vectorf.
In torch this resolves to (num_instances, 6).
This quantity contains the linear and angular velocities of the articulation root's actor frame
relative to the world.
"""
self._ensure_fk_fresh()
# ovphysx ROOT_VELOCITY is COM velocity; link velocity comes from the first
# element of the backend-order per-link velocity tensor.
if self.has_body_ordering:
backend_buffer = self._body_com_vel_w_backend
self._read_spatial_vector_binding(TT.LINK_VELOCITY, backend_buffer)
else:
backend_buffer = self._body_com_vel_w
self._read_spatial_vector_binding(TT.LINK_VELOCITY, backend_buffer)
if self._root_link_vel_w.timestamp < self._sim_timestamp:
self._read_launch_cache.launch(
"root_link_vel_w",
_copy_first_body,
dim=self.num_instances,
inputs=[backend_buffer.data],
outputs=[self._root_link_vel_w.data],
)
self._root_link_vel_w.timestamp = self._sim_timestamp
if self._root_link_vel_w_ta is None:
self._root_link_vel_w_ta = ProxyArray(self._root_link_vel_w.data)
return self._root_link_vel_w_ta
@property
def root_com_pose_w(self) -> ProxyArray:
"""Root center of mass pose ``[pos, quat]`` in simulation world frame [m, -].
Shape is (num_instances,), dtype = wp.transformf.
In torch this resolves to (num_instances, 7).
This quantity is the pose of the articulation root's center of mass frame relative to the world.
The orientation is provided in (x, y, z, w) format.
"""
if self._root_com_pose_w.timestamp < self._sim_timestamp:
self._read_launch_cache.launch(
"root_com_pose_w",
_compose_root_com_pose,
dim=self.num_instances,
inputs=[self.root_link_pose_w, self._backend_body_com_pose_b],
outputs=[self._root_com_pose_w.data],
)
self._root_com_pose_w.timestamp = self._sim_timestamp
if self._root_com_pose_w_ta is None:
self._root_com_pose_w_ta = ProxyArray(self._root_com_pose_w.data)
return self._root_com_pose_w_ta
@property
def root_com_vel_w(self) -> ProxyArray:
"""Root center of mass velocity ``[lin_vel, ang_vel]`` in simulation world frame [m/s, rad/s].
Shape is (num_instances,), dtype = wp.spatial_vectorf.
In torch this resolves to (num_instances, 6).
This quantity contains the linear and angular velocities of the articulation root's center of mass frame
relative to the world.
"""
self._read_spatial_vector_binding(TT.ROOT_VELOCITY, self._root_com_vel_w)
if self._root_com_vel_w_ta is None:
self._root_com_vel_w_ta = ProxyArray(self._root_com_vel_w.data)
return self._root_com_vel_w_ta
def _fetch_body_com_pose_b_backend(self, buf: TimestampedBuffer) -> None:
"""Read the current backend-order body COM pose from its binding when stale.
Backend fetch for the shared :meth:`_ensure_body_com_pose_b_current` /
:attr:`_backend_body_com_pose_b`. ``BODY_COM_POSE`` is a static binding, so this
stages it at most once per invalidation via
:meth:`_read_static_binding_into_buf`.
"""
self._read_static_binding_into_buf(TT.BODY_COM_POSE, buf)
@property
def _backend_body_link_pose_w(self) -> wp.array(dtype=wp.transformf, ndim=2):
"""Backend-order body link pose buffer for wrench composition.
Refreshes the world-frame link poses straight from the ``LINK_POSE`` binding in backend
body order, skipping the backend-to-public reorder that :attr:`body_link_pose_w` performs.
Used by wrench composition, which only needs each body's world orientation and position
(identical in either order for the same physical body), so it must not advance the public
:attr:`body_link_pose_w` shadow.
"""
self._ensure_fk_fresh()
if not self.has_body_ordering:
self._read_transform_binding(TT.LINK_POSE, self._body_link_pose_w)
return self._body_link_pose_w.data
backend_buffer = self._body_link_pose_w_backend
self._read_transform_binding(TT.LINK_POSE, backend_buffer)
return backend_buffer.data
"""
Body state properties.
"""
def _refresh_reordered_body_buffer(
self,
buf: TimestampedBuffer,
backend_buffer: TimestampedBuffer | None,
tensor_type: int,
*,
component_count: int | None = None,
) -> None:
"""Refresh a body buffer from its binding, gathering into public order under body ordering.
Under identity body ordering the binding is read straight into the public buffer;
otherwise it is staged in backend order and gathered into public order when the
public buffer is stale for the current step.
Args:
buf: Owned public-order buffer to refresh in place.
backend_buffer: Backend-order staging buffer used under body ordering.
tensor_type: ``TensorType`` key of the source binding.
component_count: Trailing components per body for a three-dimensional buffer,
or ``None`` for a two-dimensional buffer.
"""
if self.body_ordering is None:
self._read_binding_into_buf(tensor_type, buf)
return
if buf.timestamp >= self._sim_timestamp:
return
self._read_binding_into_buf(tensor_type, backend_buffer)
if component_count is None:
self._read_launch_cache.launch(
(id(buf), "body_2d"),
ordering_kernels.reorder_2d_backend_to_user,
dim=(self._num_instances, self._num_bodies),
inputs=[backend_buffer.data, self.body_ordering.user_to_backend],
outputs=[buf.data],
)
else:
self._read_launch_cache.launch(
(id(buf), "body_3d"),
ordering_kernels.reorder_3d_backend_to_user,
dim=(self._num_instances, self._num_bodies, component_count),
inputs=[backend_buffer.data, self.body_ordering.user_to_backend],
outputs=[buf.data],
)
buf.timestamp = backend_buffer.timestamp
@property
def body_mass(self) -> ProxyArray:
"""Body masses [kg].
Shape is (num_instances, num_bodies), dtype = ``wp.float32``.
Routed through pinned-host staging because the underlying OVPhysX
binding is CPU-only (``ARTICULATION_BODY_MASS``).
"""
self._refresh_reordered_body_buffer(self._body_mass, self._body_mass_backend, TT.BODY_MASS)
if self._body_mass_ta is None:
self._body_mass_ta = ProxyArray(self._body_mass.data)
return self._body_mass_ta
@property
def body_inertia(self) -> ProxyArray:
"""Body inertia tensors [kg*m^2].
Shape is (num_instances, num_bodies, 9), dtype = ``wp.float32``; the
trailing 9 is the row-major 3×3 inertia tensor.
Routed through pinned-host staging (``ARTICULATION_BODY_INERTIA`` is
a CPU-only binding).
"""
self._refresh_reordered_body_buffer(
self._body_inertia, self._body_inertia_backend, TT.BODY_INERTIA, component_count=9
)
if self._body_inertia_ta is None:
self._body_inertia_ta = ProxyArray(self._body_inertia.data)
return self._body_inertia_ta
@property
def body_link_pose_w(self) -> ProxyArray:
"""Body link pose ``[pos, quat]`` in simulation world frame [m, -].
Shape is (num_instances, num_bodies), dtype = wp.transformf.
In torch this resolves to (num_instances, num_bodies, 7).
This quantity is the pose of the articulation links' actor frame relative to the world.
The orientation is provided in (x, y, z, w) format.
"""
self._ensure_fk_fresh()
self._refresh_reordered_body_buffer(self._body_link_pose_w, self._body_link_pose_w_backend, TT.LINK_POSE)
if self._body_link_pose_w_ta is None:
self._body_link_pose_w_ta = ProxyArray(self._body_link_pose_w.data)
return self._body_link_pose_w_ta
@property
def body_com_vel_w(self) -> ProxyArray:
"""Body center of mass velocity ``[lin_vel, ang_vel]`` in simulation world frame [m/s, rad/s].
Shape is (num_instances, num_bodies), dtype = wp.spatial_vectorf.
In torch this resolves to (num_instances, num_bodies, 6).
"""
self._ensure_fk_fresh()
self._refresh_reordered_body_buffer(self._body_com_vel_w, self._body_com_vel_w_backend, TT.LINK_VELOCITY)
if self._body_com_vel_w_ta is None:
self._body_com_vel_w_ta = ProxyArray(self._body_com_vel_w.data)
return self._body_com_vel_w_ta
@property
def body_link_vel_w(self) -> ProxyArray:
"""Body link velocity ``[lin_vel, ang_vel]`` in simulation world frame [m/s, rad/s].
Shape is (num_instances, num_bodies), dtype = wp.spatial_vectorf.
In torch this resolves to (num_instances, num_bodies, 6).
Derived from :attr:`body_com_vel_w` and :attr:`body_com_pose_b` via
:func:`~isaaclab_ov.assets.kernels.get_body_link_vel_from_body_com_vel`.
"""
if self._body_link_vel_w.timestamp >= self._sim_timestamp:
if self._body_link_vel_w_ta is None:
self._body_link_vel_w_ta = ProxyArray(self._body_link_vel_w.data)
return self._body_link_vel_w_ta
_ = self.body_com_vel_w
_ = self.body_link_pose_w
_ = self.body_com_pose_b
self._read_launch_cache.launch(
"body_link_vel_w",
get_body_link_vel_from_body_com_vel,
dim=(self.num_instances, self.num_bodies),
inputs=[self._body_com_vel_w.data, self._body_link_pose_w.data, self._body_com_pose_b.data],
outputs=[self._body_link_vel_w.data],
)
self._body_link_vel_w.timestamp = self._sim_timestamp
if self._body_link_vel_w_ta is None:
self._body_link_vel_w_ta = ProxyArray(self._body_link_vel_w.data)
return self._body_link_vel_w_ta
@property
def body_com_pose_w(self) -> ProxyArray:
"""Body center of mass pose ``[pos, quat]`` in simulation world frame [m, -].
Shape is (num_instances, num_bodies), dtype = wp.transformf.
In torch this resolves to (num_instances, num_bodies, 7).
Derived from :attr:`body_link_pose_w` and :attr:`body_com_pose_b` via
:func:`~isaaclab_ov.assets.kernels.get_body_com_pose_from_body_link_pose`.
The orientation is provided in (x, y, z, w) format.
"""
if self._body_com_pose_w.timestamp >= self._sim_timestamp:
if self._body_com_pose_w_ta is None:
self._body_com_pose_w_ta = ProxyArray(self._body_com_pose_w.data)
return self._body_com_pose_w_ta
_ = self.body_link_pose_w
_ = self.body_com_pose_b
self._read_launch_cache.launch(
"body_com_pose_w",
get_body_com_pose_from_body_link_pose,
dim=(self.num_instances, self.num_bodies),
inputs=[self._body_link_pose_w.data, self._body_com_pose_b.data],
outputs=[self._body_com_pose_w.data],
)
self._body_com_pose_w.timestamp = self._sim_timestamp
if self._body_com_pose_w_ta is None:
self._body_com_pose_w_ta = ProxyArray(self._body_com_pose_w.data)
return self._body_com_pose_w_ta
@property
def body_com_acc_w(self) -> ProxyArray:
"""Acceleration of all bodies center of mass ``[lin_acc, ang_acc]`` [m/s^2, rad/s^2].
Shape is (num_instances, num_bodies), dtype = wp.spatial_vectorf.
In torch this resolves to (num_instances, num_bodies, 6).
All values are relative to the world.
"""
self._refresh_reordered_body_buffer(self._body_com_acc_w, self._body_com_acc_w_backend, TT.LINK_ACCELERATION)
if self._body_com_acc_w_ta is None:
self._body_com_acc_w_ta = ProxyArray(self._body_com_acc_w.data)
return self._body_com_acc_w_ta
@property
def body_com_pose_b(self) -> ProxyArray:
"""Center of mass pose ``[pos, quat]`` of all bodies in their respective body's link frames [m, -].
Shape is (num_instances, num_bodies), dtype = wp.transformf.
In torch this resolves to (num_instances, num_bodies, 7).
This quantity is the pose of the center of mass frame of the rigid body relative to the body's link frame.
The orientation is provided in (x, y, z, w) format.
"""
self._ensure_body_com_pose_b_current()
if self._body_com_pose_b_ta is None:
self._body_com_pose_b_ta = ProxyArray(self._body_com_pose_b.data)
return self._body_com_pose_b_ta
"""
Dynamics quantities (task-space controllers).
"""
@property
def body_com_jacobian_w(self) -> ProxyArray:
"""See :attr:`isaaclab.assets.BaseArticulationData.body_com_jacobian_w`."""
if self._body_com_jacobian_w.timestamp < self._sim_timestamp:
has_body_ordering = self.has_body_ordering
has_joint_ordering = self.has_joint_ordering
if has_body_ordering or has_joint_ordering or self._has_reversed_joints:
self._binding_read(TT.JACOBIAN, self._body_com_jacobian_w_backend)
self._read_launch_cache.launch(
"body_com_jacobian_w",
ordering_kernels.reorder_jacobian_backend_to_user,
dim=self._body_com_jacobian_w.data.shape,
inputs=[
self._body_com_jacobian_w_backend,
self._jacobian_body_user_to_backend,
self._jacobian_joint_user_to_backend,
self._joint_dof_signs,
self._num_base_dofs,
has_body_ordering,
has_joint_ordering,
],
outputs=[self._body_com_jacobian_w.data],
)
else:
self._binding_read(TT.JACOBIAN, self._body_com_jacobian_w.data)
self._body_com_jacobian_w.timestamp = self._sim_timestamp
return self._body_com_jacobian_w_ta
@property
def body_link_jacobian_w(self) -> ProxyArray:
"""See :attr:`isaaclab.assets.BaseArticulationData.body_link_jacobian_w`."""
self._read_launch_cache.launch(
"body_link_jacobian_w",
articulation_kernels.shift_jacobian_com_to_origin,
dim=self._body_link_jacobian_w.shape[:2] + (self._body_link_jacobian_w.shape[3],),
inputs=[
self.body_link_pose_w.warp,
self.body_com_pos_b.warp,
self._jacobian_link_offset,
self.body_com_jacobian_w.warp,
],
outputs=[self._body_link_jacobian_w],
)
return self._body_link_jacobian_w_ta
def _refresh_generalized_dynamics_buffer(
self, buffer: TimestampedBuffer, backend_buffer: wp.array, tensor_type: int, reorder_kernel: wp.Kernel
) -> None:
"""Refresh a generalized dynamics buffer and gather its joint axes when needed."""
if buffer.timestamp >= self._sim_timestamp:
return
if self.has_joint_ordering or self._has_reversed_joints:
self._binding_read(tensor_type, backend_buffer)
self._read_launch_cache.launch(
(id(buffer), "generalized_dynamics"),
reorder_kernel,
dim=buffer.data.shape,
inputs=[
backend_buffer,
self._jacobian_joint_user_to_backend,
self._joint_dof_signs,
self._num_base_dofs,
self.has_joint_ordering,
],
outputs=[buffer.data],
)
else:
self._binding_read(tensor_type, buffer.data)
buffer.timestamp = self._sim_timestamp
@property
def mass_matrix(self) -> ProxyArray:
"""See :attr:`isaaclab.assets.BaseArticulationData.mass_matrix`."""
self._refresh_generalized_dynamics_buffer(
self._mass_matrix,
self._mass_matrix_backend,
TT.MASS_MATRIX,
ordering_kernels.reorder_mass_matrix_backend_to_user,
)
return self._mass_matrix_ta
@property
def gravity_compensation_forces(self) -> ProxyArray:
"""See :attr:`isaaclab.assets.BaseArticulationData.gravity_compensation_forces`."""
self._refresh_generalized_dynamics_buffer(
self._gravity_compensation_forces,
self._gravity_compensation_forces_backend,
TT.GRAVITY_FORCE,
ordering_kernels.reorder_generalized_vector_backend_to_user,
)
return self._gravity_compensation_forces_ta
"""
Joint state properties.
"""
@property
def joint_pos(self) -> ProxyArray:
"""Joint positions of all joints [m or rad, depending on joint type].
Shape is (num_instances, num_joints), dtype = wp.float32.
"""
self._refresh_joint_pos()
if self._joint_pos_ta is None:
self._joint_pos_ta = ProxyArray(self._joint_pos_buf.data)
return self._joint_pos_ta
def _refresh_joint_state_user(
self,
user_buffer: TimestampedBuffer,
backend_buffer: TimestampedBuffer | None,
tensor_type: int,
) -> None:
"""Refresh public and backend-order joint-state buffers when stale."""
if not self.has_joint_ordering:
self._read_binding_into_buf(tensor_type, user_buffer)
return
self._read_binding_into_buf(tensor_type, backend_buffer)
if user_buffer.timestamp < backend_buffer.timestamp:
self._read_launch_cache.launch(
(id(user_buffer), "joint_state"),
ordering_kernels.reorder_2d_backend_to_user,
dim=(self.num_instances, self.num_joints),
inputs=[backend_buffer.data, self.joint_ordering.user_to_backend],
outputs=[user_buffer.data],
)
user_buffer.timestamp = backend_buffer.timestamp
def _get_joint_state_write_buffer(
self,
user_buffer: TimestampedBuffer,
backend_buffer: TimestampedBuffer | None,
tensor_type: int,
require_current: bool,
) -> wp.array:
"""Return complete backend-order joint-state rows used by OVPhysX setters."""
if require_current:
self._refresh_joint_state_user(user_buffer, backend_buffer, tensor_type)
if not self.has_joint_ordering:
return user_buffer.data
return backend_buffer.data
def _refresh_joint_pos(self) -> None:
"""Refresh public and backend-order joint-position buffers when stale."""
self._refresh_joint_state_user(self._joint_pos_buf, self._joint_pos_backend, TT.DOF_POSITION)
def _get_joint_pos_write_buffer(self, require_current: bool) -> wp.array:
"""Return the complete backend-order position rows used by OVPhysX setters."""
return self._get_joint_state_write_buffer(
self._joint_pos_buf, self._joint_pos_backend, TT.DOF_POSITION, require_current
)
@property
def joint_vel(self) -> ProxyArray:
"""Joint velocities of all joints [m/s or rad/s, depending on joint type].
Shape is (num_instances, num_joints), dtype = wp.float32.
"""
self._refresh_joint_vel()
if self._joint_vel_ta is None:
self._joint_vel_ta = ProxyArray(self._joint_vel_buf.data)
return self._joint_vel_ta
def _refresh_joint_vel(self) -> None:
"""Refresh public and backend-order joint-velocity buffers when stale."""
self._refresh_joint_state_user(self._joint_vel_buf, self._joint_vel_backend, TT.DOF_VELOCITY)
def _get_joint_vel_write_buffer(self, require_current: bool) -> wp.array:
"""Return the complete backend-order velocity rows used by OVPhysX setters."""
return self._get_joint_state_write_buffer(
self._joint_vel_buf, self._joint_vel_backend, TT.DOF_VELOCITY, require_current
)
@property
def joint_acc(self) -> ProxyArray:
"""Joint acceleration of all joints [m/s^2 or rad/s^2, depending on joint type].
Shape is (num_instances, num_joints), dtype = wp.float32.
.. note::
This quantity is computed via finite differencing of joint velocities. It is recomputed
lazily: a read after one or more :meth:`update` steps refreshes it, while a read after a
manual joint-velocity write returns ``0`` until the next step, because the write resets
the finite-difference baseline.
"""
if self._joint_acc.timestamp < self._sim_timestamp:
# Finite-difference the joint velocities. The FD kernel also advances
# ``_previous_joint_vel`` in place, so no separate copy is needed.
time_elapsed = self._sim_timestamp - self._joint_acc.timestamp
if self.joint_ordering is not None:
# Fuse the backend-to-public reorder into the finite difference: read the
# backend-order velocity source directly and map it to public order inside the
# kernel, saving the separate reorder launch that ``joint_vel`` would run.
# ``_previous_joint_vel`` stays in public order to match the joint-velocity
# write path, which resets the finite-difference baseline in public order.
self._read_binding_into_buf(TT.DOF_VELOCITY, self._joint_vel_backend)
wp.launch(
_fd_joint_acc_ordered,
dim=(self._num_instances, self._num_joints),
inputs=[
self._joint_vel_backend.data,
self.joint_ordering.user_to_backend,
self._previous_joint_vel,
1.0 / time_elapsed,
],
outputs=[self._joint_acc.data],
device=self.device,
)
else:
joint_vel = self.joint_vel.warp
wp.launch(
_fd_joint_acc,
dim=(self._num_instances, self._num_joints),
inputs=[joint_vel, self._previous_joint_vel, 1.0 / time_elapsed],
outputs=[self._joint_acc.data],
device=self.device,
)
self._joint_acc.timestamp = self._sim_timestamp
if self._joint_acc_ta is None:
self._joint_acc_ta = ProxyArray(self._joint_acc.data)
return self._joint_acc_ta
"""
Derived Properties.
"""
@property
def projected_gravity_b(self) -> ProxyArray:
"""Projection of the gravity direction on base frame.
Shape is (num_instances,), dtype = wp.vec3f. In torch this resolves to (num_instances, 3).
"""
if self._projected_gravity_b.timestamp < self._sim_timestamp:
self._read_launch_cache.launch(
"projected_gravity_b",
_projected_gravity,
dim=self.num_instances,
inputs=[self.GRAVITY_VEC_W, self.root_link_pose_w],
outputs=[self._projected_gravity_b.data],
)
self._projected_gravity_b.timestamp = self._sim_timestamp
if self._projected_gravity_b_ta is None:
self._projected_gravity_b_ta = ProxyArray(self._projected_gravity_b.data)
return self._projected_gravity_b_ta
@property
def heading_w(self) -> ProxyArray:
"""Yaw heading of the base frame (in radians) [rad].
Shape is (num_instances,), dtype = wp.float32.
.. note::
This quantity is computed by assuming that the forward-direction of the base
frame is along x-direction, i.e. :math:`(1, 0, 0)`.
"""
if self._heading_w.timestamp < self._sim_timestamp:
self._read_launch_cache.launch(
"heading_w",
_compute_heading,
dim=self.num_instances,
inputs=[self.FORWARD_VEC_B, self.root_link_pose_w],
outputs=[self._heading_w.data],
)
self._heading_w.timestamp = self._sim_timestamp
if self._heading_w_ta is None:
self._heading_w_ta = ProxyArray(self._heading_w.data)
return self._heading_w_ta
@property
def root_link_lin_vel_b(self) -> ProxyArray:
"""Root link linear velocity in base frame [m/s].
Shape is (num_instances,), dtype = wp.vec3f. In torch this resolves to (num_instances, 3).
This quantity is the linear velocity of the articulation root's actor frame with respect to its actor frame.
"""
if self._root_link_lin_vel_b.timestamp < self._sim_timestamp:
self._read_launch_cache.launch(
"root_link_lin_vel_b",
_world_vel_to_body_lin,
dim=self.num_instances,
inputs=[self.root_link_pose_w, self.root_link_vel_w],
outputs=[self._root_link_lin_vel_b.data],
)
self._root_link_lin_vel_b.timestamp = self._sim_timestamp
if self._root_link_lin_vel_b_ta is None:
self._root_link_lin_vel_b_ta = ProxyArray(self._root_link_lin_vel_b.data)
return self._root_link_lin_vel_b_ta
@property
def root_link_ang_vel_b(self) -> ProxyArray:
"""Root link angular velocity in base frame [rad/s].
Shape is (num_instances,), dtype = wp.vec3f. In torch this resolves to (num_instances, 3).
This quantity is the angular velocity of the articulation root's actor frame with respect to its actor frame.
"""
if self._root_link_ang_vel_b.timestamp < self._sim_timestamp:
self._read_launch_cache.launch(
"root_link_ang_vel_b",
_world_vel_to_body_ang,
dim=self.num_instances,
inputs=[self.root_link_pose_w, self.root_link_vel_w],
outputs=[self._root_link_ang_vel_b.data],
)
self._root_link_ang_vel_b.timestamp = self._sim_timestamp
if self._root_link_ang_vel_b_ta is None:
self._root_link_ang_vel_b_ta = ProxyArray(self._root_link_ang_vel_b.data)
return self._root_link_ang_vel_b_ta
@property
def root_com_lin_vel_b(self) -> ProxyArray:
"""Root center of mass linear velocity in base frame [m/s].
Shape is (num_instances,), dtype = wp.vec3f. In torch this resolves to (num_instances, 3).
This quantity is the linear velocity of the articulation root's center of mass frame
with respect to its actor frame.
"""
if self._root_com_lin_vel_b.timestamp < self._sim_timestamp:
self._read_launch_cache.launch(
"root_com_lin_vel_b",
_world_vel_to_body_lin,
dim=self.num_instances,
inputs=[self.root_link_pose_w, self.root_com_vel_w],
outputs=[self._root_com_lin_vel_b.data],
)
self._root_com_lin_vel_b.timestamp = self._sim_timestamp
if self._root_com_lin_vel_b_ta is None:
self._root_com_lin_vel_b_ta = ProxyArray(self._root_com_lin_vel_b.data)
return self._root_com_lin_vel_b_ta
@property
def root_com_ang_vel_b(self) -> ProxyArray:
"""Root center of mass angular velocity in base frame [rad/s].
Shape is (num_instances,), dtype = wp.vec3f. In torch this resolves to (num_instances, 3).
This quantity is the angular velocity of the articulation root's center of mass frame
with respect to its actor frame.
"""
if self._root_com_ang_vel_b.timestamp < self._sim_timestamp:
self._read_launch_cache.launch(
"root_com_ang_vel_b",
_world_vel_to_body_ang,
dim=self.num_instances,
inputs=[self.root_link_pose_w, self.root_com_vel_w],
outputs=[self._root_com_ang_vel_b.data],
)
self._root_com_ang_vel_b.timestamp = self._sim_timestamp
if self._root_com_ang_vel_b_ta is None:
self._root_com_ang_vel_b_ta = ProxyArray(self._root_com_ang_vel_b.data)
return self._root_com_ang_vel_b_ta
"""
Sliced properties.
"""
@property
def root_link_pos_w(self) -> ProxyArray:
"""Root link position in simulation world frame [m].
Shape is (num_instances,), dtype = wp.vec3f. In torch this resolves to (num_instances, 3).
"""
parent = self.root_link_pose_w
if self._root_link_pos_w_ta is None:
self._root_link_pos_w_ta = ProxyArray(self._get_pos_from_transform(parent.warp))
return self._root_link_pos_w_ta
@property
def root_link_quat_w(self) -> ProxyArray:
"""Root link orientation (x, y, z, w) in simulation world frame.
Shape is (num_instances,), dtype = wp.quatf. In torch this resolves to (num_instances, 4).
"""
parent = self.root_link_pose_w
if self._root_link_quat_w_ta is None:
self._root_link_quat_w_ta = ProxyArray(self._get_quat_from_transform(parent.warp))
return self._root_link_quat_w_ta
@property
def root_link_lin_vel_w(self) -> ProxyArray:
"""Root link linear velocity in simulation world frame [m/s].
Shape is (num_instances,), dtype = wp.vec3f. In torch this resolves to (num_instances, 3).
"""
parent = self.root_link_vel_w
if self._root_link_lin_vel_w_ta is None:
self._root_link_lin_vel_w_ta = ProxyArray(self._get_lin_vel_from_spatial_vector(parent.warp))
return self._root_link_lin_vel_w_ta
@property
def root_link_ang_vel_w(self) -> ProxyArray:
"""Root link angular velocity in simulation world frame [rad/s].
Shape is (num_instances,), dtype = wp.vec3f. In torch this resolves to (num_instances, 3).
"""
parent = self.root_link_vel_w
if self._root_link_ang_vel_w_ta is None:
self._root_link_ang_vel_w_ta = ProxyArray(self._get_ang_vel_from_spatial_vector(parent.warp))
return self._root_link_ang_vel_w_ta
@property
def root_com_pos_w(self) -> ProxyArray:
"""Root center of mass position in simulation world frame [m].
Shape is (num_instances,), dtype = wp.vec3f. In torch this resolves to (num_instances, 3).
"""
parent = self.root_com_pose_w
if self._root_com_pos_w_ta is None:
self._root_com_pos_w_ta = ProxyArray(self._get_pos_from_transform(parent.warp))
return self._root_com_pos_w_ta
@property
def root_com_quat_w(self) -> ProxyArray:
"""Root center of mass orientation (x, y, z, w) in simulation world frame.
Shape is (num_instances,), dtype = wp.quatf. In torch this resolves to (num_instances, 4).
"""
parent = self.root_com_pose_w
if self._root_com_quat_w_ta is None:
self._root_com_quat_w_ta = ProxyArray(self._get_quat_from_transform(parent.warp))
return self._root_com_quat_w_ta
@property
def root_com_lin_vel_w(self) -> ProxyArray:
"""Root center of mass linear velocity in simulation world frame [m/s].
Shape is (num_instances,), dtype = wp.vec3f. In torch this resolves to (num_instances, 3).
"""
parent = self.root_com_vel_w
if self._root_com_lin_vel_w_ta is None:
self._root_com_lin_vel_w_ta = ProxyArray(self._get_lin_vel_from_spatial_vector(parent.warp))
return self._root_com_lin_vel_w_ta
@property
def root_com_ang_vel_w(self) -> ProxyArray:
"""Root center of mass angular velocity in simulation world frame [rad/s].
Shape is (num_instances,), dtype = wp.vec3f. In torch this resolves to (num_instances, 3).
"""
parent = self.root_com_vel_w
if self._root_com_ang_vel_w_ta is None:
self._root_com_ang_vel_w_ta = ProxyArray(self._get_ang_vel_from_spatial_vector(parent.warp))
return self._root_com_ang_vel_w_ta
@property
def body_link_pos_w(self) -> ProxyArray:
"""Positions of all bodies in simulation world frame [m].
Shape is (num_instances, num_bodies), dtype = wp.vec3f.
In torch this resolves to (num_instances, num_bodies, 3).
"""
parent = self.body_link_pose_w
if self._body_link_pos_w_ta is None:
self._body_link_pos_w_ta = ProxyArray(self._get_pos_from_transform(parent.warp))
return self._body_link_pos_w_ta
@property
def body_link_quat_w(self) -> ProxyArray:
"""Orientation (x, y, z, w) of all bodies in simulation world frame.
Shape is (num_instances, num_bodies), dtype = wp.quatf.
In torch this resolves to (num_instances, num_bodies, 4).
"""
parent = self.body_link_pose_w
if self._body_link_quat_w_ta is None:
self._body_link_quat_w_ta = ProxyArray(self._get_quat_from_transform(parent.warp))
return self._body_link_quat_w_ta
@property
def body_link_lin_vel_w(self) -> ProxyArray:
"""Linear velocity of all bodies in simulation world frame [m/s].
Shape is (num_instances, num_bodies), dtype = wp.vec3f.
In torch this resolves to (num_instances, num_bodies, 3).
"""
parent = self.body_link_vel_w
if self._body_link_lin_vel_w_ta is None:
self._body_link_lin_vel_w_ta = ProxyArray(self._get_lin_vel_from_spatial_vector(parent.warp))
return self._body_link_lin_vel_w_ta
@property
def body_link_ang_vel_w(self) -> ProxyArray:
"""Angular velocity of all bodies in simulation world frame [rad/s].
Shape is (num_instances, num_bodies), dtype = wp.vec3f.
In torch this resolves to (num_instances, num_bodies, 3).
"""
parent = self.body_link_vel_w
if self._body_link_ang_vel_w_ta is None:
self._body_link_ang_vel_w_ta = ProxyArray(self._get_ang_vel_from_spatial_vector(parent.warp))
return self._body_link_ang_vel_w_ta
@property
def body_com_pos_w(self) -> ProxyArray:
"""Positions of all bodies' center of mass in simulation world frame [m].
Shape is (num_instances, num_bodies), dtype = wp.vec3f.
In torch this resolves to (num_instances, num_bodies, 3).
"""
parent = self.body_com_pose_w
if self._body_com_pos_w_ta is None:
self._body_com_pos_w_ta = ProxyArray(self._get_pos_from_transform(parent.warp))
return self._body_com_pos_w_ta
@property
def body_com_quat_w(self) -> ProxyArray:
"""Orientation (x, y, z, w) of the principal axes of inertia of all bodies in simulation world frame.
Shape is (num_instances, num_bodies), dtype = wp.quatf.
In torch this resolves to (num_instances, num_bodies, 4).
"""
parent = self.body_com_pose_w
if self._body_com_quat_w_ta is None:
self._body_com_quat_w_ta = ProxyArray(self._get_quat_from_transform(parent.warp))
return self._body_com_quat_w_ta
@property
def body_com_lin_vel_w(self) -> ProxyArray:
"""Linear velocity of all bodies in simulation world frame [m/s].
Shape is (num_instances, num_bodies), dtype = wp.vec3f.
In torch this resolves to (num_instances, num_bodies, 3).
"""
parent = self.body_com_vel_w
if self._body_com_lin_vel_w_ta is None:
self._body_com_lin_vel_w_ta = ProxyArray(self._get_lin_vel_from_spatial_vector(parent.warp))
return self._body_com_lin_vel_w_ta
@property
def body_com_ang_vel_w(self) -> ProxyArray:
"""Angular velocity of all bodies in simulation world frame [rad/s].
Shape is (num_instances, num_bodies), dtype = wp.vec3f.
In torch this resolves to (num_instances, num_bodies, 3).
"""
parent = self.body_com_vel_w
if self._body_com_ang_vel_w_ta is None:
self._body_com_ang_vel_w_ta = ProxyArray(self._get_ang_vel_from_spatial_vector(parent.warp))
return self._body_com_ang_vel_w_ta
@property
def body_com_lin_acc_w(self) -> ProxyArray:
"""Linear acceleration of all bodies in simulation world frame [m/s^2].
Shape is (num_instances, num_bodies), dtype = wp.vec3f.
In torch this resolves to (num_instances, num_bodies, 3).
"""
parent = self.body_com_acc_w
if self._body_com_lin_acc_w_ta is None:
self._body_com_lin_acc_w_ta = ProxyArray(self._get_lin_vel_from_spatial_vector(parent.warp))
return self._body_com_lin_acc_w_ta
@property
def body_com_ang_acc_w(self) -> ProxyArray:
"""Angular acceleration of all bodies in simulation world frame [rad/s^2].
Shape is (num_instances, num_bodies), dtype = wp.vec3f.
In torch this resolves to (num_instances, num_bodies, 3).
"""
parent = self.body_com_acc_w
if self._body_com_ang_acc_w_ta is None:
self._body_com_ang_acc_w_ta = ProxyArray(self._get_ang_vel_from_spatial_vector(parent.warp))
return self._body_com_ang_acc_w_ta
@property
def body_com_pos_b(self) -> ProxyArray:
"""Center of mass position of all of the bodies in their respective link frames [m].
Shape is (num_instances, num_bodies), dtype = wp.vec3f.
In torch this resolves to (num_instances, num_bodies, 3).
"""
parent = self.body_com_pose_b
if self._body_com_pos_b_ta is None:
self._body_com_pos_b_ta = ProxyArray(self._get_pos_from_transform(parent.warp))
return self._body_com_pos_b_ta
@property
def body_com_quat_b(self) -> ProxyArray:
"""Orientation (x, y, z, w) of the principal axes of inertia of all of the bodies in their
respective link frames.
Shape is (num_instances, num_bodies), dtype = wp.quatf.
In torch this resolves to (num_instances, num_bodies, 4).
"""
parent = self.body_com_pose_b
if self._body_com_quat_b_ta is None:
self._body_com_quat_b_ta = ProxyArray(self._get_quat_from_transform(parent.warp))
return self._body_com_quat_b_ta
"""
Internal helpers.
"""
def _create_buffers(self) -> None: # noqa: C901
"""Allocate core buffers and defer optional nonidentity joint/body-ordering staging."""
super()._create_buffers()
N = self._num_instances
D = self._num_joints
L = self._num_bodies
dev = self.device
# -- Root state buffers
self._root_link_pose_w = TimestampedBuffer(N, dev, wp.transformf)
self._root_link_vel_w = TimestampedBuffer(N, dev, wp.spatial_vectorf)
self._root_com_pose_w = TimestampedBuffer(N, dev, wp.transformf)
self._root_com_vel_w = TimestampedBuffer(N, dev, wp.spatial_vectorf)
# -- Body state buffers
self._body_link_pose_w = TimestampedBuffer((N, L), dev, wp.transformf)
self._body_link_pose_w_backend: TimestampedBuffer | None = None
self._body_link_vel_w = TimestampedBuffer((N, L), dev, wp.spatial_vectorf)
self._body_com_pose_b = TimestampedBuffer((N, L), dev, wp.transformf)
self._body_com_pose_b_backend: TimestampedBuffer | None = None
self._body_com_pose_w = TimestampedBuffer((N, L), dev, wp.transformf)
self._body_com_vel_w = TimestampedBuffer((N, L), dev, wp.spatial_vectorf)
self._body_com_vel_w_backend: TimestampedBuffer | None = None
self._body_com_acc_w = TimestampedBuffer((N, L), dev, wp.spatial_vectorf)
self._body_com_acc_w_backend: TimestampedBuffer | None = None
# -- Joint state buffers
self._joint_pos_buf = TimestampedBuffer((N, D), dev, wp.float32)
self._joint_pos_backend: TimestampedBuffer | None = None
self._joint_vel_buf = TimestampedBuffer((N, D), dev, wp.float32)
self._joint_vel_backend: TimestampedBuffer | None = None
self._joint_acc = TimestampedBuffer((N, D), dev, wp.float32)
self._previous_joint_vel = wp.zeros((N, D), dtype=wp.float32, device=dev)
# -- Dynamics quantities for task-space controllers
self._jacobian_link_offset = 1 if self._view.is_fixed_base else 0
self._num_base_dofs = 0 if self._view.is_fixed_base else 6
num_jacobian_bodies = L - self._jacobian_link_offset
num_generalized_dofs = D + self._num_base_dofs
jacobian_shape = (N, num_jacobian_bodies, 6, num_generalized_dofs)
mass_matrix_shape = (N, num_generalized_dofs, num_generalized_dofs)
gravity_shape = (N, num_generalized_dofs)
self._jacobian_body_user_to_backend = self._make_jacobian_body_user_to_backend()
self._jacobian_joint_user_to_backend = wp.array(range(D), dtype=wp.int32, device=dev)
self._body_com_jacobian_w = TimestampedBuffer(jacobian_shape, dev, wp.float32)
self._body_com_jacobian_w_backend = wp.zeros(jacobian_shape, dtype=wp.float32, device=dev)
self._body_link_jacobian_w = wp.zeros(jacobian_shape, dtype=wp.float32, device=dev)
self._mass_matrix = TimestampedBuffer(mass_matrix_shape, dev, wp.float32)
self._mass_matrix_backend = wp.zeros(mass_matrix_shape, dtype=wp.float32, device=dev)
self._gravity_compensation_forces = TimestampedBuffer(gravity_shape, dev, wp.float32)
self._gravity_compensation_forces_backend = wp.zeros(gravity_shape, dtype=wp.float32, device=dev)
# -- Joint properties (CPU-only; timestamped so they can be re-read after writes)
self._joint_stiffness = TimestampedBuffer((N, D), dev, wp.float32)
self._joint_damping = TimestampedBuffer((N, D), dev, wp.float32)
self._joint_armature = TimestampedBuffer((N, D), dev, wp.float32)
self._joint_pos_limits = TimestampedBuffer((N, D), dev, wp.vec2f)
self._joint_vel_limits = TimestampedBuffer((N, D), dev, wp.float32)
self._joint_effort_limits = TimestampedBuffer((N, D), dev, wp.float32)
self._joint_stiffness_backend: TimestampedBuffer | None = None
self._joint_damping_backend: TimestampedBuffer | None = None
self._joint_armature_backend: TimestampedBuffer | None = None
self._joint_pos_limits_backend: TimestampedBuffer | None = None
self._joint_vel_limits_backend: TimestampedBuffer | None = None
self._joint_effort_limits_backend: TimestampedBuffer | None = None
# Friction: single (N, D, 3) TimestampedBuffer; per-component views are created lazily.
self._joint_friction_props_buf = TimestampedBuffer((N, D, 3), dev, wp.float32)
self._joint_friction_props_backend: TimestampedBuffer | None = None
# These are strided wp.array views into _joint_friction_props_buf.data; created in
# _pin_proxy_arrays after the buffer exists.
self._joint_friction_coeff: wp.array | None = None
self._joint_dynamic_friction_coeff: wp.array | None = None
self._joint_viscous_friction_coeff: wp.array | None = None
# -- Body properties (CPU-only; read once at init, re-read via _read_scalar_binding)
self._body_mass = TimestampedBuffer((N, L), dev, wp.float32)
self._body_mass_backend: TimestampedBuffer | None = None
self._body_inertia = TimestampedBuffer((N, L, 9), dev, wp.float32)
self._body_inertia_backend: TimestampedBuffer | None = None
# -- Soft limits / custom joint properties
self._soft_joint_pos_limits = wp.zeros((N, D), dtype=wp.vec2f, device=dev)
self._soft_joint_vel_limits = wp.zeros((N, D), dtype=wp.float32, device=dev)
self._gear_ratio = wp.ones((N, D), dtype=wp.float32, device=dev)
# -- Command buffers
self._joint_pos_target = wp.zeros((N, D), dtype=wp.float32, device=dev)
self._joint_vel_target = wp.zeros((N, D), dtype=wp.float32, device=dev)
self._joint_effort_target = wp.zeros((N, D), dtype=wp.float32, device=dev)
self._computed_torque = wp.zeros((N, D), dtype=wp.float32, device=dev)
self._applied_torque = wp.zeros((N, D), dtype=wp.float32, device=dev)
# -- Default state
self._default_root_pose = wp.zeros(N, dtype=wp.transformf, device=dev)
self._default_root_vel = wp.zeros(N, dtype=wp.spatial_vectorf, device=dev)
self._default_joint_pos = wp.zeros((N, D), dtype=wp.float32, device=dev)
self._default_joint_vel = wp.zeros((N, D), dtype=wp.float32, device=dev)
# -- Derived property buffers
self._projected_gravity_b = TimestampedBuffer(N, dev, wp.vec3f)
self._heading_w = TimestampedBuffer(N, dev, wp.float32)
self._root_link_lin_vel_b = TimestampedBuffer(N, dev, wp.vec3f)
self._root_link_ang_vel_b = TimestampedBuffer(N, dev, wp.vec3f)
self._root_com_lin_vel_b = TimestampedBuffer(N, dev, wp.vec3f)
self._root_com_ang_vel_b = TimestampedBuffer(N, dev, wp.vec3f)
# -- Deprecated combined state buffers (TimestampedBuffer; lazily filled on first access)
self._root_state_w_buf = TimestampedBuffer(N, dev, vec13f)
self._root_link_state_w_buf = TimestampedBuffer(N, dev, vec13f)
self._root_com_state_w_buf = TimestampedBuffer(N, dev, vec13f)
self._default_root_state_buf = wp.zeros(N, dtype=vec13f, device=dev)
# -- Deprecated body combined state buffers (TimestampedBuffer; lazily filled on first access)
self._body_state_w_buf = TimestampedBuffer((N, L), dev, vec13f)
self._body_link_state_w_buf = TimestampedBuffer((N, L), dev, vec13f)
self._body_com_state_w_buf = TimestampedBuffer((N, L), dev, vec13f)
# -- Tendon property buffers (always allocated; empty shape when T==0 so
# properties never return None). Routed through _read_scalar_binding.
T_fix = self._num_fixed_tendons
T_spa = self._num_spatial_tendons
self._fixed_tendon_stiffness = TimestampedBuffer((N, T_fix), dev, wp.float32)
self._fixed_tendon_damping = TimestampedBuffer((N, T_fix), dev, wp.float32)
self._fixed_tendon_limit_stiffness = TimestampedBuffer((N, T_fix), dev, wp.float32)
self._fixed_tendon_rest_length = TimestampedBuffer((N, T_fix), dev, wp.float32)
self._fixed_tendon_offset = TimestampedBuffer((N, T_fix), dev, wp.float32)
# Legacy alias kept for any internal callers that used the old vec2f buffer.
self._fixed_tendon_pos_limits = TimestampedBuffer((N, T_fix), dev, wp.vec2f)
self._spatial_tendon_stiffness = TimestampedBuffer((N, T_spa), dev, wp.float32)
self._spatial_tendon_damping = TimestampedBuffer((N, T_spa), dev, wp.float32)
self._spatial_tendon_limit_stiffness = TimestampedBuffer((N, T_spa), dev, wp.float32)
self._spatial_tendon_offset = TimestampedBuffer((N, T_spa), dev, wp.float32)
# -- CPU staging buffers for CPU-only bindings.
# Pre-allocate all of them so there is no per-step allocation on the hot path.
# These are keyed by tensor_type in self._cpu_staging_buffers; _binding_read
# selects the right one at read time. The sizes must match the binding shapes
# (flat float32). On a GPU sim the buffers are pinned-host (page-locked) so
# the wheel can dispatch async copies; on a CPU sim the staging copy is
# functionally redundant but the buffer must still exist for the write
# helpers, so we allocate unpinned and pay only the intra-CPU memcpy.
pinned = dev != "cpu"
self._cpu_body_mass = wp.zeros((N, L), dtype=wp.float32, device="cpu", pinned=pinned)
self._cpu_body_coms = wp.zeros((N, L, 7), dtype=wp.float32, device="cpu", pinned=pinned)
self._cpu_body_inertia = wp.zeros((N, L, 9), dtype=wp.float32, device="cpu", pinned=pinned)
self._cpu_joint_stiffness = wp.zeros((N, D), dtype=wp.float32, device="cpu", pinned=pinned)
self._cpu_joint_damping = wp.zeros((N, D), dtype=wp.float32, device="cpu", pinned=pinned)
self._cpu_joint_position_limit = wp.zeros((N, D, 2), dtype=wp.float32, device="cpu", pinned=pinned)
self._cpu_joint_velocity_limit = wp.zeros((N, D), dtype=wp.float32, device="cpu", pinned=pinned)
self._cpu_joint_effort_limit = wp.zeros((N, D), dtype=wp.float32, device="cpu", pinned=pinned)
self._cpu_joint_armature = wp.zeros((N, D), dtype=wp.float32, device="cpu", pinned=pinned)
self._cpu_joint_friction_coeff = wp.zeros((N, D), dtype=wp.float32, device="cpu", pinned=pinned)
self._cpu_joint_dynamic_friction_coeff = wp.zeros((N, D), dtype=wp.float32, device="cpu", pinned=pinned)
self._cpu_joint_viscous_friction_coeff = wp.zeros((N, D), dtype=wp.float32, device="cpu", pinned=pinned)
if T_fix > 0:
self._cpu_fixed_tendon_stiffness = wp.zeros((N, T_fix), dtype=wp.float32, device="cpu", pinned=pinned)
self._cpu_fixed_tendon_damping = wp.zeros((N, T_fix), dtype=wp.float32, device="cpu", pinned=pinned)
self._cpu_fixed_tendon_limit_stiffness = wp.zeros((N, T_fix), dtype=wp.float32, device="cpu", pinned=pinned)
self._cpu_fixed_tendon_rest_length = wp.zeros((N, T_fix), dtype=wp.float32, device="cpu", pinned=pinned)
self._cpu_fixed_tendon_offset = wp.zeros((N, T_fix), dtype=wp.float32, device="cpu", pinned=pinned)
self._cpu_fixed_tendon_pos_limits = wp.zeros((N, T_fix, 2), dtype=wp.float32, device="cpu", pinned=pinned)
if T_spa > 0:
self._cpu_spatial_tendon_stiffness = wp.zeros((N, T_spa), dtype=wp.float32, device="cpu", pinned=pinned)
self._cpu_spatial_tendon_damping = wp.zeros((N, T_spa), dtype=wp.float32, device="cpu", pinned=pinned)
self._cpu_spatial_tendon_limit_stiffness = wp.zeros(
(N, T_spa), dtype=wp.float32, device="cpu", pinned=pinned
)
self._cpu_spatial_tendon_offset = wp.zeros((N, T_spa), dtype=wp.float32, device="cpu", pinned=pinned)
# Read initial joint/body properties from bindings (one-time CPU reads).
self._read_initial_properties()
# Initialize ProxyArray wrappers (lazily created on first property access).
self._pin_proxy_arrays()
def _binding_read(self, tensor_type: int, dst: wp.array) -> None:
"""Refresh *dst* from the binding via the view, staging for CPU-only bindings.
GPU-resident state bindings (pose, velocity, …) fill *dst* directly through
:meth:`~isaaclab_ov.sim.views.OvPhysxView.read_into`, which reinterprets a
structured *dst* and reuses that reinterpret across calls so the wheel's read cache
stays warm. CPU-only property bindings (mass, COM, limits, stiffness, …) are read
into a pinned-host staging buffer first (the view does not stage across devices),
then :func:`wp.copy` moves the data to the simulation device.
Args:
tensor_type: TensorType key identifying the binding.
dst: Destination :class:`wp.array` on the simulation device.
"""
if tensor_type not in TT._CPU_ONLY_TYPES or self.device == "cpu":
self._view.read_into(tensor_type, dst)
return
# Route through a lazily-allocated pinned-host staging buffer (read_into refuses to
# cross devices), then copy to the simulation device.
staging = self._cpu_staging_buffers.get(tensor_type)
if staging is None:
staging = wp.zeros(self._view.binding_for(tensor_type).shape, dtype=wp.float32, device="cpu", pinned=True)
self._cpu_staging_buffers[tensor_type] = staging
self._view.read_into(tensor_type, staging)
# Build a flat float32 view of dst matching the staging's flat shape.
if dst.dtype == wp.float32:
view = dst
else:
view = wp.array(
ptr=dst.ptr,
shape=staging.shape,
dtype=wp.float32,
device=str(dst.device),
copy=False,
)
wp.copy(view, staging)
def _binding_write(
self,
tensor_type: int,
binding: Any,
src: wp.array,
*,
indices: wp.array | None = None,
mask: wp.array | None = None,
) -> None:
"""Write *src* to *binding*, staging through pinned-host buffers for CPU-only bindings.
Args:
tensor_type: TensorType key identifying the binding.
binding: OVPhysX TensorBinding whose ``write`` method is called.
src: Source :class:`wp.array` on the simulation device.
indices: Optional environment indices for partial writes.
mask: Optional boolean mask for partial writes.
"""
if tensor_type not in TT._CPU_ONLY_TYPES or self.device == "cpu":
binding.write(src, indices=indices, mask=mask)
return
# Stage through a pinned-host buffer.
staging = self._cpu_staging_buffers.get(tensor_type)
if staging is None:
staging = wp.zeros(binding.shape, dtype=wp.float32, device="cpu", pinned=True)
self._cpu_staging_buffers[tensor_type] = staging
if src.dtype == wp.float32:
src_view = src
else:
src_view = wp.array(
ptr=src.ptr,
shape=binding.shape,
dtype=wp.float32,
device=str(src.device),
copy=False,
)
wp.copy(staging, src_view)
wp.synchronize_stream(src.device)
binding.write(staging, indices=indices, mask=mask)
def _stage_to_pinned_cpu(self, tensor_type: int, role: str, src: wp.array) -> wp.array:
"""Copy *src* into a lazily-allocated pinned-host :class:`wp.array`.
Keyed on *(tensor_type, role)* so the same pair always reuses the same
buffer, avoiding per-call allocation on the hot path.
Args:
tensor_type: TensorType identifying the binding.
role: Disambiguating string when the same tensor_type may serve
multiple purposes (e.g. ``"read"`` vs ``"write"``).
src: Source array on the simulation device.
Returns:
Pinned-host wp.array containing a copy of *src*.
"""
key = (tensor_type, role)
staging = self._cpu_staging_buffers.get(key) # type: ignore[call-overload]
if staging is None:
if src.dtype == wp.float32:
shape = src.shape
else:
# Flatten to float32 shape matching the element byte size.
elem_floats = src.dtype.size // 4
shape = src.shape + (elem_floats,)
staging = wp.zeros(shape, dtype=wp.float32, device="cpu", pinned=True)
self._cpu_staging_buffers[key] = staging # type: ignore[index]
if src.dtype == wp.float32:
wp.copy(staging, src)
else:
flat_src = wp.array(ptr=src.ptr, shape=staging.shape, dtype=wp.float32, device=str(src.device), copy=False)
wp.copy(staging, flat_src)
wp.synchronize_stream(src.device)
return staging
def _read_initial_properties(self) -> None:
"""Read static/initial joint and body properties from ovphysx bindings.
These are one-time reads at init. Property tensors (stiffness,
damping, limits, mass, etc.) are CPU-resident in PhysX even in GPU
mode, so we read them via CPU numpy buffers and then copy to the
simulation device.
"""
def _read_cpu(tensor_type):
binding = self._get_binding(tensor_type)
if binding is None:
return None
np_buf = np.zeros(binding.shape, dtype=np.float32)
binding.read(np_buf)
return np_buf
# Joint scalar properties — write to .data since buffers are now TimestampedBuffer.
for tt, buf in [
(TT.DOF_STIFFNESS, self._joint_stiffness),
(TT.DOF_DAMPING, self._joint_damping),
(TT.DOF_ARMATURE, self._joint_armature),
(TT.DOF_MAX_VELOCITY, self._joint_vel_limits),
(TT.DOF_MAX_FORCE, self._joint_effort_limits),
]:
np_buf = _read_cpu(tt)
if np_buf is not None:
wp.copy(buf.data, wp.from_numpy(np_buf, dtype=wp.float32, device=self.device))
buf.timestamp = self._sim_timestamp
# Body mass (now a TimestampedBuffer).
np_buf = _read_cpu(TT.BODY_MASS)
if np_buf is not None:
wp.copy(self._body_mass.data, wp.from_numpy(np_buf, dtype=wp.float32, device=self.device))
self._body_mass.timestamp = self._sim_timestamp
# Joint position limits: [N, D, 2] -> (N, D) wp.vec2f stored in TimestampedBuffer.data
np_lim = _read_cpu(TT.DOF_LIMIT)
if np_lim is not None:
src = wp.from_numpy(
np_lim.reshape(self._num_instances, self._num_joints, 2), dtype=wp.vec2f, device=self.device
)
wp.copy(self._joint_pos_limits.data, src)
self._joint_pos_limits.timestamp = self._sim_timestamp
# Body inertia (now a TimestampedBuffer): [N, L, 9]
np_iner = _read_cpu(TT.BODY_INERTIA)
if np_iner is not None:
wp.copy(
self._body_inertia.data,
wp.from_numpy(np_iner, dtype=wp.float32, device=self.device),
)
self._body_inertia.timestamp = self._sim_timestamp
# Friction: [N, D, 3] -> load directly into the combined TimestampedBuffer.
# The strided per-component views (_joint_friction_coeff/dynamic/viscous) are
# created later in _pin_proxy_arrays, so we write to the combined buffer here.
np_fric = _read_cpu(TT.DOF_FRICTION_PROPERTIES)
if np_fric is not None:
fric_contiguous = np.ascontiguousarray(np_fric.reshape(self._num_instances, self._num_joints, 3))
wp.copy(
self._joint_friction_props_buf.data,
wp.from_numpy(fric_contiguous, dtype=wp.float32, device=self.device),
)
self._joint_friction_props_buf.timestamp = self._sim_timestamp
# Fixed tendon properties. PhysX exposes tendons on the simulation
# device (no ``device="cpu"`` clone in its ``set_fixed_tendon_properties``
# call); the OVPhysX wheel mirrors that, so we read directly into the
# sim-device buffer rather than via a numpy round-trip.
T_fix = self._num_fixed_tendons
if T_fix > 0:
for tt, buf in [
(TT.FIXED_TENDON_STIFFNESS, self._fixed_tendon_stiffness),
(TT.FIXED_TENDON_DAMPING, self._fixed_tendon_damping),
(TT.FIXED_TENDON_LIMIT_STIFFNESS, self._fixed_tendon_limit_stiffness),
(TT.FIXED_TENDON_REST_LENGTH, self._fixed_tendon_rest_length),
(TT.FIXED_TENDON_OFFSET, self._fixed_tendon_offset),
]:
binding = self._get_binding(tt)
if binding is not None:
self._binding_read(tt, buf.data)
buf.timestamp = self._sim_timestamp
binding = self._get_binding(TT.FIXED_TENDON_LIMIT)
if binding is not None:
self._binding_read(TT.FIXED_TENDON_LIMIT, self._fixed_tendon_pos_limits.data)
self._fixed_tendon_pos_limits.timestamp = self._sim_timestamp
# Spatial tendon properties (sim-device, see fixed-tendon comment above).
T_spa = self._num_spatial_tendons
if T_spa > 0:
for tt, buf in [
(TT.SPATIAL_TENDON_STIFFNESS, self._spatial_tendon_stiffness),
(TT.SPATIAL_TENDON_DAMPING, self._spatial_tendon_damping),
(TT.SPATIAL_TENDON_LIMIT_STIFFNESS, self._spatial_tendon_limit_stiffness),
(TT.SPATIAL_TENDON_OFFSET, self._spatial_tendon_offset),
]:
binding = self._get_binding(tt)
if binding is not None:
self._binding_read(tt, buf.data)
buf.timestamp = self._sim_timestamp
def _configure_ordering_buffers(self) -> None:
"""Allocate and seed buffers owned only by nonidentity ordering."""
if self.has_joint_ordering:
self._joint_pos_backend = TimestampedBuffer((self.num_instances, self.num_joints), self.device, wp.float32)
self._joint_vel_backend = TimestampedBuffer((self.num_instances, self.num_joints), self.device, wp.float32)
joint_property_specs = (
(self._joint_stiffness, "_joint_stiffness_backend", wp.float32),
(self._joint_damping, "_joint_damping_backend", wp.float32),
(self._joint_armature, "_joint_armature_backend", wp.float32),
(self._joint_pos_limits, "_joint_pos_limits_backend", wp.vec2f),
(self._joint_vel_limits, "_joint_vel_limits_backend", wp.float32),
(self._joint_effort_limits, "_joint_effort_limits_backend", wp.float32),
)
for user_buffer, backend_name, dtype in joint_property_specs:
backend_buffer = TimestampedBuffer((self.num_instances, self.num_joints), self.device, dtype)
backend_buffer.data.assign(user_buffer.data)
backend_buffer.timestamp = user_buffer.timestamp
setattr(self, backend_name, backend_buffer)
wp.launch(
ordering_kernels.reorder_2d_backend_to_user,
dim=(self.num_instances, self.num_joints),
inputs=[backend_buffer.data, self.joint_ordering.user_to_backend],
outputs=[user_buffer.data],
device=self.device,
)
user_buffer.timestamp = backend_buffer.timestamp
self._joint_friction_props_backend = TimestampedBuffer(
(self.num_instances, self.num_joints, 3), self.device, wp.float32
)
self._joint_friction_props_backend.data.assign(self._joint_friction_props_buf.data)
self._joint_friction_props_backend.timestamp = self._joint_friction_props_buf.timestamp
wp.launch(
ordering_kernels.reorder_3d_backend_to_user,
dim=(self.num_instances, self.num_joints, 3),
inputs=[self._joint_friction_props_backend.data, self.joint_ordering.user_to_backend],
outputs=[self._joint_friction_props_buf.data],
device=self.device,
)
self._joint_friction_props_buf.timestamp = self._joint_friction_props_backend.timestamp
if self._get_binding(TT.DOF_VELOCITY) is not None:
self._binding_read(TT.DOF_VELOCITY, self._joint_vel_backend.data)
wp.launch(
ordering_kernels.reorder_2d_backend_to_user,
dim=(self.num_instances, self.num_joints),
inputs=[self._joint_vel_backend.data, self.joint_ordering.user_to_backend],
outputs=[self._previous_joint_vel],
device=self.device,
)
reset_timestamps(
[
self._joint_pos_buf,
self._joint_vel_buf,
self._joint_acc,
self._joint_pos_backend,
self._joint_vel_backend,
]
)
if self.has_body_ordering:
self._body_link_pose_w_backend = TimestampedBuffer(
(self.num_instances, self.num_bodies), self.device, wp.transformf
)
self._body_com_pose_b_backend = TimestampedBuffer(
(self.num_instances, self.num_bodies), self.device, wp.transformf
)
self._body_com_vel_w_backend = TimestampedBuffer(
(self.num_instances, self.num_bodies), self.device, wp.spatial_vectorf
)
self._body_com_acc_w_backend = TimestampedBuffer(
(self.num_instances, self.num_bodies), self.device, wp.spatial_vectorf
)
# Invariant: from seeding onward, each backend staging must stay the backend-order
# image of its public buffer. Partial body-property setters scatter only the
# selected cells into both buffers and push full backend rows to the simulation,
# so a stale or divergent staging silently corrupts the unselected cells.
self._body_mass_backend = TimestampedBuffer((self.num_instances, self.num_bodies), self.device, wp.float32)
self._body_mass_backend.data.assign(self._body_mass.data)
self._body_mass_backend.timestamp = self._body_mass.timestamp
self._body_inertia_backend = TimestampedBuffer(
(self.num_instances, self.num_bodies, 9), self.device, wp.float32
)
self._body_inertia_backend.data.assign(self._body_inertia.data)
self._body_inertia_backend.timestamp = self._body_inertia.timestamp
wp.launch(
ordering_kernels.reorder_2d_backend_to_user,
dim=(self.num_instances, self.num_bodies),
inputs=[self._body_mass_backend.data, self.body_ordering.user_to_backend],
outputs=[self._body_mass.data],
device=self.device,
)
self._body_mass.timestamp = self._body_mass_backend.timestamp
wp.launch(
ordering_kernels.reorder_3d_backend_to_user,
dim=(self.num_instances, self.num_bodies, 9),
inputs=[self._body_inertia_backend.data, self.body_ordering.user_to_backend],
outputs=[self._body_inertia.data],
device=self.device,
)
self._body_inertia.timestamp = self._body_inertia_backend.timestamp
reset_timestamps([self._body_com_pose_b, self._body_com_pose_b_backend])
self._reset_pose()
self._reset_velocity()
self._reset_body_com_pose_b_dependents()
reset_timestamps([self._body_com_acc_w, self._body_com_acc_w_backend])
def _apply_ordering_maps_after_resolve(self) -> None:
"""Configure public-order buffers after articulation ordering maps are installed."""
self._read_launch_cache.clear()
self._configure_ordering_buffers()
self._jacobian_body_user_to_backend = self._make_jacobian_body_user_to_backend()
if self.has_joint_ordering:
self._jacobian_joint_user_to_backend = self.joint_ordering.user_to_backend
reset_timestamps(
[
self._body_com_jacobian_w,
self._mass_matrix,
self._gravity_compensation_forces,
]
)
def _pin_proxy_arrays(self) -> None:
"""Create pinned ProxyArray wrappers for all data buffers.
Called once from :meth:`_create_buffers` during initialization.
All ``_ta`` fields are lazily populated on first property access.
"""
# Defaults
self._default_root_pose_ta: ProxyArray | None = None
self._default_root_vel_ta: ProxyArray | None = None
self._default_joint_pos_ta: ProxyArray | None = None
self._default_joint_vel_ta: ProxyArray | None = None
# Joint commands (set into simulation)
self._joint_pos_target_ta: ProxyArray | None = None
self._joint_vel_target_ta: ProxyArray | None = None
self._joint_effort_target_ta: ProxyArray | None = None
# Joint commands (explicit actuator model)
self._computed_torque_ta: ProxyArray | None = None
self._applied_torque_ta: ProxyArray | None = None
# Joint properties
self._joint_stiffness_ta: ProxyArray | None = None
self._joint_damping_ta: ProxyArray | None = None
self._joint_armature_ta: ProxyArray | None = None
self._joint_friction_coeff_ta: ProxyArray | None = None
self._joint_dynamic_friction_coeff_ta: ProxyArray | None = None
self._joint_viscous_friction_coeff_ta: ProxyArray | None = None
self._joint_pos_limits_ta: ProxyArray | None = None
self._joint_vel_limits_ta: ProxyArray | None = None
self._joint_effort_limits_ta: ProxyArray | None = None
# Joint properties (custom)
self._soft_joint_pos_limits_ta: ProxyArray | None = None
self._soft_joint_vel_limits_ta: ProxyArray | None = None
self._gear_ratio_ta: ProxyArray | None = None
# Fixed tendon properties
self._fixed_tendon_stiffness_ta: ProxyArray | None = None
self._fixed_tendon_damping_ta: ProxyArray | None = None
self._fixed_tendon_limit_stiffness_ta: ProxyArray | None = None
self._fixed_tendon_rest_length_ta: ProxyArray | None = None
self._fixed_tendon_offset_ta: ProxyArray | None = None
self._fixed_tendon_pos_limits_ta: ProxyArray | None = None
# Spatial tendon properties
self._spatial_tendon_stiffness_ta: ProxyArray | None = None
self._spatial_tendon_damping_ta: ProxyArray | None = None
self._spatial_tendon_limit_stiffness_ta: ProxyArray | None = None
self._spatial_tendon_offset_ta: ProxyArray | None = None
# Root state (timestamped)
self._root_link_pose_w_ta: ProxyArray | None = None
self._root_link_vel_w_ta: ProxyArray | None = None
self._root_com_pose_w_ta: ProxyArray | None = None
self._root_com_vel_w_ta: ProxyArray | None = None
# Body state (timestamped)
self._body_link_pose_w_ta: ProxyArray | None = None
self._body_link_vel_w_ta: ProxyArray | None = None
self._body_com_pose_w_ta: ProxyArray | None = None
self._body_com_vel_w_ta: ProxyArray | None = None
self._body_com_acc_w_ta: ProxyArray | None = None
self._body_com_pose_b_ta: ProxyArray | None = None
# Dynamics quantities (task-space controllers)
self._body_com_jacobian_w_ta = ProxyArray(self._body_com_jacobian_w.data)
self._body_link_jacobian_w_ta = ProxyArray(self._body_link_jacobian_w)
self._mass_matrix_ta = ProxyArray(self._mass_matrix.data)
self._gravity_compensation_forces_ta = ProxyArray(self._gravity_compensation_forces.data)
# Body properties
self._body_mass_ta: ProxyArray | None = None
self._body_inertia_ta: ProxyArray | None = None
# Joint state (timestamped)
self._joint_pos_ta: ProxyArray | None = None
self._joint_vel_ta: ProxyArray | None = None
self._joint_acc_ta: ProxyArray | None = None
# Derived properties (timestamped)
self._projected_gravity_b_ta: ProxyArray | None = None
self._heading_w_ta: ProxyArray | None = None
self._root_link_lin_vel_b_ta: ProxyArray | None = None
self._root_link_ang_vel_b_ta: ProxyArray | None = None
self._root_com_lin_vel_b_ta: ProxyArray | None = None
self._root_com_ang_vel_b_ta: ProxyArray | None = None
# Sliced properties (root link)
self._root_link_pos_w_ta: ProxyArray | None = None
self._root_link_quat_w_ta: ProxyArray | None = None
self._root_link_lin_vel_w_ta: ProxyArray | None = None
self._root_link_ang_vel_w_ta: ProxyArray | None = None
# Sliced properties (root com)
self._root_com_pos_w_ta: ProxyArray | None = None
self._root_com_quat_w_ta: ProxyArray | None = None
self._root_com_lin_vel_w_ta: ProxyArray | None = None
self._root_com_ang_vel_w_ta: ProxyArray | None = None
# Sliced properties (body link)
self._body_link_pos_w_ta: ProxyArray | None = None
self._body_link_quat_w_ta: ProxyArray | None = None
self._body_link_lin_vel_w_ta: ProxyArray | None = None
self._body_link_ang_vel_w_ta: ProxyArray | None = None
# Sliced properties (body com)
self._body_com_pos_w_ta: ProxyArray | None = None
self._body_com_quat_w_ta: ProxyArray | None = None
self._body_com_lin_vel_w_ta: ProxyArray | None = None
self._body_com_ang_vel_w_ta: ProxyArray | None = None
self._body_com_lin_acc_w_ta: ProxyArray | None = None
self._body_com_ang_acc_w_ta: ProxyArray | None = None
# Sliced properties (body com in body frame)
self._body_com_pos_b_ta: ProxyArray | None = None
self._body_com_quat_b_ta: ProxyArray | None = None
# Deprecated state-concat properties
self._default_root_state_ta: ProxyArray | None = None
self._root_state_w_ta: ProxyArray | None = None
self._root_link_state_w_ta: ProxyArray | None = None
self._root_com_state_w_ta: ProxyArray | None = None
# Deprecated body state-concat properties
self._body_state_w_ta: ProxyArray | None = None
self._body_link_state_w_ta: ProxyArray | None = None
self._body_com_state_w_ta: ProxyArray | None = None
# Create strided wp.array views into _joint_friction_props_buf.data so that
# each friction component is accessible without copying data. The combined
# buffer has shape (N, D, 3) and contiguous float32 storage, so component k
# lives at byte offset k*4 with strides (D*3*4, 3*4).
N = self._num_instances
D = self._num_joints
_fp = self._joint_friction_props_buf.data
_float_bytes = 4 # sizeof(float32)
_stride_row = D * 3 * _float_bytes # bytes between rows
_stride_col = 3 * _float_bytes # bytes between columns (elements)
_dev = str(_fp.device)
self._joint_friction_coeff = wp.array(
ptr=_fp.ptr,
shape=(N, D),
strides=(_stride_row, _stride_col),
dtype=wp.float32,
device=_dev,
copy=False,
)
self._joint_dynamic_friction_coeff = wp.array(
ptr=_fp.ptr + _float_bytes,
shape=(N, D),
strides=(_stride_row, _stride_col),
dtype=wp.float32,
device=_dev,
copy=False,
)
self._joint_viscous_friction_coeff = wp.array(
ptr=_fp.ptr + 2 * _float_bytes,
shape=(N, D),
strides=(_stride_row, _stride_col),
dtype=wp.float32,
device=_dev,
copy=False,
)
def _invalidate_initialize_callback(self, event) -> None:
"""Invalidate cached buffers when the simulation is reinitialized.
Args:
event: Simulation event (unused).
"""
self._read_launch_cache.clear()
self._is_primed = False
self._sim_timestamp = 0.0
# Reset every TimestampedBuffer timestamp so the next property access
# triggers a fresh pull from the binding.
for attr_name in dir(self):
if attr_name.startswith("_") and not attr_name.startswith("__"):
val = getattr(self, attr_name, None)
if isinstance(val, TimestampedBuffer):
val.timestamp = -1.0
def _get_binding(self, tensor_type: int):
"""Return the binding for :paramref:`tensor_type`, or ``None`` if unavailable.
Delegates to :attr:`root_view`'s
:meth:`~isaaclab_ov.sim.views.OvPhysxView.try_binding_for`, which returns the
cached binding (creating it on first access) or ``None`` for tensor types that do
not apply to these prims.
Args:
tensor_type: TensorType key.
Returns:
The TensorBinding, or ``None`` if not available for these prims.
"""
return self._view.try_binding_for(tensor_type)
def _read_static_binding_into_buf(self, tensor_type: int, buf: TimestampedBuffer) -> None:
"""Read a static binding once after explicit invalidation."""
if buf.timestamp >= 0.0:
return
if self._get_binding(tensor_type) is None:
return
self._binding_read(tensor_type, buf.data)
buf.timestamp = 0.0
def _read_binding_into_buf(self, tensor_type: int, buf: TimestampedBuffer) -> None:
"""Refresh *buf* from the matching binding via the view, skipping if fresh or absent.
Reads route through :meth:`~isaaclab_ov.sim.views.OvPhysxView.read_into`, which
derives the structured reinterpret from the binding shape (so transform, spatial-vector,
and scalar buffers all use the same path) and reuses that reinterpret across calls;
CPU-only property bindings are staged onto the simulation device inside
:meth:`_binding_read`.
Args:
tensor_type: TensorType key.
buf: Timestamped buffer to refresh.
"""
if buf.timestamp >= self._sim_timestamp:
return
if self._get_binding(tensor_type) is None:
return
self._binding_read(tensor_type, buf.data)
buf.timestamp = self._sim_timestamp
# ``read_into`` derives the reinterpret from the binding shape and ``_binding_read`` handles
# CPU-only staging, so the transform / spatial-vector / scalar read paths are now identical;
# keep the distinct names as aliases for call-site readability.
_read_transform_binding = _read_binding_into_buf
_read_spatial_vector_binding = _read_binding_into_buf
_read_scalar_binding = _read_binding_into_buf
def _read_joint_property_binding(
self,
tensor_type: int,
user_buffer: TimestampedBuffer,
backend_buffer: TimestampedBuffer | None,
component_count: int | None = None,
) -> None:
"""Refresh a joint property binding into a public user-order buffer."""
if not self.has_joint_ordering:
self._read_scalar_binding(tensor_type, user_buffer)
return
if user_buffer.timestamp >= self._sim_timestamp:
return
self._read_scalar_binding(tensor_type, backend_buffer)
if component_count is None:
self._read_launch_cache.launch(
(id(user_buffer), "joint_property_2d"),
ordering_kernels.reorder_2d_backend_to_user,
dim=(self.num_instances, self.num_joints),
inputs=[backend_buffer.data, self.joint_ordering.user_to_backend],
outputs=[user_buffer.data],
)
else:
self._read_launch_cache.launch(
(id(user_buffer), "joint_property_3d"),
ordering_kernels.reorder_3d_backend_to_user,
dim=(self.num_instances, self.num_joints, component_count),
inputs=[backend_buffer.data, self.joint_ordering.user_to_backend],
outputs=[user_buffer.data],
)
user_buffer.timestamp = backend_buffer.timestamp
def _read_joint_friction_binding(self) -> None:
"""Refresh joint friction properties into the public user-order buffer."""
self._read_joint_property_binding(
TT.DOF_FRICTION_PROPERTIES,
self._joint_friction_props_buf,
self._joint_friction_props_backend,
component_count=3,
)
def _get_pos_from_transform(self, transform: wp.array) -> wp.array:
"""Return a position view aliased into a transform array.
Args:
transform: Source transform array.
Returns:
vec3f view into the position component.
"""
return wp.array(
ptr=transform.ptr,
shape=transform.shape,
dtype=wp.vec3f,
strides=transform.strides,
device=self.device,
)
def _get_quat_from_transform(self, transform: wp.array) -> wp.array:
"""Return a quaternion view aliased into a transform array.
Args:
transform: Source transform array.
Returns:
quatf view into the quaternion component (offset 3 floats = 12 bytes).
"""
return wp.array(
ptr=transform.ptr + 3 * 4,
shape=transform.shape,
dtype=wp.quatf,
strides=transform.strides,
device=self.device,
)
def _get_lin_vel_from_spatial_vector(self, sv: wp.array) -> wp.array:
"""Return a linear velocity view aliased into a spatial vector array.
Args:
sv: Source spatial vector array.
Returns:
vec3f view into the linear velocity component.
"""
return wp.array(
ptr=sv.ptr,
shape=sv.shape,
dtype=wp.vec3f,
strides=sv.strides,
device=self.device,
)
def _get_ang_vel_from_spatial_vector(self, sv: wp.array) -> wp.array:
"""Return an angular velocity view aliased into a spatial vector array.
Args:
sv: Source spatial vector array.
Returns:
vec3f view into the angular velocity component (offset 3 floats = 12 bytes).
"""
return wp.array(
ptr=sv.ptr + 3 * 4,
shape=sv.shape,
dtype=wp.vec3f,
strides=sv.strides,
device=self.device,
)
"""
Deprecated properties.
"""
@property
def default_root_state(self) -> ProxyArray:
"""Deprecated. Use :attr:`default_root_pose` and :attr:`default_root_vel` instead.
Shape is (num_instances,), dtype = ``vec13f``. In torch this resolves to (num_instances, 13).
"""
warnings.warn(
"default_root_state is deprecated. Use default_root_pose and default_root_vel.",
DeprecationWarning,
stacklevel=2,
)
self._read_launch_cache.launch(
"default_root_state",
concat_root_pose_and_vel_to_state,
dim=self.num_instances,
inputs=[self._default_root_pose, self._default_root_vel],
outputs=[self._default_root_state_buf],
)
if self._default_root_state_ta is None:
self._default_root_state_ta = ProxyArray(self._default_root_state_buf)
return self._default_root_state_ta
@property
def root_state_w(self) -> ProxyArray:
"""Deprecated. Use :attr:`root_link_pose_w` and :attr:`root_com_vel_w` instead.
Shape is (num_instances,), dtype = ``vec13f``. In torch this resolves to (num_instances, 13).
"""
warnings.warn(
"The `root_state_w` property will be deprecated in IsaacLab 4.0. Please use `root_link_pose_w` and "
"`root_com_vel_w` instead.",
DeprecationWarning,
stacklevel=2,
)
if self._root_state_w_buf.timestamp < self._sim_timestamp:
self._read_launch_cache.launch(
"root_state_w",
concat_root_pose_and_vel_to_state,
dim=self.num_instances,
inputs=[self.root_link_pose_w, self.root_com_vel_w],
outputs=[self._root_state_w_buf.data],
)
self._root_state_w_buf.timestamp = self._sim_timestamp
if self._root_state_w_ta is None:
self._root_state_w_ta = ProxyArray(self._root_state_w_buf.data)
return self._root_state_w_ta
@property
def root_link_state_w(self) -> ProxyArray:
"""Deprecated. Use :attr:`root_link_pose_w` and :attr:`root_link_vel_w` instead.
Shape is (num_instances,), dtype = ``vec13f``. In torch this resolves to (num_instances, 13).
"""
warnings.warn(
"The `root_link_state_w` property will be deprecated in IsaacLab 4.0. Please use `root_link_pose_w` and "
"`root_link_vel_w` instead.",
DeprecationWarning,
stacklevel=2,
)
if self._root_link_state_w_buf.timestamp < self._sim_timestamp:
self._read_launch_cache.launch(
"root_link_state_w",
concat_root_pose_and_vel_to_state,
dim=self.num_instances,
inputs=[self.root_link_pose_w, self.root_link_vel_w],
outputs=[self._root_link_state_w_buf.data],
)
self._root_link_state_w_buf.timestamp = self._sim_timestamp
if self._root_link_state_w_ta is None:
self._root_link_state_w_ta = ProxyArray(self._root_link_state_w_buf.data)
return self._root_link_state_w_ta
@property
def root_com_state_w(self) -> ProxyArray:
"""Deprecated. Use :attr:`root_com_pose_w` and :attr:`root_com_vel_w` instead.
Shape is (num_instances,), dtype = ``vec13f``. In torch this resolves to (num_instances, 13).
"""
warnings.warn(
"The `root_com_state_w` property will be deprecated in IsaacLab 4.0. Please use `root_com_pose_w` and "
"`root_com_vel_w` instead.",
DeprecationWarning,
stacklevel=2,
)
if self._root_com_state_w_buf.timestamp < self._sim_timestamp:
self._read_launch_cache.launch(
"root_com_state_w",
concat_root_pose_and_vel_to_state,
dim=self.num_instances,
inputs=[self.root_com_pose_w, self.root_com_vel_w],
outputs=[self._root_com_state_w_buf.data],
)
self._root_com_state_w_buf.timestamp = self._sim_timestamp
if self._root_com_state_w_ta is None:
self._root_com_state_w_ta = ProxyArray(self._root_com_state_w_buf.data)
return self._root_com_state_w_ta
@property
def body_state_w(self) -> ProxyArray:
"""Deprecated. Use :attr:`body_link_pose_w` and :attr:`body_com_vel_w` instead.
Shape is (num_instances, num_bodies), dtype = ``vec13f``.
In torch this resolves to (num_instances, num_bodies, 13).
"""
warnings.warn(
"The `body_state_w` property will be deprecated in IsaacLab 4.0. Please use `body_link_pose_w` and "
"`body_com_vel_w` instead.",
DeprecationWarning,
stacklevel=2,
)
if self._body_state_w_buf.timestamp >= self._sim_timestamp:
if self._body_state_w_ta is None:
self._body_state_w_ta = ProxyArray(self._body_state_w_buf.data)
return self._body_state_w_ta
_ = self.body_link_pose_w
_ = self.body_com_vel_w
self._read_launch_cache.launch(
"body_state_w",
concat_body_pose_and_vel_to_state,
dim=(self.num_instances, self.num_bodies),
inputs=[self._body_link_pose_w.data, self._body_com_vel_w.data],
outputs=[self._body_state_w_buf.data],
)
self._body_state_w_buf.timestamp = self._sim_timestamp
if self._body_state_w_ta is None:
self._body_state_w_ta = ProxyArray(self._body_state_w_buf.data)
return self._body_state_w_ta
@property
def body_link_state_w(self) -> ProxyArray:
"""Deprecated. Use :attr:`body_link_pose_w` and :attr:`body_link_vel_w` instead.
Shape is (num_instances, num_bodies), dtype = ``vec13f``.
In torch this resolves to (num_instances, num_bodies, 13).
"""
warnings.warn(
"The `body_link_state_w` property will be deprecated in IsaacLab 4.0. Please use `body_link_pose_w` and "
"`body_link_vel_w` instead.",
DeprecationWarning,
stacklevel=2,
)
if self._body_link_state_w_buf.timestamp >= self._sim_timestamp:
if self._body_link_state_w_ta is None:
self._body_link_state_w_ta = ProxyArray(self._body_link_state_w_buf.data)
return self._body_link_state_w_ta
_ = self.body_link_pose_w
_ = self.body_link_vel_w
self._read_launch_cache.launch(
"body_link_state_w",
concat_body_pose_and_vel_to_state,
dim=(self.num_instances, self.num_bodies),
inputs=[self._body_link_pose_w.data, self._body_link_vel_w.data],
outputs=[self._body_link_state_w_buf.data],
)
self._body_link_state_w_buf.timestamp = self._sim_timestamp
if self._body_link_state_w_ta is None:
self._body_link_state_w_ta = ProxyArray(self._body_link_state_w_buf.data)
return self._body_link_state_w_ta
@property
def body_com_state_w(self) -> ProxyArray:
"""Deprecated. Use :attr:`body_com_pose_w` and :attr:`body_com_vel_w` instead.
Shape is (num_instances, num_bodies), dtype = ``vec13f``.
In torch this resolves to (num_instances, num_bodies, 13).
"""
warnings.warn(
"The `body_com_state_w` property will be deprecated in IsaacLab 4.0. Please use `body_com_pose_w` and "
"`body_com_vel_w` instead.",
DeprecationWarning,
stacklevel=2,
)
if self._body_com_state_w_buf.timestamp >= self._sim_timestamp:
if self._body_com_state_w_ta is None:
self._body_com_state_w_ta = ProxyArray(self._body_com_state_w_buf.data)
return self._body_com_state_w_ta
_ = self.body_com_pose_w
_ = self.body_com_vel_w
self._read_launch_cache.launch(
"body_com_state_w",
concat_body_pose_and_vel_to_state,
dim=(self.num_instances, self.num_bodies),
inputs=[self._body_com_pose_w.data, self._body_com_vel_w.data],
outputs=[self._body_com_state_w_buf.data],
)
self._body_com_state_w_buf.timestamp = self._sim_timestamp
if self._body_com_state_w_ta is None:
self._body_com_state_w_ta = ProxyArray(self._body_com_state_w_buf.data)
return self._body_com_state_w_ta