Add Newton deformable object API with coupled rigid-deformable simulation#5287
Add Newton deformable object API with coupled rigid-deformable simulation#5287xiangdonglai wants to merge 9 commits intoisaac-sim:developfrom
Conversation
…particles in newton manager
Introduce _mark_transforms_dirty() and _mark_particles_dirty() as independent primitives, keeping _mark_state_dirty() as a convenience wrapper. Replace direct flag assignments in start_simulation() with method calls for consistency. Also restore use_cuda_graph=True in pick_cloth config (was disabled for debugging).
There was a problem hiding this comment.
🤖 Isaac Lab Review Bot
Summary
This PR introduces first-class deformable object support for the Newton backend — a major feature that brings cloth and volumetric soft-body simulation alongside rigid-body articulations. The design follows Isaac Lab's established patterns: a backend-agnostic base class (BaseDeformableObject) in isaaclab, a Newton-specific implementation, and new mesh spawners. The CoupledSolver for alternating rigid/VBD substeps is architecturally sound. However, there are a few implementation issues worth addressing, notably a CPU-bound Python loop in the kinematic target path and missing test coverage.
Design Assessment
Design is sound. The backend-agnostic BaseDeformableObject / DeformableObjectCfg in isaaclab core mirrors the existing RigidObject pattern and provides clean separation between the interface and the Newton-specific implementation. The FactoryBase pattern for auto-dispatching to the correct backend is consistent with how Articulation and RigidObject work.
The CoupledSolver encapsulating both a rigid solver and VBD with configurable coupling modes is a clean design. The deformable registry pattern (register during __init__, consume during newton_replicate) handles the initialization ordering well.
Findings
🔴 Critical: CPU-bound Python loop in kinematic target write — source/isaaclab_newton/isaaclab_newton/assets/deformable_object/deformable_object.py:351
The write_nodal_kinematic_target_to_sim_index method copies targets using a Python for loop with wp.to_torch(env_ids)[idx].item() called on every iteration. This forces a GPU→CPU sync per iteration and is O(n) in Python for each call. With many environments, this will be a significant bottleneck — especially since the equivalent position/velocity writes use efficient Warp kernel launches.
# Current (slow):
for idx in range(env_ids.shape[0]):
env_id = int(wp.to_torch(env_ids)[idx].item())
buffer_torch[env_id] = targets_torch[idx]Fix: Use torch.index_copy_ or a Warp scatter kernel (like scatter_particles_vec3f_index already used for positions/velocities) to perform this entirely on GPU:
env_ids_torch = wp.to_torch(env_ids)
if full_data:
buffer_torch[env_ids_torch] = targets_torch[env_ids_torch]
else:
buffer_torch[env_ids_torch] = targets_torch[:env_ids_torch.shape[0]]🟡 Warning: particle_q_prev is the same array as particle_q in two-way coupling — source/isaaclab_newton/isaaclab_newton/physics/coupled_solver.py:355-359
In _apply_reactions, both particle_q and particle_q_prev parameters of _kernel_body_particle_reaction receive state.particle_q. The comment says "state_out still holds the previous substep's particle positions" but then passes state.particle_q (from state_in) for both. This means the friction computation's relative displacement dx = particle_q[p_idx] - particle_q_prev[p_idx] is always zero, effectively disabling friction in two-way coupling.
If the intent is zero friction on the first substep, this should be documented. Otherwise, state_out.particle_q should be passed as particle_q_prev.
🟡 Warning: sync_particles_to_usd silently catches all exceptions at debug level — source/isaaclab_newton/isaaclab_newton/physics/newton_manager.py
The entire sync_particles_to_usd method wraps its core logic in try/except Exception with only a logger.debug message. If Fabric sync fails (e.g., due to a stale usdrt selection, wrong offsets, or dtype mismatch), the viewport will silently show stale deformation with no visible error. Since this runs every render frame, users would see frozen/incorrect cloth with no diagnostic information.
Fix: Log at warning level on first failure, then throttle to debug for subsequent failures (or at minimum use logger.warning instead of logger.debug).
🟡 Warning: Commented-out code left in coupled_solver.py — source/isaaclab_newton/isaaclab_newton/physics/coupled_solver.py:321-337
The _rigid_step method contains several commented-out lines (saved_shape_contact_pair_count, the else branch for MuJoCo, the if self._is_featherstone guard). The commented-out if self._is_featherstone check and the unconditional model.particle_count = 0 masking suggest this was meant to be conditional but was simplified during development. This is fine functionally (both solvers need particle masking), but the dead code should be cleaned up before merge.
🟡 Warning: Commented-out code in tutorial script — scripts/tutorials/01_assets/run_deformable_object.py:61-66
The old MeshCuboidCfg spawn config is left as a commented-out block. The # for _ in range(200): on line 105 and # physics_cfg = NewtonCfg(solver_cfg=XPBDSolverCfg(...)) on line 163 are also debug remnants. Tutorial scripts serve as documentation — commented-out alternatives confuse new users.
🔵 Suggestion: _MAX_REACTION_CONTACTS hardcoded at 2048 — source/isaaclab_newton/isaaclab_newton/physics/coupled_solver.py:45
The reaction kernel launches with a fixed dim=2048 regardless of actual contact count. While the kernel early-exits for tid >= contact_count[0], this wastes GPU threads when contacts are few and could silently miss contacts if a complex scene exceeds 2048. Consider reading contacts.soft_contact_count on CPU (single int) to set the launch dimension, or make this configurable via CoupledSolverCfg.
🔵 Suggestion: Module-level imports between class definitions — source/isaaclab_newton/isaaclab_newton/assets/deformable_object/deformable_object.py:60-70
The DeformableRegistryEntry dataclass is defined at module scope, followed by imports from .deformable_object_data and .kernels, then the DeformableObject class. While this works, placing imports between class definitions is unusual and reduces readability. Consider moving all module-level imports to the top (possibly under TYPE_CHECKING if circular) and keeping only the dataclass definition inline.
Test Coverage
This PR adds zero test files. The PR checklist explicitly shows "I have added tests" as unchecked.
Given the scope of changes (new base class, new Newton implementation, new coupled solver, new mesh spawners, two new task environments), the following tests are critically needed:
- Deformable object lifecycle:
_initialize_impl,reset,write_nodal_pos_to_sim_index,write_nodal_kinematic_target_to_sim_index— verify shapes, dtypes, partial reset with subset ofenv_ids - Warp kernels:
gather_particles_vec3f,scatter_particles_vec3f_index,enforce_kinematic_targets,compute_mean_vec3f_over_vertices— unit tests with known inputs/outputs - TetMesh generation:
TetMesh.cuboidsurface extraction,from_fileerror handling - CoupledSolver: Basic smoke test that one substep doesn't crash, one-way vs two-way mode selection
CI Status
✅ labeler — pass
Verdict
Minor fixes needed
The architecture and overall approach are solid — this is a well-structured feature that follows Isaac Lab's established patterns. The critical Python-loop performance issue in kinematic target writing should be fixed before merge. The particle_q_prev aliasing in two-way coupling friction warrants clarification (is zero friction on first substep intentional?). The commented-out code should be cleaned up, and test coverage should be added at least for the core kernels and deformable object lifecycle.
| buffer_torch = wp.to_torch(self._data.nodal_kinematic_target) | ||
| if full_data: | ||
| for idx in range(env_ids.shape[0]): | ||
| env_id = int(wp.to_torch(env_ids)[idx].item()) |
There was a problem hiding this comment.
🔴 Critical: CPU-bound Python loop forces GPU→CPU sync per iteration
This for loop calls wp.to_torch(env_ids)[idx].item() on every iteration, synchronizing GPU→CPU each time. With many environments this is a major bottleneck — the equivalent write_nodal_pos_to_sim_index and write_nodal_velocity_to_sim_index use efficient Warp kernel launches.
| env_id = int(wp.to_torch(env_ids)[idx].item()) | |
| env_ids_torch = wp.to_torch(env_ids) | |
| if full_data: | |
| buffer_torch[env_ids_torch] = targets_torch[env_ids_torch] | |
| else: | |
| buffer_torch[env_ids_torch] = targets_torch[:env_ids_torch.shape[0]] |
| # For the first substep, this is the initial particle_q. | ||
| state.particle_q, | ||
| model.particle_radius, | ||
| state.body_q, |
There was a problem hiding this comment.
🟡 Warning: particle_q_prev receives the same array as particle_q
Both arguments get state.particle_q, so dx = particle_q[p_idx] - particle_q_prev[p_idx] is always zero — effectively disabling friction in two-way coupling. If this is intentional for the first substep, a comment would help. Otherwise, should state_out.particle_q be passed as particle_q_prev?
Summary
This PR adds first-class deformable object support to Isaac Lab's Newton physics backend, enabling cloth and volumetric soft-body simulation alongside rigid-body articulations with one-way and two-way coupling.
Fixes #5285
Motivation
Isaac Lab currently supports deformable objects only through the PhysX backend. The Newton backend offers access to the VBD (Vertex Block Descent) solver for cloth and soft bodies, which is important for manipulation tasks involving deformable objects (e.g., cloth folding, pick-and-place of soft items). This change bridges that gap by introducing a
DeformableObjectasset type for Newton and the plumbing needed to visualize, replicate, and couple deformable bodies with rigid articulations.Changes
Core API (
isaaclab)BaseDeformableObjectabstract base class andDeformableObjectCfgproviding a backend-agnostic interface for deformable assets (particle positions, velocities, kinematic targets, nodal forces)spawn_mesh_cone,spawn_mesh_torus, etc.) and USD mesh loading utilities for deformable geometryNewton backend (
isaaclab_newton)DeformableObject: registers particles with Newton's model builder, handles multi-env cloning vianewton_replicate, exposes read/write APIs forparticle_q,particle_qd, and kinematic constraintsCoupledSolver: alternates a rigid-body solver (Featherstone or MuJoCo) and VBD per substep, supporting both one-way and two-way coupling with normal + Coulomb friction forcesNewtonManagerextensions: deformable body registry, Fabric particle sync (sync_particles_to_usdviawp.fabricarrayarray), deferred dirty-flag pattern (_mark_transforms_dirty/_mark_particles_dirty), geometry streaming fix for Kit viewport flickerNewtonManagerCfgadditions: VBD solver configuration, coupled solver settingsType of change
Screenshots
Checklist
pre-commitchecks with./isaaclab.sh --formatconfig/extension.tomlfileCONTRIBUTORS.mdor my name already exists there