From cf0666bb200e5d8f8947692ba168e22db94d7eef Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Fri, 31 Jul 2026 19:22:10 +0200 Subject: [PATCH 01/41] Retune Franka deformable lift for stable grasping The Franka deformable lift tasks could not grasp reliably. The gripper tunneled through the beam between soft vertices, and the shared lift MDP terms offered no deformable-aware rewards or divergence guards, so diverged environments kept poisoning the rollout. Add a task-local mdp package with deformable-aware rewards, observations, terminations, events, a gravity curriculum, and a pose command that tracks the deformable center of mass. Retune the beam material, the collider contact and rest offsets, and the arm and hand actuator gains so the gripper no longer crushes the deformable. Enable full-surface rigid-soft contact backed by signed-distance fields on the gripper so contacts are caught between vertices. Give the cloth task a kinematic rigid support that is registered with the coupler so the cloth no longer passes through it. Select the action space through a preset, with joint-space targets as the default and task-space inverse kinematics available through presets=ik. Support the above in Newton with NewtonShapeSDFCfg to provision volume SDFs on collider shapes selected by label regex, a collision pipeline flag for full-surface rigid-soft contact, and a configurable per-body particle contact buffer so contacts are not silently dropped. Full-surface contact on the coupled proxy solver additionally requires Newton with proxy-body contact harvesting, tracked in newton-physics/newton#3756. --- .../lab_newton/isaaclab_newton.physics.rst | 6 + .../state_machine/lift_franka_soft.py | 230 ++++++------- .../changelog.d/mym-lift-soft.minor.rst | 6 + .../deformable/newton_manager_cfg.py | 10 + .../changelog.d/mym-lift-soft.minor.rst | 9 + .../isaaclab_newton/physics/__init__.pyi | 2 + .../physics/newton_collision_cfg.py | 11 + .../isaaclab_newton/physics/newton_manager.py | 34 ++ .../physics/newton_manager_cfg.py | 35 ++ .../changelog.d/mym-lift-soft.minor.rst | 53 +++ .../franka_soft/agents/rsl_rl_ppo_cfg.py | 26 +- .../franka_soft/franka_cloth_env_cfg.py | 100 +++--- .../config/franka_soft/franka_soft_env_cfg.py | 297 ++++++++++++----- .../lift/config/franka_soft/mdp/__init__.py | 10 + .../lift/config/franka_soft/mdp/__init__.pyi | 64 ++++ .../config/franka_soft/mdp/curriculums.py | 75 +++++ .../lift/config/franka_soft/mdp/events.py | 181 +++++++++++ .../config/franka_soft/mdp/observations.py | 117 +++++++ .../config/franka_soft/mdp/pose_commands.py | 82 +++++ .../lift/config/franka_soft/mdp/rewards.py | 304 ++++++++++++++++++ .../config/franka_soft/mdp/terminations.py | 158 +++++++++ .../core/lift/config/franka_soft/mdp/utils.py | 95 ++++++ 22 files changed, 1642 insertions(+), 263 deletions(-) create mode 100644 source/isaaclab_contrib/changelog.d/mym-lift-soft.minor.rst create mode 100644 source/isaaclab_newton/changelog.d/mym-lift-soft.minor.rst create mode 100644 source/isaaclab_tasks/changelog.d/mym-lift-soft.minor.rst create mode 100644 source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/__init__.py create mode 100644 source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/__init__.pyi create mode 100644 source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/curriculums.py create mode 100644 source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/events.py create mode 100644 source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/observations.py create mode 100644 source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/pose_commands.py create mode 100644 source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/rewards.py create mode 100644 source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/terminations.py create mode 100644 source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/utils.py diff --git a/docs/source/api/lab_newton/isaaclab_newton.physics.rst b/docs/source/api/lab_newton/isaaclab_newton.physics.rst index 8570e55f8806..b53add0c744c 100644 --- a/docs/source/api/lab_newton/isaaclab_newton.physics.rst +++ b/docs/source/api/lab_newton/isaaclab_newton.physics.rst @@ -18,6 +18,7 @@ NewtonCollisionPipelineCfg HydroelasticSDFCfg NewtonShapeCfg + NewtonShapeSDFCfg NewtonMJWarpManager NewtonXPBDManager NewtonFeatherstoneManager @@ -87,6 +88,11 @@ Physics Configuration :show-inheritance: :exclude-members: __init__ +.. autoclass:: NewtonShapeSDFCfg + :members: + :show-inheritance: + :exclude-members: __init__ + Solver Managers --------------- diff --git a/scripts/environments/state_machine/lift_franka_soft.py b/scripts/environments/state_machine/lift_franka_soft.py index 814fe6d9a140..dd88db4a184a 100644 --- a/scripts/environments/state_machine/lift_franka_soft.py +++ b/scripts/environments/state_machine/lift_franka_soft.py @@ -11,19 +11,38 @@ .. code-block:: bash + # Kitless run with the Newton OpenGL viewer (default). uv run python scripts/environments/state_machine/lift_franka_soft.py -""" + # Headless. + uv run python scripts/environments/state_machine/lift_franka_soft.py --viz none + + # Record a video. Requires Isaac Sim, since RecordVideo goes through the Kit RTX viewport. + uv run python scripts/environments/state_machine/lift_franka_soft.py --video -"""Launch Omniverse Toolkit first.""" +""" import argparse +import os +import sys +from collections.abc import Sequence -from isaaclab.app import AppLauncher +import gymnasium as gym +import torch +import warp as wp + +from isaaclab.app import add_launcher_args, launch_simulation +from isaaclab.assets.deformable_object.deformable_object_data import DeformableObjectData +from isaaclab.visualizers import VisualizerCfg + +import isaaclab_tasks # noqa: F401 +from isaaclab_tasks.core.lift.config.franka_soft.franka_soft_env_cfg import ActionsCfg +from isaaclab_tasks.utils import resolve_task_config, setup_preset_cli # add argparse arguments parser = argparse.ArgumentParser(description="Pick and lift a deformable with a robotic arm.") parser.add_argument("--num_envs", type=int, default=1, help="Number of environments to simulate.") +parser.add_argument("--num_steps", type=int, default=1000, help="Number of environment steps to run.") parser.add_argument("--task", type=str, default="Isaac-Lift-Soft-Franka", help="The task to run.") parser.add_argument("--video", action="store_true", default=False, help="Record a video of the rollout.") parser.add_argument("--video_length", type=int, default=500, help="Length of the recorded video (in env steps).") @@ -33,38 +52,17 @@ default="videos/lift_franka_soft", help="Directory to write recorded videos into.", ) -# append AppLauncher cli args -AppLauncher.add_app_launcher_args(parser) -# parse the arguments -args_cli = parser.parse_args() - +add_launcher_args(parser) +# the task runs on Newton, so default to the kitless viewer +parser.set_defaults(visualizer=["newton"]) +args_cli, hydra_args = setup_preset_cli(parser) +sys.argv = [sys.argv[0]] + hydra_args + +# RecordVideo needs an rgb_array render mode, which is the Kit RTX viewport: enable cameras and +# request the Kit visualizer so launch_simulation starts Isaac Sim. if args_cli.video: args_cli.enable_cameras = True - -# launch omniverse app -app_launcher = AppLauncher(args_cli) -simulation_app = app_launcher.app - -# disable metrics assembler due to scene graph instancing -from isaaclab.sim.utils import disable_extension - -disable_extension("omni.usd.metrics.assembler.ui") - -"""Rest everything else.""" - -import os -from collections.abc import Sequence - -import gymnasium as gym -import torch -import warp as wp - -from isaaclab.assets.deformable_object.deformable_object_data import DeformableObjectData -from isaaclab.envs.utils.video_recorder_cfg import VideoRecorderCfg -from isaaclab.visualizers import VisualizerCfg - -import isaaclab_tasks # noqa: F401 -from isaaclab_tasks.utils.parse_cfg import parse_env_cfg + args_cli.visualizer = ["kit"] # initialize warp wp.init() @@ -186,7 +184,7 @@ class PickSmWaitTime: REST = wp.constant(0.2) APPROACH_ABOVE_OBJECT = wp.constant(1.0) - APPROACH_OBJECT = wp.constant(1.0) + APPROACH_OBJECT = wp.constant(1.5) GRASP_OBJECT = wp.constant(1.0) LIFT_OBJECT = wp.constant(1.5) OPEN_GRIPPER = wp.constant(0.0) @@ -281,88 +279,94 @@ def compute(self, ee_pose: torch.Tensor, object_pose: torch.Tensor, des_object_p def main(): - # parse configuration - env_cfg = parse_env_cfg( - args_cli.task, - device=args_cli.device, - num_envs=args_cli.num_envs, - ) - env_cfg.sim.default_visualizer_cfg = VisualizerCfg(eye=(2.1, 1.0, 1.3)) - - # attach internal video recorder when --video is requested - if args_cli.video: - video_folder = os.path.abspath(args_cli.video_folder) - env_cfg.video_recorders = [ - VideoRecorderCfg( - source="visualizer", - output_dir=video_folder, + # create environment + render_mode = "rgb_array" if args_cli.video else None + # parse configuration via Hydra, so presets can be selected on the CLI (e.g. presets=isaacsim_physx) + env_cfg, _ = resolve_task_config(args_cli.task, "") + env_cfg.sim.device = args_cli.device + env_cfg.scene.num_envs = args_cli.num_envs + # the state machine emits absolute end-effector poses, so pick the IK action preset; the env + # defaults to relative joint targets, which RL trains on. + env_cfg.actions = ActionsCfg().ik + # frame the deformable at (0.5, 0.0, 0.05) rather than the world origin. ``viewer`` drives the + # Kit viewport and the video recorder; ``default_visualizer_cfg`` drives the Newton/Kit + # visualizer window, which otherwise falls back to VisualizerCfg's (4.0, -4.0, 3.0). + env_cfg.viewer.eye = (1.3, 0.6, 0.5) + env_cfg.viewer.lookat = (0.5, 0.0, 0.05) + env_cfg.sim.default_visualizer_cfg = VisualizerCfg(eye=env_cfg.viewer.eye, lookat=env_cfg.viewer.lookat) + + with launch_simulation(env_cfg, args_cli): + env = gym.make(args_cli.task, cfg=env_cfg, render_mode=render_mode) + + # wrap for video recording + if args_cli.video: + video_folder = os.path.abspath(args_cli.video_folder) + os.makedirs(video_folder, exist_ok=True) + env = gym.wrappers.RecordVideo( + env, + video_folder=video_folder, + step_trigger=lambda step: step == 0, video_length=args_cli.video_length, - video_interval=0, + disable_logger=True, ) - ] - print(f"[INFO] Recording video to {video_folder} (length={args_cli.video_length} steps)") - - env = gym.make(args_cli.task, cfg=env_cfg) - - # reset environment at start - env.reset() - - # create action buffers (position + quaternion) - actions = torch.zeros(env.unwrapped.action_space.shape, device=env.unwrapped.device) - actions[:, 3] = 1.0 - # desired rotation after grasping - desired_orientation = torch.zeros((env.unwrapped.num_envs, 4), device=env.unwrapped.device) - desired_orientation[:, 0] = 1.0 - - # Top-down approach: identity quaternion (wxyz, w=1) aligns panda_hand with the Franka root, - # giving the canonical top-down grasp pose. The bar lies along world-X, so the gripper - # closes across its short side without any wrist twist. - object_grasp_orientation = torch.zeros((env.unwrapped.num_envs, 4), device=env.unwrapped.device) - object_grasp_orientation[:, 0] = 1.0 - # Grasp at the deformable's centre of mass. - object_local_grasp_position = torch.tensor([0.0, 0.0, 0.0], device=env.unwrapped.device) - - # create state machine - pick_sm = PickAndLiftSm(env_cfg.sim.dt * env_cfg.decimation, env.unwrapped.num_envs, env.unwrapped.device) - - while simulation_app.is_running(): - # run everything in inference mode - with torch.inference_mode(): - # step environment - dones = env.step(actions)[-2] - - # observations - # -- end-effector frame - ee_frame_sensor = env.unwrapped.scene["ee_frame"] - tcp_rest_position = ( - ee_frame_sensor.data.target_pos_w.torch[..., 0, :].clone() - env.unwrapped.scene.env_origins - ) - tcp_rest_orientation = ee_frame_sensor.data.target_quat_w.torch[..., 0, :].clone() - # -- object frame - object_data: DeformableObjectData = env.unwrapped.scene["deformable"].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] - - # advance state machine - actions = pick_sm.compute( - torch.cat([tcp_rest_position, tcp_rest_orientation], dim=-1), - torch.cat([object_position, object_grasp_orientation], dim=-1), - torch.cat([desired_position, desired_orientation], dim=-1), - ) - - # reset state machine - if dones.any(): - pick_sm.reset_idx(dones.nonzero(as_tuple=False).squeeze(-1)) - - # close the environment - env.close() + print(f"[INFO] Recording video to {video_folder} (length={args_cli.video_length} steps)") + + # reset environment at start + env.reset() + + # create action buffers (position + quaternion) + actions = torch.zeros(env.unwrapped.action_space.shape, device=env.unwrapped.device) + actions[:, 3] = 1.0 + # desired rotation after grasping + desired_orientation = torch.zeros((env.unwrapped.num_envs, 4), device=env.unwrapped.device) + desired_orientation[:, 0] = 1.0 + + # Top-down approach: identity quaternion (wxyz, w=1) aligns panda_hand with the Franka root, + # giving the canonical top-down grasp pose. The bar lies along world-X, so the gripper + # closes across its short side without any wrist twist. + object_grasp_orientation = torch.zeros((env.unwrapped.num_envs, 4), device=env.unwrapped.device) + object_grasp_orientation[:, 0] = 1.0 + # Grasp 1 cm below the deformable's centre of mass, so the fingers close around its lower half. + object_local_grasp_position = torch.tensor([0.0, 0.0, -0.01], device=env.unwrapped.device) + + # create state machine + pick_sm = PickAndLiftSm(env_cfg.sim.dt * env_cfg.decimation, env.unwrapped.num_envs, env.unwrapped.device) + + for _ in range(args_cli.num_steps): + # run everything in inference mode + with torch.inference_mode(): + # step environment + dones = env.step(actions)[-2] + + # observations + # -- end-effector frame + ee_frame_sensor = env.unwrapped.scene["ee_frame"] + tcp_rest_position = ( + ee_frame_sensor.data.target_pos_w.torch[..., 0, :].clone() - env.unwrapped.scene.env_origins + ) + tcp_rest_orientation = ee_frame_sensor.data.target_quat_w.torch[..., 0, :].clone() + # -- object frame + object_data: DeformableObjectData = env.unwrapped.scene["deformable"].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] + + # advance state machine + actions = pick_sm.compute( + torch.cat([tcp_rest_position, tcp_rest_orientation], dim=-1), + torch.cat([object_position, object_grasp_orientation], dim=-1), + torch.cat([desired_position, desired_orientation], dim=-1), + ) + + # reset state machine + if dones.any(): + pick_sm.reset_idx(dones.nonzero(as_tuple=False).squeeze(-1)) + + # close the environment + env.close() if __name__ == "__main__": - # run the main function main() - # close sim app - simulation_app.close() diff --git a/source/isaaclab_contrib/changelog.d/mym-lift-soft.minor.rst b/source/isaaclab_contrib/changelog.d/mym-lift-soft.minor.rst new file mode 100644 index 000000000000..0909867a3b55 --- /dev/null +++ b/source/isaaclab_contrib/changelog.d/mym-lift-soft.minor.rst @@ -0,0 +1,6 @@ +Added +^^^^^ + +* Added :attr:`~isaaclab_contrib.deformable.VBDSolverCfg.rigid_body_particle_contact_buffer_size` + to size the per-body particle contact list. Contacts past the buffer are dropped from the body's + reaction list, which pushes the particles without recoiling the body and injects energy. diff --git a/source/isaaclab_contrib/isaaclab_contrib/deformable/newton_manager_cfg.py b/source/isaaclab_contrib/isaaclab_contrib/deformable/newton_manager_cfg.py index 70d04ed2f7fd..af4607860def 100644 --- a/source/isaaclab_contrib/isaaclab_contrib/deformable/newton_manager_cfg.py +++ b/source/isaaclab_contrib/isaaclab_contrib/deformable/newton_manager_cfg.py @@ -118,6 +118,16 @@ class VBDSolverCfg(NewtonModelSolverCfg): 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 body-particle 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): diff --git a/source/isaaclab_newton/changelog.d/mym-lift-soft.minor.rst b/source/isaaclab_newton/changelog.d/mym-lift-soft.minor.rst new file mode 100644 index 000000000000..3f9fd2104285 --- /dev/null +++ b/source/isaaclab_newton/changelog.d/mym-lift-soft.minor.rst @@ -0,0 +1,9 @@ +Added +^^^^^ + +* Added :attr:`~isaaclab_newton.physics.NewtonCollisionPipelineCfg.enable_rigid_soft_full_surface_contact` + to generate edge and triangle-interior soft contacts against rigid SDFs, so rigid features that + pass between soft vertices are caught. +* Added :class:`~isaaclab_newton.physics.NewtonShapeSDFCfg` and + :attr:`~isaaclab_newton.physics.NewtonCfg.sdf_shape_cfgs` to provision volume SDFs on collider + shapes selected by label regex, as required by full-surface rigid-soft contact. diff --git a/source/isaaclab_newton/isaaclab_newton/physics/__init__.pyi b/source/isaaclab_newton/isaaclab_newton/physics/__init__.pyi index 46cc26e9318f..fa541baaafa8 100644 --- a/source/isaaclab_newton/isaaclab_newton/physics/__init__.pyi +++ b/source/isaaclab_newton/isaaclab_newton/physics/__init__.pyi @@ -17,6 +17,7 @@ __all__ = [ "NewtonCollisionPipelineCfg", "NewtonManager", "NewtonShapeCfg", + "NewtonShapeSDFCfg", "NewtonSolverCfg", "NewtonXPBDManager", "XPBDSolverCfg", @@ -35,6 +36,7 @@ from .newton_manager import NewtonManager from .newton_manager_cfg import ( NewtonCfg, NewtonShapeCfg, + NewtonShapeSDFCfg, NewtonSolverCfg, ) from .xpbd_manager import NewtonXPBDManager diff --git a/source/isaaclab_newton/isaaclab_newton/physics/newton_collision_cfg.py b/source/isaaclab_newton/isaaclab_newton/physics/newton_collision_cfg.py index 9e75d3153308..3a7b810208d6 100644 --- a/source/isaaclab_newton/isaaclab_newton/physics/newton_collision_cfg.py +++ b/source/isaaclab_newton/isaaclab_newton/physics/newton_collision_cfg.py @@ -150,6 +150,17 @@ class NewtonCollisionPipelineCfg: Defaults to ``0.01`` (same as Newton's default). """ + enable_rigid_soft_full_surface_contact: bool = False + """Whether to generate soft contacts over the full soft-mesh surface against rigid SDFs. + + When ``True``, Newton adds edge and triangle-interior soft contacts (in addition to the + per-vertex particle contacts) so rigid features that pass between soft vertices are caught. + Requires a volume SDF on every participating rigid mesh/convex shape; provision these via + :attr:`~isaaclab_newton.physics.NewtonShapeSDFCfg` on :attr:`NewtonCfg.sdf_shape_cfgs`. + + Defaults to ``False`` (same as Newton's default). + """ + requires_grad: bool | None = None """Whether to enable gradient computation for collision. diff --git a/source/isaaclab_newton/isaaclab_newton/physics/newton_manager.py b/source/isaaclab_newton/isaaclab_newton/physics/newton_manager.py index fd5e0a735f7b..6394ccd5be74 100644 --- a/source/isaaclab_newton/isaaclab_newton/physics/newton_manager.py +++ b/source/isaaclab_newton/isaaclab_newton/physics/newton_manager.py @@ -1178,6 +1178,35 @@ def _prepare_builder_for_finalize(cls, builder: ModelBuilder) -> None: The default implementation is a no-op. """ + @classmethod + def _provision_shape_sdfs(cls, builder: ModelBuilder) -> None: + """Request volume SDFs on collider shapes selected by :attr:`NewtonCfg.sdf_shape_cfgs`. + + Newton retains per-shape SDF requests on the builder (``shape_force_sdf`` and + ``shape_sdf_max_resolution``) until :meth:`ModelBuilder.finalize` generates the SDF data. + Rather than intercepting shape creation, this matches the already-populated shape labels + by regex and flips the retained flags, equivalent to calling + ``ShapeConfig.configure_sdf(force_sdf=True, max_resolution=...)`` at add time. Required by + :attr:`NewtonCollisionPipelineCfg.enable_rigid_soft_full_surface_contact`. + """ + cfg = PhysicsManager._cfg + if not isinstance(cfg, NewtonCfg) or not cfg.sdf_shape_cfgs: + return + + labels = list(getattr(builder, "shape_label", ()) or ()) + if not labels: + return + + for sdf_cfg in cfg.sdf_shape_cfgs: + if not sdf_cfg.shape_label_patterns: + continue + matched, _ = resolve_matching_names(sdf_cfg.shape_label_patterns, labels, raise_when_no_match=False) + for index in matched: + builder.shape_force_sdf[index] = True + if sdf_cfg.max_resolution is not None: + builder.shape_sdf_max_resolution[index] = sdf_cfg.max_resolution + builder.shape_sdf_target_voxel_size[index] = None + @classmethod def cl_register_site(cls, body_pattern: str | None, xform: wp.transform, *, per_world: bool = False) -> str: """Register a site request for injection into prototypes before replication. @@ -1524,6 +1553,11 @@ def start_simulation(cls) -> None: cls._builder.request_state_attributes(*cls._pending_extended_state_attributes) NewtonManager._pending_extended_state_attributes = set() cls._prepare_builder_for_finalize(cls._builder) + # Provision volume SDFs on selected collider shapes (e.g. gripper) before finalize so + # full-surface rigid-soft contact has the SDFs it requires. Runs after the subclass hook + # so replicated shape labels are present, and unconditionally so subclasses that override + # _prepare_builder_for_finalize without calling super() still get it. + cls._provision_shape_sdfs(cls._builder) with Timer(name="newton_finalize_builder", msg="Finalize builder took:"): NewtonManager._model = cls._builder.finalize(device=device) cls._model.set_gravity(cls._gravity_vector) 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 8249d04bd096..94872de460cc 100644 --- a/source/isaaclab_newton/isaaclab_newton/physics/newton_manager_cfg.py +++ b/source/isaaclab_newton/isaaclab_newton/physics/newton_manager_cfg.py @@ -8,6 +8,7 @@ from __future__ import annotations import logging +from dataclasses import field from typing import TYPE_CHECKING, Literal from isaaclab.physics import PhysicsCfg @@ -98,6 +99,32 @@ class NewtonShapeCfg: """ +@configclass +class NewtonShapeSDFCfg: + """Provisions a volume SDF on selected rigid collider shapes before finalize. + + Full-surface rigid-soft contact + (:attr:`~isaaclab_newton.physics.NewtonCollisionPipelineCfg.enable_rigid_soft_full_surface_contact`) + requires an SDF on every participating rigid mesh/convex shape. Newton retains per-shape SDF + requests on the builder until finalize; this config targets shapes by full label regex and + forwards to ``ModelBuilder.ShapeConfig.configure_sdf(force_sdf=True)`` semantics. + """ + + shape_label_patterns: list[str] = field(default_factory=list) + """Full Newton shape-label regexes; every matched collider shape receives a volume SDF. + + Matching is a full-label regex against ``Model.shape_label`` (append ``.*`` to match a body's + descendant collider shapes, e.g. ``r"/World/envs/env_.*/Robot/panda_hand.*"``). + """ + + max_resolution: int | None = None + """Maximum SDF grid resolution [voxels], must be divisible by 8. + + ``None`` builds the SDF at Newton's default resolution (``force_sdf`` only), which is the + lightest way to provision the SDF needed for full-surface contact. + """ + + @configclass class NewtonCfg(PhysicsCfg): """Configuration for Newton physics manager. @@ -169,6 +196,14 @@ class NewtonCfg(PhysicsCfg): :class:`NewtonShapeCfg` for the declared fields. """ + sdf_shape_cfgs: list[NewtonShapeSDFCfg] = field(default_factory=list) + """Per-shape volume SDF provisioning applied to the builder before finalize. + + Each entry selects collider shapes by label regex and requests a volume SDF on them, as + required by :attr:`NewtonCollisionPipelineCfg.enable_rigid_soft_full_surface_contact`. + Defaults to an empty list (no SDFs provisioned beyond Newton's own heuristics). + """ + simplify_meshes: bool = True """Whether Newton replication simplifies mesh colliders to convex hulls. diff --git a/source/isaaclab_tasks/changelog.d/mym-lift-soft.minor.rst b/source/isaaclab_tasks/changelog.d/mym-lift-soft.minor.rst new file mode 100644 index 000000000000..f90ad6e01a17 --- /dev/null +++ b/source/isaaclab_tasks/changelog.d/mym-lift-soft.minor.rst @@ -0,0 +1,53 @@ +Added +^^^^^ + +* Added a task-local ``mdp`` package for the Franka deformable lift environments, providing + deformable-aware rewards, observations, terminations, events, curricula, and a pose command that + tracks the deformable center of mass. +* Added an ``ik`` action preset to the Franka deformable lift environments that drives the arm with + an absolute end-effector pose through a differential inverse-kinematics controller. Select it + with ``presets=ik``. +* Added a gravity curriculum to the Franka deformable lift environments that linearly ramps the + vertical gravity up to -9.81 m/s^2 over the first environment steps, so the policy learns to + grasp before it has to hold the object up. The soft-beam tasks ramp from near zero over 10000 + steps, the cloth tasks from -1.0 m/s^2 over 20000 steps. +* Added terminations for a diverged solve (non-finite deformable or robot state) and for joint + velocities beyond the simulation limits, so unrecoverable environments reset instead of poisoning + the rollout. + +Changed +^^^^^^^ + +* **Breaking:** Changed the Franka deformable lift environments to select their action space + through a :class:`~isaaclab_tasks.utils.PresetCfg`, with ``joint`` (relative joint-position arm + targets plus a limit-rescaled gripper) as the new default. ``Isaac-Lift-Soft-Franka`` and + ``Isaac-Lift-Soft-Franka-Camera`` previously used absolute task-space differential inverse + kinematics, and ``Isaac-Lift-Cloth-Franka`` and ``Isaac-Lift-Cloth-Franka-Camera`` previously + used absolute joint-position targets. Append ``presets=ik`` to the run command to get the + task-space inverse-kinematics action space back. +* **Breaking:** Changed the rsl_rl ``experiment_name`` of ``Isaac-Lift-Soft-Franka`` and + ``Isaac-Lift-Cloth-Franka`` from ``franka_deformable`` to ``franka_soft``. New runs are written to + ``logs/rsl_rl/franka_soft``; move existing ``logs/rsl_rl/franka_deformable`` run directories there + to resume from an older checkpoint. +* Changed the Franka deformable lift environments to simulate under real gravity: gravity is no + longer disabled on the robot, and the vertical gravity of ``Isaac-Lift-Soft-Franka`` and + ``Isaac-Lift-Soft-Franka-Camera`` is no longer zeroed. +* Changed the simulation step of the Franka deformable lift environments from 1/60 s to 1/120 s + with a decimation of 4, which halves the policy control rate from 60 Hz to 30 Hz, and raised the + default number of environments of ``Isaac-Lift-Soft-Franka`` from 128 to 2048. +* Changed the table of the Franka deformable lift environments from the ``SeattleLabTable`` USD + asset to an invisible cuboid collider whose top surface sits at ``z = 0``. The goal command's + success visualizer draws the table instead, tinted by whether the goal is reached. +* Changed the rsl_rl PPO configuration of ``Isaac-Lift-Soft-Franka`` and + ``Isaac-Lift-Cloth-Franka`` to the actor/critic model configurations, using + :class:`~isaaclab_rl.rsl_rl.RslRlMLPModelCfg` with observation normalization and explicit + ``obs_groups``, a learning rate of 1e-3, and ``max_iterations`` lowered from 50000 to 5000. +* Retuned ``Isaac-Lift-Soft-Franka`` and ``Isaac-Lift-Soft-Franka-Camera`` for stable grasping: a + stiffer and denser beam, a smaller particle radius, explicit collider contact and rest offsets, + and full-surface rigid-soft contact with signed-distance fields on the gripper. +* Retuned the Franka arm and hand actuator gains of the Franka deformable lift environments, adding + realistic armature and a slower, weaker gripper so it settles on the object instead of crushing + it. This replaces the previous per-task gripper overrides, so the cloth tasks now use the same + gains as the soft-beam tasks. +* Changed ``Isaac-Lift-Cloth-Franka`` to place the cloth over a kinematic rigid support that is + registered with the coupler, so the cloth no longer passes through it. diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/agents/rsl_rl_ppo_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/agents/rsl_rl_ppo_cfg.py index b884b0f16152..3150aa9b4782 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/agents/rsl_rl_ppo_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/agents/rsl_rl_ppo_cfg.py @@ -9,7 +9,6 @@ RslRlCNNModelCfg, RslRlMLPModelCfg, RslRlOnPolicyRunnerCfg, - RslRlPpoActorCriticCfg, RslRlPpoAlgorithmCfg, ) @@ -32,18 +31,25 @@ @configclass class FrankaDeformablePPORunnerCfg(RslRlOnPolicyRunnerCfg): num_steps_per_env = 24 - max_iterations = 50000 + max_iterations = 5000 save_interval = 50 - experiment_name = "franka_deformable" - policy = RslRlPpoActorCriticCfg( - init_noise_std=1.0, - actor_obs_normalization=False, - critic_obs_normalization=False, - actor_hidden_dims=[256, 128, 64], - critic_hidden_dims=[256, 128, 64], + experiment_name = "franka_soft" + obs_groups = { + "actor": ["policy"], + "critic": ["policy"], + } + actor = RslRlMLPModelCfg( + hidden_dims=[256, 128, 64], + activation="elu", + obs_normalization=True, + distribution_cfg=RslRlMLPModelCfg.GaussianDistributionCfg(init_std=1.0), + ) + critic = RslRlMLPModelCfg( + hidden_dims=[256, 128, 64], activation="elu", + obs_normalization=True, ) - algorithm = ALGO_CFG + algorithm = ALGO_CFG.replace(learning_rate=1.0e-3) @configclass 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 ff2123deec25..277d3da69b8d 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,7 @@ from __future__ import annotations -from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg, NewtonShapeCfg +from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg from isaaclab_newton.sim.schemas import NewtonDeformableBodyPropertiesCfg from isaaclab_newton.sim.spawners.materials import NewtonSurfaceDeformableBodyMaterialCfg from isaaclab_ovphysx.physics import OvPhysxCfg @@ -15,19 +15,20 @@ from isaaclab_physx.sim.spawners.materials import PhysxSurfaceDeformableBodyMaterialCfg import isaaclab.sim as sim_utils -from isaaclab.assets import AssetBaseCfg +from isaaclab.assets import RigidObjectCfg from isaaclab.assets.deformable_object import DeformableObjectCfg +from isaaclab.managers import CurriculumTermCfg as CurrTerm from isaaclab.managers import EventTermCfg as EventTerm from isaaclab.managers import SceneEntityCfg from isaaclab.sensors import CameraCfg 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_contrib.deformable.newton_manager_cfg import VBDSolverCfg -from isaaclab_tasks.core.lift import mdp from isaaclab_tasks.utils import PresetCfg +from . import mdp from .franka_soft_env_cfg import ( FRANKA_CAMERA_CFG, FrankaCameraObservationsCfg, @@ -58,18 +59,16 @@ class PhysicsCfg(PresetCfg): CouplerEntryCfg( name="rigid", solver_cfg=MJWarpSolverCfg( - njmax=40, - nconmax=20, cone="elliptic", ls_iterations=20, integrator="implicitfast", - ccd_iterations=100, ), - bodies=[r"/World/envs/env_.*/Robot"], + # the cube is a rigid body, so it must be owned by the rigid entry + bodies=[r"/World/envs/env_.*/Robot", r"/World/envs/env_.*/Cube"], ), CouplerEntryCfg( name="soft", - solver_cfg=VBDSolverCfg(iterations=10), + solver_cfg=VBDSolverCfg(iterations=10, rigid_body_particle_contact_buffer_size=1024), all_particles=True, include_static_shapes=True, ), @@ -81,20 +80,15 @@ class PhysicsCfg(PresetCfg): bodies=[ r"/World/envs/env_.*/Robot/panda_hand", r"/World/envs/env_.*/Robot/panda_(left|right)finger", + r"/World/envs/env_.*/Cube", ], + # detect contact every substep so the gripper stops at the cloth surface collide_interval=1, ) ], iterations=1, - model_cfg=NewtonModelCfg( - soft_contact_ke=1e3, - soft_contact_kd=1e-5, - soft_contact_mu=0.5, - ), ), - default_shape_cfg=NewtonShapeCfg(ke=1e3, kd=1e-5, mu=1e-4), - num_substeps=10, - use_cuda_graph=True, + num_substeps=2, ) ovphysx: OvPhysxCfg = OvPhysxCfg() @@ -157,12 +151,15 @@ class FrankaClothSceneCfg(_FrankaSoftSceneCfg): deformable: DeformableCfg = DeformableCfg() - # Static collidable cube the cloth drops onto (sits on the table top at z = 0). - cube: AssetBaseCfg = AssetBaseCfg( + # Collidable cube the cloth drapes onto (sits on the table top at z = 0). Kinematic so the + # reset event can move it under the randomized cloth without it being simulated. + cube: RigidObjectCfg = RigidObjectCfg( prim_path="{ENV_REGEX_NS}/Cube", - init_state=AssetBaseCfg.InitialStateCfg(pos=(0.45, 0.0, 0.04)), + init_state=RigidObjectCfg.InitialStateCfg(pos=(0.45, 0.0, 0.04)), spawn=sim_utils.CuboidCfg( size=(0.03, 0.01, 0.08), + rigid_props=sim_utils.RigidBodyPropertiesCfg(kinematic_enabled=True, disable_gravity=True), + mass_props=sim_utils.MassPropertiesCfg(mass=1.0), collision_props=sim_utils.CollisionPropertiesCfg(), visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.2, 0.2, 0.25)), ), @@ -198,24 +195,36 @@ class FrankaClothCameraSceneCfg(FrankaClothSceneCfg): @configclass -class ActionsCfg: - """7-dim arm joint position + 1-dim binary gripper.""" +class CurriculumCfg: + """Ramp the action-rate penalty once the policy has learned to lift (matches rigid recipe).""" - arm_action = mdp.JointPositionActionCfg( - asset_name="robot", joint_names=["panda_joint.*"], scale=0.1, use_default_offset=True + action_rate = CurrTerm( + func=mdp.modify_reward_weight, params={"term_name": "action_rate", "weight": -1e-2, "num_steps": 50000} ) - gripper_action = mdp.BinaryJointPositionActionCfg( - asset_name="robot", - joint_names=["panda_finger.*"], - open_command_expr={"panda_finger_.*": 0.05}, - close_command_expr={"panda_finger_.*": 0.0}, + + # Since we use 24 steps per env, 20000 steps correspond to 20000/24 = 833.33 learning iterations + gravity = CurrTerm( + func=mdp.modify_gravity_linear, + params={"start_gravity_z": -1.0, "end_gravity_z": -9.81, "start_step": 0, "end_step": 20000}, ) @configclass -class EventCfg(FrankaSoftEventCfg): +class FrankaClothEventCfg(FrankaSoftEventCfg): """Reset and startup events for the Franka cloth environment.""" + # Replaces the base term so the cube follows the randomized cloth position. + reset_deformable = EventTerm( + func=mdp.reset_deformable_over_support, + mode="reset", + params={ + "position_range": {"x": (-0.1, 0.1), "y": (-0.25, 0.25), "z": (0.0, 0.0)}, + "support_offset_range": {"x": (-0.02, 0.02), "y": (-0.02, 0.02)}, + "asset_cfg": SceneEntityCfg("deformable"), + "support_cfg": SceneEntityCfg("cube"), + }, + ) + robot_physics_material = EventTerm( func=mdp.randomize_rigid_body_material, mode="startup", @@ -229,9 +238,9 @@ class EventCfg(FrankaSoftEventCfg): ) -def _make_ovphysx_event_cfg() -> EventCfg: +def _make_ovphysx_event_cfg() -> FrankaClothEventCfg: """Create cloth events that select all robot shapes on OvPhysX.""" - cfg = EventCfg() + cfg = FrankaClothEventCfg() cfg.robot_physics_material.params["asset_cfg"] = SceneEntityCfg("robot") return cfg @@ -240,8 +249,8 @@ def _make_ovphysx_event_cfg() -> EventCfg: class EventPresetCfg(PresetCfg): """Preset config for Franka cloth startup and reset events.""" - newton_mjwarp_vbd_proxy: EventCfg = EventCfg() - ovphysx: EventCfg = _make_ovphysx_event_cfg() + newton_mjwarp_vbd_proxy: FrankaClothEventCfg = FrankaClothEventCfg() + ovphysx: FrankaClothEventCfg = _make_ovphysx_event_cfg() default = newton_mjwarp_vbd_proxy @@ -255,30 +264,13 @@ class EventPresetCfg(PresetCfg): class FrankaClothEnvCfg(FrankaSoftEnvCfg): """Manager-based RL environment: Franka Panda lifting a surface deformable.""" - # Scene settings scene: FrankaClothScenePresetCfg = FrankaClothScenePresetCfg() - # Basic settings - actions: ActionsCfg = ActionsCfg() - # MDP settings events: EventPresetCfg = EventPresetCfg() + curriculum: CurriculumCfg = CurriculumCfg() def __post_init__(self) -> None: - # general settings - self.decimation = 1 - self.episode_length_s = 5.0 - - # simulation settings - self.sim.dt = 1 / 60.0 - self.sim.render_interval = self.decimation - - # Hint for the viewport camera when running interactively with --viz kit. - # Using default_visualizer_cfg rather than visualizer_cfgs avoids forcing - # Kit viewport creation in kitless/headless contexts. - from isaaclab_visualizers.kit import KitVisualizerCfg - - self.sim.default_visualizer_cfg = KitVisualizerCfg( - origin_type="asset", origin_track_path="robot", origin_env_index=0, eye=(1.25, -1.5, 0.6) - ) + super().__post_init__() + # override the soft-beam physics with the cloth presets self.sim.physics = PhysicsCfg() 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 610ca1bae507..1553dceeca2b 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 @@ -3,25 +3,30 @@ # # SPDX-License-Identifier: BSD-3-Clause -"""Configuration for the Franka deformable lifting environment.""" +"""Configuration for the Franka deformable (soft beam) lifting environment.""" from __future__ import annotations -from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg, NewtonShapeCfg +from isaaclab_newton.physics import ( + MJWarpSolverCfg, + NewtonCfg, + NewtonCollisionPipelineCfg, + NewtonShapeSDFCfg, +) from isaaclab_newton.sim.schemas import NewtonDeformableBodyPropertiesCfg from isaaclab_newton.sim.spawners.materials import NewtonDeformableBodyMaterialCfg from isaaclab_ovphysx.physics import OvPhysxCfg from isaaclab_physx.physics import PhysxCfg -from isaaclab_physx.sim.schemas import PhysxDeformableBodyPropertiesCfg +from isaaclab_physx.sim.schemas import PhysxCollisionCfg, PhysxDeformableBodyPropertiesCfg from isaaclab_physx.sim.spawners.materials import PhysxDeformableBodyMaterialCfg import isaaclab.sim as sim_utils from isaaclab.assets import ArticulationCfg, AssetBaseCfg from isaaclab.assets.deformable_object import DeformableObjectCfg -from isaaclab.controllers.differential_ik_cfg import DifferentialIKControllerCfg +from isaaclab.controllers import DifferentialIKControllerCfg from isaaclab.envs import ManagerBasedRLEnvCfg from isaaclab.envs import mdp as env_mdp -from isaaclab.envs.mdp.actions.actions_cfg import DifferentialInverseKinematicsActionCfg +from isaaclab.managers import CurriculumTermCfg as CurrTerm from isaaclab.managers import EventTermCfg as EventTerm from isaaclab.managers import ObservationGroupCfg as ObsGroup from isaaclab.managers import ObservationTermCfg as ObsTerm @@ -33,9 +38,10 @@ from isaaclab.scene import InteractiveSceneCfg from isaaclab.sensors import CameraCfg, FrameTransformerCfg from isaaclab.sensors.frame_transformer.frame_transformer_cfg import OffsetCfg -from isaaclab.sim.spawners.from_files.from_files_cfg import GroundPlaneCfg, UsdFileCfg +from isaaclab.sim.spawners.from_files.from_files_cfg import GroundPlaneCfg from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR from isaaclab.utils.configclass import configclass +from isaaclab.visualizers import VisualizerCfg from isaaclab_contrib.coupling import ( CouplerEntryCfg, @@ -47,10 +53,11 @@ VBDSolverCfg, ) -from isaaclab_tasks.core.lift import mdp from isaaclab_tasks.utils import PresetCfg from isaaclab_tasks.utils.presets import MultiBackendRendererCfg +from . import mdp + ## # Pre-defined configs ## @@ -64,8 +71,16 @@ # Shared volume material parameters. The Newton config below uses the equivalent Lame parameters. -YOUNGS_MODULUS = 8e4 -POISSONS_RATIO = 0.25 +YOUNGS_MODULUS = 2e5 +POISSONS_RATIO = 0.3 + +# Table collider whose top surface sits at z = 0. Spawned invisible: the command term's success +# visualizer draws it instead, tinted by whether the goal is reached. +TABLE_SPAWN_CFG = sim_utils.CuboidCfg( + size=(1.3, 0.9, 1.05), + collision_props=sim_utils.CollisionPropertiesCfg(), + visible=False, +) FRANKA_CAMERA_CFG = CameraCfg( @@ -91,14 +106,14 @@ class DeformableCfg(PresetCfg): prim_path="{ENV_REGEX_NS}/Deformable", init_state=DeformableObjectCfg.InitialStateCfg(pos=(0.5, 0.0, 0.05)), spawn=sim_utils.MeshCuboidCfg( - size=(0.3, 0.05, 0.05), + size=(0.3, 0.04, 0.04), deformable_props=NewtonDeformableBodyPropertiesCfg(), - visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.95, 0.85, 0.1)), + visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.45, 0.45, 0.85)), physics_material=NewtonDeformableBodyMaterialCfg( - density=300.0, + density=1000.0, k_mu=YOUNGS_MODULUS / (2.0 * (1.0 + POISSONS_RATIO)), k_lambda=(YOUNGS_MODULUS * POISSONS_RATIO / ((1.0 + POISSONS_RATIO) * (1.0 - 2.0 * POISSONS_RATIO))), - particle_radius=0.01, + particle_radius=0.0025, ), ), ) @@ -107,15 +122,16 @@ class DeformableCfg(PresetCfg): prim_path="{ENV_REGEX_NS}/Deformable", init_state=DeformableObjectCfg.InitialStateCfg(pos=(0.5, 0.0, 0.05)), spawn=sim_utils.MeshCuboidCfg( - size=(0.3, 0.05, 0.05), + size=(0.3, 0.04, 0.04), deformable_props=PhysxDeformableBodyPropertiesCfg(), - visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.95, 0.85, 0.1)), + collision_props=[PhysxCollisionCfg(rest_offset=0.0005, contact_offset=0.005)], + visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.45, 0.45, 0.85)), physics_material=PhysxDeformableBodyMaterialCfg( - density=300.0, + density=1000.0, youngs_modulus=YOUNGS_MODULUS, poissons_ratio=POISSONS_RATIO, - static_friction=10.0, - dynamic_friction=5.0, + static_friction=1.0, + dynamic_friction=1.0, ), ), ) @@ -142,7 +158,7 @@ class PhysicsCfg(PresetCfg): ), CouplerEntryCfg( name="soft", - solver_cfg=VBDSolverCfg(iterations=10), + solver_cfg=VBDSolverCfg(iterations=10, rigid_body_particle_contact_buffer_size=256), all_particles=True, include_static_shapes=True, ), @@ -155,21 +171,32 @@ class PhysicsCfg(PresetCfg): r"/World/envs/env_.*/Robot/panda_hand", r"/World/envs/env_.*/Robot/panda_(left|right)finger", ], - collide_interval=5, + collide_interval=1, + collision_pipeline=NewtonCollisionPipelineCfg( + enable_rigid_soft_full_surface_contact=True, + ), ) ], iterations=1, - model_cfg=NewtonModelCfg( - soft_contact_ke=1e4, - soft_contact_kd=1e-5, - soft_contact_mu=5.0, - ), + model_cfg=NewtonModelCfg(soft_contact_ke=5.0e3), ), - default_shape_cfg=NewtonShapeCfg(ke=4e4, kd=1e-5, mu=5.0), - num_substeps=10, + sdf_shape_cfgs=[ + NewtonShapeSDFCfg( + shape_label_patterns=[ + r"/World/envs/env_.*/Robot/panda_hand/collisions/collisions", + r"/World/envs/env_.*/Robot/panda_(left|right)finger/collisions/collisions", + ], + # ~2.3 mm voxels on the fingers; finer isn't needed for contact resolution. + max_resolution=8, + ) + ], + num_substeps=2, ) - isaacsim_physx: PhysxCfg = PhysxCfg() + isaacsim_physx: PhysxCfg = PhysxCfg( + friction_offset_threshold=0.001, + friction_correlation_distance=0.005, + ) ovphysx: OvPhysxCfg = OvPhysxCfg() physx: PhysxAutoCfg = PhysxAutoCfg(isaacsim_physx=isaacsim_physx, ovphysx=ovphysx) @@ -202,13 +229,13 @@ class _FrankaSoftSceneCfg(InteractiveSceneCfg): deformable: DeformableCfg = DeformableCfg() - # static table matching the Newton example: half-extents (0.4, 0.4, 0.1) → top at z = 0.2 - # NOTE: SeattleLabTable USD has its origin on the top surface, so the deformable object - # sits directly on it when placed at z = 0.05. + # static table collider with its top surface at z = 0. Kept invisible: the success + # visualizer renders the visible table, colored by whether the goal is reached + # (see CommandsCfg). table: AssetBaseCfg = AssetBaseCfg( prim_path="{ENV_REGEX_NS}/Table", - init_state=AssetBaseCfg.InitialStateCfg(pos=[0.5, 0.0, 0.0], rot=[0.0, 0.0, 0.707, 0.707]), - spawn=UsdFileCfg(usd_path=f"{ISAAC_NUCLEUS_DIR}/Props/Mounts/SeattleLabTable/table_instanceable.usd"), + init_state=AssetBaseCfg.InitialStateCfg(pos=[0.5, 0.0, -0.525]), + spawn=TABLE_SPAWN_CFG, ) # ground plane @@ -228,14 +255,29 @@ class _FrankaSoftSceneCfg(InteractiveSceneCfg): ) def __post_init__(self) -> None: - # disable gravity on the arm so the low-PD actuators do not need to fight gravity sag, - # which is the dominant source of steady-state IK tracking error. - self.robot.spawn.rigid_props.disable_gravity = True - - # increase franka gripper stiffness - self.robot.actuators["panda_hand"].effort_limit_sim = 500.0 - self.robot.actuators["panda_hand"].stiffness = 1000.0 - self.robot.actuators["panda_hand"].damping = 100.0 + # Re-tuned Franka actuators: stiff arm gains with realistic armature so the low-inertia + # default gains do not let the fingers tunnel through the soft body, and a slower, weaker + # gripper so it settles on the beam surface instead of crushing it. Velocity limits are + # required by the joint_vel_out_of_sim_limit termination. Scoped here rather than in + # FRANKA_PANDA_CFG so the other Franka tasks keep the stock asset. + shoulder = self.robot.actuators["panda_shoulder"] + shoulder.velocity_limit_sim = 2.175 + shoulder.stiffness = 600.0 + shoulder.damping = 50.0 + shoulder.armature = {"panda_joint[1-2]": 0.6057, "panda_joint[3-4]": 0.4625} + + forearm = self.robot.actuators["panda_forearm"] + forearm.velocity_limit_sim = 2.61 + forearm.stiffness = {"panda_joint5": 250.0, "panda_joint6": 150.0, "panda_joint7": 50.0} + forearm.damping = {"panda_joint5": 30.0, "panda_joint6": 25.0, "panda_joint7": 15.0} + forearm.armature = 0.2055 + + hand = self.robot.actuators["panda_hand"] + hand.effort_limit_sim = 70.0 + hand.velocity_limit_sim = 0.2 + hand.stiffness = 750.0 + hand.damping = 175.0 + hand.armature = 0.1 @configclass @@ -254,12 +296,12 @@ class _FrankaSoftCameraSceneCfg(_FrankaSoftSceneCfg): class CommandsCfg: """Commands for the deformable goal pose (xyz + identity quat in robot root frame).""" - deformable_pose = mdp.UniformPoseCommandCfg( + deformable_pose = mdp.DeformableUniformPoseCommandCfg( asset_name="robot", - body_name="panda_hand", + object_name="deformable", resampling_time_range=(5.0, 5.0), debug_vis=True, - ranges=mdp.UniformPoseCommandCfg.Ranges( + ranges=mdp.DeformableUniformPoseCommandCfg.Ranges( pos_x=(0.4, 0.6), pos_y=(-0.25, 0.25), pos_z=(0.25, 0.5), @@ -267,16 +309,16 @@ class CommandsCfg: pitch=(0.0, 0.0), yaw=(0.0, 0.0), ), - # Render the goal as a transparent colored sphere (a point) instead of a coordinate frame. - goal_pose_visualizer_cfg=VisualizationMarkersCfg( - prim_path="/Visuals/Command/goal_pose", + # the invisible table is drawn by these markers, tinted green once the goal is reached + success_vis_asset_name="table", + success_visualizer_cfg=VisualizationMarkersCfg( + prim_path="/Visuals/SuccessMarkers", markers={ - "sphere": sim_utils.SphereCfg( - radius=0.03, - visual_material=sim_utils.PreviewSurfaceCfg( - diffuse_color=(0.1, 0.9, 0.2), - opacity=0.4, - ), + "failure": TABLE_SPAWN_CFG.replace( + visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.8, 0.5, 0.5)), visible=True + ), + "success": TABLE_SPAWN_CFG.replace( + visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.5, 0.8, 0.5)), visible=True ), }, ), @@ -284,10 +326,21 @@ class CommandsCfg: @configclass -class ActionsCfg: +class _JointActionsCfg: + """7-dim relative joint-position arm targets + 1-dim limit-rescaled gripper.""" + + arm_action = mdp.RelativeJointPositionActionCfg(asset_name="robot", joint_names=["panda_joint.*"], scale=0.03) + + gripper_action = mdp.JointPositionToLimitsActionCfg( + asset_name="robot", joint_names=["panda_finger.*"], rescale_to_limits=True + ) + + +@configclass +class _IkActionsCfg: """7-dim absolute end-effector pose (xyz + quaternion) via differential IK + 1-dim binary gripper.""" - arm_action = DifferentialInverseKinematicsActionCfg( + arm_action = mdp.DifferentialInverseKinematicsActionCfg( asset_name="robot", joint_names=["panda_joint.*"], body_name="panda_hand", @@ -297,16 +350,28 @@ class ActionsCfg: ik_method="dls", ik_params={"lambda_val": 0.6}, ), - body_offset=DifferentialInverseKinematicsActionCfg.OffsetCfg(pos=[0.0, 0.0, 0.107]), + body_offset=mdp.DifferentialInverseKinematicsActionCfg.OffsetCfg(pos=[0.0, 0.0, 0.107]), ) + gripper_action = mdp.BinaryJointPositionActionCfg( asset_name="robot", joint_names=["panda_finger.*"], open_command_expr={"panda_finger_.*": 0.05}, - close_command_expr={"panda_finger_.*": 0.0}, + close_command_expr={"panda_finger_.*": 0.015}, ) +@configclass +class ActionsCfg(PresetCfg): + """Action-space presets: joint-space for RL, task-space IK for scripted end-effector control.""" + + joint: _JointActionsCfg = _JointActionsCfg() + + ik: _IkActionsCfg = _IkActionsCfg() + + default = joint + + @configclass class ObservationsCfg: """Policy observations: joint state, deformable COM in robot frame, target, last action.""" @@ -394,7 +459,7 @@ class EventCfg: func=mdp.reset_nodal_state_uniform, mode="reset", params={ - "position_range": {"x": (-0.05, 0.05), "y": (-0.05, 0.05), "z": (0.0, 0.0)}, + "position_range": {"x": (-0.15, 0.1), "y": (-0.2, 0.2), "z": (0.0, 0.0)}, "velocity_range": {}, "asset_cfg": SceneEntityCfg("deformable"), }, @@ -403,48 +468,79 @@ class EventCfg: @configclass class RewardsCfg: - """Lift-to-target reward for a deformable object.""" - + """Deformable analogue of the winning rigid-cube lift recipe. + + The table top sits at z = 0 and the beam's rest COM at ~0.05 m. The dense lift reward gates at + 0.02 m, i.e. below rest, so it stays graded across the full lift range and only vanishes when + the beam is pressed into the table. The goal-tracking terms gate at 0.0 m, so they are + effectively always active. Success = COM within 5 cm of the goal position. + """ + + # The hand targets the COM so the grasp lands mid-beam (targeting the nearest node lets the + # gripper grab an end, which turns the beam into an unstable pole). The fingers target the + # nearest surface node instead: targeting the COM with both fingers is maximized only when + # both fingertips reach the object's center, i.e. by closing and indenting the beam. reaching_deformable = RewTerm( - func=mdp.deformable_ee_distance, + func=mdp.deformable_com_ee_distance, params={"std": 0.1, "asset_cfg": SceneEntityCfg("deformable")}, weight=5.0, ) + lifting_deformable = RewTerm( - func=mdp.deformable_lifted, - params={"minimal_height": 0.04, "asset_cfg": SceneEntityCfg("deformable")}, + func=mdp.deformable_lifting, + params={"std": 0.1, "minimal_height": 0.02, "asset_cfg": SceneEntityCfg("deformable")}, weight=5.0, ) + + deformable_goal_tracking_delta = RewTerm( + func=mdp.deformable_com_goal_distance_delta, + params={ + "minimal_height": 0.0, + "command_name": "deformable_pose", + "asset_cfg": SceneEntityCfg("deformable"), + }, + weight=500.0, + ) + deformable_goal_tracking = RewTerm( func=mdp.deformable_com_goal_distance, params={ "std": 0.3, - "minimal_height": 0.075, + "minimal_height": 0.0, "command_name": "deformable_pose", + "success_threshold": 0.05, "asset_cfg": SceneEntityCfg("deformable"), }, - weight=16.0, + weight=2.0, ) - deformable_goal_tracking_fine_grained = RewTerm( - func=mdp.deformable_com_goal_distance, + + success_bonus = RewTerm( + func=mdp.deformable_com_goal_reached, params={ - "std": 0.05, - "minimal_height": 0.075, + "minimal_height": 0.0, "command_name": "deformable_pose", + "success_threshold": 0.05, "asset_cfg": SceneEntityCfg("deformable"), }, - weight=5.0, + weight=10.0, ) - action_rate = RewTerm(func=mdp.action_rate_l2, weight=-1e-2) - gripper_close = RewTerm( - func=mdp.gripper_close_action, - params={"action_name": "gripper_action"}, - weight=-1.0, + action_rate = RewTerm(func=mdp.action_rate_l2, weight=-1e-4) + + +@configclass +class CurriculumCfg: + """Ramp the action-rate penalty once the policy has learned to lift (matches rigid recipe).""" + + action_rate = CurrTerm( + func=mdp.modify_reward_weight, params={"term_name": "action_rate", "weight": -1e-2, "num_steps": 40000} + ) + + # Since we use 24 steps per env, 10000 steps correspond to 10000/24 = 416.67 learning iterations + gravity = CurrTerm( + func=mdp.modify_gravity_linear, + params={"start_gravity_z": -0.0001, "end_gravity_z": -9.81, "start_step": 0, "end_step": 10000}, ) - joint_vel = RewTerm(func=mdp.joint_vel_l2, weight=-1e-2) - joint_torque = RewTerm(func=mdp.joint_torques_l2, weight=-1e-4) - joint_acc = RewTerm(func=mdp.joint_acc_l2, weight=-1e-4) @configclass @@ -472,6 +568,25 @@ class TerminationsCfg: params={"minimum_height": 0.0, "ee_frame_cfg": SceneEntityCfg("ee_frame")}, ) + joint_vel_out_of_limit = DoneTerm( + func=mdp.joint_vel_out_of_sim_limit, + params={"asset_cfg": SceneEntityCfg("robot")}, + ) + + # real failure, not a time out: a diverged solve must bootstrap as a termination + deformable_invalid = DoneTerm( + func=mdp.deformable_state_invalid, + params={"asset_cfg": SceneEntityCfg("deformable")}, + ) + + # The measured divergence poisons the beam too, so deformable_invalid resets that case. This + # covers a robot-only divergence: every reward term is deformable-driven and sanitized, and the + # other robot terminations fail open on NaN, so nothing else would ever reset the environment. + robot_invalid = DoneTerm( + func=mdp.robot_state_invalid, + params={"asset_cfg": SceneEntityCfg("robot")}, + ) + ## # Environment configuration @@ -481,11 +596,11 @@ class TerminationsCfg: @configclass class FrankaSoftSceneCfg(PresetCfg): newton_mjwarp_vbd_proxy: _FrankaSoftSceneCfg = _FrankaSoftSceneCfg( - num_envs=128, env_spacing=2.5, replicate_physics=True + num_envs=2048, env_spacing=2.0, replicate_physics=True ) # PhysX does not support replicating physics for deformable objects - physx: _FrankaSoftSceneCfg = _FrankaSoftSceneCfg(num_envs=128, env_spacing=2.5, replicate_physics=False) + physx: _FrankaSoftSceneCfg = _FrankaSoftSceneCfg(num_envs=2048, env_spacing=2.0, replicate_physics=False) isaacsim_physx = physx ovphysx: _FrankaSoftSceneCfg = _FrankaSoftSceneCfg(num_envs=128, env_spacing=2.5, replicate_physics=True) @@ -498,16 +613,16 @@ class FrankaSoftCameraSceneCfg(PresetCfg): """Scene presets for visual Franka soft lifting.""" newton_mjwarp_vbd_proxy: _FrankaSoftCameraSceneCfg = _FrankaSoftCameraSceneCfg( - num_envs=128, env_spacing=2.5, replicate_physics=True + num_envs=128, env_spacing=2.0, replicate_physics=True ) - physx: _FrankaSoftCameraSceneCfg = _FrankaSoftCameraSceneCfg(num_envs=128, env_spacing=2.5, replicate_physics=False) + physx: _FrankaSoftCameraSceneCfg = _FrankaSoftCameraSceneCfg(num_envs=128, env_spacing=2.0, replicate_physics=False) isaacsim_physx = physx default = newton_mjwarp_vbd_proxy @configclass class FrankaSoftEnvCfg(ManagerBasedRLEnvCfg): - """Manager-based RL environment: Franka Panda lifting a volume deformable.""" + """Manager-based RL environment: Franka Panda lifting a soft beam to a target pose.""" # Scene settings scene: FrankaSoftSceneCfg = FrankaSoftSceneCfg() @@ -518,19 +633,29 @@ class FrankaSoftEnvCfg(ManagerBasedRLEnvCfg): # MDP settings rewards: RewardsCfg = RewardsCfg() terminations: TerminationsCfg = TerminationsCfg() + # Parent reset events + per-env material domain randomization. events: EventCfg = EventCfg() + # Ramp the action-rate penalty once the policy has learned to lift. + curriculum: CurriculumCfg = CurriculumCfg() def __post_init__(self) -> None: # general settings - self.decimation = 1 + self.decimation = 4 self.episode_length_s = 5.0 # simulation settings - self.sim.dt = 1 / 60.0 + self.sim.dt = 1.0 / 120 self.sim.render_interval = self.decimation - self.sim.gravity = (0.0, 0.0, 0.0) + self.sim.gravity = (0.0, 0.0, -9.81) self.sim.physics = PhysicsCfg() + self.viewer.eye = (0.75, 0.25, 0.65) + self.viewer.lookat = (0.0, 0.75, 0.4) + self.sim.default_visualizer_cfg = VisualizerCfg(eye=self.viewer.eye, lookat=self.viewer.lookat) + + self.video_recorder.window_width = 1920 + self.video_recorder.window_height = 1080 + @configclass class FrankaSoftCameraEnvCfg(FrankaSoftEnvCfg): diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/__init__.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/__init__.py new file mode 100644 index 000000000000..188dd9bacd92 --- /dev/null +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/__init__.py @@ -0,0 +1,10 @@ +# 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 + +"""This sub-module contains the functions that are specific to the deformable lift environments.""" + +from isaaclab.utils.module import lazy_export + +lazy_export() diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/__init__.pyi b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/__init__.pyi new file mode 100644 index 000000000000..ac119215daa7 --- /dev/null +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/__init__.pyi @@ -0,0 +1,64 @@ +# 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 + +__all__ = [ + # observations + "deformable_com_in_robot_root_frame", + "DeformableSampledPointsInRobotRootFrame", + # rewards + "deformable_lifted", + "deformable_lifting", + "deformable_ee_distance", + "deformable_com_ee_distance", + "deformable_fingertip_distance", + "deformable_com_goal_distance", + "deformable_com_goal_distance_delta", + "deformable_com_goal_reached", + # terminations + "deformable_com_below_minimum", + "deformable_nodal_vel_above_maximum", + "deformable_outside_table_bounds", + "deformable_state_invalid", + "joint_vel_out_of_sim_limit", + "robot_state_invalid", + # events + "randomize_deformable_material", + "reset_deformable_over_support", + # commands + "DeformableUniformPoseCommand", + "DeformableUniformPoseCommandCfg", + # curriculums + "modify_gravity_linear", +] + +from .curriculums import modify_gravity_linear +from .events import randomize_deformable_material, reset_deformable_over_support +from .observations import ( + DeformableSampledPointsInRobotRootFrame, + deformable_com_in_robot_root_frame, +) +from .pose_commands import ( + DeformableUniformPoseCommand, + DeformableUniformPoseCommandCfg, +) +from .rewards import ( + deformable_com_ee_distance, + deformable_com_goal_distance, + deformable_com_goal_distance_delta, + deformable_com_goal_reached, + deformable_ee_distance, + deformable_fingertip_distance, + deformable_lifted, + deformable_lifting, +) +from .terminations import ( + deformable_com_below_minimum, + deformable_nodal_vel_above_maximum, + deformable_outside_table_bounds, + deformable_state_invalid, + joint_vel_out_of_sim_limit, + robot_state_invalid, +) +from isaaclab_tasks.core.lift.mdp import * diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/curriculums.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/curriculums.py new file mode 100644 index 000000000000..6c89fb35da09 --- /dev/null +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/curriculums.py @@ -0,0 +1,75 @@ +# 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 + +"""Curriculum functions for the deformable lift tasks.""" + +from __future__ import annotations + +from collections.abc import Sequence +from typing import TYPE_CHECKING + +import isaaclab.sim as sim_utils +from isaaclab.managers import CurriculumTermCfg, ManagerTermBase + +if TYPE_CHECKING: + from isaaclab.envs import ManagerBasedRLEnv + + +class modify_gravity_linear(ManagerTermBase): + """Curriculum that linearly ramps the vertical gravity toward its target value. + + The vertical gravity component is interpolated from :paramref:`start_gravity_z` to + :paramref:`end_gravity_z` [m/s^2] as the global step counter advances from + :paramref:`start_step` to :paramref:`end_step`, then held constant. This lets the policy + first learn under near-weightless dynamics before full gravity is applied. + + The active physics backend is detected automatically (PhysX or Newton). + """ + + def __init__(self, cfg: CurriculumTermCfg, env: ManagerBasedRLEnv): + super().__init__(cfg, env) + + manager_name = env.sim.physics_manager.__name__.lower() + if "newton" in manager_name: + self._backend = "newton" + import isaaclab_newton.physics.newton_manager as newton_manager_module # noqa: PLC0415 + from newton import ModelFlags # noqa: PLC0415 + + self._newton_manager = newton_manager_module.NewtonManager + self._notify_model_properties = ModelFlags.MODEL_PROPERTIES + else: + self._backend = "physx" + import carb # noqa: PLC0415 + + self._carb = carb + self._physics_sim_view = sim_utils.SimulationContext.instance().physics_sim_view + + def __call__( + self, + env: ManagerBasedRLEnv, + env_ids: Sequence[int], + start_gravity_z: float, + end_gravity_z: float, + start_step: int, + end_step: int, + ) -> float: + # linearly interpolate the vertical gravity based on training progress + alpha = (env.common_step_counter - start_step) / max(end_step - start_step, 1) + alpha = min(max(alpha, 0.0), 1.0) + gravity_z = start_gravity_z + alpha * (end_gravity_z - start_gravity_z) + + if self._backend == "newton": + import warp as wp # noqa: PLC0415 + + model = self._newton_manager.get_model() + if model is None or model.gravity is None: + raise RuntimeError("Newton model is not initialized. Cannot modify gravity.") + # write to all worlds so gravity stays consistent regardless of per-env reset timing + wp.to_torch(model.gravity)[:, 2] = gravity_z + self._newton_manager.add_model_change(self._notify_model_properties) + else: + self._physics_sim_view.set_gravity(self._carb.Float3(0.0, 0.0, gravity_z)) + + return gravity_z diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/events.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/events.py new file mode 100644 index 000000000000..89eb1d4cd3f3 --- /dev/null +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/events.py @@ -0,0 +1,181 @@ +# 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 + +"""Events for the deformable lift environments.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +import torch +import warp as wp + +from isaaclab.managers import SceneEntityCfg +from isaaclab.utils import math as math_utils + +if TYPE_CHECKING: + from isaaclab.assets import DeformableObject, RigidObject + from isaaclab.envs import ManagerBasedEnv + + +@wp.kernel +def _write_tet_materials( + tet_indices: wp.array2d(dtype=wp.int32), + particle_offset0: wp.int32, + particles_per_body: wp.int32, + k_mu: wp.array(dtype=wp.float32), + k_lambda: wp.array(dtype=wp.float32), + tet_materials: wp.array2d(dtype=wp.float32), +): + """Write per-env Lame parameters into the shared tet-material array (leaves k_damp untouched).""" + t = wp.tid() + # Map the tet to its env via its first particle index (contiguous under replicate_physics). + e = (tet_indices[t, 0] - particle_offset0) // particles_per_body + tet_materials[t, 0] = k_mu[e] + tet_materials[t, 1] = k_lambda[e] + + +@wp.kernel +def _scale_particle_mass( + offsets: wp.array(dtype=wp.int32), + density_scale: wp.array(dtype=wp.float32), + spawn_mass: wp.array(dtype=wp.float32), + particle_mass: wp.array(dtype=wp.float32), + particle_inv_mass: wp.array(dtype=wp.float32), +): + """Scale free particle masses by the per-env density ratio; skip kinematic particles.""" + e, j = wp.tid() + flat_idx = offsets[e] + j + if particle_inv_mass[flat_idx] == 0.0: + return + m = spawn_mass[flat_idx] * density_scale[e] + particle_mass[flat_idx] = m + particle_inv_mass[flat_idx] = 1.0 / m + + +def randomize_deformable_material( + env: ManagerBasedEnv, + env_ids: torch.Tensor | None, + asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), + youngs_modulus_range: tuple[float, float] = (7e4, 5e5), + density_range: tuple[float, float] = (100.0, 1000.0), + poissons_ratio: float = 0.25, +) -> None: + """Randomize the deformable object's material stiffness and density per environment. + + Startup event that samples a Young's modulus and density independently for every + environment instance and writes the corresponding Lame parameters into the Newton + tetrahedral materials and the particle masses. The tetrahedral damping term + ``k_damp`` is preserved. Kinematic particles keep their infinite mass. + + Args: + env: The environment instance. + env_ids: Unused; all instances are randomized at startup. + asset_cfg: Scene entity of the deformable object to randomize. + youngs_modulus_range: Sampling bounds for the Young's modulus [Pa]. + density_range: Sampling bounds for the mass density [kg/m^3]. + poissons_ratio: Poisson's ratio [dimensionless] used to convert to Lame parameters. + """ + # Imported here, not at module scope: pulling ``newton`` imports ``pxr`` and a second USD + # runtime, which must not happen before the Kit app has started. + from isaaclab_newton.physics import NewtonManager + from newton import ModelFlags + + asset = env.scene[asset_cfg.name] + device = env.device + num_instances = asset.num_instances + + model = NewtonManager.get_model() + if model is None: + return + + nu = poissons_ratio + + # Sample per-env Young's modulus and density, then convert E to Lame parameters. + youngs = torch.empty(num_instances, device=device).uniform_(*youngs_modulus_range) + density = torch.empty(num_instances, device=device).uniform_(*density_range) + k_mu = youngs / (2.0 * (1.0 + nu)) + k_lambda = youngs * nu / ((1.0 + nu) * (1.0 - 2.0 * nu)) + + k_mu_wp = wp.from_torch(k_mu.contiguous(), dtype=wp.float32) + k_lambda_wp = wp.from_torch(k_lambda.contiguous(), dtype=wp.float32) + + particle_offset0 = int(asset._recorded_particle_offsets[0]) + particles_per_body = asset._particles_per_body + + wp.launch( + _write_tet_materials, + dim=(model.tet_materials.shape[0],), + inputs=[model.tet_indices, particle_offset0, particles_per_body, k_mu_wp, k_lambda_wp], + outputs=[model.tet_materials], + device=device, + ) + + # Scale masses by density relative to the spawn baseline (spawn mass already encodes it). + spawn_density = asset.cfg.spawn.physics_material.density + density_scale = (density / spawn_density).contiguous() + density_scale_wp = wp.from_torch(density_scale, dtype=wp.float32) + spawn_mass = wp.clone(model.particle_mass) + + wp.launch( + _scale_particle_mass, + dim=(num_instances, particles_per_body), + inputs=[asset._particle_offsets, density_scale_wp, spawn_mass], + outputs=[model.particle_mass, model.particle_inv_mass], + device=device, + ) + + # Refresh the asset's cached inverse-mass snapshot used by the kinematic-target restore. + asset._default_particle_inv_mass = wp.clone(model.particle_inv_mass) + + # notify the solver that model properties changed, else the randomization is ignored + NewtonManager.add_model_change(ModelFlags.MODEL_PROPERTIES) + + +def reset_deformable_over_support( + env: ManagerBasedEnv, + env_ids: torch.Tensor, + position_range: dict[str, tuple[float, float]], + support_offset_range: dict[str, tuple[float, float]], + asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), + support_cfg: SceneEntityCfg = SceneEntityCfg("cube"), +) -> None: + """Reset a deformable object and keep a support body underneath it. + + The deformable is displaced from its default nodal state by a sample from + :paramref:`position_range`. The support receives the same planar displacement plus an + independent sample from :paramref:`support_offset_range`, so it stays under the deformable + while still varying between resets. + + Args: + env: The environment instance. + env_ids: The environment indices to reset. + position_range: Deformable displacement bounds [m] keyed by ``x``, ``y``, ``z``. + support_offset_range: Support jitter bounds [m] keyed by ``x``, ``y``, applied on top of + the deformable's displacement. + asset_cfg: Scene entity of the deformable object to reset. + support_cfg: Scene entity of the rigid support body to keep underneath. + """ + deformable: DeformableObject = env.scene[asset_cfg.name] + support: RigidObject = env.scene[support_cfg.name] + + # shared planar displacement, so the support tracks the deformable + ranges = torch.tensor([position_range.get(key, (0.0, 0.0)) for key in ("x", "y", "z")], device=deformable.device) + offset = math_utils.sample_uniform(ranges[:, 0], ranges[:, 1], (len(env_ids), 3), device=deformable.device) + + nodal_state = deformable.data.default_nodal_state_w.torch[env_ids].clone() + nodal_state[..., :3] += offset.unsqueeze(1) + deformable.write_nodal_state_to_sim(nodal_state, env_ids=env_ids) + + ranges = torch.tensor([support_offset_range.get(key, (0.0, 0.0)) for key in ("x", "y")], device=support.device) + jitter = math_utils.sample_uniform(ranges[:, 0], ranges[:, 1], (len(env_ids), 2), device=support.device) + + root_pose = support.data.default_root_pose.torch[env_ids].clone() + root_pose[:, :3] += env.scene.env_origins[env_ids] + root_pose[:, :2] += offset[:, :2] + jitter + support.write_root_pose_to_sim_index(root_pose=root_pose, env_ids=env_ids) + support.write_root_velocity_to_sim_index( + root_velocity=torch.zeros_like(support.data.default_root_vel.torch[env_ids]), env_ids=env_ids + ) diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/observations.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/observations.py new file mode 100644 index 000000000000..c38ba710f7d2 --- /dev/null +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/observations.py @@ -0,0 +1,117 @@ +# 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 + +"""Observation functions for the deformable lift tasks.""" + +from __future__ import annotations + +from collections.abc import Sequence +from typing import TYPE_CHECKING + +import torch + +from isaaclab.managers import ManagerTermBase, SceneEntityCfg +from isaaclab.utils.math import subtract_frame_transforms + +from .utils import _com_w, _nodal_pos_w + +if TYPE_CHECKING: + from isaaclab.assets import Articulation, DeformableObject + from isaaclab.envs import ManagerBasedRLEnv + from isaaclab.managers import ObservationTermCfg + + +def deformable_com_in_robot_root_frame( + env: ManagerBasedRLEnv, + asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), + robot_cfg: SceneEntityCfg = SceneEntityCfg("robot"), +) -> torch.Tensor: + """Position of the deformable object's COM in the robot's root frame [m]. + + The COM is the mean of the deformable's nodal positions (see + :attr:`~isaaclab.assets.DeformableObject.data.root_pos_w`). + + Returns: + Tensor of shape ``(num_envs, 3)``. + """ + asset: DeformableObject = env.scene[asset_cfg.name] + robot: Articulation = env.scene[robot_cfg.name] + com_w = _com_w(asset) + com_b, _ = subtract_frame_transforms(robot.data.root_pos_w.torch, robot.data.root_quat_w.torch, com_w) + return com_b + + +class DeformableSampledPointsInRobotRootFrame(ManagerTermBase): + """Sampled deformable nodal points expressed in the robot's root frame. + + The point indices are sampled on reset, then reused within the episode so + each observed point follows the same material node over time. + """ + + def __init__(self, cfg: ObservationTermCfg, env: ManagerBasedRLEnv): + super().__init__(cfg, env) + + self.asset_cfg: SceneEntityCfg = cfg.params.get("asset_cfg", SceneEntityCfg("deformable")) + self.robot_cfg: SceneEntityCfg = cfg.params.get("robot_cfg", SceneEntityCfg("robot")) + self.num_points: int = cfg.params.get("num_points", 20) + + asset: DeformableObject = env.scene[self.asset_cfg.name] + self.num_nodes = asset.data.nodal_pos_w.shape[1] + self.node_ids = torch.empty(env.num_envs, self.num_points, dtype=torch.long, device=env.device) + self.reset() + + def reset(self, env_ids: Sequence[int] | None = None) -> None: + """Resample observed deformable nodes for the selected environments.""" + if env_ids is None: + env_ids = slice(None) + num_envs = self.num_envs + else: + num_envs = len(env_ids) + + if self.num_points <= self.num_nodes: + self.node_ids[env_ids] = ( + torch.rand((num_envs, self.num_nodes), device=self.device).topk(self.num_points, dim=1).indices + ) + else: + self.node_ids[env_ids] = torch.randint(self.num_nodes, (num_envs, self.num_points), device=self.device) + + def __call__( + self, + env: ManagerBasedRLEnv, + asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), + robot_cfg: SceneEntityCfg = SceneEntityCfg("robot"), + num_points: int = 20, + ) -> torch.Tensor: + """Sample deformable nodal positions in the robot's root frame. + + Args: + env: The environment instance. + asset_cfg: The deformable object entity. + robot_cfg: The robot entity providing the reference frame. + num_points: Number of sampled points. + + Returns: + Flattened tensor of shape ``(num_envs, 3 * num_points)`` with sampled + point positions [m] in the robot root frame. + """ + asset: DeformableObject = env.scene[asset_cfg.name] + robot: Articulation = env.scene[robot_cfg.name] + if num_points != self.num_points: + raise ValueError( + f"Requested {num_points} deformable points, but this term was initialized with {self.num_points}." + ) + + nodal_pos_w = _nodal_pos_w(asset) + sampled_points_w = nodal_pos_w.gather(1, self.node_ids.unsqueeze(-1).expand(-1, -1, 3)) + + flat_sampled_points_w = sampled_points_w.reshape(-1, 3) + root_pos_w = robot.data.root_pos_w.torch.unsqueeze(1).expand(-1, num_points, -1) + root_quat_w = robot.data.root_quat_w.torch.unsqueeze(1).expand(-1, num_points, -1) + sampled_points_b, _ = subtract_frame_transforms( + root_pos_w.reshape(-1, 3), + root_quat_w.reshape(-1, 4), + flat_sampled_points_w, + ) + return sampled_points_b.view(env.num_envs, -1) diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/pose_commands.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/pose_commands.py new file mode 100644 index 000000000000..2cda5216a49c --- /dev/null +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/pose_commands.py @@ -0,0 +1,82 @@ +# 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 + +"""Pose command terms for the deformable lift tasks.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +import torch + +from isaaclab.assets import AssetBaseCfg +from isaaclab.utils.configclass import configclass +from isaaclab.utils.math import combine_frame_transforms + +from isaaclab_tasks.core.dexsuite.mdp.commands.pose_commands import ObjectUniformPoseCommand +from isaaclab_tasks.core.dexsuite.mdp.commands.pose_commands_cfg import ObjectUniformPoseCommandCfg + +if TYPE_CHECKING: + from isaaclab.assets import DeformableObject + from isaaclab.envs import ManagerBasedEnv + + +class DeformableUniformPoseCommand(ObjectUniformPoseCommand): + """Uniform position command for a deformable object, tracked by its center of mass. + + Deformable objects expose no root orientation, so the target is tracked with the COM + (:attr:`~isaaclab.assets.DeformableObject.data.root_pos_w`) and only ``position_only`` + commands are supported. + + The success visualizer asset may be a static asset (``AssetBaseCfg``), which has no + runtime view. In that case its world position is the fixed spawn offset from the + environment origins. + """ + + cfg: DeformableUniformPoseCommandCfg + """Configuration for the command generator.""" + + object: DeformableObject + """The deformable object tracked by the command.""" + + def __init__(self, cfg: DeformableUniformPoseCommandCfg, env: ManagerBasedEnv): + if not cfg.position_only: + raise ValueError("DeformableUniformPoseCommand only supports position_only commands.") + super().__init__(cfg, env) + + # static assets are stored as their config, so their world position is constant + if isinstance(self.success_vis_asset, AssetBaseCfg): + offset = torch.tensor(self.success_vis_asset.init_state.pos, device=self.device) + self._static_success_vis_pos_w = env.scene.env_origins + offset + else: + self._static_success_vis_pos_w = None + + def _update_metrics(self): + # transform command from base frame to simulation world frame + self.pose_command_w[:, :3], self.pose_command_w[:, 3:] = combine_frame_transforms( + self.robot.data.root_pos_w.torch, + self.robot.data.root_quat_w.torch, + self.pose_command_b[:, :3], + self.pose_command_b[:, 3:], + ) + com_w = self.object.data.root_pos_w.torch + self.metrics["position_error"] = torch.linalg.norm(self.pose_command_w[:, :3] - com_w, dim=-1) + + if self.success_vis_asset is None: + return + # same success radius as the goal markers of the base class + success_id = (self.metrics["position_error"] < 0.05).int() + if self._static_success_vis_pos_w is not None: + vis_pos_w = self._static_success_vis_pos_w + else: + vis_pos_w = self.success_vis_asset.data.root_pos_w.torch + self.success_visualizer.visualize(vis_pos_w, marker_indices=success_id) + + +@configclass +class DeformableUniformPoseCommandCfg(ObjectUniformPoseCommandCfg): + """Configuration for the deformable uniform pose command generator.""" + + class_type: type[DeformableUniformPoseCommand] | str = "{DIR}.pose_commands:DeformableUniformPoseCommand" diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/rewards.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/rewards.py new file mode 100644 index 000000000000..1a1e9a4df875 --- /dev/null +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/rewards.py @@ -0,0 +1,304 @@ +# 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 + +"""Reward functions for the deformable lift tasks.""" + +from __future__ import annotations + +from collections.abc import Sequence +from typing import TYPE_CHECKING + +import torch + +from isaaclab.managers import ManagerTermBase, RewardTermCfg, SceneEntityCfg +from isaaclab.utils.math import combine_frame_transforms + +from .utils import _body_pos_w, _com_w, _ee_pos_w, _nodal_pos_w + +if TYPE_CHECKING: + from isaaclab.assets import Articulation, DeformableObject + from isaaclab.envs import ManagerBasedRLEnv + from isaaclab.sensors import FrameTransformer + + +def deformable_lifted( + env: ManagerBasedRLEnv, + minimal_height: float, + asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), +) -> torch.Tensor: + """Reward if the deformable COM is above a minimum height. + + Args: + env: The environment instance. + minimal_height: Minimum COM height [m]. + asset_cfg: The deformable object entity. + + Returns: + Reward tensor with shape ``(num_envs,)``. + """ + asset: DeformableObject = env.scene[asset_cfg.name] + com_z = _com_w(asset)[:, 2] + return torch.where(com_z > minimal_height, 1.0, 0.0) + + +def deformable_lifting( + env: ManagerBasedRLEnv, + std: float, + minimal_height: float, + asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), +) -> torch.Tensor: + """Reward raising the deformable COM above ``minimal_height`` [m] using a tanh kernel with scale ``std`` [m]. + + Dense analogue of :func:`deformable_lifted`: ungated and continuous, so it supplies a smooth + upward gradient rather than a binary step. Returns ``0`` at or below ``minimal_height`` and + saturates toward ``1``. + + Args: + env: The environment instance. + std: The tanh kernel standard deviation [m]. + minimal_height: Minimum COM height [m]. + asset_cfg: The deformable object entity. + + Returns: + Reward tensor with shape ``(num_envs,)``. + """ + asset: DeformableObject = env.scene[asset_cfg.name] + com_z = _com_w(asset)[:, 2] + height = (com_z - minimal_height).clamp(min=0.0) + return torch.tanh(height / std) + + +def deformable_ee_distance( + env: ManagerBasedRLEnv, + std: float, + asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), + ee_frame_cfg: SceneEntityCfg = SceneEntityCfg("ee_frame"), +) -> torch.Tensor: + """Reward reaching the deformable's nearest nodal point with the end-effector. + + Args: + env: The environment instance. + std: The tanh kernel standard deviation [m]. + asset_cfg: The deformable object entity. + ee_frame_cfg: The end-effector frame entity. + + Returns: + Reward tensor with shape ``(num_envs,)``. + """ + asset: DeformableObject = env.scene[asset_cfg.name] + ee_frame: FrameTransformer = env.scene[ee_frame_cfg.name] + nodal_pos_w = _nodal_pos_w(asset) + ee_w = _ee_pos_w(ee_frame) + distance = torch.linalg.norm(nodal_pos_w - ee_w.unsqueeze(1), dim=2).min(dim=1).values + return 1.0 - torch.tanh(distance / std) + + +def deformable_com_ee_distance( + env: ManagerBasedRLEnv, + std: float, + asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), + ee_frame_cfg: SceneEntityCfg = SceneEntityCfg("ee_frame"), +) -> torch.Tensor: + """Reward reaching the deformable's center of mass with the end-effector using a tanh kernel. + + Uses the COM (:attr:`~isaaclab.assets.DeformableObject.data.root_pos_w`) rather than the nearest + node, so the gripper is drawn to the object's middle. For an elongated body (e.g. a beam) this + steers the grasp toward the center instead of an end, keeping the object balanced when lifted. + + Args: + env: The environment instance. + std: The tanh kernel standard deviation [m]. + asset_cfg: The deformable object entity. + ee_frame_cfg: The end-effector frame entity. + + Returns: + Reward tensor with shape ``(num_envs,)``. + """ + asset: DeformableObject = env.scene[asset_cfg.name] + ee_frame: FrameTransformer = env.scene[ee_frame_cfg.name] + com_w = _com_w(asset) + ee_w = _ee_pos_w(ee_frame) + distance = torch.linalg.norm(com_w - ee_w, dim=1) + return 1.0 - torch.tanh(distance / std) + + +def deformable_fingertip_distance( + env: ManagerBasedRLEnv, + std: float, + asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), + robot_cfg: SceneEntityCfg = SceneEntityCfg("robot"), + target_com: bool = False, +) -> torch.Tensor: + """Reward closing the gripper around the deformable using a tanh kernel with scale ``std`` [m]. + + Each selected finger body is rewarded for approaching the nearest deformable node + (:attr:`~isaaclab.assets.DeformableObject.data.nodal_pos_w`), + so grasping any part of the soft body is credited rather than only its center. Supplies the grasp + gradient that the EE-reach reward lacks. When ``target_com`` is set, each finger is instead drawn + to the object's center of mass, biasing the grasp to the middle of an elongated body (e.g. a beam). + + Args: + env: The environment instance. + std: The tanh kernel standard deviation [m]. + asset_cfg: The deformable object entity. + robot_cfg: The robot entity with ``body_ids`` selecting the finger bodies. + target_com: If ``True``, target the COM instead of the nearest node. + + Returns: + Reward tensor with shape ``(num_envs,)``. + """ + asset: DeformableObject = env.scene[asset_cfg.name] + robot: Articulation = env.scene[robot_cfg.name] + # target points in world frame: COM (num_envs, 1, 3) or all nodes (num_envs, num_nodes, 3) + if target_com: + target_w = _com_w(asset).unsqueeze(1) + else: + target_w = _nodal_pos_w(asset) + # selected finger bodies in world frame: (num_envs, num_fingers, 3) + finger_pos_w = _body_pos_w(robot, robot_cfg.body_ids) + # nearest target to each finger: (num_envs, num_fingers) + distance = torch.linalg.norm(finger_pos_w.unsqueeze(2) - target_w.unsqueeze(1), dim=3) + nearest = distance.min(dim=2).values + return (1.0 - torch.tanh(nearest / std)).mean(dim=1) + + +class deformable_com_goal_distance(ManagerTermBase): + """Reward tracking of the goal position by the deformable's COM (tanh kernel). + + Only credits when the COM is above ``minimal_height`` [m] (i.e. the object is lifted). + The command is interpreted as ``[x, y, z, qw, qx, qy, qz]`` in the robot's root frame. + + If ``success_threshold`` is provided in the term params, this also tracks per-episode + success (sticky binary: COM ever within ``success_threshold`` [m] of the commanded goal + while lifted above ``minimal_height``) and logs the mean across environments under + ``Metrics/success_rate`` on reset. + """ + + def __init__(self, cfg: RewardTermCfg, env: ManagerBasedRLEnv): + super().__init__(cfg, env) + self._track_success = cfg.params.get("success_threshold") is not None + if self._track_success: + self._succeeded = torch.zeros(env.num_envs, dtype=torch.bool, device=env.device) + + def reset(self, env_ids: Sequence[int] | None = None): + if env_ids is None: + env_ids = slice(None) + if self._track_success: + self._env.extras.setdefault("log", {})["Metrics/success_rate"] = ( + self._succeeded[env_ids].float().mean().item() + ) + self._succeeded[env_ids] = False + + def __call__( + self, + env: ManagerBasedRLEnv, + std: float, + minimal_height: float, + command_name: str, + robot_cfg: SceneEntityCfg = SceneEntityCfg("robot"), + asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), + success_threshold: float | None = None, + ) -> torch.Tensor: + robot: Articulation = env.scene[robot_cfg.name] + asset: DeformableObject = env.scene[asset_cfg.name] + command = env.command_manager.get_command(command_name) + des_pos_w, _ = combine_frame_transforms( + robot.data.root_pos_w.torch, robot.data.root_quat_w.torch, command[:, :3] + ) + com_w = _com_w(asset) + distance = torch.linalg.norm(des_pos_w - com_w, dim=1) + is_lifted = com_w[:, 2] > minimal_height + if success_threshold is not None: + self._succeeded |= is_lifted & (distance < success_threshold) + return is_lifted.float() * (1.0 - torch.tanh(distance / std)) + + +class deformable_com_goal_distance_delta(ManagerTermBase): + """Reward progress of the deformable COM toward the commanded goal. + + Returns the per-step decrease in the COM-to-goal distance (previous minus current), + gated so it only credits while the COM is lifted above ``minimal_height`` [m]. The stored + distance is re-baselined on the first step after reset so the reset teleport does not + produce a spurious reward. Success tracking matches :class:`deformable_com_goal_distance`. + """ + + def __init__(self, cfg: RewardTermCfg, env: ManagerBasedRLEnv): + super().__init__(cfg, env) + self._prev_distance = torch.zeros(env.num_envs, device=env.device) + self._needs_baseline = torch.ones(env.num_envs, dtype=torch.bool, device=env.device) + self._track_success = cfg.params.get("success_threshold") is not None + if self._track_success: + self._succeeded = torch.zeros(env.num_envs, dtype=torch.bool, device=env.device) + + def reset(self, env_ids: Sequence[int] | None = None): + if env_ids is None: + env_ids = slice(None) + self._needs_baseline[env_ids] = True + if self._track_success: + self._env.extras.setdefault("log", {})["Metrics/success_rate"] = ( + self._succeeded[env_ids].float().mean().item() + ) + self._succeeded[env_ids] = False + + def __call__( + self, + env: ManagerBasedRLEnv, + minimal_height: float, + command_name: str, + robot_cfg: SceneEntityCfg = SceneEntityCfg("robot"), + asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), + success_threshold: float | None = None, + ) -> torch.Tensor: + robot: Articulation = env.scene[robot_cfg.name] + asset: DeformableObject = env.scene[asset_cfg.name] + command = env.command_manager.get_command(command_name) + des_pos_w, _ = combine_frame_transforms( + robot.data.root_pos_w.torch, robot.data.root_quat_w.torch, command[:, :3] + ) + com_w = _com_w(asset) + distance = torch.linalg.norm(des_pos_w - com_w, dim=1) + self._prev_distance = torch.where(self._needs_baseline, distance, self._prev_distance) + self._needs_baseline[:] = False + is_lifted = com_w[:, 2] > minimal_height + if success_threshold is not None: + self._succeeded |= is_lifted & (distance < success_threshold) + delta = self._prev_distance - distance + self._prev_distance = distance + return is_lifted.float() * delta + + +def deformable_com_goal_reached( + env: ManagerBasedRLEnv, + minimal_height: float, + command_name: str, + success_threshold: float, + robot_cfg: SceneEntityCfg = SceneEntityCfg("robot"), + asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), +) -> torch.Tensor: + """Per-step success bonus for holding the deformable COM at the goal. + + Returns ``1.0`` while the COM is within ``success_threshold`` [m] of the commanded goal + position and lifted above ``minimal_height`` [m], else ``0.0``. Matches the condition + tracked as ``Metrics/success_rate`` in :class:`deformable_com_goal_distance`. + + Args: + env: The environment instance. + minimal_height: Minimum COM height for the bonus to apply [m]. + command_name: Name of the goal-pose command term. + success_threshold: Maximum COM-to-goal distance counted as success [m]. + robot_cfg: The robot entity providing the goal reference frame. + asset_cfg: The deformable object entity. + + Returns: + Reward tensor with shape ``(num_envs,)`` valued in ``{0, 1}``. + """ + robot: Articulation = env.scene[robot_cfg.name] + asset: DeformableObject = env.scene[asset_cfg.name] + command = env.command_manager.get_command(command_name) + des_pos_w, _ = combine_frame_transforms(robot.data.root_pos_w.torch, robot.data.root_quat_w.torch, command[:, :3]) + com_w = _com_w(asset) + distance = torch.linalg.norm(des_pos_w - com_w, dim=1) + is_lifted = com_w[:, 2] > minimal_height + return (is_lifted & (distance < success_threshold)).float() diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/terminations.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/terminations.py new file mode 100644 index 000000000000..65eb246e865c --- /dev/null +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/terminations.py @@ -0,0 +1,158 @@ +# 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 + +"""Termination functions for the deformable lift tasks. + +The functions can be passed to the :class:`isaaclab.managers.TerminationTermCfg` object to enable +the termination introduced by the function. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +import torch + +from isaaclab.managers import SceneEntityCfg + +if TYPE_CHECKING: + from isaaclab.assets import Articulation, DeformableObject + from isaaclab.envs import ManagerBasedRLEnv + + +def deformable_com_below_minimum( + env: ManagerBasedRLEnv, + minimum_height: float, + asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), +) -> torch.Tensor: + """Termination signal when the deformable's COM falls below ``minimum_height`` [m].""" + asset: DeformableObject = env.scene[asset_cfg.name] + com_z = asset.data.root_pos_w.torch[:, 2] + return com_z < minimum_height + + +def deformable_outside_table_bounds( + env: ManagerBasedRLEnv, + x_bounds: tuple[float, float], + y_bounds: tuple[float, float], + asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), +) -> torch.Tensor: + """Terminate if any deformable nodal point leaves the table footprint. + + Args: + env: The environment instance. + x_bounds: Allowed x-position range in the environment frame [m]. + y_bounds: Allowed y-position range in the environment frame [m]. + asset_cfg: The deformable object entity. + + Returns: + Boolean tensor with shape ``(num_envs,)``. + """ + asset: DeformableObject = env.scene[asset_cfg.name] + nodal_pos = asset.data.nodal_pos_w.torch - env.scene.env_origins.unsqueeze(1) + outside_x = (nodal_pos[..., 0] < x_bounds[0]) | (nodal_pos[..., 0] > x_bounds[1]) + outside_y = (nodal_pos[..., 1] < y_bounds[0]) | (nodal_pos[..., 1] > y_bounds[1]) + return torch.any(outside_x | outside_y, dim=1) + + +def deformable_nodal_vel_above_maximum( + env: ManagerBasedRLEnv, + maximum_velocity: float, + asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), +) -> torch.Tensor: + """Terminate when any deformable node moves faster than ``maximum_velocity`` [m/s]. + + Guards against solver blow-up, where penalty contact ejects nodes at implausible speeds. + + Args: + env: The environment instance. + maximum_velocity: Maximum allowed nodal speed [m/s]. + asset_cfg: The deformable object entity. + + Returns: + Boolean tensor with shape ``(num_envs,)``. + """ + asset: DeformableObject = env.scene[asset_cfg.name] + speed = torch.linalg.norm(asset.data.nodal_vel_w.torch, dim=-1) + return speed.max(dim=1).values > maximum_velocity + + +def deformable_state_invalid( + env: ManagerBasedRLEnv, + asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), + position_limit: float = 1.0e4, +) -> torch.Tensor: + """Terminate when the deformable state is no longer numerically valid. + + Guards against a diverging solve: once any node position or velocity turns non-finite, the + center of mass (the mean over nodes) is non-finite too, so every reward and observation reading + it is poisoned for the rest of the episode. Terminating resets the environment so training can + continue. + + This reads the raw state, unlike the sanitized accessors used by the reward and observation + terms, which would otherwise mask the divergence. Node positions beyond ``position_limit`` are + also flagged, since a blow-up passes through large finite values before it overflows to + infinity. Unlike :func:`deformable_outside_table_bounds`, which only checks x and y, this covers + all three axes. + + Args: + env: The environment instance. + asset_cfg: The deformable object entity. + position_limit: Maximum absolute nodal position component treated as valid [m]. + + Returns: + Boolean tensor with shape ``(num_envs,)``. + """ + asset: DeformableObject = env.scene[asset_cfg.name] + nodal_pos_w = asset.data.nodal_pos_w.torch + nodal_vel_w = asset.data.nodal_vel_w.torch + valid = torch.isfinite(nodal_pos_w).flatten(1).all(dim=1) + valid &= torch.isfinite(nodal_vel_w).flatten(1).all(dim=1) + valid &= (nodal_pos_w.abs() <= position_limit).flatten(1).all(dim=1) + return ~valid + + +def robot_state_invalid( + env: ManagerBasedRLEnv, + asset_cfg: SceneEntityCfg = SceneEntityCfg("robot"), + position_limit: float = 1.0e4, +) -> torch.Tensor: + """Terminate when the robot state is no longer numerically valid. + + Robot-side analogue of :func:`deformable_state_invalid`. The coupled rigid/soft solve can turn + an environment's whole state non-finite in a single step, and MuJoCo Warp warm-starts the next + substep from that state, so the environment stays dead until it is reset. The other robot + terminations fail open here, since ``abs(NaN) > limit`` and ``NaN < limit`` are both ``False``. + + This reads the raw state, unlike the sanitized accessors used by the reward and observation + terms, which would otherwise mask the divergence. + + Args: + env: The environment instance. + asset_cfg: The robot entity. + position_limit: Maximum absolute body position component treated as valid [m]. + + Returns: + Boolean tensor with shape ``(num_envs,)``. + """ + asset: Articulation = env.scene[asset_cfg.name] + body_pos_w = asset.data.body_pos_w.torch + valid = torch.isfinite(asset.data.joint_pos.torch).all(dim=1) + valid &= torch.isfinite(asset.data.joint_vel.torch).all(dim=1) + valid &= torch.isfinite(body_pos_w).flatten(1).all(dim=1) + valid &= (body_pos_w.abs() <= position_limit).flatten(1).all(dim=1) + return ~valid + + +def joint_vel_out_of_sim_limit( + env: ManagerBasedRLEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") +) -> torch.Tensor: + """Terminate when joint velocities exceed actuator simulator limits [m/s or rad/s, depending on joint type].""" + asset: Articulation = env.scene[asset_cfg.name] + joint_ids = asset_cfg.joint_ids if asset_cfg.joint_ids is not None else slice(None) + limits = torch.full_like(asset.data.joint_vel.torch, torch.inf) + for actuator in asset.actuators.values(): + limits[:, actuator.joint_indices] = actuator.velocity_limit_sim + return torch.any(torch.abs(asset.data.joint_vel.torch[:, joint_ids]) > limits[:, joint_ids], dim=1) diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/utils.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/utils.py new file mode 100644 index 000000000000..8c8e1ce91e05 --- /dev/null +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/utils.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 + +"""Sanitized state accessors shared by the deformable lift MDP terms. + +The coupled rigid/soft solve can diverge and turn a whole environment's state non-finite. Measured +behaviour is a single-step event in one environment out of thousands: the robot joint state, the +robot body poses, the end-effector frame and every deformable node all become ``NaN`` at once, with +no growth in the preceding steps. Every reward or observation reading that state then returns +``NaN``, and RL libraries check the returned rewards and observations, so one diverged environment +aborts the whole run. + +Terminating on the divergence is not enough on its own. In +:meth:`~isaaclab.envs.ManagerBasedRLEnv.step` the reward manager runs after the termination manager +but *before* the environments are reset, so rewards for the terminating step are still computed from +the diverged state. Observations are computed after the reset and are normally clean, but the +pre-reset paths (an active recorder term, or ``compute_final_obs``) also read the diverged state. + +Reward terms, and the deformable observation terms, therefore read state through the helpers below, +which replace non-finite entries with ``0.0``. This places a diverged body at the world origin, +yielding a finite but meaningless value for exactly one step. That is intentional and acceptable, +because :func:`~isaaclab_tasks.core.lift.config.franka_soft.mdp.deformable_state_invalid` and +:func:`~isaaclab_tasks.core.lift.config.franka_soft.mdp.robot_state_invalid` flag the same step from +the raw state and the environment is reset immediately. + +The robot's root pose is deliberately left raw: the Franka is fixed-base, so body 0 is welded and +its transform has no joint-state dependence, keeping it finite while every descendant body goes +non-finite. A floating-base variant of this task would have to sanitize it too. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +import torch + +if TYPE_CHECKING: + from isaaclab.assets import Articulation, DeformableObject + from isaaclab.sensors import FrameTransformer + + +def _finite(value: torch.Tensor) -> torch.Tensor: + """Copy of ``value`` with every non-finite entry replaced by ``0.0``.""" + return torch.nan_to_num(value, nan=0.0, posinf=0.0, neginf=0.0) + + +def _com_w(asset: DeformableObject) -> torch.Tensor: + """Sanitized world-frame center of mass of a deformable object [m]. + + Args: + asset: The deformable object entity. + + Returns: + Tensor of shape ``(num_envs, 3)`` with non-finite entries replaced by ``0.0``. + """ + return _finite(asset.data.root_pos_w.torch) + + +def _nodal_pos_w(asset: DeformableObject) -> torch.Tensor: + """Sanitized world-frame nodal positions of a deformable object [m]. + + Args: + asset: The deformable object entity. + + Returns: + Tensor of shape ``(num_envs, num_nodes, 3)`` with non-finite entries replaced by ``0.0``. + """ + return _finite(asset.data.nodal_pos_w.torch) + + +def _body_pos_w(asset: Articulation, body_ids: slice | list[int]) -> torch.Tensor: + """Sanitized world-frame positions of the selected robot bodies [m]. + + Args: + asset: The articulation entity. + body_ids: Indices of the bodies to read. + + Returns: + Tensor of shape ``(num_envs, num_bodies, 3)`` with non-finite entries replaced by ``0.0``. + """ + return _finite(asset.data.body_pos_w.torch[:, body_ids]) + + +def _ee_pos_w(sensor: FrameTransformer) -> torch.Tensor: + """Sanitized world-frame position of the first target frame of a frame transformer [m]. + + Args: + sensor: The frame transformer sensor. + + Returns: + Tensor of shape ``(num_envs, 3)`` with non-finite entries replaced by ``0.0``. + """ + return _finite(sensor.data.target_pos_w.torch[..., 0, :]) From dd9798fcad152380ce1e4e74f4cdb3daf22ce8a2 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Fri, 31 Jul 2026 19:56:38 +0200 Subject: [PATCH 02/41] Style: Minimize comments --- .../config/franka_soft/franka_soft_env_cfg.py | 28 ++++--------------- 1 file changed, 6 insertions(+), 22 deletions(-) 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 1553dceeca2b..ad736b239ee8 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 @@ -255,11 +255,7 @@ class _FrankaSoftSceneCfg(InteractiveSceneCfg): ) def __post_init__(self) -> None: - # Re-tuned Franka actuators: stiff arm gains with realistic armature so the low-inertia - # default gains do not let the fingers tunnel through the soft body, and a slower, weaker - # gripper so it settles on the beam surface instead of crushing it. Velocity limits are - # required by the joint_vel_out_of_sim_limit termination. Scoped here rather than in - # FRANKA_PANDA_CFG so the other Franka tasks keep the stock asset. + # Re-tuned Franka actuators, most importantly with realistic velocity limits. shoulder = self.robot.actuators["panda_shoulder"] shoulder.velocity_limit_sim = 2.175 shoulder.stiffness = 600.0 @@ -468,18 +464,8 @@ class EventCfg: @configclass class RewardsCfg: - """Deformable analogue of the winning rigid-cube lift recipe. - - The table top sits at z = 0 and the beam's rest COM at ~0.05 m. The dense lift reward gates at - 0.02 m, i.e. below rest, so it stays graded across the full lift range and only vanishes when - the beam is pressed into the table. The goal-tracking terms gate at 0.0 m, so they are - effectively always active. Success = COM within 5 cm of the goal position. - """ - - # The hand targets the COM so the grasp lands mid-beam (targeting the nearest node lets the - # gripper grab an end, which turns the beam into an unstable pole). The fingers target the - # nearest surface node instead: targeting the COM with both fingers is maximized only when - # both fingertips reach the object's center, i.e. by closing and indenting the beam. + """Lift-to-target reward for a deformable object.""" + reaching_deformable = RewTerm( func=mdp.deformable_com_ee_distance, params={"std": 0.1, "asset_cfg": SceneEntityCfg("deformable")}, @@ -533,13 +519,13 @@ class CurriculumCfg: """Ramp the action-rate penalty once the policy has learned to lift (matches rigid recipe).""" action_rate = CurrTerm( - func=mdp.modify_reward_weight, params={"term_name": "action_rate", "weight": -1e-2, "num_steps": 40000} + func=mdp.modify_reward_weight, params={"term_name": "action_rate", "weight": -1e-2, "num_steps": 15000} ) # Since we use 24 steps per env, 10000 steps correspond to 10000/24 = 416.67 learning iterations gravity = CurrTerm( func=mdp.modify_gravity_linear, - params={"start_gravity_z": -0.0001, "end_gravity_z": -9.81, "start_step": 0, "end_step": 10000}, + params={"start_gravity_z": -0.0001, "end_gravity_z": -9.81, "start_step": 0, "end_step": 5000}, ) @@ -633,9 +619,7 @@ class FrankaSoftEnvCfg(ManagerBasedRLEnvCfg): # MDP settings rewards: RewardsCfg = RewardsCfg() terminations: TerminationsCfg = TerminationsCfg() - # Parent reset events + per-env material domain randomization. events: EventCfg = EventCfg() - # Ramp the action-rate penalty once the policy has learned to lift. curriculum: CurriculumCfg = CurriculumCfg() def __post_init__(self) -> None: @@ -666,5 +650,5 @@ class FrankaSoftCameraEnvCfg(FrankaSoftEnvCfg): def __post_init__(self) -> None: super().__post_init__() - # Warm up the RTX render product/annotator (Newton skips the PhysX assets_loading render loop). + # Warm up the RTX render product/annotator (Newton skips the PhysX assets_loading render loop), helps with passing rendering tests. self.num_rerenders_on_reset = 2 From 8d50d910222703c0d1d71adb74c5f6d533144a5c Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Fri, 31 Jul 2026 20:16:14 +0200 Subject: [PATCH 03/41] Style: Unnecessary invalidrobot state reset is removed (still trains without) --- .../franka_soft/agents/rsl_rl_ppo_cfg.py | 2 +- .../config/franka_soft/franka_soft_env_cfg.py | 8 ----- .../lift/config/franka_soft/mdp/__init__.pyi | 2 -- .../config/franka_soft/mdp/terminations.py | 32 ------------------- .../core/lift/config/franka_soft/mdp/utils.py | 5 ++- 5 files changed, 3 insertions(+), 46 deletions(-) diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/agents/rsl_rl_ppo_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/agents/rsl_rl_ppo_cfg.py index 3150aa9b4782..15d0d85f78d7 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/agents/rsl_rl_ppo_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/agents/rsl_rl_ppo_cfg.py @@ -55,7 +55,7 @@ class FrankaDeformablePPORunnerCfg(RslRlOnPolicyRunnerCfg): @configclass class FrankaDeformableCameraPPORunnerCfg(RslRlOnPolicyRunnerCfg): num_steps_per_env = 24 - max_iterations = 50000 + max_iterations = 5000 save_interval = 50 experiment_name = "franka_deformable_camera" obs_groups = { 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 ad736b239ee8..287c67619d15 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 @@ -565,14 +565,6 @@ class TerminationsCfg: params={"asset_cfg": SceneEntityCfg("deformable")}, ) - # The measured divergence poisons the beam too, so deformable_invalid resets that case. This - # covers a robot-only divergence: every reward term is deformable-driven and sanitized, and the - # other robot terminations fail open on NaN, so nothing else would ever reset the environment. - robot_invalid = DoneTerm( - func=mdp.robot_state_invalid, - params={"asset_cfg": SceneEntityCfg("robot")}, - ) - ## # Environment configuration diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/__init__.pyi b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/__init__.pyi index ac119215daa7..9911d8e295c0 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/__init__.pyi +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/__init__.pyi @@ -22,7 +22,6 @@ __all__ = [ "deformable_outside_table_bounds", "deformable_state_invalid", "joint_vel_out_of_sim_limit", - "robot_state_invalid", # events "randomize_deformable_material", "reset_deformable_over_support", @@ -59,6 +58,5 @@ from .terminations import ( deformable_outside_table_bounds, deformable_state_invalid, joint_vel_out_of_sim_limit, - robot_state_invalid, ) from isaaclab_tasks.core.lift.mdp import * diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/terminations.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/terminations.py index 65eb246e865c..5ae6db57093a 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/terminations.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/terminations.py @@ -114,38 +114,6 @@ def deformable_state_invalid( return ~valid -def robot_state_invalid( - env: ManagerBasedRLEnv, - asset_cfg: SceneEntityCfg = SceneEntityCfg("robot"), - position_limit: float = 1.0e4, -) -> torch.Tensor: - """Terminate when the robot state is no longer numerically valid. - - Robot-side analogue of :func:`deformable_state_invalid`. The coupled rigid/soft solve can turn - an environment's whole state non-finite in a single step, and MuJoCo Warp warm-starts the next - substep from that state, so the environment stays dead until it is reset. The other robot - terminations fail open here, since ``abs(NaN) > limit`` and ``NaN < limit`` are both ``False``. - - This reads the raw state, unlike the sanitized accessors used by the reward and observation - terms, which would otherwise mask the divergence. - - Args: - env: The environment instance. - asset_cfg: The robot entity. - position_limit: Maximum absolute body position component treated as valid [m]. - - Returns: - Boolean tensor with shape ``(num_envs,)``. - """ - asset: Articulation = env.scene[asset_cfg.name] - body_pos_w = asset.data.body_pos_w.torch - valid = torch.isfinite(asset.data.joint_pos.torch).all(dim=1) - valid &= torch.isfinite(asset.data.joint_vel.torch).all(dim=1) - valid &= torch.isfinite(body_pos_w).flatten(1).all(dim=1) - valid &= (body_pos_w.abs() <= position_limit).flatten(1).all(dim=1) - return ~valid - - def joint_vel_out_of_sim_limit( env: ManagerBasedRLEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") ) -> torch.Tensor: diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/utils.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/utils.py index 8c8e1ce91e05..c24590c4a908 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/utils.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/utils.py @@ -21,9 +21,8 @@ Reward terms, and the deformable observation terms, therefore read state through the helpers below, which replace non-finite entries with ``0.0``. This places a diverged body at the world origin, yielding a finite but meaningless value for exactly one step. That is intentional and acceptable, -because :func:`~isaaclab_tasks.core.lift.config.franka_soft.mdp.deformable_state_invalid` and -:func:`~isaaclab_tasks.core.lift.config.franka_soft.mdp.robot_state_invalid` flag the same step from -the raw state and the environment is reset immediately. +because :func:`~isaaclab_tasks.core.lift.config.franka_soft.mdp.deformable_state_invalid` flags the +same step from the raw state and the environment is reset immediately. The robot's root pose is deliberately left raw: the Franka is fixed-base, so body 0 is welded and its transform has no joint-state dependence, keeping it finite while every descendant body goes From 4034ef7405835d18bb71cb40434f160a9219dd76 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Fri, 31 Jul 2026 20:35:44 +0200 Subject: [PATCH 04/41] Style: Unify termination cfg --- .../config/franka_soft/franka_soft_env_cfg.py | 12 +++----- .../lift/config/franka_soft/mdp/__init__.pyi | 6 ++-- .../config/franka_soft/mdp/terminations.py | 29 +++++++------------ 3 files changed, 17 insertions(+), 30 deletions(-) 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 287c67619d15..75575a550f83 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 @@ -531,24 +531,20 @@ class CurriculumCfg: @configclass class TerminationsCfg: - """Time out + table bounds/drop termination.""" + """Time out + workspace bounds termination.""" time_out = DoneTerm(func=mdp.time_out, time_out=True) - deformable_outside_table = DoneTerm( - func=mdp.deformable_outside_table_bounds, + deformable_out_of_bounds = DoneTerm( + func=mdp.deformable_outside_bounds, params={ "x_bounds": (0.0, 1.0), "y_bounds": (-0.5, 0.5), + "z_bounds": (-0.02, 1.0), "asset_cfg": SceneEntityCfg("deformable"), }, ) - deformable_dropped = DoneTerm( - func=mdp.deformable_com_below_minimum, - params={"minimum_height": -0.1, "asset_cfg": SceneEntityCfg("deformable")}, - ) - ee_below_table = DoneTerm( func=mdp.ee_below_minimum, params={"minimum_height": 0.0, "ee_frame_cfg": SceneEntityCfg("ee_frame")}, diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/__init__.pyi b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/__init__.pyi index 9911d8e295c0..9e95d647564d 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/__init__.pyi +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/__init__.pyi @@ -17,9 +17,8 @@ __all__ = [ "deformable_com_goal_distance_delta", "deformable_com_goal_reached", # terminations - "deformable_com_below_minimum", "deformable_nodal_vel_above_maximum", - "deformable_outside_table_bounds", + "deformable_outside_bounds", "deformable_state_invalid", "joint_vel_out_of_sim_limit", # events @@ -53,9 +52,8 @@ from .rewards import ( deformable_lifting, ) from .terminations import ( - deformable_com_below_minimum, deformable_nodal_vel_above_maximum, - deformable_outside_table_bounds, + deformable_outside_bounds, deformable_state_invalid, joint_vel_out_of_sim_limit, ) diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/terminations.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/terminations.py index 5ae6db57093a..ab04713cca25 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/terminations.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/terminations.py @@ -22,29 +22,22 @@ from isaaclab.envs import ManagerBasedRLEnv -def deformable_com_below_minimum( - env: ManagerBasedRLEnv, - minimum_height: float, - asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), -) -> torch.Tensor: - """Termination signal when the deformable's COM falls below ``minimum_height`` [m].""" - asset: DeformableObject = env.scene[asset_cfg.name] - com_z = asset.data.root_pos_w.torch[:, 2] - return com_z < minimum_height - - -def deformable_outside_table_bounds( +def deformable_outside_bounds( env: ManagerBasedRLEnv, x_bounds: tuple[float, float], y_bounds: tuple[float, float], + z_bounds: tuple[float, float], asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), ) -> torch.Tensor: - """Terminate if any deformable nodal point leaves the table footprint. + """Terminate if any deformable nodal point leaves the allowed workspace box. + + Covers both leaving the table footprint (x, y) and being dropped off it (z). Args: env: The environment instance. x_bounds: Allowed x-position range in the environment frame [m]. y_bounds: Allowed y-position range in the environment frame [m]. + z_bounds: Allowed z-position range in the environment frame [m]. asset_cfg: The deformable object entity. Returns: @@ -52,9 +45,9 @@ def deformable_outside_table_bounds( """ asset: DeformableObject = env.scene[asset_cfg.name] nodal_pos = asset.data.nodal_pos_w.torch - env.scene.env_origins.unsqueeze(1) - outside_x = (nodal_pos[..., 0] < x_bounds[0]) | (nodal_pos[..., 0] > x_bounds[1]) - outside_y = (nodal_pos[..., 1] < y_bounds[0]) | (nodal_pos[..., 1] > y_bounds[1]) - return torch.any(outside_x | outside_y, dim=1) + lower = torch.tensor([x_bounds[0], y_bounds[0], z_bounds[0]], device=nodal_pos.device) + upper = torch.tensor([x_bounds[1], y_bounds[1], z_bounds[1]], device=nodal_pos.device) + return ((nodal_pos < lower) | (nodal_pos > upper)).flatten(1).any(dim=1) def deformable_nodal_vel_above_maximum( @@ -94,8 +87,8 @@ def deformable_state_invalid( This reads the raw state, unlike the sanitized accessors used by the reward and observation terms, which would otherwise mask the divergence. Node positions beyond ``position_limit`` are also flagged, since a blow-up passes through large finite values before it overflows to - infinity. Unlike :func:`deformable_outside_table_bounds`, which only checks x and y, this covers - all three axes. + infinity. Unlike :func:`deformable_outside_bounds`, whose limits are task bounds, this one is a + numerical sanity check. Args: env: The environment instance. From a41efb056fcd5c51c9908d871a7dc0585437c33d Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Fri, 31 Jul 2026 20:46:50 +0200 Subject: [PATCH 05/41] Style: Unnecessary deformable invalid state --- .../config/franka_soft/franka_soft_env_cfg.py | 6 ---- .../lift/config/franka_soft/mdp/__init__.pyi | 2 -- .../config/franka_soft/mdp/terminations.py | 35 ------------------- .../core/lift/config/franka_soft/mdp/utils.py | 13 +++---- 4 files changed, 4 insertions(+), 52 deletions(-) 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 75575a550f83..2475198ad769 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 @@ -555,12 +555,6 @@ class TerminationsCfg: params={"asset_cfg": SceneEntityCfg("robot")}, ) - # real failure, not a time out: a diverged solve must bootstrap as a termination - deformable_invalid = DoneTerm( - func=mdp.deformable_state_invalid, - params={"asset_cfg": SceneEntityCfg("deformable")}, - ) - ## # Environment configuration diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/__init__.pyi b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/__init__.pyi index 9e95d647564d..dc8132a8f4cf 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/__init__.pyi +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/__init__.pyi @@ -19,7 +19,6 @@ __all__ = [ # terminations "deformable_nodal_vel_above_maximum", "deformable_outside_bounds", - "deformable_state_invalid", "joint_vel_out_of_sim_limit", # events "randomize_deformable_material", @@ -54,7 +53,6 @@ from .rewards import ( from .terminations import ( deformable_nodal_vel_above_maximum, deformable_outside_bounds, - deformable_state_invalid, joint_vel_out_of_sim_limit, ) from isaaclab_tasks.core.lift.mdp import * diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/terminations.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/terminations.py index ab04713cca25..e3905aab3c75 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/terminations.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/terminations.py @@ -72,41 +72,6 @@ def deformable_nodal_vel_above_maximum( return speed.max(dim=1).values > maximum_velocity -def deformable_state_invalid( - env: ManagerBasedRLEnv, - asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), - position_limit: float = 1.0e4, -) -> torch.Tensor: - """Terminate when the deformable state is no longer numerically valid. - - Guards against a diverging solve: once any node position or velocity turns non-finite, the - center of mass (the mean over nodes) is non-finite too, so every reward and observation reading - it is poisoned for the rest of the episode. Terminating resets the environment so training can - continue. - - This reads the raw state, unlike the sanitized accessors used by the reward and observation - terms, which would otherwise mask the divergence. Node positions beyond ``position_limit`` are - also flagged, since a blow-up passes through large finite values before it overflows to - infinity. Unlike :func:`deformable_outside_bounds`, whose limits are task bounds, this one is a - numerical sanity check. - - Args: - env: The environment instance. - asset_cfg: The deformable object entity. - position_limit: Maximum absolute nodal position component treated as valid [m]. - - Returns: - Boolean tensor with shape ``(num_envs,)``. - """ - asset: DeformableObject = env.scene[asset_cfg.name] - nodal_pos_w = asset.data.nodal_pos_w.torch - nodal_vel_w = asset.data.nodal_vel_w.torch - valid = torch.isfinite(nodal_pos_w).flatten(1).all(dim=1) - valid &= torch.isfinite(nodal_vel_w).flatten(1).all(dim=1) - valid &= (nodal_pos_w.abs() <= position_limit).flatten(1).all(dim=1) - return ~valid - - def joint_vel_out_of_sim_limit( env: ManagerBasedRLEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") ) -> torch.Tensor: diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/utils.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/utils.py index c24590c4a908..d04d4fec372b 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/utils.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/utils.py @@ -12,17 +12,12 @@ ``NaN``, and RL libraries check the returned rewards and observations, so one diverged environment aborts the whole run. -Terminating on the divergence is not enough on its own. In -:meth:`~isaaclab.envs.ManagerBasedRLEnv.step` the reward manager runs after the termination manager -but *before* the environments are reset, so rewards for the terminating step are still computed from -the diverged state. Observations are computed after the reset and are normally clean, but the -pre-reset paths (an active recorder term, or ``compute_final_obs``) also read the diverged state. - Reward terms, and the deformable observation terms, therefore read state through the helpers below, which replace non-finite entries with ``0.0``. This places a diverged body at the world origin, -yielding a finite but meaningless value for exactly one step. That is intentional and acceptable, -because :func:`~isaaclab_tasks.core.lift.config.franka_soft.mdp.deformable_state_invalid` flags the -same step from the raw state and the environment is reset immediately. +yielding finite but meaningless values. The task has no termination on numerical validity: the +bounds terminations fail open on ``NaN`` (both ``NaN < lower`` and ``NaN > upper`` are ``False``), +so a diverged environment runs to its time out and is reset there. Training tolerates this, since +the event is rare and the sanitized rewards stay finite throughout. The robot's root pose is deliberately left raw: the Franka is fixed-base, so body 0 is welded and its transform has no joint-state dependence, keeping it finite while every descendant body goes From 648721aaacf70588e8936059ae11da30689867da Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Mon, 3 Aug 2026 13:52:32 +0200 Subject: [PATCH 06/41] Fix: Policy still trains without delta position reward --- .../config/franka_soft/franka_soft_env_cfg.py | 10 ---- .../lift/config/franka_soft/mdp/__init__.pyi | 4 -- .../lift/config/franka_soft/mdp/rewards.py | 54 ------------------- .../config/franka_soft/mdp/terminations.py | 22 -------- 4 files changed, 90 deletions(-) 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 2475198ad769..5e1023140d88 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 @@ -478,16 +478,6 @@ class RewardsCfg: weight=5.0, ) - deformable_goal_tracking_delta = RewTerm( - func=mdp.deformable_com_goal_distance_delta, - params={ - "minimal_height": 0.0, - "command_name": "deformable_pose", - "asset_cfg": SceneEntityCfg("deformable"), - }, - weight=500.0, - ) - deformable_goal_tracking = RewTerm( func=mdp.deformable_com_goal_distance, params={ diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/__init__.pyi b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/__init__.pyi index dc8132a8f4cf..0cf4f671172e 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/__init__.pyi +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/__init__.pyi @@ -14,10 +14,8 @@ __all__ = [ "deformable_com_ee_distance", "deformable_fingertip_distance", "deformable_com_goal_distance", - "deformable_com_goal_distance_delta", "deformable_com_goal_reached", # terminations - "deformable_nodal_vel_above_maximum", "deformable_outside_bounds", "joint_vel_out_of_sim_limit", # events @@ -43,7 +41,6 @@ from .pose_commands import ( from .rewards import ( deformable_com_ee_distance, deformable_com_goal_distance, - deformable_com_goal_distance_delta, deformable_com_goal_reached, deformable_ee_distance, deformable_fingertip_distance, @@ -51,7 +48,6 @@ from .rewards import ( deformable_lifting, ) from .terminations import ( - deformable_nodal_vel_above_maximum, deformable_outside_bounds, joint_vel_out_of_sim_limit, ) diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/rewards.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/rewards.py index 1a1e9a4df875..bf76c35c28ee 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/rewards.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/rewards.py @@ -215,60 +215,6 @@ def __call__( return is_lifted.float() * (1.0 - torch.tanh(distance / std)) -class deformable_com_goal_distance_delta(ManagerTermBase): - """Reward progress of the deformable COM toward the commanded goal. - - Returns the per-step decrease in the COM-to-goal distance (previous minus current), - gated so it only credits while the COM is lifted above ``minimal_height`` [m]. The stored - distance is re-baselined on the first step after reset so the reset teleport does not - produce a spurious reward. Success tracking matches :class:`deformable_com_goal_distance`. - """ - - def __init__(self, cfg: RewardTermCfg, env: ManagerBasedRLEnv): - super().__init__(cfg, env) - self._prev_distance = torch.zeros(env.num_envs, device=env.device) - self._needs_baseline = torch.ones(env.num_envs, dtype=torch.bool, device=env.device) - self._track_success = cfg.params.get("success_threshold") is not None - if self._track_success: - self._succeeded = torch.zeros(env.num_envs, dtype=torch.bool, device=env.device) - - def reset(self, env_ids: Sequence[int] | None = None): - if env_ids is None: - env_ids = slice(None) - self._needs_baseline[env_ids] = True - if self._track_success: - self._env.extras.setdefault("log", {})["Metrics/success_rate"] = ( - self._succeeded[env_ids].float().mean().item() - ) - self._succeeded[env_ids] = False - - def __call__( - self, - env: ManagerBasedRLEnv, - minimal_height: float, - command_name: str, - robot_cfg: SceneEntityCfg = SceneEntityCfg("robot"), - asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), - success_threshold: float | None = None, - ) -> torch.Tensor: - robot: Articulation = env.scene[robot_cfg.name] - asset: DeformableObject = env.scene[asset_cfg.name] - command = env.command_manager.get_command(command_name) - des_pos_w, _ = combine_frame_transforms( - robot.data.root_pos_w.torch, robot.data.root_quat_w.torch, command[:, :3] - ) - com_w = _com_w(asset) - distance = torch.linalg.norm(des_pos_w - com_w, dim=1) - self._prev_distance = torch.where(self._needs_baseline, distance, self._prev_distance) - self._needs_baseline[:] = False - is_lifted = com_w[:, 2] > minimal_height - if success_threshold is not None: - self._succeeded |= is_lifted & (distance < success_threshold) - delta = self._prev_distance - distance - self._prev_distance = distance - return is_lifted.float() * delta - - def deformable_com_goal_reached( env: ManagerBasedRLEnv, minimal_height: float, diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/terminations.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/terminations.py index e3905aab3c75..1a27cb071d84 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/terminations.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/terminations.py @@ -50,28 +50,6 @@ def deformable_outside_bounds( return ((nodal_pos < lower) | (nodal_pos > upper)).flatten(1).any(dim=1) -def deformable_nodal_vel_above_maximum( - env: ManagerBasedRLEnv, - maximum_velocity: float, - asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), -) -> torch.Tensor: - """Terminate when any deformable node moves faster than ``maximum_velocity`` [m/s]. - - Guards against solver blow-up, where penalty contact ejects nodes at implausible speeds. - - Args: - env: The environment instance. - maximum_velocity: Maximum allowed nodal speed [m/s]. - asset_cfg: The deformable object entity. - - Returns: - Boolean tensor with shape ``(num_envs,)``. - """ - asset: DeformableObject = env.scene[asset_cfg.name] - speed = torch.linalg.norm(asset.data.nodal_vel_w.torch, dim=-1) - return speed.max(dim=1).values > maximum_velocity - - def joint_vel_out_of_sim_limit( env: ManagerBasedRLEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") ) -> torch.Tensor: From 46f5cdd106107ad1fc6858222b0c7fb697e7c598 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Mon, 3 Aug 2026 14:48:52 +0200 Subject: [PATCH 07/41] Fix: Remove duplicate terminations --- .../changelog.d/mym-lift-soft.minor.rst | 4 +-- .../isaaclab_tasks/core/lift/mdp/__init__.pyi | 4 --- .../core/lift/mdp/terminations.py | 25 +------------------ 3 files changed, 3 insertions(+), 30 deletions(-) diff --git a/source/isaaclab_tasks/changelog.d/mym-lift-soft.minor.rst b/source/isaaclab_tasks/changelog.d/mym-lift-soft.minor.rst index f90ad6e01a17..e65fe03d2142 100644 --- a/source/isaaclab_tasks/changelog.d/mym-lift-soft.minor.rst +++ b/source/isaaclab_tasks/changelog.d/mym-lift-soft.minor.rst @@ -42,10 +42,10 @@ Changed ``Isaac-Lift-Cloth-Franka`` to the actor/critic model configurations, using :class:`~isaaclab_rl.rsl_rl.RslRlMLPModelCfg` with observation normalization and explicit ``obs_groups``, a learning rate of 1e-3, and ``max_iterations`` lowered from 50000 to 5000. -* Retuned ``Isaac-Lift-Soft-Franka`` and ``Isaac-Lift-Soft-Franka-Camera`` for stable grasping: a +* Re-tuned ``Isaac-Lift-Soft-Franka`` and ``Isaac-Lift-Soft-Franka-Camera`` for stable grasping: a stiffer and denser beam, a smaller particle radius, explicit collider contact and rest offsets, and full-surface rigid-soft contact with signed-distance fields on the gripper. -* Retuned the Franka arm and hand actuator gains of the Franka deformable lift environments, adding +* Re-tuned the Franka arm and hand actuator gains of the Franka deformable lift environments, adding realistic armature and a slower, weaker gripper so it settles on the object instead of crushing it. This replaces the previous per-task gripper overrides, so the cloth tasks now use the same gains as the soft-beam tasks. diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/__init__.pyi b/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/__init__.pyi index 93cb88f1efd8..e79d6934ca06 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/__init__.pyi +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/__init__.pyi @@ -40,8 +40,6 @@ __all__ = [ "position_command_progress", "success_reward", "abnormal_robot_state", - "deformable_com_below_minimum", - "deformable_outside_table_bounds", "ee_below_minimum", "object_reached_goal", "out_of_bound", @@ -85,8 +83,6 @@ from .rewards import ( ) from .terminations import ( abnormal_robot_state, - deformable_com_below_minimum, - deformable_outside_table_bounds, ee_below_minimum, object_reached_goal, out_of_bound, diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/terminations.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/terminations.py index 166bd9e805db..17a802353e22 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/terminations.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/terminations.py @@ -20,7 +20,7 @@ from isaaclab.utils.math import combine_frame_transforms if TYPE_CHECKING: - from isaaclab.assets import Articulation, DeformableObject, RigidObject + from isaaclab.assets import Articulation, RigidObject from isaaclab.envs import ManagerBasedRLEnv from isaaclab.sensors import FrameTransformer @@ -88,29 +88,6 @@ def object_reached_goal( return torch.linalg.norm(des_pos_w - object.data.root_pos_w.torch[:, :3], dim=1) < threshold -def deformable_com_below_minimum( - env: ManagerBasedRLEnv, - minimum_height: float, - asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), -) -> torch.Tensor: - """Return whether the deformable object's COM is below the minimum height [m].""" - asset: DeformableObject = env.scene[asset_cfg.name] - return wp.to_torch(asset.data.root_pos_w)[:, 2] < minimum_height - - -def deformable_outside_table_bounds( - env: ManagerBasedRLEnv, - x_bounds: tuple[float, float], - y_bounds: tuple[float, float], - asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), -) -> torch.Tensor: - """Return whether any deformable node left the table footprint [m].""" - asset: DeformableObject = env.scene[asset_cfg.name] - nodal_pos = wp.to_torch(asset.data.nodal_pos_w) - env.scene.env_origins.unsqueeze(1) - outside_x = (nodal_pos[..., 0] < x_bounds[0]) | (nodal_pos[..., 0] > x_bounds[1]) - outside_y = (nodal_pos[..., 1] < y_bounds[0]) | (nodal_pos[..., 1] > y_bounds[1]) - return torch.any(outside_x | outside_y, dim=1) - def ee_below_minimum( env: ManagerBasedRLEnv, From 5e637d2d913d2e5452cad175704c66b6c8bc10c1 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Mon, 3 Aug 2026 17:44:14 +0200 Subject: [PATCH 08/41] Feat: new franka asset allows SDF using analytic shapes for the gripper. WIP training tuning --- .../lab_newton/isaaclab_newton.physics.rst | 6 - .../changelog.d/mym-lift-soft.minor.rst | 3 - .../isaaclab_newton/physics/__init__.pyi | 2 - .../physics/newton_collision_cfg.py | 4 +- .../isaaclab_newton/physics/newton_manager.py | 34 ------ .../physics/newton_manager_cfg.py | 35 ------ .../changelog.d/mym-lift-soft.minor.rst | 5 +- .../config/franka_soft/franka_soft_env_cfg.py | 113 ++++++++++++------ 8 files changed, 81 insertions(+), 121 deletions(-) diff --git a/docs/source/api/lab_newton/isaaclab_newton.physics.rst b/docs/source/api/lab_newton/isaaclab_newton.physics.rst index b53add0c744c..8570e55f8806 100644 --- a/docs/source/api/lab_newton/isaaclab_newton.physics.rst +++ b/docs/source/api/lab_newton/isaaclab_newton.physics.rst @@ -18,7 +18,6 @@ NewtonCollisionPipelineCfg HydroelasticSDFCfg NewtonShapeCfg - NewtonShapeSDFCfg NewtonMJWarpManager NewtonXPBDManager NewtonFeatherstoneManager @@ -88,11 +87,6 @@ Physics Configuration :show-inheritance: :exclude-members: __init__ -.. autoclass:: NewtonShapeSDFCfg - :members: - :show-inheritance: - :exclude-members: __init__ - Solver Managers --------------- diff --git a/source/isaaclab_newton/changelog.d/mym-lift-soft.minor.rst b/source/isaaclab_newton/changelog.d/mym-lift-soft.minor.rst index 3f9fd2104285..f89e4eaf6d17 100644 --- a/source/isaaclab_newton/changelog.d/mym-lift-soft.minor.rst +++ b/source/isaaclab_newton/changelog.d/mym-lift-soft.minor.rst @@ -4,6 +4,3 @@ Added * Added :attr:`~isaaclab_newton.physics.NewtonCollisionPipelineCfg.enable_rigid_soft_full_surface_contact` to generate edge and triangle-interior soft contacts against rigid SDFs, so rigid features that pass between soft vertices are caught. -* Added :class:`~isaaclab_newton.physics.NewtonShapeSDFCfg` and - :attr:`~isaaclab_newton.physics.NewtonCfg.sdf_shape_cfgs` to provision volume SDFs on collider - shapes selected by label regex, as required by full-surface rigid-soft contact. diff --git a/source/isaaclab_newton/isaaclab_newton/physics/__init__.pyi b/source/isaaclab_newton/isaaclab_newton/physics/__init__.pyi index fa541baaafa8..46cc26e9318f 100644 --- a/source/isaaclab_newton/isaaclab_newton/physics/__init__.pyi +++ b/source/isaaclab_newton/isaaclab_newton/physics/__init__.pyi @@ -17,7 +17,6 @@ __all__ = [ "NewtonCollisionPipelineCfg", "NewtonManager", "NewtonShapeCfg", - "NewtonShapeSDFCfg", "NewtonSolverCfg", "NewtonXPBDManager", "XPBDSolverCfg", @@ -36,7 +35,6 @@ from .newton_manager import NewtonManager from .newton_manager_cfg import ( NewtonCfg, NewtonShapeCfg, - NewtonShapeSDFCfg, NewtonSolverCfg, ) from .xpbd_manager import NewtonXPBDManager diff --git a/source/isaaclab_newton/isaaclab_newton/physics/newton_collision_cfg.py b/source/isaaclab_newton/isaaclab_newton/physics/newton_collision_cfg.py index 3a7b810208d6..2f9a56c13fa0 100644 --- a/source/isaaclab_newton/isaaclab_newton/physics/newton_collision_cfg.py +++ b/source/isaaclab_newton/isaaclab_newton/physics/newton_collision_cfg.py @@ -155,8 +155,8 @@ class NewtonCollisionPipelineCfg: When ``True``, Newton adds edge and triangle-interior soft contacts (in addition to the per-vertex particle contacts) so rigid features that pass between soft vertices are caught. - Requires a volume SDF on every participating rigid mesh/convex shape; provision these via - :attr:`~isaaclab_newton.physics.NewtonShapeSDFCfg` on :attr:`NewtonCfg.sdf_shape_cfgs`. + Analytic shapes (boxes, capsules, spheres) are full-surface-capable without an SDF; any + participating mesh/convex collider must carry a volume SDF. Defaults to ``False`` (same as Newton's default). """ diff --git a/source/isaaclab_newton/isaaclab_newton/physics/newton_manager.py b/source/isaaclab_newton/isaaclab_newton/physics/newton_manager.py index 6394ccd5be74..fd5e0a735f7b 100644 --- a/source/isaaclab_newton/isaaclab_newton/physics/newton_manager.py +++ b/source/isaaclab_newton/isaaclab_newton/physics/newton_manager.py @@ -1178,35 +1178,6 @@ def _prepare_builder_for_finalize(cls, builder: ModelBuilder) -> None: The default implementation is a no-op. """ - @classmethod - def _provision_shape_sdfs(cls, builder: ModelBuilder) -> None: - """Request volume SDFs on collider shapes selected by :attr:`NewtonCfg.sdf_shape_cfgs`. - - Newton retains per-shape SDF requests on the builder (``shape_force_sdf`` and - ``shape_sdf_max_resolution``) until :meth:`ModelBuilder.finalize` generates the SDF data. - Rather than intercepting shape creation, this matches the already-populated shape labels - by regex and flips the retained flags, equivalent to calling - ``ShapeConfig.configure_sdf(force_sdf=True, max_resolution=...)`` at add time. Required by - :attr:`NewtonCollisionPipelineCfg.enable_rigid_soft_full_surface_contact`. - """ - cfg = PhysicsManager._cfg - if not isinstance(cfg, NewtonCfg) or not cfg.sdf_shape_cfgs: - return - - labels = list(getattr(builder, "shape_label", ()) or ()) - if not labels: - return - - for sdf_cfg in cfg.sdf_shape_cfgs: - if not sdf_cfg.shape_label_patterns: - continue - matched, _ = resolve_matching_names(sdf_cfg.shape_label_patterns, labels, raise_when_no_match=False) - for index in matched: - builder.shape_force_sdf[index] = True - if sdf_cfg.max_resolution is not None: - builder.shape_sdf_max_resolution[index] = sdf_cfg.max_resolution - builder.shape_sdf_target_voxel_size[index] = None - @classmethod def cl_register_site(cls, body_pattern: str | None, xform: wp.transform, *, per_world: bool = False) -> str: """Register a site request for injection into prototypes before replication. @@ -1553,11 +1524,6 @@ def start_simulation(cls) -> None: cls._builder.request_state_attributes(*cls._pending_extended_state_attributes) NewtonManager._pending_extended_state_attributes = set() cls._prepare_builder_for_finalize(cls._builder) - # Provision volume SDFs on selected collider shapes (e.g. gripper) before finalize so - # full-surface rigid-soft contact has the SDFs it requires. Runs after the subclass hook - # so replicated shape labels are present, and unconditionally so subclasses that override - # _prepare_builder_for_finalize without calling super() still get it. - cls._provision_shape_sdfs(cls._builder) with Timer(name="newton_finalize_builder", msg="Finalize builder took:"): NewtonManager._model = cls._builder.finalize(device=device) cls._model.set_gravity(cls._gravity_vector) 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 94872de460cc..8249d04bd096 100644 --- a/source/isaaclab_newton/isaaclab_newton/physics/newton_manager_cfg.py +++ b/source/isaaclab_newton/isaaclab_newton/physics/newton_manager_cfg.py @@ -8,7 +8,6 @@ from __future__ import annotations import logging -from dataclasses import field from typing import TYPE_CHECKING, Literal from isaaclab.physics import PhysicsCfg @@ -99,32 +98,6 @@ class NewtonShapeCfg: """ -@configclass -class NewtonShapeSDFCfg: - """Provisions a volume SDF on selected rigid collider shapes before finalize. - - Full-surface rigid-soft contact - (:attr:`~isaaclab_newton.physics.NewtonCollisionPipelineCfg.enable_rigid_soft_full_surface_contact`) - requires an SDF on every participating rigid mesh/convex shape. Newton retains per-shape SDF - requests on the builder until finalize; this config targets shapes by full label regex and - forwards to ``ModelBuilder.ShapeConfig.configure_sdf(force_sdf=True)`` semantics. - """ - - shape_label_patterns: list[str] = field(default_factory=list) - """Full Newton shape-label regexes; every matched collider shape receives a volume SDF. - - Matching is a full-label regex against ``Model.shape_label`` (append ``.*`` to match a body's - descendant collider shapes, e.g. ``r"/World/envs/env_.*/Robot/panda_hand.*"``). - """ - - max_resolution: int | None = None - """Maximum SDF grid resolution [voxels], must be divisible by 8. - - ``None`` builds the SDF at Newton's default resolution (``force_sdf`` only), which is the - lightest way to provision the SDF needed for full-surface contact. - """ - - @configclass class NewtonCfg(PhysicsCfg): """Configuration for Newton physics manager. @@ -196,14 +169,6 @@ class NewtonCfg(PhysicsCfg): :class:`NewtonShapeCfg` for the declared fields. """ - sdf_shape_cfgs: list[NewtonShapeSDFCfg] = field(default_factory=list) - """Per-shape volume SDF provisioning applied to the builder before finalize. - - Each entry selects collider shapes by label regex and requests a volume SDF on them, as - required by :attr:`NewtonCollisionPipelineCfg.enable_rigid_soft_full_surface_contact`. - Defaults to an empty list (no SDFs provisioned beyond Newton's own heuristics). - """ - simplify_meshes: bool = True """Whether Newton replication simplifies mesh colliders to convex hulls. diff --git a/source/isaaclab_tasks/changelog.d/mym-lift-soft.minor.rst b/source/isaaclab_tasks/changelog.d/mym-lift-soft.minor.rst index e65fe03d2142..4d453687dbf0 100644 --- a/source/isaaclab_tasks/changelog.d/mym-lift-soft.minor.rst +++ b/source/isaaclab_tasks/changelog.d/mym-lift-soft.minor.rst @@ -44,7 +44,10 @@ Changed ``obs_groups``, a learning rate of 1e-3, and ``max_iterations`` lowered from 50000 to 5000. * Re-tuned ``Isaac-Lift-Soft-Franka`` and ``Isaac-Lift-Soft-Franka-Camera`` for stable grasping: a stiffer and denser beam, a smaller particle radius, explicit collider contact and rest offsets, - and full-surface rigid-soft contact with signed-distance fields on the gripper. + and full-surface rigid-soft contact. +* Changed the robot asset of the Franka deformable lift environments to the Menagerie + ``franka_panda.usda``, whose analytic gripper colliders support full-surface rigid-soft contact + directly without provisioning signed-distance fields. * Re-tuned the Franka arm and hand actuator gains of the Franka deformable lift environments, adding realistic armature and a slower, weaker gripper so it settles on the object instead of crushing it. This replaces the previous per-task gripper overrides, so the cloth tasks now use the same 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 5e1023140d88..5817b0925e2c 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,7 +11,6 @@ MJWarpSolverCfg, NewtonCfg, NewtonCollisionPipelineCfg, - NewtonShapeSDFCfg, ) from isaaclab_newton.sim.schemas import NewtonDeformableBodyPropertiesCfg from isaaclab_newton.sim.spawners.materials import NewtonDeformableBodyMaterialCfg @@ -21,6 +20,7 @@ from isaaclab_physx.sim.spawners.materials import PhysxDeformableBodyMaterialCfg import isaaclab.sim as sim_utils +from isaaclab.actuators import ImplicitActuatorCfg from isaaclab.assets import ArticulationCfg, AssetBaseCfg from isaaclab.assets.deformable_object import DeformableObjectCfg from isaaclab.controllers import DifferentialIKControllerCfg @@ -39,7 +39,7 @@ from isaaclab.sensors import CameraCfg, FrameTransformerCfg from isaaclab.sensors.frame_transformer.frame_transformer_cfg import OffsetCfg from isaaclab.sim.spawners.from_files.from_files_cfg import GroundPlaneCfg -from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR +from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR, ISAACLAB_NUCLEUS_DIR from isaaclab.utils.configclass import configclass from isaaclab.visualizers import VisualizerCfg @@ -62,7 +62,7 @@ # Pre-defined configs ## -from isaaclab_assets.robots.franka import FRANKA_PANDA_CFG # isort:skip +from isaaclab_assets.robots.franka import FRANKA_PANDA_CFG, FRANKA_PANDA_MENAGERIE_CFG # isort:skip ## @@ -168,8 +168,8 @@ class PhysicsCfg(PresetCfg): source="rigid", destination="soft", bodies=[ - r"/World/envs/env_.*/Robot/panda_hand", - r"/World/envs/env_.*/Robot/panda_(left|right)finger", + r"/World/envs/env_.*/Robot/Geometry/.*panda_hand", + r"/World/envs/env_.*/Robot/Geometry/.*panda_(left|right)finger", ], collide_interval=1, collision_pipeline=NewtonCollisionPipelineCfg( @@ -180,16 +180,6 @@ class PhysicsCfg(PresetCfg): iterations=1, model_cfg=NewtonModelCfg(soft_contact_ke=5.0e3), ), - sdf_shape_cfgs=[ - NewtonShapeSDFCfg( - shape_label_patterns=[ - r"/World/envs/env_.*/Robot/panda_hand/collisions/collisions", - r"/World/envs/env_.*/Robot/panda_(left|right)finger/collisions/collisions", - ], - # ~2.3 mm voxels on the fingers; finer isn't needed for contact resolution. - max_resolution=8, - ) - ], num_substeps=2, ) @@ -212,15 +202,15 @@ class PhysicsCfg(PresetCfg): class _FrankaSoftSceneCfg(InteractiveSceneCfg): """Scene for the Franka deformable environment.""" - robot: ArticulationCfg = FRANKA_PANDA_CFG.replace(prim_path="{ENV_REGEX_NS}/Robot") + robot: ArticulationCfg = FRANKA_PANDA_MENAGERIE_CFG.replace(prim_path="{ENV_REGEX_NS}/Robot") # end-effector frame for reward shaping ee_frame: FrameTransformerCfg = FrameTransformerCfg( - prim_path="/World/envs/env_.*/Robot/panda_link0", + prim_path="/World/envs/env_.*/Robot/Geometry/.*panda_link0", debug_vis=False, target_frames=[ FrameTransformerCfg.FrameCfg( - prim_path="/World/envs/env_.*/Robot/panda_hand", + prim_path="/World/envs/env_.*/Robot/Geometry/.*panda_hand", name="end_effector", offset=OffsetCfg(pos=[0.0, 0.0, 0.1034]), ), @@ -255,25 +245,72 @@ class _FrankaSoftSceneCfg(InteractiveSceneCfg): ) def __post_init__(self) -> None: - # Re-tuned Franka actuators, most importantly with realistic velocity limits. - shoulder = self.robot.actuators["panda_shoulder"] - shoulder.velocity_limit_sim = 2.175 - shoulder.stiffness = 600.0 - shoulder.damping = 50.0 - shoulder.armature = {"panda_joint[1-2]": 0.6057, "panda_joint[3-4]": 0.4625} - - forearm = self.robot.actuators["panda_forearm"] - forearm.velocity_limit_sim = 2.61 - forearm.stiffness = {"panda_joint5": 250.0, "panda_joint6": 150.0, "panda_joint7": 50.0} - forearm.damping = {"panda_joint5": 30.0, "panda_joint6": 25.0, "panda_joint7": 15.0} - forearm.armature = 0.2055 - - hand = self.robot.actuators["panda_hand"] - hand.effort_limit_sim = 70.0 - hand.velocity_limit_sim = 0.2 - hand.stiffness = 750.0 - hand.damping = 175.0 - hand.armature = 0.1 + # Menagerie asset: analytic gripper colliders, so full-surface contact needs no SDF. + # self.robot.spawn.usd_path = f"{ISAACLAB_NUCLEUS_DIR}/Robots/FrankaEmika/franka_panda.usda" + + # # inspired by libfranka's joint_impedance_control.cpp + # shoulder = self.robot.actuators["panda_shoulder"] + # shoulder.velocity_limit_sim = 2.175 + # shoulder.stiffness = 600.0 + # shoulder.damping = 50.0 + # shoulder.armature = {"panda_joint[1-2]": 0.6057, "panda_joint[3-4]": 0.4625} + + # forearm = self.robot.actuators["panda_forearm"] + # forearm.velocity_limit_sim = 2.61 + # forearm.stiffness = {"panda_joint5": 250.0, "panda_joint6": 150.0, "panda_joint7": 50.0} + # forearm.damping = {"panda_joint5": 30.0, "panda_joint6": 25.0, "panda_joint7": 15.0} + # forearm.armature = 0.2055 + + # hand = self.robot.actuators["panda_hand"] + # hand.effort_limit_sim = 70.0 + # hand.velocity_limit_sim = 0.2 + # hand.stiffness = 750.0 + # hand.damping = 175.0 + # hand.armature = 0.1 + self.robot.actuators = { + # inspired by libfranka's joint_impedance_control.cpp + "panda_arm": ImplicitActuatorCfg( + joint_names_expr=["panda_joint[1-7]"], + effort_limit_sim={"panda_joint[1-4]": 87.0, "panda_joint[5-7]": 12.0}, + velocity_limit={"panda_joint[1-4]": 2.175, "panda_joint[5-7]": 2.61}, + # velocity_limit_sim={"panda_joint[1-4]": 20.0, "panda_joint[5-7]": 25.0}, + stiffness={ + "panda_joint[1-4]": 600.0, + "panda_joint5": 250.0, + "panda_joint6": 150.0, + "panda_joint7": 50.0, + }, + damping={ + "panda_joint[1-4]": 50.0, + "panda_joint5": 30.0, + "panda_joint6": 25.0, + "panda_joint7": 15.0, + }, + armature={ + "panda_joint[1-2]": 0.6057, + "panda_joint[3-4]": 0.4625, + "panda_joint[5-7]": 0.2055, + }, + ), + "panda_hand": ImplicitActuatorCfg( + joint_names_expr=["panda_finger_joint1"], + effort_limit_sim=70.0, + velocity_limit=0.2, + velocity_limit_sim=2.0, + stiffness=350.0, + damping=175.0, + armature=0.1, + ), + "panda_finger2_passive": ImplicitActuatorCfg( + joint_names_expr=["panda_finger_joint2"], + effort_limit_sim=1.0, + velocity_limit=0.2, + velocity_limit_sim=2.0, + stiffness=0.0, + damping=0.0, + armature=0.1, + ), + } @configclass @@ -622,5 +659,5 @@ class FrankaSoftCameraEnvCfg(FrankaSoftEnvCfg): def __post_init__(self) -> None: super().__post_init__() - # Warm up the RTX render product/annotator (Newton skips the PhysX assets_loading render loop), helps with passing rendering tests. + # Warm up the RTX render product/annotator (Newton skips the PhysX assets_loading render loop). self.num_rerenders_on_reset = 2 From 00cfa6e43d3c811eba33dd47066cd7498364d8a0 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Mon, 3 Aug 2026 20:50:34 +0200 Subject: [PATCH 09/41] Feat: Trainable mjwarp lift soft --- .../config/franka_soft/franka_soft_env_cfg.py | 36 ++++--------------- 1 file changed, 7 insertions(+), 29 deletions(-) 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 5817b0925e2c..6ff35a8b64a9 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 @@ -178,7 +178,7 @@ class PhysicsCfg(PresetCfg): ) ], iterations=1, - model_cfg=NewtonModelCfg(soft_contact_ke=5.0e3), + model_cfg=NewtonModelCfg(soft_contact_ke=8.0e3, soft_contact_mu=10.0), ), num_substeps=2, ) @@ -245,28 +245,6 @@ class _FrankaSoftSceneCfg(InteractiveSceneCfg): ) def __post_init__(self) -> None: - # Menagerie asset: analytic gripper colliders, so full-surface contact needs no SDF. - # self.robot.spawn.usd_path = f"{ISAACLAB_NUCLEUS_DIR}/Robots/FrankaEmika/franka_panda.usda" - - # # inspired by libfranka's joint_impedance_control.cpp - # shoulder = self.robot.actuators["panda_shoulder"] - # shoulder.velocity_limit_sim = 2.175 - # shoulder.stiffness = 600.0 - # shoulder.damping = 50.0 - # shoulder.armature = {"panda_joint[1-2]": 0.6057, "panda_joint[3-4]": 0.4625} - - # forearm = self.robot.actuators["panda_forearm"] - # forearm.velocity_limit_sim = 2.61 - # forearm.stiffness = {"panda_joint5": 250.0, "panda_joint6": 150.0, "panda_joint7": 50.0} - # forearm.damping = {"panda_joint5": 30.0, "panda_joint6": 25.0, "panda_joint7": 15.0} - # forearm.armature = 0.2055 - - # hand = self.robot.actuators["panda_hand"] - # hand.effort_limit_sim = 70.0 - # hand.velocity_limit_sim = 0.2 - # hand.stiffness = 750.0 - # hand.damping = 175.0 - # hand.armature = 0.1 self.robot.actuators = { # inspired by libfranka's joint_impedance_control.cpp "panda_arm": ImplicitActuatorCfg( @@ -365,7 +343,7 @@ class _JointActionsCfg: arm_action = mdp.RelativeJointPositionActionCfg(asset_name="robot", joint_names=["panda_joint.*"], scale=0.03) gripper_action = mdp.JointPositionToLimitsActionCfg( - asset_name="robot", joint_names=["panda_finger.*"], rescale_to_limits=True + asset_name="robot", joint_names=["panda_finger_joint1"], rescale_to_limits=True ) @@ -388,9 +366,9 @@ class _IkActionsCfg: gripper_action = mdp.BinaryJointPositionActionCfg( asset_name="robot", - joint_names=["panda_finger.*"], - open_command_expr={"panda_finger_.*": 0.05}, - close_command_expr={"panda_finger_.*": 0.015}, + joint_names=["panda_finger_joint1"], + open_command_expr={"panda_finger_joint1": 0.04}, + close_command_expr={"panda_finger_joint1": 0.015}, ) @@ -535,7 +513,7 @@ class RewardsCfg: "success_threshold": 0.05, "asset_cfg": SceneEntityCfg("deformable"), }, - weight=10.0, + weight=20.0, ) action_rate = RewTerm(func=mdp.action_rate_l2, weight=-1e-4) @@ -552,7 +530,7 @@ class CurriculumCfg: # Since we use 24 steps per env, 10000 steps correspond to 10000/24 = 416.67 learning iterations gravity = CurrTerm( func=mdp.modify_gravity_linear, - params={"start_gravity_z": -0.0001, "end_gravity_z": -9.81, "start_step": 0, "end_step": 5000}, + params={"start_gravity_z": -0.0001, "end_gravity_z": -9.81, "start_step": 0, "end_step": 10000}, ) From 8875f69395c84d9e4d2e01925df90f86278e62ef Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Mon, 3 Aug 2026 22:21:13 +0200 Subject: [PATCH 10/41] Fix: Physx training --- .../isaaclab/sim/spawners/meshes/meshes.py | 20 +++++++++---------- .../config/franka_soft/franka_soft_env_cfg.py | 7 +++++-- .../config/franka_soft/mdp/pose_commands.py | 4 ++-- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py index 22e851b6aed3..28bf16b9e5f0 100644 --- a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py +++ b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py @@ -362,9 +362,8 @@ def _spawn_mesh_geom_from_mesh( There is a difference in how the properties are applied to the prim based on the type of object: - - Deformable body properties: The properties are applied to the parent prim: ``{prim_path}``. - - Collision properties: The properties are applied to the simulation mesh ``{prim_path}/sim_mesh`` for - deformable bodies, and to the mesh prim ``{prim_path}/geometry/mesh`` otherwise. + - Deformable body properties: The properties are applied to the mesh prim: ``{prim_path}/geometry/mesh``. + - Collision properties: The properties are applied to the mesh prim: ``{prim_path}/geometry/mesh``. - Rigid body properties: The properties are applied to the parent prim: ``{prim_path}``. Args: @@ -382,9 +381,9 @@ def _spawn_mesh_geom_from_mesh( Raises: ValueError: If a prim already exists at the given path. ValueError: If both deformable and rigid properties are used. + ValueError: If both deformable and collision properties are used. ValueError: If the physics material is not of the correct type. Deformable properties require a deformable physics material, and rigid properties require a rigid physics material. - ValueError: If deformable properties are used with non-fragment collision properties. .. _USDGeomMesh: https://openusd.org/dev/api/class_usd_geom_mesh.html """ @@ -399,11 +398,6 @@ def _spawn_mesh_geom_from_mesh( # check that invalid schema types are not used if cfg.deformable_props is not None and cfg.rigid_props is not None: raise ValueError("Cannot use both deformable and rigid properties at the same time.") - if cfg.deformable_props is not None and cfg.collision_props is not None: - # only fragments resolve onto the simulation mesh, legacy cfgs would target the inert body prim - frags = cfg.collision_props if isinstance(cfg.collision_props, (list, tuple)) else [cfg.collision_props] - if not all(isinstance(frag, schemas.SchemaFragment) for frag in frags): - raise ValueError("Deformable bodies require 'collision_props' as collision fragments.") # check material types are correct if cfg.deformable_props is not None and cfg.physics_material is not None: if not isinstance(cfg.physics_material, DeformableBodyMaterialBaseCfg): @@ -420,6 +414,12 @@ def _spawn_mesh_geom_from_mesh( if not is_rigid_material: raise ValueError("Rigid properties require a rigid physics material.") + # refine the surface for deformable primitives + if cfg.deformable_props is not None: + max_edge = 0.3 * float(np.linalg.norm(mesh.bounding_box.extents)) + vertices, faces = trimesh.remesh.subdivide_to_size(mesh.vertices, mesh.faces, max_edge=max_edge) + mesh = trimesh.Trimesh(vertices=vertices, faces=faces, process=False) + # create all the paths we need for clarity geom_prim_path = prim_path + "/geometry" mesh_prim_path = geom_prim_path + "/mesh" @@ -516,4 +516,4 @@ def _spawn_mesh_geom_from_mesh( if rigid_frags and all(isinstance(f, schemas.SchemaFragment) for f in rigid_frags): schemas.apply_rigid_body_properties(prim_path, rigid_frags, stage=stage) else: - schemas.define_rigid_body_properties(prim_path, cfg.rigid_props, stage=stage) + schemas.define_rigid_body_properties(prim_path, cfg.rigid_props, stage=stage) \ No newline at end of file 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 6ff35a8b64a9..fb2a2f4f52fa 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 @@ -206,11 +206,14 @@ class _FrankaSoftSceneCfg(InteractiveSceneCfg): # end-effector frame for reward shaping ee_frame: FrameTransformerCfg = FrameTransformerCfg( - prim_path="/World/envs/env_.*/Robot/Geometry/.*panda_link0", + prim_path="/World/envs/env_.*/Robot/Geometry/panda_link0", debug_vis=False, target_frames=[ FrameTransformerCfg.FrameCfg( - prim_path="/World/envs/env_.*/Robot/Geometry/.*panda_hand", + prim_path=( + "/World/envs/env_.*/Robot/Geometry/panda_link0/panda_link1/panda_link2/panda_link3/" + "panda_link4/panda_link5/panda_link6/panda_link7/panda_hand" + ), name="end_effector", offset=OffsetCfg(pos=[0.0, 0.0, 0.1034]), ), diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/pose_commands.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/pose_commands.py index 2cda5216a49c..218a858b8f3a 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/pose_commands.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/pose_commands.py @@ -15,8 +15,8 @@ from isaaclab.utils.configclass import configclass from isaaclab.utils.math import combine_frame_transforms -from isaaclab_tasks.core.dexsuite.mdp.commands.pose_commands import ObjectUniformPoseCommand -from isaaclab_tasks.core.dexsuite.mdp.commands.pose_commands_cfg import ObjectUniformPoseCommandCfg +from isaaclab_tasks.core.lift.mdp.commands.pose_commands import ObjectUniformPoseCommand +from isaaclab_tasks.core.lift.mdp.commands.pose_commands_cfg import ObjectUniformPoseCommandCfg if TYPE_CHECKING: from isaaclab.assets import DeformableObject From 9606f2de8b19e5109dd865196a8ac520b1d43418 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Mon, 3 Aug 2026 22:59:55 +0200 Subject: [PATCH 11/41] Feat: Tuned physx parameters with new franka asset --- docs/source/_static/css/environment-browser.js | 8 ++++---- docs/source/overview/environments.rst | 12 ++++++++++-- .../lift/config/franka_soft/agents/rsl_rl_ppo_cfg.py | 2 +- .../lift/config/franka_soft/franka_soft_env_cfg.py | 12 ++++++------ 4 files changed, 21 insertions(+), 13 deletions(-) diff --git a/docs/source/_static/css/environment-browser.js b/docs/source/_static/css/environment-browser.js index 02dd2940055e..1924f1ce46c0 100644 --- a/docs/source/_static/css/environment-browser.js +++ b/docs/source/_static/css/environment-browser.js @@ -21,13 +21,13 @@ ["Isaac-Fourbar-Pole-Swingup", "rsl_rl", "newton_kamino", "", ""], ["Isaac-Humanoid", "rl_games,rsl_rl,skrl,sb3", "isaacsim_physx,newton_mjwarp", "", ""], ["Isaac-Humanoid-Direct", "rl_games,rsl_rl,skrl", "isaacsim_physx,newton_mjwarp,ovphysx", "", ""], - ["Isaac-Lift-Cloth-Franka", "rsl_rl", "newton_mjwarp_vbd_proxy,ovphysx", "", ""], - ["Isaac-Lift-Cloth-Franka-Camera", "rsl_rl", "newton_mjwarp_vbd_proxy,ovphysx", "isaacsim_rtx,newton_renderer,ovrtx", ""], + ["Isaac-Lift-Cloth-Franka", "rsl_rl", "newton_mjwarp_vbd_proxy,ovphysx", "", "ik,joint"], + ["Isaac-Lift-Cloth-Franka-Camera", "rsl_rl", "newton_mjwarp_vbd_proxy,ovphysx", "isaacsim_rtx,newton_renderer,ovrtx", "ik,joint"], ["Isaac-Lift-Franka", "rsl_rl", "isaacsim_physx,newton_mjwarp", "", "cube,shapes"], ["Isaac-Lift-KukaAllegro", "rsl_rl", "isaacsim_physx,newton_mjwarp,ovphysx", "", "cube,shapes"], ["Isaac-Lift-KukaAllegro-Camera", "rsl_rl", "isaacsim_physx,newton_mjwarp,ovphysx", "isaacsim_rtx,newton_renderer,ovrtx", "albedo128,albedo256,albedo64,cube,depth128,depth256,depth64,duo_camera,raycaster_depth128,raycaster_depth256,raycaster_depth64,rgb128,rgb256,rgb64,semantic_segmentation128,semantic_segmentation256,semantic_segmentation64,shapes,simple_shading_constant_diffuse128,simple_shading_constant_diffuse256,simple_shading_constant_diffuse64,simple_shading_diffuse_mdl128,simple_shading_diffuse_mdl256,simple_shading_diffuse_mdl64,simple_shading_full_mdl128,simple_shading_full_mdl256,simple_shading_full_mdl64,single_camera"], - ["Isaac-Lift-Soft-Franka", "rsl_rl", "isaacsim_physx,newton_mjwarp_vbd_proxy,ovphysx", "", ""], - ["Isaac-Lift-Soft-Franka-Camera", "rsl_rl", "isaacsim_physx,newton_mjwarp_vbd_proxy,ovphysx", "isaacsim_rtx,newton_renderer,ovrtx", ""], + ["Isaac-Lift-Soft-Franka", "rsl_rl", "isaacsim_physx,newton_mjwarp_vbd_proxy,ovphysx", "", "ik,joint"], + ["Isaac-Lift-Soft-Franka-Camera", "rsl_rl", "isaacsim_physx,newton_mjwarp_vbd_proxy,ovphysx", "isaacsim_rtx,newton_renderer,ovrtx", "ik,joint"], ["Isaac-Open-Drawer-Franka", "rl_games,rsl_rl,skrl", "", "", ""], ["Isaac-Open-Drawer-Franka-Direct", "rl_games,rsl_rl,skrl", "isaacsim_physx,newton_mjwarp,ovphysx", "", ""], ["Isaac-Pendulum-Direct", "rl_games,skrl", "", "", ""], diff --git a/docs/source/overview/environments.rst b/docs/source/overview/environments.rst index b8d38a83dccc..0b927494af77 100644 --- a/docs/source/overview/environments.rst +++ b/docs/source/overview/environments.rst @@ -234,22 +234,26 @@ for the lift-cube environment: | |lift-soft-franka| | |lift-soft-franka-link| | Pick a deformable soft body and bring it to a sampled target position with | **physics=** ``isaacsim_physx``, | | | | the Franka robot | ``newton_mjwarp_vbd_proxy``, | | | | | ``ovphysx`` | + | | | | **presets=** ``ik``, ``joint`` | +-------------------------+------------------------------+-----------------------------------------------------------------------------+-----------------------------------------+ | |lift-soft-franka| | |lift-soft-franka-cam-link| | Camera (vision) variant of the soft-body lift task using RGB observations | **physics=** ``isaacsim_physx``, | | | | | ``newton_mjwarp_vbd_proxy``, | | | | | ``ovphysx`` | | | | | **renderer=** ``isaacsim_rtx``, | | | | | ``newton_renderer``, ``ovrtx`` | + | | | | **presets=** ``ik``, ``joint`` | +-------------------------+------------------------------+-----------------------------------------------------------------------------+-----------------------------------------+ | |lift-cloth-franka| | |lift-cloth-franka-link| | Lift a deformable cloth from a table with the Franka robot | **physics=** | | | | | ``newton_mjwarp_vbd_proxy``, | | | | | ``ovphysx`` | + | | | | **presets=** ``ik``, ``joint`` | +-------------------------+------------------------------+-----------------------------------------------------------------------------+-----------------------------------------+ | |lift-cloth-franka| | |lift-cloth-franka-cam-link| | Camera (vision) variant of the cloth lift task using RGB observations | **physics=** | | | | | ``newton_mjwarp_vbd_proxy``, | | | | | ``ovphysx`` | | | | | **renderer=** ``isaacsim_rtx``, | | | | | ``newton_renderer``, ``ovrtx`` | + | | | | **presets=** ``ik``, ``joint`` | +-------------------------+------------------------------+-----------------------------------------------------------------------------+-----------------------------------------+ | |stack-cube| | |stack-cube-link| | Stack three cubes (bottom to top: blue, red, green) with the Franka robot. | **physics=** ``isaacsim_physx``, | | | | Blueprint env used for the NVIDIA Isaac GR00T blueprint for synthetic | ``newton_mjwarp`` | @@ -1093,12 +1097,14 @@ including disabling runtime perturbations used for training. * - Isaac-Lift-Cloth-Franka - Manager Based - **rsl_rl** (PPO) - - **physics=** ``newton_mjwarp_vbd_proxy``, ``ovphysx`` + - | **physics=** ``newton_mjwarp_vbd_proxy``, ``ovphysx`` + | **presets=** ``ik``, ``joint`` * - Isaac-Lift-Cloth-Franka-Camera - Manager Based - **rsl_rl** (PPO) - | **physics=** ``newton_mjwarp_vbd_proxy``, ``ovphysx`` | **renderer=** ``isaacsim_rtx``, ``newton_renderer``, ``ovrtx`` + | **presets=** ``ik``, ``joint`` * - Isaac-Lift-Franka - Manager Based - **rsl_rl** (PPO) @@ -1118,12 +1124,14 @@ including disabling runtime perturbations used for training. * - Isaac-Lift-Soft-Franka - Manager Based - **rsl_rl** (PPO) - - **physics=** ``isaacsim_physx``, ``newton_mjwarp_vbd_proxy``, ``ovphysx`` + - | **physics=** ``isaacsim_physx``, ``newton_mjwarp_vbd_proxy``, ``ovphysx`` + | **presets=** ``ik``, ``joint`` * - Isaac-Lift-Soft-Franka-Camera - Manager Based - **rsl_rl** (PPO) - | **physics=** ``isaacsim_physx``, ``newton_mjwarp_vbd_proxy``, ``ovphysx`` | **renderer=** ``isaacsim_rtx``, ``newton_renderer``, ``ovrtx`` + | **presets=** ``ik``, ``joint`` * - Isaac-Open-Drawer-Franka - Manager Based - **rl_games** (PPO), **rsl_rl** (PPO), **skrl** (PPO) diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/agents/rsl_rl_ppo_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/agents/rsl_rl_ppo_cfg.py index 15d0d85f78d7..61c7f8d1f05f 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/agents/rsl_rl_ppo_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/agents/rsl_rl_ppo_cfg.py @@ -31,7 +31,7 @@ @configclass class FrankaDeformablePPORunnerCfg(RslRlOnPolicyRunnerCfg): num_steps_per_env = 24 - max_iterations = 5000 + max_iterations = 3000 save_interval = 50 experiment_name = "franka_soft" obs_groups = { 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 fb2a2f4f52fa..f603f297d587 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 @@ -124,14 +124,14 @@ class DeformableCfg(PresetCfg): spawn=sim_utils.MeshCuboidCfg( size=(0.3, 0.04, 0.04), deformable_props=PhysxDeformableBodyPropertiesCfg(), - collision_props=[PhysxCollisionCfg(rest_offset=0.0005, contact_offset=0.005)], + collision_props=[PhysxCollisionCfg(rest_offset=0.0025, contact_offset=0.01)], visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.45, 0.45, 0.85)), physics_material=PhysxDeformableBodyMaterialCfg( density=1000.0, youngs_modulus=YOUNGS_MODULUS, poissons_ratio=POISSONS_RATIO, - static_friction=1.0, - dynamic_friction=1.0, + static_friction=5.0, + dynamic_friction=5.0, ), ), ) @@ -184,8 +184,8 @@ class PhysicsCfg(PresetCfg): ) isaacsim_physx: PhysxCfg = PhysxCfg( - friction_offset_threshold=0.001, - friction_correlation_distance=0.005, + friction_offset_threshold=0.005, + friction_correlation_distance=0.01, ) ovphysx: OvPhysxCfg = OvPhysxCfg() physx: PhysxAutoCfg = PhysxAutoCfg(isaacsim_physx=isaacsim_physx, ovphysx=ovphysx) @@ -253,7 +253,7 @@ def __post_init__(self) -> None: "panda_arm": ImplicitActuatorCfg( joint_names_expr=["panda_joint[1-7]"], effort_limit_sim={"panda_joint[1-4]": 87.0, "panda_joint[5-7]": 12.0}, - velocity_limit={"panda_joint[1-4]": 2.175, "panda_joint[5-7]": 2.61}, + velocity_limit_sim={"panda_joint[1-4]": 2.175, "panda_joint[5-7]": 2.61}, # velocity_limit_sim={"panda_joint[1-4]": 20.0, "panda_joint[5-7]": 25.0}, stiffness={ "panda_joint[1-4]": 600.0, From 946d2bbc89380fff00f34d1a37f494d419dfdd8c Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 4 Aug 2026 00:07:21 +0200 Subject: [PATCH 12/41] Test: Higher friction and more action rate penalty --- .../core/lift/config/franka_soft/franka_soft_env_cfg.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 f603f297d587..034aac48afd4 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 @@ -130,8 +130,8 @@ class DeformableCfg(PresetCfg): density=1000.0, youngs_modulus=YOUNGS_MODULUS, poissons_ratio=POISSONS_RATIO, - static_friction=5.0, - dynamic_friction=5.0, + static_friction=10.0, + dynamic_friction=10.0, ), ), ) @@ -519,7 +519,7 @@ class RewardsCfg: weight=20.0, ) - action_rate = RewTerm(func=mdp.action_rate_l2, weight=-1e-4) + action_rate = RewTerm(func=mdp.action_rate_l2, weight=-1e-3) @configclass @@ -527,7 +527,7 @@ class CurriculumCfg: """Ramp the action-rate penalty once the policy has learned to lift (matches rigid recipe).""" action_rate = CurrTerm( - func=mdp.modify_reward_weight, params={"term_name": "action_rate", "weight": -1e-2, "num_steps": 15000} + func=mdp.modify_reward_weight, params={"term_name": "action_rate", "weight": -1e-1, "num_steps": 15000} ) # Since we use 24 steps per env, 10000 steps correspond to 10000/24 = 416.67 learning iterations From ebf371b693d278c41de7c0b3ffc0e9b7bd470e9a Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 4 Aug 2026 11:25:53 +0200 Subject: [PATCH 13/41] Fix pre-commit formatting --- source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py | 2 +- .../core/lift/config/franka_soft/franka_soft_env_cfg.py | 4 ++-- .../isaaclab_tasks/core/lift/mdp/terminations.py | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py index 28bf16b9e5f0..5e7a982eecd0 100644 --- a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py +++ b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py @@ -516,4 +516,4 @@ def _spawn_mesh_geom_from_mesh( if rigid_frags and all(isinstance(f, schemas.SchemaFragment) for f in rigid_frags): schemas.apply_rigid_body_properties(prim_path, rigid_frags, stage=stage) else: - schemas.define_rigid_body_properties(prim_path, cfg.rigid_props, stage=stage) \ No newline at end of file + schemas.define_rigid_body_properties(prim_path, cfg.rigid_props, stage=stage) 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 034aac48afd4..64c0e58f5d6a 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 @@ -39,7 +39,7 @@ from isaaclab.sensors import CameraCfg, FrameTransformerCfg from isaaclab.sensors.frame_transformer.frame_transformer_cfg import OffsetCfg from isaaclab.sim.spawners.from_files.from_files_cfg import GroundPlaneCfg -from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR, ISAACLAB_NUCLEUS_DIR +from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR from isaaclab.utils.configclass import configclass from isaaclab.visualizers import VisualizerCfg @@ -62,7 +62,7 @@ # Pre-defined configs ## -from isaaclab_assets.robots.franka import FRANKA_PANDA_CFG, FRANKA_PANDA_MENAGERIE_CFG # isort:skip +from isaaclab_assets.robots.franka import FRANKA_PANDA_MENAGERIE_CFG # isort:skip ## diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/terminations.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/terminations.py index 17a802353e22..d7a90c4e00ec 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/terminations.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/terminations.py @@ -88,7 +88,6 @@ def object_reached_goal( return torch.linalg.norm(des_pos_w - object.data.root_pos_w.torch[:, :3], dim=1) < threshold - def ee_below_minimum( env: ManagerBasedRLEnv, minimum_height: float, From 93b38616ce94485751795f0cb2f906974e13fe12 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 4 Aug 2026 11:26:18 +0200 Subject: [PATCH 14/41] Remove video support from soft lift demo --- .../state_machine/lift_franka_soft.py | 39 ++----------------- 1 file changed, 3 insertions(+), 36 deletions(-) diff --git a/scripts/environments/state_machine/lift_franka_soft.py b/scripts/environments/state_machine/lift_franka_soft.py index dd88db4a184a..776cb0f0bd9f 100644 --- a/scripts/environments/state_machine/lift_franka_soft.py +++ b/scripts/environments/state_machine/lift_franka_soft.py @@ -17,13 +17,9 @@ # Headless. uv run python scripts/environments/state_machine/lift_franka_soft.py --viz none - # Record a video. Requires Isaac Sim, since RecordVideo goes through the Kit RTX viewport. - uv run python scripts/environments/state_machine/lift_franka_soft.py --video - """ import argparse -import os import sys from collections.abc import Sequence @@ -44,26 +40,12 @@ parser.add_argument("--num_envs", type=int, default=1, help="Number of environments to simulate.") parser.add_argument("--num_steps", type=int, default=1000, help="Number of environment steps to run.") parser.add_argument("--task", type=str, default="Isaac-Lift-Soft-Franka", help="The task to run.") -parser.add_argument("--video", action="store_true", default=False, help="Record a video of the rollout.") -parser.add_argument("--video_length", type=int, default=500, help="Length of the recorded video (in env steps).") -parser.add_argument( - "--video_folder", - type=str, - default="videos/lift_franka_soft", - help="Directory to write recorded videos into.", -) add_launcher_args(parser) # the task runs on Newton, so default to the kitless viewer parser.set_defaults(visualizer=["newton"]) args_cli, hydra_args = setup_preset_cli(parser) sys.argv = [sys.argv[0]] + hydra_args -# RecordVideo needs an rgb_array render mode, which is the Kit RTX viewport: enable cameras and -# request the Kit visualizer so launch_simulation starts Isaac Sim. -if args_cli.video: - args_cli.enable_cameras = True - args_cli.visualizer = ["kit"] - # initialize warp wp.init() @@ -279,8 +261,6 @@ 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 # parse configuration via Hydra, so presets can be selected on the CLI (e.g. presets=isaacsim_physx) env_cfg, _ = resolve_task_config(args_cli.task, "") env_cfg.sim.device = args_cli.device @@ -289,27 +269,14 @@ def main(): # defaults to relative joint targets, which RL trains on. env_cfg.actions = ActionsCfg().ik # frame the deformable at (0.5, 0.0, 0.05) rather than the world origin. ``viewer`` drives the - # Kit viewport and the video recorder; ``default_visualizer_cfg`` drives the Newton/Kit - # visualizer window, which otherwise falls back to VisualizerCfg's (4.0, -4.0, 3.0). + # Kit viewport; ``default_visualizer_cfg`` drives the Newton/Kit visualizer window, which + # otherwise falls back to VisualizerCfg's (4.0, -4.0, 3.0). env_cfg.viewer.eye = (1.3, 0.6, 0.5) env_cfg.viewer.lookat = (0.5, 0.0, 0.05) env_cfg.sim.default_visualizer_cfg = VisualizerCfg(eye=env_cfg.viewer.eye, lookat=env_cfg.viewer.lookat) with launch_simulation(env_cfg, args_cli): - env = gym.make(args_cli.task, cfg=env_cfg, render_mode=render_mode) - - # wrap for video recording - if args_cli.video: - video_folder = os.path.abspath(args_cli.video_folder) - os.makedirs(video_folder, exist_ok=True) - env = gym.wrappers.RecordVideo( - env, - video_folder=video_folder, - step_trigger=lambda step: step == 0, - video_length=args_cli.video_length, - disable_logger=True, - ) - print(f"[INFO] Recording video to {video_folder} (length={args_cli.video_length} steps)") + env = gym.make(args_cli.task, cfg=env_cfg) # reset environment at start env.reset() From c41c26e1c2fed964772660c16e4f5de6b9ab47d2 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 4 Aug 2026 11:53:20 +0200 Subject: [PATCH 15/41] Restore deformable collision validation --- .../isaaclab/isaaclab/sim/spawners/meshes/meshes.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py index 5e7a982eecd0..faaac56f6fcf 100644 --- a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py +++ b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py @@ -362,8 +362,9 @@ def _spawn_mesh_geom_from_mesh( There is a difference in how the properties are applied to the prim based on the type of object: - - Deformable body properties: The properties are applied to the mesh prim: ``{prim_path}/geometry/mesh``. - - Collision properties: The properties are applied to the mesh prim: ``{prim_path}/geometry/mesh``. + - Deformable body properties: The properties are applied to the parent prim: ``{prim_path}``. + - Collision properties: The properties are applied to the simulation mesh ``{prim_path}/sim_mesh`` for + deformable bodies, and to the mesh prim ``{prim_path}/geometry/mesh`` otherwise. - Rigid body properties: The properties are applied to the parent prim: ``{prim_path}``. Args: @@ -381,9 +382,9 @@ def _spawn_mesh_geom_from_mesh( Raises: ValueError: If a prim already exists at the given path. ValueError: If both deformable and rigid properties are used. - ValueError: If both deformable and collision properties are used. ValueError: If the physics material is not of the correct type. Deformable properties require a deformable physics material, and rigid properties require a rigid physics material. + ValueError: If deformable properties are used with non-fragment collision properties. .. _USDGeomMesh: https://openusd.org/dev/api/class_usd_geom_mesh.html """ @@ -398,6 +399,11 @@ def _spawn_mesh_geom_from_mesh( # check that invalid schema types are not used if cfg.deformable_props is not None and cfg.rigid_props is not None: raise ValueError("Cannot use both deformable and rigid properties at the same time.") + if cfg.deformable_props is not None and cfg.collision_props is not None: + # only fragments resolve onto the simulation mesh, legacy cfgs would target the inert body prim + frags = cfg.collision_props if isinstance(cfg.collision_props, (list, tuple)) else [cfg.collision_props] + if not all(isinstance(frag, schemas.SchemaFragment) for frag in frags): + raise ValueError("Deformable bodies require 'collision_props' as collision fragments.") # check material types are correct if cfg.deformable_props is not None and cfg.physics_material is not None: if not isinstance(cfg.physics_material, DeformableBodyMaterialBaseCfg): From fb1999a998d63dd0f0c8c77cd3d5b40bbec23beb Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 4 Aug 2026 12:18:44 +0200 Subject: [PATCH 16/41] Correct soft lift changelog fragment --- .../changelog.d/mym-lift-soft.major.rst | 16 ++++++ .../changelog.d/mym-lift-soft.minor.rst | 56 ------------------- 2 files changed, 16 insertions(+), 56 deletions(-) create mode 100644 source/isaaclab_tasks/changelog.d/mym-lift-soft.major.rst delete mode 100644 source/isaaclab_tasks/changelog.d/mym-lift-soft.minor.rst diff --git a/source/isaaclab_tasks/changelog.d/mym-lift-soft.major.rst b/source/isaaclab_tasks/changelog.d/mym-lift-soft.major.rst new file mode 100644 index 000000000000..a10bad341739 --- /dev/null +++ b/source/isaaclab_tasks/changelog.d/mym-lift-soft.major.rst @@ -0,0 +1,16 @@ +Added +^^^^^ + +* Added deformable-specific commands, observations, rewards, events, terminations, and curricula, + plus ``joint`` and ``ik`` action presets, to the Franka soft-beam and cloth lift environments. + +Changed +^^^^^^^ + +* **Breaking:** Changed the default action space to relative joint-position control. Use + ``presets=ik`` for task-space inverse-kinematics control; integrations using the cloth + environments' previous absolute joint targets must update their actions. +* **Breaking:** Changed the non-camera ``rsl_rl`` experiment name from ``franka_deformable`` to + ``franka_soft``. Update log and checkpoint paths that refer to ``logs/rsl_rl/franka_deformable``. +* Re-tuned the robot, scenes, contact handling, control rate, and ``rsl_rl`` configuration for stable + gravity-based training across the supported physics backends. diff --git a/source/isaaclab_tasks/changelog.d/mym-lift-soft.minor.rst b/source/isaaclab_tasks/changelog.d/mym-lift-soft.minor.rst deleted file mode 100644 index 4d453687dbf0..000000000000 --- a/source/isaaclab_tasks/changelog.d/mym-lift-soft.minor.rst +++ /dev/null @@ -1,56 +0,0 @@ -Added -^^^^^ - -* Added a task-local ``mdp`` package for the Franka deformable lift environments, providing - deformable-aware rewards, observations, terminations, events, curricula, and a pose command that - tracks the deformable center of mass. -* Added an ``ik`` action preset to the Franka deformable lift environments that drives the arm with - an absolute end-effector pose through a differential inverse-kinematics controller. Select it - with ``presets=ik``. -* Added a gravity curriculum to the Franka deformable lift environments that linearly ramps the - vertical gravity up to -9.81 m/s^2 over the first environment steps, so the policy learns to - grasp before it has to hold the object up. The soft-beam tasks ramp from near zero over 10000 - steps, the cloth tasks from -1.0 m/s^2 over 20000 steps. -* Added terminations for a diverged solve (non-finite deformable or robot state) and for joint - velocities beyond the simulation limits, so unrecoverable environments reset instead of poisoning - the rollout. - -Changed -^^^^^^^ - -* **Breaking:** Changed the Franka deformable lift environments to select their action space - through a :class:`~isaaclab_tasks.utils.PresetCfg`, with ``joint`` (relative joint-position arm - targets plus a limit-rescaled gripper) as the new default. ``Isaac-Lift-Soft-Franka`` and - ``Isaac-Lift-Soft-Franka-Camera`` previously used absolute task-space differential inverse - kinematics, and ``Isaac-Lift-Cloth-Franka`` and ``Isaac-Lift-Cloth-Franka-Camera`` previously - used absolute joint-position targets. Append ``presets=ik`` to the run command to get the - task-space inverse-kinematics action space back. -* **Breaking:** Changed the rsl_rl ``experiment_name`` of ``Isaac-Lift-Soft-Franka`` and - ``Isaac-Lift-Cloth-Franka`` from ``franka_deformable`` to ``franka_soft``. New runs are written to - ``logs/rsl_rl/franka_soft``; move existing ``logs/rsl_rl/franka_deformable`` run directories there - to resume from an older checkpoint. -* Changed the Franka deformable lift environments to simulate under real gravity: gravity is no - longer disabled on the robot, and the vertical gravity of ``Isaac-Lift-Soft-Franka`` and - ``Isaac-Lift-Soft-Franka-Camera`` is no longer zeroed. -* Changed the simulation step of the Franka deformable lift environments from 1/60 s to 1/120 s - with a decimation of 4, which halves the policy control rate from 60 Hz to 30 Hz, and raised the - default number of environments of ``Isaac-Lift-Soft-Franka`` from 128 to 2048. -* Changed the table of the Franka deformable lift environments from the ``SeattleLabTable`` USD - asset to an invisible cuboid collider whose top surface sits at ``z = 0``. The goal command's - success visualizer draws the table instead, tinted by whether the goal is reached. -* Changed the rsl_rl PPO configuration of ``Isaac-Lift-Soft-Franka`` and - ``Isaac-Lift-Cloth-Franka`` to the actor/critic model configurations, using - :class:`~isaaclab_rl.rsl_rl.RslRlMLPModelCfg` with observation normalization and explicit - ``obs_groups``, a learning rate of 1e-3, and ``max_iterations`` lowered from 50000 to 5000. -* Re-tuned ``Isaac-Lift-Soft-Franka`` and ``Isaac-Lift-Soft-Franka-Camera`` for stable grasping: a - stiffer and denser beam, a smaller particle radius, explicit collider contact and rest offsets, - and full-surface rigid-soft contact. -* Changed the robot asset of the Franka deformable lift environments to the Menagerie - ``franka_panda.usda``, whose analytic gripper colliders support full-surface rigid-soft contact - directly without provisioning signed-distance fields. -* Re-tuned the Franka arm and hand actuator gains of the Franka deformable lift environments, adding - realistic armature and a slower, weaker gripper so it settles on the object instead of crushing - it. This replaces the previous per-task gripper overrides, so the cloth tasks now use the same - gains as the soft-beam tasks. -* Changed ``Isaac-Lift-Cloth-Franka`` to place the cloth over a kinematic rigid support that is - registered with the coupler, so the cloth no longer passes through it. From e9634defaf7d54c7500c5f3c13186127c88ab42a Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 4 Aug 2026 13:16:58 +0200 Subject: [PATCH 17/41] Fix: Use a consistent way of modifying gravity --- .../franka_soft/franka_cloth_env_cfg.py | 26 ++++++- .../config/franka_soft/franka_soft_env_cfg.py | 57 +++++++++++++- .../lift/config/franka_soft/mdp/__init__.pyi | 4 +- .../config/franka_soft/mdp/curriculums.py | 77 +++++-------------- 4 files changed, 96 insertions(+), 68 deletions(-) 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 277d3da69b8d..1ccd241470a2 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 @@ -204,11 +204,30 @@ class CurriculumCfg: # Since we use 24 steps per env, 20000 steps correspond to 20000/24 = 833.33 learning iterations gravity = CurrTerm( - func=mdp.modify_gravity_linear, - params={"start_gravity_z": -1.0, "end_gravity_z": -9.81, "start_step": 0, "end_step": 20000}, + func=mdp.modify_term_cfg, + params={ + "address": "events.variable_gravity.params.gravity_distribution_params", + "modify_fn": mdp.gravity_range_linear, + "modify_params": { + "start_gravity_z": -1.0, + "end_gravity_z": -9.81, + "start_step": 0, + "end_step": 20000, + }, + }, ) +@configclass +class CurriculumPresetCfg(PresetCfg): + """Preset config for Franka cloth curricula.""" + + newton_mjwarp_vbd_proxy: CurriculumCfg = CurriculumCfg() + ovphysx: CurriculumCfg = CurriculumCfg().replace(gravity=None) + + default = newton_mjwarp_vbd_proxy + + @configclass class FrankaClothEventCfg(FrankaSoftEventCfg): """Reset and startup events for the Franka cloth environment.""" @@ -242,6 +261,7 @@ def _make_ovphysx_event_cfg() -> FrankaClothEventCfg: """Create cloth events that select all robot shapes on OvPhysX.""" cfg = FrankaClothEventCfg() cfg.robot_physics_material.params["asset_cfg"] = SceneEntityCfg("robot") + cfg.variable_gravity = None return cfg @@ -266,7 +286,7 @@ class FrankaClothEnvCfg(FrankaSoftEnvCfg): scene: FrankaClothScenePresetCfg = FrankaClothScenePresetCfg() events: EventPresetCfg = EventPresetCfg() - curriculum: CurriculumCfg = CurriculumCfg() + curriculum: CurriculumPresetCfg = CurriculumPresetCfg() def __post_init__(self) -> None: super().__post_init__() 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 64c0e58f5d6a..df69c6649736 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 @@ -479,6 +479,15 @@ class EventCfg: }, ) + variable_gravity = EventTerm( + func=mdp.randomize_physics_scene_gravity, + mode="reset", + params={ + "gravity_distribution_params": ([0.0, 0.0, -9.81], [0.0, 0.0, -9.81]), + "operation": "abs", + }, + ) + @configclass class RewardsCfg: @@ -532,11 +541,46 @@ class CurriculumCfg: # Since we use 24 steps per env, 10000 steps correspond to 10000/24 = 416.67 learning iterations gravity = CurrTerm( - func=mdp.modify_gravity_linear, - params={"start_gravity_z": -0.0001, "end_gravity_z": -9.81, "start_step": 0, "end_step": 10000}, + func=mdp.modify_term_cfg, + params={ + "address": "events.variable_gravity.params.gravity_distribution_params", + "modify_fn": mdp.gravity_range_linear, + "modify_params": { + "start_gravity_z": -0.0001, + "end_gravity_z": -9.81, + "start_step": 0, + "end_step": 10000, + }, + }, ) +@configclass +class EventPresetCfg(PresetCfg): + """Backend presets for soft-lift events. Ovphysx does not support variable gravity, so the event is disabled.""" + + newton_mjwarp_vbd_proxy: EventCfg = EventCfg() + physx: EventCfg = EventCfg() + isaacsim_physx: EventCfg = EventCfg() + ovphysx: EventCfg = EventCfg() + ovphysx.variable_gravity = None + + default = newton_mjwarp_vbd_proxy + + +@configclass +class CurriculumPresetCfg(PresetCfg): + """Backend presets that omit OVPhysX gravity scheduling.""" + + newton_mjwarp_vbd_proxy: CurriculumCfg = CurriculumCfg() + physx: CurriculumCfg = CurriculumCfg() + isaacsim_physx: CurriculumCfg = CurriculumCfg() + ovphysx: CurriculumCfg = CurriculumCfg() + ovphysx.gravity = None + + default = newton_mjwarp_vbd_proxy + + @configclass class TerminationsCfg: """Time out + workspace bounds termination.""" @@ -609,8 +653,8 @@ class FrankaSoftEnvCfg(ManagerBasedRLEnvCfg): # MDP settings rewards: RewardsCfg = RewardsCfg() terminations: TerminationsCfg = TerminationsCfg() - events: EventCfg = EventCfg() - curriculum: CurriculumCfg = CurriculumCfg() + events: EventPresetCfg = EventPresetCfg() + curriculum: CurriculumPresetCfg = CurriculumPresetCfg() def __post_init__(self) -> None: # general settings @@ -630,6 +674,11 @@ def __post_init__(self) -> None: self.video_recorder.window_width = 1920 self.video_recorder.window_height = 1080 + def play_mode(self): + super().play_mode() + if self.curriculum is not None: + self.curriculum.gravity = None + @configclass class FrankaSoftCameraEnvCfg(FrankaSoftEnvCfg): diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/__init__.pyi b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/__init__.pyi index 0cf4f671172e..880aab6a9e20 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/__init__.pyi +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/__init__.pyi @@ -25,10 +25,10 @@ __all__ = [ "DeformableUniformPoseCommand", "DeformableUniformPoseCommandCfg", # curriculums - "modify_gravity_linear", + "gravity_range_linear", ] -from .curriculums import modify_gravity_linear +from .curriculums import gravity_range_linear from .events import randomize_deformable_material, reset_deformable_over_support from .observations import ( DeformableSampledPointsInRobotRootFrame, diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/curriculums.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/curriculums.py index 6c89fb35da09..34c4b25b5475 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/curriculums.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/curriculums.py @@ -10,66 +10,25 @@ from collections.abc import Sequence from typing import TYPE_CHECKING -import isaaclab.sim as sim_utils -from isaaclab.managers import CurriculumTermCfg, ManagerTermBase - if TYPE_CHECKING: from isaaclab.envs import ManagerBasedRLEnv -class modify_gravity_linear(ManagerTermBase): - """Curriculum that linearly ramps the vertical gravity toward its target value. - - The vertical gravity component is interpolated from :paramref:`start_gravity_z` to - :paramref:`end_gravity_z` [m/s^2] as the global step counter advances from - :paramref:`start_step` to :paramref:`end_step`, then held constant. This lets the policy - first learn under near-weightless dynamics before full gravity is applied. - - The active physics backend is detected automatically (PhysX or Newton). - """ - - def __init__(self, cfg: CurriculumTermCfg, env: ManagerBasedRLEnv): - super().__init__(cfg, env) - - manager_name = env.sim.physics_manager.__name__.lower() - if "newton" in manager_name: - self._backend = "newton" - import isaaclab_newton.physics.newton_manager as newton_manager_module # noqa: PLC0415 - from newton import ModelFlags # noqa: PLC0415 - - self._newton_manager = newton_manager_module.NewtonManager - self._notify_model_properties = ModelFlags.MODEL_PROPERTIES - else: - self._backend = "physx" - import carb # noqa: PLC0415 - - self._carb = carb - self._physics_sim_view = sim_utils.SimulationContext.instance().physics_sim_view - - def __call__( - self, - env: ManagerBasedRLEnv, - env_ids: Sequence[int], - start_gravity_z: float, - end_gravity_z: float, - start_step: int, - end_step: int, - ) -> float: - # linearly interpolate the vertical gravity based on training progress - alpha = (env.common_step_counter - start_step) / max(end_step - start_step, 1) - alpha = min(max(alpha, 0.0), 1.0) - gravity_z = start_gravity_z + alpha * (end_gravity_z - start_gravity_z) - - if self._backend == "newton": - import warp as wp # noqa: PLC0415 - - model = self._newton_manager.get_model() - if model is None or model.gravity is None: - raise RuntimeError("Newton model is not initialized. Cannot modify gravity.") - # write to all worlds so gravity stays consistent regardless of per-env reset timing - wp.to_torch(model.gravity)[:, 2] = gravity_z - self._newton_manager.add_model_change(self._notify_model_properties) - else: - self._physics_sim_view.set_gravity(self._carb.Float3(0.0, 0.0, gravity_z)) - - return gravity_z +def gravity_range_linear( + env: ManagerBasedRLEnv, + _env_ids: Sequence[int], + _value: tuple[list[float], list[float]], + start_gravity_z: float, + end_gravity_z: float, + start_step: int, + end_step: int, +) -> tuple[list[float], list[float]]: + """Linearly interpolate deterministic vertical gravity bounds [m/s^2].""" + if end_step <= start_step: + raise ValueError("end_step must be greater than start_step.") + + alpha = (env.common_step_counter - start_step) / (end_step - start_step) + alpha = min(max(alpha, 0.0), 1.0) + gravity_z = start_gravity_z + alpha * (end_gravity_z - start_gravity_z) + gravity = [0.0, 0.0, gravity_z] + return gravity, gravity.copy() From 96dba265bce34087a623bcc5a7539bf87a20bcaa Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 4 Aug 2026 14:56:33 +0200 Subject: [PATCH 18/41] Fix: Remove ovphysx since it does not support the new franka asset and requires workaround for the gravity curriculum --- .../source/_static/css/environment-browser.js | 8 +- .../newton/using-vbd-solver.rst | 2 +- docs/source/overview/environments.rst | 20 +-- .../changelog.d/mym-lift-soft.skip | 0 .../tasks/test_lift_franka_soft_deformable.py | 145 ------------------ .../changelog.d/mym-lift-soft.major.rst | 6 + .../franka_soft/franka_cloth_env_cfg.py | 61 +------- .../config/franka_soft/franka_soft_env_cfg.py | 39 +---- .../test_franka_deformable_ovphysx_cfg.py | 78 ---------- 9 files changed, 25 insertions(+), 334 deletions(-) create mode 100644 source/isaaclab_ovphysx/changelog.d/mym-lift-soft.skip delete mode 100644 source/isaaclab_ovphysx/test/tasks/test_lift_franka_soft_deformable.py delete mode 100644 source/isaaclab_tasks/test/core/test_franka_deformable_ovphysx_cfg.py diff --git a/docs/source/_static/css/environment-browser.js b/docs/source/_static/css/environment-browser.js index 1924f1ce46c0..93fcd005568d 100644 --- a/docs/source/_static/css/environment-browser.js +++ b/docs/source/_static/css/environment-browser.js @@ -21,13 +21,13 @@ ["Isaac-Fourbar-Pole-Swingup", "rsl_rl", "newton_kamino", "", ""], ["Isaac-Humanoid", "rl_games,rsl_rl,skrl,sb3", "isaacsim_physx,newton_mjwarp", "", ""], ["Isaac-Humanoid-Direct", "rl_games,rsl_rl,skrl", "isaacsim_physx,newton_mjwarp,ovphysx", "", ""], - ["Isaac-Lift-Cloth-Franka", "rsl_rl", "newton_mjwarp_vbd_proxy,ovphysx", "", "ik,joint"], - ["Isaac-Lift-Cloth-Franka-Camera", "rsl_rl", "newton_mjwarp_vbd_proxy,ovphysx", "isaacsim_rtx,newton_renderer,ovrtx", "ik,joint"], + ["Isaac-Lift-Cloth-Franka", "rsl_rl", "newton_mjwarp_vbd_proxy", "", "ik,joint"], + ["Isaac-Lift-Cloth-Franka-Camera", "rsl_rl", "newton_mjwarp_vbd_proxy", "isaacsim_rtx,newton_renderer,ovrtx", "ik,joint"], ["Isaac-Lift-Franka", "rsl_rl", "isaacsim_physx,newton_mjwarp", "", "cube,shapes"], ["Isaac-Lift-KukaAllegro", "rsl_rl", "isaacsim_physx,newton_mjwarp,ovphysx", "", "cube,shapes"], ["Isaac-Lift-KukaAllegro-Camera", "rsl_rl", "isaacsim_physx,newton_mjwarp,ovphysx", "isaacsim_rtx,newton_renderer,ovrtx", "albedo128,albedo256,albedo64,cube,depth128,depth256,depth64,duo_camera,raycaster_depth128,raycaster_depth256,raycaster_depth64,rgb128,rgb256,rgb64,semantic_segmentation128,semantic_segmentation256,semantic_segmentation64,shapes,simple_shading_constant_diffuse128,simple_shading_constant_diffuse256,simple_shading_constant_diffuse64,simple_shading_diffuse_mdl128,simple_shading_diffuse_mdl256,simple_shading_diffuse_mdl64,simple_shading_full_mdl128,simple_shading_full_mdl256,simple_shading_full_mdl64,single_camera"], - ["Isaac-Lift-Soft-Franka", "rsl_rl", "isaacsim_physx,newton_mjwarp_vbd_proxy,ovphysx", "", "ik,joint"], - ["Isaac-Lift-Soft-Franka-Camera", "rsl_rl", "isaacsim_physx,newton_mjwarp_vbd_proxy,ovphysx", "isaacsim_rtx,newton_renderer,ovrtx", "ik,joint"], + ["Isaac-Lift-Soft-Franka", "rsl_rl", "isaacsim_physx,newton_mjwarp_vbd_proxy", "", "ik,joint"], + ["Isaac-Lift-Soft-Franka-Camera", "rsl_rl", "isaacsim_physx,newton_mjwarp_vbd_proxy", "isaacsim_rtx,newton_renderer,ovrtx", "ik,joint"], ["Isaac-Open-Drawer-Franka", "rl_games,rsl_rl,skrl", "", "", ""], ["Isaac-Open-Drawer-Franka-Direct", "rl_games,rsl_rl,skrl", "isaacsim_physx,newton_mjwarp,ovphysx", "", ""], ["Isaac-Pendulum-Direct", "rl_games,skrl", "", "", ""], 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 6f5b46bcb912..9faf0758f78b 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 @@ -272,7 +272,7 @@ The core Franka soft-body task demonstrates the proxy configuration: .. literalinclude:: ../../../../../../source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/franka_soft_env_cfg.py :language: python :start-at: newton_mjwarp_vbd_proxy: NewtonCfg - :end-before: isaacsim_physx: PhysxCfg = PhysxCfg() + :end-before: isaacsim_physx: PhysxCfg = PhysxCfg( :dedent: 4 What the selectors do: diff --git a/docs/source/overview/environments.rst b/docs/source/overview/environments.rst index 0b927494af77..c2f1882fe427 100644 --- a/docs/source/overview/environments.rst +++ b/docs/source/overview/environments.rst @@ -232,25 +232,21 @@ for the lift-cube environment: | |lift-cube| | |lift-cube-link| | Pick a cube and bring it to a sampled target position with the Franka robot | | +-------------------------+------------------------------+-----------------------------------------------------------------------------+-----------------------------------------+ | |lift-soft-franka| | |lift-soft-franka-link| | Pick a deformable soft body and bring it to a sampled target position with | **physics=** ``isaacsim_physx``, | - | | | the Franka robot | ``newton_mjwarp_vbd_proxy``, | - | | | | ``ovphysx`` | + | | | the Franka robot | ``newton_mjwarp_vbd_proxy`` | | | | | **presets=** ``ik``, ``joint`` | +-------------------------+------------------------------+-----------------------------------------------------------------------------+-----------------------------------------+ | |lift-soft-franka| | |lift-soft-franka-cam-link| | Camera (vision) variant of the soft-body lift task using RGB observations | **physics=** ``isaacsim_physx``, | - | | | | ``newton_mjwarp_vbd_proxy``, | - | | | | ``ovphysx`` | + | | | | ``newton_mjwarp_vbd_proxy`` | | | | | **renderer=** ``isaacsim_rtx``, | | | | | ``newton_renderer``, ``ovrtx`` | | | | | **presets=** ``ik``, ``joint`` | +-------------------------+------------------------------+-----------------------------------------------------------------------------+-----------------------------------------+ | |lift-cloth-franka| | |lift-cloth-franka-link| | Lift a deformable cloth from a table with the Franka robot | **physics=** | - | | | | ``newton_mjwarp_vbd_proxy``, | - | | | | ``ovphysx`` | + | | | | ``newton_mjwarp_vbd_proxy`` | | | | | **presets=** ``ik``, ``joint`` | +-------------------------+------------------------------+-----------------------------------------------------------------------------+-----------------------------------------+ | |lift-cloth-franka| | |lift-cloth-franka-cam-link| | Camera (vision) variant of the cloth lift task using RGB observations | **physics=** | - | | | | ``newton_mjwarp_vbd_proxy``, | - | | | | ``ovphysx`` | + | | | | ``newton_mjwarp_vbd_proxy`` | | | | | **renderer=** ``isaacsim_rtx``, | | | | | ``newton_renderer``, ``ovrtx`` | | | | | **presets=** ``ik``, ``joint`` | @@ -1097,12 +1093,12 @@ including disabling runtime perturbations used for training. * - Isaac-Lift-Cloth-Franka - Manager Based - **rsl_rl** (PPO) - - | **physics=** ``newton_mjwarp_vbd_proxy``, ``ovphysx`` + - | **physics=** ``newton_mjwarp_vbd_proxy`` | **presets=** ``ik``, ``joint`` * - Isaac-Lift-Cloth-Franka-Camera - Manager Based - **rsl_rl** (PPO) - - | **physics=** ``newton_mjwarp_vbd_proxy``, ``ovphysx`` + - | **physics=** ``newton_mjwarp_vbd_proxy`` | **renderer=** ``isaacsim_rtx``, ``newton_renderer``, ``ovrtx`` | **presets=** ``ik``, ``joint`` * - Isaac-Lift-Franka @@ -1124,12 +1120,12 @@ including disabling runtime perturbations used for training. * - Isaac-Lift-Soft-Franka - Manager Based - **rsl_rl** (PPO) - - | **physics=** ``isaacsim_physx``, ``newton_mjwarp_vbd_proxy``, ``ovphysx`` + - | **physics=** ``isaacsim_physx``, ``newton_mjwarp_vbd_proxy`` | **presets=** ``ik``, ``joint`` * - Isaac-Lift-Soft-Franka-Camera - Manager Based - **rsl_rl** (PPO) - - | **physics=** ``isaacsim_physx``, ``newton_mjwarp_vbd_proxy``, ``ovphysx`` + - | **physics=** ``isaacsim_physx``, ``newton_mjwarp_vbd_proxy`` | **renderer=** ``isaacsim_rtx``, ``newton_renderer``, ``ovrtx`` | **presets=** ``ik``, ``joint`` * - Isaac-Open-Drawer-Franka diff --git a/source/isaaclab_ovphysx/changelog.d/mym-lift-soft.skip b/source/isaaclab_ovphysx/changelog.d/mym-lift-soft.skip new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/source/isaaclab_ovphysx/test/tasks/test_lift_franka_soft_deformable.py b/source/isaaclab_ovphysx/test/tasks/test_lift_franka_soft_deformable.py deleted file mode 100644 index acde2b54cc9a..000000000000 --- a/source/isaaclab_ovphysx/test/tasks/test_lift_franka_soft_deformable.py +++ /dev/null @@ -1,145 +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 - -"""Task-level smoke tests for OVPhysX volume and surface deformables.""" - -from __future__ import annotations - -import gymnasium as gym -import ovphysx.types # noqa: F401 -import pytest -import torch -import warp as wp -from isaaclab_ovphysx import tensor_types as TT # noqa: E402 - -from isaaclab.sim import SimulationContext # noqa: E402 - -import isaaclab_tasks # noqa: F401, E402 -from isaaclab_tasks.core.lift.config.franka_soft.franka_cloth_env_cfg import FrankaClothEnvCfg # noqa: E402 -from isaaclab_tasks.core.lift.config.franka_soft.franka_soft_env_cfg import FrankaSoftEnvCfg # noqa: E402 -from isaaclab_tasks.utils.hydra import resolve_presets # noqa: E402 - -wp.init() - -_NUM_ENVS = 2 - - -def _configure_deformable_lift_ovphysx_smoke( - cfg_cls: type[FrankaSoftEnvCfg], -) -> FrankaSoftEnvCfg: - """Build a minimal multi-environment OvPhysX deformable-lift task.""" - cfg = resolve_presets(cfg_cls(), ("ovphysx",)) - cfg.sim.device = "cuda:0" - cfg.scene.num_envs = _NUM_ENVS - - # Keep these smokes focused on the stock task deformable and shared MDP data - # path while avoiding unrelated external props. - cfg.scene.table = None - cfg.scene.sky_light = None - cfg.scene.ground = None - cfg.commands.deformable_pose.debug_vis = False - cfg.ui_window_class_type = None - return cfg - - -@pytest.mark.skipif(not torch.cuda.is_available(), reason="OVPhysX deformables require CUDA") -@pytest.mark.isaacsim_ci -def test_lift_franka_soft_task_reads_and_steps_volume_deformable(): - """Reset and step finite soft-lift observations and deformable state.""" - env = None - try: - cfg = _configure_deformable_lift_ovphysx_smoke(FrankaSoftEnvCfg) - env = gym.make("Isaac-Lift-Soft-Franka", cfg=cfg) - env.unwrapped.sim._app_control_on_stop_handle = None - - obs, _ = env.reset() - policy_obs = obs["policy"] - assert policy_obs.shape[0] == _NUM_ENVS - assert torch.isfinite(policy_obs).all() - - deformable = env.unwrapped.scene["deformable"] - assert deformable.is_initialized - assert deformable.num_instances == _NUM_ENVS - assert deformable.max_sim_vertices_per_body > 0 - assert torch.isfinite(deformable.data.nodal_state_w.torch).all() - - ee_frame = env.unwrapped.scene["ee_frame"] - assert torch.isfinite(ee_frame.data.target_pos_w.torch).all() - - targets = deformable.data.nodal_kinematic_target - assert targets is not None - expected_targets = targets.torch.clone() - updated_targets = expected_targets[:1].clone() - updated_targets[..., 3] = 1.0 - updated_targets[:, :, :3] = deformable.data.nodal_pos_w.torch[:1] + torch.tensor( - [0.0, 0.0, 0.03], device=env.unwrapped.device - ) - updated_targets[:, :, 3] = 0.0 - deformable.write_nodal_kinematic_target_to_sim_index( - updated_targets, env_ids=torch.tensor([0], device=env.unwrapped.device) - ) - expected_targets[0] = updated_targets[0] - readback_targets = wp.to_torch(deformable.root_view.get_attribute(TT.DEFORMABLE_SIM_KINEMATIC_TARGET)) - torch.testing.assert_close(readback_targets, expected_targets, rtol=1e-5, atol=1e-5) - - arm_action = env.unwrapped.action_manager.get_term("arm_action") - ee_pos_curr, ee_quat_curr = arm_action._compute_frame_pose() - actions = torch.zeros(env.action_space.shape, device=env.unwrapped.device) - actions[:, :3] = ee_pos_curr - actions[:, 3:7] = ee_quat_curr - for _ in range(3): - obs, reward, terminated, time_out, _ = env.step(actions) - assert torch.isfinite(obs["policy"]).all() - assert torch.isfinite(reward).all() - assert torch.isfinite(deformable.data.nodal_state_w.torch).all() - assert torch.isfinite(deformable.data.root_pos_w.torch).all() - assert torch.isfinite(ee_frame.data.target_pos_w.torch).all() - assert not terminated.any() - assert not time_out.any() - finally: - try: - if env is not None: - env.close() - finally: - SimulationContext.clear_instance() - - -@pytest.mark.skipif(not torch.cuda.is_available(), reason="OVPhysX deformables require CUDA") -@pytest.mark.isaacsim_ci -def test_lift_franka_cloth_task_reads_and_steps_surface_deformable(): - """Reset and step finite cloth-lift observations and deformable state.""" - env = None - try: - cfg = _configure_deformable_lift_ovphysx_smoke(FrankaClothEnvCfg) - env = gym.make("Isaac-Lift-Cloth-Franka", cfg=cfg) - env.unwrapped.sim._app_control_on_stop_handle = None - - obs, _ = env.reset() - assert obs["policy"].shape[0] == _NUM_ENVS - assert torch.isfinite(obs["policy"]).all() - - deformable = env.unwrapped.scene["deformable"] - assert deformable.is_initialized - assert deformable.num_instances == _NUM_ENVS - assert deformable.max_sim_vertices_per_body > 0 - assert deformable.data.nodal_kinematic_target is None - assert torch.isfinite(deformable.data.nodal_state_w.torch).all() - assert torch.isfinite(deformable.data.root_pos_w.torch).all() - - actions = torch.zeros(env.action_space.shape, device=env.unwrapped.device) - for _ in range(3): - obs, reward, terminated, time_out, _ = env.step(actions) - assert torch.isfinite(obs["policy"]).all() - assert torch.isfinite(reward).all() - assert torch.isfinite(deformable.data.nodal_state_w.torch).all() - assert torch.isfinite(deformable.data.root_pos_w.torch).all() - assert not terminated.any() - assert not time_out.any() - finally: - try: - if env is not None: - env.close() - finally: - SimulationContext.clear_instance() diff --git a/source/isaaclab_tasks/changelog.d/mym-lift-soft.major.rst b/source/isaaclab_tasks/changelog.d/mym-lift-soft.major.rst index a10bad341739..72bbdfeadc07 100644 --- a/source/isaaclab_tasks/changelog.d/mym-lift-soft.major.rst +++ b/source/isaaclab_tasks/changelog.d/mym-lift-soft.major.rst @@ -14,3 +14,9 @@ Changed ``franka_soft``. Update log and checkpoint paths that refer to ``logs/rsl_rl/franka_deformable``. * Re-tuned the robot, scenes, contact handling, control rate, and ``rsl_rl`` configuration for stable gravity-based training across the supported physics backends. + +Removed +^^^^^^^ + +* **Breaking:** Removed the unsupported ``ovphysx`` preset from the Franka soft-beam and cloth lift + environments. Use ``isaacsim_physx`` for the soft-beam task or ``newton_mjwarp_vbd_proxy`` for either task. 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 1ccd241470a2..7e659d70af32 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 @@ -10,9 +10,6 @@ from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg from isaaclab_newton.sim.schemas import NewtonDeformableBodyPropertiesCfg from isaaclab_newton.sim.spawners.materials import NewtonSurfaceDeformableBodyMaterialCfg -from isaaclab_ovphysx.physics import OvPhysxCfg -from isaaclab_physx.sim.schemas import PhysxDeformableBodyPropertiesCfg -from isaaclab_physx.sim.spawners.materials import PhysxSurfaceDeformableBodyMaterialCfg import isaaclab.sim as sim_utils from isaaclab.assets import RigidObjectCfg @@ -91,8 +88,6 @@ class PhysicsCfg(PresetCfg): num_substeps=2, ) - ovphysx: OvPhysxCfg = OvPhysxCfg() - default = newton_mjwarp_vbd_proxy @@ -120,28 +115,6 @@ class DeformableCfg(PresetCfg): ), ) - ovphysx: DeformableObjectCfg = DeformableObjectCfg( - prim_path="{ENV_REGEX_NS}/Deformable", - init_state=DeformableObjectCfg.InitialStateCfg(pos=(0.4, 0.0, 0.2)), - spawn=sim_utils.MeshRectangleCfg( - size=(0.2, 0.2), - resolution=(30, 30), - deformable_props=PhysxDeformableBodyPropertiesCfg(), - visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.95, 0.85, 0.1)), - physics_material=PhysxSurfaceDeformableBodyMaterialCfg( - density=50.0, - youngs_modulus=2000.0, - poissons_ratio=0.25, - surface_thickness=0.005, - surface_stretch_stiffness=0.8, - surface_shear_stiffness=0.7, - surface_bend_stiffness=0.6, - elasticity_damping=0.03, - bend_damping=0.04, - ), - ), - ) - default = newton_mjwarp_vbd_proxy @@ -182,8 +155,6 @@ class FrankaClothScenePresetCfg(PresetCfg): num_envs=128, env_spacing=2.5, replicate_physics=True ) - ovphysx: FrankaClothSceneCfg = FrankaClothSceneCfg(num_envs=128, env_spacing=2.5, replicate_physics=True) - default = newton_mjwarp_vbd_proxy @@ -218,16 +189,6 @@ class CurriculumCfg: ) -@configclass -class CurriculumPresetCfg(PresetCfg): - """Preset config for Franka cloth curricula.""" - - newton_mjwarp_vbd_proxy: CurriculumCfg = CurriculumCfg() - ovphysx: CurriculumCfg = CurriculumCfg().replace(gravity=None) - - default = newton_mjwarp_vbd_proxy - - @configclass class FrankaClothEventCfg(FrankaSoftEventCfg): """Reset and startup events for the Franka cloth environment.""" @@ -257,24 +218,6 @@ class FrankaClothEventCfg(FrankaSoftEventCfg): ) -def _make_ovphysx_event_cfg() -> FrankaClothEventCfg: - """Create cloth events that select all robot shapes on OvPhysX.""" - cfg = FrankaClothEventCfg() - cfg.robot_physics_material.params["asset_cfg"] = SceneEntityCfg("robot") - cfg.variable_gravity = None - return cfg - - -@configclass -class EventPresetCfg(PresetCfg): - """Preset config for Franka cloth startup and reset events.""" - - newton_mjwarp_vbd_proxy: FrankaClothEventCfg = FrankaClothEventCfg() - ovphysx: FrankaClothEventCfg = _make_ovphysx_event_cfg() - - default = newton_mjwarp_vbd_proxy - - ## # Environment configuration ## @@ -285,8 +228,8 @@ class FrankaClothEnvCfg(FrankaSoftEnvCfg): """Manager-based RL environment: Franka Panda lifting a surface deformable.""" scene: FrankaClothScenePresetCfg = FrankaClothScenePresetCfg() - events: EventPresetCfg = EventPresetCfg() - curriculum: CurriculumPresetCfg = CurriculumPresetCfg() + events: FrankaClothEventCfg = FrankaClothEventCfg() + curriculum: CurriculumCfg = CurriculumCfg() def __post_init__(self) -> None: super().__post_init__() 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 df69c6649736..51c90a994f74 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 @@ -14,7 +14,6 @@ ) from isaaclab_newton.sim.schemas import NewtonDeformableBodyPropertiesCfg from isaaclab_newton.sim.spawners.materials import NewtonDeformableBodyMaterialCfg -from isaaclab_ovphysx.physics import OvPhysxCfg from isaaclab_physx.physics import PhysxCfg from isaaclab_physx.sim.schemas import PhysxCollisionCfg, PhysxDeformableBodyPropertiesCfg from isaaclab_physx.sim.spawners.materials import PhysxDeformableBodyMaterialCfg @@ -137,8 +136,6 @@ class DeformableCfg(PresetCfg): ) isaacsim_physx = physx - ovphysx: DeformableObjectCfg = physx - default = newton_mjwarp_vbd_proxy @@ -187,8 +184,8 @@ class PhysicsCfg(PresetCfg): friction_offset_threshold=0.005, friction_correlation_distance=0.01, ) - ovphysx: OvPhysxCfg = OvPhysxCfg() - physx: PhysxAutoCfg = PhysxAutoCfg(isaacsim_physx=isaacsim_physx, ovphysx=ovphysx) + + physx: PhysxAutoCfg = PhysxAutoCfg(isaacsim_physx=isaacsim_physx) default = newton_mjwarp_vbd_proxy @@ -555,32 +552,6 @@ class CurriculumCfg: ) -@configclass -class EventPresetCfg(PresetCfg): - """Backend presets for soft-lift events. Ovphysx does not support variable gravity, so the event is disabled.""" - - newton_mjwarp_vbd_proxy: EventCfg = EventCfg() - physx: EventCfg = EventCfg() - isaacsim_physx: EventCfg = EventCfg() - ovphysx: EventCfg = EventCfg() - ovphysx.variable_gravity = None - - default = newton_mjwarp_vbd_proxy - - -@configclass -class CurriculumPresetCfg(PresetCfg): - """Backend presets that omit OVPhysX gravity scheduling.""" - - newton_mjwarp_vbd_proxy: CurriculumCfg = CurriculumCfg() - physx: CurriculumCfg = CurriculumCfg() - isaacsim_physx: CurriculumCfg = CurriculumCfg() - ovphysx: CurriculumCfg = CurriculumCfg() - ovphysx.gravity = None - - default = newton_mjwarp_vbd_proxy - - @configclass class TerminationsCfg: """Time out + workspace bounds termination.""" @@ -623,8 +594,6 @@ class FrankaSoftSceneCfg(PresetCfg): physx: _FrankaSoftSceneCfg = _FrankaSoftSceneCfg(num_envs=2048, env_spacing=2.0, replicate_physics=False) isaacsim_physx = physx - ovphysx: _FrankaSoftSceneCfg = _FrankaSoftSceneCfg(num_envs=128, env_spacing=2.5, replicate_physics=True) - default = newton_mjwarp_vbd_proxy @@ -653,8 +622,8 @@ class FrankaSoftEnvCfg(ManagerBasedRLEnvCfg): # MDP settings rewards: RewardsCfg = RewardsCfg() terminations: TerminationsCfg = TerminationsCfg() - events: EventPresetCfg = EventPresetCfg() - curriculum: CurriculumPresetCfg = CurriculumPresetCfg() + events: EventCfg = EventCfg() + curriculum: CurriculumCfg = CurriculumCfg() def __post_init__(self) -> None: # general settings diff --git a/source/isaaclab_tasks/test/core/test_franka_deformable_ovphysx_cfg.py b/source/isaaclab_tasks/test/core/test_franka_deformable_ovphysx_cfg.py deleted file mode 100644 index 7ae19992fda8..000000000000 --- a/source/isaaclab_tasks/test/core/test_franka_deformable_ovphysx_cfg.py +++ /dev/null @@ -1,78 +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 - -"""Sim-free regression tests for the Franka deformable OvPhysX presets.""" - -import pytest -from isaaclab_ovphysx.physics import OvPhysxCfg -from isaaclab_physx.sim.schemas import PhysxDeformableBodyPropertiesCfg -from isaaclab_physx.sim.spawners.materials import ( - PhysxDeformableBodyMaterialCfg, - PhysxSurfaceDeformableBodyMaterialCfg, -) - -from isaaclab_tasks.core.lift.config.franka_soft.franka_cloth_env_cfg import ( - FrankaClothCameraEnvCfg, - FrankaClothEnvCfg, -) -from isaaclab_tasks.core.lift.config.franka_soft.franka_soft_env_cfg import FrankaSoftCameraEnvCfg, FrankaSoftEnvCfg -from isaaclab_tasks.utils.hydra import resolve_presets - - -def test_soft_task_ovphysx_preset_selects_complete_authored_scene(): - """Test that the soft-task OvPhysX preset selects a fully authored scene.""" - cfg = resolve_presets(FrankaSoftEnvCfg(), ("ovphysx",)) - - assert isinstance(cfg.sim.physics, OvPhysxCfg) - assert cfg.scene.replicate_physics is True - assert isinstance(cfg.scene.deformable.spawn.deformable_props, PhysxDeformableBodyPropertiesCfg) - assert isinstance(cfg.scene.deformable.spawn.physics_material, PhysxDeformableBodyMaterialCfg) - - -def test_cloth_task_ovphysx_preset_selects_complete_authored_scene(): - """Test that the cloth-task OvPhysX preset selects a fully authored scene.""" - cfg = resolve_presets(FrankaClothEnvCfg(), ("ovphysx",)) - - assert isinstance(cfg.sim.physics, OvPhysxCfg) - assert cfg.scene.replicate_physics is True - assert isinstance(cfg.scene.deformable.spawn.deformable_props, PhysxDeformableBodyPropertiesCfg) - assert isinstance(cfg.scene.deformable.spawn.physics_material, PhysxSurfaceDeformableBodyMaterialCfg) - assert cfg.events.robot_physics_material.params["asset_cfg"].body_names is None - - -@pytest.mark.parametrize( - ("env_cfg_type", "material_type"), - [ - (FrankaSoftCameraEnvCfg, PhysxDeformableBodyMaterialCfg), - (FrankaClothCameraEnvCfg, PhysxSurfaceDeformableBodyMaterialCfg), - ], -) -def test_camera_task_ovphysx_preset_selects_complete_authored_scene(env_cfg_type, material_type): - """Test that each camera-task OvPhysX preset selects a fully authored scene.""" - cfg = resolve_presets(env_cfg_type(), ("ovphysx",)) - - assert isinstance(cfg.sim.physics, OvPhysxCfg) - assert cfg.scene.replicate_physics is True - assert isinstance(cfg.scene.deformable.spawn.deformable_props, PhysxDeformableBodyPropertiesCfg) - assert isinstance(cfg.scene.deformable.spawn.physics_material, material_type) - - -def test_cloth_rendering_variant_applies_deterministic_overrides(): - """Test that the rendering test applies overrides after preset resolution.""" - from rendering_test_utils import _configure_franka_camera_test_env_cfg - - expected_range = { - "x": (0.0, 0.0), - "y": (0.0, 0.0), - "z": (0.0, 0.0), - } - - cfg = resolve_presets(FrankaClothCameraEnvCfg(), ("ovphysx",)) - _configure_franka_camera_test_env_cfg(cfg, "rgb") - - assert cfg.scene.num_envs == 4 - assert cfg.scene.replicate_physics is True - assert cfg.scene.base_camera.data_types == ["rgb"] - assert cfg.events.reset_deformable.params["position_range"] == expected_range From 2c1704feabf229a62b2ee8968f37afc520db18cd Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 4 Aug 2026 15:12:06 +0200 Subject: [PATCH 19/41] Style: Remove unnecessary event on material randomiztion --- .../lift/config/franka_soft/mdp/__init__.pyi | 3 +- .../lift/config/franka_soft/mdp/events.py | 115 ------------------ 2 files changed, 1 insertion(+), 117 deletions(-) diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/__init__.pyi b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/__init__.pyi index 880aab6a9e20..88695b013b88 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/__init__.pyi +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/__init__.pyi @@ -19,7 +19,6 @@ __all__ = [ "deformable_outside_bounds", "joint_vel_out_of_sim_limit", # events - "randomize_deformable_material", "reset_deformable_over_support", # commands "DeformableUniformPoseCommand", @@ -29,7 +28,7 @@ __all__ = [ ] from .curriculums import gravity_range_linear -from .events import randomize_deformable_material, reset_deformable_over_support +from .events import reset_deformable_over_support from .observations import ( DeformableSampledPointsInRobotRootFrame, deformable_com_in_robot_root_frame, diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/events.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/events.py index 89eb1d4cd3f3..05bdda48800a 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/events.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/events.py @@ -10,7 +10,6 @@ from typing import TYPE_CHECKING import torch -import warp as wp from isaaclab.managers import SceneEntityCfg from isaaclab.utils import math as math_utils @@ -20,120 +19,6 @@ from isaaclab.envs import ManagerBasedEnv -@wp.kernel -def _write_tet_materials( - tet_indices: wp.array2d(dtype=wp.int32), - particle_offset0: wp.int32, - particles_per_body: wp.int32, - k_mu: wp.array(dtype=wp.float32), - k_lambda: wp.array(dtype=wp.float32), - tet_materials: wp.array2d(dtype=wp.float32), -): - """Write per-env Lame parameters into the shared tet-material array (leaves k_damp untouched).""" - t = wp.tid() - # Map the tet to its env via its first particle index (contiguous under replicate_physics). - e = (tet_indices[t, 0] - particle_offset0) // particles_per_body - tet_materials[t, 0] = k_mu[e] - tet_materials[t, 1] = k_lambda[e] - - -@wp.kernel -def _scale_particle_mass( - offsets: wp.array(dtype=wp.int32), - density_scale: wp.array(dtype=wp.float32), - spawn_mass: wp.array(dtype=wp.float32), - particle_mass: wp.array(dtype=wp.float32), - particle_inv_mass: wp.array(dtype=wp.float32), -): - """Scale free particle masses by the per-env density ratio; skip kinematic particles.""" - e, j = wp.tid() - flat_idx = offsets[e] + j - if particle_inv_mass[flat_idx] == 0.0: - return - m = spawn_mass[flat_idx] * density_scale[e] - particle_mass[flat_idx] = m - particle_inv_mass[flat_idx] = 1.0 / m - - -def randomize_deformable_material( - env: ManagerBasedEnv, - env_ids: torch.Tensor | None, - asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), - youngs_modulus_range: tuple[float, float] = (7e4, 5e5), - density_range: tuple[float, float] = (100.0, 1000.0), - poissons_ratio: float = 0.25, -) -> None: - """Randomize the deformable object's material stiffness and density per environment. - - Startup event that samples a Young's modulus and density independently for every - environment instance and writes the corresponding Lame parameters into the Newton - tetrahedral materials and the particle masses. The tetrahedral damping term - ``k_damp`` is preserved. Kinematic particles keep their infinite mass. - - Args: - env: The environment instance. - env_ids: Unused; all instances are randomized at startup. - asset_cfg: Scene entity of the deformable object to randomize. - youngs_modulus_range: Sampling bounds for the Young's modulus [Pa]. - density_range: Sampling bounds for the mass density [kg/m^3]. - poissons_ratio: Poisson's ratio [dimensionless] used to convert to Lame parameters. - """ - # Imported here, not at module scope: pulling ``newton`` imports ``pxr`` and a second USD - # runtime, which must not happen before the Kit app has started. - from isaaclab_newton.physics import NewtonManager - from newton import ModelFlags - - asset = env.scene[asset_cfg.name] - device = env.device - num_instances = asset.num_instances - - model = NewtonManager.get_model() - if model is None: - return - - nu = poissons_ratio - - # Sample per-env Young's modulus and density, then convert E to Lame parameters. - youngs = torch.empty(num_instances, device=device).uniform_(*youngs_modulus_range) - density = torch.empty(num_instances, device=device).uniform_(*density_range) - k_mu = youngs / (2.0 * (1.0 + nu)) - k_lambda = youngs * nu / ((1.0 + nu) * (1.0 - 2.0 * nu)) - - k_mu_wp = wp.from_torch(k_mu.contiguous(), dtype=wp.float32) - k_lambda_wp = wp.from_torch(k_lambda.contiguous(), dtype=wp.float32) - - particle_offset0 = int(asset._recorded_particle_offsets[0]) - particles_per_body = asset._particles_per_body - - wp.launch( - _write_tet_materials, - dim=(model.tet_materials.shape[0],), - inputs=[model.tet_indices, particle_offset0, particles_per_body, k_mu_wp, k_lambda_wp], - outputs=[model.tet_materials], - device=device, - ) - - # Scale masses by density relative to the spawn baseline (spawn mass already encodes it). - spawn_density = asset.cfg.spawn.physics_material.density - density_scale = (density / spawn_density).contiguous() - density_scale_wp = wp.from_torch(density_scale, dtype=wp.float32) - spawn_mass = wp.clone(model.particle_mass) - - wp.launch( - _scale_particle_mass, - dim=(num_instances, particles_per_body), - inputs=[asset._particle_offsets, density_scale_wp, spawn_mass], - outputs=[model.particle_mass, model.particle_inv_mass], - device=device, - ) - - # Refresh the asset's cached inverse-mass snapshot used by the kinematic-target restore. - asset._default_particle_inv_mass = wp.clone(model.particle_inv_mass) - - # notify the solver that model properties changed, else the randomization is ignored - NewtonManager.add_model_change(ModelFlags.MODEL_PROPERTIES) - - def reset_deformable_over_support( env: ManagerBasedEnv, env_ids: torch.Tensor, From f982d86e43782e8de64dce1b0d5b7ccfb5f193f0 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 4 Aug 2026 15:47:34 +0200 Subject: [PATCH 20/41] Style: remove comments --- scripts/environments/state_machine/lift_franka_soft.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/scripts/environments/state_machine/lift_franka_soft.py b/scripts/environments/state_machine/lift_franka_soft.py index 776cb0f0bd9f..6ba98adce128 100644 --- a/scripts/environments/state_machine/lift_franka_soft.py +++ b/scripts/environments/state_machine/lift_franka_soft.py @@ -268,9 +268,6 @@ def main(): # the state machine emits absolute end-effector poses, so pick the IK action preset; the env # defaults to relative joint targets, which RL trains on. env_cfg.actions = ActionsCfg().ik - # frame the deformable at (0.5, 0.0, 0.05) rather than the world origin. ``viewer`` drives the - # Kit viewport; ``default_visualizer_cfg`` drives the Newton/Kit visualizer window, which - # otherwise falls back to VisualizerCfg's (4.0, -4.0, 3.0). env_cfg.viewer.eye = (1.3, 0.6, 0.5) env_cfg.viewer.lookat = (0.5, 0.0, 0.05) env_cfg.sim.default_visualizer_cfg = VisualizerCfg(eye=env_cfg.viewer.eye, lookat=env_cfg.viewer.lookat) From f30a97764f07d496d4c39e4e9c78240c4b362591 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 4 Aug 2026 15:49:54 +0200 Subject: [PATCH 21/41] Docs: Correctness i ndocstring --- .../core/lift/config/franka_soft/franka_soft_env_cfg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 51c90a994f74..5f153d0c7ef5 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 @@ -385,7 +385,7 @@ class ActionsCfg(PresetCfg): @configclass class ObservationsCfg: - """Policy observations: joint state, deformable COM in robot frame, target, last action.""" + """Policy observations: relative joint state, sampled deformable points, target command, and last action.""" @configclass class PolicyCfg(ObsGroup): From 4d9388d33c6ddf0178fefbd72565ef8bf784116b Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 4 Aug 2026 15:57:09 +0200 Subject: [PATCH 22/41] Fix: Check finite after gather --- .../core/lift/config/franka_soft/mdp/observations.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/observations.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/observations.py index c38ba710f7d2..340e38df1c80 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/observations.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/observations.py @@ -15,7 +15,7 @@ from isaaclab.managers import ManagerTermBase, SceneEntityCfg from isaaclab.utils.math import subtract_frame_transforms -from .utils import _com_w, _nodal_pos_w +from .utils import _com_w, _finite if TYPE_CHECKING: from isaaclab.assets import Articulation, DeformableObject @@ -103,8 +103,9 @@ def __call__( f"Requested {num_points} deformable points, but this term was initialized with {self.num_points}." ) - nodal_pos_w = _nodal_pos_w(asset) + nodal_pos_w = asset.data.nodal_pos_w.torch sampled_points_w = nodal_pos_w.gather(1, self.node_ids.unsqueeze(-1).expand(-1, -1, 3)) + sampled_points_w = _finite(sampled_points_w) flat_sampled_points_w = sampled_points_w.reshape(-1, 3) root_pos_w = robot.data.root_pos_w.torch.unsqueeze(1).expand(-1, num_points, -1) From 708fb000bc5f0c1065c0900528a86f94e21591b3 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 4 Aug 2026 16:38:34 +0200 Subject: [PATCH 23/41] Fix: Nan guards no longer needed for training soft env --- .../config/franka_soft/mdp/observations.py | 5 +- .../lift/config/franka_soft/mdp/rewards.py | 24 +++-- .../core/lift/config/franka_soft/mdp/utils.py | 89 ------------------- 3 files changed, 12 insertions(+), 106 deletions(-) delete mode 100644 source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/utils.py diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/observations.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/observations.py index 340e38df1c80..e83c68794665 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/observations.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/observations.py @@ -15,8 +15,6 @@ from isaaclab.managers import ManagerTermBase, SceneEntityCfg from isaaclab.utils.math import subtract_frame_transforms -from .utils import _com_w, _finite - if TYPE_CHECKING: from isaaclab.assets import Articulation, DeformableObject from isaaclab.envs import ManagerBasedRLEnv @@ -38,7 +36,7 @@ def deformable_com_in_robot_root_frame( """ asset: DeformableObject = env.scene[asset_cfg.name] robot: Articulation = env.scene[robot_cfg.name] - com_w = _com_w(asset) + com_w = asset.data.root_pos_w.torch com_b, _ = subtract_frame_transforms(robot.data.root_pos_w.torch, robot.data.root_quat_w.torch, com_w) return com_b @@ -105,7 +103,6 @@ def __call__( nodal_pos_w = asset.data.nodal_pos_w.torch sampled_points_w = nodal_pos_w.gather(1, self.node_ids.unsqueeze(-1).expand(-1, -1, 3)) - sampled_points_w = _finite(sampled_points_w) flat_sampled_points_w = sampled_points_w.reshape(-1, 3) root_pos_w = robot.data.root_pos_w.torch.unsqueeze(1).expand(-1, num_points, -1) diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/rewards.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/rewards.py index bf76c35c28ee..2a7088a262ee 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/rewards.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/rewards.py @@ -15,8 +15,6 @@ from isaaclab.managers import ManagerTermBase, RewardTermCfg, SceneEntityCfg from isaaclab.utils.math import combine_frame_transforms -from .utils import _body_pos_w, _com_w, _ee_pos_w, _nodal_pos_w - if TYPE_CHECKING: from isaaclab.assets import Articulation, DeformableObject from isaaclab.envs import ManagerBasedRLEnv @@ -39,7 +37,7 @@ def deformable_lifted( Reward tensor with shape ``(num_envs,)``. """ asset: DeformableObject = env.scene[asset_cfg.name] - com_z = _com_w(asset)[:, 2] + com_z = asset.data.root_pos_w.torch[:, 2] return torch.where(com_z > minimal_height, 1.0, 0.0) @@ -65,7 +63,7 @@ def deformable_lifting( Reward tensor with shape ``(num_envs,)``. """ asset: DeformableObject = env.scene[asset_cfg.name] - com_z = _com_w(asset)[:, 2] + com_z = asset.data.root_pos_w.torch[:, 2] height = (com_z - minimal_height).clamp(min=0.0) return torch.tanh(height / std) @@ -89,8 +87,8 @@ def deformable_ee_distance( """ asset: DeformableObject = env.scene[asset_cfg.name] ee_frame: FrameTransformer = env.scene[ee_frame_cfg.name] - nodal_pos_w = _nodal_pos_w(asset) - ee_w = _ee_pos_w(ee_frame) + nodal_pos_w = asset.data.nodal_pos_w.torch + ee_w = ee_frame.data.target_pos_w.torch[..., 0, :] distance = torch.linalg.norm(nodal_pos_w - ee_w.unsqueeze(1), dim=2).min(dim=1).values return 1.0 - torch.tanh(distance / std) @@ -118,8 +116,8 @@ def deformable_com_ee_distance( """ asset: DeformableObject = env.scene[asset_cfg.name] ee_frame: FrameTransformer = env.scene[ee_frame_cfg.name] - com_w = _com_w(asset) - ee_w = _ee_pos_w(ee_frame) + com_w = asset.data.root_pos_w.torch + ee_w = ee_frame.data.target_pos_w.torch[..., 0, :] distance = torch.linalg.norm(com_w - ee_w, dim=1) return 1.0 - torch.tanh(distance / std) @@ -153,11 +151,11 @@ def deformable_fingertip_distance( robot: Articulation = env.scene[robot_cfg.name] # target points in world frame: COM (num_envs, 1, 3) or all nodes (num_envs, num_nodes, 3) if target_com: - target_w = _com_w(asset).unsqueeze(1) + target_w = asset.data.root_pos_w.torch.unsqueeze(1) else: - target_w = _nodal_pos_w(asset) + target_w = asset.data.nodal_pos_w.torch # selected finger bodies in world frame: (num_envs, num_fingers, 3) - finger_pos_w = _body_pos_w(robot, robot_cfg.body_ids) + finger_pos_w = robot.data.body_pos_w.torch[:, robot_cfg.body_ids] # nearest target to each finger: (num_envs, num_fingers) distance = torch.linalg.norm(finger_pos_w.unsqueeze(2) - target_w.unsqueeze(1), dim=3) nearest = distance.min(dim=2).values @@ -207,7 +205,7 @@ def __call__( des_pos_w, _ = combine_frame_transforms( robot.data.root_pos_w.torch, robot.data.root_quat_w.torch, command[:, :3] ) - com_w = _com_w(asset) + com_w = asset.data.root_pos_w.torch distance = torch.linalg.norm(des_pos_w - com_w, dim=1) is_lifted = com_w[:, 2] > minimal_height if success_threshold is not None: @@ -244,7 +242,7 @@ def deformable_com_goal_reached( asset: DeformableObject = env.scene[asset_cfg.name] command = env.command_manager.get_command(command_name) des_pos_w, _ = combine_frame_transforms(robot.data.root_pos_w.torch, robot.data.root_quat_w.torch, command[:, :3]) - com_w = _com_w(asset) + com_w = asset.data.root_pos_w.torch distance = torch.linalg.norm(des_pos_w - com_w, dim=1) is_lifted = com_w[:, 2] > minimal_height return (is_lifted & (distance < success_threshold)).float() diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/utils.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/utils.py deleted file mode 100644 index d04d4fec372b..000000000000 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/utils.py +++ /dev/null @@ -1,89 +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 - -"""Sanitized state accessors shared by the deformable lift MDP terms. - -The coupled rigid/soft solve can diverge and turn a whole environment's state non-finite. Measured -behaviour is a single-step event in one environment out of thousands: the robot joint state, the -robot body poses, the end-effector frame and every deformable node all become ``NaN`` at once, with -no growth in the preceding steps. Every reward or observation reading that state then returns -``NaN``, and RL libraries check the returned rewards and observations, so one diverged environment -aborts the whole run. - -Reward terms, and the deformable observation terms, therefore read state through the helpers below, -which replace non-finite entries with ``0.0``. This places a diverged body at the world origin, -yielding finite but meaningless values. The task has no termination on numerical validity: the -bounds terminations fail open on ``NaN`` (both ``NaN < lower`` and ``NaN > upper`` are ``False``), -so a diverged environment runs to its time out and is reset there. Training tolerates this, since -the event is rare and the sanitized rewards stay finite throughout. - -The robot's root pose is deliberately left raw: the Franka is fixed-base, so body 0 is welded and -its transform has no joint-state dependence, keeping it finite while every descendant body goes -non-finite. A floating-base variant of this task would have to sanitize it too. -""" - -from __future__ import annotations - -from typing import TYPE_CHECKING - -import torch - -if TYPE_CHECKING: - from isaaclab.assets import Articulation, DeformableObject - from isaaclab.sensors import FrameTransformer - - -def _finite(value: torch.Tensor) -> torch.Tensor: - """Copy of ``value`` with every non-finite entry replaced by ``0.0``.""" - return torch.nan_to_num(value, nan=0.0, posinf=0.0, neginf=0.0) - - -def _com_w(asset: DeformableObject) -> torch.Tensor: - """Sanitized world-frame center of mass of a deformable object [m]. - - Args: - asset: The deformable object entity. - - Returns: - Tensor of shape ``(num_envs, 3)`` with non-finite entries replaced by ``0.0``. - """ - return _finite(asset.data.root_pos_w.torch) - - -def _nodal_pos_w(asset: DeformableObject) -> torch.Tensor: - """Sanitized world-frame nodal positions of a deformable object [m]. - - Args: - asset: The deformable object entity. - - Returns: - Tensor of shape ``(num_envs, num_nodes, 3)`` with non-finite entries replaced by ``0.0``. - """ - return _finite(asset.data.nodal_pos_w.torch) - - -def _body_pos_w(asset: Articulation, body_ids: slice | list[int]) -> torch.Tensor: - """Sanitized world-frame positions of the selected robot bodies [m]. - - Args: - asset: The articulation entity. - body_ids: Indices of the bodies to read. - - Returns: - Tensor of shape ``(num_envs, num_bodies, 3)`` with non-finite entries replaced by ``0.0``. - """ - return _finite(asset.data.body_pos_w.torch[:, body_ids]) - - -def _ee_pos_w(sensor: FrameTransformer) -> torch.Tensor: - """Sanitized world-frame position of the first target frame of a frame transformer [m]. - - Args: - sensor: The frame transformer sensor. - - Returns: - Tensor of shape ``(num_envs, 3)`` with non-finite entries replaced by ``0.0``. - """ - return _finite(sensor.data.target_pos_w.torch[..., 0, :]) From 490205ba029e8b9d2d3c998ecce6518f707a4fc6 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 4 Aug 2026 16:40:33 +0200 Subject: [PATCH 24/41] Feat: Log the gravity curriculum without using ADR --- .../config/franka_soft/franka_soft_env_cfg.py | 15 ++++++--------- .../lift/config/franka_soft/mdp/curriculums.py | 11 +++++++---- 2 files changed, 13 insertions(+), 13 deletions(-) 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 5f153d0c7ef5..5f16640050f1 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 @@ -538,16 +538,13 @@ class CurriculumCfg: # Since we use 24 steps per env, 10000 steps correspond to 10000/24 = 416.67 learning iterations gravity = CurrTerm( - func=mdp.modify_term_cfg, + func=mdp.gravity_range_linear, params={ - "address": "events.variable_gravity.params.gravity_distribution_params", - "modify_fn": mdp.gravity_range_linear, - "modify_params": { - "start_gravity_z": -0.0001, - "end_gravity_z": -9.81, - "start_step": 0, - "end_step": 10000, - }, + "event_name": "variable_gravity", + "start_gravity_z": -0.0001, + "end_gravity_z": -9.81, + "start_step": 0, + "end_step": 10000, }, ) diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/curriculums.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/curriculums.py index 34c4b25b5475..91f7916d72d3 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/curriculums.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/curriculums.py @@ -17,13 +17,13 @@ def gravity_range_linear( env: ManagerBasedRLEnv, _env_ids: Sequence[int], - _value: tuple[list[float], list[float]], + event_name: str, start_gravity_z: float, end_gravity_z: float, start_step: int, end_step: int, -) -> tuple[list[float], list[float]]: - """Linearly interpolate deterministic vertical gravity bounds [m/s^2].""" +) -> dict[str, float]: + """Linearly ramp an event's deterministic vertical gravity [m/s^2].""" if end_step <= start_step: raise ValueError("end_step must be greater than start_step.") @@ -31,4 +31,7 @@ def gravity_range_linear( alpha = min(max(alpha, 0.0), 1.0) gravity_z = start_gravity_z + alpha * (end_gravity_z - start_gravity_z) gravity = [0.0, 0.0, gravity_z] - return gravity, gravity.copy() + event_cfg = env.event_manager.get_term_cfg(event_name) + event_cfg.params["gravity_distribution_params"] = (gravity, gravity.copy()) + env.event_manager.set_term_cfg(event_name, event_cfg) + return {"gravity_z": gravity_z} From 569d55b26246bdc298faaa5401d6ed5a8da0803d Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 4 Aug 2026 17:06:26 +0200 Subject: [PATCH 25/41] Fix: Unify the observations of lift task --- .../lift/config/franka_soft/mdp/__init__.pyi | 7 -- .../config/franka_soft/mdp/observations.py | 115 ------------------ .../core/lift/mdp/observations.py | 5 +- 3 files changed, 2 insertions(+), 125 deletions(-) delete mode 100644 source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/observations.py diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/__init__.pyi b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/__init__.pyi index 88695b013b88..f4c00a57422d 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/__init__.pyi +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/__init__.pyi @@ -4,9 +4,6 @@ # SPDX-License-Identifier: BSD-3-Clause __all__ = [ - # observations - "deformable_com_in_robot_root_frame", - "DeformableSampledPointsInRobotRootFrame", # rewards "deformable_lifted", "deformable_lifting", @@ -29,10 +26,6 @@ __all__ = [ from .curriculums import gravity_range_linear from .events import reset_deformable_over_support -from .observations import ( - DeformableSampledPointsInRobotRootFrame, - deformable_com_in_robot_root_frame, -) from .pose_commands import ( DeformableUniformPoseCommand, DeformableUniformPoseCommandCfg, diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/observations.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/observations.py deleted file mode 100644 index e83c68794665..000000000000 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/observations.py +++ /dev/null @@ -1,115 +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 - -"""Observation functions for the deformable lift tasks.""" - -from __future__ import annotations - -from collections.abc import Sequence -from typing import TYPE_CHECKING - -import torch - -from isaaclab.managers import ManagerTermBase, SceneEntityCfg -from isaaclab.utils.math import subtract_frame_transforms - -if TYPE_CHECKING: - from isaaclab.assets import Articulation, DeformableObject - from isaaclab.envs import ManagerBasedRLEnv - from isaaclab.managers import ObservationTermCfg - - -def deformable_com_in_robot_root_frame( - env: ManagerBasedRLEnv, - asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), - robot_cfg: SceneEntityCfg = SceneEntityCfg("robot"), -) -> torch.Tensor: - """Position of the deformable object's COM in the robot's root frame [m]. - - The COM is the mean of the deformable's nodal positions (see - :attr:`~isaaclab.assets.DeformableObject.data.root_pos_w`). - - Returns: - Tensor of shape ``(num_envs, 3)``. - """ - asset: DeformableObject = env.scene[asset_cfg.name] - robot: Articulation = env.scene[robot_cfg.name] - com_w = asset.data.root_pos_w.torch - com_b, _ = subtract_frame_transforms(robot.data.root_pos_w.torch, robot.data.root_quat_w.torch, com_w) - return com_b - - -class DeformableSampledPointsInRobotRootFrame(ManagerTermBase): - """Sampled deformable nodal points expressed in the robot's root frame. - - The point indices are sampled on reset, then reused within the episode so - each observed point follows the same material node over time. - """ - - def __init__(self, cfg: ObservationTermCfg, env: ManagerBasedRLEnv): - super().__init__(cfg, env) - - self.asset_cfg: SceneEntityCfg = cfg.params.get("asset_cfg", SceneEntityCfg("deformable")) - self.robot_cfg: SceneEntityCfg = cfg.params.get("robot_cfg", SceneEntityCfg("robot")) - self.num_points: int = cfg.params.get("num_points", 20) - - asset: DeformableObject = env.scene[self.asset_cfg.name] - self.num_nodes = asset.data.nodal_pos_w.shape[1] - self.node_ids = torch.empty(env.num_envs, self.num_points, dtype=torch.long, device=env.device) - self.reset() - - def reset(self, env_ids: Sequence[int] | None = None) -> None: - """Resample observed deformable nodes for the selected environments.""" - if env_ids is None: - env_ids = slice(None) - num_envs = self.num_envs - else: - num_envs = len(env_ids) - - if self.num_points <= self.num_nodes: - self.node_ids[env_ids] = ( - torch.rand((num_envs, self.num_nodes), device=self.device).topk(self.num_points, dim=1).indices - ) - else: - self.node_ids[env_ids] = torch.randint(self.num_nodes, (num_envs, self.num_points), device=self.device) - - def __call__( - self, - env: ManagerBasedRLEnv, - asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), - robot_cfg: SceneEntityCfg = SceneEntityCfg("robot"), - num_points: int = 20, - ) -> torch.Tensor: - """Sample deformable nodal positions in the robot's root frame. - - Args: - env: The environment instance. - asset_cfg: The deformable object entity. - robot_cfg: The robot entity providing the reference frame. - num_points: Number of sampled points. - - Returns: - Flattened tensor of shape ``(num_envs, 3 * num_points)`` with sampled - point positions [m] in the robot root frame. - """ - asset: DeformableObject = env.scene[asset_cfg.name] - robot: Articulation = env.scene[robot_cfg.name] - if num_points != self.num_points: - raise ValueError( - f"Requested {num_points} deformable points, but this term was initialized with {self.num_points}." - ) - - nodal_pos_w = asset.data.nodal_pos_w.torch - sampled_points_w = nodal_pos_w.gather(1, self.node_ids.unsqueeze(-1).expand(-1, -1, 3)) - - flat_sampled_points_w = sampled_points_w.reshape(-1, 3) - root_pos_w = robot.data.root_pos_w.torch.unsqueeze(1).expand(-1, num_points, -1) - root_quat_w = robot.data.root_quat_w.torch.unsqueeze(1).expand(-1, num_points, -1) - sampled_points_b, _ = subtract_frame_transforms( - root_pos_w.reshape(-1, 3), - root_quat_w.reshape(-1, 4), - flat_sampled_points_w, - ) - return sampled_points_b.view(env.num_envs, -1) diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/observations.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/observations.py index a7b253906b0b..69b280ee3ae3 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/observations.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/observations.py @@ -9,7 +9,6 @@ from typing import TYPE_CHECKING import torch -import warp as wp from isaaclab.managers import ManagerTermBase, SceneEntityCfg from isaaclab.utils.math import quat_apply, quat_apply_inverse, quat_inv, quat_mul, subtract_frame_transforms @@ -269,8 +268,8 @@ def deformable_com_in_robot_root_frame( """Position of the deformable object's COM in the robot's root frame [m].""" asset: DeformableObject = env.scene[asset_cfg.name] robot: Articulation = env.scene[robot_cfg.name] - com_w = wp.to_torch(asset.data.root_pos_w) - com_b, _ = subtract_frame_transforms(wp.to_torch(robot.data.root_pos_w), wp.to_torch(robot.data.root_quat_w), com_w) + com_w = asset.data.root_pos_w.torch + com_b, _ = subtract_frame_transforms(robot.data.root_pos_w.torch, robot.data.root_quat_w.torch, com_w) return com_b From 2f4d477a9fbf6fb432fad5e40289eea4f4a95d6b Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 4 Aug 2026 17:42:53 +0200 Subject: [PATCH 26/41] Fix: Clean up lift mdp, unify the soft and core lift mdp. --- .../config/franka_soft/franka_soft_env_cfg.py | 4 +- .../lift/config/franka_soft/mdp/__init__.py | 10 - .../lift/config/franka_soft/mdp/__init__.pyi | 46 ---- .../config/franka_soft/mdp/curriculums.py | 37 --- .../lift/config/franka_soft/mdp/events.py | 66 ----- .../config/franka_soft/mdp/pose_commands.py | 82 ------ .../lift/config/franka_soft/mdp/rewards.py | 248 ------------------ .../config/franka_soft/mdp/terminations.py | 62 ----- .../isaaclab_tasks/core/lift/mdp/__init__.pyi | 22 +- .../core/lift/mdp/commands/__init__.py | 4 +- .../core/lift/mdp/commands/__init__.pyi | 3 +- .../core/lift/mdp/commands/pose_commands.py | 61 ++++- .../lift/mdp/commands/pose_commands_cfg.py | 9 +- .../core/lift/mdp/curriculums.py | 23 ++ .../isaaclab_tasks/core/lift/mdp/events.py | 49 +++- .../isaaclab_tasks/core/lift/mdp/rewards.py | 198 ++++++++++++-- .../core/lift/mdp/terminations.py | 42 ++- 17 files changed, 385 insertions(+), 581 deletions(-) delete mode 100644 source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/__init__.py delete mode 100644 source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/__init__.pyi delete mode 100644 source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/curriculums.py delete mode 100644 source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/events.py delete mode 100644 source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/pose_commands.py delete mode 100644 source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/rewards.py delete mode 100644 source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/terminations.py 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 5f16640050f1..0e6524f61b37 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 @@ -55,7 +55,7 @@ from isaaclab_tasks.utils import PresetCfg from isaaclab_tasks.utils.presets import MultiBackendRendererCfg -from . import mdp +from ... import mdp ## # Pre-defined configs @@ -503,7 +503,7 @@ class RewardsCfg: ) deformable_goal_tracking = RewTerm( - func=mdp.deformable_com_goal_distance, + func=mdp.DeformableComGoalDistance, params={ "std": 0.3, "minimal_height": 0.0, diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/__init__.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/__init__.py deleted file mode 100644 index 188dd9bacd92..000000000000 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/__init__.py +++ /dev/null @@ -1,10 +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 - -"""This sub-module contains the functions that are specific to the deformable lift environments.""" - -from isaaclab.utils.module import lazy_export - -lazy_export() diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/__init__.pyi b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/__init__.pyi deleted file mode 100644 index f4c00a57422d..000000000000 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/__init__.pyi +++ /dev/null @@ -1,46 +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 - -__all__ = [ - # rewards - "deformable_lifted", - "deformable_lifting", - "deformable_ee_distance", - "deformable_com_ee_distance", - "deformable_fingertip_distance", - "deformable_com_goal_distance", - "deformable_com_goal_reached", - # terminations - "deformable_outside_bounds", - "joint_vel_out_of_sim_limit", - # events - "reset_deformable_over_support", - # commands - "DeformableUniformPoseCommand", - "DeformableUniformPoseCommandCfg", - # curriculums - "gravity_range_linear", -] - -from .curriculums import gravity_range_linear -from .events import reset_deformable_over_support -from .pose_commands import ( - DeformableUniformPoseCommand, - DeformableUniformPoseCommandCfg, -) -from .rewards import ( - deformable_com_ee_distance, - deformable_com_goal_distance, - deformable_com_goal_reached, - deformable_ee_distance, - deformable_fingertip_distance, - deformable_lifted, - deformable_lifting, -) -from .terminations import ( - deformable_outside_bounds, - joint_vel_out_of_sim_limit, -) -from isaaclab_tasks.core.lift.mdp import * diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/curriculums.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/curriculums.py deleted file mode 100644 index 91f7916d72d3..000000000000 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/curriculums.py +++ /dev/null @@ -1,37 +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 - -"""Curriculum functions for the deformable lift tasks.""" - -from __future__ import annotations - -from collections.abc import Sequence -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from isaaclab.envs import ManagerBasedRLEnv - - -def gravity_range_linear( - env: ManagerBasedRLEnv, - _env_ids: Sequence[int], - event_name: str, - start_gravity_z: float, - end_gravity_z: float, - start_step: int, - end_step: int, -) -> dict[str, float]: - """Linearly ramp an event's deterministic vertical gravity [m/s^2].""" - if end_step <= start_step: - raise ValueError("end_step must be greater than start_step.") - - alpha = (env.common_step_counter - start_step) / (end_step - start_step) - alpha = min(max(alpha, 0.0), 1.0) - gravity_z = start_gravity_z + alpha * (end_gravity_z - start_gravity_z) - gravity = [0.0, 0.0, gravity_z] - event_cfg = env.event_manager.get_term_cfg(event_name) - event_cfg.params["gravity_distribution_params"] = (gravity, gravity.copy()) - env.event_manager.set_term_cfg(event_name, event_cfg) - return {"gravity_z": gravity_z} diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/events.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/events.py deleted file mode 100644 index 05bdda48800a..000000000000 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/events.py +++ /dev/null @@ -1,66 +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 - -"""Events for the deformable lift environments.""" - -from __future__ import annotations - -from typing import TYPE_CHECKING - -import torch - -from isaaclab.managers import SceneEntityCfg -from isaaclab.utils import math as math_utils - -if TYPE_CHECKING: - from isaaclab.assets import DeformableObject, RigidObject - from isaaclab.envs import ManagerBasedEnv - - -def reset_deformable_over_support( - env: ManagerBasedEnv, - env_ids: torch.Tensor, - position_range: dict[str, tuple[float, float]], - support_offset_range: dict[str, tuple[float, float]], - asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), - support_cfg: SceneEntityCfg = SceneEntityCfg("cube"), -) -> None: - """Reset a deformable object and keep a support body underneath it. - - The deformable is displaced from its default nodal state by a sample from - :paramref:`position_range`. The support receives the same planar displacement plus an - independent sample from :paramref:`support_offset_range`, so it stays under the deformable - while still varying between resets. - - Args: - env: The environment instance. - env_ids: The environment indices to reset. - position_range: Deformable displacement bounds [m] keyed by ``x``, ``y``, ``z``. - support_offset_range: Support jitter bounds [m] keyed by ``x``, ``y``, applied on top of - the deformable's displacement. - asset_cfg: Scene entity of the deformable object to reset. - support_cfg: Scene entity of the rigid support body to keep underneath. - """ - deformable: DeformableObject = env.scene[asset_cfg.name] - support: RigidObject = env.scene[support_cfg.name] - - # shared planar displacement, so the support tracks the deformable - ranges = torch.tensor([position_range.get(key, (0.0, 0.0)) for key in ("x", "y", "z")], device=deformable.device) - offset = math_utils.sample_uniform(ranges[:, 0], ranges[:, 1], (len(env_ids), 3), device=deformable.device) - - nodal_state = deformable.data.default_nodal_state_w.torch[env_ids].clone() - nodal_state[..., :3] += offset.unsqueeze(1) - deformable.write_nodal_state_to_sim(nodal_state, env_ids=env_ids) - - ranges = torch.tensor([support_offset_range.get(key, (0.0, 0.0)) for key in ("x", "y")], device=support.device) - jitter = math_utils.sample_uniform(ranges[:, 0], ranges[:, 1], (len(env_ids), 2), device=support.device) - - root_pose = support.data.default_root_pose.torch[env_ids].clone() - root_pose[:, :3] += env.scene.env_origins[env_ids] - root_pose[:, :2] += offset[:, :2] + jitter - support.write_root_pose_to_sim_index(root_pose=root_pose, env_ids=env_ids) - support.write_root_velocity_to_sim_index( - root_velocity=torch.zeros_like(support.data.default_root_vel.torch[env_ids]), env_ids=env_ids - ) diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/pose_commands.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/pose_commands.py deleted file mode 100644 index 218a858b8f3a..000000000000 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/pose_commands.py +++ /dev/null @@ -1,82 +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 - -"""Pose command terms for the deformable lift tasks.""" - -from __future__ import annotations - -from typing import TYPE_CHECKING - -import torch - -from isaaclab.assets import AssetBaseCfg -from isaaclab.utils.configclass import configclass -from isaaclab.utils.math import combine_frame_transforms - -from isaaclab_tasks.core.lift.mdp.commands.pose_commands import ObjectUniformPoseCommand -from isaaclab_tasks.core.lift.mdp.commands.pose_commands_cfg import ObjectUniformPoseCommandCfg - -if TYPE_CHECKING: - from isaaclab.assets import DeformableObject - from isaaclab.envs import ManagerBasedEnv - - -class DeformableUniformPoseCommand(ObjectUniformPoseCommand): - """Uniform position command for a deformable object, tracked by its center of mass. - - Deformable objects expose no root orientation, so the target is tracked with the COM - (:attr:`~isaaclab.assets.DeformableObject.data.root_pos_w`) and only ``position_only`` - commands are supported. - - The success visualizer asset may be a static asset (``AssetBaseCfg``), which has no - runtime view. In that case its world position is the fixed spawn offset from the - environment origins. - """ - - cfg: DeformableUniformPoseCommandCfg - """Configuration for the command generator.""" - - object: DeformableObject - """The deformable object tracked by the command.""" - - def __init__(self, cfg: DeformableUniformPoseCommandCfg, env: ManagerBasedEnv): - if not cfg.position_only: - raise ValueError("DeformableUniformPoseCommand only supports position_only commands.") - super().__init__(cfg, env) - - # static assets are stored as their config, so their world position is constant - if isinstance(self.success_vis_asset, AssetBaseCfg): - offset = torch.tensor(self.success_vis_asset.init_state.pos, device=self.device) - self._static_success_vis_pos_w = env.scene.env_origins + offset - else: - self._static_success_vis_pos_w = None - - def _update_metrics(self): - # transform command from base frame to simulation world frame - self.pose_command_w[:, :3], self.pose_command_w[:, 3:] = combine_frame_transforms( - self.robot.data.root_pos_w.torch, - self.robot.data.root_quat_w.torch, - self.pose_command_b[:, :3], - self.pose_command_b[:, 3:], - ) - com_w = self.object.data.root_pos_w.torch - self.metrics["position_error"] = torch.linalg.norm(self.pose_command_w[:, :3] - com_w, dim=-1) - - if self.success_vis_asset is None: - return - # same success radius as the goal markers of the base class - success_id = (self.metrics["position_error"] < 0.05).int() - if self._static_success_vis_pos_w is not None: - vis_pos_w = self._static_success_vis_pos_w - else: - vis_pos_w = self.success_vis_asset.data.root_pos_w.torch - self.success_visualizer.visualize(vis_pos_w, marker_indices=success_id) - - -@configclass -class DeformableUniformPoseCommandCfg(ObjectUniformPoseCommandCfg): - """Configuration for the deformable uniform pose command generator.""" - - class_type: type[DeformableUniformPoseCommand] | str = "{DIR}.pose_commands:DeformableUniformPoseCommand" diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/rewards.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/rewards.py deleted file mode 100644 index 2a7088a262ee..000000000000 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/rewards.py +++ /dev/null @@ -1,248 +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 - -"""Reward functions for the deformable lift tasks.""" - -from __future__ import annotations - -from collections.abc import Sequence -from typing import TYPE_CHECKING - -import torch - -from isaaclab.managers import ManagerTermBase, RewardTermCfg, SceneEntityCfg -from isaaclab.utils.math import combine_frame_transforms - -if TYPE_CHECKING: - from isaaclab.assets import Articulation, DeformableObject - from isaaclab.envs import ManagerBasedRLEnv - from isaaclab.sensors import FrameTransformer - - -def deformable_lifted( - env: ManagerBasedRLEnv, - minimal_height: float, - asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), -) -> torch.Tensor: - """Reward if the deformable COM is above a minimum height. - - Args: - env: The environment instance. - minimal_height: Minimum COM height [m]. - asset_cfg: The deformable object entity. - - Returns: - Reward tensor with shape ``(num_envs,)``. - """ - asset: DeformableObject = env.scene[asset_cfg.name] - com_z = asset.data.root_pos_w.torch[:, 2] - return torch.where(com_z > minimal_height, 1.0, 0.0) - - -def deformable_lifting( - env: ManagerBasedRLEnv, - std: float, - minimal_height: float, - asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), -) -> torch.Tensor: - """Reward raising the deformable COM above ``minimal_height`` [m] using a tanh kernel with scale ``std`` [m]. - - Dense analogue of :func:`deformable_lifted`: ungated and continuous, so it supplies a smooth - upward gradient rather than a binary step. Returns ``0`` at or below ``minimal_height`` and - saturates toward ``1``. - - Args: - env: The environment instance. - std: The tanh kernel standard deviation [m]. - minimal_height: Minimum COM height [m]. - asset_cfg: The deformable object entity. - - Returns: - Reward tensor with shape ``(num_envs,)``. - """ - asset: DeformableObject = env.scene[asset_cfg.name] - com_z = asset.data.root_pos_w.torch[:, 2] - height = (com_z - minimal_height).clamp(min=0.0) - return torch.tanh(height / std) - - -def deformable_ee_distance( - env: ManagerBasedRLEnv, - std: float, - asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), - ee_frame_cfg: SceneEntityCfg = SceneEntityCfg("ee_frame"), -) -> torch.Tensor: - """Reward reaching the deformable's nearest nodal point with the end-effector. - - Args: - env: The environment instance. - std: The tanh kernel standard deviation [m]. - asset_cfg: The deformable object entity. - ee_frame_cfg: The end-effector frame entity. - - Returns: - Reward tensor with shape ``(num_envs,)``. - """ - asset: DeformableObject = env.scene[asset_cfg.name] - ee_frame: FrameTransformer = env.scene[ee_frame_cfg.name] - nodal_pos_w = asset.data.nodal_pos_w.torch - ee_w = ee_frame.data.target_pos_w.torch[..., 0, :] - distance = torch.linalg.norm(nodal_pos_w - ee_w.unsqueeze(1), dim=2).min(dim=1).values - return 1.0 - torch.tanh(distance / std) - - -def deformable_com_ee_distance( - env: ManagerBasedRLEnv, - std: float, - asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), - ee_frame_cfg: SceneEntityCfg = SceneEntityCfg("ee_frame"), -) -> torch.Tensor: - """Reward reaching the deformable's center of mass with the end-effector using a tanh kernel. - - Uses the COM (:attr:`~isaaclab.assets.DeformableObject.data.root_pos_w`) rather than the nearest - node, so the gripper is drawn to the object's middle. For an elongated body (e.g. a beam) this - steers the grasp toward the center instead of an end, keeping the object balanced when lifted. - - Args: - env: The environment instance. - std: The tanh kernel standard deviation [m]. - asset_cfg: The deformable object entity. - ee_frame_cfg: The end-effector frame entity. - - Returns: - Reward tensor with shape ``(num_envs,)``. - """ - asset: DeformableObject = env.scene[asset_cfg.name] - ee_frame: FrameTransformer = env.scene[ee_frame_cfg.name] - com_w = asset.data.root_pos_w.torch - ee_w = ee_frame.data.target_pos_w.torch[..., 0, :] - distance = torch.linalg.norm(com_w - ee_w, dim=1) - return 1.0 - torch.tanh(distance / std) - - -def deformable_fingertip_distance( - env: ManagerBasedRLEnv, - std: float, - asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), - robot_cfg: SceneEntityCfg = SceneEntityCfg("robot"), - target_com: bool = False, -) -> torch.Tensor: - """Reward closing the gripper around the deformable using a tanh kernel with scale ``std`` [m]. - - Each selected finger body is rewarded for approaching the nearest deformable node - (:attr:`~isaaclab.assets.DeformableObject.data.nodal_pos_w`), - so grasping any part of the soft body is credited rather than only its center. Supplies the grasp - gradient that the EE-reach reward lacks. When ``target_com`` is set, each finger is instead drawn - to the object's center of mass, biasing the grasp to the middle of an elongated body (e.g. a beam). - - Args: - env: The environment instance. - std: The tanh kernel standard deviation [m]. - asset_cfg: The deformable object entity. - robot_cfg: The robot entity with ``body_ids`` selecting the finger bodies. - target_com: If ``True``, target the COM instead of the nearest node. - - Returns: - Reward tensor with shape ``(num_envs,)``. - """ - asset: DeformableObject = env.scene[asset_cfg.name] - robot: Articulation = env.scene[robot_cfg.name] - # target points in world frame: COM (num_envs, 1, 3) or all nodes (num_envs, num_nodes, 3) - if target_com: - target_w = asset.data.root_pos_w.torch.unsqueeze(1) - else: - target_w = asset.data.nodal_pos_w.torch - # selected finger bodies in world frame: (num_envs, num_fingers, 3) - finger_pos_w = robot.data.body_pos_w.torch[:, robot_cfg.body_ids] - # nearest target to each finger: (num_envs, num_fingers) - distance = torch.linalg.norm(finger_pos_w.unsqueeze(2) - target_w.unsqueeze(1), dim=3) - nearest = distance.min(dim=2).values - return (1.0 - torch.tanh(nearest / std)).mean(dim=1) - - -class deformable_com_goal_distance(ManagerTermBase): - """Reward tracking of the goal position by the deformable's COM (tanh kernel). - - Only credits when the COM is above ``minimal_height`` [m] (i.e. the object is lifted). - The command is interpreted as ``[x, y, z, qw, qx, qy, qz]`` in the robot's root frame. - - If ``success_threshold`` is provided in the term params, this also tracks per-episode - success (sticky binary: COM ever within ``success_threshold`` [m] of the commanded goal - while lifted above ``minimal_height``) and logs the mean across environments under - ``Metrics/success_rate`` on reset. - """ - - def __init__(self, cfg: RewardTermCfg, env: ManagerBasedRLEnv): - super().__init__(cfg, env) - self._track_success = cfg.params.get("success_threshold") is not None - if self._track_success: - self._succeeded = torch.zeros(env.num_envs, dtype=torch.bool, device=env.device) - - def reset(self, env_ids: Sequence[int] | None = None): - if env_ids is None: - env_ids = slice(None) - if self._track_success: - self._env.extras.setdefault("log", {})["Metrics/success_rate"] = ( - self._succeeded[env_ids].float().mean().item() - ) - self._succeeded[env_ids] = False - - def __call__( - self, - env: ManagerBasedRLEnv, - std: float, - minimal_height: float, - command_name: str, - robot_cfg: SceneEntityCfg = SceneEntityCfg("robot"), - asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), - success_threshold: float | None = None, - ) -> torch.Tensor: - robot: Articulation = env.scene[robot_cfg.name] - asset: DeformableObject = env.scene[asset_cfg.name] - command = env.command_manager.get_command(command_name) - des_pos_w, _ = combine_frame_transforms( - robot.data.root_pos_w.torch, robot.data.root_quat_w.torch, command[:, :3] - ) - com_w = asset.data.root_pos_w.torch - distance = torch.linalg.norm(des_pos_w - com_w, dim=1) - is_lifted = com_w[:, 2] > minimal_height - if success_threshold is not None: - self._succeeded |= is_lifted & (distance < success_threshold) - return is_lifted.float() * (1.0 - torch.tanh(distance / std)) - - -def deformable_com_goal_reached( - env: ManagerBasedRLEnv, - minimal_height: float, - command_name: str, - success_threshold: float, - robot_cfg: SceneEntityCfg = SceneEntityCfg("robot"), - asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), -) -> torch.Tensor: - """Per-step success bonus for holding the deformable COM at the goal. - - Returns ``1.0`` while the COM is within ``success_threshold`` [m] of the commanded goal - position and lifted above ``minimal_height`` [m], else ``0.0``. Matches the condition - tracked as ``Metrics/success_rate`` in :class:`deformable_com_goal_distance`. - - Args: - env: The environment instance. - minimal_height: Minimum COM height for the bonus to apply [m]. - command_name: Name of the goal-pose command term. - success_threshold: Maximum COM-to-goal distance counted as success [m]. - robot_cfg: The robot entity providing the goal reference frame. - asset_cfg: The deformable object entity. - - Returns: - Reward tensor with shape ``(num_envs,)`` valued in ``{0, 1}``. - """ - robot: Articulation = env.scene[robot_cfg.name] - asset: DeformableObject = env.scene[asset_cfg.name] - command = env.command_manager.get_command(command_name) - des_pos_w, _ = combine_frame_transforms(robot.data.root_pos_w.torch, robot.data.root_quat_w.torch, command[:, :3]) - com_w = asset.data.root_pos_w.torch - distance = torch.linalg.norm(des_pos_w - com_w, dim=1) - is_lifted = com_w[:, 2] > minimal_height - return (is_lifted & (distance < success_threshold)).float() diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/terminations.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/terminations.py deleted file mode 100644 index 1a27cb071d84..000000000000 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka_soft/mdp/terminations.py +++ /dev/null @@ -1,62 +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 - -"""Termination functions for the deformable lift tasks. - -The functions can be passed to the :class:`isaaclab.managers.TerminationTermCfg` object to enable -the termination introduced by the function. -""" - -from __future__ import annotations - -from typing import TYPE_CHECKING - -import torch - -from isaaclab.managers import SceneEntityCfg - -if TYPE_CHECKING: - from isaaclab.assets import Articulation, DeformableObject - from isaaclab.envs import ManagerBasedRLEnv - - -def deformable_outside_bounds( - env: ManagerBasedRLEnv, - x_bounds: tuple[float, float], - y_bounds: tuple[float, float], - z_bounds: tuple[float, float], - asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), -) -> torch.Tensor: - """Terminate if any deformable nodal point leaves the allowed workspace box. - - Covers both leaving the table footprint (x, y) and being dropped off it (z). - - Args: - env: The environment instance. - x_bounds: Allowed x-position range in the environment frame [m]. - y_bounds: Allowed y-position range in the environment frame [m]. - z_bounds: Allowed z-position range in the environment frame [m]. - asset_cfg: The deformable object entity. - - Returns: - Boolean tensor with shape ``(num_envs,)``. - """ - asset: DeformableObject = env.scene[asset_cfg.name] - nodal_pos = asset.data.nodal_pos_w.torch - env.scene.env_origins.unsqueeze(1) - lower = torch.tensor([x_bounds[0], y_bounds[0], z_bounds[0]], device=nodal_pos.device) - upper = torch.tensor([x_bounds[1], y_bounds[1], z_bounds[1]], device=nodal_pos.device) - return ((nodal_pos < lower) | (nodal_pos > upper)).flatten(1).any(dim=1) - - -def joint_vel_out_of_sim_limit( - env: ManagerBasedRLEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") -) -> torch.Tensor: - """Terminate when joint velocities exceed actuator simulator limits [m/s or rad/s, depending on joint type].""" - asset: Articulation = env.scene[asset_cfg.name] - joint_ids = asset_cfg.joint_ids if asset_cfg.joint_ids is not None else slice(None) - limits = torch.full_like(asset.data.joint_vel.torch, torch.inf) - for actuator in asset.actuators.values(): - limits[:, actuator.joint_indices] = actuator.velocity_limit_sim - return torch.any(torch.abs(asset.data.joint_vel.torch[:, joint_ids]) > limits[:, joint_ids], dim=1) diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/__init__.pyi b/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/__init__.pyi index e79d6934ca06..7141e29219d2 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/__init__.pyi +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/__init__.pyi @@ -17,6 +17,16 @@ __all__ = [ "reset_to_target", "get_reset_state", "set_reset_state", + "joint_vel_out_of_sim_limit", + "deformable_outside_bounds", + "deformable_lifting", + "deformable_fingertip_distance", + "deformable_com_goal_reached", + "deformable_com_ee_distance", + "DeformableComGoalDistance", + "reset_deformable_over_support", + "gravity_range_linear", + "DeformableUniformPoseCommandCfg", "ObjectUniformPoseCommandCfg", "DifficultyScheduler", "initial_final_interpolate_fn", @@ -45,13 +55,14 @@ __all__ = [ "out_of_bound", ] -from .commands import ObjectUniformPoseCommandCfg -from .curriculums import DifficultyScheduler, initial_final_interpolate_fn +from .commands import DeformableUniformPoseCommandCfg, ObjectUniformPoseCommandCfg +from .curriculums import DifficultyScheduler, gravity_range_linear, initial_final_interpolate_fn from .events import ( SuccessMonitor, conditional_reset, grasp_travel_distance, mesh_clearance, + reset_deformable_over_support, reset_joints_shared_offset, reset_to_target, slab_clearance, @@ -68,8 +79,13 @@ from .observations import ( vision_camera, ) from .rewards import ( + DeformableComGoalDistance, contacts, contact_count, + deformable_com_ee_distance, + deformable_com_goal_reached, + deformable_fingertip_distance, + deformable_lifting, deformable_com_goal_distance, deformable_ee_distance, deformable_lifted, @@ -82,8 +98,10 @@ from .rewards import ( success_reward, ) from .terminations import ( + deformable_outside_bounds, abnormal_robot_state, ee_below_minimum, + joint_vel_out_of_sim_limit, object_reached_goal, out_of_bound, ) diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/commands/__init__.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/commands/__init__.py index 83f55101029b..e14e0f6d52c5 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/commands/__init__.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/commands/__init__.py @@ -3,4 +3,6 @@ # # SPDX-License-Identifier: BSD-3-Clause -from .pose_commands_cfg import * # noqa: F401, F403 +from isaaclab.utils.module import lazy_export + +lazy_export() diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/commands/__init__.pyi b/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/commands/__init__.pyi index 50695b343506..0a9bc6289bb3 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/commands/__init__.pyi +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/commands/__init__.pyi @@ -4,7 +4,8 @@ # SPDX-License-Identifier: BSD-3-Clause __all__ = [ + "DeformableUniformPoseCommandCfg", "ObjectUniformPoseCommandCfg", ] -from .pose_commands_cfg import ObjectUniformPoseCommandCfg +from .pose_commands_cfg import DeformableUniformPoseCommandCfg, ObjectUniformPoseCommandCfg diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/commands/pose_commands.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/commands/pose_commands.py index b0165e452ee3..2b52a61c1ec3 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/commands/pose_commands.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/commands/pose_commands.py @@ -13,15 +13,16 @@ import torch +from isaaclab.assets import AssetBaseCfg from isaaclab.managers import CommandTerm from isaaclab.utils.leapp import POSE7_ELEMENT_NAMES from isaaclab.utils.math import combine_frame_transforms, compute_pose_error, quat_from_euler_xyz, quat_unique if TYPE_CHECKING: - from isaaclab.assets import Articulation, RigidObject + from isaaclab.assets import Articulation, DeformableObject, RigidObject from isaaclab.envs import ManagerBasedEnv - from . import pose_commands_cfg as dex_cmd_cfgs + from .pose_commands_cfg import DeformableUniformPoseCommandCfg, ObjectUniformPoseCommandCfg class ObjectUniformPoseCommand(CommandTerm): @@ -49,10 +50,10 @@ class ObjectUniformPoseCommand(CommandTerm): and optional visualization settings. """ - cfg: dex_cmd_cfgs.ObjectUniformPoseCommandCfg + cfg: ObjectUniformPoseCommandCfg """Configuration for the command generator.""" - def __init__(self, cfg: dex_cmd_cfgs.ObjectUniformPoseCommandCfg, env: ManagerBasedEnv): + def __init__(self, cfg: ObjectUniformPoseCommandCfg, env: ManagerBasedEnv): """Initialize the command generator class. Args: @@ -194,3 +195,55 @@ def _debug_vis_callback(self, event): self.goal_visualizer.visualize(self.pose_command_w[:, :3], marker_indices=success_id + 1) # -- current object position self.curr_visualizer.visualize(obj_pos, marker_indices=success_id + 1) + + +class DeformableUniformPoseCommand(ObjectUniformPoseCommand): + """Uniform position command for a deformable object, tracked by its center of mass. + + Deformable objects expose no root orientation, so the target is tracked with the COM + (:attr:`~isaaclab.assets.DeformableObject.data.root_pos_w`) and only ``position_only`` + commands are supported. + + The success visualizer asset may be a static asset (``AssetBaseCfg``), which has no + runtime view. In that case its world position is the fixed spawn offset from the + environment origins. + """ + + cfg: DeformableUniformPoseCommandCfg + """Configuration for the command generator.""" + + object: DeformableObject + """The deformable object tracked by the command.""" + + def __init__(self, cfg: DeformableUniformPoseCommandCfg, env: ManagerBasedEnv): + if not cfg.position_only: + raise ValueError("DeformableUniformPoseCommand only supports position_only commands.") + super().__init__(cfg, env) + + # static assets are stored as their config, so their world position is constant + if isinstance(self.success_vis_asset, AssetBaseCfg): + offset = torch.tensor(self.success_vis_asset.init_state.pos, device=self.device) + self._static_success_vis_pos_w = env.scene.env_origins + offset + else: + self._static_success_vis_pos_w = None + + def _update_metrics(self): + # transform command from base frame to simulation world frame + self.pose_command_w[:, :3], self.pose_command_w[:, 3:] = combine_frame_transforms( + self.robot.data.root_pos_w.torch, + self.robot.data.root_quat_w.torch, + self.pose_command_b[:, :3], + self.pose_command_b[:, 3:], + ) + com_w = self.object.data.root_pos_w.torch + self.metrics["position_error"] = torch.linalg.norm(self.pose_command_w[:, :3] - com_w, dim=-1) + + if self.success_vis_asset is None: + return + # same success radius as the goal markers of the base class + success_id = (self.metrics["position_error"] < 0.05).int() + if self._static_success_vis_pos_w is not None: + vis_pos_w = self._static_success_vis_pos_w + else: + vis_pos_w = self.success_vis_asset.data.root_pos_w.torch + self.success_visualizer.visualize(vis_pos_w, marker_indices=success_id) diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/commands/pose_commands_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/commands/pose_commands_cfg.py index b43523c45738..5836818e4627 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/commands/pose_commands_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/commands/pose_commands_cfg.py @@ -13,7 +13,7 @@ from isaaclab.utils.configclass import configclass if TYPE_CHECKING: - from .pose_commands import ObjectUniformPoseCommand + from .pose_commands import DeformableUniformPoseCommand, ObjectUniformPoseCommand ALIGN_MARKER_CFG = VisualizationMarkersCfg( markers={ @@ -94,3 +94,10 @@ class Ranges: prim_path="/Visuals/SuccessMarkers", markers={} ) """The configuration for the success visualization marker. User needs to add the markers""" + + +@configclass +class DeformableUniformPoseCommandCfg(ObjectUniformPoseCommandCfg): + """Configuration for the deformable uniform pose command generator.""" + + class_type: type["DeformableUniformPoseCommand"] | str = "{DIR}.pose_commands:DeformableUniformPoseCommand" diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/curriculums.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/curriculums.py index 5bc6129e1986..8a3884fad20c 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/curriculums.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/curriculums.py @@ -83,3 +83,26 @@ def __call__( # Python float: downstream ADR terms compare and interpolate host-side self.difficulty_frac = (torch.mean(self.current_adr_difficulties) / max(max_difficulty, 1)).item() return self.difficulty_frac + + +def gravity_range_linear( + env: ManagerBasedRLEnv, + _env_ids: Sequence[int], + event_name: str, + start_gravity_z: float, + end_gravity_z: float, + start_step: int, + end_step: int, +) -> dict[str, float]: + """Linearly ramp an event's deterministic vertical gravity [m/s^2].""" + if end_step <= start_step: + raise ValueError("end_step must be greater than start_step.") + + alpha = (env.common_step_counter - start_step) / (end_step - start_step) + alpha = min(max(alpha, 0.0), 1.0) + gravity_z = start_gravity_z + alpha * (end_gravity_z - start_gravity_z) + gravity = [0.0, 0.0, gravity_z] + event_cfg = env.event_manager.get_term_cfg(event_name) + event_cfg.params["gravity_distribution_params"] = (gravity, gravity.copy()) + env.event_manager.set_term_cfg(event_name, event_cfg) + return {"gravity_z": gravity_z} diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/events.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/events.py index cc4f86a2a3ef..0d9a367b56b8 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/events.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/events.py @@ -31,7 +31,7 @@ if TYPE_CHECKING: from collections.abc import Sequence - from isaaclab.assets import Articulation + from isaaclab.assets import Articulation, DeformableObject, RigidObject from isaaclab.envs import ManagerBasedEnv, ManagerBasedRLEnv from .events_cfg import GraspTravelDistanceCfg, MeshClearanceCfg, SlabClearanceCfg, SuccessMonitorCfg @@ -897,3 +897,50 @@ def __call__(self, env: ManagerBasedEnv, env_ids: torch.Tensor) -> torch.Tensor: device=env.device, ) return wp.to_torch(out_min) >= self.cfg.min_clearance + + +def reset_deformable_over_support( + env: ManagerBasedEnv, + env_ids: torch.Tensor, + position_range: dict[str, tuple[float, float]], + support_offset_range: dict[str, tuple[float, float]], + asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), + support_cfg: SceneEntityCfg = SceneEntityCfg("cube"), +) -> None: + """Reset a deformable object and keep a support body underneath it. + + The deformable is displaced from its default nodal state by a sample from + :paramref:`position_range`. The support receives the same planar displacement plus an + independent sample from :paramref:`support_offset_range`, so it stays under the deformable + while still varying between resets. + + Args: + env: The environment instance. + env_ids: The environment indices to reset. + position_range: Deformable displacement bounds [m] keyed by ``x``, ``y``, ``z``. + support_offset_range: Support jitter bounds [m] keyed by ``x``, ``y``, applied on top of + the deformable's displacement. + asset_cfg: Scene entity of the deformable object to reset. + support_cfg: Scene entity of the rigid support body to keep underneath. + """ + deformable: DeformableObject = env.scene[asset_cfg.name] + support: RigidObject = env.scene[support_cfg.name] + + # shared planar displacement, so the support tracks the deformable + ranges = torch.tensor([position_range.get(key, (0.0, 0.0)) for key in ("x", "y", "z")], device=deformable.device) + offset = sample_uniform(ranges[:, 0], ranges[:, 1], (len(env_ids), 3), device=deformable.device) + + nodal_state = deformable.data.default_nodal_state_w.torch[env_ids].clone() + nodal_state[..., :3] += offset.unsqueeze(1) + deformable.write_nodal_state_to_sim_index(nodal_state, env_ids=env_ids) + + ranges = torch.tensor([support_offset_range.get(key, (0.0, 0.0)) for key in ("x", "y")], device=support.device) + jitter = sample_uniform(ranges[:, 0], ranges[:, 1], (len(env_ids), 2), device=support.device) + + root_pose = support.data.default_root_pose.torch[env_ids].clone() + root_pose[:, :3] += env.scene.env_origins[env_ids] + root_pose[:, :2] += offset[:, :2] + jitter + support.write_root_pose_to_sim_index(root_pose=root_pose, env_ids=env_ids) + support.write_root_velocity_to_sim_index( + root_velocity=torch.zeros_like(support.data.default_root_vel.torch[env_ids]), env_ids=env_ids + ) diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/rewards.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/rewards.py index 54cdff0fa54b..848c0ca3457a 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/rewards.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/rewards.py @@ -10,9 +10,8 @@ from typing import TYPE_CHECKING import torch -import warp as wp -from isaaclab.managers import ManagerTermBase, SceneEntityCfg +from isaaclab.managers import ManagerTermBase, RewardTermCfg, SceneEntityCfg from isaaclab.utils import math as math_utils from isaaclab.utils.math import combine_frame_transforms, compute_pose_error @@ -375,27 +374,158 @@ def deformable_lifted( minimal_height: float, asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), ) -> torch.Tensor: - """Reward if the deformable COM is above a minimum height.""" + """Reward if the deformable COM is above a minimum height. + + Args: + env: The environment instance. + minimal_height: Minimum COM height [m]. + asset_cfg: The deformable object entity. + + Returns: + Reward tensor with shape ``(num_envs,)``. + """ asset: DeformableObject = env.scene[asset_cfg.name] - com_z = wp.to_torch(asset.data.root_pos_w)[:, 2] + com_z = asset.data.root_pos_w.torch[:, 2] return torch.where(com_z > minimal_height, 1.0, 0.0) +def deformable_lifting( + env: ManagerBasedRLEnv, + std: float, + minimal_height: float, + asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), +) -> torch.Tensor: + """Reward raising the deformable COM above ``minimal_height`` [m] using a tanh kernel with scale ``std`` [m]. + + Dense analogue of :func:`deformable_lifted`: ungated and continuous, so it supplies a smooth + upward gradient rather than a binary step. Returns ``0`` at or below ``minimal_height`` and + saturates toward ``1``. + + Args: + env: The environment instance. + std: The tanh kernel standard deviation [m]. + minimal_height: Minimum COM height [m]. + asset_cfg: The deformable object entity. + + Returns: + Reward tensor with shape ``(num_envs,)``. + """ + asset: DeformableObject = env.scene[asset_cfg.name] + com_z = asset.data.root_pos_w.torch[:, 2] + height = (com_z - minimal_height).clamp(min=0.0) + return torch.tanh(height / std) + + def deformable_ee_distance( env: ManagerBasedRLEnv, std: float, asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), ee_frame_cfg: SceneEntityCfg = SceneEntityCfg("ee_frame"), ) -> torch.Tensor: - """Reward reaching the deformable's nearest nodal point with the end-effector.""" + """Reward reaching the deformable's nearest nodal point with the end-effector. + + Args: + env: The environment instance. + std: The tanh kernel standard deviation [m]. + asset_cfg: The deformable object entity. + ee_frame_cfg: The end-effector frame entity. + + Returns: + Reward tensor with shape ``(num_envs,)``. + """ asset: DeformableObject = env.scene[asset_cfg.name] ee_frame: FrameTransformer = env.scene[ee_frame_cfg.name] - nodal_pos_w = wp.to_torch(asset.data.nodal_pos_w) - ee_w = wp.to_torch(ee_frame.data.target_pos_w)[..., 0, :] + nodal_pos_w = asset.data.nodal_pos_w.torch + ee_w = ee_frame.data.target_pos_w.torch[..., 0, :] distance = torch.linalg.norm(nodal_pos_w - ee_w.unsqueeze(1), dim=2).min(dim=1).values return 1.0 - torch.tanh(distance / std) +def deformable_com_ee_distance( + env: ManagerBasedRLEnv, + std: float, + asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), + ee_frame_cfg: SceneEntityCfg = SceneEntityCfg("ee_frame"), +) -> torch.Tensor: + """Reward reaching the deformable's center of mass with the end-effector using a tanh kernel. + + Uses the COM (:attr:`~isaaclab.assets.DeformableObject.data.root_pos_w`) rather than the nearest + node, so the gripper is drawn to the object's middle. For an elongated body (e.g. a beam) this + steers the grasp toward the center instead of an end, keeping the object balanced when lifted. + + Args: + env: The environment instance. + std: The tanh kernel standard deviation [m]. + asset_cfg: The deformable object entity. + ee_frame_cfg: The end-effector frame entity. + + Returns: + Reward tensor with shape ``(num_envs,)``. + """ + asset: DeformableObject = env.scene[asset_cfg.name] + ee_frame: FrameTransformer = env.scene[ee_frame_cfg.name] + com_w = asset.data.root_pos_w.torch + ee_w = ee_frame.data.target_pos_w.torch[..., 0, :] + distance = torch.linalg.norm(com_w - ee_w, dim=1) + return 1.0 - torch.tanh(distance / std) + + +def deformable_fingertip_distance( + env: ManagerBasedRLEnv, + std: float, + asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), + robot_cfg: SceneEntityCfg = SceneEntityCfg("robot"), + target_com: bool = False, +) -> torch.Tensor: + """Reward closing the gripper around the deformable using a tanh kernel with scale ``std`` [m]. + + Each selected finger body is rewarded for approaching the nearest deformable node + (:attr:`~isaaclab.assets.DeformableObject.data.nodal_pos_w`), + so grasping any part of the soft body is credited rather than only its center. Supplies the grasp + gradient that the EE-reach reward lacks. When ``target_com`` is set, each finger is instead drawn + to the object's center of mass, biasing the grasp to the middle of an elongated body (e.g. a beam). + + Args: + env: The environment instance. + std: The tanh kernel standard deviation [m]. + asset_cfg: The deformable object entity. + robot_cfg: The robot entity with ``body_ids`` selecting the finger bodies. + target_com: If ``True``, target the COM instead of the nearest node. + + Returns: + Reward tensor with shape ``(num_envs,)``. + """ + asset: DeformableObject = env.scene[asset_cfg.name] + robot: Articulation = env.scene[robot_cfg.name] + # target points in world frame: COM (num_envs, 1, 3) or all nodes (num_envs, num_nodes, 3) + if target_com: + target_w = asset.data.root_pos_w.torch.unsqueeze(1) + else: + target_w = asset.data.nodal_pos_w.torch + # selected finger bodies in world frame: (num_envs, num_fingers, 3) + finger_pos_w = robot.data.body_pos_w.torch[:, robot_cfg.body_ids] + # nearest target to each finger: (num_envs, num_fingers) + distance = torch.linalg.norm(finger_pos_w.unsqueeze(2) - target_w.unsqueeze(1), dim=3) + nearest = distance.min(dim=2).values + return (1.0 - torch.tanh(nearest / std)).mean(dim=1) + + +def _deformable_com_goal_metrics( + env: ManagerBasedRLEnv, + minimal_height: float, + command_name: str, + robot_cfg: SceneEntityCfg, + asset_cfg: SceneEntityCfg, +) -> tuple[torch.Tensor, torch.Tensor]: + """Compute deformable COM goal distance and lifted state.""" + robot: Articulation = env.scene[robot_cfg.name] + asset: DeformableObject = env.scene[asset_cfg.name] + command = env.command_manager.get_command(command_name) + des_pos_w, _ = combine_frame_transforms(robot.data.root_pos_w.torch, robot.data.root_quat_w.torch, command[:, :3]) + com_w = asset.data.root_pos_w.torch + return torch.linalg.norm(des_pos_w - com_w, dim=1), com_w[:, 2] > minimal_height + + def deformable_com_goal_distance( env: ManagerBasedRLEnv, std: float, @@ -404,16 +534,50 @@ def deformable_com_goal_distance( robot_cfg: SceneEntityCfg = SceneEntityCfg("robot"), asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), ) -> torch.Tensor: - """Reward tracking of the goal position by the deformable's COM.""" - robot: Articulation = env.scene[robot_cfg.name] - asset: DeformableObject = env.scene[asset_cfg.name] - command = env.command_manager.get_command(command_name) - des_pos_w, _ = combine_frame_transforms( - wp.to_torch(robot.data.root_pos_w), wp.to_torch(robot.data.root_quat_w), command[:, :3] - ) - com_w = wp.to_torch(asset.data.root_pos_w) - distance = torch.linalg.norm(des_pos_w - com_w, dim=1) - return (com_w[:, 2] > minimal_height) * (1.0 - torch.tanh(distance / std)) + """Reward tracking the goal position with the lifted deformable COM.""" + distance, is_lifted = _deformable_com_goal_metrics(env, minimal_height, command_name, robot_cfg, asset_cfg) + return is_lifted.float() * (1.0 - torch.tanh(distance / std)) + + +class DeformableComGoalDistance(ManagerTermBase): + """Reward deformable COM goal tracking and log episode success.""" + + def __init__(self, cfg: RewardTermCfg, env: ManagerBasedRLEnv): + super().__init__(cfg, env) + self._succeeded = torch.zeros(env.num_envs, dtype=torch.bool, device=env.device) + + def reset(self, env_ids: Sequence[int] | None = None) -> None: + if env_ids is None: + env_ids = slice(None) + self._env.extras.setdefault("log", {})["Metrics/success_rate"] = self._succeeded[env_ids].float().mean().item() + self._succeeded[env_ids] = False + + def __call__( + self, + env: ManagerBasedRLEnv, + std: float, + minimal_height: float, + command_name: str, + success_threshold: float, + robot_cfg: SceneEntityCfg = SceneEntityCfg("robot"), + asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), + ) -> torch.Tensor: + distance, is_lifted = _deformable_com_goal_metrics(env, minimal_height, command_name, robot_cfg, asset_cfg) + self._succeeded |= is_lifted & (distance < success_threshold) + return is_lifted.float() * (1.0 - torch.tanh(distance / std)) + + +def deformable_com_goal_reached( + env: ManagerBasedRLEnv, + minimal_height: float, + command_name: str, + success_threshold: float, + robot_cfg: SceneEntityCfg = SceneEntityCfg("robot"), + asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), +) -> torch.Tensor: + """Reward the deformable COM for reaching the lifted goal.""" + distance, is_lifted = _deformable_com_goal_metrics(env, minimal_height, command_name, robot_cfg, asset_cfg) + return (is_lifted & (distance < success_threshold)).float() def gripper_close_action(env: ManagerBasedRLEnv, action_name: str = "gripper_action") -> torch.Tensor: diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/terminations.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/terminations.py index d7a90c4e00ec..add6bdfd4287 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/terminations.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/terminations.py @@ -20,7 +20,7 @@ from isaaclab.utils.math import combine_frame_transforms if TYPE_CHECKING: - from isaaclab.assets import Articulation, RigidObject + from isaaclab.assets import Articulation, DeformableObject, RigidObject from isaaclab.envs import ManagerBasedRLEnv from isaaclab.sensors import FrameTransformer @@ -97,3 +97,43 @@ def ee_below_minimum( ee_frame: FrameTransformer = env.scene[ee_frame_cfg.name] ee_z = wp.to_torch(ee_frame.data.target_pos_w)[..., 0, 2] - env.scene.env_origins[:, 2] return ee_z < minimum_height + + +def deformable_outside_bounds( + env: ManagerBasedRLEnv, + x_bounds: tuple[float, float], + y_bounds: tuple[float, float], + z_bounds: tuple[float, float], + asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), +) -> torch.Tensor: + """Terminate if any deformable nodal point leaves the allowed workspace box. + + Covers both leaving the table footprint (x, y) and being dropped off it (z). + + Args: + env: The environment instance. + x_bounds: Allowed x-position range in the environment frame [m]. + y_bounds: Allowed y-position range in the environment frame [m]. + z_bounds: Allowed z-position range in the environment frame [m]. + asset_cfg: The deformable object entity. + + Returns: + Boolean tensor with shape ``(num_envs,)``. + """ + asset: DeformableObject = env.scene[asset_cfg.name] + nodal_pos = asset.data.nodal_pos_w.torch - env.scene.env_origins.unsqueeze(1) + lower = torch.tensor([x_bounds[0], y_bounds[0], z_bounds[0]], device=nodal_pos.device) + upper = torch.tensor([x_bounds[1], y_bounds[1], z_bounds[1]], device=nodal_pos.device) + return ((nodal_pos < lower) | (nodal_pos > upper)).flatten(1).any(dim=1) + + +def joint_vel_out_of_sim_limit( + env: ManagerBasedRLEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot") +) -> torch.Tensor: + """Terminate when joint velocities exceed actuator simulator limits [m/s or rad/s, depending on joint type].""" + asset: Articulation = env.scene[asset_cfg.name] + joint_ids = asset_cfg.joint_ids if asset_cfg.joint_ids is not None else slice(None) + limits = torch.full_like(asset.data.joint_vel.torch, torch.inf) + for actuator in asset.actuators.values(): + limits[:, actuator.joint_indices] = actuator.velocity_limit_sim + return torch.any(torch.abs(asset.data.joint_vel.torch[:, joint_ids]) > limits[:, joint_ids], dim=1) From 1cadcf1c4ec23e3c9c2da710af81f4efdf1b6433 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 4 Aug 2026 17:45:16 +0200 Subject: [PATCH 27/41] Feat: Cloth env cleanup for stable fast training --- .../franka_soft/franka_cloth_env_cfg.py | 79 ++++--------------- 1 file changed, 15 insertions(+), 64 deletions(-) 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 7e659d70af32..cd1f81803dc7 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,25 +7,24 @@ from __future__ import annotations -from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg +from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg, NewtonCollisionPipelineCfg from isaaclab_newton.sim.schemas import NewtonDeformableBodyPropertiesCfg from isaaclab_newton.sim.spawners.materials import NewtonSurfaceDeformableBodyMaterialCfg import isaaclab.sim as sim_utils from isaaclab.assets import RigidObjectCfg from isaaclab.assets.deformable_object import DeformableObjectCfg -from isaaclab.managers import CurriculumTermCfg as CurrTerm from isaaclab.managers import EventTermCfg as EventTerm from isaaclab.managers import SceneEntityCfg from isaaclab.sensors import CameraCfg from isaaclab.utils.configclass import configclass from isaaclab_contrib.coupling import CouplerEntryCfg, CouplerProxyCfg, CouplerProxyMappingCfg -from isaaclab_contrib.deformable.newton_manager_cfg import VBDSolverCfg +from isaaclab_contrib.deformable.newton_manager_cfg import NewtonModelCfg, VBDSolverCfg from isaaclab_tasks.utils import PresetCfg -from . import mdp +from ... import mdp from .franka_soft_env_cfg import ( FRANKA_CAMERA_CFG, FrankaCameraObservationsCfg, @@ -40,12 +39,6 @@ # Scene definition ## -ROBOT_SHAPE_MATERIAL_MU = 100.0 -"""Franka collision-shape friction coefficient [dimensionless] used for Newton cloth contact.""" - -ROBOT_SHAPE_MATERIAL_BODY_NAMES = ".*" -"""Franka body-name regex receiving :data:`ROBOT_SHAPE_MATERIAL_MU`.""" - @configclass class PhysicsCfg(PresetCfg): @@ -75,15 +68,18 @@ class PhysicsCfg(PresetCfg): source="rigid", destination="soft", bodies=[ - r"/World/envs/env_.*/Robot/panda_hand", - r"/World/envs/env_.*/Robot/panda_(left|right)finger", + r"/World/envs/env_.*/Robot/Geometry/.*panda_hand", + r"/World/envs/env_.*/Robot/Geometry/.*panda_(left|right)finger", r"/World/envs/env_.*/Cube", ], - # detect contact every substep so the gripper stops at the cloth surface collide_interval=1, + collision_pipeline=NewtonCollisionPipelineCfg( + enable_rigid_soft_full_surface_contact=True, + ), ) ], iterations=1, + model_cfg=NewtonModelCfg(soft_contact_mu=10.0), ), num_substeps=2, ) @@ -100,16 +96,16 @@ class DeformableCfg(PresetCfg): init_state=DeformableObjectCfg.InitialStateCfg(pos=(0.4, 0.0, 0.2)), spawn=sim_utils.MeshRectangleCfg( size=(0.2, 0.2), - resolution=(30, 30), + resolution=(10, 10), deformable_props=NewtonDeformableBodyPropertiesCfg(), visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.95, 0.85, 0.1)), physics_material=NewtonSurfaceDeformableBodyMaterialCfg( - density=50.0, - particle_radius=0.005, + density=10.0, + particle_radius=0.0025, tri_ke=5e2, tri_ka=5e2, tri_kd=1e-3, - edge_ke=2.0, + edge_ke=0.5, edge_kd=1e-3, ), ), @@ -130,7 +126,7 @@ class FrankaClothSceneCfg(_FrankaSoftSceneCfg): prim_path="{ENV_REGEX_NS}/Cube", init_state=RigidObjectCfg.InitialStateCfg(pos=(0.45, 0.0, 0.04)), spawn=sim_utils.CuboidCfg( - size=(0.03, 0.01, 0.08), + size=(0.01, 0.03, 0.08), rigid_props=sim_utils.RigidBodyPropertiesCfg(kinematic_enabled=True, disable_gravity=True), mass_props=sim_utils.MassPropertiesCfg(mass=1.0), collision_props=sim_utils.CollisionPropertiesCfg(), @@ -138,21 +134,13 @@ class FrankaClothSceneCfg(_FrankaSoftSceneCfg): ), ) - def __post_init__(self) -> None: - super().__post_init__() - - # increase franka gripper stiffness - self.robot.actuators["panda_hand"].effort_limit_sim = 500.0 - self.robot.actuators["panda_hand"].stiffness = 2000.0 - self.robot.actuators["panda_hand"].damping = 100.0 - @configclass class FrankaClothScenePresetCfg(PresetCfg): """Preset config for the Franka surface deformable scene.""" newton_mjwarp_vbd_proxy: FrankaClothSceneCfg = FrankaClothSceneCfg( - num_envs=128, env_spacing=2.5, replicate_physics=True + num_envs=2048, env_spacing=2.5, replicate_physics=True ) default = newton_mjwarp_vbd_proxy @@ -165,30 +153,6 @@ class FrankaClothCameraSceneCfg(FrankaClothSceneCfg): base_camera: CameraCfg = FRANKA_CAMERA_CFG -@configclass -class CurriculumCfg: - """Ramp the action-rate penalty once the policy has learned to lift (matches rigid recipe).""" - - action_rate = CurrTerm( - func=mdp.modify_reward_weight, params={"term_name": "action_rate", "weight": -1e-2, "num_steps": 50000} - ) - - # Since we use 24 steps per env, 20000 steps correspond to 20000/24 = 833.33 learning iterations - gravity = CurrTerm( - func=mdp.modify_term_cfg, - params={ - "address": "events.variable_gravity.params.gravity_distribution_params", - "modify_fn": mdp.gravity_range_linear, - "modify_params": { - "start_gravity_z": -1.0, - "end_gravity_z": -9.81, - "start_step": 0, - "end_step": 20000, - }, - }, - ) - - @configclass class FrankaClothEventCfg(FrankaSoftEventCfg): """Reset and startup events for the Franka cloth environment.""" @@ -205,18 +169,6 @@ class FrankaClothEventCfg(FrankaSoftEventCfg): }, ) - robot_physics_material = EventTerm( - func=mdp.randomize_rigid_body_material, - mode="startup", - params={ - "asset_cfg": SceneEntityCfg("robot", body_names=ROBOT_SHAPE_MATERIAL_BODY_NAMES), - "static_friction_range": (ROBOT_SHAPE_MATERIAL_MU, ROBOT_SHAPE_MATERIAL_MU), - "dynamic_friction_range": (ROBOT_SHAPE_MATERIAL_MU, ROBOT_SHAPE_MATERIAL_MU), - "restitution_range": (0.0, 0.0), - "num_buckets": 1, - }, - ) - ## # Environment configuration @@ -229,7 +181,6 @@ class FrankaClothEnvCfg(FrankaSoftEnvCfg): scene: FrankaClothScenePresetCfg = FrankaClothScenePresetCfg() events: FrankaClothEventCfg = FrankaClothEventCfg() - curriculum: CurriculumCfg = CurriculumCfg() def __post_init__(self) -> None: super().__post_init__() From 2541b19f08c6c6a82e6345a3f8f535a374a8b269 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Tue, 4 Aug 2026 17:50:50 +0200 Subject: [PATCH 28/41] Style: Clean up generic success vis in pose command --- .../core/lift/mdp/commands/pose_commands.py | 35 ++++++++----------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/commands/pose_commands.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/commands/pose_commands.py index 2b52a61c1ec3..a21173c8f3e9 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/commands/pose_commands.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/commands/pose_commands.py @@ -66,10 +66,16 @@ def __init__(self, cfg: ObjectUniformPoseCommandCfg, env: ManagerBasedEnv): # extract the robot and body index for which the command is generated self.robot: Articulation = env.scene[cfg.asset_name] self.object: RigidObject = env.scene[cfg.object_name] + self.success_vis_asset: RigidObject | AssetBaseCfg | None if cfg.success_vis_asset_name in env.scene.keys(): - self.success_vis_asset: RigidObject = env.scene[cfg.success_vis_asset_name] + self.success_vis_asset = env.scene[cfg.success_vis_asset_name] else: self.success_vis_asset = None + if isinstance(self.success_vis_asset, AssetBaseCfg): + offset = torch.tensor(self.success_vis_asset.init_state.pos, device=self.device) + self._static_success_vis_pos_w = env.scene.env_origins + offset + else: + self._static_success_vis_pos_w = None # create buffers # -- commands: (x, y, z, qx, qy, qz, qw) in root frame @@ -135,9 +141,13 @@ def _update_metrics(self): self.metrics["orientation_error"] = torch.linalg.norm(rot_error, dim=-1) success_id &= self.metrics["orientation_error"] < 0.5 if self.success_vis_asset is not None: - self.success_visualizer.visualize( - self.success_vis_asset.data.root_pos_w.torch, marker_indices=success_id.int() - ) + self.success_visualizer.visualize(self._get_success_vis_pos_w(), marker_indices=success_id.int()) + + def _get_success_vis_pos_w(self) -> torch.Tensor: + """Return the success visualization positions in the world frame.""" + if self._static_success_vis_pos_w is not None: + return self._static_success_vis_pos_w + return self.success_vis_asset.data.root_pos_w.torch def _resample_command(self, env_ids: Sequence[int]): # sample new pose targets @@ -203,10 +213,6 @@ class DeformableUniformPoseCommand(ObjectUniformPoseCommand): Deformable objects expose no root orientation, so the target is tracked with the COM (:attr:`~isaaclab.assets.DeformableObject.data.root_pos_w`) and only ``position_only`` commands are supported. - - The success visualizer asset may be a static asset (``AssetBaseCfg``), which has no - runtime view. In that case its world position is the fixed spawn offset from the - environment origins. """ cfg: DeformableUniformPoseCommandCfg @@ -220,13 +226,6 @@ def __init__(self, cfg: DeformableUniformPoseCommandCfg, env: ManagerBasedEnv): raise ValueError("DeformableUniformPoseCommand only supports position_only commands.") super().__init__(cfg, env) - # static assets are stored as their config, so their world position is constant - if isinstance(self.success_vis_asset, AssetBaseCfg): - offset = torch.tensor(self.success_vis_asset.init_state.pos, device=self.device) - self._static_success_vis_pos_w = env.scene.env_origins + offset - else: - self._static_success_vis_pos_w = None - def _update_metrics(self): # transform command from base frame to simulation world frame self.pose_command_w[:, :3], self.pose_command_w[:, 3:] = combine_frame_transforms( @@ -242,8 +241,4 @@ def _update_metrics(self): return # same success radius as the goal markers of the base class success_id = (self.metrics["position_error"] < 0.05).int() - if self._static_success_vis_pos_w is not None: - vis_pos_w = self._static_success_vis_pos_w - else: - vis_pos_w = self.success_vis_asset.data.root_pos_w.torch - self.success_visualizer.visualize(vis_pos_w, marker_indices=success_id) + self.success_visualizer.visualize(self._get_success_vis_pos_w(), marker_indices=success_id) From c944d43efb17bac8915488cd5aaf55dcdb93787a Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Wed, 5 Aug 2026 10:18:05 +0200 Subject: [PATCH 29/41] FiX :Clean up gravity set --- .../core/lift/config/franka_soft/franka_soft_env_cfg.py | 1 - 1 file changed, 1 deletion(-) 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 0e6524f61b37..42f8f9d1a134 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 @@ -630,7 +630,6 @@ def __post_init__(self) -> None: # simulation settings self.sim.dt = 1.0 / 120 self.sim.render_interval = self.decimation - self.sim.gravity = (0.0, 0.0, -9.81) self.sim.physics = PhysicsCfg() self.viewer.eye = (0.75, 0.25, 0.65) From 91abd1a059d51244143b3a9e1c2fad6edf32ec66 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Wed, 5 Aug 2026 10:33:01 +0200 Subject: [PATCH 30/41] Style: Clean up soft rewards --- .../changelog.d/mym-lift-soft.major.rst | 2 + .../isaaclab_tasks/core/lift/mdp/__init__.pyi | 6 - .../isaaclab_tasks/core/lift/mdp/rewards.py | 117 +----------------- 3 files changed, 5 insertions(+), 120 deletions(-) diff --git a/source/isaaclab_tasks/changelog.d/mym-lift-soft.major.rst b/source/isaaclab_tasks/changelog.d/mym-lift-soft.major.rst index 72bbdfeadc07..532a898bf9d9 100644 --- a/source/isaaclab_tasks/changelog.d/mym-lift-soft.major.rst +++ b/source/isaaclab_tasks/changelog.d/mym-lift-soft.major.rst @@ -20,3 +20,5 @@ Removed * **Breaking:** Removed the unsupported ``ovphysx`` preset from the Franka soft-beam and cloth lift environments. Use ``isaacsim_physx`` for the soft-beam task or ``newton_mjwarp_vbd_proxy`` for either task. +* **Breaking:** Removed :func:`~isaaclab_tasks.core.lift.mdp.deformable_lifted`. Use + :func:`~isaaclab_tasks.core.lift.mdp.deformable_lifting` for a smooth ``tanh``-shaped lifting reward. diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/__init__.pyi b/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/__init__.pyi index 7141e29219d2..53e0a536e5ec 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/__init__.pyi +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/__init__.pyi @@ -20,7 +20,6 @@ __all__ = [ "joint_vel_out_of_sim_limit", "deformable_outside_bounds", "deformable_lifting", - "deformable_fingertip_distance", "deformable_com_goal_reached", "deformable_com_ee_distance", "DeformableComGoalDistance", @@ -39,9 +38,7 @@ __all__ = [ "vision_camera", "contacts", "contact_count", - "deformable_com_goal_distance", "deformable_ee_distance", - "deformable_lifted", "gripper_close_action", "object_ee_distance", "orientation_command_error_tanh", @@ -84,11 +81,8 @@ from .rewards import ( contact_count, deformable_com_ee_distance, deformable_com_goal_reached, - deformable_fingertip_distance, deformable_lifting, - deformable_com_goal_distance, deformable_ee_distance, - deformable_lifted, gripper_close_action, object_ee_distance, orientation_command_error_tanh, diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/rewards.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/rewards.py index 848c0ca3457a..7d148153f871 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/rewards.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/rewards.py @@ -369,47 +369,13 @@ def __call__( return self._progress(quat_distance, gate, min_improvement, command) -def deformable_lifted( - env: ManagerBasedRLEnv, - minimal_height: float, - asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), -) -> torch.Tensor: - """Reward if the deformable COM is above a minimum height. - - Args: - env: The environment instance. - minimal_height: Minimum COM height [m]. - asset_cfg: The deformable object entity. - - Returns: - Reward tensor with shape ``(num_envs,)``. - """ - asset: DeformableObject = env.scene[asset_cfg.name] - com_z = asset.data.root_pos_w.torch[:, 2] - return torch.where(com_z > minimal_height, 1.0, 0.0) - - def deformable_lifting( env: ManagerBasedRLEnv, std: float, minimal_height: float, asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), ) -> torch.Tensor: - """Reward raising the deformable COM above ``minimal_height`` [m] using a tanh kernel with scale ``std`` [m]. - - Dense analogue of :func:`deformable_lifted`: ungated and continuous, so it supplies a smooth - upward gradient rather than a binary step. Returns ``0`` at or below ``minimal_height`` and - saturates toward ``1``. - - Args: - env: The environment instance. - std: The tanh kernel standard deviation [m]. - minimal_height: Minimum COM height [m]. - asset_cfg: The deformable object entity. - - Returns: - Reward tensor with shape ``(num_envs,)``. - """ + """Reward lifting the deformable COM above ``minimal_height`` [m] with a tanh kernel (``std`` [m]).""" asset: DeformableObject = env.scene[asset_cfg.name] com_z = asset.data.root_pos_w.torch[:, 2] height = (com_z - minimal_height).clamp(min=0.0) @@ -422,17 +388,7 @@ def deformable_ee_distance( asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), ee_frame_cfg: SceneEntityCfg = SceneEntityCfg("ee_frame"), ) -> torch.Tensor: - """Reward reaching the deformable's nearest nodal point with the end-effector. - - Args: - env: The environment instance. - std: The tanh kernel standard deviation [m]. - asset_cfg: The deformable object entity. - ee_frame_cfg: The end-effector frame entity. - - Returns: - Reward tensor with shape ``(num_envs,)``. - """ + """Reward end-effector proximity to the nearest deformable node with a tanh kernel (``std`` [m]).""" asset: DeformableObject = env.scene[asset_cfg.name] ee_frame: FrameTransformer = env.scene[ee_frame_cfg.name] nodal_pos_w = asset.data.nodal_pos_w.torch @@ -447,21 +403,7 @@ def deformable_com_ee_distance( asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), ee_frame_cfg: SceneEntityCfg = SceneEntityCfg("ee_frame"), ) -> torch.Tensor: - """Reward reaching the deformable's center of mass with the end-effector using a tanh kernel. - - Uses the COM (:attr:`~isaaclab.assets.DeformableObject.data.root_pos_w`) rather than the nearest - node, so the gripper is drawn to the object's middle. For an elongated body (e.g. a beam) this - steers the grasp toward the center instead of an end, keeping the object balanced when lifted. - - Args: - env: The environment instance. - std: The tanh kernel standard deviation [m]. - asset_cfg: The deformable object entity. - ee_frame_cfg: The end-effector frame entity. - - Returns: - Reward tensor with shape ``(num_envs,)``. - """ + """Reward end-effector proximity to the deformable COM with a tanh kernel (``std`` [m]).""" asset: DeformableObject = env.scene[asset_cfg.name] ee_frame: FrameTransformer = env.scene[ee_frame_cfg.name] com_w = asset.data.root_pos_w.torch @@ -470,46 +412,6 @@ def deformable_com_ee_distance( return 1.0 - torch.tanh(distance / std) -def deformable_fingertip_distance( - env: ManagerBasedRLEnv, - std: float, - asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), - robot_cfg: SceneEntityCfg = SceneEntityCfg("robot"), - target_com: bool = False, -) -> torch.Tensor: - """Reward closing the gripper around the deformable using a tanh kernel with scale ``std`` [m]. - - Each selected finger body is rewarded for approaching the nearest deformable node - (:attr:`~isaaclab.assets.DeformableObject.data.nodal_pos_w`), - so grasping any part of the soft body is credited rather than only its center. Supplies the grasp - gradient that the EE-reach reward lacks. When ``target_com`` is set, each finger is instead drawn - to the object's center of mass, biasing the grasp to the middle of an elongated body (e.g. a beam). - - Args: - env: The environment instance. - std: The tanh kernel standard deviation [m]. - asset_cfg: The deformable object entity. - robot_cfg: The robot entity with ``body_ids`` selecting the finger bodies. - target_com: If ``True``, target the COM instead of the nearest node. - - Returns: - Reward tensor with shape ``(num_envs,)``. - """ - asset: DeformableObject = env.scene[asset_cfg.name] - robot: Articulation = env.scene[robot_cfg.name] - # target points in world frame: COM (num_envs, 1, 3) or all nodes (num_envs, num_nodes, 3) - if target_com: - target_w = asset.data.root_pos_w.torch.unsqueeze(1) - else: - target_w = asset.data.nodal_pos_w.torch - # selected finger bodies in world frame: (num_envs, num_fingers, 3) - finger_pos_w = robot.data.body_pos_w.torch[:, robot_cfg.body_ids] - # nearest target to each finger: (num_envs, num_fingers) - distance = torch.linalg.norm(finger_pos_w.unsqueeze(2) - target_w.unsqueeze(1), dim=3) - nearest = distance.min(dim=2).values - return (1.0 - torch.tanh(nearest / std)).mean(dim=1) - - def _deformable_com_goal_metrics( env: ManagerBasedRLEnv, minimal_height: float, @@ -526,19 +428,6 @@ def _deformable_com_goal_metrics( return torch.linalg.norm(des_pos_w - com_w, dim=1), com_w[:, 2] > minimal_height -def deformable_com_goal_distance( - env: ManagerBasedRLEnv, - std: float, - minimal_height: float, - command_name: str, - robot_cfg: SceneEntityCfg = SceneEntityCfg("robot"), - asset_cfg: SceneEntityCfg = SceneEntityCfg("deformable"), -) -> torch.Tensor: - """Reward tracking the goal position with the lifted deformable COM.""" - distance, is_lifted = _deformable_com_goal_metrics(env, minimal_height, command_name, robot_cfg, asset_cfg) - return is_lifted.float() * (1.0 - torch.tanh(distance / std)) - - class DeformableComGoalDistance(ManagerTermBase): """Reward deformable COM goal tracking and log episode success.""" From d135083e02a4afc4976311bfc4853875fa32c50e Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Wed, 5 Aug 2026 11:21:17 +0200 Subject: [PATCH 31/41] Fix: Edge refinement for the cuboid as config parameter --- source/isaaclab/changelog.d/mym-lift-soft.rst | 5 +++++ .../isaaclab/sim/spawners/meshes/meshes.py | 14 +++++++------ .../sim/spawners/meshes/meshes_cfg.py | 9 ++++++++- source/isaaclab/test/sim/test_spawn_meshes.py | 20 +++++++++++++++++++ .../config/franka_soft/franka_soft_env_cfg.py | 2 ++ 5 files changed, 43 insertions(+), 7 deletions(-) create mode 100644 source/isaaclab/changelog.d/mym-lift-soft.rst diff --git a/source/isaaclab/changelog.d/mym-lift-soft.rst b/source/isaaclab/changelog.d/mym-lift-soft.rst new file mode 100644 index 000000000000..d38ac6f99fd9 --- /dev/null +++ b/source/isaaclab/changelog.d/mym-lift-soft.rst @@ -0,0 +1,5 @@ +Added +^^^^^ + +* Added :attr:`~isaaclab.sim.spawners.meshes.MeshCuboidCfg.edge_refinement` to control cuboid surface + mesh refinement. diff --git a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py index faaac56f6fcf..77d31567b8f7 100644 --- a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py +++ b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py @@ -105,9 +105,17 @@ def spawn_mesh_cuboid( Raises: ValueError: If a prim already exists at the given path. + ValueError: If :attr:`~isaaclab.sim.MeshCuboidCfg.edge_refinement` is not finite or is less than ``1.0``. """ + if not np.isfinite(cfg.edge_refinement) or cfg.edge_refinement < 1.0: + raise ValueError(f"Cuboid mesh edge refinement must be finite and at least 1.0, got {cfg.edge_refinement}.") + # create a trimesh box box = trimesh.creation.box(cfg.size) + if cfg.edge_refinement > 1.0: + max_edge = float(np.linalg.norm(box.bounding_box.extents)) / cfg.edge_refinement + vertices, faces = trimesh.remesh.subdivide_to_size(box.vertices, box.faces, max_edge=max_edge) + box = trimesh.Trimesh(vertices=vertices, faces=faces, process=False) # obtain stage handle stage = get_current_stage() @@ -420,12 +428,6 @@ def _spawn_mesh_geom_from_mesh( if not is_rigid_material: raise ValueError("Rigid properties require a rigid physics material.") - # refine the surface for deformable primitives - if cfg.deformable_props is not None: - max_edge = 0.3 * float(np.linalg.norm(mesh.bounding_box.extents)) - vertices, faces = trimesh.remesh.subdivide_to_size(mesh.vertices, mesh.faces, max_edge=max_edge) - mesh = trimesh.Trimesh(vertices=vertices, faces=faces, process=False) - # create all the paths we need for clarity geom_prim_path = prim_path + "/geometry" mesh_prim_path = geom_prim_path + "/mesh" diff --git a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes_cfg.py b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes_cfg.py index bc7955c675b6..ba3152dc31f8 100644 --- a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes_cfg.py +++ b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes_cfg.py @@ -99,7 +99,14 @@ class MeshCuboidCfg(MeshCfg): func: Callable | str = "{DIR}.meshes:spawn_mesh_cuboid" size: tuple[float, float, float] = MISSING - """Size of the cuboid (in m).""" + """Size of the cuboid [m].""" + + edge_refinement: float = 1.0 + """Surface edge refinement factor relative to the bounding-box diagonal. + + The maximum edge length is the diagonal divided by this value. The factor must be at least + ``1.0``. Defaults to ``1.0``, which leaves the base mesh unchanged. + """ @configclass diff --git a/source/isaaclab/test/sim/test_spawn_meshes.py b/source/isaaclab/test/sim/test_spawn_meshes.py index a9ad5158c2f3..8dd2f5f35f6c 100644 --- a/source/isaaclab/test/sim/test_spawn_meshes.py +++ b/source/isaaclab/test/sim/test_spawn_meshes.py @@ -13,6 +13,7 @@ """Rest everything follows.""" +import numpy as np import pytest import isaaclab.sim as sim_utils @@ -102,6 +103,25 @@ def test_spawn_cuboid(sim): # Check properties prim = sim.stage.GetPrimAtPath("/World/Cube/geometry/mesh") assert prim.GetPrimTypeInfo().GetTypeName() == "Mesh" + assert len(prim.GetAttribute("points").Get()) == 8 + assert len(prim.GetAttribute("faceVertexCounts").Get()) == 12 + + +def test_spawn_cuboid_with_edge_refinement(sim): + """Test cuboid surface edge refinement.""" + size = (1.0, 2.0, 3.0) + edge_refinement = 3.0 + cfg = sim_utils.MeshCuboidCfg(size=size, edge_refinement=edge_refinement) + cfg.func("/World/RefinedCube", cfg) + + prim = sim.stage.GetPrimAtPath("/World/RefinedCube/geometry/mesh") + points = np.asarray(prim.GetAttribute("points").Get()) + faces = np.asarray(prim.GetAttribute("faceVertexIndices").Get()).reshape(-1, 3) + edges = points[faces[:, [0, 1, 1, 2, 2, 0]]].reshape(-1, 2, 3) + + assert len(points) > 8 + assert len(faces) > 12 + assert np.linalg.norm(edges[:, 0] - edges[:, 1], axis=1).max() <= np.linalg.norm(size) / edge_refinement def test_spawn_sphere(sim): 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 42f8f9d1a134..0094c9c75dc4 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 @@ -106,6 +106,7 @@ class DeformableCfg(PresetCfg): init_state=DeformableObjectCfg.InitialStateCfg(pos=(0.5, 0.0, 0.05)), spawn=sim_utils.MeshCuboidCfg( size=(0.3, 0.04, 0.04), + edge_refinement=3.0, deformable_props=NewtonDeformableBodyPropertiesCfg(), visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.45, 0.45, 0.85)), physics_material=NewtonDeformableBodyMaterialCfg( @@ -122,6 +123,7 @@ class DeformableCfg(PresetCfg): init_state=DeformableObjectCfg.InitialStateCfg(pos=(0.5, 0.0, 0.05)), spawn=sim_utils.MeshCuboidCfg( size=(0.3, 0.04, 0.04), + edge_refinement=3.0, deformable_props=PhysxDeformableBodyPropertiesCfg(), collision_props=[PhysxCollisionCfg(rest_offset=0.0025, contact_offset=0.01)], visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.45, 0.45, 0.85)), From 2a563a1306d5fcfc0948a5f997e58be9e61391f8 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Wed, 5 Aug 2026 11:26:02 +0200 Subject: [PATCH 32/41] Docs: Update changelogs and decsriptions --- .../newton/using-vbd-solver.rst | 6 +++++ .../changelog.d/mym-lift-soft.minor.rst | 4 +-- .../deformable/newton_manager_cfg.py | 2 +- .../changelog.d/mym-lift-soft.minor.rst | 4 +-- .../physics/newton_collision_cfg.py | 2 +- .../changelog.d/mym-lift-soft.major.rst | 25 +++++++++++++------ 6 files changed, 30 insertions(+), 13 deletions(-) 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 9faf0758f78b..a5e302b1a91c 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 @@ -188,6 +188,8 @@ Core Solve - Description * - ``iterations`` - Default: ``10``. Number of VBD iterations per substep. Increasing this value improves deformation and contact convergence, especially for stiff materials or rigid gripper contacts, but increases runtime. + * - ``rigid_body_particle_contact_buffer_size`` + - Default: ``256``. Per-body capacity for particle, edge, and face soft contacts. Increase it if Newton reports a per-body contact buffer overflow. * - ``integrate_with_external_rigid_solver`` - Default: ``False``. Set to ``True`` only when a manual manager integrates rigid bodies in the shared model. Proxy-coupled entries use partitioned model views and leave this ``False``. @@ -298,6 +300,10 @@ What the selectors do: VBD. Only the ``panda_hand`` and the two fingers are exposed as proxies, so VBD sees three rigid proxies regardless of the number of arm links. +Full-surface rigid-soft contact includes edge and triangle-interior contacts. +Analytic proxy shapes support it directly, while mesh and convex proxy shapes +require a volume SDF. + .. important:: The coupler currently rejects diff --git a/source/isaaclab_contrib/changelog.d/mym-lift-soft.minor.rst b/source/isaaclab_contrib/changelog.d/mym-lift-soft.minor.rst index 0909867a3b55..c1f1ab4b3c78 100644 --- a/source/isaaclab_contrib/changelog.d/mym-lift-soft.minor.rst +++ b/source/isaaclab_contrib/changelog.d/mym-lift-soft.minor.rst @@ -2,5 +2,5 @@ Added ^^^^^ * Added :attr:`~isaaclab_contrib.deformable.VBDSolverCfg.rigid_body_particle_contact_buffer_size` - to size the per-body particle contact list. Contacts past the buffer are dropped from the body's - reaction list, which pushes the particles without recoiling the body and injects energy. + to size each body's particle, edge, and face soft-contact list. Contacts past the buffer are + dropped from the body's reaction list, which can inject energy. diff --git a/source/isaaclab_contrib/isaaclab_contrib/deformable/newton_manager_cfg.py b/source/isaaclab_contrib/isaaclab_contrib/deformable/newton_manager_cfg.py index af4607860def..7bcbbe1c5b50 100644 --- a/source/isaaclab_contrib/isaaclab_contrib/deformable/newton_manager_cfg.py +++ b/source/isaaclab_contrib/isaaclab_contrib/deformable/newton_manager_cfg.py @@ -119,7 +119,7 @@ class VBDSolverCfg(NewtonModelSolverCfg): """Initial stiffness seed for all rigid body contacts [N/m].""" rigid_body_particle_contact_buffer_size: int = 256 - """Per-body capacity of the body-particle soft-contact list. + """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 diff --git a/source/isaaclab_newton/changelog.d/mym-lift-soft.minor.rst b/source/isaaclab_newton/changelog.d/mym-lift-soft.minor.rst index f89e4eaf6d17..9f68181632bd 100644 --- a/source/isaaclab_newton/changelog.d/mym-lift-soft.minor.rst +++ b/source/isaaclab_newton/changelog.d/mym-lift-soft.minor.rst @@ -2,5 +2,5 @@ Added ^^^^^ * Added :attr:`~isaaclab_newton.physics.NewtonCollisionPipelineCfg.enable_rigid_soft_full_surface_contact` - to generate edge and triangle-interior soft contacts against rigid SDFs, so rigid features that - pass between soft vertices are caught. + to generate edge and triangle-interior soft contacts against full-surface-capable rigid colliders. + Analytic shapes work directly; mesh and convex colliders require a volume SDF. diff --git a/source/isaaclab_newton/isaaclab_newton/physics/newton_collision_cfg.py b/source/isaaclab_newton/isaaclab_newton/physics/newton_collision_cfg.py index 2f9a56c13fa0..c24f7e0d536e 100644 --- a/source/isaaclab_newton/isaaclab_newton/physics/newton_collision_cfg.py +++ b/source/isaaclab_newton/isaaclab_newton/physics/newton_collision_cfg.py @@ -151,7 +151,7 @@ class NewtonCollisionPipelineCfg: """ enable_rigid_soft_full_surface_contact: bool = False - """Whether to generate soft contacts over the full soft-mesh surface against rigid SDFs. + """Whether to generate soft contacts against full-surface-capable rigid colliders. When ``True``, Newton adds edge and triangle-interior soft contacts (in addition to the per-vertex particle contacts) so rigid features that pass between soft vertices are caught. diff --git a/source/isaaclab_tasks/changelog.d/mym-lift-soft.major.rst b/source/isaaclab_tasks/changelog.d/mym-lift-soft.major.rst index 532a898bf9d9..95b5d20ca26a 100644 --- a/source/isaaclab_tasks/changelog.d/mym-lift-soft.major.rst +++ b/source/isaaclab_tasks/changelog.d/mym-lift-soft.major.rst @@ -1,8 +1,8 @@ Added ^^^^^ -* Added deformable-specific commands, observations, rewards, events, terminations, and curricula, - plus ``joint`` and ``ik`` action presets, to the Franka soft-beam and cloth lift environments. +* Added a deformable COM pose command, dense deformable rewards with success logging, a gravity + curriculum, and ``joint`` and ``ik`` action presets to the Franka soft-beam and cloth lift tasks. Changed ^^^^^^^ @@ -12,13 +12,24 @@ Changed environments' previous absolute joint targets must update their actions. * **Breaking:** Changed the non-camera ``rsl_rl`` experiment name from ``franka_deformable`` to ``franka_soft``. Update log and checkpoint paths that refer to ``logs/rsl_rl/franka_deformable``. -* Re-tuned the robot, scenes, contact handling, control rate, and ``rsl_rl`` configuration for stable - gravity-based training across the supported physics backends. +* Re-tuned the robot, scenes, contact handling, control rate, and ``rsl_rl`` configuration for + gravity-based training. Removed ^^^^^^^ -* **Breaking:** Removed the unsupported ``ovphysx`` preset from the Franka soft-beam and cloth lift - environments. Use ``isaacsim_physx`` for the soft-beam task or ``newton_mjwarp_vbd_proxy`` for either task. * **Breaking:** Removed :func:`~isaaclab_tasks.core.lift.mdp.deformable_lifted`. Use - :func:`~isaaclab_tasks.core.lift.mdp.deformable_lifting` for a smooth ``tanh``-shaped lifting reward. + :func:`~isaaclab_tasks.core.lift.mdp.deformable_lifting` and set the required ``std``. +* **Breaking:** Removed :func:`~isaaclab_tasks.core.lift.mdp.deformable_com_goal_distance`. Use + :class:`~isaaclab_tasks.core.lift.mdp.DeformableComGoalDistance` and set the required + ``success_threshold``. +* **Breaking:** Removed :func:`~isaaclab_tasks.core.lift.mdp.deformable_outside_table_bounds`. Use + :func:`~isaaclab_tasks.core.lift.mdp.deformable_outside_bounds` and set the required ``z_bounds``. +* **Breaking:** Removed :func:`~isaaclab_tasks.core.lift.mdp.deformable_com_below_minimum`. Use + :func:`~isaaclab_tasks.core.lift.mdp.deformable_outside_bounds` with appropriate ``z_bounds`` for + workspace termination. +* **Breaking:** Removed the state-machine demo's video arguments. Use ``--num_steps`` to control + the finite demo and an external capture workflow to record it. +* **Breaking:** Removed the unsupported ``ovphysx`` preset from the Franka soft-beam and cloth lift + environments. Use ``isaacsim_physx`` for the soft-beam task or + ``newton_mjwarp_vbd_proxy`` for either task. From 95f6ff312268c6de056aea709d26131d4c2cfdd4 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Wed, 5 Aug 2026 11:44:26 +0200 Subject: [PATCH 33/41] Fix: Reset gripper separate from arm --- .../config/franka_soft/franka_soft_env_cfg.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) 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 0094c9c75dc4..6f6bac6969fc 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 @@ -253,7 +253,6 @@ def __post_init__(self) -> None: joint_names_expr=["panda_joint[1-7]"], effort_limit_sim={"panda_joint[1-4]": 87.0, "panda_joint[5-7]": 12.0}, velocity_limit_sim={"panda_joint[1-4]": 2.175, "panda_joint[5-7]": 2.61}, - # velocity_limit_sim={"panda_joint[1-4]": 20.0, "panda_joint[5-7]": 25.0}, stiffness={ "panda_joint[1-4]": 600.0, "panda_joint5": 250.0, @@ -462,10 +461,23 @@ class BaseImageCfg(ObsGroup): class EventCfg: """Reset events: robot to default joint config, deformable with small position randomization.""" - reset_robot_joints = EventTerm( + reset_robot_arm_joints = EventTerm( func=mdp.reset_joints_by_scale, mode="reset", - params={"position_range": (0.9, 1.1), "velocity_range": (0.0, 0.0)}, + params={ + "position_range": (0.9, 1.1), + "velocity_range": (0.0, 0.0), + "asset_cfg": SceneEntityCfg("robot", joint_names="panda_joint.*"), + }, + ) + + reset_robot_gripper_joints = EventTerm( + func=mdp.reset_joints_shared_offset, + mode="reset", + params={ + "position_range": (-0.02, 0.0), + "asset_cfg": SceneEntityCfg("robot", joint_names="panda_finger_joint.*"), + }, ) reset_deformable = EventTerm( From 8bee47b9c8905963acb00aaeb8e3be5c98abf901 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Wed, 5 Aug 2026 11:54:44 +0200 Subject: [PATCH 34/41] Test: Soft environment tests --- .../core/test_lift_soft_franka_presets.py | 140 ++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 source/isaaclab_tasks/test/core/test_lift_soft_franka_presets.py diff --git a/source/isaaclab_tasks/test/core/test_lift_soft_franka_presets.py b/source/isaaclab_tasks/test/core/test_lift_soft_franka_presets.py new file mode 100644 index 000000000000..ef51f4edb44f --- /dev/null +++ b/source/isaaclab_tasks/test/core/test_lift_soft_franka_presets.py @@ -0,0 +1,140 @@ +# 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 + +"""Sim-free gates for the Franka soft-beam and cloth lift presets and their MDP terms.""" + +from types import SimpleNamespace + +import pytest +import torch + +from isaaclab_tasks.core.lift import mdp +from isaaclab_tasks.core.lift.config.franka_soft.franka_cloth_env_cfg import FrankaClothEnvCfg +from isaaclab_tasks.core.lift.config.franka_soft.franka_soft_env_cfg import FrankaSoftEnvCfg +from isaaclab_tasks.utils import resolve_presets + + +## +# Preset resolution +## + + +def test_supported_physics_presets_resolve(): + """The soft task keeps both PhysX and Newton; the cloth task keeps only Newton.""" + soft_newton = resolve_presets(FrankaSoftEnvCfg(), ("newton_mjwarp_vbd_proxy",)) + assert type(soft_newton.sim.physics).__name__ == "NewtonCfg" + + soft_physx = resolve_presets(FrankaSoftEnvCfg(), ("isaacsim_physx",)) + assert type(soft_physx.sim.physics).__name__ == "PhysxCfg" + # the PhysX beam rests on the table via explicit collision offsets instead of the 20 mm default + collision_props = soft_physx.scene.deformable.spawn.collision_props + assert collision_props and collision_props[0].rest_offset == pytest.approx(0.0025) + + cloth_newton = resolve_presets(FrankaClothEnvCfg(), ("newton_mjwarp_vbd_proxy",)) + assert type(cloth_newton.sim.physics).__name__ == "NewtonCfg" + + +## +# MDP term math (fake scene / manager, no simulator) +## + + +class _FakeScene: + """Minimal ``env.scene`` supporting ``scene[name]`` and ``env_origins``.""" + + def __init__(self, assets, env_origins=None): + self._assets = assets + self.env_origins = env_origins + + def __getitem__(self, key): + return self._assets[key] + + +class _FakeEventManager: + """Round-trips a single event term cfg through ``get_term_cfg`` / ``set_term_cfg``.""" + + def __init__(self, params): + self._cfg = SimpleNamespace(params=params) + + def get_term_cfg(self, name): + return self._cfg + + def set_term_cfg(self, name, cfg): + self._cfg = cfg + + +def _gravity_env(step): + params = {"gravity_distribution_params": ([0.0, 0.0, 0.0], [0.0, 0.0, 0.0])} + return SimpleNamespace(common_step_counter=step, event_manager=_FakeEventManager(params)) + + +def test_gravity_range_linear_validates_step_order(): + """A non-increasing step window is rejected loudly.""" + with pytest.raises(ValueError, match="end_step must be greater"): + mdp.gravity_range_linear( + _gravity_env(0), [], "variable_gravity", start_gravity_z=-1e-4, end_gravity_z=-9.81, start_step=10, end_step=10 + ) + + +def test_gravity_range_linear_clamps_and_interpolates(): + """Gravity clamps outside the window, interpolates inside, and is written back to the event term.""" + kwargs = dict(event_name="g", start_gravity_z=-1.0, end_gravity_z=-9.0, start_step=100, end_step=300) + + # before the window: clamped to the start value + assert mdp.gravity_range_linear(_gravity_env(0), [], **kwargs)["gravity_z"] == pytest.approx(-1.0) + # midpoint: halfway between start and end + assert mdp.gravity_range_linear(_gravity_env(200), [], **kwargs)["gravity_z"] == pytest.approx(-5.0) + + # after the window: clamped to the end value and pushed into the event cfg + env = _gravity_env(10_000) + assert mdp.gravity_range_linear(env, [], **kwargs)["gravity_z"] == pytest.approx(-9.0) + ramped = env.event_manager.get_term_cfg("g").params["gravity_distribution_params"] + assert ramped[0] == [0.0, 0.0, pytest.approx(-9.0)] + + +def _articulation(joint_vel, actuators): + return SimpleNamespace( + data=SimpleNamespace(joint_vel=SimpleNamespace(torch=joint_vel)), + actuators=actuators, + ) + + +def test_joint_vel_out_of_sim_limit_triggers_per_actuator(): + """A joint over its actuator's sim limit terminates; the joint_ids selection is respected.""" + # env 0 within limits; env 1 exceeds the arm limit (2.0) on joint index 2 + joint_vel = torch.tensor([[0.5, 0.5, 0.5, 0.5], [0.5, 0.5, 9.0, 0.5]]) + actuators = { + "arm": SimpleNamespace(joint_indices=[0, 1, 2], velocity_limit_sim=2.0), + "hand": SimpleNamespace(joint_indices=[3], velocity_limit_sim=1.0), + } + env = SimpleNamespace(scene=_FakeScene({"robot": _articulation(joint_vel, actuators)})) + + out = mdp.joint_vel_out_of_sim_limit(env, SimpleNamespace(name="robot", joint_ids=None)) + assert out.tolist() == [False, True] + + # excluding the offending joint clears the violation + out = mdp.joint_vel_out_of_sim_limit(env, SimpleNamespace(name="robot", joint_ids=[0, 1, 3])) + assert out.tolist() == [False, False] + + +def test_deformable_outside_bounds_covers_z(): + """A node dropped below the z floor terminates even when x and y stay inside.""" + env_origins = torch.tensor([[10.0, 0.0, 0.0], [0.0, 0.0, 0.0]]) + # env 0 node 0 falls to env-frame z = -0.5 (below the floor); env 1 stays inside the box + nodal_pos_w = torch.tensor([ + [[10.5, 0.0, -0.5], [10.5, 0.0, 0.2]], + [[0.5, 0.0, 0.2], [0.5, 0.0, 0.3]], + ]) + asset = SimpleNamespace(data=SimpleNamespace(nodal_pos_w=SimpleNamespace(torch=nodal_pos_w))) + env = SimpleNamespace(scene=_FakeScene({"deformable": asset}, env_origins=env_origins)) + + out = mdp.deformable_outside_bounds( + env, + x_bounds=(0.0, 1.0), + y_bounds=(-0.5, 0.5), + z_bounds=(-0.02, 1.0), + asset_cfg=SimpleNamespace(name="deformable"), + ) + assert out.tolist() == [True, False] From 0f3558075c01c162506e4a3979035154c50ae98b Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Wed, 5 Aug 2026 11:55:45 +0200 Subject: [PATCH 35/41] Feat: State machine also resets on termination --- .../environments/state_machine/lift_franka_soft.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/scripts/environments/state_machine/lift_franka_soft.py b/scripts/environments/state_machine/lift_franka_soft.py index 6ba98adce128..7a03c5e49230 100644 --- a/scripts/environments/state_machine/lift_franka_soft.py +++ b/scripts/environments/state_machine/lift_franka_soft.py @@ -300,7 +300,12 @@ def main(): # run everything in inference mode with torch.inference_mode(): # step environment - dones = env.step(actions)[-2] + _, _, terminated, time_outs, _ = env.step(actions) + dones = terminated | time_outs + + # reset state machine + if dones.any(): + pick_sm.reset_idx(dones.nonzero(as_tuple=False).squeeze(-1)) # observations # -- end-effector frame @@ -324,10 +329,6 @@ def main(): torch.cat([desired_position, desired_orientation], dim=-1), ) - # reset state machine - if dones.any(): - pick_sm.reset_idx(dones.nonzero(as_tuple=False).squeeze(-1)) - # close the environment env.close() From db82df4db3f7eb636fadd75e3b46c2146463bd2d Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Wed, 5 Aug 2026 13:20:21 +0200 Subject: [PATCH 36/41] Fix: joint preset actions by default in soft env --- docs/source/_static/css/environment-browser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/_static/css/environment-browser.js b/docs/source/_static/css/environment-browser.js index 93fcd005568d..471a9d04d4df 100644 --- a/docs/source/_static/css/environment-browser.js +++ b/docs/source/_static/css/environment-browser.js @@ -150,7 +150,7 @@ populateSelect(fields.physics, task.physics, [fields.physics.value, "newton_mjwarp", "isaacsim_physx", "ovphysx", "newton_kamino"]); const preferredRenderer = fields.physics.value.startsWith("newton") ? "newton_renderer" : "isaacsim_rtx"; populateSelect(fields.renderer, task.renderer, [fields.renderer.value, preferredRenderer, "ovrtx"]); - populateSelect(fields.presets, task.presets, [fields.presets.value, "rgb", "cube", "single_camera"]); + populateSelect(fields.presets, task.presets, [fields.presets.value, "joint", "ik", "rgb", "cube", "single_camera"]); }; const currentCommand = () => { From 00b63257eb0a6694fc573907418dce10835904ef Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Wed, 5 Aug 2026 13:26:12 +0200 Subject: [PATCH 37/41] Test: Tune cloth env, working at 800 iterations --- .../franka_soft/franka_cloth_env_cfg.py | 35 +++++++++++++++---- 1 file changed, 29 insertions(+), 6 deletions(-) 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 cd1f81803dc7..06654f1a733c 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 @@ -15,6 +15,7 @@ from isaaclab.assets import RigidObjectCfg from isaaclab.assets.deformable_object import DeformableObjectCfg from isaaclab.managers import EventTermCfg as EventTerm +from isaaclab.managers import RewardTermCfg as RewTerm from isaaclab.managers import SceneEntityCfg from isaaclab.sensors import CameraCfg from isaaclab.utils.configclass import configclass @@ -34,6 +35,9 @@ from .franka_soft_env_cfg import ( EventCfg as FrankaSoftEventCfg, ) +from .franka_soft_env_cfg import ( + RewardsCfg as FrankaSoftRewardsCfg, +) ## # Scene definition @@ -42,7 +46,6 @@ @configclass class PhysicsCfg(PresetCfg): - # Newton physics: MJWarp rigid + VBD soft, coupled through lagged proxies newton_mjwarp_vbd_proxy: NewtonCfg = NewtonCfg( solver_cfg=CouplerProxyCfg( entries=[ @@ -93,15 +96,15 @@ class DeformableCfg(PresetCfg): newton_mjwarp_vbd_proxy: DeformableObjectCfg = DeformableObjectCfg( prim_path="{ENV_REGEX_NS}/Deformable", - init_state=DeformableObjectCfg.InitialStateCfg(pos=(0.4, 0.0, 0.2)), + init_state=DeformableObjectCfg.InitialStateCfg(pos=(0.4, 0.0, 0.1)), spawn=sim_utils.MeshRectangleCfg( size=(0.2, 0.2), - resolution=(10, 10), + resolution=(8, 8), deformable_props=NewtonDeformableBodyPropertiesCfg(), visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.95, 0.85, 0.1)), physics_material=NewtonSurfaceDeformableBodyMaterialCfg( density=10.0, - particle_radius=0.0025, + particle_radius=0.001, tri_ke=5e2, tri_ka=5e2, tri_kd=1e-3, @@ -124,12 +127,14 @@ class FrankaClothSceneCfg(_FrankaSoftSceneCfg): # reset event can move it under the randomized cloth without it being simulated. cube: RigidObjectCfg = RigidObjectCfg( prim_path="{ENV_REGEX_NS}/Cube", - init_state=RigidObjectCfg.InitialStateCfg(pos=(0.45, 0.0, 0.04)), + init_state=RigidObjectCfg.InitialStateCfg(pos=(0.4, 0.05, 0.04)), spawn=sim_utils.CuboidCfg( size=(0.01, 0.03, 0.08), rigid_props=sim_utils.RigidBodyPropertiesCfg(kinematic_enabled=True, disable_gravity=True), mass_props=sim_utils.MassPropertiesCfg(mass=1.0), collision_props=sim_utils.CollisionPropertiesCfg(), + # low friction so the cloth slides off the cube easily when lifted + physics_material=sim_utils.RigidBodyMaterialCfg(static_friction=0.1, dynamic_friction=0.1), visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.2, 0.2, 0.25)), ), ) @@ -140,7 +145,7 @@ class FrankaClothScenePresetCfg(PresetCfg): """Preset config for the Franka surface deformable scene.""" newton_mjwarp_vbd_proxy: FrankaClothSceneCfg = FrankaClothSceneCfg( - num_envs=2048, env_spacing=2.5, replicate_physics=True + num_envs=2048, env_spacing=2.0, replicate_physics=True ) default = newton_mjwarp_vbd_proxy @@ -175,12 +180,30 @@ class FrankaClothEventCfg(FrankaSoftEventCfg): ## +@configclass +class FrankaClothRewardsCfg(FrankaSoftRewardsCfg): + """Rewards for the Franka cloth environment.""" + + reaching_deformable = RewTerm( + func=mdp.deformable_ee_distance, + params={"std": 0.1, "asset_cfg": SceneEntityCfg("deformable")}, + weight=5.0, + ) + + lifting_deformable = RewTerm( + func=mdp.deformable_lifting, + params={"std": 0.1, "minimal_height": 0.1, "asset_cfg": SceneEntityCfg("deformable")}, + weight=5.0, + ) + + @configclass class FrankaClothEnvCfg(FrankaSoftEnvCfg): """Manager-based RL environment: Franka Panda lifting a surface deformable.""" scene: FrankaClothScenePresetCfg = FrankaClothScenePresetCfg() events: FrankaClothEventCfg = FrankaClothEventCfg() + rewards: FrankaClothRewardsCfg = FrankaClothRewardsCfg() def __post_init__(self) -> None: super().__post_init__() From 163af9103a74f44cd843ea9d6b65698d27c6a0c7 Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Wed, 5 Aug 2026 13:47:33 +0200 Subject: [PATCH 38/41] Style: Sync files after develop rebase --- .../core/test_lift_soft_franka_presets.py | 19 +++++++++++++------ uv.lock | 16 ++++++++-------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/source/isaaclab_tasks/test/core/test_lift_soft_franka_presets.py b/source/isaaclab_tasks/test/core/test_lift_soft_franka_presets.py index ef51f4edb44f..c5bb5bb6675e 100644 --- a/source/isaaclab_tasks/test/core/test_lift_soft_franka_presets.py +++ b/source/isaaclab_tasks/test/core/test_lift_soft_franka_presets.py @@ -15,7 +15,6 @@ from isaaclab_tasks.core.lift.config.franka_soft.franka_soft_env_cfg import FrankaSoftEnvCfg from isaaclab_tasks.utils import resolve_presets - ## # Preset resolution ## @@ -74,7 +73,13 @@ def test_gravity_range_linear_validates_step_order(): """A non-increasing step window is rejected loudly.""" with pytest.raises(ValueError, match="end_step must be greater"): mdp.gravity_range_linear( - _gravity_env(0), [], "variable_gravity", start_gravity_z=-1e-4, end_gravity_z=-9.81, start_step=10, end_step=10 + _gravity_env(0), + [], + "variable_gravity", + start_gravity_z=-1e-4, + end_gravity_z=-9.81, + start_step=10, + end_step=10, ) @@ -123,10 +128,12 @@ def test_deformable_outside_bounds_covers_z(): """A node dropped below the z floor terminates even when x and y stay inside.""" env_origins = torch.tensor([[10.0, 0.0, 0.0], [0.0, 0.0, 0.0]]) # env 0 node 0 falls to env-frame z = -0.5 (below the floor); env 1 stays inside the box - nodal_pos_w = torch.tensor([ - [[10.5, 0.0, -0.5], [10.5, 0.0, 0.2]], - [[0.5, 0.0, 0.2], [0.5, 0.0, 0.3]], - ]) + nodal_pos_w = torch.tensor( + [ + [[10.5, 0.0, -0.5], [10.5, 0.0, 0.2]], + [[0.5, 0.0, 0.2], [0.5, 0.0, 0.3]], + ] + ) asset = SimpleNamespace(data=SimpleNamespace(nodal_pos_w=SimpleNamespace(torch=nodal_pos_w))) env = SimpleNamespace(scene=_FakeScene({"deformable": asset}, env_origins=env_origins)) diff --git a/uv.lock b/uv.lock index 384c33388232..6662d44c88a9 100644 --- a/uv.lock +++ b/uv.lock @@ -2291,12 +2291,12 @@ wheels = [ [[package]] name = "isaaclab" -version = "15.2.0" +version = "15.3.0" source = { editable = "source/isaaclab" } [[package]] name = "isaaclab-assets" -version = "0.6.2" +version = "0.6.3" source = { editable = "source/isaaclab_assets" } dependencies = [ { name = "isaaclab", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-isaacsim') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-mimic') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ov') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ovphysx') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-test') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-mimic' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-isaacsim') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-mimic') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ov') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ovphysx') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-test') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-mimic' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-isaacsim') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-mimic') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ov') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ovphysx') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-test') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-mimic' and extra == 'extra-12-isaaclab-dev-teleop')" }, @@ -2671,7 +2671,7 @@ requires-dist = [ [[package]] name = "isaaclab-newton" -version = "2.5.0" +version = "2.6.0" source = { editable = "source/isaaclab_newton" } dependencies = [ { name = "isaaclab", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-isaacsim') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-mimic') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ov') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ovphysx') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-test') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-mimic' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-isaacsim') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-mimic') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ov') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ovphysx') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-test') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-mimic' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-isaacsim') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-mimic') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ov') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ovphysx') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-test') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-mimic' and extra == 'extra-12-isaaclab-dev-teleop')" }, @@ -2682,7 +2682,7 @@ requires-dist = [{ name = "isaaclab", editable = "source/isaaclab" }] [[package]] name = "isaaclab-ov" -version = "0.10.2" +version = "0.10.3" source = { editable = "source/isaaclab_ov" } dependencies = [ { name = "isaaclab", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-isaacsim') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-mimic') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ov') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ovphysx') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-test') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-mimic' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-isaacsim') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-mimic') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ov') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ovphysx') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-test') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-mimic' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-isaacsim') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-mimic') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ov') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ovphysx') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-test') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-mimic' and extra == 'extra-12-isaaclab-dev-teleop')" }, @@ -2712,7 +2712,7 @@ requires-dist = [ [[package]] name = "isaaclab-physx" -version = "4.0.1" +version = "4.1.0" source = { editable = "source/isaaclab_physx" } dependencies = [ { name = "isaaclab", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-isaacsim') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-mimic') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ov') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ovphysx') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-test') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-mimic' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-isaacsim') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-mimic') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ov') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ovphysx') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-test') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-mimic' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-isaacsim') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-mimic') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ov') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ovphysx') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-test') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-mimic' and extra == 'extra-12-isaaclab-dev-teleop')" }, @@ -2734,7 +2734,7 @@ requires-dist = [{ name = "isaaclab", editable = "source/isaaclab" }] [[package]] name = "isaaclab-rl" -version = "0.12.0" +version = "0.13.0" source = { editable = "source/isaaclab_rl" } dependencies = [ { name = "isaaclab", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-isaacsim') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-mimic') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ov') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ovphysx') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-test') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-mimic' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-isaacsim') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-mimic') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ov') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ovphysx') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-test') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-mimic' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-isaacsim') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-mimic') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ov') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ovphysx') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-test') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-mimic' and extra == 'extra-12-isaaclab-dev-teleop')" }, @@ -2751,7 +2751,7 @@ requires-dist = [ [[package]] name = "isaaclab-tasks" -version = "11.0.0" +version = "12.0.0" source = { editable = "source/isaaclab_tasks" } dependencies = [ { name = "isaaclab", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-isaacsim') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-mimic') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ov') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ovphysx') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-test') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-mimic' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-isaacsim') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-mimic') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ov') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ovphysx') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-test') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-mimic' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-isaacsim') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-mimic') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ov') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ovphysx') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-test') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-mimic' and extra == 'extra-12-isaaclab-dev-teleop')" }, @@ -2792,7 +2792,7 @@ requires-dist = [{ name = "isaaclab", editable = "source/isaaclab" }] [[package]] name = "isaaclab-visualizers" -version = "1.3.1" +version = "1.4.0" source = { editable = "source/isaaclab_visualizers" } dependencies = [ { name = "isaaclab", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-isaacsim') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-mimic') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ov') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ovphysx') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-test') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-mimic' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-isaacsim') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-mimic') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ov') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ovphysx') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-test') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-mimic' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-isaacsim') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-mimic') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ov') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ovphysx') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-test') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-mimic' and extra == 'extra-12-isaaclab-dev-teleop')" }, From cd13a7aa85932ef96795e4e92ff4a0d0b574eadf Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Wed, 5 Aug 2026 15:11:16 +0200 Subject: [PATCH 39/41] Migrate articulation targets to canonical API Use Newton's canonical joint target attributes for articulation bindings and actuator forwarding. Keep the legacy PhysX wrapper fields available for compatibility. --- .../changelog.d/mym-lift-soft.minor.rst | 5 +++++ .../isaaclab_newton/actuators/adapter.py | 2 +- .../isaaclab_newton/actuators/physx_wrapper.py | 4 +++- .../assets/articulation/articulation_data.py | 4 ++-- .../mock_interfaces/views/mock_articulation_view.py | 12 ++++++------ source/isaaclab_physx/changelog.d/mym-lift-soft.rst | 4 ++++ .../assets/articulation/articulation.py | 2 ++ 7 files changed, 23 insertions(+), 10 deletions(-) create mode 100644 source/isaaclab_physx/changelog.d/mym-lift-soft.rst diff --git a/source/isaaclab_newton/changelog.d/mym-lift-soft.minor.rst b/source/isaaclab_newton/changelog.d/mym-lift-soft.minor.rst index 9f68181632bd..23ca66ec52b0 100644 --- a/source/isaaclab_newton/changelog.d/mym-lift-soft.minor.rst +++ b/source/isaaclab_newton/changelog.d/mym-lift-soft.minor.rst @@ -4,3 +4,8 @@ Added * Added :attr:`~isaaclab_newton.physics.NewtonCollisionPipelineCfg.enable_rigid_soft_full_surface_contact` to generate edge and triangle-interior soft contacts against full-surface-capable rigid colliders. Analytic shapes work directly; mesh and convex colliders require a volume SDF. + +Fixed +^^^^^ + +* Fixed articulation target bindings for the Newton 1.5 control API. diff --git a/source/isaaclab_newton/isaaclab_newton/actuators/adapter.py b/source/isaaclab_newton/isaaclab_newton/actuators/adapter.py index 689232b81e60..7f6fc64bf65d 100644 --- a/source/isaaclab_newton/isaaclab_newton/actuators/adapter.py +++ b/source/isaaclab_newton/isaaclab_newton/actuators/adapter.py @@ -148,7 +148,7 @@ def step(self, sim_state: Any, sim_control: Any, dt: float) -> None: Newton ``State`` on the Newton backend, :class:`~isaaclab_newton.actuators.physx_wrapper.PhysxActuatorWrapper` on the PhysX backend. - sim_control: Object with ``joint_f``, ``joint_target_pos``, etc. + sim_control: Object with ``joint_f``, ``joint_target_q``, etc. Newton ``Control`` on the Newton backend, :class:`~isaaclab_newton.actuators.physx_wrapper.PhysxActuatorWrapper` on the PhysX backend. diff --git a/source/isaaclab_newton/isaaclab_newton/actuators/physx_wrapper.py b/source/isaaclab_newton/isaaclab_newton/actuators/physx_wrapper.py index b3f48a2f9dee..8c788be408ae 100644 --- a/source/isaaclab_newton/isaaclab_newton/actuators/physx_wrapper.py +++ b/source/isaaclab_newton/isaaclab_newton/actuators/physx_wrapper.py @@ -7,7 +7,7 @@ Newton's :meth:`Actuator.step` requires a ``sim_state`` / ``sim_control`` pair that exposes flat 1-D Warp arrays (``joint_q``, ``joint_qd``, -``joint_target_pos``, ``joint_f``, …). On the **Newton backend** these +``joint_target_q``, ``joint_f``, …). On the **Newton backend** these are the ``State`` and ``Control`` objects that the solver already owns — no wrapper is needed because: @@ -45,6 +45,8 @@ class PhysxActuatorWrapper: joint_q: wp.array | None = None joint_qd: wp.array | None = None + joint_target_q: wp.array | None = None + joint_target_qd: wp.array | None = None joint_target_pos: wp.array | None = None joint_target_vel: wp.array | None = None joint_act: wp.array | None = None diff --git a/source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation_data.py b/source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation_data.py index 5029082a5da5..b2a6a576edd3 100644 --- a/source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation_data.py +++ b/source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation_data.py @@ -1685,10 +1685,10 @@ def _create_simulation_bindings(self) -> None: ] self._sim_bind_joint_act = self._root_view.get_attribute("joint_act", SimulationManager.get_control())[:, 0] self._sim_bind_joint_position_target = self._root_view.get_attribute( - "joint_target_pos", SimulationManager.get_control() + "joint_target_q", SimulationManager.get_control() )[:, 0] self._sim_bind_joint_velocity_target = self._root_view.get_attribute( - "joint_target_vel", SimulationManager.get_control() + "joint_target_qd", SimulationManager.get_control() )[:, 0] else: # No joints (e.g., free-floating rigid body) - set bindings to empty arrays diff --git a/source/isaaclab_newton/isaaclab_newton/test/mock_interfaces/views/mock_articulation_view.py b/source/isaaclab_newton/isaaclab_newton/test/mock_interfaces/views/mock_articulation_view.py index 30c29fbcd337..3820642a0422 100644 --- a/source/isaaclab_newton/isaaclab_newton/test/mock_interfaces/views/mock_articulation_view.py +++ b/source/isaaclab_newton/isaaclab_newton/test/mock_interfaces/views/mock_articulation_view.py @@ -297,8 +297,8 @@ def __init__( "body_f": None, "joint_f": None, "joint_act": None, - "joint_target_pos": None, - "joint_target_vel": None, + "joint_target_q": None, + "joint_target_qd": None, "joint_limit_ke": None, "joint_limit_kd": None, } @@ -448,8 +448,8 @@ def _create_default_attribute(self, name: str) -> wp.array: "joint_effort_limit", "joint_f", "joint_act", - "joint_target_pos", - "joint_target_vel", + "joint_target_q", + "joint_target_qd", "joint_limit_ke", "joint_limit_kd", ): @@ -752,8 +752,8 @@ def set_random_mock_data(self) -> None: "joint_effort_limit", "joint_f", "joint_act", - "joint_target_pos", - "joint_target_vel", + "joint_target_q", + "joint_target_qd", "joint_limit_ke", "joint_limit_kd", ): diff --git a/source/isaaclab_physx/changelog.d/mym-lift-soft.rst b/source/isaaclab_physx/changelog.d/mym-lift-soft.rst new file mode 100644 index 000000000000..9e220f411d06 --- /dev/null +++ b/source/isaaclab_physx/changelog.d/mym-lift-soft.rst @@ -0,0 +1,4 @@ +Fixed +^^^^^ + +* Fixed Newton 1.5 actuator target bindings on the PhysX backend. diff --git a/source/isaaclab_physx/isaaclab_physx/assets/articulation/articulation.py b/source/isaaclab_physx/isaaclab_physx/assets/articulation/articulation.py index e5d0f7485181..c5875271f0e3 100644 --- a/source/isaaclab_physx/isaaclab_physx/assets/articulation/articulation.py +++ b/source/isaaclab_physx/isaaclab_physx/assets/articulation/articulation.py @@ -4474,6 +4474,8 @@ def _process_actuators_cfg(self): w = self._physx_actuator_wrapper w.joint_q = self._data.joint_pos.warp.reshape(-1) w.joint_qd = self._data.joint_vel.warp.reshape(-1) + w.joint_target_q = self._data.joint_pos_target.warp.reshape(-1) + w.joint_target_qd = self._data.joint_vel_target.warp.reshape(-1) w.joint_target_pos = self._data.joint_pos_target.warp.reshape(-1) w.joint_target_vel = self._data.joint_vel_target.warp.reshape(-1) w.joint_act = self._data.joint_effort_target.warp.reshape(-1) From a5e6b8b1f642938f3039d421b00f194f47aeb2cf Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Wed, 5 Aug 2026 15:11:48 +0200 Subject: [PATCH 40/41] Update Newton dependency revision Pin Newton to the revision containing the articulation target API needed by Isaac Lab. Align Warp and Newton USD schemas with the dependency floors declared by that revision, then regenerate dependency metadata. --- pyproject.toml | 12 +++++----- source/isaaclab/changelog.d/mym-lift-soft.rst | 6 +++++ .../test/install_ci/uv_pip/uv-overrides.txt | 4 ++-- tools/wheel_builder/uv-overrides.txt | 4 ++-- uv.lock | 22 +++++++++---------- 5 files changed, 27 insertions(+), 21 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 0f23813db722..92698adcfe63 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,7 +52,7 @@ dependencies = [ "pyglet>=2.1.6,<3", "transformers==4.57.6", "einops", - "warp-lang==1.16.0.dev20260723", + "warp-lang==1.16.0", "matplotlib>=3.10.3", # pillow: floor, not exact — an exact pin below Isaac Sim's prebundled version forces a # downgrade that deletes the prebundled copy other extensions symlink into (nvbugs 6410989). @@ -90,7 +90,7 @@ dependencies = [ # The importers extra carries the mesh-processing deps (coacd, fast-simplification, # ...) that honoring USD-authored ``physics:approximation`` requires. "newton[sim,importers]>=1.2.0", - # Loose bound co-resolves with isaacsim's ==0.2.0; the override below forces >=0.4.0. + # Loose bound co-resolves with isaacsim's ==0.2.0; the override below forces >=0.4.1. "newton-usd-schemas>=0.2.0", "PyOpenGL-accelerate>=3.1.0", # ----- newton interactive viewer GUI (in base, no extra required) ----- @@ -201,8 +201,8 @@ torchaudio = "2.11.0" ovphysx = "0.5.9" ovrtx = ">=0.4.0,<0.5.0" ovstage = ">=0.1.0,<0.2.0" -newton = "10402ecbaf2d8afda00507123155042cbcb5c3fb" -warp = "1.16.0.dev20260723" +newton = "fd8d9d4e184b1298f989be13b0fbe3b59c5bbd58" +warp = "1.16.0" [tool.ruff] line-length = 120 @@ -383,9 +383,9 @@ override-dependencies = [ "numpy>=2", "mujoco~=3.11.0", "mujoco-warp~=3.11.0", - "newton[sim,importers] @ git+https://github.com/newton-physics/newton.git@10402ecbaf2d8afda00507123155042cbcb5c3fb", + "newton[sim,importers] @ git+https://github.com/newton-physics/newton.git@fd8d9d4e184b1298f989be13b0fbe3b59c5bbd58", # Force the Newton-matched schemas over isaacsim's ==0.2.0 pin. - "newton-usd-schemas>=0.4.0", + "newton-usd-schemas>=0.4.1", "torch==2.11.0", "torchvision==0.26.0", "torchaudio==2.11.0", diff --git a/source/isaaclab/changelog.d/mym-lift-soft.rst b/source/isaaclab/changelog.d/mym-lift-soft.rst index d38ac6f99fd9..f41e81e2af8b 100644 --- a/source/isaaclab/changelog.d/mym-lift-soft.rst +++ b/source/isaaclab/changelog.d/mym-lift-soft.rst @@ -3,3 +3,9 @@ Added * Added :attr:`~isaaclab.sim.spawners.meshes.MeshCuboidCfg.edge_refinement` to control cuboid surface mesh refinement. + +Changed +^^^^^^^ + +* Changed the pinned Newton revision, Warp version, and Newton USD schemas floor to support full-surface + rigid-soft contact handling. Run ``uv sync`` to update existing environments. diff --git a/source/isaaclab/test/install_ci/uv_pip/uv-overrides.txt b/source/isaaclab/test/install_ci/uv_pip/uv-overrides.txt index d8062b2cc483..045e4fe4fe5e 100644 --- a/source/isaaclab/test/install_ci/uv_pip/uv-overrides.txt +++ b/source/isaaclab/test/install_ci/uv_pip/uv-overrides.txt @@ -1,8 +1,8 @@ numpy>=2 mujoco~=3.11.0 mujoco-warp~=3.11.0 -newton[sim,importers] @ git+https://github.com/newton-physics/newton.git@10402ecbaf2d8afda00507123155042cbcb5c3fb -newton-usd-schemas>=0.4.0 +newton[sim,importers] @ git+https://github.com/newton-physics/newton.git@fd8d9d4e184b1298f989be13b0fbe3b59c5bbd58 +newton-usd-schemas>=0.4.1 torch==2.11.0 torchvision==0.26.0 torchaudio==2.11.0 diff --git a/tools/wheel_builder/uv-overrides.txt b/tools/wheel_builder/uv-overrides.txt index d8062b2cc483..045e4fe4fe5e 100644 --- a/tools/wheel_builder/uv-overrides.txt +++ b/tools/wheel_builder/uv-overrides.txt @@ -1,8 +1,8 @@ numpy>=2 mujoco~=3.11.0 mujoco-warp~=3.11.0 -newton[sim,importers] @ git+https://github.com/newton-physics/newton.git@10402ecbaf2d8afda00507123155042cbcb5c3fb -newton-usd-schemas>=0.4.0 +newton[sim,importers] @ git+https://github.com/newton-physics/newton.git@fd8d9d4e184b1298f989be13b0fbe3b59c5bbd58 +newton-usd-schemas>=0.4.1 torch==2.11.0 torchvision==0.26.0 torchaudio==2.11.0 diff --git a/uv.lock b/uv.lock index 6662d44c88a9..3d8dc138224d 100644 --- a/uv.lock +++ b/uv.lock @@ -44,8 +44,8 @@ prerelease-mode = "allow" overrides = [ { name = "mujoco", specifier = "~=3.11.0" }, { name = "mujoco-warp", specifier = "~=3.11.0" }, - { name = "newton", extras = ["sim", "importers"], git = "https://github.com/newton-physics/newton.git?rev=10402ecbaf2d8afda00507123155042cbcb5c3fb" }, - { name = "newton-usd-schemas", specifier = ">=0.4.0" }, + { name = "newton", extras = ["sim", "importers"], git = "https://github.com/newton-physics/newton.git?rev=fd8d9d4e184b1298f989be13b0fbe3b59c5bbd58" }, + { name = "newton-usd-schemas", specifier = ">=0.4.1" }, { name = "numpy", specifier = ">=2" }, { name = "torch", marker = "(platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'linux' and sys_platform != 'win32')", specifier = "==2.11.0" }, { name = "torch", marker = "platform_machine == 'aarch64' and sys_platform == 'linux'", specifier = "==2.11.0", index = "https://download.pytorch.org/whl/cu130" }, @@ -2637,7 +2637,7 @@ requires-dist = [ { name = "usd-core", marker = "platform_machine == 'AMD64' or platform_machine == 'x86_64'", specifier = ">=25.11,<26.0" }, { name = "usd-exchange", marker = "platform_machine == 'aarch64'", specifier = ">=2.2" }, { name = "viser", marker = "extra == 'viser'", specifier = ">=1.0.16" }, - { name = "warp-lang", specifier = "==1.16.0.dev20260723" }, + { name = "warp-lang", specifier = "==1.16.0" }, ] provides-extras = ["tetrahedralization", "video", "test", "sb3", "skrl", "rl-games", "rsl-rl", "viser", "rerun", "isaacsim", "ov", "ovphysx", "ovrtx", "mimic", "teleop", "rlinf", "all", "leapp"] @@ -4018,7 +4018,7 @@ wheels = [ [[package]] name = "newton" version = "1.5.0.dev0" -source = { git = "https://github.com/newton-physics/newton.git?rev=10402ecbaf2d8afda00507123155042cbcb5c3fb#10402ecbaf2d8afda00507123155042cbcb5c3fb" } +source = { git = "https://github.com/newton-physics/newton.git?rev=fd8d9d4e184b1298f989be13b0fbe3b59c5bbd58#fd8d9d4e184b1298f989be13b0fbe3b59c5bbd58" } dependencies = [ { name = "warp-lang", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-isaacsim') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-mimic') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ov') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ovphysx') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-test') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-mimic' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-isaacsim') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-mimic') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ov') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ovphysx') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-test') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-mimic' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-isaacsim') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-mimic') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ov') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ovphysx') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-test') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-mimic' and extra == 'extra-12-isaaclab-dev-teleop')" }, ] @@ -4061,11 +4061,11 @@ wheels = [ [[package]] name = "newton-usd-schemas" -version = "0.4.0" +version = "0.4.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/dd/12/a837c15d0ab7c8d89e67ff11fd0d071446f016ae5ff15b06b14baf5715db/newton_usd_schemas-0.4.0.tar.gz", hash = "sha256:d2706f90fdda1f1bbf66198fe41e3f4a9d3d78f86694cd882f5db4b3981383f3", size = 68824, upload-time = "2026-07-03T17:45:28.82Z" } +sdist = { url = "https://files.pythonhosted.org/packages/98/5f/aad1672263880d7e0ee3a7ce9b877ef16e99dba45fb5eb8084181eb7ee1e/newton_usd_schemas-0.4.1.tar.gz", hash = "sha256:1f6946ce2741d7a86ca8e3e84d7b64328c88b42a3b29160a7607b2ce0c837bf4", size = 69561, upload-time = "2026-07-30T17:40:41.505Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/b5/82058bdb0c64c94953d77010558700d14666ff577de2ca7e8daf8c59b306/newton_usd_schemas-0.4.0-py3-none-any.whl", hash = "sha256:212182de78191fed320631bf38b02a62467ddf77d5f43672b11758bcf2d86e47", size = 19333, upload-time = "2026-07-03T17:45:27.573Z" }, + { url = "https://files.pythonhosted.org/packages/9b/24/d1d44f682dbd28392ef0b6f9e6f9e40374c6009af06e494fa6a9308f79e2/newton_usd_schemas-0.4.1-py3-none-any.whl", hash = "sha256:8911e846c65448426609a2945db6d1135d33e84bf4107efd888a62287b1aa93d", size = 19424, upload-time = "2026-07-30T17:40:40.076Z" }, ] [[package]] @@ -7050,15 +7050,15 @@ wheels = [ [[package]] name = "warp-lang" -version = "1.16.0.dev20260723" +version = "1.16.0" source = { registry = "https://pypi.nvidia.com/" } dependencies = [ { name = "numpy", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-isaacsim') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-mimic') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ov') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ovphysx') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-test') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-12-isaaclab-dev-mimic' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-isaacsim') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-mimic') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ov') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ovphysx') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-test') or (sys_platform == 'linux' and extra == 'extra-12-isaaclab-dev-mimic' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-isaacsim') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-all' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-mimic') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ov') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-ovphysx') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-teleop') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-isaacsim' and extra == 'extra-12-isaaclab-dev-test') or (sys_platform == 'win32' and extra == 'extra-12-isaaclab-dev-mimic' and extra == 'extra-12-isaaclab-dev-teleop')" }, ] wheels = [ - { url = "https://pypi.nvidia.com/warp-lang/warp_lang-1.16.0.dev20260723-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:b722791dbb7ad0a2a13b76842b9d0c1c6acc160da8d70fcfc33b85aa2bbeb79f" }, - { url = "https://pypi.nvidia.com/warp-lang/warp_lang-1.16.0.dev20260723-py3-none-manylinux_2_34_aarch64.whl", hash = "sha256:51a01b4c367bed672532eccff9a6bb32ebc7b16bc20f7fab9c863ee667409af2" }, - { url = "https://pypi.nvidia.com/warp-lang/warp_lang-1.16.0.dev20260723-py3-none-win_amd64.whl", hash = "sha256:4d35531efb0c64066edb570a738d14976d049a3786a1bd0ba4d073f1d2c5b3aa" }, + { url = "https://pypi.nvidia.com/warp-lang/warp_lang-1.16.0-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:96449fc1e3b354185e2f09434fb794b5953ab2e8673b104d7e7f48d5d418bb35" }, + { url = "https://pypi.nvidia.com/warp-lang/warp_lang-1.16.0-py3-none-manylinux_2_34_aarch64.whl", hash = "sha256:d4715171bd6821436b82293d774c9a082ace08215c189572b4348d1e48fb8ee8" }, + { url = "https://pypi.nvidia.com/warp-lang/warp_lang-1.16.0-py3-none-win_amd64.whl", hash = "sha256:8690c9096e0a271985339d4aa4b37675e7d62852620ca861ac032dc85b124542" }, ] [[package]] From 71809207a144cd59adaa098c18ac3c30a9d5e72a Mon Sep 17 00:00:00 2001 From: Mike Yan Michelis Date: Wed, 5 Aug 2026 15:51:28 +0200 Subject: [PATCH 41/41] Fix: move video resolution --- .../config/franka_soft/franka_cloth_env_cfg.py | 2 +- .../lift/config/franka_soft/franka_soft_env_cfg.py | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) 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 06654f1a733c..f25956beae68 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 @@ -192,7 +192,7 @@ class FrankaClothRewardsCfg(FrankaSoftRewardsCfg): lifting_deformable = RewTerm( func=mdp.deformable_lifting, - params={"std": 0.1, "minimal_height": 0.1, "asset_cfg": SceneEntityCfg("deformable")}, + params={"std": 0.1, "minimal_height": 0.08, "asset_cfg": SceneEntityCfg("deformable")}, weight=5.0, ) 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 6f6bac6969fc..59dd518f0117 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 @@ -620,6 +620,12 @@ class FrankaSoftCameraSceneCfg(PresetCfg): default = newton_mjwarp_vbd_proxy +@configclass +class _FrankaSoftVisualizerCfg(VisualizerCfg): + window_width: int = 1920 + window_height: int = 1080 + + @configclass class FrankaSoftEnvCfg(ManagerBasedRLEnvCfg): """Manager-based RL environment: Franka Panda lifting a soft beam to a target pose.""" @@ -648,10 +654,10 @@ def __post_init__(self) -> None: self.viewer.eye = (0.75, 0.25, 0.65) self.viewer.lookat = (0.0, 0.75, 0.4) - self.sim.default_visualizer_cfg = VisualizerCfg(eye=self.viewer.eye, lookat=self.viewer.lookat) - - self.video_recorder.window_width = 1920 - self.video_recorder.window_height = 1080 + self.sim.default_visualizer_cfg = _FrankaSoftVisualizerCfg( + eye=self.viewer.eye, + lookat=self.viewer.lookat, + ) def play_mode(self): super().play_mode()