Source code for isaaclab_contrib.deformable.deformable_object_data
# 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 warp as wp
from isaaclab_newton.physics import NewtonManager as SimulationManager
from isaaclab.assets.deformable_object.base_deformable_object_data import BaseDeformableObjectData
from isaaclab.utils.buffers import TimestampedBufferWarp as TimestampedBuffer
from isaaclab.utils.warp import ProxyArray
from .kernels import compute_mean_vec3f_over_vertices, compute_nodal_state_w, gather_particles_vec3f, vec6f
[docs]
class DeformableObjectData(BaseDeformableObjectData):
"""Data container for a deformable object (Newton backend).
Newton stores all particles in flat arrays (``model.particle_q``, ``state.particle_qd``).
This data class builds a per-instance view by gathering from the flat arrays using
precomputed offsets.
The data is lazily updated, meaning that the data is only updated when it is accessed.
"""
def __init__(
self,
particle_offsets: wp.array,
particles_per_body: int,
num_instances: int,
device: str,
):
"""Initialize the Newton deformable object data.
Args:
particle_offsets: Per-instance start offset into the flat particle array.
Shape is (num_instances,) with dtype int32.
particles_per_body: Number of particles per deformable body instance.
num_instances: Number of deformable body instances.
device: The device used for processing.
"""
super().__init__(device)
# Store dimensions and indexing
self._particle_offsets = particle_offsets
self._particles_per_body = particles_per_body
self._num_instances = num_instances
# Initialize lazy buffers
self._nodal_pos_w = TimestampedBuffer((num_instances, particles_per_body), device, wp.vec3f)
self._nodal_vel_w = TimestampedBuffer((num_instances, particles_per_body), device, wp.vec3f)
self._nodal_state_w = TimestampedBuffer((num_instances, particles_per_body), device, vec6f)
self._root_pos_w = TimestampedBuffer((num_instances,), device, wp.vec3f)
self._root_vel_w = TimestampedBuffer((num_instances,), device, wp.vec3f)
self._nodal_pos_w_ta: ProxyArray | None = None
self._nodal_vel_w_ta: ProxyArray | None = None
self._nodal_state_w_ta: ProxyArray | None = None
self._root_pos_w_ta: ProxyArray | None = None
self._root_vel_w_ta: ProxyArray | None = None
self._create_simulation_bindings()
##
# Defaults.
##
default_nodal_state_w: ProxyArray = None
"""Default nodal state ``[nodal_pos, nodal_vel]`` in simulation world frame.
Shape is (num_instances, particles_per_body) with dtype vec6f.
"""
##
# Kinematic commands.
##
nodal_kinematic_target: ProxyArray = None
"""Simulation mesh kinematic targets for the deformable bodies.
Shape is (num_instances, particles_per_body) with dtype vec4f.
"""
def _create_simulation_bindings(self) -> None:
"""Validate the current Newton particle state and invalidate gathered buffers.
Newton may swap :attr:`state_0` and :attr:`state_1` across substeps, so deformable data does not keep
long-lived particle array bindings. Read properties query :meth:`SimulationManager.get_state_0` at gather time
and materialize object-local views from the current flat particle arrays.
"""
self._get_current_particle_state()
# Invalidate lazy buffers gathered from the previous simulation state.
self._nodal_pos_w.timestamp = -1.0
self._nodal_vel_w.timestamp = -1.0
self._nodal_state_w.timestamp = -1.0
self._root_pos_w.timestamp = -1.0
self._root_vel_w.timestamp = -1.0
def _get_current_particle_state(self):
"""Return the current Newton state containing deformable particle arrays."""
state = SimulationManager.get_state_0()
if state is None or state.particle_q is None or state.particle_qd is None:
raise RuntimeError(
"Failed to access Newton deformable particle state. Ensure the Newton model has been finalized and "
"contains particle position and velocity arrays."
)
return state
##
# Properties.
##
@property
def nodal_pos_w(self) -> ProxyArray:
"""Nodal positions in simulation world frame [m]. Shape is (num_instances, particles_per_body) vec3f."""
if self._nodal_pos_w.timestamp < self._sim_timestamp:
state = self._get_current_particle_state()
wp.launch(
gather_particles_vec3f,
dim=(self._num_instances, self._particles_per_body),
inputs=[state.particle_q, self._particle_offsets, self._particles_per_body],
outputs=[self._nodal_pos_w.data],
device=self.device,
)
self._nodal_pos_w.timestamp = self._sim_timestamp
if self._nodal_pos_w_ta is None:
self._nodal_pos_w_ta = ProxyArray(self._nodal_pos_w.data)
return self._nodal_pos_w_ta
@property
def nodal_vel_w(self) -> ProxyArray:
"""Nodal velocities in simulation world frame [m/s]. Shape is (num_instances, particles_per_body) vec3f."""
if self._nodal_vel_w.timestamp < self._sim_timestamp:
state = self._get_current_particle_state()
wp.launch(
gather_particles_vec3f,
dim=(self._num_instances, self._particles_per_body),
inputs=[state.particle_qd, self._particle_offsets, self._particles_per_body],
outputs=[self._nodal_vel_w.data],
device=self.device,
)
self._nodal_vel_w.timestamp = self._sim_timestamp
if self._nodal_vel_w_ta is None:
self._nodal_vel_w_ta = ProxyArray(self._nodal_vel_w.data)
return self._nodal_vel_w_ta
@property
def nodal_state_w(self) -> ProxyArray:
"""Nodal state ``[nodal_pos, nodal_vel]`` in simulation world frame [m, m/s].
Shape is (num_instances, particles_per_body) vec6f.
"""
if self._nodal_state_w.timestamp < self._sim_timestamp:
wp.launch(
compute_nodal_state_w,
dim=(self._num_instances, self._particles_per_body),
inputs=[self.nodal_pos_w.warp, self.nodal_vel_w.warp],
outputs=[self._nodal_state_w.data],
device=self.device,
)
self._nodal_state_w.timestamp = self._sim_timestamp
if self._nodal_state_w_ta is None:
self._nodal_state_w_ta = ProxyArray(self._nodal_state_w.data)
return self._nodal_state_w_ta
##
# Derived properties.
##
@property
def root_pos_w(self) -> ProxyArray:
"""Root position from nodal positions [m]. Shape is (num_instances,) vec3f.
This quantity is computed as the mean of the nodal positions.
"""
if self._root_pos_w.timestamp < self._sim_timestamp:
wp.launch(
compute_mean_vec3f_over_vertices,
dim=(self._num_instances,),
inputs=[self.nodal_pos_w.warp, self._particles_per_body],
outputs=[self._root_pos_w.data],
device=self.device,
)
self._root_pos_w.timestamp = self._sim_timestamp
if self._root_pos_w_ta is None:
self._root_pos_w_ta = ProxyArray(self._root_pos_w.data)
return self._root_pos_w_ta
@property
def root_vel_w(self) -> ProxyArray:
"""Root velocity from nodal velocities [m/s]. Shape is (num_instances,) vec3f.
This quantity is computed as the mean of the nodal velocities.
"""
if self._root_vel_w.timestamp < self._sim_timestamp:
wp.launch(
compute_mean_vec3f_over_vertices,
dim=(self._num_instances,),
inputs=[self.nodal_vel_w.warp, self._particles_per_body],
outputs=[self._root_vel_w.data],
device=self.device,
)
self._root_vel_w.timestamp = self._sim_timestamp
if self._root_vel_w_ta is None:
self._root_vel_w_ta = ProxyArray(self._root_vel_w.data)
return self._root_vel_w_ta