Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions docs/source/api/lab_contrib/isaaclab_contrib.deformable.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@ isaaclab_contrib.deformable
deformable_object_data.DeformableObjectData
newton_manager_cfg.VBDSolverCfg
newton_manager_cfg.CoupledMJWarpVBDSolverCfg
newton_manager_cfg.ProxyCoupledMJWarpVBDSolverCfg
newton_manager_cfg.CoupledFeatherstoneVBDSolverCfg
newton_manager_cfg.NewtonModelCfg
newton_manager_cfg.CoupledNewtonCfg
vbd_manager.NewtonVBDManager
coupled_mjwarp_vbd_manager.NewtonCoupledMJWarpVBDManager
proxy_coupled_mjwarp_vbd_manager.NewtonProxyCoupledMJWarpVBDManager
coupled_featherstone_vbd_manager.NewtonCoupledFeatherstoneVBDManager

Deformable Object
Expand Down Expand Up @@ -44,6 +47,11 @@ Newton Solver Configurations
:show-inheritance:
:exclude-members: __init__

.. autoclass:: isaaclab_contrib.deformable.newton_manager_cfg.ProxyCoupledMJWarpVBDSolverCfg
:members:
:show-inheritance:
:exclude-members: __init__

.. autoclass:: isaaclab_contrib.deformable.newton_manager_cfg.CoupledFeatherstoneVBDSolverCfg
:members:
:show-inheritance:
Expand All @@ -54,6 +62,11 @@ Newton Solver Configurations
:show-inheritance:
:exclude-members: __init__

.. autoclass:: isaaclab_contrib.deformable.newton_manager_cfg.CoupledNewtonCfg
:members:
:show-inheritance:
:exclude-members: __init__

Newton Solver Managers
----------------------

Expand All @@ -67,6 +80,11 @@ Newton Solver Managers
:inherited-members:
:show-inheritance:

.. autoclass:: isaaclab_contrib.deformable.proxy_coupled_mjwarp_vbd_manager.NewtonProxyCoupledMJWarpVBDManager
:members:
:inherited-members:
:show-inheritance:

.. autoclass:: isaaclab_contrib.deformable.coupled_featherstone_vbd_manager.NewtonCoupledFeatherstoneVBDManager
:members:
:inherited-members:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,19 @@ before it works well with VBD.
VBD is usually exposed through a task-specific physics preset rather than a
general ``newton_vbd`` preset. Deformable-only scenes can use
:class:`~isaaclab_contrib.deformable.VBDSolverCfg` directly. Robot or
rigid-body scenes usually use
:class:`~isaaclab_contrib.deformable.CoupledMJWarpVBDSolverCfg` or
:class:`~isaaclab_contrib.deformable.CoupledFeatherstoneVBDSolverCfg` so one
solver advances rigid bodies and VBD advances deformable particles.
rigid-body scenes usually use one of the coupled configs so one solver advances
rigid bodies and VBD advances deformable particles:

* :class:`~isaaclab_contrib.deformable.CoupledMJWarpVBDSolverCfg` — alternates
the rigid (MJWarp) and VBD substeps. Use it when the same robot should both
contact and feel the deformable.
* :class:`~isaaclab_contrib.deformable.ProxyCoupledMJWarpVBDSolverCfg` —
partitions the model between an MJWarp entry and a VBD entry, exposing
selected rigid bodies as *proxies* in the VBD view via lagged impulses (see
:ref:`newton-vbd-proxy-coupling` below). Use it when only a few rigid bodies
(e.g. a gripper) need to interact with the deformable.
* :class:`~isaaclab_contrib.deformable.CoupledFeatherstoneVBDSolverCfg` —
alternates Featherstone and VBD; supports kinematic one-way coupling.

Start from a Supported Deformable Task
--------------------------------------
Expand Down Expand Up @@ -207,6 +216,101 @@ The rigid solver parameters still matter. For example, MJWarp's ``nconmax`` and
:doc:`mjwarp-solver` for the MJWarp-side parameters.


.. _newton-vbd-proxy-coupling:

Proxy-Coupled MJWarp + VBD
--------------------------

:class:`~isaaclab_contrib.deformable.ProxyCoupledMJWarpVBDSolverCfg` is an
alternative MJWarp + VBD coupling that wraps Newton's
:class:`newton.solvers.SolverCoupledProxy`. Instead of alternating two
full-model substeps, the model is **partitioned** between an MJWarp entry and a
VBD entry, and selected rigid bodies are exposed to VBD as *proxies* — virtual
copies that VBD collides against. Contact feedback is returned to MJWarp as
lagged impulses. This typically scales better than the alternating coupling
when only a small set of rigid bodies (e.g. the fingers of a gripper) actually
needs to touch the deformable, since the bulk of the articulation is solved
purely by MJWarp without seeing the particle contacts.

