Skip to content

Add Newton deformable object API with coupled rigid-deformable simulation#5287

Draft
xiangdonglai wants to merge 9 commits intoisaac-sim:developfrom
xiangdonglai:donglaix/deformable_newton_api
Draft

Add Newton deformable object API with coupled rigid-deformable simulation#5287
xiangdonglai wants to merge 9 commits intoisaac-sim:developfrom
xiangdonglai:donglaix/deformable_newton_api

Conversation

@xiangdonglai
Copy link
Copy Markdown

@xiangdonglai xiangdonglai commented Apr 15, 2026

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 DeformableObject asset type for Newton and the plumbing needed to visualize, replicate, and couple deformable bodies with rigid articulations.

Changes

Core API (isaaclab)

  • New BaseDeformableObject abstract base class and DeformableObjectCfg providing a backend-agnostic interface for deformable assets (particle positions, velocities, kinematic targets, nodal forces)
  • New mesh spawner functions (spawn_mesh_cone, spawn_mesh_torus, etc.) and USD mesh loading utilities for deformable geometry

Newton backend (isaaclab_newton)

  • DeformableObject: registers particles with Newton's model builder, handles multi-env cloning via newton_replicate, exposes read/write APIs for particle_q, particle_qd, and kinematic constraints
  • CoupledSolver: alternates a rigid-body solver (Featherstone or MuJoCo) and VBD per substep, supporting both one-way and two-way coupling with normal + Coulomb friction forces
  • NewtonManager extensions: deformable body registry, Fabric particle sync (sync_particles_to_usd via wp.fabricarrayarray), deferred dirty-flag pattern (_mark_transforms_dirty / _mark_particles_dirty), geometry streaming fix for Kit viewport flicker
  • NewtonManagerCfg additions: VBD solver configuration, coupled solver settings

Type of change

  • New feature (non-breaking change which adds functionality)
  • Documentation update

Screenshots

Checklist

  • I have read and understood the contribution guidelines
  • I have run the pre-commit checks with ./isaaclab.sh --format
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • I have updated the changelog and the corresponding version in the extension's config/extension.toml file
  • I have added my name to the CONTRIBUTORS.md or my name already exists there

xiangdonglai and others added 8 commits April 15, 2026 15:42
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).
@github-actions github-actions bot added the isaac-lab Related to Isaac Lab team label Apr 15, 2026
Copy link
Copy Markdown

@isaaclab-review-bot isaaclab-review-bot bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 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 writesource/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 couplingsource/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 levelsource/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.pysource/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 scriptscripts/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 2048source/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 definitionssource/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 of env_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.cuboid surface extraction, from_file error 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())
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 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.

Suggested change
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,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

isaac-lab Related to Isaac Lab team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants