diff --git a/docs/source/api/lab_contrib/isaaclab_contrib.deformable.rst b/docs/source/api/lab_contrib/isaaclab_contrib.deformable.rst index 71f19f45136..fc8bf8e979e 100644 --- a/docs/source/api/lab_contrib/isaaclab_contrib.deformable.rst +++ b/docs/source/api/lab_contrib/isaaclab_contrib.deformable.rst @@ -9,11 +9,6 @@ isaaclab_contrib.deformable deformable_object.DeformableObject deformable_object_data.DeformableObjectData - newton_manager_cfg.NewtonModelCfg - newton_manager_cfg.NewtonModelSolverCfg - newton_manager_cfg.VBDSolverCfg - newton_manager_cfg.CoupledMJWarpVBDSolverCfg - vbd_manager.NewtonVBDManager Deformable Object ----------------- @@ -28,34 +23,3 @@ Deformable Object :inherited-members: :show-inheritance: :exclude-members: __init__ - -Newton Solver Configurations ----------------------------- - -.. autoclass:: isaaclab_contrib.deformable.newton_manager_cfg.NewtonModelCfg - :members: - :show-inheritance: - :exclude-members: __init__ - -.. autoclass:: isaaclab_contrib.deformable.newton_manager_cfg.NewtonModelSolverCfg - :members: - :show-inheritance: - :exclude-members: __init__ - -.. autoclass:: isaaclab_contrib.deformable.newton_manager_cfg.VBDSolverCfg - :members: - :show-inheritance: - :exclude-members: __init__ - -.. autoclass:: isaaclab_contrib.deformable.newton_manager_cfg.CoupledMJWarpVBDSolverCfg - :members: - :show-inheritance: - :exclude-members: __init__ - -Newton Solver Managers ----------------------- - -.. autoclass:: isaaclab_contrib.deformable.vbd_manager.NewtonVBDManager - :members: - :inherited-members: - :show-inheritance: diff --git a/docs/source/api/lab_newton/isaaclab_newton.physics.rst b/docs/source/api/lab_newton/isaaclab_newton.physics.rst index 6fc699d4205..cb21aceb62e 100644 --- a/docs/source/api/lab_newton/isaaclab_newton.physics.rst +++ b/docs/source/api/lab_newton/isaaclab_newton.physics.rst @@ -9,15 +9,18 @@ NewtonManager NewtonCfg + NewtonSoftContactCfg NewtonCollisionPipelineCfg NewtonFeatherstoneManager NewtonKaminoManager NewtonMPMManager NewtonMJWarpManager + NewtonVBDManager NewtonShapeCfg NewtonSolverCfg NewtonXPBDManager MJWarpSolverCfg + VBDSolverCfg XPBDSolverCfg FeatherstoneSolverCfg KaminoCollisionDetectorCfg @@ -50,6 +53,11 @@ Physics Configuration :show-inheritance: :exclude-members: __init__ +.. autoclass:: NewtonSoftContactCfg + :members: + :show-inheritance: + :exclude-members: __init__ + .. autoclass:: NewtonSolverCfg :members: :show-inheritance: @@ -60,6 +68,11 @@ Physics Configuration :show-inheritance: :exclude-members: __init__ +.. autoclass:: VBDSolverCfg + :members: + :show-inheritance: + :exclude-members: __init__ + .. autoclass:: XPBDSolverCfg :members: :show-inheritance: @@ -143,6 +156,11 @@ Solver Managers :inherited-members: :show-inheritance: +.. autoclass:: NewtonVBDManager + :members: + :inherited-members: + :show-inheritance: + .. autoclass:: NewtonXPBDManager :members: :inherited-members: diff --git a/docs/source/migration/migrating_deformables.rst b/docs/source/migration/migrating_deformables.rst index dbfc75cd1a5..0e213385408 100644 --- a/docs/source/migration/migrating_deformables.rst +++ b/docs/source/migration/migrating_deformables.rst @@ -72,7 +72,6 @@ from the physics backend package: * - ``from isaaclab_physx.assets import DeformableObjectCfg`` - ``from isaaclab.assets import DeformableObjectCfg`` - Removed Properties ^^^^^^^^^^^^^^^^^^ @@ -245,9 +244,6 @@ Limitations deformable will raise a ``ValueError``. - **Surface-specific solver properties** (``collision_pair_update_frequency``, ``collision_iteration_multiplier``) have no effect on volume deformables. -- **Newton deformables are experimental.** They are implemented in - :mod:`isaaclab_contrib.deformable` and currently target VBD-based solvers and - coupled rigid-deformable workflows. .. _Omni Physics documentation: https://docs.omniverse.nvidia.com/kit/docs/omni_physics/110.0/dev_guide/deformables/deformable_bodies.html diff --git a/docs/source/overview/core-concepts/physical-backends/newton/index.rst b/docs/source/overview/core-concepts/physical-backends/newton/index.rst index fb91324469e..8d07cf6df32 100644 --- a/docs/source/overview/core-concepts/physical-backends/newton/index.rst +++ b/docs/source/overview/core-concepts/physical-backends/newton/index.rst @@ -35,8 +35,9 @@ the following solver pages: * :doc:`mjwarp-solver` — the primary, validated solver path. * :doc:`kamino-solver` — beta support on selected classic tasks. -* :doc:`using-vbd-solver`: experimental VBD solver for cloth and soft bodies, - with MJWarp and VBD proxy coupling through :mod:`isaaclab_contrib.coupling`. +* :doc:`using-vbd-solver`: VBD solver for cloth and soft bodies. Standalone VBD + is available through :mod:`isaaclab_newton.physics`; MJWarp and VBD proxy and + ADMM coupling is available through :mod:`isaaclab_contrib.coupling`. * :doc:`using-mpm`: experimental implicit MPM for particle materials, including rigid-MPM proxy coupling. diff --git a/docs/source/overview/core-concepts/physical-backends/newton/newton-manager-abstraction.rst b/docs/source/overview/core-concepts/physical-backends/newton/newton-manager-abstraction.rst index 3634ecf073f..5ea53a97f38 100644 --- a/docs/source/overview/core-concepts/physical-backends/newton/newton-manager-abstraction.rst +++ b/docs/source/overview/core-concepts/physical-backends/newton/newton-manager-abstraction.rst @@ -140,7 +140,7 @@ The MJWarp + VBD deformable manager is a concrete example: * :class:`~isaaclab_contrib.custom_coupling.CoupledMJWarpVBDSolverCfg` stores a ``rigid_solver_cfg`` for :class:`~isaaclab_newton.physics.MJWarpSolverCfg`, a - ``soft_solver_cfg`` for :class:`~isaaclab_contrib.deformable.VBDSolverCfg`, + ``soft_solver_cfg`` for :class:`~isaaclab_newton.physics.VBDSolverCfg`, and a ``coupling_mode``. * ``NewtonCoupledMJWarpVBDManager._build_solver()`` constructs ``SolverMuJoCo`` and ``SolverVBD`` from those sub-configs. @@ -232,13 +232,15 @@ Tune the coupled contact behavior before training a policy: * Lower the arm actuator stiffness enough that the arm can respond to contact penalties. Prefer the arm being pushed back over the gripper clipping into the deformable. -* Tune :attr:`~isaaclab_contrib.deformable.NewtonModelCfg.soft_contact_ke` +* Tune :attr:`~isaaclab_newton.physics.NewtonSoftContactCfg.soft_contact_ke` first. Increase it only as much as needed to prevent clipping, then adjust - :attr:`~isaaclab_contrib.deformable.NewtonModelCfg.soft_contact_mu` so the + :attr:`~isaaclab_newton.physics.NewtonSoftContactCfg.soft_contact_mu` so the gripper can carry the object without requiring an obviously unphysical friction value. Use - :attr:`~isaaclab_contrib.deformable.NewtonModelCfg.soft_contact_kd` for + :attr:`~isaaclab_newton.physics.NewtonSoftContactCfg.soft_contact_kd` for stabilization if contacts chatter. + Set this configuration on the outer + :attr:`~isaaclab_newton.physics.NewtonCfg.soft_contact_cfg` field. * Tune the ``soft_contact_*`` values together with the rigid shape contact material, because the shape's ``ke``/``kd``/``mu`` also affect the effective contact. Set shape defaults via @@ -254,7 +256,7 @@ Tune the coupled contact behavior before training a policy: increase ``particle_radius`` in the deformable material so contact is detected earlier from a larger distance. * If the rigid shapes still clip through the deformable, increase - :attr:`~isaaclab_contrib.deformable.VBDSolverCfg.iterations`; more VBD + :attr:`~isaaclab_newton.physics.VBDSolverCfg.iterations`; more VBD iterations can improve contact convergence. diff --git a/docs/source/overview/core-concepts/physical-backends/newton/supported-features.rst b/docs/source/overview/core-concepts/physical-backends/newton/supported-features.rst index 9fa30170a5c..5e39b1f37b0 100644 --- a/docs/source/overview/core-concepts/physical-backends/newton/supported-features.rst +++ b/docs/source/overview/core-concepts/physical-backends/newton/supported-features.rst @@ -56,6 +56,8 @@ isaaclab isaaclab_newton ^^^^^^^^^^^^^^^^ +* Standalone VBD deformable solver + * Implicit Material Point Method (MPM) solver and declarative particle assets * Fixed and capture-compatible capacity-bounded sparse MPM grids * Standard visual-material binding for MPM particle visualization @@ -63,9 +65,8 @@ isaaclab_newton isaaclab_contrib ^^^^^^^^^^^^^^^^ -* Experimental Newton deformable objects -* VBD deformable solver (see :doc:`using-vbd-solver`) -* MJWarp and VBD proxy coupling +* Newton deformable object integration +* MJWarp and VBD proxy and ADMM coupling * Proxy-based coupling for rigid and particle solvers, including MJWarp + MPM The following sensors are backend-agnostic (implemented in ``isaaclab`` core) @@ -112,10 +113,10 @@ Solver Coverage * **Kamino solver**: beta. Currently validated on ``Isaac-Cartpole-Direct``, ``Isaac-Ant-Direct``, ``Isaac-Cartpole``, and ``Isaac-Ant``. See :doc:`kamino-solver`. -* **VBD solver**: experimental, exposed through :mod:`isaaclab_contrib.deformable` - for cloth, soft-body, and cable simulation. Rigid and deformable scenes can use - proxy coupling from :mod:`isaaclab_contrib.coupling` so MJWarp advances rigid - bodies and VBD advances deformable particles. Cable objects work with standalone +* **VBD solver**: experimental, exposed through :mod:`isaaclab_newton.physics` + for standalone cloth, soft-body, and cable simulation. Rigid and deformable + scenes can use proxy or ADMM coupling from :mod:`isaaclab_contrib.coupling` so + MJWarp advances rigid bodies and VBD advances deformable particles. Cable objects work with standalone VBD and with :class:`~isaaclab_contrib.coupling.CouplerProxyCfg` when a named VBD entry owns the cable. See :doc:`using-vbd-solver` and :doc:`newton-manager-abstraction`. diff --git a/docs/source/overview/core-concepts/physical-backends/newton/using-cables.rst b/docs/source/overview/core-concepts/physical-backends/newton/using-cables.rst index f50afdc5b6e..4ea02d2b795 100644 --- a/docs/source/overview/core-concepts/physical-backends/newton/using-cables.rst +++ b/docs/source/overview/core-concepts/physical-backends/newton/using-cables.rst @@ -132,13 +132,11 @@ Picking a Solver Cables can **only** be simulated under Newton's VBD solver, which is the only solver that steps ``JointType.CABLE`` joints. A cable-only scene uses a -standalone :class:`~isaaclab_contrib.deformable.VBDSolverCfg`: +standalone :class:`~isaaclab_newton.physics.VBDSolverCfg`: .. code-block:: python - from isaaclab_newton.physics import NewtonCfg - - from isaaclab_contrib.deformable import VBDSolverCfg + from isaaclab_newton.physics import NewtonCfg, VBDSolverCfg sim_cfg = sim_utils.SimulationCfg( dt=0.01, diff --git a/docs/source/overview/core-concepts/physical-backends/newton/using-vbd-solver.rst b/docs/source/overview/core-concepts/physical-backends/newton/using-vbd-solver.rst index b2b7c63c453..702c7bb2a5e 100644 --- a/docs/source/overview/core-concepts/physical-backends/newton/using-vbd-solver.rst +++ b/docs/source/overview/core-concepts/physical-backends/newton/using-vbd-solver.rst @@ -5,24 +5,22 @@ VBD Solver Vertex Block Descent (VBD) is a Newton solver for cloth and soft-body simulation. In Isaac Lab, VBD is enabled by selecting a -:class:`~isaaclab_newton.physics.NewtonCfg` whose ``solver_cfg`` is provided by -:mod:`isaaclab_contrib.deformable`. +:class:`~isaaclab_newton.physics.NewtonCfg` whose ``solver_cfg`` is a +:class:`~isaaclab_newton.physics.VBDSolverCfg`. -VBD support is experimental. The solver managers, configuration fields, and -recommended tuning values may change while Newton deformable support is under -active development. A task that works with PhysX or with Newton's MuJoCo-Warp -solver may still need deformable assets, materials, contacts, and coupling tuned -before it works well with VBD. +A task that works with PhysX or with Newton's MuJoCo-Warp solver may still need +deformable assets, materials, contacts, and coupling tuned 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 can use either: +:class:`~isaaclab_newton.physics.VBDSolverCfg` directly. For robot or +rigid-body scenes, the named-entry coupling options are: * :class:`~isaaclab_contrib.coupling.CouplerProxyCfg` for the proxy coupling used by the core Franka tasks. -* :class:`~isaaclab_contrib.custom_coupling.CoupledMJWarpVBDSolverCfg` for the - opt-in shared-model example with custom substep ordering. +* :class:`~isaaclab_contrib.coupling.CouplerAdmmCfg` for linearized ADMM + coupling between named solver entries. Start from a Supported Deformable Task -------------------------------------- @@ -42,8 +40,7 @@ Start from a Supported Deformable Task ./isaaclab.sh -i tetrahedralization -Before adding VBD to a new task, first run one of the experimental Franka -deformable tasks: +Before adding VBD to a new task, first run one of the Franka deformable tasks: .. tab-set:: @@ -86,9 +83,9 @@ Add a VBD Physics Preset Tasks that support multiple physics options usually store ``SimulationCfg.physics`` as a :class:`~isaaclab_tasks.utils.hydra.PresetCfg`. For deformable Newton tasks, -the preset is a plain :class:`~isaaclab_newton.physics.NewtonCfg` whose solver -config carries :class:`~isaaclab_contrib.deformable.NewtonModelCfg` through its -:class:`~isaaclab_contrib.deformable.NewtonModelSolverCfg` base class. +the preset is a plain :class:`~isaaclab_newton.physics.NewtonCfg`. Standalone +VBD and soft-contact configuration live in :mod:`isaaclab_newton.physics`, while +proxy and ADMM coupling live in :mod:`isaaclab_contrib.coupling`. The Franka soft-body and cloth tasks define task-specific proxy presets. @@ -102,8 +99,8 @@ The important pieces are: * Add :class:`~isaaclab_contrib.coupling.CouplerProxyMappingCfg` entries for the collidable rigid bodies exposed to VBD. Leave ``integrate_with_external_rigid_solver=False`` for proxy-coupled VBD entries. -* Set the solver config's ``model_cfg`` to a - :class:`~isaaclab_contrib.deformable.NewtonModelCfg` when body-particle or +* Set the outer :attr:`~isaaclab_newton.physics.NewtonCfg.soft_contact_cfg` to a + :class:`~isaaclab_newton.physics.NewtonSoftContactCfg` when body-particle or self-contact values need task-level tuning. * Keep the preset at the same config path used by the task's :class:`~isaaclab.sim.SimulationCfg`, for example ``env.sim.physics``. @@ -164,8 +161,8 @@ task, validate the following before tuning solver parameters: * Proxy-coupled robot tasks should expose only the collidable bodies needed for deformable contact. * Contact-heavy scenes usually need task-specific ``num_substeps``, - :class:`~isaaclab_contrib.deformable.VBDSolverCfg`, and - :class:`~isaaclab_contrib.deformable.NewtonModelCfg` values. Start from the + :class:`~isaaclab_newton.physics.VBDSolverCfg`, and + :class:`~isaaclab_newton.physics.NewtonSoftContactCfg` values. Start from the Franka soft-body or cloth preset that most closely resembles the scene. * Use a small visual smoke test before training. Confirm that the deformable spawns, renders, deforms, and contacts rigid bodies as expected. @@ -174,7 +171,7 @@ VBD Solver Parameters --------------------- The following fields are specific to -:class:`~isaaclab_contrib.deformable.VBDSolverCfg`. They are grouped by the part +:class:`~isaaclab_newton.physics.VBDSolverCfg`. They are grouped by the part of the solver they affect. Core Solve @@ -258,8 +255,8 @@ coupling when deformable contact is localized to selected rigid bodies. .. _newton-vbd-proxy-coupling: -Proxy-Coupled MJWarp + VBD --------------------------- +Coupled MJWarp + VBD +-------------------- :class:`~isaaclab_contrib.coupling.CouplerProxyCfg` is the coupling used by the core Franka tasks. It partitions the model between named solver entries and @@ -347,6 +344,11 @@ Key proxy-specific parameters: Body selectors must use full Newton body-label regexes, such as ``/World/envs/env_.*/Robot``. Proxy mappings also accept raw Newton body ids. +:class:`~isaaclab_contrib.coupling.CouplerAdmmCfg` provides linearized ADMM +coupling between the same named solver entries. Set ``contact_pairs`` to select +entry pairs explicitly, or leave it as ``None`` to detect every distinct pair. +Use ``iterations`` and ``rho`` to tune the ADMM solve. + Try the demo: .. tab-set:: @@ -378,8 +380,9 @@ Contact and Material Parameters Contact Model ^^^^^^^^^^^^^ -:class:`~isaaclab_contrib.deformable.NewtonModelCfg` applies contact parameters -to the finalized Newton model: +:class:`~isaaclab_newton.physics.NewtonSoftContactCfg` applies contact parameters +to the finalized Newton model through +:attr:`~isaaclab_newton.physics.NewtonCfg.soft_contact_cfg`: .. list-table:: :header-rows: 1 @@ -390,7 +393,7 @@ to the finalized Newton model: * - ``soft_contact_ke`` - Default: ``1.0e3`` [N/m]. Stiffness for body-particle and particle self-contact. Increase it to reduce clipping through rigid shapes or through other deformable particles. If it is too high, the object can stop visibly deforming or require more VBD iterations and substeps. * - ``soft_contact_kd`` - - Default: ``1.0e-2`` [N*s/m]. Contact damping. Increase it to reduce chatter or bouncing. Too much damping can make contact response sticky or overdamped. + - Default: ``1.0e1`` [N*s/m]. Contact damping. Increase it to reduce chatter or bouncing. Too much damping can make contact response sticky or overdamped. * - ``soft_contact_mu`` - Default: ``0.5``. Friction coefficient for body-particle and particle self-contact. Increase it when a gripper cannot carry the deformable object without slipping. @@ -467,12 +470,12 @@ Use the following sequence when bringing up a new VBD task: the expected range without rigid contact. 5. Increase ``num_substeps`` or decrease ``dt`` if the object is unstable before increasing stiffness further. -6. Increase :attr:`~isaaclab_contrib.deformable.VBDSolverCfg.iterations` when +6. Increase :attr:`~isaaclab_newton.physics.VBDSolverCfg.iterations` when contacts or stiff materials do not converge within a substep. -7. Tune :attr:`~isaaclab_contrib.deformable.NewtonModelCfg.soft_contact_ke` to +7. Tune :attr:`~isaaclab_newton.physics.NewtonSoftContactCfg.soft_contact_ke` to reduce rigid/deformable clipping, then tune - :attr:`~isaaclab_contrib.deformable.NewtonModelCfg.soft_contact_mu` for grip - and :attr:`~isaaclab_contrib.deformable.NewtonModelCfg.soft_contact_kd` for + :attr:`~isaaclab_newton.physics.NewtonSoftContactCfg.soft_contact_mu` for grip + and :attr:`~isaaclab_newton.physics.NewtonSoftContactCfg.soft_contact_kd` for chatter. 8. Enable self-contact only after body-particle contact is stable, then tune ``particle_self_contact_radius`` for active self-contact thickness, diff --git a/docs/source/tutorials/01_assets/run_deformable_object.rst b/docs/source/tutorials/01_assets/run_deformable_object.rst index dc4470f4aef..f5ce4aed08b 100644 --- a/docs/source/tutorials/01_assets/run_deformable_object.rst +++ b/docs/source/tutorials/01_assets/run_deformable_object.rst @@ -13,7 +13,8 @@ surface deformables (cloth), see the deformable demo at ``scripts/demos/deformab The deformable object API and schema define/modify functions are shared across backends, while deformable property and material configuration classes are backend-specific. PhysX simulates soft bodies using the Finite -Element Method (FEM); the Newton experimental backend uses VBD-based deformable support from +Element Method (FEM); the Newton experimental backend uses the core VBD solver from +:mod:`isaaclab_newton.physics` with the deformable object integration from :mod:`isaaclab_contrib.deformable`. The volume deformable comprises of two tetrahedral meshes -- a simulation mesh and a collision mesh. The simulation mesh is used to simulate the deformations of the soft body, while the collision mesh is used to detect collisions diff --git a/scripts/demos/deformables.py b/scripts/demos/deformables.py index 5ecd9b41c9e..cdc53bc8e27 100644 --- a/scripts/demos/deformables.py +++ b/scripts/demos/deformables.py @@ -70,7 +70,7 @@ from isaaclab.assets import DeformableObject if args_cli.physics == "newton_vbd": - from isaaclab_contrib.deformable.newton_manager_cfg import NewtonModelCfg # isort:skip + from isaaclab_newton.physics import NewtonSoftContactCfg # isort:skip from isaaclab_newton.sim.schemas import NewtonDeformableBodyPropertiesCfg as DeformableBodyPropertiesCfg from isaaclab_newton.sim.spawners.materials import ( NewtonDeformableBodyMaterialCfg as VolumeDeformableMaterialCfg, @@ -270,7 +270,7 @@ def main(): physics_cfg.solver_cfg.particle_self_contact_radius = 0.0001 physics_cfg.solver_cfg.particle_self_contact_margin = 0.1 physics_cfg.num_substeps = 4 - physics_cfg.solver_cfg.model_cfg = NewtonModelCfg( + physics_cfg.soft_contact_cfg = NewtonSoftContactCfg( soft_contact_ke=1.0e5, soft_contact_kd=1.0e0, soft_contact_mu=0.01, diff --git a/scripts/demos/newton_viewer_block_and_tackle.py b/scripts/demos/newton_viewer_block_and_tackle.py index 5ce4ec1acd3..616aa7a7af7 100644 --- a/scripts/demos/newton_viewer_block_and_tackle.py +++ b/scripts/demos/newton_viewer_block_and_tackle.py @@ -24,13 +24,11 @@ import newton import newton.utils import warp as wp -from isaaclab_newton.physics import NewtonCfg, NewtonManager, NewtonShapeCfg +from isaaclab_newton.physics import NewtonCfg, NewtonManager, NewtonShapeCfg, VBDSolverCfg import isaaclab.sim as sim_utils from isaaclab.utils.configclass import configclass -from isaaclab_contrib.deformable import VBDSolverCfg - MECHANICAL_ADVANTAGE = 4 LOAD_MASS = 5.0 HANDLE_MASS = 0.5 # Compensated in the load mass; raises Newton's picking-force limit. diff --git a/source/isaaclab/changelog.d/mmichelis-vbd-core.rst b/source/isaaclab/changelog.d/mmichelis-vbd-core.rst new file mode 100644 index 00000000000..39c88d13632 --- /dev/null +++ b/source/isaaclab/changelog.d/mmichelis-vbd-core.rst @@ -0,0 +1,7 @@ +Changed +^^^^^^^ + +* Changed the ``newton_vbd`` launcher backend to use + :class:`~isaaclab_newton.physics.VBDSolverCfg` from core. Custom launchers + should import the solver configuration from ``isaaclab_newton.physics`` + instead of ``isaaclab_contrib.deformable``. diff --git a/source/isaaclab/isaaclab/app/sim_launcher.py b/source/isaaclab/isaaclab/app/sim_launcher.py index b3c2fff5d81..8f0c3fbd009 100644 --- a/source/isaaclab/isaaclab/app/sim_launcher.py +++ b/source/isaaclab/isaaclab/app/sim_launcher.py @@ -22,7 +22,7 @@ from dataclasses import dataclass from typing import Any -from isaaclab_newton.physics import NewtonCfg +from isaaclab_newton.physics import NewtonCfg, VBDSolverCfg from isaaclab_ov.physics import OvPhysxCfg from isaaclab_ov.renderers import OVRTXRendererCfg from isaaclab_physx.physics import PhysxCfg @@ -74,15 +74,6 @@ def make_physics_cfg(physics_cfg_str: str) -> PhysicsCfg: if physics_cfg_str == "newton_mjwarp": return NewtonCfg() if physics_cfg_str == "newton_vbd": - # lazy import: core depends on isaaclab_contrib only when VBD is requested - try: - from isaaclab_contrib.deformable.newton_manager_cfg import VBDSolverCfg - except ImportError as err: - raise ImportError( - "The 'newton_vbd' physics backend requires the isaaclab_contrib package." - " Install it with `./isaaclab.sh -i contrib`." - ) from err - return NewtonCfg(solver_cfg=VBDSolverCfg()) if physics_cfg_str == "ovphysx": return OvPhysxCfg() diff --git a/source/isaaclab/test/app/test_kwarg_launch.py b/source/isaaclab/test/app/test_kwarg_launch.py index 249fad06891..268ac7d9bba 100644 --- a/source/isaaclab/test/app/test_kwarg_launch.py +++ b/source/isaaclab/test/app/test_kwarg_launch.py @@ -7,6 +7,7 @@ import logging import pytest +from isaaclab_newton.physics import NewtonCfg, VBDSolverCfg import isaaclab.app as app_module import isaaclab.app.app_launcher as app_launcher_module @@ -18,6 +19,13 @@ pytestmark = pytest.mark.integration +def test_make_physics_cfg_builds_core_vbd(): + physics_cfg = sim_launcher.make_physics_cfg("newton_vbd") + + assert isinstance(physics_cfg, NewtonCfg) + assert isinstance(physics_cfg.solver_cfg, VBDSolverCfg) + + @pytest.mark.usefixtures("mocker") def test_livestream_launch_with_kwargs(mocker): """Test launching with keyword arguments.""" diff --git a/source/isaaclab_contrib/changelog.d/mmichelis-vbd-core.major.rst b/source/isaaclab_contrib/changelog.d/mmichelis-vbd-core.major.rst new file mode 100644 index 00000000000..c7cd90e35e9 --- /dev/null +++ b/source/isaaclab_contrib/changelog.d/mmichelis-vbd-core.major.rst @@ -0,0 +1,13 @@ +Changed +^^^^^^^ + +* **Breaking:** Moved the standalone VBD solver from + ``isaaclab_contrib.deformable`` to :mod:`isaaclab_newton.physics`. Import + :class:`~isaaclab_newton.physics.NewtonVBDManager` and + :class:`~isaaclab_newton.physics.VBDSolverCfg` from their new location, and + move ``NewtonModelCfg`` and ``NewtonModelSolverCfg`` soft-contact settings to + :attr:`~isaaclab_newton.physics.NewtonCfg.soft_contact_cfg`. +* **Breaking:** Removed the + ``isaaclab_contrib.deformable.CoupledMJWarpVBDSolverCfg`` compatibility + alias. Import + :class:`~isaaclab_contrib.custom_coupling.CoupledMJWarpVBDSolverCfg` instead. diff --git a/source/isaaclab_contrib/isaaclab_contrib/coupling/coupler.py b/source/isaaclab_contrib/isaaclab_contrib/coupling/coupler.py index c69aeb3d413..9cd38559578 100644 --- a/source/isaaclab_contrib/isaaclab_contrib/coupling/coupler.py +++ b/source/isaaclab_contrib/isaaclab_contrib/coupling/coupler.py @@ -21,13 +21,13 @@ ) from isaaclab_newton.physics.mpm_manager import NewtonMPMManager from isaaclab_newton.physics.newton_manager import NewtonManager +from isaaclab_newton.physics.vbd_manager import NewtonVBDManager from newton import CollisionPipeline, Model, ModelBuilder, ShapeFlags from newton.solvers.experimental.coupled import SolverCoupled, SolverCoupledADMM, SolverCoupledProxy from isaaclab.physics import PhysicsManager from isaaclab.utils.string import resolve_matching_names -from ..deformable.vbd_manager import NewtonVBDManager from .coupler_cfg import ( CouplerAdmmCfg, CouplerCfg, @@ -141,11 +141,6 @@ def _validate_config(cls, solver_cfg: CouplerCfg) -> None: raise ValueError( f"CouplerEntryCfg {entry.name!r} contains a nested CouplerCfg; nested couplers are not supported." ) - if getattr(nested_cfg, "model_cfg", None) is not None: - raise ValueError( - f"CouplerEntryCfg {entry.name!r} sets solver_cfg.model_cfg, but model parameters are global. " - "Set model_cfg on the outer CouplerCfg instead." - ) manager = nested_cfg.class_type factory = getattr(manager, "_create_solver", None) if not callable(factory) or getattr(factory, "__func__", factory) is NewtonManager._create_solver.__func__: diff --git a/source/isaaclab_contrib/isaaclab_contrib/coupling/coupler_cfg.py b/source/isaaclab_contrib/isaaclab_contrib/coupling/coupler_cfg.py index 82c433d97ed..03c87860ace 100644 --- a/source/isaaclab_contrib/isaaclab_contrib/coupling/coupler_cfg.py +++ b/source/isaaclab_contrib/isaaclab_contrib/coupling/coupler_cfg.py @@ -20,8 +20,6 @@ from isaaclab.utils.configclass import configclass -from ..deformable.newton_manager_cfg import NewtonModelSolverCfg - if TYPE_CHECKING: from isaaclab_newton.physics import NewtonManager from newton import CollisionPipeline @@ -130,7 +128,7 @@ class CouplerProxyMappingCfg: @configclass -class CouplerCfg(NewtonModelSolverCfg): +class CouplerCfg(NewtonSolverCfg): """Base configuration for a Newton experimental coupled solver. Bodies, particles, joints, and shapes may be assigned to at most one diff --git a/source/isaaclab_contrib/isaaclab_contrib/custom_coupling/coupled_mjwarp_vbd_manager.py b/source/isaaclab_contrib/isaaclab_contrib/custom_coupling/coupled_mjwarp_vbd_manager.py index 1dcdafdb847..ddde78d1048 100644 --- a/source/isaaclab_contrib/isaaclab_contrib/custom_coupling/coupled_mjwarp_vbd_manager.py +++ b/source/isaaclab_contrib/isaaclab_contrib/custom_coupling/coupled_mjwarp_vbd_manager.py @@ -9,11 +9,10 @@ import warp as wp from isaaclab_newton.physics.newton_manager import NewtonManager +from isaaclab_newton.physics.vbd_manager import NewtonVBDManager from newton import Contacts, Control, Model, State from newton.solvers import SolverBase, SolverMuJoCo, SolverVBD -from isaaclab_contrib.deformable.vbd_manager import NewtonVBDManager - from .kernels import _kernel_body_particle_reaction from .newton_manager_cfg import CoupledMJWarpVBDSolverCfg diff --git a/source/isaaclab_contrib/isaaclab_contrib/custom_coupling/franka_soft_env_cfg.py b/source/isaaclab_contrib/isaaclab_contrib/custom_coupling/franka_soft_env_cfg.py index b4fce318d40..273e45dd875 100644 --- a/source/isaaclab_contrib/isaaclab_contrib/custom_coupling/franka_soft_env_cfg.py +++ b/source/isaaclab_contrib/isaaclab_contrib/custom_coupling/franka_soft_env_cfg.py @@ -5,12 +5,10 @@ """Franka soft lifting environment using the custom coupling manager.""" -from isaaclab_newton.physics import MJWarpSolverCfg +from isaaclab_newton.physics import MJWarpSolverCfg, VBDSolverCfg from isaaclab.utils.configclass import configclass -from isaaclab_contrib.deformable.newton_manager_cfg import VBDSolverCfg - from isaaclab_tasks.core.lift.config.franka_soft.franka_soft_env_cfg import FrankaSoftEnvCfg from isaaclab_tasks.core.lift.config.franka_soft.franka_soft_env_cfg import PhysicsCfg as CorePhysicsCfg @@ -35,7 +33,6 @@ class PhysicsCfg(CorePhysicsCfg): soft_solver_cfg=VBDSolverCfg( integrate_with_external_rigid_solver=True, ), - model_cfg=CorePhysicsCfg().newton_mjwarp_vbd_proxy.solver_cfg.model_cfg, ), ) diff --git a/source/isaaclab_contrib/isaaclab_contrib/custom_coupling/newton_manager_cfg.py b/source/isaaclab_contrib/isaaclab_contrib/custom_coupling/newton_manager_cfg.py index 6ebac8e6b44..13729d521bf 100644 --- a/source/isaaclab_contrib/isaaclab_contrib/custom_coupling/newton_manager_cfg.py +++ b/source/isaaclab_contrib/isaaclab_contrib/custom_coupling/newton_manager_cfg.py @@ -9,18 +9,16 @@ from typing import TYPE_CHECKING, Literal -from isaaclab_newton.physics import MJWarpSolverCfg +from isaaclab_newton.physics import MJWarpSolverCfg, NewtonSolverCfg, VBDSolverCfg from isaaclab.utils.configclass import configclass -from isaaclab_contrib.deformable.newton_manager_cfg import NewtonModelSolverCfg, VBDSolverCfg - if TYPE_CHECKING: from isaaclab_newton.physics import NewtonManager @configclass -class CoupledMJWarpVBDSolverCfg(NewtonModelSolverCfg): +class CoupledMJWarpVBDSolverCfg(NewtonSolverCfg): """Configuration for the custom MJWarp and VBD coupling manager.""" class_type: type[NewtonManager] | str = "{DIR}.coupled_mjwarp_vbd_manager:NewtonCoupledMJWarpVBDManager" diff --git a/source/isaaclab_contrib/isaaclab_contrib/custom_coupling/tasks.py b/source/isaaclab_contrib/isaaclab_contrib/custom_coupling/tasks.py index 41a54d156f7..45ddaac048c 100644 --- a/source/isaaclab_contrib/isaaclab_contrib/custom_coupling/tasks.py +++ b/source/isaaclab_contrib/isaaclab_contrib/custom_coupling/tasks.py @@ -5,10 +5,8 @@ """Gym registration for the opt-in custom coupling example. -Registration lives here rather than in the package ``__init__`` so that importing the -example's library modules, including from the deprecated -:class:`~isaaclab_contrib.deformable.CoupledMJWarpVBDSolverCfg`, does not register the -task. +Registration lives here rather than in the package ``__init__`` so importing the +example's library modules does not register the task. """ import gymnasium as gym diff --git a/source/isaaclab_contrib/isaaclab_contrib/deformable/__init__.pyi b/source/isaaclab_contrib/isaaclab_contrib/deformable/__init__.pyi index 385ad5977db..7b61168e4d9 100644 --- a/source/isaaclab_contrib/isaaclab_contrib/deformable/__init__.pyi +++ b/source/isaaclab_contrib/isaaclab_contrib/deformable/__init__.pyi @@ -4,19 +4,9 @@ # SPDX-License-Identifier: BSD-3-Clause __all__ = [ - "CoupledMJWarpVBDSolverCfg", "DeformableObject", "DeformableObjectData", - "NewtonModelCfg", - "NewtonModelSolverCfg", - "VBDSolverCfg", ] from .deformable_object import DeformableObject from .deformable_object_data import DeformableObjectData -from .newton_manager_cfg import ( - CoupledMJWarpVBDSolverCfg, - NewtonModelCfg, - NewtonModelSolverCfg, - VBDSolverCfg, -) diff --git a/source/isaaclab_contrib/isaaclab_contrib/deformable/newton_manager_cfg.py b/source/isaaclab_contrib/isaaclab_contrib/deformable/newton_manager_cfg.py deleted file mode 100644 index 7bcbbe1c5b5..00000000000 --- a/source/isaaclab_contrib/isaaclab_contrib/deformable/newton_manager_cfg.py +++ /dev/null @@ -1,160 +0,0 @@ -# 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 - -"""Configuration classes for VBD and global Newton model parameters.""" - -from __future__ import annotations - -import warnings -from typing import TYPE_CHECKING, Literal - -from isaaclab_newton.physics import MJWarpSolverCfg, NewtonSolverCfg - -from isaaclab.utils.configclass import configclass - -if TYPE_CHECKING: - from isaaclab_newton.physics import NewtonManager - - -@configclass -class NewtonModelCfg: - """Global Newton model parameters applied after builder finalization. - - These control model-level contact behavior shared across all objects. - """ - - soft_contact_ke: float = 1.0e3 - """Body-particle and particle self-contact stiffness [N/m]. - - Effective per-contact stiffness is the average of this value and the rigid - shape's material stiffness. - """ - - soft_contact_kd: float = 1.0e-2 - """Body-particle contact damping [N*s/m].""" - - soft_contact_mu: float = 0.5 - """Body-particle contact friction coefficient [dimensionless]. - - Effective per-contact friction is ``sqrt(soft_contact_mu * shape_mu)``, where - ``shape_mu`` is the rigid shape's own friction coefficient (from its per-asset - material or :attr:`~isaaclab_newton.physics.NewtonShapeCfg.mu` default), not - set by this config. - """ - - -@configclass -class NewtonModelSolverCfg(NewtonSolverCfg): - """Base for solver configs whose manager applies :class:`NewtonModelCfg` to the finalized model. - - TODO: Temporary. This base only exists because :class:`NewtonModelCfg` lives in - ``isaaclab_contrib`` while :class:`NewtonSolverCfg` is in ``isaaclab_newton`` core. - Once these model params move into core, ``model_cfg`` should live on - :class:`NewtonSolverCfg` (or ``NewtonCfg``) directly and this class can be removed. - """ - - model_cfg: NewtonModelCfg | None = None - """Global Newton model parameters applied after builder finalization.""" - - -@configclass -class VBDSolverCfg(NewtonModelSolverCfg): - """Configuration for the Vertex Block Descent (VBD) solver. - - Supports cloth, soft bodies, and coupled rigid-body systems. Requires - ``ModelBuilder.color()`` before ``finalize()`` to build the vertex coloring. - """ - - class_type: type[NewtonManager] | str = "{DIR}.vbd_manager:NewtonVBDManager" - """Manager class for the VBD solver.""" - - iterations: int = 10 - """Number of VBD iterations per substep.""" - - integrate_with_external_rigid_solver: bool = False - """Whether rigid bodies are integrated by an external solver (one-way coupling). - - Set to ``True`` when coupling cloth with a separate rigid-body solver so VBD - only integrates the cloth particles. - """ - - particle_enable_self_contact: bool = False - """Whether to enable VBD deformable's self-contact.""" - - particle_self_contact_radius: float = 0.005 - """Particle radius used for self-contact detection [m].""" - - particle_self_contact_margin: float = 0.005 - """Self-contact detection margin [m]. Should be >= particle_self_contact_radius.""" - - particle_collision_detection_interval: int = -1 - """How often particle self-contact detection is applied. - - ``< 0``: once before initialization. ``0``: once before and once after - initialization. ``k >= 1``: before every ``k`` VBD iterations. - """ - - particle_vertex_contact_buffer_size: int = 32 - """Preallocation size for each vertex's vertex-triangle collision buffer.""" - - particle_edge_contact_buffer_size: int = 64 - """Preallocation size for each edge's edge-edge collision buffer.""" - - particle_topological_contact_filter_threshold: int = 2 - """Maximum topological distance (in rings) below which self-contacts are discarded. - - Only used when ``particle_enable_self_contact`` is ``True``. Values > 3 - significantly increase computation time. - """ - - particle_rest_shape_contact_exclusion_radius: float = 0.0 - """Rest-configuration separation threshold for filtering close primitives [m]. - - Only used when ``particle_enable_self_contact`` is ``True``. - """ - - rigid_contact_k_start: float = 1.0e2 - """Initial stiffness seed for all rigid body contacts [N/m].""" - - rigid_body_particle_contact_buffer_size: int = 256 - """Per-body capacity of the particle, edge, and face soft-contact list. - - Contacts past this count are dropped from the body's reaction list: the particles are still - pushed but the body does not recoil, injecting energy. Newton prints ``Per-body particle - contact buffer overflowed N > size`` on overflow; raise this above the observed ``N``. Only - used when VBD integrates the rigid bodies itself, i.e. - :attr:`integrate_with_external_rigid_solver` is ``False``. - """ - - -@configclass -class CoupledMJWarpVBDSolverCfg(NewtonModelSolverCfg): - """Deprecated configuration for the coupled MJWarp and VBD solver. - - .. deprecated:: 0.5.0 - Use :class:`isaaclab_contrib.custom_coupling.CoupledMJWarpVBDSolverCfg`. - """ - - class_type: type[NewtonManager] | str = ( - "isaaclab_contrib.custom_coupling.coupled_mjwarp_vbd_manager:NewtonCoupledMJWarpVBDManager" - ) - """Manager class for the coupled MJWarp and VBD solver.""" - - rigid_solver_cfg: MJWarpSolverCfg = MJWarpSolverCfg() - """Rigid-body sub-solver configuration.""" - - soft_solver_cfg: VBDSolverCfg = VBDSolverCfg(integrate_with_external_rigid_solver=True) - """VBD sub-solver configuration.""" - - coupling_mode: Literal["one_way", "two_way"] = "two_way" - """Coupling direction between the rigid and VBD solvers.""" - - def __post_init__(self) -> None: - warnings.warn( - "isaaclab_contrib.deformable.CoupledMJWarpVBDSolverCfg is deprecated. " - "Use isaaclab_contrib.custom_coupling.CoupledMJWarpVBDSolverCfg.", - DeprecationWarning, - stacklevel=2, - ) diff --git a/source/isaaclab_contrib/isaaclab_contrib/deformable/vbd_manager.py b/source/isaaclab_contrib/isaaclab_contrib/deformable/vbd_manager.py deleted file mode 100644 index ca49ec37981..00000000000 --- a/source/isaaclab_contrib/isaaclab_contrib/deformable/vbd_manager.py +++ /dev/null @@ -1,259 +0,0 @@ -# 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 - -"""VBD Newton manager.""" - -from __future__ import annotations - -import logging -from typing import TYPE_CHECKING - -import warp as wp -from isaaclab_newton.physics.newton_manager import NewtonManager -from newton import Model -from newton._src.usd.schemas import SchemaResolverNewton, SchemaResolverPhysx -from newton.solvers import SolverVBD - -from isaaclab.sim.utils.stage import get_current_stage - -from .deformable_object import ( - add_deformable_entry_to_builder, - clear_deformable_builder_hooks, - install_deformable_builder_hooks, - setup_registered_deformable_fabric_sync, -) -from .newton_manager_cfg import NewtonModelSolverCfg, VBDSolverCfg - -if TYPE_CHECKING: - from isaaclab.sim.simulation_context import SimulationContext - -logger = logging.getLogger(__name__) - - -def _apply_model_cfg(model: Model) -> None: - """Apply the active solver cfg's :class:`NewtonModelCfg` to the finalized model. - - Sets the model-global ``soft_contact_ke/kd/mu``. Per-shape material defaults - are applied earlier via ``builder.default_shape_cfg``, not here. - """ - from isaaclab.physics import PhysicsManager - - solver_cfg = getattr(PhysicsManager._cfg, "solver_cfg", None) - if not isinstance(solver_cfg, NewtonModelSolverCfg) or solver_cfg.model_cfg is None: - return - - model_cfg = solver_cfg.model_cfg - model.soft_contact_ke = float(model_cfg.soft_contact_ke) - model.soft_contact_kd = float(model_cfg.soft_contact_kd) - model.soft_contact_mu = float(model_cfg.soft_contact_mu) - - -class NewtonVBDManager(NewtonManager): - """:class:`NewtonManager` specialization for the VBD solver. - - Always uses Newton's :class:`CollisionPipeline` for contact handling. - """ - - @classmethod - def initialize(cls, sim_context: SimulationContext) -> None: - """Initialize the manager with simulation context. - - Args: - sim_context: Parent simulation context. - - TODO: Subclass should not override this method, once deformables - supported on Newton import_usd, this can be unified with NewtonManager's - implementation. - """ - - # Deformable body registry and extension hooks. - # Experimental deformable support registers callbacks here so the manager - # and cloner can invoke them without hard-coding deformable logic. - install_deformable_builder_hooks() - - super().initialize(sim_context) - - @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``. - - For each registered deformable body, both the simulation mesh (which - carries ``UsdPhysics.CollisionAPI``) and the visual mesh are returned. - The sim mesh must be skipped so Newton does not create a redundant - static mesh collider alongside the particles produced by - ``add_soft_mesh``. The visual mesh is skipped so Newton does not - treat it as a collider — Kit reads it directly from USD for rendering. - - Paths may contain regex patterns; Newton's ``add_usd`` matches them - via :func:`re.match`. - """ - paths: list[str] = [] - for entry in cls._deformable_registry: - paths.append(entry.sim_mesh_prim_path) - paths.append(entry.vis_mesh_prim_path) - return paths - - @classmethod - def start_simulation(cls) -> None: - """Start simulation by finalizing model and initializing state. - - This function finalizes the model and initializes the simulation state. - Note: Collision pipeline is initialized later in initialize_solver() after - we determine whether the solver needs external collision detection. - - TODO: Subclass should not override this method, missing piece is - having Newton bind a surface mesh to volume deformable tetrahedral mesh - in addition to removing the deformable_registry data structure. - """ - # Color the replicated builder before finalization. - if cls._builder is not None: - cls._builder.color() - super().start_simulation() - - if cls._model is not None: - _apply_model_cfg(cls._model) - - # Setup USD/Fabric sync for Kit viewport deformable rendering - setup_registered_deformable_fabric_sync(cls) - - @classmethod - def instantiate_builder_from_stage(cls): - """Create builder from USD stage with special treatment for deformable - bodies, as these are not read from USD yet. - - Detects env Xforms (e.g. ``/World/Env_0``, ``/World/Env_1``) and builds - each as a separate Newton world via ``begin_world``/``end_world``. - Falls back to a flat ``add_usd`` when no env Xforms are found. - - TODO: Subclass should not override this method, once deformables - supported on Newton import_usd, this can be unified with NewtonManager's - implementation. - """ - import re - - from pxr import UsdGeom - - stage = get_current_stage() - up_axis = UsdGeom.GetStageUpAxis(stage) - - # Scan /World children for env-like Xforms (Env_0, env_1, ...) - env_pattern = re.compile(r"^[Ee]nv_(\d+)$") - world_prim = stage.GetPrimAtPath("/World") - env_paths: list[tuple[int, str]] = [] - if world_prim and world_prim.IsValid(): - for child in world_prim.GetChildren(): - m = env_pattern.match(child.GetName()) - if m: - env_paths.append((int(m.group(1)), child.GetPath().pathString)) - env_paths.sort(key=lambda x: x[0]) - - builder = cls.create_builder(up_axis=up_axis) - - schema_resolvers = [SchemaResolverNewton(), SchemaResolverPhysx()] - - # Deformable sim/visual mesh paths must be skipped by ``add_usd`` - # so they don't get duplicated as static colliders. - deformable_ignore_paths = cls._get_deformable_ignore_paths() - - if not env_paths: - # No env Xforms — flat loading - builder.add_usd(stage, ignore_paths=deformable_ignore_paths, schema_resolvers=schema_resolvers) - - # Add deformable bodies from the registry (single world at origin). - for entry in cls._deformable_registry: - add_deformable_entry_to_builder(builder, entry, 0, [0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 1.0]) - else: - # Load everything except the env subtrees (ground plane, lights, etc.) - ignore_paths = [path for _, path in env_paths] + deformable_ignore_paths - builder.add_usd(stage, ignore_paths=ignore_paths, schema_resolvers=schema_resolvers) - - # Build a prototype from the first env (all envs assumed identical) - _, proto_path = env_paths[0] - proto = cls.create_builder(up_axis=up_axis) - proto.add_usd( - stage, - root_path=proto_path, - ignore_paths=deformable_ignore_paths, - schema_resolvers=schema_resolvers, - ) - - # Inject registered sites into the proto before replication - global_sites, proto_sites, world_sites = cls._cl_inject_sites(builder, {proto_path: proto}) - global_site_map: dict[str, tuple[int, None]] = {label: (idx, None) for label, idx in global_sites.items()} - num_worlds = len(env_paths) - local_site_map: dict[str, list[list[int]]] = {} - site_entries = proto_sites.get(id(proto), {}) - - # Add each env as a separate Newton world - xform_cache = UsdGeom.XformCache() - for col, (_, env_path) in enumerate(env_paths): - builder.begin_world() - offset = builder.shape_count - world_xform = xform_cache.GetLocalToWorldTransform(stage.GetPrimAtPath(env_path)) - translation = world_xform.ExtractTranslation() - rotation = world_xform.ExtractRotationQuat() - pos = (translation[0], translation[1], translation[2]) - quat = ( - rotation.GetImaginary()[0], - rotation.GetImaginary()[1], - rotation.GetImaginary()[2], - rotation.GetReal(), - ) - env_xform = wp.transform(pos, quat) - builder.add_builder(proto, xform=env_xform) - for label, xform in world_sites.items(): - if label not in local_site_map: - local_site_map[label] = [[] for _ in range(num_worlds)] - site_idx = builder.add_site(body=-1, xform=wp.transform_multiply(env_xform, xform), label=label) - local_site_map[label][col].append(site_idx) - for label, proto_shape_indices in site_entries.items(): - if label not in local_site_map: - local_site_map[label] = [[] for _ in range(num_worlds)] - for proto_shape_idx in proto_shape_indices: - local_site_map[label][col].append(offset + proto_shape_idx) - - # Add deformable bodies from the registry into this world. - for entry in cls._deformable_registry: - add_deformable_entry_to_builder(builder, entry, col, list(pos), quat) - - builder.end_world() - - NewtonManager._cl_site_index_map = { - **global_site_map, - **{label: (None, per_world) for label, per_world in local_site_map.items()}, - } - NewtonManager._num_envs = len(env_paths) - - builder.color() - cls.set_builder(builder) - - @classmethod - def _create_solver(cls, model: Model, solver_cfg: VBDSolverCfg) -> SolverVBD: - """Construct the configured VBD solver.""" - return SolverVBD(model, **cls._filter_solver_kwargs(SolverVBD, solver_cfg)) - - @classmethod - def _build_solver(cls, model: Model, solver_cfg: VBDSolverCfg) -> None: - """Construct :class:`SolverVBD` and populate the base-class slots. - - VBD always uses Newton's :class:`CollisionPipeline` and steps with - separate input/output states, so the flags are fixed. - """ - NewtonManager._solver = cls._create_solver(model, solver_cfg) - NewtonManager._use_single_state = False - NewtonManager._needs_collision_pipeline = True - NewtonManager._supports_rigid_body_force_input = not solver_cfg.integrate_with_external_rigid_solver - - @classmethod - def _simulate_physics_only(cls) -> None: - # Rebuild BVH once per step for solvers that require it (e.g. VBD cloth). - if cls._model.particle_count > 0 and hasattr(cls._solver, "rebuild_bvh"): - cls._solver.rebuild_bvh(cls._state_0) - super()._simulate_physics_only() diff --git a/source/isaaclab_contrib/test/coupling/test_coupler.py b/source/isaaclab_contrib/test/coupling/test_coupler.py index 5bf2a3fe6dc..53c47c87310 100644 --- a/source/isaaclab_contrib/test/coupling/test_coupler.py +++ b/source/isaaclab_contrib/test/coupling/test_coupler.py @@ -25,6 +25,8 @@ MJWarpSolverCfg, MPMSolverCfg, NewtonCollisionPipelineCfg, + NewtonVBDManager, + VBDSolverCfg, XPBDSolverCfg, ) from isaaclab_newton.physics.newton_manager import NewtonManager @@ -40,8 +42,6 @@ NewtonCouplerManager, coupler, ) -from isaaclab_contrib.deformable.newton_manager_cfg import NewtonModelCfg, VBDSolverCfg -from isaaclab_contrib.deformable.vbd_manager import NewtonVBDManager @dataclass @@ -168,7 +168,6 @@ def test_config_validation_requires_newton_solver_config(): ("solver_cfg", "entry_kwargs", "error_type", "match"), [ (CouplerProxyCfg(), {}, ValueError, "nested CouplerCfg"), - (VBDSolverCfg(model_cfg=NewtonModelCfg()), {}, ValueError, "model parameters are global"), (KaminoPADMMSolverCfg(), {}, NotImplementedError, "FK/reset lifecycle"), ( MPMSolverCfg(project_outside_colliders=True), diff --git a/source/isaaclab_contrib/test/custom_coupling/test_manager.py b/source/isaaclab_contrib/test/custom_coupling/test_manager.py index 3ae1d62e809..33033387ccf 100644 --- a/source/isaaclab_contrib/test/custom_coupling/test_manager.py +++ b/source/isaaclab_contrib/test/custom_coupling/test_manager.py @@ -8,17 +8,11 @@ from unittest.mock import MagicMock import pytest -from isaaclab_newton.physics import MJWarpSolverCfg +from isaaclab_newton.physics import MJWarpSolverCfg, VBDSolverCfg import isaaclab_contrib.custom_coupling.coupled_mjwarp_vbd_manager as manager_module from isaaclab_contrib.custom_coupling.coupled_mjwarp_vbd_manager import NewtonCoupledMJWarpVBDManager from isaaclab_contrib.custom_coupling.newton_manager_cfg import CoupledMJWarpVBDSolverCfg -from isaaclab_contrib.deformable.newton_manager_cfg import ( - CoupledMJWarpVBDSolverCfg as LegacyMJWarpCfg, -) -from isaaclab_contrib.deformable.newton_manager_cfg import ( - VBDSolverCfg, -) def test_reset_forwards_to_both_subsolvers(monkeypatch: pytest.MonkeyPatch) -> None: @@ -105,15 +99,6 @@ def test_build_solver_sets_capabilities(monkeypatch: pytest.MonkeyPatch) -> None assert manager_module.NewtonManager._supports_rigid_body_force_input is True -def test_legacy_mjwarp_solver_config_warns() -> None: - with pytest.warns(DeprecationWarning, match="custom_coupling.CoupledMJWarpVBDSolverCfg"): - solver_cfg = LegacyMJWarpCfg() - - assert solver_cfg.class_type == ( - "isaaclab_contrib.custom_coupling.coupled_mjwarp_vbd_manager:NewtonCoupledMJWarpVBDManager" - ) - - @pytest.mark.parametrize("mode", ["one_way", "two_way"]) def test_step_preserves_input_forces(mode: str, monkeypatch: pytest.MonkeyPatch) -> None: state_in = MagicMock() diff --git a/source/isaaclab_contrib/test/custom_coupling/test_manual_coupling.py b/source/isaaclab_contrib/test/custom_coupling/test_manual_coupling.py index 473d4589c7f..beff8141dbf 100644 --- a/source/isaaclab_contrib/test/custom_coupling/test_manual_coupling.py +++ b/source/isaaclab_contrib/test/custom_coupling/test_manual_coupling.py @@ -19,7 +19,7 @@ import pytest import torch from isaaclab_newton.assets import Articulation, RigidObject -from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg +from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg, VBDSolverCfg from isaaclab_newton.sim.schemas import NewtonDeformableBodyPropertiesCfg from isaaclab_newton.sim.spawners.materials import NewtonDeformableBodyMaterialCfg @@ -29,7 +29,7 @@ from isaaclab.sim import SimulationCfg, build_simulation_context from isaaclab_contrib.custom_coupling import CoupledMJWarpVBDSolverCfg -from isaaclab_contrib.deformable import DeformableObject, VBDSolverCfg +from isaaclab_contrib.deformable import DeformableObject from isaaclab_assets import FRANKA_PANDA_CFG # isort:skip diff --git a/source/isaaclab_contrib/test/deformable/test_deformable_builder_hooks.py b/source/isaaclab_contrib/test/deformable/test_deformable_builder_hooks.py index db0b40492ff..124c9881f0b 100644 --- a/source/isaaclab_contrib/test/deformable/test_deformable_builder_hooks.py +++ b/source/isaaclab_contrib/test/deformable/test_deformable_builder_hooks.py @@ -13,13 +13,12 @@ from isaaclab_newton.physics import NewtonManager from isaaclab_newton.sim.spawners.materials import NewtonDeformableMaterialCfg -from isaaclab_contrib.deformable import DeformableObject, VBDSolverCfg +from isaaclab_contrib.deformable import DeformableObject from isaaclab_contrib.deformable.deformable_object import ( DeformableRegistryEntry, add_deformable_entry_to_builder, setup_registered_deformable_fabric_sync, ) -from isaaclab_contrib.deformable.vbd_manager import NewtonVBDManager class _FakeBuilder: @@ -82,23 +81,6 @@ def _vec3_as_tuple(value) -> tuple[float, float, float]: def test_deformable_package_exports_public_symbols(): """Test that deformable symbols are exported from the package root.""" assert DeformableObject.__name__ == "DeformableObject" - assert VBDSolverCfg.__name__ == "VBDSolverCfg" - - -@pytest.mark.parametrize("external_rigid_solver", [False, True]) -def test_vbd_solver_force_input_capability(monkeypatch, external_rigid_solver: bool): - """VBD consumes rigid forces only when it owns AVBD rigid integration.""" - solver = object() - monkeypatch.setattr(NewtonVBDManager, "_create_solver", lambda model, cfg: solver) - monkeypatch.setattr(NewtonManager, "_solver", None) - monkeypatch.setattr(NewtonManager, "_use_single_state", True) - monkeypatch.setattr(NewtonManager, "_needs_collision_pipeline", False) - monkeypatch.setattr(NewtonManager, "_supports_rigid_body_force_input", False) - - NewtonVBDManager._build_solver(object(), VBDSolverCfg(integrate_with_external_rigid_solver=external_rigid_solver)) - - assert NewtonManager._solver is solver - assert NewtonManager._supports_rigid_body_force_input is not external_rigid_solver def test_newton_material_defaults_match_registry_defaults(): diff --git a/source/isaaclab_contrib/test/deformable/test_deformable_object.py b/source/isaaclab_contrib/test/deformable/test_deformable_object.py index 1676fd654ce..89ea2ddca4c 100644 --- a/source/isaaclab_contrib/test/deformable/test_deformable_object.py +++ b/source/isaaclab_contrib/test/deformable/test_deformable_object.py @@ -20,7 +20,7 @@ import torch import warp as wp from flaky import flaky -from isaaclab_newton.physics import NewtonCfg, NewtonManager +from isaaclab_newton.physics import NewtonCfg, NewtonManager, VBDSolverCfg from isaaclab_newton.sim.schemas import NewtonDeformableBodyPropertiesCfg from isaaclab_newton.sim.spawners.materials import ( NewtonDeformableBodyMaterialCfg, @@ -32,8 +32,6 @@ from isaaclab.assets import DeformableObject, DeformableObjectCfg from isaaclab.sim import SimulationCfg, build_simulation_context -from isaaclab_contrib.deformable.newton_manager_cfg import VBDSolverCfg - NEWTON_VBD_CFG = SimulationCfg( physics=NewtonCfg( solver_cfg=VBDSolverCfg(iterations=3), diff --git a/source/isaaclab_newton/changelog.d/mmichelis-vbd-core.minor.rst b/source/isaaclab_newton/changelog.d/mmichelis-vbd-core.minor.rst new file mode 100644 index 00000000000..d1b69571864 --- /dev/null +++ b/source/isaaclab_newton/changelog.d/mmichelis-vbd-core.minor.rst @@ -0,0 +1,7 @@ +Added +^^^^^ + +* Added :class:`~isaaclab_newton.physics.NewtonVBDManager`, + :class:`~isaaclab_newton.physics.VBDSolverCfg`, and + :class:`~isaaclab_newton.physics.NewtonSoftContactCfg` to the core Newton + physics package. diff --git a/source/isaaclab_newton/isaaclab_newton/physics/__init__.pyi b/source/isaaclab_newton/isaaclab_newton/physics/__init__.pyi index 3e15ec5b1c5..5edd6ce555a 100644 --- a/source/isaaclab_newton/isaaclab_newton/physics/__init__.pyi +++ b/source/isaaclab_newton/isaaclab_newton/physics/__init__.pyi @@ -25,7 +25,10 @@ __all__ = [ "NewtonManager", "NewtonMJWarpManager", "NewtonShapeCfg", + "NewtonSoftContactCfg", "NewtonSolverCfg", + "NewtonVBDManager", + "VBDSolverCfg", "NewtonXPBDManager", "XPBDSolverCfg", ] @@ -53,7 +56,10 @@ from .newton_manager import NewtonManager from .newton_manager_cfg import ( NewtonCfg, NewtonShapeCfg, + NewtonSoftContactCfg, NewtonSolverCfg, ) +from .vbd_manager import NewtonVBDManager +from .vbd_manager_cfg import VBDSolverCfg from .xpbd_manager import NewtonXPBDManager from .xpbd_manager_cfg import XPBDSolverCfg diff --git a/source/isaaclab_newton/isaaclab_newton/physics/newton_manager.py b/source/isaaclab_newton/isaaclab_newton/physics/newton_manager.py index 4b4d44d6f76..17eface9b9e 100644 --- a/source/isaaclab_newton/isaaclab_newton/physics/newton_manager.py +++ b/source/isaaclab_newton/isaaclab_newton/physics/newton_manager.py @@ -1542,6 +1542,11 @@ def start_simulation(cls) -> None: cls._prepare_builder_for_finalize(cls._builder) with Timer(name="newton_finalize_builder", msg="Finalize builder took:", activity="Finalizing physics model"): NewtonManager._model = cls._builder.finalize(device=device) + cfg = PhysicsManager._cfg + if isinstance(cfg, NewtonCfg) and cfg.soft_contact_cfg is not None: + cls._model.soft_contact_ke = float(cfg.soft_contact_cfg.soft_contact_ke) + cls._model.soft_contact_kd = float(cfg.soft_contact_cfg.soft_contact_kd) + cls._model.soft_contact_mu = float(cfg.soft_contact_cfg.soft_contact_mu) cls._model.set_gravity(cls._gravity_vector) cls._model.num_envs = cls._num_envs @@ -1759,6 +1764,11 @@ def _inject_terrain_heightfields(cls, stage: Usd.Stage, builder: ModelBuilder) - ignore_paths.append(prim.GetPath().pathString) return ignore_paths + @classmethod + def _get_usd_import_ignore_paths(cls) -> list[str]: + """Return solver-specific prim paths excluded from USD import.""" + return [] + @classmethod def instantiate_builder_from_stage(cls): """Create builder from USD stage. @@ -1801,10 +1811,13 @@ def instantiate_builder_from_stage(cls): # constants in lockstep or MJWarp resolution will silently diverge # from the live backend. hf_ignore_paths = cls._inject_terrain_heightfields(stage, builder) + solver_ignore_paths = cls._get_usd_import_ignore_paths() if not env_paths: # No env Xforms — flat loading - import_result = builder.add_usd(stage, ignore_paths=hf_ignore_paths, schema_resolvers=schema_resolvers) + import_result = builder.add_usd( + stage, ignore_paths=[*hf_ignore_paths, *solver_ignore_paths], schema_resolvers=schema_resolvers + ) _restore_visible_colliders_without_visual_shapes(builder, stage, import_result["path_shape_map"]) replace_newton_builder_shape_colors(builder, stage) NewtonManager._world_xforms = [wp.transform()] @@ -1813,7 +1826,7 @@ def instantiate_builder_from_stage(cls): else: # Load everything except the env subtrees (ground plane, lights, etc.) # and any terrain colliders already added as heightfields above. - ignore_paths = [path for _, path in env_paths] + hf_ignore_paths + ignore_paths = [path for _, path in env_paths] + hf_ignore_paths + solver_ignore_paths import_result = builder.add_usd(stage, ignore_paths=ignore_paths, schema_resolvers=schema_resolvers) _restore_visible_colliders_without_visual_shapes(builder, stage, import_result["path_shape_map"]) replace_newton_builder_shape_colors(builder, stage) @@ -1821,7 +1834,10 @@ def instantiate_builder_from_stage(cls): _, proto_path = env_paths[0] source_builders = {proto_path: cls.create_builder(up_axis=up_axis)} import_result = source_builders[proto_path].add_usd( - stage, root_path=proto_path, schema_resolvers=schema_resolvers + stage, + root_path=proto_path, + ignore_paths=solver_ignore_paths, + schema_resolvers=schema_resolvers, ) _restore_visible_colliders_without_visual_shapes( source_builders[proto_path], stage, import_result["path_shape_map"] diff --git a/source/isaaclab_newton/isaaclab_newton/physics/newton_manager_cfg.py b/source/isaaclab_newton/isaaclab_newton/physics/newton_manager_cfg.py index c7f0d59fc27..e97f604e16d 100644 --- a/source/isaaclab_newton/isaaclab_newton/physics/newton_manager_cfg.py +++ b/source/isaaclab_newton/isaaclab_newton/physics/newton_manager_cfg.py @@ -53,6 +53,28 @@ class NewtonSolverCfg: """ +@configclass +class NewtonSoftContactCfg: + """Global soft-contact parameters applied to the finalized Newton model.""" + + soft_contact_ke: float = 1.0e3 + """Body-particle and particle self-contact stiffness [N/m]. + + Effective body-particle stiffness is ``0.5 * (soft_contact_ke + shape_ke)``, + where ``shape_ke`` is the rigid shape's material stiffness. + """ + + soft_contact_kd: float = 10.0 + """Body-particle contact damping [N*s/m].""" + + soft_contact_mu: float = 0.5 + """Body-particle contact friction coefficient [dimensionless]. + + Effective body-particle friction is ``sqrt(soft_contact_mu * shape_mu)``, + where ``shape_mu`` is the rigid shape's material friction coefficient. + """ + + @configclass class NewtonShapeCfg: """Default per-shape collision properties applied to all shapes in a Newton scene. @@ -158,6 +180,12 @@ class NewtonCfg(PhysicsCfg): solver_cfg: NewtonSolverCfg | None = None """Solver configuration. If None (default), MJWarpSolverCfg is used by default.""" + soft_contact_cfg: NewtonSoftContactCfg | None = None + """Global soft-contact parameters applied after model finalization. + + If ``None``, Newton model defaults are preserved. + """ + collision_cfg: NewtonCollisionPipelineCfg | None = None """Newton collision pipeline configuration. @@ -168,6 +196,7 @@ class NewtonCfg(PhysicsCfg): - :class:`KaminoPADMMSolverCfg` or :class:`KaminoDVISolverCfg` with ``use_collision_detector=False``, - :class:`XPBDSolverCfg` (always), + - :class:`VBDSolverCfg` (always), - :class:`FeatherstoneSolverCfg` (always). :class:`~isaaclab_newton.physics.MPMSolverCfg` does not use this pipeline; @@ -247,6 +276,7 @@ def __post_init__(self): from isaaclab_newton.physics.mjwarp_manager_cfg import MJWarpSolverCfg self.solver_cfg = MJWarpSolverCfg() + self.class_type = self.solver_cfg.class_type # Mid-tick re-collide is silently disabled when collision_decimation >= num_substeps. diff --git a/source/isaaclab_newton/isaaclab_newton/physics/vbd_manager.py b/source/isaaclab_newton/isaaclab_newton/physics/vbd_manager.py new file mode 100644 index 00000000000..e20100d709e --- /dev/null +++ b/source/isaaclab_newton/isaaclab_newton/physics/vbd_manager.py @@ -0,0 +1,95 @@ +# 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 + +"""VBD Newton manager.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from newton import Model +from newton.solvers import SolverVBD + +from .newton_manager import NewtonManager +from .vbd_manager_cfg import VBDSolverCfg + +if TYPE_CHECKING: + from isaaclab.sim.simulation_context import SimulationContext + + +class NewtonVBDManager(NewtonManager): + """Newton manager specialization for the VBD solver.""" + + @classmethod + def initialize(cls, sim_context: SimulationContext) -> None: + """Initialize VBD deformable integration when contrib is available.""" + try: + from isaaclab_contrib.deformable.deformable_object import install_deformable_builder_hooks + except ModuleNotFoundError as exc: + if exc.name not in {"isaaclab_contrib", "isaaclab_contrib.deformable"}: + raise + else: + install_deformable_builder_hooks() + super().initialize(sim_context) + + @classmethod + def start_simulation(cls) -> None: + """Start simulation and bind registered deformables to Fabric.""" + if cls._builder is not None: + cls._builder.color() + super().start_simulation() + try: + from isaaclab_contrib.deformable.deformable_object import setup_registered_deformable_fabric_sync + except ModuleNotFoundError as exc: + if exc.name not in {"isaaclab_contrib", "isaaclab_contrib.deformable"}: + raise + else: + setup_registered_deformable_fabric_sync(cls) + + @classmethod + def instantiate_builder_from_stage(cls) -> None: + """Create and color the VBD builder from the USD stage.""" + super().instantiate_builder_from_stage() + if cls._builder is None: + raise RuntimeError("Newton stage import did not create a builder.") + cls._builder.color() + + @classmethod + def _get_usd_import_ignore_paths(cls) -> list[str]: + """Return registered deformable mesh paths excluded from USD import.""" + return [ + path for entry in cls._deformable_registry for path in (entry.sim_mesh_prim_path, entry.vis_mesh_prim_path) + ] + + @classmethod + def _create_solver(cls, model: Model, solver_cfg: VBDSolverCfg) -> SolverVBD: + """Construct the configured VBD solver.""" + return SolverVBD(model, **cls._filter_solver_kwargs(SolverVBD, solver_cfg)) + + @classmethod + def _build_solver(cls, model: Model, solver_cfg: VBDSolverCfg) -> None: + """Construct VBD and configure its base-manager state.""" + NewtonManager._solver = cls._create_solver(model, solver_cfg) + NewtonManager._use_single_state = False + NewtonManager._needs_collision_pipeline = True + NewtonManager._supports_rigid_body_force_input = not solver_cfg.integrate_with_external_rigid_solver + + @classmethod + def _solver_specific_clear(cls) -> None: + """Clear contrib deformable integration when available.""" + try: + from isaaclab_contrib.deformable.deformable_object import clear_deformable_builder_hooks + except ModuleNotFoundError as exc: + if exc.name not in {"isaaclab_contrib", "isaaclab_contrib.deformable"}: + raise + else: + clear_deformable_builder_hooks() + + @classmethod + def _simulate_physics_only(cls) -> None: + """Rebuild the VBD particle BVH before stepping physics.""" + if cls._model.particle_count > 0 and hasattr(cls._solver, "rebuild_bvh"): + cls._solver.rebuild_bvh(cls._state_0) + super()._simulate_physics_only() diff --git a/source/isaaclab_newton/isaaclab_newton/physics/vbd_manager_cfg.py b/source/isaaclab_newton/isaaclab_newton/physics/vbd_manager_cfg.py new file mode 100644 index 00000000000..2923f8c7e6b --- /dev/null +++ b/source/isaaclab_newton/isaaclab_newton/physics/vbd_manager_cfg.py @@ -0,0 +1,69 @@ +# 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 + +"""Configuration for the Newton VBD solver.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from isaaclab.utils.configclass import configclass + +from .newton_manager_cfg import NewtonSolverCfg + +if TYPE_CHECKING: + from isaaclab_newton.physics import NewtonManager + + +@configclass +class VBDSolverCfg(NewtonSolverCfg): + """Configuration for the Vertex Block Descent solver.""" + + class_type: type[NewtonManager] | str = "{DIR}.vbd_manager:NewtonVBDManager" + """Manager class for the VBD solver.""" + + iterations: int = 10 + """Number of VBD iterations per substep.""" + + integrate_with_external_rigid_solver: bool = False + """Whether an external solver integrates rigid bodies.""" + + particle_enable_self_contact: bool = False + """Whether to enable particle self-contact.""" + + particle_self_contact_radius: float = 0.005 + """Particle radius used for self-contact detection [m].""" + + particle_self_contact_margin: float = 0.005 + """Self-contact detection margin [m].""" + + particle_collision_detection_interval: int = -1 + """How often particle self-contact detection is applied. + + ``< 0``: once before initialization. ``0``: once before and once after + initialization. ``k >= 1``: before every ``k`` VBD iterations. + """ + + particle_vertex_contact_buffer_size: int = 32 + """Preallocation size for each vertex contact buffer.""" + + particle_edge_contact_buffer_size: int = 64 + """Preallocation size for each edge contact buffer.""" + + particle_topological_contact_filter_threshold: int = 2 + """Topological distance below which self-contacts are discarded.""" + + particle_rest_shape_contact_exclusion_radius: float = 0.0 + """Rest-shape separation threshold for filtering contacts [m].""" + + rigid_contact_k_start: float = 1.0e2 + """Initial stiffness seed for rigid-body contacts [N/m].""" + + rigid_body_particle_contact_buffer_size: int = 256 + """Per-body capacity of the particle, edge, and face soft-contact list. + + Increase this value when Newton reports a per-body particle contact buffer overflow. + Only used when :attr:`integrate_with_external_rigid_solver` is ``False``. + """ diff --git a/source/isaaclab_newton/test/assets/test_cable_object.py b/source/isaaclab_newton/test/assets/test_cable_object.py index b733e3761f2..3e1d1a667cf 100644 --- a/source/isaaclab_newton/test/assets/test_cable_object.py +++ b/source/isaaclab_newton/test/assets/test_cable_object.py @@ -14,7 +14,7 @@ pytest.importorskip("newton") from isaaclab_newton.assets import CableObject as NewtonCableObject -from isaaclab_newton.physics import NewtonCfg, XPBDSolverCfg +from isaaclab_newton.physics import NewtonCfg, VBDSolverCfg, XPBDSolverCfg from isaaclab_newton.physics import NewtonManager as SimulationManager import isaaclab.sim as sim_utils @@ -28,7 +28,6 @@ from isaaclab.utils.configclass import configclass from isaaclab_contrib.coupling import CouplerEntryCfg, CouplerProxyCfg, CouplerProxyMappingCfg -from isaaclab_contrib.deformable import VBDSolverCfg @configclass diff --git a/source/isaaclab_newton/test/physics/test_newton_fabric_body_sync.py b/source/isaaclab_newton/test/physics/test_newton_fabric_body_sync.py index 865e9e725c4..ce2f20015fe 100644 --- a/source/isaaclab_newton/test/physics/test_newton_fabric_body_sync.py +++ b/source/isaaclab_newton/test/physics/test_newton_fabric_body_sync.py @@ -15,7 +15,7 @@ import pytest import torch import warp as wp -from isaaclab_newton.physics import NewtonCfg, NewtonManager, XPBDSolverCfg +from isaaclab_newton.physics import NewtonCfg, NewtonManager, VBDSolverCfg, XPBDSolverCfg from pxr import UsdGeom from usdrt import Gf, Rt @@ -29,8 +29,6 @@ from isaaclab.utils import math as math_utils from isaaclab.utils.configclass import configclass -from isaaclab_contrib.deformable import VBDSolverCfg - @configclass class _RenderSceneCfg(InteractiveSceneCfg): diff --git a/source/isaaclab_newton/test/physics/test_newton_manager_abstraction.py b/source/isaaclab_newton/test/physics/test_newton_manager_abstraction.py index ffbec53b167..01b7720e5e1 100644 --- a/source/isaaclab_newton/test/physics/test_newton_manager_abstraction.py +++ b/source/isaaclab_newton/test/physics/test_newton_manager_abstraction.py @@ -49,11 +49,13 @@ NewtonMPMManager, NewtonShapeCfg, NewtonSolverCfg, + NewtonVBDManager, NewtonXPBDManager, + VBDSolverCfg, XPBDSolverCfg, ) from isaaclab_newton.physics.mpm_manager import _make_solver_config -from newton.solvers import SolverFeatherstone, SolverImplicitMPM, SolverKamino, SolverMuJoCo, SolverXPBD +from newton.solvers import SolverFeatherstone, SolverImplicitMPM, SolverKamino, SolverMuJoCo, SolverVBD, SolverXPBD from isaaclab.physics import PhysicsManager from isaaclab.sim import SimulationCfg, build_simulation_context @@ -89,6 +91,14 @@ True, id="xpbd", ), + pytest.param( + lambda: VBDSolverCfg(), + NewtonVBDManager, + SolverVBD, + False, + True, + id="vbd", + ), pytest.param( lambda: FeatherstoneSolverCfg(), NewtonFeatherstoneManager, @@ -125,6 +135,7 @@ RIGID_BODY_FORCE_INPUT_SUPPORT = { NewtonMJWarpManager: True, + NewtonVBDManager: True, NewtonXPBDManager: True, NewtonFeatherstoneManager: True, NewtonKaminoManager: True, @@ -957,7 +968,14 @@ def reset(self, state, world_mask=None, flags=0): @pytest.mark.parametrize( "manager", - [NewtonMJWarpManager, NewtonXPBDManager, NewtonFeatherstoneManager, NewtonKaminoManager, NewtonMPMManager], + [ + NewtonMJWarpManager, + NewtonXPBDManager, + NewtonVBDManager, + NewtonFeatherstoneManager, + NewtonKaminoManager, + NewtonMPMManager, + ], ) def test_subclass_of_newton_manager(manager): """All concrete managers inherit from :class:`NewtonManager`.""" @@ -977,6 +995,7 @@ def test_clear_resets_rigid_body_force_capability(monkeypatch): for manager in ( NewtonMJWarpManager, NewtonXPBDManager, + NewtonVBDManager, NewtonFeatherstoneManager, NewtonKaminoManager, NewtonMPMManager, @@ -1024,7 +1043,14 @@ def test_abstract_create_solver_raises(): @pytest.mark.parametrize( "manager", - [NewtonMJWarpManager, NewtonXPBDManager, NewtonFeatherstoneManager, NewtonKaminoManager, NewtonMPMManager], + [ + NewtonMJWarpManager, + NewtonXPBDManager, + NewtonVBDManager, + NewtonFeatherstoneManager, + NewtonKaminoManager, + NewtonMPMManager, + ], ) def test_manager_name_starts_with_newton(manager): """The ``"newton"`` prefix is required by :class:`InteractiveScene` and the @@ -1108,6 +1134,17 @@ def test_initialize_solver_populates_canonical_state( jitter=0.0, radius_mean=0.02, ) + elif expected_solver_cls is SolverVBD: + builder.add_cloth_mesh( + pos=wp.vec3(0.0, 0.0, 0.1), + rot=wp.quat_identity(), + scale=1.0, + vel=wp.vec3(0.0), + vertices=[wp.vec3(0.0, 0.0, 0.0), wp.vec3(0.1, 0.0, 0.0), wp.vec3(0.0, 0.1, 0.0)], + indices=[0, 1, 2], + density=1.0, + particle_radius=0.01, + ) else: # Pre-populate the builder with a minimal scene so MJCF conversion has # something to work with. diff --git a/source/isaaclab_newton/test/physics/test_vbd_core.py b/source/isaaclab_newton/test/physics/test_vbd_core.py new file mode 100644 index 00000000000..a493a854e8e --- /dev/null +++ b/source/isaaclab_newton/test/physics/test_vbd_core.py @@ -0,0 +1,241 @@ +# 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 + +"""Tests for the core Newton VBD integration.""" + +from __future__ import annotations + +import importlib +from types import SimpleNamespace + +import pytest +from isaaclab_newton.physics import NewtonCfg, NewtonManager, NewtonSoftContactCfg + +from isaaclab.physics import PhysicsManager + + +@pytest.mark.parametrize( + ("soft_contact_cfg", "expected"), + [ + pytest.param(None, (7.0, 8.0, 9.0), id="preserve"), + pytest.param( + NewtonSoftContactCfg(soft_contact_ke=11.0, soft_contact_kd=12.0, soft_contact_mu=13.0), + (11.0, 12.0, 13.0), + id="override", + ), + ], +) +def test_soft_contact_cfg_updates_finalized_model(monkeypatch, soft_contact_cfg, expected): + """Soft-contact configuration updates the finalized model when provided.""" + state_values = [] + + class Model: + soft_contact_ke = 7.0 + soft_contact_kd = 8.0 + soft_contact_mu = 9.0 + world_count = 0 + articulation_count = 0 + + def set_gravity(self, gravity): + pass + + def state(self): + state_values.append((self.soft_contact_ke, self.soft_contact_kd, self.soft_contact_mu)) + return object() + + def control(self): + return object() + + class Builder: + body_label = () + up_axis = None + + def finalize(self, *, device): + return model + + model = Model() + monkeypatch.setattr(PhysicsManager, "_cfg", NewtonCfg(soft_contact_cfg=soft_contact_cfg), raising=False) + monkeypatch.setattr(PhysicsManager, "_device", "cpu", raising=False) + monkeypatch.setattr(NewtonManager, "_builder", Builder(), raising=False) + monkeypatch.setattr(NewtonManager, "_up_axis", "Z", raising=False) + monkeypatch.setattr(NewtonManager, "_gravity_vector", (0.0, 0.0, -9.81), raising=False) + monkeypatch.setattr(NewtonManager, "_num_envs", 0, raising=False) + monkeypatch.setattr(NewtonManager, "_clone_physics_only", True, raising=False) + monkeypatch.setattr(NewtonManager, "_pending_extended_state_attributes", set(), raising=False) + monkeypatch.setattr(NewtonManager, "_pending_extended_contact_attributes", set(), raising=False) + for attr in ( + "_model", + "_state_0", + "_state_1", + "_control", + "_adapter", + "_use_newton_actuators_active", + "_world_reset_mask", + "_fk_reset_mask", + ): + monkeypatch.setattr(NewtonManager, attr, getattr(NewtonManager, attr, None), raising=False) + monkeypatch.setattr(NewtonManager, "_cl_pending_sites", {}, raising=False) + monkeypatch.setattr(NewtonManager, "_drain_stale_cuda_error", classmethod(lambda cls: None)) + monkeypatch.setattr(NewtonManager, "dispatch_event", classmethod(lambda cls, event: None)) + + NewtonManager.start_simulation() + + assert (model.soft_contact_ke, model.soft_contact_kd, model.soft_contact_mu) == expected + + assert state_values == [expected, expected] + + +@pytest.mark.parametrize("env_paths", [(), ("/World/Env_0", "/World/Env_1")], ids=["flat", "replicated"]) +def test_vbd_excludes_registered_deformable_meshes(monkeypatch, env_paths): + """VBD excludes registered simulation and visual meshes from USD import.""" + physics = importlib.import_module("isaaclab_newton.physics") + pxr = importlib.import_module("pxr") + newton_module = importlib.import_module("isaaclab_newton.physics.newton_manager") + builders = [] + hook_calls = [] + replicate_calls = [] + + class Builder: + def __init__(self): + self.imports = [] + self.color_calls = 0 + + def add_usd(self, stage, *, root_path=None, ignore_paths=(), schema_resolvers=()): + self.imports.append((root_path, list(ignore_paths))) + return {"path_shape_map": {}} + + def color(self): + self.color_calls += 1 + + children = [ + SimpleNamespace( + GetName=lambda path=path: path.rsplit("/", 1)[-1], + GetPath=lambda path=path: SimpleNamespace(pathString=path), + ) + for path in env_paths + ] + world_prim = SimpleNamespace(IsValid=lambda: True, GetChildren=lambda: children) + stage = SimpleNamespace(GetPrimAtPath=lambda path: world_prim if path == "/World" else path) + rotation = SimpleNamespace(GetImaginary=lambda: (0.0, 0.0, 0.0), GetReal=lambda: 1.0) + matrix = SimpleNamespace(ExtractTranslation=lambda: (0.0, 0.0, 0.0), ExtractRotationQuat=lambda: rotation) + usd_geom = SimpleNamespace( + GetStageUpAxis=lambda stage: "Z", + XformCache=lambda: SimpleNamespace(GetLocalToWorldTransform=lambda prim: matrix), + ) + + def create_builder(cls, *, up_axis): + builder = Builder() + builders.append(builder) + return builder + + def replicate(*args, **kwargs): + replicate_calls.append(kwargs) + return {}, [object() for _ in env_paths] + + monkeypatch.setattr(newton_module, "get_current_stage", lambda: stage) + monkeypatch.setattr(pxr, "UsdGeom", usd_geom) + monkeypatch.setattr(newton_module, "_restore_visible_colliders_without_visual_shapes", lambda *args: None) + monkeypatch.setattr(newton_module, "replace_newton_builder_shape_colors", lambda *args: None) + monkeypatch.setattr(newton_module, "replicate_builder_mapping", replicate) + monkeypatch.setattr(physics.NewtonVBDManager, "create_builder", classmethod(create_builder)) + monkeypatch.setattr( + physics.NewtonVBDManager, + "_inject_terrain_heightfields", + classmethod(lambda cls, stage, builder: ["/World/terrain"]), + ) + monkeypatch.setattr( + physics.NewtonVBDManager, + "_cl_inject_sites", + classmethod(lambda cls, builder, source_builders: ({}, {}, {})), + ) + monkeypatch.setattr( + physics.NewtonVBDManager, "set_builder", classmethod(lambda cls, builder: setattr(cls, "_builder", builder)) + ) + + def hook(builder, world_idx, position, rotation): + hook_calls.append(world_idx) + + monkeypatch.setattr(physics.NewtonVBDManager, "_per_world_builder_hooks", [hook]) + monkeypatch.setattr( + physics.NewtonVBDManager, + "_deformable_registry", + [SimpleNamespace(sim_mesh_prim_path="/World/soft/sim", vis_mesh_prim_path="/World/soft/visual")], + ) + monkeypatch.setattr(physics.NewtonVBDManager, "_builder", None) + monkeypatch.setattr(NewtonManager, "_cl_site_index_map", {}) + monkeypatch.setattr(NewtonManager, "_world_xforms", []) + monkeypatch.setattr(NewtonManager, "_num_envs", 0) + + physics.NewtonVBDManager.instantiate_builder_from_stage() + + deformable_paths = ["/World/soft/sim", "/World/soft/visual"] + if env_paths: + assert builders[0].imports == [(None, [*env_paths, "/World/terrain", *deformable_paths])] + assert builders[1].imports == [("/World/Env_0", deformable_paths)] + assert replicate_calls[0]["per_world_builder_hooks"] == [hook] + else: + assert builders[0].imports == [(None, ["/World/terrain", *deformable_paths])] + assert hook_calls == [0] + assert builders[0].color_calls == 1 + + +def test_vbd_colors_prebuilt_builder_before_start(monkeypatch): + """VBD colors a prebuilt builder before starting simulation.""" + physics = importlib.import_module("isaaclab_newton.physics") + deformable_module = importlib.import_module("isaaclab_contrib.deformable.deformable_object") + events = [] + + class Builder: + def color(self): + events.append("color") + + monkeypatch.setattr(physics.NewtonVBDManager, "_builder", Builder()) + monkeypatch.setattr(NewtonManager, "start_simulation", classmethod(lambda cls: events.append("start"))) + monkeypatch.setattr(deformable_module, "setup_registered_deformable_fabric_sync", lambda manager_cls: None) + + physics.NewtonVBDManager.start_simulation() + + assert events == ["color", "start"] + + +@pytest.mark.parametrize("external_rigid_solver", [False, True]) +def test_vbd_solver_force_input_capability(monkeypatch, external_rigid_solver): + """VBD accepts rigid forces only when it integrates rigid bodies.""" + physics = importlib.import_module("isaaclab_newton.physics") + solver = object() + monkeypatch.setattr(physics.NewtonVBDManager, "_create_solver", lambda model, cfg: solver) + monkeypatch.setattr(NewtonManager, "_solver", None) + monkeypatch.setattr(NewtonManager, "_use_single_state", True) + monkeypatch.setattr(NewtonManager, "_needs_collision_pipeline", False) + monkeypatch.setattr(NewtonManager, "_supports_rigid_body_force_input", False) + + solver_cfg = physics.VBDSolverCfg(integrate_with_external_rigid_solver=external_rigid_solver) + physics.NewtonVBDManager._build_solver(object(), solver_cfg) + + assert NewtonManager._solver is solver + assert NewtonManager._supports_rigid_body_force_input is not external_rigid_solver + + +def test_vbd_rebuilds_particle_bvh_before_physics_step(monkeypatch): + """VBD rebuilds its particle BVH before the base physics step.""" + physics = importlib.import_module("isaaclab_newton.physics") + events = [] + state = object() + + class Solver: + def rebuild_bvh(self, solver_state): + events.append(("rebuild", solver_state)) + + def simulate_physics_only(cls): + events.append(("step", cls)) + + monkeypatch.setattr(NewtonManager, "_simulate_physics_only", classmethod(simulate_physics_only)) + monkeypatch.setattr(physics.NewtonVBDManager, "_model", SimpleNamespace(particle_count=1)) + monkeypatch.setattr(physics.NewtonVBDManager, "_solver", Solver()) + monkeypatch.setattr(physics.NewtonVBDManager, "_state_0", state) + + physics.NewtonVBDManager._simulate_physics_only() + + assert events == [("rebuild", state), ("step", physics.NewtonVBDManager)] diff --git a/source/isaaclab_tasks/changelog.d/mmichelis-vbd-core.rst b/source/isaaclab_tasks/changelog.d/mmichelis-vbd-core.rst new file mode 100644 index 00000000000..802e069f157 --- /dev/null +++ b/source/isaaclab_tasks/changelog.d/mmichelis-vbd-core.rst @@ -0,0 +1,6 @@ +Changed +^^^^^^^ + +* Changed the Franka soft-body task configurations to use the core + :class:`~isaaclab_newton.physics.VBDSolverCfg` and + :attr:`~isaaclab_newton.physics.NewtonCfg.soft_contact_cfg`. diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/franka_cable_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/franka_cable_env_cfg.py index 91685db4169..3bcb623b929 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/franka_cable_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/franka_cable_env_cfg.py @@ -7,7 +7,7 @@ from __future__ import annotations -from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg, NewtonShapeCfg +from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg, NewtonShapeCfg, VBDSolverCfg import isaaclab.sim as sim_utils from isaaclab.assets import AssetBaseCfg, CableObjectCfg @@ -23,7 +23,6 @@ from isaaclab.utils.configclass import configclass from isaaclab_contrib.coupling import CouplerEntryCfg, CouplerProxyCfg, CouplerProxyMappingCfg -from isaaclab_contrib.deformable import VBDSolverCfg from isaaclab_tasks.utils import PresetCfg diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/franka_cloth_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/franka_cloth_env_cfg.py index a5c22a35601..a20cd141a5a 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/franka_cloth_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/franka_cloth_env_cfg.py @@ -7,7 +7,13 @@ from __future__ import annotations -from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg, NewtonCollisionPipelineCfg +from isaaclab_newton.physics import ( + MJWarpSolverCfg, + NewtonCfg, + NewtonCollisionPipelineCfg, + NewtonSoftContactCfg, + VBDSolverCfg, +) from isaaclab_newton.sim.schemas import NewtonDeformableBodyPropertiesCfg from isaaclab_newton.sim.spawners.materials import NewtonSurfaceDeformableBodyMaterialCfg from isaaclab_physx.physics import PhysxCfg @@ -25,7 +31,6 @@ from isaaclab.utils.configclass import configclass from isaaclab_contrib.coupling import CouplerEntryCfg, CouplerProxyCfg, CouplerProxyMappingCfg -from isaaclab_contrib.deformable.newton_manager_cfg import NewtonModelCfg, VBDSolverCfg from isaaclab_tasks.utils import PresetCfg @@ -85,7 +90,11 @@ class PhysicsCfg(PresetCfg): ) ], iterations=1, - model_cfg=NewtonModelCfg(soft_contact_ke=8e3, soft_contact_mu=10.0), + ), + soft_contact_cfg=NewtonSoftContactCfg( + soft_contact_ke=8.0e3, + soft_contact_kd=1.0e-2, + soft_contact_mu=10.0, ), num_substeps=2, ) diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/franka_soft_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/franka_soft_env_cfg.py index 1fe189d3f14..97185161b8d 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/franka_soft_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/franka_soft_env_cfg.py @@ -11,6 +11,8 @@ MJWarpSolverCfg, NewtonCfg, NewtonCollisionPipelineCfg, + NewtonSoftContactCfg, + VBDSolverCfg, ) from isaaclab_newton.sim.schemas import NewtonDeformableBodyPropertiesCfg from isaaclab_newton.sim.spawners.materials import NewtonDeformableBodyMaterialCfg @@ -47,10 +49,6 @@ CouplerProxyCfg, CouplerProxyMappingCfg, ) -from isaaclab_contrib.deformable.newton_manager_cfg import ( - NewtonModelCfg, - VBDSolverCfg, -) from isaaclab_tasks.utils import PresetCfg from isaaclab_tasks.utils.presets import MultiBackendRendererCfg @@ -177,7 +175,11 @@ class PhysicsCfg(PresetCfg): ) ], iterations=1, - model_cfg=NewtonModelCfg(soft_contact_ke=8.0e3, soft_contact_mu=10.0), + ), + soft_contact_cfg=NewtonSoftContactCfg( + soft_contact_ke=8.0e3, + soft_contact_kd=1.0e-2, + soft_contact_mu=10.0, ), num_substeps=2, )