The Franka soft-body task ships a ``newton_mjwarp_vbd_proxy`` preset (the new
default for ``Isaac-Lift-Soft-Franka-v0``) that demonstrates the typical
configuration:

.. literalinclude:: ../../../../../../source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/lift_franka_soft/franka_soft_env_cfg.py
:language: python
:start-at: newton_mjwarp_vbd_proxy: CoupledNewtonCfg
:end-before: physx: PhysxCfg = PhysxCfg()
:dedent: 4

What the selectors do:

* ``mjwarp_bodies`` and ``vbd_bodies`` partition every body in the model. Each
entry is either a :class:`~isaaclab.managers.SceneEntityCfg` (resolved
against the scene's ``prim_path``, optionally narrowed by ``body_names``) or
a raw prim-path regex string matched against ``model.body_label`` (e.g.
``"/World/envs/env_.*/Robot"``). Joints inherit their child body's owner;
shapes inherit their body's owner. Static shapes (world geometry) always go
to VBD so the proxy collision pipeline can test against the ground. A body
matching both partitions, or matching neither, is an error.
* ``proxy_bodies`` selects the (rigid) MJWarp bodies that VBD should collide
against. Only bodies that own at least one
``newton.ShapeFlags.COLLIDE_SHAPES`` shape are kept. For
:class:`~isaaclab.managers.SceneEntityCfg` entries, ``body_names`` is
**required** here since proxies must be a strict subset of the asset.
* In the snippet above, the entire Franka articulation is routed to MJWarp,
the deformable particles are owned by VBD, and only the ``panda_hand`` and
the two fingers are exposed as proxies — so VBD only ever sees three rigid
proxies regardless of how many links the arm has.

Key proxy-specific parameters:

.. list-table::
:header-rows: 1
:widths: 30 70

* - Parameter
- Description
* - ``proxy_mode``
- Default: ``"lagged"``. ``"lagged"`` syncs source begin poses and end
velocities, then rewinds lagged feedback before the destination solve.
``"staggered"`` syncs source end poses and end velocities directly.
Lagged is the safer default; staggered can be tighter but is more
sensitive to timestep.
* - ``proxy_iterations``
- Default: ``1``. Number of relaxation iterations per coupled substep.
Increase it when proxy contact feedback needs more accuracy.
* - ``proxy_collide_interval``
- Default: ``1``. How often (in proxy passes) the proxy collision
pipeline rebuilds candidate pairs. Increase it for cheaper but
slightly staler proxy contacts.
* - ``proxy_mass_scale``
- Default: ``1.0``. Multiplier for the virtual inertia of proxy bodies
in the VBD view. Increase it to make proxies behave more like fixed
obstacles to VBD.

Because the proxy solver resolves
:class:`~isaaclab.managers.SceneEntityCfg` selectors against the scene at
solver-build time, the coupled preset must be wrapped in a
:class:`~isaaclab_contrib.deformable.CoupledNewtonCfg` (a thin
:class:`~isaaclab_newton.physics.NewtonCfg` subclass with a ``scene_cfg`` and a
``model_cfg`` field). The env's ``__post_init__`` is responsible for setting
``self.sim.physics.scene_cfg = self.scene`` — the Franka soft env does this
automatically via the :class:`~isaaclab_tasks.utils.PresetCfg` plumbing.

Try the demo:

.. code-block:: bash

# zero-agent visual smoke test (default preset is now the proxy-coupled one)
./isaaclab.sh -p scripts/environments/zero_agent.py --task Isaac-Lift-Soft-Franka-v0 --num_envs 1 --visualizer kit

# scripted pick-and-lift via state machine
./isaaclab.sh -p scripts/environments/state_machine/lift_franka_soft.py --num_envs 1

# explicitly select the alternating-substep preset instead
./isaaclab.sh -p scripts/environments/zero_agent.py --task Isaac-Lift-Soft-Franka-v0 --num_envs 1 presets=newton_mjwarp_vbd


Contact and Material Parameters
-------------------------------

Expand Down
34 changes: 10 additions & 24 deletions scripts/environments/state_machine/lift_franka_soft.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,6 @@
from isaaclab.assets.deformable_object.deformable_object_data import DeformableObjectData

import isaaclab_tasks # noqa: F401
from isaaclab_tasks.manager_based.manipulation.lift_franka_soft.franka_cloth_env_cfg import FrankaClothEnvCfg
from isaaclab_tasks.manager_based.manipulation.lift_franka_soft.franka_soft_env_cfg import FrankaSoftEnvCfg
from isaaclab_tasks.utils.parse_cfg import parse_env_cfg

# initialize warp
Expand Down Expand Up @@ -284,26 +282,14 @@ def compute(self, ee_pose: torch.Tensor, object_pose: torch.Tensor, des_object_p
def main():
# create environment
render_mode = "rgb_array" if args_cli.video else None
if args_cli.task == "Isaac-Lift-Soft-Franka-v0":
# parse configuration
env_cfg: FrankaSoftEnvCfg = parse_env_cfg(
"Isaac-Lift-Soft-Franka-v0",
device=args_cli.device,
num_envs=args_cli.num_envs,
)
env_cfg.viewer.eye = (2.1, 1.0, 1.3)
env = gym.make("Isaac-Lift-Soft-Franka-v0", cfg=env_cfg, render_mode=render_mode)
elif args_cli.task == "Isaac-Lift-Soft-Franka-Cloth-v0":
# parse configuration
env_cfg: FrankaClothEnvCfg = parse_env_cfg(
"Isaac-Lift-Cloth-Franka-v0",
device=args_cli.device,
num_envs=args_cli.num_envs,
)
env_cfg.viewer.eye = (2.1, 1.0, 1.3)
env = gym.make("Isaac-Lift-Cloth-Franka-v0", cfg=FrankaClothEnvCfg(), render_mode=render_mode)
else:
raise ValueError(f"Unknown task: {args_cli.task}")
# parse configuration
env_cfg = parse_env_cfg(
args_cli.task,
device=args_cli.device,
num_envs=args_cli.num_envs,
)
env_cfg.viewer.eye = (2.1, 1.0, 1.3)
env = gym.make(args_cli.task, cfg=env_cfg, render_mode=render_mode)

# wrap for video recording
if args_cli.video:
Expand Down Expand Up @@ -353,12 +339,12 @@ def main():
)
tcp_rest_orientation = ee_frame_sensor.data.target_quat_w.torch[..., 0, :].clone()
# -- object frame
object_data: DeformableObjectData = env.unwrapped.scene["deformable"].data
object_data: DeformableObjectData = env.unwrapped.scene["object"].data
object_position = object_data.root_pos_w.torch - env.unwrapped.scene.env_origins
object_position += object_local_grasp_position

# -- target object frame
desired_position = env.unwrapped.command_manager.get_command("deformable_pose")[..., :3]
desired_position = env.unwrapped.command_manager.get_command("object_pose")[..., :3]

# advance state machine
actions = pick_sm.compute(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Added
^^^^^

* Added :class:`~isaaclab_contrib.deformable.newton_manager_cfg.ProxyCoupledMJWarpVBDSolverCfg`
and the matching
:class:`~isaaclab_contrib.deformable.proxy_coupled_mjwarp_vbd_manager.NewtonProxyCoupledMJWarpVBDManager`,
wrapping :class:`newton.solvers.experimental.coupled.SolverCoupledProxy` to
split simulation between MuJoCo Warp (rigids/articulations) and VBD
(particles/deformables), with selected MJWarp bodies exposed as proxies in
the VBD view.

* Added support for raw prim-path regex strings (e.g. ``"/World/envs/env_.*/MyCube"``)
in the body-selector lists of
:class:`~isaaclab_contrib.deformable.newton_manager_cfg.ProxyCoupledMJWarpVBDSolverCfg`,
alongside :class:`~isaaclab.managers.SceneEntityCfg` entries. Useful for
claiming rigid assets that aren't registered as named scene entities.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

from __future__ import annotations

import inspect
import logging
from typing import TYPE_CHECKING

Expand All @@ -21,7 +20,6 @@

from .deformable_object import (
add_deformable_entry_to_builder,
clear_deformable_builder_hooks,
install_deformable_builder_hooks,
)
from .kernels import _kernel_body_particle_reaction
Expand Down Expand Up @@ -82,11 +80,6 @@ def step(cls) -> None:
NewtonManager._model_changes = set()
super().step()

@classmethod
def _solver_specific_clear(cls):
"""Clear VBD-specific state."""
clear_deformable_builder_hooks()

@classmethod
def _get_deformable_ignore_paths(cls) -> list[str]:
"""Return USD prim paths to skip when calling ``builder.add_usd``.
Expand Down Expand Up @@ -299,13 +292,10 @@ def _build_solver(cls, model: Model, solver_cfg: CoupledFeatherstoneVBDSolverCfg
"""
cls._coupling_mode = solver_cfg.coupling_mode

valid = set(inspect.signature(SolverFeatherstone.__init__).parameters) - {"self", "model"}
kwargs = {k: v for k, v in solver_cfg.rigid_solver_cfg.to_dict().items() if k in valid}
cls._rigid_solver = SolverFeatherstone(model, **kwargs)

valid = set(inspect.signature(SolverVBD.__init__).parameters) - {"self", "model"}
kwargs = {k: v for k, v in solver_cfg.soft_solver_cfg.to_dict().items() if k in valid}
cls._soft_solver = SolverVBD(model, **kwargs)
cls._rigid_solver = SolverFeatherstone(
model, **cls._filter_solver_kwargs(SolverFeatherstone, solver_cfg.rigid_solver_cfg)
)
cls._soft_solver = SolverVBD(model, **cls._filter_solver_kwargs(SolverVBD, solver_cfg.soft_solver_cfg))

# Dummy solver for the newtonmanager
NewtonManager._solver = SolverBase(model)
Expand Down Expand Up @@ -339,8 +329,12 @@ def _step_solver(
cls._step_kinematic(state_in, state_out, control, substep_dt)
elif cls._coupling_mode == "one_way":
cls._step_one_way(state_in, state_out, control, substep_dt)
else:
elif cls._coupling_mode == "two_way":
cls._step_two_way(state_in, state_out, control, substep_dt)
else:
raise ValueError(
f"Unknown coupling_mode={cls._coupling_mode!r}; expected one of {{'kinematic', 'one_way', 'two_way'}}."
)

@classmethod
def _simulate_physics_only(cls) -> None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

from __future__ import annotations

import inspect
import logging
from typing import TYPE_CHECKING

Expand All @@ -21,7 +20,6 @@

from .deformable_object import (
add_deformable_entry_to_builder,
clear_deformable_builder_hooks,
install_deformable_builder_hooks,
)
from .kernels import _kernel_body_particle_reaction
Expand Down Expand Up @@ -82,11 +80,6 @@ def step(cls) -> None:
NewtonManager._model_changes = set()
super().step()

@classmethod
def _solver_specific_clear(cls):
"""Clear VBD-specific state."""
clear_deformable_builder_hooks()

@classmethod
def _get_deformable_ignore_paths(cls) -> list[str]:
"""Return USD prim paths to skip when calling ``builder.add_usd``.
Expand Down Expand Up @@ -299,13 +292,8 @@ def _build_solver(cls, model: Model, solver_cfg: CoupledMJWarpVBDSolverCfg) -> N
"""
cls._coupling_mode = solver_cfg.coupling_mode

valid = set(inspect.signature(SolverMuJoCo.__init__).parameters) - {"self", "model"}
kwargs = {k: v for k, v in solver_cfg.rigid_solver_cfg.to_dict().items() if k in valid}
cls._rigid_solver = SolverMuJoCo(model, **kwargs)

valid = set(inspect.signature(SolverVBD.__init__).parameters) - {"self", "model"}
kwargs = {k: v for k, v in solver_cfg.soft_solver_cfg.to_dict().items() if k in valid}
cls._soft_solver = SolverVBD(model, **kwargs)
cls._rigid_solver = SolverMuJoCo(model, **cls._filter_solver_kwargs(SolverMuJoCo, solver_cfg.rigid_solver_cfg))
cls._soft_solver = SolverVBD(model, **cls._filter_solver_kwargs(SolverVBD, solver_cfg.soft_solver_cfg))

# Dummy solver for the newtonmanager
NewtonManager._solver = SolverBase(model)
Expand All @@ -328,8 +316,10 @@ def _step_solver(
"""
if cls._coupling_mode == "one_way":
cls._step_one_way(state_in, state_out, control, substep_dt)
else:
elif cls._coupling_mode == "two_way":
cls._step_two_way(state_in, state_out, control, substep_dt)
else:
raise ValueError(f"Unknown coupling_mode={cls._coupling_mode!r}; expected one of {{'one_way', 'two_way'}}.")

@classmethod
def _simulate_physics_only(cls) -> None:
Expand Down
Loading
Loading