diff --git a/docs/source/_static/css/environment-browser.js b/docs/source/_static/css/environment-browser.js index dadfd2dca455..9ed0fc201eec 100644 --- a/docs/source/_static/css/environment-browser.js +++ b/docs/source/_static/css/environment-browser.js @@ -25,25 +25,24 @@ ["Isaac-Lift-Cable-Franka-Camera", "rsl_rl", "newton_mjwarp_vbd_proxy", "isaacsim_rtx,newton_renderer,ovrtx", "ik,joint", "tasks/manipulation/franka_lift_cable.jpg", false, {"*": ["joint"]}], ["Isaac-Lift-Cloth-Franka", "rsl_rl", "isaacsim_physx,newton_mjwarp_vbd_proxy", "", "ik,joint", "tasks/manipulation/franka_lift_cloth.jpg", false, {"*": ["joint"]}], ["Isaac-Lift-Cloth-Franka-Camera", "rsl_rl", "isaacsim_physx,newton_mjwarp_vbd_proxy", "isaacsim_rtx,newton_renderer,ovrtx", "ik,joint", "tasks/manipulation/franka_lift_cloth.jpg", false, {"*": ["joint"]}], - ["Isaac-Lift-Franka", "rsl_rl", "isaacsim_physx,newton_mjwarp,ovphysx", "", "cube,shapes", "", false, {"*": ["shapes"]}], + ["Isaac-Lift-Franka", "rsl_rl", "isaacsim_physx,newton_mjwarp,ovphysx", "", "arm_collisions,cube,shapes", "", false, {"*": ["shapes"]}], ["Isaac-Lift-KukaAllegro", "rsl_rl", "isaacsim_physx,newton_mjwarp,ovphysx", "", "cube,shapes", "tasks/manipulation/kuka_allegro_lift.jpg", false, {"*": ["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", "tasks/manipulation/kuka_allegro_lift.jpg", false, {"*": ["rgb64", "shapes", "single_camera"]}], ["Isaac-Lift-Soft-Franka", "rsl_rl", "isaacsim_physx,newton_mjwarp_vbd_proxy", "", "ik,joint", "newton/franka-mjwarp-vbd-coupling.png", false, {"*": ["joint"]}, {}, "tetrahedralization"], ["Isaac-Lift-Soft-Franka-Camera", "rsl_rl", "isaacsim_physx,newton_mjwarp_vbd_proxy", "isaacsim_rtx,newton_renderer,ovrtx", "ik,joint", "newton/franka-mjwarp-vbd-coupling.png", false, {"*": ["joint"]}, {}, "tetrahedralization"], - ["Isaac-Open-Drawer-Franka-Direct", "rl_games,rsl_rl,skrl", "isaacsim_physx,newton_mjwarp,ovphysx", "", "", "tasks/manipulation/franka_open_drawer.jpg"], - ["Isaac-Open-Drawer-Franka", "rl_games,rsl_rl,skrl", "isaacsim_physx,newton_mjwarp,ovphysx", "", "", "tasks/manipulation/franka_open_drawer.jpg"], + ["Isaac-Open-Drawer-Franka-Direct", "rl_games,rsl_rl,skrl", "isaacsim_physx,newton_mjwarp,ovphysx", "", "arm_collisions", "tasks/manipulation/franka_open_drawer.jpg"], + ["Isaac-Open-Drawer-Franka", "rl_games,rsl_rl,skrl", "isaacsim_physx,newton_mjwarp,ovphysx", "", "arm_collisions", "tasks/manipulation/franka_open_drawer.jpg"], ["Isaac-Pendulum-MARL-Direct", "rl_games,skrl", "isaacsim_physx,newton_kamino,newton_mjwarp,ovphysx", "", "", "tasks/classic/cart_double_pendulum.jpg", false, {}, {"skrl": "MAPPO"}], - ["Isaac-Reach-Franka", "rl_games,rsl_rl,skrl", "isaacsim_physx,newton_mjwarp,ovphysx", "", "diffik,diffik_abs,joint_pos,newton_ik", "tasks/manipulation/franka_reach.jpg", true, {"*": ["joint_pos"]}], - ["Isaac-Reach-Franka-OSC", "rsl_rl", "isaacsim_physx,newton_mjwarp,ovphysx", "", "diffik_abs", "tasks/manipulation/franka_reach.jpg", false, {"*": ["diffik_abs"]}], + ["Isaac-Reach-Franka", "rl_games,rsl_rl,skrl", "isaacsim_physx,newton_mjwarp,ovphysx", "", "arm_collisions,diffik,diffik_abs,joint_pos,newton_ik", "tasks/manipulation/franka_reach.jpg", true, {"*": ["joint_pos"]}], + ["Isaac-Reach-Franka-OSC", "rsl_rl", "isaacsim_physx,newton_mjwarp,ovphysx", "", "arm_collisions,diffik_abs", "tasks/manipulation/franka_reach.jpg", false, {"*": ["diffik_abs"]}], ["Isaac-Reach-UR10", "rl_games,rsl_rl,skrl", "isaacsim_physx,newton_mjwarp,ovphysx", "", "", "tasks/manipulation/ur10_reach.jpg", true], - ["Isaac-RenderBenchmark-Franka-Cabinet", "", "isaacsim_physx,newton_mjwarp", "isaacsim_rtx,newton_renderer,ovrtx", "albedo,depth,rgb,simple_shading_constant_diffuse,simple_shading_diffuse_mdl,simple_shading_full_mdl", "", false, {"*": ["rgb"]}], + ["Isaac-RenderBenchmark-Franka-Cabinet", "", "isaacsim_physx,newton_mjwarp,ovphysx", "isaacsim_rtx,newton_renderer,ovrtx", "albedo,depth,rgb,simple_shading_constant_diffuse,simple_shading_diffuse_mdl,simple_shading_full_mdl", "", false, {"*": ["rgb"]}], ["Isaac-Reorient-Cube-Allegro-Direct", "rl_games,rsl_rl,skrl", "isaacsim_physx,newton_mjwarp,ovphysx", "", "", "tasks/manipulation/allegro_cube.jpg", true], ["Isaac-Reorient-Cube-Allegro", "rl_games,rsl_rl,skrl", "isaacsim_physx,newton_mjwarp,ovphysx", "", "randomized,reset_only", "tasks/manipulation/allegro_cube.jpg", false, {"*": ["reset_only"]}], ["Isaac-Reorient-Cube-Shadow-Direct", "rl_games,rsl_rl,skrl", "isaacsim_physx,newton_mjwarp,ovphysx", "", "", "tasks/manipulation/shadow_cube.jpg"], ["Isaac-Reorient-Cube-Shadow", "rl_games,rsl_rl,skrl", "isaacsim_physx,newton_mjwarp,ovphysx", "", "asymmetric,randomized"], ["Isaac-Reorient-Cube-Shadow-Camera-Direct", "rl_games,rsl_rl", "isaacsim_physx,newton_mjwarp,ovphysx", "isaacsim_rtx,newton_renderer,ovrtx", "albedo,depth,full,rgb,semantic_segmentation,simple_shading_constant_diffuse,simple_shading_diffuse_mdl,simple_shading_full_mdl", "tasks/manipulation/shadow_cube.jpg", false, {"*": ["full"]}], ["Isaac-Reorient-Cube-Shadow-Camera", "rl_games,rsl_rl", "isaacsim_physx,newton_mjwarp,ovphysx", "isaacsim_rtx,newton_renderer,ovrtx", "albedo,depth,full,randomized,rgb,semantic_segmentation,simple_shading_constant_diffuse,simple_shading_diffuse_mdl,simple_shading_full_mdl", "", false, {"*": ["full"]}], - ["Isaac-Reorient-Franka", "rsl_rl", "isaacsim_physx,newton_mjwarp,ovphysx", "", "cube,shapes", "", false, {"*": ["shapes"]}], ["Isaac-Reorient-KukaAllegro", "rsl_rl", "isaacsim_physx,newton_mjwarp,ovphysx", "", "cube,shapes", "tasks/manipulation/kuka_allegro_reorient.jpg", false, {"*": ["shapes"]}], ["Isaac-Reorient-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", "tasks/manipulation/kuka_allegro_reorient.jpg", false, {"*": ["rgb64", "shapes", "single_camera"]}], ["Isaac-Shadow-Handover-Direct", "rl_games,rsl_rl,skrl", "isaacsim_physx,newton_mjwarp,ovphysx", "", "", "tasks/manipulation/shadow_hand_over.jpg", false, {}, {"skrl": "MAPPO"}], @@ -92,14 +91,14 @@ ["IsaacContrib-Lift-Cube-Franka-IK-Abs", "", "", "", "", "tasks/manipulation/franka_lift.jpg"], ["IsaacContrib-Lift-Cube-Franka-IK-Rel", "", "", "", "", "tasks/manipulation/franka_lift.jpg"], ["IsaacContrib-Lift-Cube-OpenArm", "rl_games,rsl_rl", "", "", "", "tasks/manipulation/openarm_uni_lift.jpg"], - ["IsaacContrib-Multitask-Manipulation", "rsl_rl", "", "", "", "tasks/manipulation/multitask_manipulation.jpg"], + ["IsaacContrib-Multitask-Manipulation", "rsl_rl", "", "", "arm_collisions", "tasks/manipulation/multitask_manipulation.jpg"], ["IsaacContrib-Navigation-3DObstacles-ARL-Robot-1", "rl_games,rsl_rl,skrl", "", "", "", "tasks/drone_arl/arl_robot_1_navigation.jpg"], ["IsaacContrib-Navigation-Flat-AnymalC", "rsl_rl,skrl", "", "", "", "tasks/navigation/anymal_c_nav.jpg"], ["IsaacContrib-NutPour-GR1T2-Pink-IK-Abs", "", "", "", ""], - ["IsaacContrib-Open-Drawer-Franka-IK-Abs", "rsl_rl", "isaacsim_physx,newton_mjwarp,ovphysx", "", ""], - ["IsaacContrib-Open-Drawer-Franka-IK-Rel", "rsl_rl", "isaacsim_physx,newton_mjwarp,ovphysx", "", ""], + ["IsaacContrib-Open-Drawer-Franka-IK-Abs", "rsl_rl", "isaacsim_physx,newton_mjwarp,ovphysx", "", "arm_collisions"], + ["IsaacContrib-Open-Drawer-Franka-IK-Rel", "rsl_rl", "isaacsim_physx,newton_mjwarp,ovphysx", "", "arm_collisions"], ["IsaacContrib-Open-Drawer-OpenArm", "rl_games,rsl_rl", "", "", "", "tasks/manipulation/openarm_uni_open_drawer.jpg"], - ["IsaacContrib-PickPlace-FixedBaseUpperBodyIK-G1-Abs", "", "", "", "", "tasks/manipulation/g1_pick_place_fixed_base.jpg"], + ["IsaacContrib-PickPlace-FixedBaseUpperBodyIK-G1-Abs", "", "", "isaacsim_rtx,newton_renderer,ovrtx", "", "tasks/manipulation/g1_pick_place_fixed_base.jpg"], ["IsaacContrib-PickPlace-G1-InspireFTP-Abs", "", "", "", "", "tasks/manipulation/g1_pick_place.jpg"], ["IsaacContrib-PickPlace-GR1T2-Abs", "", "", "isaacsim_rtx,newton_renderer,ovrtx", "", "tasks/manipulation/gr-1_pick_place.jpg"], ["IsaacContrib-PickPlace-GR1T2-WaistEnabled-Abs", "", "", "", "", "tasks/manipulation/gr-1_pick_place_waist.jpg"], @@ -336,7 +335,6 @@ [/Reach-UR10/, "tasks/manipulation/ur10_reach.jpg"], [/Reorient-Cube-Allegro/, "tasks/manipulation/allegro_cube.jpg"], [/Reorient-Cube-Shadow/, "tasks/manipulation/shadow_cube.jpg"], - [/Reorient-Franka/, "tasks/manipulation/franka_lift.jpg"], [/Reorient-KukaAllegro/, "tasks/manipulation/kuka_allegro_reorient.jpg"], [/Shadow-Handover/, "tasks/manipulation/shadow_hand_over.jpg"], [/AnymalB/, "tasks/locomotion/anymal_b_flat.jpg"], diff --git a/examples/haply_teleoperation.py b/examples/haply_teleoperation.py index 129d9fa3e674..679150eac477 100644 --- a/examples/haply_teleoperation.py +++ b/examples/haply_teleoperation.py @@ -33,7 +33,7 @@ from isaaclab.utils import configclass, instantiate, replace from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR -from isaaclab_assets import FRANKA_PANDA_HIGH_PD_CFG +from isaaclab_assets import FRANKA_PANDA_FLAT_HIGH_PD_CFG if TYPE_CHECKING: from isaaclab.assets import Articulation, RigidObject @@ -127,10 +127,11 @@ class FrankaHaplySceneCfg(InteractiveSceneCfg): init_state=AssetBaseCfg.InitialStateCfg(pos=(0.50, 0.0, 1.05), rot=(0.707, 0, 0, 0.707)), ) - robot_cfg: ArticulationCfg = replace(FRANKA_PANDA_HIGH_PD_CFG, prim_path="{ENV_REGEX_NS}/Robot") + robot_cfg: ArticulationCfg = replace(FRANKA_PANDA_FLAT_HIGH_PD_CFG, prim_path="{ENV_REGEX_NS}/Robot") robot = instantiate(robot_cfg) robot.init_state.pos = (-0.02, 0.0, 1.05) robot.spawn.activate_contact_sensors = True + robot.spawn.variants["Physics"] = "mujoco" if args_cli.physics == "newton_mjwarp" else "physx" cube = RigidObjectCfg( prim_path="{ENV_REGEX_NS}/Cube", @@ -146,7 +147,7 @@ class FrankaHaplySceneCfg(InteractiveSceneCfg): ) left_finger_contact_sensor = ContactSensorCfg( - prim_path="{ENV_REGEX_NS}/Robot/panda_leftfinger", + prim_path="{ENV_REGEX_NS}/Robot/(Geometry/.*/)?panda_leftfinger", update_period=0.0, history_length=3, debug_vis=True, @@ -154,7 +155,7 @@ class FrankaHaplySceneCfg(InteractiveSceneCfg): ) right_finger_contact_sensor = ContactSensorCfg( - prim_path="{ENV_REGEX_NS}/Robot/panda_rightfinger", + prim_path="{ENV_REGEX_NS}/Robot/(Geometry/.*/)?panda_rightfinger", update_period=0.0, history_length=3, debug_vis=True, diff --git a/examples/heterogeneous_scene.py b/examples/heterogeneous_scene.py index b7ec74d2f509..a4d8a334fe47 100644 --- a/examples/heterogeneous_scene.py +++ b/examples/heterogeneous_scene.py @@ -95,7 +95,6 @@ "IsaacContrib-Navigation-Flat-AnymalC", # arm and hand manipulation "Isaac-Lift-Franka", - "Isaac-Reorient-Franka", "Isaac-Lift-KukaAllegro", "Isaac-Reorient-KukaAllegro", "Isaac-Open-Drawer-Franka", diff --git a/scripts/tutorials/05_controllers/run_diff_ik.py b/scripts/tutorials/05_controllers/run_diff_ik.py index 9f91b6596195..2acd470b6fb6 100644 --- a/scripts/tutorials/05_controllers/run_diff_ik.py +++ b/scripts/tutorials/05_controllers/run_diff_ik.py @@ -53,7 +53,7 @@ ## # Pre-defined configs ## -from isaaclab_assets import FRANKA_PANDA_HIGH_PD_CFG, UR10_CFG # isort:skip +from isaaclab_assets import FRANKA_PANDA_FLAT_HIGH_PD_CFG, UR10_CFG # isort:skip @configclass @@ -82,7 +82,7 @@ class TableTopSceneCfg(InteractiveSceneCfg): # articulation if args_cli.robot == "franka_panda": - robot = replace(FRANKA_PANDA_HIGH_PD_CFG, prim_path="{ENV_REGEX_NS}/Robot") + robot = replace(FRANKA_PANDA_FLAT_HIGH_PD_CFG, prim_path="{ENV_REGEX_NS}/Robot") elif args_cli.robot == "ur10": robot = replace(UR10_CFG, prim_path="{ENV_REGEX_NS}/Robot") else: diff --git a/scripts/tutorials/05_controllers/run_osc.py b/scripts/tutorials/05_controllers/run_osc.py index f2dddf957364..03b8e886c75e 100644 --- a/scripts/tutorials/05_controllers/run_osc.py +++ b/scripts/tutorials/05_controllers/run_osc.py @@ -57,7 +57,7 @@ ## # Pre-defined configs ## -from isaaclab_assets import FRANKA_PANDA_HIGH_PD_CFG # isort:skip +from isaaclab_assets import FRANKA_PANDA_FLAT_HIGH_PD_CFG # isort:skip @configclass @@ -97,11 +97,9 @@ class SceneCfg(InteractiveSceneCfg): debug_vis=False, ) - robot = replace(FRANKA_PANDA_HIGH_PD_CFG, prim_path="{ENV_REGEX_NS}/Robot") - robot.actuators["panda_shoulder"].stiffness = 0.0 - robot.actuators["panda_shoulder"].damping = 0.0 - robot.actuators["panda_forearm"].stiffness = 0.0 - robot.actuators["panda_forearm"].damping = 0.0 + robot = replace(FRANKA_PANDA_FLAT_HIGH_PD_CFG, prim_path="{ENV_REGEX_NS}/Robot") + robot.actuators["panda_arm"].stiffness = 0.0 + robot.actuators["panda_arm"].damping = 0.0 robot.spawn.rigid_props.disable_gravity = True diff --git a/source/isaaclab/changelog.d/maximiliank-diffik-action-offset.minor.rst b/source/isaaclab/changelog.d/maximiliank-diffik-action-offset.minor.rst new file mode 100644 index 000000000000..2af70b827142 --- /dev/null +++ b/source/isaaclab/changelog.d/maximiliank-diffik-action-offset.minor.rst @@ -0,0 +1,5 @@ +Added +^^^^^ + +* Added an ``offset`` to ``DifferentialInverseKinematicsActionCfg`` so normalized policies can use + an affine task-space action transform. The default is zero and preserves existing configurations. diff --git a/source/isaaclab/isaaclab/envs/mdp/actions/actions_cfg.py b/source/isaaclab/isaaclab/envs/mdp/actions/actions_cfg.py index 6bd98583073e..ecb0ef1dabe2 100644 --- a/source/isaaclab/isaaclab/envs/mdp/actions/actions_cfg.py +++ b/source/isaaclab/isaaclab/envs/mdp/actions/actions_cfg.py @@ -325,6 +325,8 @@ class OffsetCfg: """Offset of target frame w.r.t. to the body frame. Defaults to None, in which case no offset is applied.""" scale: float | tuple[float, ...] = 1.0 """Scale factor for the action. Defaults to 1.0.""" + offset: float | tuple[float, ...] = 0.0 + """Offset applied to the scaled action in controller-specific units. Defaults to 0.0.""" controller: DifferentialIKControllerCfg = MISSING """The configuration for the differential IK controller.""" diff --git a/source/isaaclab/isaaclab/envs/mdp/actions/task_space_actions.py b/source/isaaclab/isaaclab/envs/mdp/actions/task_space_actions.py index 3b077356cea5..e089047e49b7 100644 --- a/source/isaaclab/isaaclab/envs/mdp/actions/task_space_actions.py +++ b/source/isaaclab/isaaclab/envs/mdp/actions/task_space_actions.py @@ -34,15 +34,15 @@ class DifferentialInverseKinematicsAction(ActionTerm): r"""Inverse Kinematics action term. - This action term performs pre-processing of the raw actions using scaling transformation. + This action term pre-processes raw actions using an affine transformation. .. math:: - \text{action} = \text{scaling} \times \text{input action} + \text{action} = \text{offset} + \text{scaling} \times \text{input action} \text{joint position} = J^{-} \times \text{action} - where :math:`\text{scaling}` is the scaling applied to the input action, and :math:`\text{input action}` - is the input action from the user, :math:`J` is the Jacobian over the articulation's actuated joints, - and \text{joint position} is the desired joint position command for the articulation's joints. + where :math:`\text{offset}` and :math:`\text{scaling}` define the affine transformation, + :math:`\text{input action}` is the input action from the user, :math:`J` is the Jacobian over the + articulation's actuated joints, and \text{joint position} is the desired joint position command. """ cfg: actions_cfg.DifferentialInverseKinematicsActionCfg @@ -50,7 +50,9 @@ class DifferentialInverseKinematicsAction(ActionTerm): _asset: Articulation """The articulation asset on which the action term is applied.""" _scale: torch.Tensor - """The scaling factor applied to the input action. Shape is (1, action_dim).""" + """The scaling factor applied to the input action. Shape is (num_envs, action_dim).""" + _offset: torch.Tensor + """The offset applied to the scaled action. Shape is (num_envs, action_dim).""" _clip: torch.Tensor """The clip applied to the input action.""" @@ -100,9 +102,11 @@ def __init__(self, cfg: actions_cfg.DifferentialInverseKinematicsActionCfg, env: # owned buffer; _compute_frame_jacobian mutates this, not the data-layer view. self._jacobian_b = torch.zeros(self.num_envs, 6, len(self._jacobi_joint_ids), device=self.device) - # save the scale as tensors + # save the affine transform as tensors self._scale = torch.zeros((self.num_envs, self.action_dim), device=self.device) self._scale[:] = torch.tensor(self.cfg.scale, device=self.device) + self._offset = torch.zeros((self.num_envs, self.action_dim), device=self.device) + self._offset[:] = torch.tensor(self.cfg.offset, device=self.device) # convert the fixed offsets to torch tensors of batched shape if self.cfg.body_offset is not None: @@ -160,6 +164,7 @@ def IO_descriptor(self) -> GenericActionIODescriptor: - body_name: The name of the body. - joint_names: The names of the joints. - scale: The scale of the action term. + - offset: The offset of the action term. - clip: The clip of the action term. - controller_cfg: The configuration of the controller. - body_offset: The offset of the body. @@ -174,6 +179,7 @@ def IO_descriptor(self) -> GenericActionIODescriptor: self._IO_descriptor.body_name = self._body_name self._IO_descriptor.joint_names = self._joint_names self._IO_descriptor.scale = self._scale + self._IO_descriptor.offset = self._offset if self.cfg.clip is not None: self._IO_descriptor.clip = self.cfg.clip else: @@ -189,7 +195,7 @@ def IO_descriptor(self) -> GenericActionIODescriptor: def process_actions(self, actions: torch.Tensor): # store the raw actions self._raw_actions[:] = actions - self._processed_actions[:] = self.raw_actions * self._scale + self._processed_actions[:] = self.raw_actions * self._scale + self._offset if self.cfg.clip is not None: self._processed_actions = torch.clamp( self._processed_actions, min=self._clip[:, :, 0], max=self._clip[:, :, 1] diff --git a/source/isaaclab/isaaclab/sim/utils/queries.py b/source/isaaclab/isaaclab/sim/utils/queries.py index 0994b049c44c..b73ae060289a 100644 --- a/source/isaaclab/isaaclab/sim/utils/queries.py +++ b/source/isaaclab/isaaclab/sim/utils/queries.py @@ -387,6 +387,7 @@ def resolve_matching_prims_from_source( env_regex_ns: str = "/World/envs/env_[^/]+", raise_if_no_matches: bool = True, traverse_instance_prims: bool = True, + prefer_direct_matches: bool = False, ) -> list[tuple[Usd.Prim, str]]: """Resolve matching prims from a single(source) instance when multiple instances are present. @@ -402,6 +403,8 @@ def resolve_matching_prims_from_source( env_regex_ns: Namespace pattern that marks one instance root when no clone plan applies. raise_if_no_matches: Whether to raise if no prim matches ``path_expr``. Defaults to True. traverse_instance_prims: Whether to traverse instance prims when applying ``predicate``. + prefer_direct_matches: Prefer matching prims named by ``path_expr`` over their matching descendants. + Descendants remain a fallback when no direct prim satisfies ``predicate``. Returns: A list of ``(source_prim, destination_expr)`` pairs. Empty only when @@ -481,6 +484,11 @@ def resolve_matching_prims_from_source( unique_matches.setdefault(child_path, (child, dest + child_path[len(source_path) :])) results = list(unique_matches.values()) + if prefer_direct_matches: + direct_matches = [pair for pair in results if re.fullmatch(path_expr, pair[0].GetPath().pathString)] + if direct_matches: + results = direct_matches + if expected_num_matches is not None and len(results) != expected_num_matches: raise RuntimeError(f"Expected {expected_num_matches} prims at '{path_expr}', found {len(results)}.") if raise_if_no_matches and not results: diff --git a/source/isaaclab/test/controllers/test_differential_ik.py b/source/isaaclab/test/controllers/test_differential_ik.py index 4b91b1158cff..4df4cad5d92d 100644 --- a/source/isaaclab/test/controllers/test_differential_ik.py +++ b/source/isaaclab/test/controllers/test_differential_ik.py @@ -31,7 +31,7 @@ ## # Pre-defined configs ## -from isaaclab_assets import FRANKA_PANDA_HIGH_PD_CFG, UR10_CFG # isort:skip +from isaaclab_assets import FRANKA_PANDA_FLAT_HIGH_PD_CFG, UR10_CFG # isort:skip pytestmark = pytest.mark.integration @@ -441,7 +441,7 @@ def test_franka_ik_pose_abs(sim): sim_context, num_envs, ee_pose_b_des_set = sim # Create robot instance - robot_cfg = replace(FRANKA_PANDA_HIGH_PD_CFG, prim_path="{ENV_REGEX_NS}/Robot") + robot_cfg = replace(FRANKA_PANDA_FLAT_HIGH_PD_CFG, prim_path="{ENV_REGEX_NS}/Robot") robot = Articulation(cfg=robot_cfg) # Create IK controller diff --git a/source/isaaclab/test/controllers/test_operational_space.py b/source/isaaclab/test/controllers/test_operational_space.py index 2dd6e63e4028..76ed5a42165f 100644 --- a/source/isaaclab/test/controllers/test_operational_space.py +++ b/source/isaaclab/test/controllers/test_operational_space.py @@ -50,7 +50,7 @@ subtract_frame_transforms, ) -from isaaclab_assets import FRANKA_PANDA_CFG, G1_29DOF_CFG # isort:skip +from isaaclab_assets import FRANKA_PANDA_LEGACY_CFG, G1_29DOF_CFG # isort:skip pytestmark = pytest.mark.integration @@ -95,7 +95,8 @@ def sim(): # clone the env xform cloner.usd_replicate(stage, [env_fmt.format(0)], [env_fmt], env_ids, positions=env_origins) - robot_cfg = replace(FRANKA_PANDA_CFG, prim_path="{ENV_REGEX_NS}/Robot") + # Keep controller regressions on their original plant; canonical asset parity is tested by each backend. + robot_cfg = replace(FRANKA_PANDA_LEGACY_CFG, prim_path="{ENV_REGEX_NS}/Robot") # Explicit torque actuators enforce effort limits on the commands sent to the simulator. for actuator_name in ("panda_shoulder", "panda_forearm"): actuator_cfg = robot_cfg.actuators[actuator_name] diff --git a/source/isaaclab/test/envs/test_diffik_action_processing.py b/source/isaaclab/test/envs/test_diffik_action_processing.py new file mode 100644 index 000000000000..8a6d9634a9ea --- /dev/null +++ b/source/isaaclab/test/envs/test_diffik_action_processing.py @@ -0,0 +1,56 @@ +# 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 + +"""Unit tests for differential inverse-kinematics action processing.""" + +from types import SimpleNamespace + +import pytest +import torch + +from isaaclab.controllers import DifferentialIKControllerCfg +from isaaclab.envs.mdp.actions.actions_cfg import DifferentialInverseKinematicsActionCfg +from isaaclab.envs.mdp.actions.task_space_actions import DifferentialInverseKinematicsAction + +pytestmark = pytest.mark.unit + + +def test_process_actions_applies_scale_then_offset(monkeypatch: pytest.MonkeyPatch) -> None: + """DiffIK actions use the configured affine transformation before reaching the controller.""" + num_envs = 2 + controller_cfg = DifferentialIKControllerCfg(command_type="position", use_relative_mode=False, ik_method="dls") + cfg = DifferentialInverseKinematicsActionCfg( + asset_name="robot", + joint_names=[".*"], + body_name="tool", + scale=(0.5, 2.0, -1.0), + offset=(0.1, -0.2, 0.3), + controller=controller_cfg, + ) + asset = SimpleNamespace( + find_joints=lambda _names: ([0, 1, 2], ["joint1", "joint2", "joint3"]), + find_bodies=lambda _name: ([0], ["tool"]), + num_joints=3, + num_base_dofs=0, + is_fixed_base=False, + ) + env = SimpleNamespace(scene={"robot": asset}, num_envs=num_envs, device="cpu") + action = DifferentialInverseKinematicsAction(cfg, env) + ee_pos = torch.zeros(num_envs, 3) + ee_quat = torch.tensor([[0.0, 0.0, 0.0, 1.0]]).repeat(num_envs, 1) + monkeypatch.setattr(action, "_compute_frame_pose", lambda: (ee_pos, ee_quat)) + + raw_actions = torch.tensor([[1.0, -2.0, 0.5], [-1.0, 0.25, -0.5]]) + expected = torch.tensor([[0.6, -4.2, -0.2], [-0.4, 0.3, 0.8]]) + + action.process_actions(raw_actions) + + torch.testing.assert_close(action.raw_actions, raw_actions) + torch.testing.assert_close(action.processed_actions, expected) + torch.testing.assert_close(action._ik_controller.ee_pos_des, expected) + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/source/isaaclab/test/sim/test_cloner.py b/source/isaaclab/test/sim/test_cloner.py index 51bfc38077f8..7ea2acef280c 100644 --- a/source/isaaclab/test/sim/test_cloner.py +++ b/source/isaaclab/test/sim/test_cloner.py @@ -285,3 +285,11 @@ def test_resolve_matching_prims_from_source(sim, with_clone_plan): "/World/envs/env_[^/]+/Robot/foo/bar", "/World/envs/env_[^/]+/Robot/other/bar", ] + + matches = queries.resolve_matching_prims_from_source( + r"/World/envs/env_[^/]+/Robot/foo", + predicate=lambda prim: prim.GetName() in {"foo", "bar"}, + expected_num_matches=1, + prefer_direct_matches=True, + ) + assert [prim.GetPath().pathString for prim, _ in matches] == ["/World/envs/env_0/Robot/foo"] diff --git a/source/isaaclab_assets/changelog.d/maximiliank-franka-flat-config.minor.rst b/source/isaaclab_assets/changelog.d/maximiliank-franka-flat-config.minor.rst new file mode 100644 index 000000000000..6618848f9c25 --- /dev/null +++ b/source/isaaclab_assets/changelog.d/maximiliank-franka-flat-config.minor.rst @@ -0,0 +1,14 @@ +Added +^^^^^ + +* Added ``FRANKA_PANDA_FLAT_CFG`` and ``FRANKA_PANDA_FLAT_HIGH_PD_CFG`` for the shared flat Franka + asset. These configurations selected gripper-only colliders and the ``panda_arm`` actuator group; + select the ``Physics`` variant for the target backend. + +Deprecated +^^^^^^^^^^ + +* Deprecated ``FRANKA_PANDA_CFG``, ``FRANKA_PANDA_HIGH_PD_CFG``, and + ``FRANKA_PANDA_MENAGERIE_CFG``. Their previous asset and actuator contracts remained available + during the deprecation window. Use the corresponding ``FRANKA_PANDA_FLAT_*`` configuration to + migrate to the shared flat asset, and replace shoulder/forearm actuator overrides with ``panda_arm``. diff --git a/source/isaaclab_assets/isaaclab_assets/__init__.pyi b/source/isaaclab_assets/isaaclab_assets/__init__.pyi index 2c491a499d7d..b38edb800a5f 100644 --- a/source/isaaclab_assets/isaaclab_assets/__init__.pyi +++ b/source/isaaclab_assets/isaaclab_assets/__init__.pyi @@ -22,7 +22,10 @@ __all__ = [ "GR1T2_CFG", "GR1T2_HIGH_PD_CFG", "FRANKA_PANDA_CFG", + "FRANKA_PANDA_FLAT_CFG", + "FRANKA_PANDA_FLAT_HIGH_PD_CFG", "FRANKA_PANDA_HIGH_PD_CFG", + "FRANKA_PANDA_LEGACY_CFG", "FRANKA_PANDA_MENAGERIE_CFG", "FRANKA_ROBOTIQ_GRIPPER_CFG", "FOURBAR_POLE_CFG", @@ -89,7 +92,10 @@ from .robots import ( GR1T2_CFG, GR1T2_HIGH_PD_CFG, FRANKA_PANDA_CFG, + FRANKA_PANDA_FLAT_CFG, + FRANKA_PANDA_FLAT_HIGH_PD_CFG, FRANKA_PANDA_HIGH_PD_CFG, + FRANKA_PANDA_LEGACY_CFG, FRANKA_PANDA_MENAGERIE_CFG, FRANKA_ROBOTIQ_GRIPPER_CFG, FOURBAR_POLE_CFG, diff --git a/source/isaaclab_assets/isaaclab_assets/robots/__init__.pyi b/source/isaaclab_assets/isaaclab_assets/robots/__init__.pyi index 023c8e801230..e54481a15ee5 100644 --- a/source/isaaclab_assets/isaaclab_assets/robots/__init__.pyi +++ b/source/isaaclab_assets/isaaclab_assets/robots/__init__.pyi @@ -22,7 +22,10 @@ __all__ = [ "GR1T2_CFG", "GR1T2_HIGH_PD_CFG", "FRANKA_PANDA_CFG", + "FRANKA_PANDA_FLAT_CFG", + "FRANKA_PANDA_FLAT_HIGH_PD_CFG", "FRANKA_PANDA_HIGH_PD_CFG", + "FRANKA_PANDA_LEGACY_CFG", "FRANKA_PANDA_MENAGERIE_CFG", "FRANKA_ROBOTIQ_GRIPPER_CFG", "FOURBAR_POLE_CFG", @@ -82,7 +85,10 @@ from .cassie import CASSIE_CFG from .fourier import GR1T2_CFG, GR1T2_HIGH_PD_CFG from .franka import ( FRANKA_PANDA_CFG, + FRANKA_PANDA_FLAT_CFG, + FRANKA_PANDA_FLAT_HIGH_PD_CFG, FRANKA_PANDA_HIGH_PD_CFG, + FRANKA_PANDA_LEGACY_CFG, FRANKA_PANDA_MENAGERIE_CFG, FRANKA_ROBOTIQ_GRIPPER_CFG, ) diff --git a/source/isaaclab_assets/isaaclab_assets/robots/franka.py b/source/isaaclab_assets/isaaclab_assets/robots/franka.py index c8749fab4fdd..11849e12b234 100644 --- a/source/isaaclab_assets/isaaclab_assets/robots/franka.py +++ b/source/isaaclab_assets/isaaclab_assets/robots/franka.py @@ -7,14 +7,23 @@ The following configurations are available: -* :obj:`FRANKA_PANDA_CFG`: Franka Emika Panda robot with Panda hand -* :obj:`FRANKA_PANDA_MENAGERIE_CFG`: Franka Emika Panda robot converted from MuJoCo Menagerie -* :obj:`FRANKA_PANDA_HIGH_PD_CFG`: Franka Emika Panda robot with Panda hand with stiffer PD control +* :obj:`FRANKA_PANDA_FLAT_CFG`: Shared flat asset for maintained Franka tasks +* :obj:`FRANKA_PANDA_FLAT_HIGH_PD_CFG`: Flat asset with stiffer PD control +* :obj:`FRANKA_PANDA_LEGACY_CFG`: Legacy Franka Emika Panda asset configuration +* :obj:`FRANKA_PANDA_CFG`: Deprecated alias retaining the legacy asset and actuators +* :obj:`FRANKA_PANDA_HIGH_PD_CFG`: Deprecated legacy high-PD configuration +* :obj:`FRANKA_PANDA_MENAGERIE_CFG`: Deprecated nested-instance Menagerie configuration * :obj:`FRANKA_ROBOTIQ_GRIPPER_CFG`: Franka robot with Robotiq_2f_85 gripper +The old public names retain their original configuration contracts for the deprecation window. Use +``FRANKA_PANDA_FLAT_CFG`` for new code and select its ``Physics`` variant for the chosen backend. + Reference: https://github.com/frankaemika/franka_ros """ +import warnings +from typing import TYPE_CHECKING + from isaaclab_newton.sim.schemas import NewtonArticulationCfg from isaaclab_physx.sim.schemas import PhysxArticulationCfg, PhysxRigidBodyCfg @@ -28,7 +37,7 @@ # Configuration ## -FRANKA_PANDA_CFG = ArticulationCfg( +FRANKA_PANDA_LEGACY_CFG = ArticulationCfg( spawn=sim_utils.UsdFileCfg( usd_path=f"{ISAACLAB_NUCLEUS_DIR}/Robots/FrankaEmika/Legacy/panda_instanceable.usd", activate_contact_sensors=False, @@ -77,45 +86,120 @@ }, soft_joint_pos_limit_factor=1.0, ) -"""Configuration of Franka Emika Panda robot.""" +"""Configuration of the legacy Franka Emika Panda robot asset.""" -FRANKA_PANDA_MENAGERIE_CFG = clone(FRANKA_PANDA_CFG) -FRANKA_PANDA_MENAGERIE_CFG.spawn.usd_path = f"{ISAACLAB_NUCLEUS_DIR}/Robots/FrankaEmika/franka_panda.usda" -FRANKA_PANDA_MENAGERIE_CFG.actuators = { +FRANKA_PANDA_FLAT_CFG = clone(FRANKA_PANDA_LEGACY_CFG) +FRANKA_PANDA_FLAT_CFG.spawn.usd_path = f"{ISAACLAB_NUCLEUS_DIR}/Robots/FrankaEmika/franka_panda.usda" +FRANKA_PANDA_FLAT_CFG.spawn.variants = {"Physics": "physx", "Colliders": "gripper_only"} +next( + props for props in FRANKA_PANDA_FLAT_CFG.spawn.articulation_props if isinstance(props, PhysxArticulationCfg) +).enabled_self_collisions = False +next( + props for props in FRANKA_PANDA_FLAT_CFG.spawn.articulation_props if isinstance(props, NewtonArticulationCfg) +).self_collision_enabled = False +FRANKA_PANDA_FLAT_CFG.actuators = { "panda_arm": ImplicitActuatorCfg( joint_names_expr=["panda_joint[1-7]"], + joint_effort_limit={"panda_joint[1-4]": 100.0, "panda_joint[5-7]": 12.0}, joint_velocity_limit={"panda_joint[1-4]": 20.0, "panda_joint[5-7]": 25.0}, stiffness=None, damping=None, + viscous_friction=0.0, ), "panda_hand": ImplicitActuatorCfg( - joint_names_expr=["panda_finger_joint.*"], + joint_names_expr=["panda_finger_joint1"], + joint_effort_limit=200.0, stiffness=None, damping=None, + viscous_friction=0.0, + ), + "panda_finger2_passive": ImplicitActuatorCfg( + joint_names_expr=["panda_finger_joint2"], + joint_effort_limit=200.0, + stiffness=0.0, + damping=0.0, + viscous_friction=0.0, ), } -"""Configuration of the MuJoCo Menagerie-derived Franka Emika Panda robot. +"""Configuration of the Franka Emika Panda robot. -The converted model has different inertial and drive authoring from the legacy asset used by -:attr:`FRANKA_PANDA_CFG`. The solver velocity limits provide consistent behavior across physics -backends, while the arm and hand retain their USD-authored drives. +The flat asset contains PhysX and MuJoCo physics variants and gripper-only, primitive, and convex-hull +collider variants. The gripper-only collider variant is the default. Explicit solver properties keep +the actuator contract consistent across physics payloads. Only the leading finger has an active drive; +the authored mimic constraint moves the passive follower. The standalone configuration selects the PhysX +payload by default; direct Newton consumers must select the ``mujoco`` physics variant explicitly. """ -FRANKA_PANDA_HIGH_PD_CFG = clone(FRANKA_PANDA_CFG) -FRANKA_PANDA_HIGH_PD_CFG.spawn.rigid_props.disable_gravity = True -FRANKA_PANDA_HIGH_PD_CFG.actuators["panda_shoulder"].stiffness = 400.0 -FRANKA_PANDA_HIGH_PD_CFG.actuators["panda_shoulder"].damping = 80.0 -FRANKA_PANDA_HIGH_PD_CFG.actuators["panda_forearm"].stiffness = 400.0 -FRANKA_PANDA_HIGH_PD_CFG.actuators["panda_forearm"].damping = 80.0 +FRANKA_PANDA_FLAT_HIGH_PD_CFG = clone(FRANKA_PANDA_FLAT_CFG) +FRANKA_PANDA_FLAT_HIGH_PD_CFG.spawn.rigid_props.disable_gravity = True +FRANKA_PANDA_FLAT_HIGH_PD_CFG.actuators["panda_arm"].stiffness = 400.0 +FRANKA_PANDA_FLAT_HIGH_PD_CFG.actuators["panda_arm"].damping = 80.0 """Configuration of Franka Emika Panda robot with stiffer PD control. This configuration is useful for task-space control using differential IK. """ -FRANKA_ROBOTIQ_GRIPPER_CFG = clone(FRANKA_PANDA_CFG) +_FRANKA_PANDA_COMPAT_CFG = clone(FRANKA_PANDA_LEGACY_CFG) + + +_FRANKA_PANDA_LEGACY_HIGH_PD_CFG = clone(FRANKA_PANDA_LEGACY_CFG) +_FRANKA_PANDA_LEGACY_HIGH_PD_CFG.spawn.rigid_props.disable_gravity = True +for actuator in ("panda_shoulder", "panda_forearm"): + _FRANKA_PANDA_LEGACY_HIGH_PD_CFG.actuators[actuator].stiffness = 400.0 + _FRANKA_PANDA_LEGACY_HIGH_PD_CFG.actuators[actuator].damping = 80.0 + + +_FRANKA_PANDA_NESTED_MENAGERIE_CFG = clone(FRANKA_PANDA_LEGACY_CFG) +_FRANKA_PANDA_NESTED_MENAGERIE_CFG.spawn.usd_path = ( + f"{ISAACLAB_NUCLEUS_DIR}/Robots/FrankaEmika/franka_panda_nestedInstance.usda" +) +_FRANKA_PANDA_NESTED_MENAGERIE_CFG.actuators = { + "panda_arm": ImplicitActuatorCfg( + joint_names_expr=["panda_joint[1-7]"], + joint_velocity_limit={"panda_joint[1-4]": 20.0, "panda_joint[5-7]": 25.0}, + stiffness=None, + damping=None, + ), + "panda_hand": ImplicitActuatorCfg( + joint_names_expr=["panda_finger_joint.*"], + stiffness=None, + damping=None, + ), +} + + +_DEPRECATED_FRANKA_CFGS = { + "FRANKA_PANDA_CFG": (_FRANKA_PANDA_COMPAT_CFG, "FRANKA_PANDA_FLAT_CFG"), + "FRANKA_PANDA_HIGH_PD_CFG": (_FRANKA_PANDA_LEGACY_HIGH_PD_CFG, "FRANKA_PANDA_FLAT_HIGH_PD_CFG"), + "FRANKA_PANDA_MENAGERIE_CFG": (_FRANKA_PANDA_NESTED_MENAGERIE_CFG, "FRANKA_PANDA_FLAT_CFG"), +} + +if TYPE_CHECKING: + FRANKA_PANDA_CFG: ArticulationCfg + FRANKA_PANDA_HIGH_PD_CFG: ArticulationCfg + FRANKA_PANDA_MENAGERIE_CFG: ArticulationCfg + + +def __getattr__(name: str) -> ArticulationCfg: + if name not in _DEPRECATED_FRANKA_CFGS: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + cfg, replacement = _DEPRECATED_FRANKA_CFGS[name] + warnings.warn( + f"{name} is deprecated; use {replacement} for the flat Franka asset.", + FutureWarning, + stacklevel=2, + ) + return cfg + + +def __dir__() -> list[str]: + return sorted(set(globals()) | _DEPRECATED_FRANKA_CFGS.keys()) + + +FRANKA_ROBOTIQ_GRIPPER_CFG = clone(FRANKA_PANDA_LEGACY_CFG) FRANKA_ROBOTIQ_GRIPPER_CFG.spawn.usd_path = f"{ISAAC_NUCLEUS_DIR}/Robots/FrankaRobotics/FrankaPanda/franka.usd" FRANKA_ROBOTIQ_GRIPPER_CFG.spawn.variants = {"Gripper": "Robotiq_2F_85"} FRANKA_ROBOTIQ_GRIPPER_CFG.spawn.rigid_props.disable_gravity = True diff --git a/source/isaaclab_assets/test/test_valid_configs.py b/source/isaaclab_assets/test/test_valid_configs.py index 3832e196def1..b267e4862bd4 100644 --- a/source/isaaclab_assets/test/test_valid_configs.py +++ b/source/isaaclab_assets/test/test_valid_configs.py @@ -19,6 +19,26 @@ from isaaclab.test.utils import DeviceScope, test_devices import isaaclab_assets as lab_assets # noqa: F401 +import isaaclab_assets.robots.franka as franka_assets + + +def test_franka_legacy_configs_warn_and_keep_their_contract() -> None: + """Deprecated Franka names retain their previous asset and actuator contracts.""" + with pytest.warns(FutureWarning, match="FRANKA_PANDA_CFG.*deprecated"): + legacy_cfg = franka_assets.FRANKA_PANDA_CFG + assert legacy_cfg.spawn.usd_path.endswith("/Legacy/panda_instanceable.usd") + assert set(legacy_cfg.actuators) == {"panda_shoulder", "panda_forearm", "panda_hand"} + + with pytest.warns(FutureWarning, match="FRANKA_PANDA_HIGH_PD_CFG.*deprecated"): + high_pd_cfg = franka_assets.FRANKA_PANDA_HIGH_PD_CFG + assert high_pd_cfg.spawn.usd_path == legacy_cfg.spawn.usd_path + assert high_pd_cfg.actuators["panda_shoulder"].stiffness == 400.0 + assert high_pd_cfg.actuators["panda_forearm"].stiffness == 400.0 + + with pytest.warns(FutureWarning, match="FRANKA_PANDA_MENAGERIE_CFG.*deprecated"): + menagerie_cfg = franka_assets.FRANKA_PANDA_MENAGERIE_CFG + assert menagerie_cfg.spawn.usd_path.endswith("/franka_panda_nestedInstance.usda") + assert set(menagerie_cfg.actuators) == {"panda_arm", "panda_hand"} @pytest.fixture(scope="module") diff --git a/source/isaaclab_mimic/changelog.d/maximiliank-franka-world-ignore.rst b/source/isaaclab_mimic/changelog.d/maximiliank-franka-world-ignore.rst new file mode 100644 index 000000000000..55285412db57 --- /dev/null +++ b/source/isaaclab_mimic/changelog.d/maximiliank-franka-world-ignore.rst @@ -0,0 +1,5 @@ +Fixed +^^^^^ + +* Kept the robot and target out of the cuRobo Franka obstacle map by using the planner's default + world exclusions. diff --git a/source/isaaclab_mimic/isaaclab_mimic/motion_planners/curobo/curobo_planner_cfg.py b/source/isaaclab_mimic/isaaclab_mimic/motion_planners/curobo/curobo_planner_cfg.py index 98230c979a44..1275f60ae444 100644 --- a/source/isaaclab_mimic/isaaclab_mimic/motion_planners/curobo/curobo_planner_cfg.py +++ b/source/isaaclab_mimic/isaaclab_mimic/motion_planners/curobo/curobo_planner_cfg.py @@ -408,8 +408,6 @@ def franka_config(cls) -> "CuroboPlannerCfg": debug_planner=False, sphere_update_freq=5, motion_noise_scale=0.02, - # World extraction tuning for Franka envs - world_ignore_substrings=["/World/defaultGroundPlane", "/curobo"], ) @classmethod diff --git a/source/isaaclab_newton/changelog.d/maximiliank-franka-material-body-selection.rst b/source/isaaclab_newton/changelog.d/maximiliank-franka-material-body-selection.rst new file mode 100644 index 000000000000..f3763ed81629 --- /dev/null +++ b/source/isaaclab_newton/changelog.d/maximiliank-franka-material-body-selection.rst @@ -0,0 +1,4 @@ +Fixed +^^^^^ + +* Fixed Newton articulation material randomization for bodies whose shapes are interleaved in the native shape array. diff --git a/source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation.py b/source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation.py index e905d1adb0e7..66d8c4bb07d1 100644 --- a/source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation.py +++ b/source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation.py @@ -271,7 +271,7 @@ def num_bodies(self) -> int: @property def num_shapes_per_body(self) -> list[int]: - """Number of collision shapes per body in public body-name order. + """Number of shapes per body in public body-name order. Each element corresponds to the body at the same index in :attr:`body_names`. Backend-order counts are cached; a nonidentity body @@ -287,11 +287,11 @@ def num_shapes_per_body(self) -> list[int]: @property def backend_num_shapes_per_body(self) -> list[int]: - """Number of collision shapes per body in active backend solver-view order. + """Number of shapes per body in active backend solver-view order. Each element corresponds to the body at the same index in - :attr:`backend_body_names`, matching the shape axis of the backend - solver arrays. The counts are cached on first access. Use + :attr:`backend_body_names`. Shapes belonging to one body need not be + contiguous in the backend shape arrays. The counts are cached on first access. Use :attr:`num_shapes_per_body` for public body order. Returns: diff --git a/source/isaaclab_newton/isaaclab_newton/envs/mdp/events.py b/source/isaaclab_newton/isaaclab_newton/envs/mdp/events.py index e7c5faa973af..4724037e0869 100644 --- a/source/isaaclab_newton/isaaclab_newton/envs/mdp/events.py +++ b/source/isaaclab_newton/isaaclab_newton/envs/mdp/events.py @@ -63,14 +63,10 @@ def __init__(self, cfg: EventTermCfg, env: ManagerBasedEnv) -> None: self._restitution_binding = asset._root_view.get_attribute("shape_material_restitution", model)[:, 0] # type: ignore if isinstance(asset, assets.Articulation) and asset_cfg.body_ids != slice(None): - # Shape counts use backend body order. - num_shapes_per_body = asset.backend_num_shapes_per_body - shape_indices_list = [] backend_body_ids = asset.map_body_ids_to_backend(asset_cfg.body_ids) - for body_id in backend_body_ids: - start_idx = sum(num_shapes_per_body[:body_id]) - end_idx = start_idx + num_shapes_per_body[body_id] - shape_indices_list.extend(range(start_idx, end_idx)) + shape_indices_list = [ + shape for body_id in backend_body_ids for shape in asset.root_view.body_shapes[body_id] + ] self._shape_indices = torch.tensor(shape_indices_list, dtype=torch.long) else: self._shape_indices = torch.arange(self._friction_binding.shape[1], dtype=torch.long) diff --git a/source/isaaclab_newton/test/assets/test_articulation.py b/source/isaaclab_newton/test/assets/test_articulation.py index f0f4b5da6dbc..9cc6e1787eac 100644 --- a/source/isaaclab_newton/test/assets/test_articulation.py +++ b/source/isaaclab_newton/test/assets/test_articulation.py @@ -66,9 +66,14 @@ ## # Pre-defined configs ## -from isaaclab_assets import ANYMAL_C_CFG, FRANKA_PANDA_CFG, FRANKA_PANDA_HIGH_PD_CFG # isort:skip +from isaaclab_assets import ANYMAL_C_CFG, FRANKA_PANDA_FLAT_CFG, FRANKA_PANDA_FLAT_HIGH_PD_CFG # isort:skip from isaaclab_assets.robots.shadow_hand import SHADOW_HAND_NEWTON_CFG +_FRANKA_PANDA_NEWTON_CFG = FRANKA_PANDA_FLAT_CFG.copy() +_FRANKA_PANDA_NEWTON_CFG.spawn.variants = {"Physics": "mujoco", "Colliders": "gripper_only"} +_FRANKA_PANDA_HIGH_PD_NEWTON_CFG = FRANKA_PANDA_FLAT_HIGH_PD_CFG.copy() +_FRANKA_PANDA_HIGH_PD_NEWTON_CFG.spawn.variants = {"Physics": "mujoco", "Colliders": "gripper_only"} + SIM_CFGs = { "humanoid": SimulationCfg( physics=NewtonCfg( @@ -227,7 +232,7 @@ def generate_articulation_cfg( actuators={"body": ImplicitActuatorCfg(joint_names_expr=[".*"], stiffness=stiffness, damping=damping)}, ) elif articulation_type == "panda": - articulation_cfg = FRANKA_PANDA_CFG + articulation_cfg = _FRANKA_PANDA_NEWTON_CFG elif articulation_type == "anymal": articulation_cfg = ANYMAL_C_CFG elif articulation_type == "shadow_hand": @@ -438,7 +443,7 @@ def generate_articulation( def _setup_franka_at_home_pose(sim, *, zero_actuator_pd: bool = False, disable_gravity: bool = True): """Build a Franka articulation at its configured home pose. - Constructs :data:`FRANKA_PANDA_HIGH_PD_CFG`, optionally zeroes the + Constructs :data:`_FRANKA_PANDA_HIGH_PD_NEWTON_CFG`, optionally zeroes the arm-actuator PD gains, resets the simulator, and teleports the arm joints to :attr:`default_joint_pos` (the env reset path that normally does this is not invoked for standalone tests, so the @@ -447,23 +452,21 @@ def _setup_franka_at_home_pose(sim, *, zero_actuator_pd: bool = False, disable_g Args: sim: The simulation context to use. - zero_actuator_pd: If True, sets the panda_shoulder/panda_forearm - actuator stiffness and damping to zero. Used by the OSC test + zero_actuator_pd: If True, sets the ``panda_arm`` actuator stiffness + and damping to zero. Used by the OSC test so OSC's joint-effort output is not opposed by the implicit-PD's residual ``kp·(target − q)``. disable_gravity: Per-body gravity flag written to the spawn config. - :data:`FRANKA_PANDA_HIGH_PD_CFG` ships with gravity disabled; + :data:`_FRANKA_PANDA_HIGH_PD_NEWTON_CFG` ships with gravity disabled; pass False for tests where the arm must feel scene gravity. Returns: Tuple of ``(robot, ee_frame_idx, ee_jacobi_idx, arm_joint_ids)``. """ - cfg = replace(clone(FRANKA_PANDA_HIGH_PD_CFG), prim_path="/World/Env_[^/]*/Robot") + cfg = replace(_FRANKA_PANDA_HIGH_PD_NEWTON_CFG, prim_path="/World/Env_[^/]*/Robot") if zero_actuator_pd: - cfg.actuators["panda_shoulder"].stiffness = 0.0 - cfg.actuators["panda_shoulder"].damping = 0.0 - cfg.actuators["panda_forearm"].stiffness = 0.0 - cfg.actuators["panda_forearm"].damping = 0.0 + cfg.actuators["panda_arm"].stiffness = 0.0 + cfg.actuators["panda_arm"].damping = 0.0 cfg.spawn.rigid_props.disable_gravity = disable_gravity sim_utils.create_prim("/World/Env_0", "Xform", translation=(0.0, 0.0, 0.0)) clone_plan_from_env_0(CloneCfg(clone_template="/World/Env_{}"), (cfg,), 1, 0.0) @@ -1738,7 +1741,6 @@ def test_out_of_range_default_joint_state(sim, device, articulation_type, state_ quantity = "positions" if state_field == "joint_pos" else "velocities" replicate(sim.get_clone_plan()) with pytest.raises(ValueError, match=f"default {quantity} out of the limits"): - replicate(sim.get_clone_plan()) sim.reset() @@ -2511,29 +2513,31 @@ def test_write_joint_viscous_friction_to_sim(sim, num_articulations, device, art Static joint friction writes also propagate directly to the Newton model. """ - articulation_cfg = generate_articulation_cfg(articulation_type) - articulation_cfg.actuators["panda_shoulder"].viscous_friction = 0.25 + articulation_cfg = clone(generate_articulation_cfg(articulation_type)) + articulation_cfg.actuators["panda_arm"].viscous_friction = 0.25 + articulation_cfg.actuators["panda_arm"].damping = 2.0 articulation, _ = generate_articulation(articulation_cfg, num_articulations, device) replicate(sim.get_clone_plan()) sim.reset() - shoulder_joint_ids = articulation.actuators["panda_shoulder"].joint_indices - expected_viscous_friction = torch.full((articulation.num_instances, 4), 0.25, device=device) + arm_joint_ids = articulation.actuators["panda_arm"].joint_indices + expected_viscous_friction = torch.full((articulation.num_instances, len(arm_joint_ids)), 0.25, device=device) torch.testing.assert_close( - articulation.data.joint_viscous_friction_coeff.torch[:, shoulder_joint_ids], expected_viscous_friction + articulation.data.joint_viscous_friction_coeff.torch[:, arm_joint_ids], expected_viscous_friction ) torch.testing.assert_close( wp.to_torch(articulation.root_view.get_attribute("joint_damping", SimulationManager.get_model()))[ - :, 0, shoulder_joint_ids + :, 0, arm_joint_ids ], expected_viscous_friction, ) - expected_pd_damping = torch.full_like(expected_viscous_friction, 4.0) - torch.testing.assert_close(articulation.data.joint_damping.torch[:, shoulder_joint_ids], expected_pd_damping) + expected_pd_damping = torch.full_like(expected_viscous_friction, 2.0) + assert not torch.allclose(expected_pd_damping, expected_viscous_friction) + torch.testing.assert_close(articulation.data.joint_damping.torch[:, arm_joint_ids], expected_pd_damping) torch.testing.assert_close( wp.to_torch(articulation.root_view.get_attribute("joint_target_kd", SimulationManager.get_model()))[ - :, 0, shoulder_joint_ids + :, 0, arm_joint_ids ], expected_pd_damping, ) @@ -2840,7 +2844,7 @@ def test_heterogeneous_scene_per_view_shapes(sim, device, add_ground_plane, arti # per-articulation shape gate without that pre-existing quirk. num_per_type = 1 - franka_cfg = replace(FRANKA_PANDA_CFG, prim_path="/World/Env_[^/]*/Franka") + franka_cfg = replace(_FRANKA_PANDA_NEWTON_CFG, prim_path="/World/Env_[^/]*/Franka") anymal_cfg = replace(ANYMAL_C_CFG, prim_path="/World/Env_[^/]*/Anymal") anymal_cfg.init_state.pos = (0.0, 5.0, anymal_cfg.init_state.pos[2]) @@ -3142,7 +3146,7 @@ def test_get_gravity_compensation_forces_static_equilibrium(sim, num_articulatio # gravity-comp signal. Default Franka cfg has stiffness=80 / damping=4 # which would absorb gravity through PD bias and hide accessor bugs. cfg = replace(base_cfg, actuators={"all": ImplicitActuatorCfg(joint_names_expr=[".*"], stiffness=0.0, damping=0.0)}) - # FRANKA_PANDA_CFG has rigid_props.disable_gravity=False already, but be + # FRANKA_PANDA_FLAT_CFG has rigid_props.disable_gravity=False already, but be # defensive — gravity must be ON for τ_gc to have anything to cancel. cfg = replace(cfg, spawn=replace(cfg.spawn, rigid_props=replace(cfg.spawn.rigid_props, disable_gravity=False))) @@ -3160,7 +3164,7 @@ def test_get_gravity_compensation_forces_static_equilibrium(sim, num_articulatio articulation.write_joint_velocity_to_sim_index(velocity=default_qd) articulation.update(sim.cfg.dt) - # Default joint pose from FRANKA_PANDA_CFG bends the elbow + # Default joint pose from FRANKA_PANDA_FLAT_CFG bends the elbow # (joint2=-0.569, joint4=-2.81, joint6=3.04) so several links carry a # gravity load — τ_gc is non-trivial in this configuration. A natural- # hang pose (all zeros) would produce near-zero τ_gc and make this diff --git a/source/isaaclab_ov/changelog.d/maximiliank-frame-transformer-nested-bodies.rst b/source/isaaclab_ov/changelog.d/maximiliank-frame-transformer-nested-bodies.rst new file mode 100644 index 000000000000..02a80fb24471 --- /dev/null +++ b/source/isaaclab_ov/changelog.d/maximiliank-frame-transformer-nested-bodies.rst @@ -0,0 +1,5 @@ +Fixed +^^^^^ + +* Fixed frame-transformer path expressions that directly match a rigid body from also selecting + nested rigid-body descendants. diff --git a/source/isaaclab_ov/changelog.d/maximiliank-ovphysx-newton-schemas.rst b/source/isaaclab_ov/changelog.d/maximiliank-ovphysx-newton-schemas.rst new file mode 100644 index 000000000000..16638c7dc60c --- /dev/null +++ b/source/isaaclab_ov/changelog.d/maximiliank-ovphysx-newton-schemas.rst @@ -0,0 +1,5 @@ +Fixed +^^^^^ + +* Registered the separately packaged Newton USD schemas with OVStage so OvPhysX preserves + solver-neutral mimic-joint constraints while populating stages. diff --git a/source/isaaclab_ov/isaaclab_ov/physics/ovphysx_manager.py b/source/isaaclab_ov/isaaclab_ov/physics/ovphysx_manager.py index 3dd1d9f26269..0da7dc12c18b 100644 --- a/source/isaaclab_ov/isaaclab_ov/physics/ovphysx_manager.py +++ b/source/isaaclab_ov/isaaclab_ov/physics/ovphysx_manager.py @@ -14,6 +14,7 @@ import atexit import contextlib +import importlib.util import logging import math import os @@ -88,6 +89,17 @@ def _prepare_default_cache_dir(cache_dir: str) -> str: logger = logging.getLogger(__name__) +def _newton_schema_root() -> str | None: + """Return the installed Newton USD schema plugin root, if available.""" + spec = importlib.util.find_spec("newton_usd_schemas") + if spec is None or spec.origin is None: + return None + schema_root = os.path.dirname(spec.origin) + if not os.path.isfile(os.path.join(schema_root, "plugInfo.json")): + return None + return schema_root + + class OvPhysxSceneDataBackend(SceneDataBackend): """Scene-data backend for the OVPhysX physics manager. @@ -399,12 +411,13 @@ def _prepare_stage_creation(cls) -> None: @classmethod def _ensure_physx_schemas_registered(cls) -> None: - """Register the codeless USD schemas published by the OVPhysX wheel. + """Register the USD schemas consumed by the OVPhysX runtime. OVStage maintains its own USD schema registry, so register the wheel's - schema root there even when the host USD runtime already provides the - same plugins. For the host USD registry, only register providers that - are not already available from a compiled plugin. + schema root and the separately packaged Newton schemas there even when + the host USD runtime already provides the same plugins. For the host USD + registry, only register providers that are not already available from a + compiled plugin. """ if cls._physx_schemas_registered: return @@ -421,8 +434,11 @@ def _ensure_physx_schemas_registered(cls) -> None: else: schema_root = getattr(ovphysx, "codeless_schema_root", None) register_ovstage_schemas = getattr(getattr(ovstage, "population", None), "register_usd_schemas", None) - if callable(schema_root) and callable(register_ovstage_schemas): - register_ovstage_schemas(str(schema_root())) + if callable(register_ovstage_schemas): + if callable(schema_root): + register_ovstage_schemas(str(schema_root())) + if (newton_schema_root := _newton_schema_root()) is not None: + register_ovstage_schemas(newton_schema_root) registry = Plug.Registry() registered_names = {plugin.name.casefold() for plugin in registry.GetAllPlugins()} # The wheel documents ``/resources`` as its stable layout and its diff --git a/source/isaaclab_ov/isaaclab_ov/sensors/frame_transformer/frame_transformer.py b/source/isaaclab_ov/isaaclab_ov/sensors/frame_transformer/frame_transformer.py index 0dec1c3c1368..f5a001c43eff 100644 --- a/source/isaaclab_ov/isaaclab_ov/sensors/frame_transformer/frame_transformer.py +++ b/source/isaaclab_ov/isaaclab_ov/sensors/frame_transformer/frame_transformer.py @@ -176,7 +176,11 @@ def has_rigid_body_api(prim) -> bool: return bool(prim.HasAPI(UsdPhysics.RigidBodyAPI)) matches = resolve_matching_prims_from_source( - prim_path, predicate=has_rigid_body_api, raise_if_no_matches=False + prim_path, + predicate=has_rigid_body_api, + expected_num_matches=1 if frame_type == "source" else None, + raise_if_no_matches=False, + prefer_direct_matches=True, ) if not matches: raise ValueError( @@ -296,8 +300,8 @@ def has_rigid_body_api(prim) -> bool: # -- target frames: use relative prim path for unique identification self._target_frame_body_names = [self._get_relative_body_path(prim_path) for prim_path in sorted_prim_paths] - # -- source frame: use relative prim path for unique identification - self._source_frame_body_name = self._get_relative_body_path(self.cfg.prim_path) + # -- source frame: retain the concrete body resolved from the configured expression + self._source_frame_body_name = tracked_body_names[0] source_frame_index = self._target_frame_body_names.index(self._source_frame_body_name) # Only remove source frame from tracked bodies if it is not also a target frame diff --git a/source/isaaclab_ov/test/assets/test_articulation.py b/source/isaaclab_ov/test/assets/test_articulation.py index f075d4462872..c12e037d59ce 100644 --- a/source/isaaclab_ov/test/assets/test_articulation.py +++ b/source/isaaclab_ov/test/assets/test_articulation.py @@ -96,7 +96,7 @@ ## # Pre-defined configs ## -from isaaclab_assets import ANYMAL_C_CFG, CARTPOLE_CFG, FRANKA_PANDA_CFG, SHADOW_HAND_PHYSX_CFG # isort:skip +from isaaclab_assets import ANYMAL_C_CFG, CARTPOLE_CFG, FRANKA_PANDA_FLAT_CFG, SHADOW_HAND_PHYSX_CFG # isort:skip wp.init() @@ -339,7 +339,7 @@ def generate_articulation_cfg( actuators={"body": ImplicitActuatorCfg(joint_names_expr=[".*"], stiffness=stiffness, damping=damping)}, ) elif articulation_type == "panda": - articulation_cfg = FRANKA_PANDA_CFG + articulation_cfg = FRANKA_PANDA_FLAT_CFG elif articulation_type == "anymal": articulation_cfg = ANYMAL_C_CFG elif articulation_type == "shadow_hand": @@ -430,6 +430,32 @@ def generate_articulation( return articulation, translations +@pytest.mark.parametrize("device", ["cuda:0"]) +@pytest.mark.parametrize("gravity_enabled", [False]) +def test_franka_newton_mimic_constraint_tracks_passive_finger(sim, device, gravity_enabled): + """Drive only the Franka leader finger and preserve mimic tracking in every clone.""" + articulation, _ = generate_articulation(FRANKA_PANDA_FLAT_CFG, 2, device) + sim.reset() + + leader_id = articulation.find_joints("panda_finger_joint1")[0][0] + follower_id = articulation.find_joints("panda_finger_joint2")[0][0] + initial_leader_pos = articulation.data.joint_pos.torch[:, leader_id].clone() + leader_target = torch.full((articulation.num_instances, 1), 0.01, device=device) + articulation.actuators.target_command.set_position_index(value=leader_target, joint_ids=[leader_id]) + + for _ in range(120): + articulation.write_data_to_sim() + sim.step() + articulation.update(sim.cfg.dt) + assert torch.isfinite(articulation.data.joint_pos.torch).all() + assert torch.isfinite(articulation.data.joint_vel.torch).all() + + leader_pos = articulation.data.joint_pos.torch[:, leader_id] + follower_pos = articulation.data.joint_pos.torch[:, follower_id] + assert torch.all(torch.abs(leader_pos - initial_leader_pos) > 0.005) + torch.testing.assert_close(follower_pos, leader_pos, rtol=0.0, atol=5.0e-4) + + @pytest.mark.parametrize("device", ["cuda:0"]) def test_newton_native_explicit_actuator_submits_ovphysx_effort(device): """Run a Newton-native explicit actuator through the current OVPhysX state and effort binding.""" @@ -1647,7 +1673,7 @@ def test_out_of_range_default_joint_vel(sim, device): 1. The articulation fails to initialize when joint velocities are out of range 2. The error is properly handled """ - articulation_cfg = replace(FRANKA_PANDA_CFG, prim_path="/World/Robot") + articulation_cfg = replace(FRANKA_PANDA_FLAT_CFG, prim_path="/World/Robot") articulation_cfg.init_state.joint_vel = { "panda_joint1": 100.0, "panda_joint[2, 4]": -60.0, @@ -2457,7 +2483,7 @@ def test_com_orientation_write_invalidates_static_inertia_cache_with_body_orderi below forbids cross-device staging, so this test is CPU-only. """ sim._app_control_on_stop_handle = None - articulation_cfg = replace(FRANKA_PANDA_CFG, body_ordering=PANDA_ROOT_PRESERVING_REVERSED_BODY_NAMES) + articulation_cfg = replace(FRANKA_PANDA_FLAT_CFG, body_ordering=PANDA_ROOT_PRESERVING_REVERSED_BODY_NAMES) articulation, _ = generate_articulation(articulation_cfg, 1, device=device) sim.reset() diff --git a/source/isaaclab_ov/test/physics/test_ovphysx_manager_lifecycle.py b/source/isaaclab_ov/test/physics/test_ovphysx_manager_lifecycle.py index 110f864f94ff..5e1ba719d1c5 100644 --- a/source/isaaclab_ov/test/physics/test_ovphysx_manager_lifecycle.py +++ b/source/isaaclab_ov/test/physics/test_ovphysx_manager_lifecycle.py @@ -156,11 +156,18 @@ def RegisterPlugins(self, paths): # A None entry makes importing OVStage raise ModuleNotFoundError. monkeypatch.setitem(sys.modules, "ovstage", fake_ovstage if has_registration_api is not None else None) monkeypatch.setitem(sys.modules, "pxr", fake_pxr) + newton_schema_root = "/schemas/newton" + monkeypatch.setattr(manager_module, "_newton_schema_root", lambda: newton_schema_root, raising=False) manager._ensure_physx_schemas_registered() manager._ensure_physx_schemas_registered() - assert ovstage_registrations == ([schema_root] if schema_root is not None and has_registration_api else []) + expected_ovstage_registrations = [] + if has_registration_api: + if schema_root is not None: + expected_ovstage_registrations.append(schema_root) + expected_ovstage_registrations.append(newton_schema_root) + assert ovstage_registrations == expected_ovstage_registrations assert host_registrations == ([expected_paths] if expected_paths else []) diff --git a/source/isaaclab_ov/test/sensors/test_frame_transformer.py b/source/isaaclab_ov/test/sensors/test_frame_transformer.py index 4f520bd382b0..45aadfb9735b 100644 --- a/source/isaaclab_ov/test/sensors/test_frame_transformer.py +++ b/source/isaaclab_ov/test/sensors/test_frame_transformer.py @@ -50,6 +50,7 @@ from isaaclab.utils import configclass, replace # noqa: E402 from isaaclab_assets.robots.anymal import ANYMAL_C_CFG # noqa: E402 +from isaaclab_assets.robots.franka import FRANKA_PANDA_FLAT_CFG # noqa: E402 wp.init() @@ -668,3 +669,44 @@ class MultiRobotSceneCfg(InteractiveSceneCfg): assert any(torch.allclose(rf_pos, expected, atol=1e-5) for expected in expected_rf_positions), ( f"RF_SHANK position {rf_pos} doesn't match either configured body offset" ) + + +@pytest.mark.parametrize("device", ["cpu", "cuda:0"]) +def test_frame_transformer_nested_rigid_bodies(device): + """Test that a matched rigid body does not include nested rigid-body descendants.""" + with _ovphysx_sim_context(device=device) as sim: + sim._app_control_on_stop_handle = None + scene_cfg = _SceneCfg(num_envs=2, env_spacing=5.0, lazy_sensor_update=False) + scene_cfg.robot = replace(FRANKA_PANDA_FLAT_CFG, prim_path="{ENV_REGEX_NS}/Robot") + scene_cfg.frame_transformer = FrameTransformerCfg( + prim_path="{ENV_REGEX_NS}/Robot/(Geometry/)?panda_link0", + target_frames=[ + FrameTransformerCfg.FrameCfg( + name="hand", + prim_path="{ENV_REGEX_NS}/Robot/(Geometry/.*/)?panda_hand", + ), + FrameTransformerCfg.FrameCfg( + name="left_finger", + prim_path="{ENV_REGEX_NS}/Robot/(Geometry/.*/)?panda_leftfinger", + ), + FrameTransformerCfg.FrameCfg( + name="right_finger", + prim_path="{ENV_REGEX_NS}/Robot/(Geometry/.*/)?panda_rightfinger", + ), + ], + ) + scene = InteractiveScene(scene_cfg) + + sim.reset() + scene.update(sim.get_physics_dt()) + + robot = scene.articulations["robot"] + source_id = robot.find_bodies("panda_link0")[0][0] + target_ids = robot.find_bodies(["panda_hand", "panda_leftfinger", "panda_rightfinger"])[0] + frame_data = scene.sensors["frame_transformer"].data + + assert frame_data.target_frame_names == ["hand", "left_finger", "right_finger"] + torch.testing.assert_close(frame_data.source_pos_w.torch, robot.data.body_pos_w.torch[:, source_id]) + torch.testing.assert_close(frame_data.source_quat_w.torch, robot.data.body_quat_w.torch[:, source_id]) + torch.testing.assert_close(frame_data.target_pos_w.torch, robot.data.body_pos_w.torch[:, target_ids]) + torch.testing.assert_close(frame_data.target_quat_w.torch, robot.data.body_quat_w.torch[:, target_ids]) diff --git a/source/isaaclab_physx/changelog.d/maximiliank-frame-transformer-nested-bodies.rst b/source/isaaclab_physx/changelog.d/maximiliank-frame-transformer-nested-bodies.rst new file mode 100644 index 000000000000..02a80fb24471 --- /dev/null +++ b/source/isaaclab_physx/changelog.d/maximiliank-frame-transformer-nested-bodies.rst @@ -0,0 +1,5 @@ +Fixed +^^^^^ + +* Fixed frame-transformer path expressions that directly match a rigid body from also selecting + nested rigid-body descendants. diff --git a/source/isaaclab_physx/isaaclab_physx/sensors/frame_transformer/frame_transformer.py b/source/isaaclab_physx/isaaclab_physx/sensors/frame_transformer/frame_transformer.py index 9a9055e10c98..1f17ce8463b2 100644 --- a/source/isaaclab_physx/isaaclab_physx/sensors/frame_transformer/frame_transformer.py +++ b/source/isaaclab_physx/isaaclab_physx/sensors/frame_transformer/frame_transformer.py @@ -191,7 +191,13 @@ def _initialize_impl(self): def has_rigid_body_api(prim) -> bool: return bool(prim.HasAPI(UsdPhysics.RigidBodyAPI)) - matches = resolve_matching_prims_from_source(prim_path, has_rigid_body_api, raise_if_no_matches=False) + matches = resolve_matching_prims_from_source( + prim_path, + has_rigid_body_api, + expected_num_matches=1 if frame_type == "source" else None, + raise_if_no_matches=False, + prefer_direct_matches=True, + ) if not matches: raise ValueError( f"Failed to create frame transformer for frame '{frame}' with path '{prim_path}'." @@ -298,8 +304,8 @@ def extract_env_num_and_prim_path(item: str) -> tuple[int, str]: # -- target frames: use relative prim path for unique identification self._target_frame_body_names = [self._get_relative_body_path(prim_path) for prim_path in sorted_prim_paths] - # -- source frame: use relative prim path for unique identification - self._source_frame_body_name = self._get_relative_body_path(self.cfg.prim_path) + # -- source frame: retain the concrete body resolved from the configured expression + self._source_frame_body_name = tracked_body_names[0] source_frame_index = self._target_frame_body_names.index(self._source_frame_body_name) # Only remove source frame from tracked bodies if it is not also a target frame diff --git a/source/isaaclab_physx/test/assets/test_articulation.py b/source/isaaclab_physx/test/assets/test_articulation.py index 7078c528911e..81e6007b18e4 100644 --- a/source/isaaclab_physx/test/assets/test_articulation.py +++ b/source/isaaclab_physx/test/assets/test_articulation.py @@ -51,8 +51,8 @@ ## from isaaclab_assets import ( # isort:skip ANYMAL_C_CFG, - FRANKA_PANDA_CFG, - FRANKA_PANDA_HIGH_PD_CFG, + FRANKA_PANDA_FLAT_CFG, + FRANKA_PANDA_FLAT_HIGH_PD_CFG, ) from isaaclab_assets.robots.shadow_hand import SHADOW_HAND_PHYSX_CFG @@ -98,7 +98,7 @@ def generate_articulation_cfg( actuators={"body": ImplicitActuatorCfg(joint_names_expr=[".*"], stiffness=stiffness, damping=damping)}, ) elif articulation_type == "panda": - articulation_cfg = FRANKA_PANDA_CFG + articulation_cfg = FRANKA_PANDA_FLAT_CFG elif articulation_type == "anymal": articulation_cfg = ANYMAL_C_CFG elif articulation_type == "shadow_hand": @@ -217,7 +217,7 @@ def _setup_franka_at_home_pose(sim): Returns: Tuple of ``(robot, ee_frame_idx, ee_jacobi_idx, arm_joint_ids)``. """ - cfg = replace(clone(FRANKA_PANDA_HIGH_PD_CFG), prim_path="/World/Env_[^/]*/Robot") + cfg = replace(clone(FRANKA_PANDA_FLAT_HIGH_PD_CFG), prim_path="/World/Env_[^/]*/Robot") sim_utils.create_prim("/World/Env_0", "Xform", translation=(0.0, 0.0, 0.0)) robot = Articulation(cfg) sim.reset() @@ -309,7 +309,7 @@ def sim(request): def test_live_manual_root_preserving_ordering_reorders_backend_reads_and_writes(sim, device, gravity_enabled): """Smoke-test non-identity joint/body ordering through a live PhysX articulation.""" articulation_cfg = replace( - FRANKA_PANDA_CFG, + FRANKA_PANDA_FLAT_CFG, prim_path="/World/Robot", joint_ordering=tuple(reversed(PANDA_JOINT_NAMES)), body_ordering=PANDA_ROOT_PRESERVING_REVERSED_BODY_NAMES, @@ -433,13 +433,13 @@ def test_reversed_joint_dynamics_use_public_joint_basis(sim, device, gravity_ena @pytest.mark.parametrize("gravity_enabled", [False]) def test_live_floating_root_writers_match_identity_after_body_reordering(sim, device, gravity_enabled): """Keep floating-base root writes invariant when public body order moves the root.""" - floating_spawn = replace(FRANKA_PANDA_CFG.spawn, fix_root_link=False) + floating_spawn = replace(FRANKA_PANDA_FLAT_CFG.spawn, fix_root_link=False) identity = Articulation( - replace(FRANKA_PANDA_CFG, prim_path="/World/IdentityRobot", spawn=floating_spawn, body_ordering=None) + replace(FRANKA_PANDA_FLAT_CFG, prim_path="/World/IdentityRobot", spawn=floating_spawn, body_ordering=None) ) ordered = Articulation( replace( - FRANKA_PANDA_CFG, + FRANKA_PANDA_FLAT_CFG, prim_path="/World/OrderedRobot", spawn=floating_spawn, body_ordering=tuple(reversed(PANDA_BODY_NAMES)), @@ -511,7 +511,9 @@ def test_live_direct_view_mass_inertia_writes_become_visible(sim, device, gravit ``user_to_backend``. """ body_ordering_arg = None if body_ordering == "identity" else PANDA_ROOT_PRESERVING_REVERSED_BODY_NAMES - articulation = Articulation(replace(FRANKA_PANDA_CFG, prim_path="/World/Robot", body_ordering=body_ordering_arg)) + articulation = Articulation( + replace(FRANKA_PANDA_FLAT_CFG, prim_path="/World/Robot", body_ordering=body_ordering_arg) + ) sim.reset() assert articulation.is_initialized @@ -861,7 +863,7 @@ def test_out_of_range_default_joint_vel(sim, device): 1. The articulation fails to initialize when joint velocities are out of range 2. The error is properly handled """ - articulation_cfg = replace(FRANKA_PANDA_CFG, prim_path="/World/Robot") + articulation_cfg = replace(FRANKA_PANDA_FLAT_CFG, prim_path="/World/Robot") articulation_cfg.init_state.joint_vel = { "panda_joint1": 100.0, "panda_joint[2, 4]": -60.0, @@ -2141,7 +2143,7 @@ def test_get_gravity_compensation_forces_static_equilibrium(sim, num_articulatio # gravity-comp signal. Default Franka cfg has stiffness=80 / damping=4 # which would absorb gravity through PD bias and hide accessor bugs. cfg = replace(base_cfg, actuators={"all": ImplicitActuatorCfg(joint_names_expr=[".*"], stiffness=0.0, damping=0.0)}) - # FRANKA_PANDA_CFG has rigid_props.disable_gravity=False already, but be + # FRANKA_PANDA_FLAT_CFG has rigid_props.disable_gravity=False already, but be # defensive — gravity must be ON for τ_gc to have anything to cancel. cfg = replace(cfg, spawn=replace(cfg.spawn, rigid_props=replace(cfg.spawn.rigid_props, disable_gravity=False))) @@ -2157,7 +2159,7 @@ def test_get_gravity_compensation_forces_static_equilibrium(sim, num_articulatio articulation.write_joint_state_to_sim(default_q, default_qd) articulation.update(sim.cfg.dt) - # Default joint pose from FRANKA_PANDA_CFG bends the elbow + # Default joint pose from FRANKA_PANDA_FLAT_CFG bends the elbow # (joint2=-0.569, joint4=-2.81, joint6=3.04) so several links carry a # gravity load — τ_gc is non-trivial in this configuration. A natural- # hang pose (all zeros) would produce near-zero τ_gc and make this diff --git a/source/isaaclab_physx/test/sensors/test_frame_transformer.py b/source/isaaclab_physx/test/sensors/test_frame_transformer.py index 7d0ffb0d918a..8775fa47d35f 100644 --- a/source/isaaclab_physx/test/sensors/test_frame_transformer.py +++ b/source/isaaclab_physx/test/sensors/test_frame_transformer.py @@ -32,6 +32,7 @@ # Pre-defined configs ## from isaaclab_assets.robots.anymal import ANYMAL_C_CFG # isort:skip +from isaaclab_assets.robots.franka import FRANKA_PANDA_FLAT_CFG # isort:skip def quat_from_euler_rpy(roll, pitch, yaw, degrees=False): @@ -720,3 +721,41 @@ def test_frame_transformer_invalidation_drops_cached_launch_state(monkeypatch): assert sensor._frame_physx_view is None assert sensor._raw_transforms is None assert sensor._update_cmd is None + + +def test_frame_transformer_nested_rigid_bodies(sim): + """Test that a matched rigid body does not include nested rigid-body descendants.""" + scene_cfg = MySceneCfg(num_envs=2, env_spacing=5.0, lazy_sensor_update=False) + scene_cfg.robot = replace(FRANKA_PANDA_FLAT_CFG, prim_path="{ENV_REGEX_NS}/Robot") + scene_cfg.frame_transformer = FrameTransformerCfg( + prim_path="{ENV_REGEX_NS}/Robot/(Geometry/)?panda_link0", + target_frames=[ + FrameTransformerCfg.FrameCfg( + name="hand", + prim_path="{ENV_REGEX_NS}/Robot/(Geometry/.*/)?panda_hand", + ), + FrameTransformerCfg.FrameCfg( + name="left_finger", + prim_path="{ENV_REGEX_NS}/Robot/(Geometry/.*/)?panda_leftfinger", + ), + FrameTransformerCfg.FrameCfg( + name="right_finger", + prim_path="{ENV_REGEX_NS}/Robot/(Geometry/.*/)?panda_rightfinger", + ), + ], + ) + scene = InteractiveScene(scene_cfg) + + sim.reset() + scene.update(sim.get_physics_dt()) + + robot = scene.articulations["robot"] + source_id = robot.find_bodies("panda_link0")[0][0] + target_ids = robot.find_bodies(["panda_hand", "panda_leftfinger", "panda_rightfinger"])[0] + frame_data = scene.sensors["frame_transformer"].data + + assert frame_data.target_frame_names == ["hand", "left_finger", "right_finger"] + torch.testing.assert_close(frame_data.source_pos_w.torch, robot.data.body_pos_w.torch[:, source_id]) + torch.testing.assert_close(frame_data.source_quat_w.torch, robot.data.body_quat_w.torch[:, source_id]) + torch.testing.assert_close(frame_data.target_pos_w.torch, robot.data.body_pos_w.torch[:, target_ids]) + torch.testing.assert_close(frame_data.target_quat_w.torch, robot.data.body_quat_w.torch[:, target_ids]) diff --git a/source/isaaclab_tasks/changelog.d/maximiliank-franka-shared-asset.major.rst b/source/isaaclab_tasks/changelog.d/maximiliank-franka-shared-asset.major.rst new file mode 100644 index 000000000000..877272b96040 --- /dev/null +++ b/source/isaaclab_tasks/changelog.d/maximiliank-franka-shared-asset.major.rst @@ -0,0 +1,18 @@ +Fixed +^^^^^ + +* Migrated maintained Franka Reach, Drawer, Lift, and deformable tasks to the shared flat asset with + backend-specific physics and gripper-only collisions by default. The ``arm_collisions`` preset retained + primitive arm contacts for tasks that need them. +* Corrected Reach action, frame-transformer, and actuator contracts for the shared asset, and fixed Franka + Lift reset sampling and success-driven motion regularization without changing Kuka-Allegro rewards. Franka + Lift's automatic ``physics=physx`` retained the eight-shape task on Isaac Sim; select explicit + ``physics=ovphysx`` for the cube-only kitless path. Existing Franka Lift checkpoints should be re-evaluated + because the reset distribution changed. + +Removed +^^^^^^^ + +* **Breaking:** Removed the unqualified ``Isaac-Reorient-Franka`` task. There is no maintained Franka + replacement; keep an external task configuration if you rely on it, or use + ``Isaac-Reorient-KukaAllegro`` for a supported in-hand reorientation task. diff --git a/source/isaaclab_tasks/isaaclab_tasks/benchmark/render_benchmark/render_benchmark_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/benchmark/render_benchmark/render_benchmark_env_cfg.py index a09bdf673811..3e1c4ba20fce 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/benchmark/render_benchmark/render_benchmark_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/benchmark/render_benchmark/render_benchmark_env_cfg.py @@ -25,10 +25,10 @@ from isaaclab.utils import configclass, replace from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR -from isaaclab_tasks.utils import PresetCfg +from isaaclab_tasks.utils import PresetCfg, preset from isaaclab_tasks.utils.presets import MultiBackendRendererCfg -from isaaclab_assets.robots.franka import FRANKA_PANDA_HIGH_PD_CFG +from isaaclab_assets.robots.franka import FRANKA_PANDA_FLAT_HIGH_PD_CFG BenchmarkMode = Literal["render", "physics_render"] """Animation mode used by the render benchmark. @@ -144,10 +144,11 @@ class RenderBenchmarkSceneCfg(InteractiveSceneCfg): init_state=RigidObjectCfg.InitialStateCfg(pos=(0.0, 0.0, -0.05)), ) robot: ArticulationCfg = replace( - FRANKA_PANDA_HIGH_PD_CFG, + FRANKA_PANDA_FLAT_HIGH_PD_CFG, prim_path="{ENV_REGEX_NS}/Robot", - init_state=replace(FRANKA_PANDA_HIGH_PD_CFG.init_state, pos=(1.0, 0.0, 0.0), rot=(0.0, 0.0, 1.0, 0.0)), + init_state=replace(FRANKA_PANDA_FLAT_HIGH_PD_CFG.init_state, pos=(1.0, 0.0, 0.0), rot=(0.0, 0.0, 1.0, 0.0)), ) + robot.spawn.variants["Physics"] = preset(default="mujoco", isaacsim_physx="physx", ovphysx="physx", physx="physx") cabinet: ArticulationCfg = ArticulationCfg( prim_path="{ENV_REGEX_NS}/Cabinet", spawn=sim_utils.UsdFileCfg( diff --git a/source/isaaclab_tasks/isaaclab_tasks/contrib/cabinet/config/franka/ik_abs_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/contrib/cabinet/config/franka/ik_abs_env_cfg.py index 7707d97e8a7e..8a7da6c667c5 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/contrib/cabinet/config/franka/ik_abs_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/contrib/cabinet/config/franka/ik_abs_env_cfg.py @@ -12,7 +12,7 @@ ## # Pre-defined configs ## -from isaaclab_assets.robots.franka import FRANKA_PANDA_HIGH_PD_CFG # isort: skip +from isaaclab_assets.robots.franka import FRANKA_PANDA_FLAT_HIGH_PD_CFG # isort: skip @configclass @@ -23,7 +23,9 @@ def __post_init__(self): # Set Franka as robot # We switch here to a stiffer PD controller for IK tracking to be better. - self.scene.robot = replace(FRANKA_PANDA_HIGH_PD_CFG, prim_path="{ENV_REGEX_NS}/Robot") + physics_variants = self.scene.robot.spawn.variants + self.scene.robot = replace(FRANKA_PANDA_FLAT_HIGH_PD_CFG, prim_path="{ENV_REGEX_NS}/Robot") + self.scene.robot.spawn.variants = physics_variants # Set actions for the specific robot type (franka) self.actions.arm_action = DifferentialInverseKinematicsActionCfg( diff --git a/source/isaaclab_tasks/isaaclab_tasks/contrib/cabinet/config/franka/ik_rel_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/contrib/cabinet/config/franka/ik_rel_env_cfg.py index 7e2e96e9460e..aae63d444a09 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/contrib/cabinet/config/franka/ik_rel_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/contrib/cabinet/config/franka/ik_rel_env_cfg.py @@ -12,7 +12,7 @@ ## # Pre-defined configs ## -from isaaclab_assets.robots.franka import FRANKA_PANDA_HIGH_PD_CFG # isort: skip +from isaaclab_assets.robots.franka import FRANKA_PANDA_FLAT_HIGH_PD_CFG # isort: skip @configclass @@ -23,7 +23,9 @@ def __post_init__(self): # Set Franka as robot # We switch here to a stiffer PD controller for IK tracking to be better. - self.scene.robot = replace(FRANKA_PANDA_HIGH_PD_CFG, prim_path="{ENV_REGEX_NS}/Robot") + physics_variants = self.scene.robot.spawn.variants + self.scene.robot = replace(FRANKA_PANDA_FLAT_HIGH_PD_CFG, prim_path="{ENV_REGEX_NS}/Robot") + self.scene.robot.spawn.variants = physics_variants # Set actions for the specific robot type (franka) self.actions.arm_action = DifferentialInverseKinematicsActionCfg( diff --git a/source/isaaclab_tasks/isaaclab_tasks/contrib/franka_pour/pour_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/contrib/franka_pour/pour_env_cfg.py index 1dc988351a67..687eaa69c89e 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/contrib/franka_pour/pour_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/contrib/franka_pour/pour_env_cfg.py @@ -40,7 +40,7 @@ from isaaclab_contrib.coupling import CouplerEntryCfg, CouplerProxyCfg, CouplerProxyMappingCfg -from isaaclab_assets.robots.franka import FRANKA_PANDA_CFG +from isaaclab_assets.robots.franka import FRANKA_PANDA_FLAT_CFG from . import mdp from .geometry import ( @@ -182,7 +182,7 @@ def spawn_franka_with_arm_collisions( orientation: tuple[float, float, float, float] | None = None, **kwargs, ): - """Spawn the canonical Franka with the intended mimic and arm-collision schemas.""" + """Spawn the canonical Franka with writable convex-hull arm colliders.""" from pxr import Usd, UsdPhysics # noqa: PLC0415 robot_prim = sim_utils.spawn_from_usd(prim_path, cfg, translation, orientation, **kwargs) @@ -191,29 +191,6 @@ def spawn_franka_with_arm_collisions( if not self_collision or not self_collision.Set(True): raise RuntimeError(f"Franka asset at {root_prim.GetPath()} has no writable Newton self-collision flag.") - finger_joints = { - prim.GetName(): prim - for prim in Usd.PrimRange(root_prim, Usd.TraverseInstanceProxies()) - if prim.GetName() in {"panda_finger_joint1", "panda_finger_joint2"} - } - if finger_joints.keys() != {"panda_finger_joint1", "panda_finger_joint2"}: - missing = sorted({"panda_finger_joint1", "panda_finger_joint2"}.difference(finger_joints)) - raise RuntimeError(f"Franka asset at {root_prim.GetPath()} is missing finger joints: {missing}.") - - leader_schemas = finger_joints["panda_finger_joint1"].GetMetadata("apiSchemas") - follower_schemas = finger_joints["panda_finger_joint2"].GetMetadata("apiSchemas") - leader_schema_names = [] if leader_schemas is None else list(leader_schemas.GetAppliedItems()) - follower_schema_names = [] if follower_schemas is None else list(follower_schemas.GetAppliedItems()) - if "NewtonMimicAPI" not in leader_schema_names or "PhysxMimicJointAPI:linear" not in follower_schema_names: - raise RuntimeError( - "The pinned Franka physics payload no longer has the expected duplicate finger-mimic schemas. " - "Update the asset hash and task-side override together." - ) - if not finger_joints["panda_finger_joint1"].RemoveAppliedSchema("NewtonMimicAPI"): - raise RuntimeError("Failed to remove the redundant Franka Newton finger-mimic schema.") - if finger_joints["panda_finger_joint1"].HasAPI("NewtonMimicAPI"): - raise RuntimeError("The redundant Franka Newton finger-mimic schema is still composed after removal.") - proxy_roots = { prim.GetName(): prim.GetParent().GetPath() for prim in Usd.PrimRange(root_prim, Usd.TraverseInstanceProxies()) @@ -337,9 +314,9 @@ class PourSceneCfg(InteractiveSceneCfg): prim_path="/World/light", spawn=sim_utils.DomeLightCfg(color=(0.75, 0.75, 0.75), intensity=3000.0), ) - robot = replace(FRANKA_PANDA_CFG, prim_path="{ENV_REGEX_NS}/Robot") + robot = replace(FRANKA_PANDA_FLAT_CFG, prim_path="{ENV_REGEX_NS}/Robot") robot.spawn.usd_path = FRANKA_POUR_ROBOT_USD_PATH - robot.spawn.variants = {"Colliders": "convex_hulls"} + robot.spawn.variants = {"Physics": "mujoco", "Colliders": "convex_hulls"} robot.spawn.func = spawn_franka_with_arm_collisions # The pouring asset relies on arm self-collision; author it in both namespaces so whichever # backend resolves the articulation sees the flag. @@ -355,16 +332,10 @@ class PourSceneCfg(InteractiveSceneCfg): ) for name, actuator_cfg in robot.actuators.items() } - robot.actuators["panda_shoulder"].stiffness = { - key: FRANKA_POUR_ARM_DRIVE_STIFFNESS[key] for key in ("panda_joint[1-2]", "panda_joint[3-4]") - } - robot.actuators["panda_shoulder"].damping = { - key: FRANKA_POUR_ARM_DRIVE_DAMPING[key] for key in ("panda_joint[1-2]", "panda_joint[3-4]") - } - robot.actuators["panda_forearm"].stiffness = { - "panda_joint[5-7]": FRANKA_POUR_ARM_DRIVE_STIFFNESS["panda_joint[5-7]"] - } - robot.actuators["panda_forearm"].damping = {"panda_joint[5-7]": FRANKA_POUR_ARM_DRIVE_DAMPING["panda_joint[5-7]"]} + robot.actuators["panda_arm"].stiffness = dict(FRANKA_POUR_ARM_DRIVE_STIFFNESS) + robot.actuators["panda_arm"].damping = dict(FRANKA_POUR_ARM_DRIVE_DAMPING) + robot.actuators["panda_finger2_passive"].stiffness = 0.0 + robot.actuators["panda_finger2_passive"].damping = 0.0 robot.spawn.joint_drive_props = MujocoJointCfg(actuatorgravcomp=True) robot.init_state.joint_pos.update(dict(zip(_ARM_JOINT_NAMES, _ARM_HOME, strict=True))) robot.init_state.joint_pos["panda_finger_joint.*"] = _GRIPPER_OPEN_POSITION diff --git a/source/isaaclab_tasks/isaaclab_tasks/contrib/lift/config/franka/ik_abs_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/contrib/lift/config/franka/ik_abs_env_cfg.py index 8bee40e4b2be..3a6521e107f9 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/contrib/lift/config/franka/ik_abs_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/contrib/lift/config/franka/ik_abs_env_cfg.py @@ -12,7 +12,7 @@ ## # Pre-defined configs ## -from isaaclab_assets.robots.franka import FRANKA_PANDA_HIGH_PD_CFG # isort: skip +from isaaclab_assets.robots.franka import FRANKA_PANDA_FLAT_HIGH_PD_CFG # isort: skip ## @@ -28,7 +28,7 @@ def __post_init__(self): # Set Franka as robot # We switch here to a stiffer PD controller for IK tracking to be better. - self.scene.robot = replace(FRANKA_PANDA_HIGH_PD_CFG, prim_path="{ENV_REGEX_NS}/Robot") + self.scene.robot = replace(FRANKA_PANDA_FLAT_HIGH_PD_CFG, prim_path="{ENV_REGEX_NS}/Robot") # Set actions for the specific robot type (franka) self.actions.arm_action = DifferentialInverseKinematicsActionCfg( diff --git a/source/isaaclab_tasks/isaaclab_tasks/contrib/lift/config/franka/ik_rel_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/contrib/lift/config/franka/ik_rel_env_cfg.py index 4869b30b8521..ff44c6c2c9a5 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/contrib/lift/config/franka/ik_rel_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/contrib/lift/config/franka/ik_rel_env_cfg.py @@ -12,7 +12,7 @@ ## # Pre-defined configs ## -from isaaclab_assets.robots.franka import FRANKA_PANDA_HIGH_PD_CFG # isort: skip +from isaaclab_assets.robots.franka import FRANKA_PANDA_FLAT_HIGH_PD_CFG # isort: skip @configclass @@ -23,7 +23,7 @@ def __post_init__(self): # Set Franka as robot # We switch here to a stiffer PD controller for IK tracking to be better. - self.scene.robot = replace(FRANKA_PANDA_HIGH_PD_CFG, prim_path="{ENV_REGEX_NS}/Robot") + self.scene.robot = replace(FRANKA_PANDA_FLAT_HIGH_PD_CFG, prim_path="{ENV_REGEX_NS}/Robot") # Set actions for the specific robot type (franka) self.actions.arm_action = DifferentialInverseKinematicsActionCfg( diff --git a/source/isaaclab_tasks/isaaclab_tasks/contrib/lift/config/franka/joint_pos_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/contrib/lift/config/franka/joint_pos_env_cfg.py index 5935bb95c19c..aabd132f7154 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/contrib/lift/config/franka/joint_pos_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/contrib/lift/config/franka/joint_pos_env_cfg.py @@ -19,7 +19,7 @@ # Pre-defined configs ## from isaaclab.markers.config import FRAME_MARKER_CFG # isort: skip -from isaaclab_assets.robots.franka import FRANKA_PANDA_CFG # isort: skip +from isaaclab_assets.robots.franka import FRANKA_PANDA_FLAT_CFG # isort: skip @configclass @@ -29,7 +29,7 @@ def __post_init__(self): super().__post_init__() # Set Franka as robot - self.scene.robot = replace(FRANKA_PANDA_CFG, prim_path="{ENV_REGEX_NS}/Robot") + self.scene.robot = replace(FRANKA_PANDA_FLAT_CFG, prim_path="{ENV_REGEX_NS}/Robot") # Set actions for the specific robot type (franka) self.actions.arm_action = mdp.JointPositionActionCfg( @@ -67,12 +67,12 @@ def __post_init__(self): marker_cfg.markers["frame"].scale = (0.1, 0.1, 0.1) marker_cfg.prim_path = "/Visuals/FrameTransformer" self.scene.ee_frame = FrameTransformerCfg( - prim_path="{ENV_REGEX_NS}/Robot/panda_link0", + prim_path="{ENV_REGEX_NS}/Robot/(Geometry/)?panda_link0", debug_vis=False, visualizer_cfg=marker_cfg, target_frames=[ FrameTransformerCfg.FrameCfg( - prim_path="{ENV_REGEX_NS}/Robot/panda_hand", + prim_path="{ENV_REGEX_NS}/Robot/(Geometry/.*/)?panda_hand", name="end_effector", offset=OffsetCfg( pos=[0.0, 0.0, 0.1034], diff --git a/source/isaaclab_tasks/isaaclab_tasks/contrib/stack/config/franka/bin_stack_ik_rel_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/contrib/stack/config/franka/bin_stack_ik_rel_env_cfg.py index 858c095779aa..8b68516ff17d 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/contrib/stack/config/franka/bin_stack_ik_rel_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/contrib/stack/config/franka/bin_stack_ik_rel_env_cfg.py @@ -12,7 +12,7 @@ ## # Pre-defined configs ## -from isaaclab_assets.robots.franka import FRANKA_PANDA_HIGH_PD_CFG # isort: skip +from isaaclab_assets.robots.franka import FRANKA_PANDA_FLAT_HIGH_PD_CFG # isort: skip @configclass @@ -22,7 +22,7 @@ def __post_init__(self): super().__post_init__() # Set Franka as robot - self.scene.robot = replace(FRANKA_PANDA_HIGH_PD_CFG, prim_path="{ENV_REGEX_NS}/Robot") + self.scene.robot = replace(FRANKA_PANDA_FLAT_HIGH_PD_CFG, prim_path="{ENV_REGEX_NS}/Robot") # Set actions for the specific robot type (franka) self.actions.arm_action = DifferentialInverseKinematicsActionCfg( diff --git a/source/isaaclab_tasks/isaaclab_tasks/contrib/stack/config/franka/bin_stack_joint_pos_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/contrib/stack/config/franka/bin_stack_joint_pos_env_cfg.py index 5d4e533e98a0..fdcc0587fa31 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/contrib/stack/config/franka/bin_stack_joint_pos_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/contrib/stack/config/franka/bin_stack_joint_pos_env_cfg.py @@ -24,7 +24,7 @@ # Pre-defined configs ## from isaaclab.markers.config import FRAME_MARKER_CFG # isort: skip -from isaaclab_assets.robots.franka import FRANKA_PANDA_CFG # isort: skip +from isaaclab_assets.robots.franka import FRANKA_PANDA_FLAT_CFG # isort: skip @configclass @@ -96,7 +96,7 @@ def __post_init__(self): self.events = EventCfg() # Set Franka as robot - self.scene.robot = replace(FRANKA_PANDA_CFG, prim_path="{ENV_REGEX_NS}/Robot") + self.scene.robot = replace(FRANKA_PANDA_FLAT_CFG, prim_path="{ENV_REGEX_NS}/Robot") self.scene.robot.spawn.semantic_tags = [("class", "robot")] # Add semantics to table @@ -182,26 +182,26 @@ def __post_init__(self): marker_cfg.markers["frame"].scale = (0.1, 0.1, 0.1) marker_cfg.prim_path = "/Visuals/FrameTransformer" self.scene.ee_frame = FrameTransformerCfg( - prim_path="{ENV_REGEX_NS}/Robot/panda_link0", + prim_path="{ENV_REGEX_NS}/Robot/(Geometry/)?panda_link0", debug_vis=False, visualizer_cfg=marker_cfg, target_frames=[ FrameTransformerCfg.FrameCfg( - prim_path="{ENV_REGEX_NS}/Robot/panda_hand", + prim_path="{ENV_REGEX_NS}/Robot/(Geometry/.*/)?panda_hand", name="end_effector", offset=OffsetCfg( pos=(0.0, 0.0, 0.0), ), ), FrameTransformerCfg.FrameCfg( - prim_path="{ENV_REGEX_NS}/Robot/panda_rightfinger", + prim_path="{ENV_REGEX_NS}/Robot/(Geometry/.*/)?panda_rightfinger", name="tool_rightfinger", offset=OffsetCfg( pos=(0.0, 0.0, 0.046), ), ), FrameTransformerCfg.FrameCfg( - prim_path="{ENV_REGEX_NS}/Robot/panda_leftfinger", + prim_path="{ENV_REGEX_NS}/Robot/(Geometry/.*/)?panda_leftfinger", name="tool_leftfinger", offset=OffsetCfg( pos=(0.0, 0.0, 0.046), diff --git a/source/isaaclab_tasks/isaaclab_tasks/contrib/stack/config/franka/stack_ik_abs_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/contrib/stack/config/franka/stack_ik_abs_env_cfg.py index 7024329b8e57..18e6efc6db2c 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/contrib/stack/config/franka/stack_ik_abs_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/contrib/stack/config/franka/stack_ik_abs_env_cfg.py @@ -18,7 +18,7 @@ ## # Pre-defined configs ## -from isaaclab_assets.robots.franka import FRANKA_PANDA_HIGH_PD_CFG # isort: skip +from isaaclab_assets.robots.franka import FRANKA_PANDA_FLAT_HIGH_PD_CFG # isort: skip def _build_franka_stack_pipeline(): @@ -114,7 +114,7 @@ def __post_init__(self): # Set Franka as robot # We switch here to a stiffer PD controller for IK tracking to be better. - self.scene.robot = replace(FRANKA_PANDA_HIGH_PD_CFG, prim_path="{ENV_REGEX_NS}/Robot") + self.scene.robot = replace(FRANKA_PANDA_FLAT_HIGH_PD_CFG, prim_path="{ENV_REGEX_NS}/Robot") # Set actions for the specific robot type (franka) self.actions.arm_action = DifferentialInverseKinematicsActionCfg( diff --git a/source/isaaclab_tasks/isaaclab_tasks/contrib/stack/config/franka/stack_ik_rel_blueprint_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/contrib/stack/config/franka/stack_ik_rel_blueprint_env_cfg.py index 2c74266768ff..74fd959e1d65 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/contrib/stack/config/franka/stack_ik_rel_blueprint_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/contrib/stack/config/franka/stack_ik_rel_blueprint_env_cfg.py @@ -31,7 +31,7 @@ ## # Pre-defined configs ## -from isaaclab_assets.robots.franka import FRANKA_PANDA_HIGH_PD_CFG # isort: skip +from isaaclab_assets.robots.franka import FRANKA_PANDA_FLAT_HIGH_PD_CFG # isort: skip def image( @@ -217,7 +217,7 @@ def __post_init__(self): # Set Franka as robot # We switch here to a stiffer PD controller for IK tracking to be better. - self.scene.robot = replace(FRANKA_PANDA_HIGH_PD_CFG, prim_path="{ENV_REGEX_NS}/Robot") + self.scene.robot = replace(FRANKA_PANDA_FLAT_HIGH_PD_CFG, prim_path="{ENV_REGEX_NS}/Robot") self.scene.robot.spawn.semantic_tags = [("class", "robot")] # Set actions for the specific robot type (franka) diff --git a/source/isaaclab_tasks/isaaclab_tasks/contrib/stack/config/franka/stack_ik_rel_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/contrib/stack/config/franka/stack_ik_rel_env_cfg.py index 559058ab8673..18cb199c8a5e 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/contrib/stack/config/franka/stack_ik_rel_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/contrib/stack/config/franka/stack_ik_rel_env_cfg.py @@ -22,7 +22,7 @@ # Pre-defined configs ## from isaaclab_assets.robots.franka import ( # isort: skip - FRANKA_PANDA_HIGH_PD_CFG, + FRANKA_PANDA_FLAT_HIGH_PD_CFG, ) @@ -37,7 +37,7 @@ def __post_init__(self): robot_init_state = self.scene.robot.init_state robot_semantic_tags = self.scene.robot.spawn.semantic_tags self.scene.robot = replace( - FRANKA_PANDA_HIGH_PD_CFG, prim_path="{ENV_REGEX_NS}/Robot", init_state=robot_init_state + FRANKA_PANDA_FLAT_HIGH_PD_CFG, prim_path="{ENV_REGEX_NS}/Robot", init_state=robot_init_state ) self.scene.robot.spawn.semantic_tags = robot_semantic_tags diff --git a/source/isaaclab_tasks/isaaclab_tasks/contrib/stack/config/franka/stack_ik_rel_env_cfg_skillgen.py b/source/isaaclab_tasks/isaaclab_tasks/contrib/stack/config/franka/stack_ik_rel_env_cfg_skillgen.py index 9563cdc4f2fc..355e91789ccc 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/contrib/stack/config/franka/stack_ik_rel_env_cfg_skillgen.py +++ b/source/isaaclab_tasks/isaaclab_tasks/contrib/stack/config/franka/stack_ik_rel_env_cfg_skillgen.py @@ -16,7 +16,7 @@ ## # Pre-defined configs ## -from isaaclab_assets.robots.franka import FRANKA_PANDA_HIGH_PD_CFG # isort: skip +from isaaclab_assets.robots.franka import FRANKA_PANDA_FLAT_HIGH_PD_CFG # isort: skip @configclass @@ -107,7 +107,7 @@ def __post_init__(self): # Set Franka as robot # We switch here to a stiffer PD controller for IK tracking to be better. - self.scene.robot = replace(FRANKA_PANDA_HIGH_PD_CFG, prim_path="{ENV_REGEX_NS}/Robot") + self.scene.robot = replace(FRANKA_PANDA_FLAT_HIGH_PD_CFG, prim_path="{ENV_REGEX_NS}/Robot") # Set actions for the specific robot type (franka) self.actions.arm_action = DifferentialInverseKinematicsActionCfg( diff --git a/source/isaaclab_tasks/isaaclab_tasks/contrib/stack/config/franka/stack_ik_rel_instance_randomize_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/contrib/stack/config/franka/stack_ik_rel_instance_randomize_env_cfg.py index 3ab5af14e90c..e7afec6d6a76 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/contrib/stack/config/franka/stack_ik_rel_instance_randomize_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/contrib/stack/config/franka/stack_ik_rel_instance_randomize_env_cfg.py @@ -12,7 +12,7 @@ ## # Pre-defined configs ## -from isaaclab_assets.robots.franka import FRANKA_PANDA_HIGH_PD_CFG # isort: skip +from isaaclab_assets.robots.franka import FRANKA_PANDA_FLAT_HIGH_PD_CFG # isort: skip @configclass @@ -25,7 +25,7 @@ def __post_init__(self): # Set Franka as robot # We switch here to a stiffer PD controller for IK tracking to be better. - self.scene.robot = replace(FRANKA_PANDA_HIGH_PD_CFG, prim_path="{ENV_REGEX_NS}/Robot") + self.scene.robot = replace(FRANKA_PANDA_FLAT_HIGH_PD_CFG, prim_path="{ENV_REGEX_NS}/Robot") # Set actions for the specific robot type (franka) self.actions.arm_action = DifferentialInverseKinematicsActionCfg( diff --git a/source/isaaclab_tasks/isaaclab_tasks/contrib/stack/config/franka/stack_ik_rel_visuomotor_cosmos_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/contrib/stack/config/franka/stack_ik_rel_visuomotor_cosmos_env_cfg.py index 4a923b7d618e..24576ff975b0 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/contrib/stack/config/franka/stack_ik_rel_visuomotor_cosmos_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/contrib/stack/config/franka/stack_ik_rel_visuomotor_cosmos_env_cfg.py @@ -115,7 +115,7 @@ def __post_init__(self): # Set cameras # Set wrist camera self.scene.wrist_cam = CameraCfg( - prim_path="{ENV_REGEX_NS}/Robot/panda_hand/wrist_cam", + prim_path="{ENV_REGEX_NS}/Robot/(Geometry/.*/)?panda_hand/wrist_cam", update_period=0.0, height=200, width=200, diff --git a/source/isaaclab_tasks/isaaclab_tasks/contrib/stack/config/franka/stack_ik_rel_visuomotor_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/contrib/stack/config/franka/stack_ik_rel_visuomotor_env_cfg.py index 3d5572611523..163008d2d4de 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/contrib/stack/config/franka/stack_ik_rel_visuomotor_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/contrib/stack/config/franka/stack_ik_rel_visuomotor_env_cfg.py @@ -28,7 +28,7 @@ # Pre-defined configs ## from isaaclab.markers.config import FRAME_MARKER_CFG # isort: skip -from isaaclab_assets.robots.franka import FRANKA_PANDA_HIGH_PD_CFG # isort: skip +from isaaclab_assets.robots.franka import FRANKA_PANDA_FLAT_HIGH_PD_CFG # isort: skip # Default arm + gripper joint pose _FRANKA_STACK_IK_REL_INIT_JOINT_POS: dict[str, float] = { @@ -221,7 +221,7 @@ def __post_init__(self): # Set Franka as robot # We switch here to a stiffer PD controller for IK tracking to be better. self.scene.robot = replace( - FRANKA_PANDA_HIGH_PD_CFG, + FRANKA_PANDA_FLAT_HIGH_PD_CFG, prim_path="{ENV_REGEX_NS}/Robot", init_state=ArticulationCfg.InitialStateCfg(joint_pos=_FRANKA_STACK_IK_REL_INIT_JOINT_POS), ) @@ -304,26 +304,26 @@ def __post_init__(self): marker_cfg.markers["frame"].scale = (0.1, 0.1, 0.1) marker_cfg.prim_path = "/Visuals/FrameTransformer" self.scene.ee_frame = FrameTransformerCfg( - prim_path="{ENV_REGEX_NS}/Robot/panda_link0", + prim_path="{ENV_REGEX_NS}/Robot/(Geometry/)?panda_link0", debug_vis=False, visualizer_cfg=marker_cfg, target_frames=[ FrameTransformerCfg.FrameCfg( - prim_path="{ENV_REGEX_NS}/Robot/panda_hand", + prim_path="{ENV_REGEX_NS}/Robot/(Geometry/.*/)?panda_hand", name="end_effector", offset=OffsetCfg( pos=[0.0, 0.0, 0.1034], ), ), FrameTransformerCfg.FrameCfg( - prim_path="{ENV_REGEX_NS}/Robot/panda_rightfinger", + prim_path="{ENV_REGEX_NS}/Robot/(Geometry/.*/)?panda_rightfinger", name="tool_rightfinger", offset=OffsetCfg( pos=(0.0, 0.0, 0.046), ), ), FrameTransformerCfg.FrameCfg( - prim_path="{ENV_REGEX_NS}/Robot/panda_leftfinger", + prim_path="{ENV_REGEX_NS}/Robot/(Geometry/.*/)?panda_leftfinger", name="tool_leftfinger", offset=OffsetCfg( pos=(0.0, 0.0, 0.046), @@ -335,7 +335,7 @@ def __post_init__(self): # Set cameras # Set wrist camera self.scene.wrist_cam = CameraCfg( - prim_path="{ENV_REGEX_NS}/Robot/panda_hand/wrist_cam", + prim_path="{ENV_REGEX_NS}/Robot/(Geometry/.*/)?panda_hand/wrist_cam", update_period=0.0, height=200, width=200, diff --git a/source/isaaclab_tasks/isaaclab_tasks/contrib/stack/config/franka/stack_joint_pos_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/contrib/stack/config/franka/stack_joint_pos_env_cfg.py index b384e71293b9..639eeb00341e 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/contrib/stack/config/franka/stack_joint_pos_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/contrib/stack/config/franka/stack_joint_pos_env_cfg.py @@ -17,7 +17,7 @@ ## # Pre-defined configs ## -from isaaclab_assets.robots.franka import FRANKA_PANDA_CFG # isort: skip +from isaaclab_assets.robots.franka import FRANKA_PANDA_FLAT_CFG # isort: skip # Default arm + gripper joint pose _FRANKA_STACK_IK_REL_INIT_JOINT_POS: dict[str, float] = { @@ -51,7 +51,7 @@ def __post_init__(self): # Set Franka as robot self.scene.robot = replace( - FRANKA_PANDA_CFG, + FRANKA_PANDA_FLAT_CFG, prim_path="{ENV_REGEX_NS}/Robot", init_state=ArticulationCfg.InitialStateCfg(joint_pos=_FRANKA_STACK_IK_REL_INIT_JOINT_POS), ) @@ -76,11 +76,19 @@ def __post_init__(self): # End-effector frame (the shared cubes/spawns come from the base scene). self.scene.ee_frame = make_ee_frame_cfg( - base_prim_path="{ENV_REGEX_NS}/Robot/panda_link0", + base_prim_path="{ENV_REGEX_NS}/Robot/(Geometry/)?panda_link0", target_specs=[ - ("{ENV_REGEX_NS}/Robot/panda_hand", "end_effector", (0.0, 0.0, 0.1034)), - ("{ENV_REGEX_NS}/Robot/panda_rightfinger", "tool_rightfinger", (0.0, 0.0, 0.046)), - ("{ENV_REGEX_NS}/Robot/panda_leftfinger", "tool_leftfinger", (0.0, 0.0, 0.046)), + ("{ENV_REGEX_NS}/Robot/(Geometry/.*/)?panda_hand", "end_effector", (0.0, 0.0, 0.1034)), + ( + "{ENV_REGEX_NS}/Robot/(Geometry/.*/)?panda_rightfinger", + "tool_rightfinger", + (0.0, 0.0, 0.046), + ), + ( + "{ENV_REGEX_NS}/Robot/(Geometry/.*/)?panda_leftfinger", + "tool_leftfinger", + (0.0, 0.0, 0.046), + ), ], marker_scale=(0.1, 0.1, 0.1), ) diff --git a/source/isaaclab_tasks/isaaclab_tasks/contrib/stack/config/franka/stack_joint_pos_instance_randomize_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/contrib/stack/config/franka/stack_joint_pos_instance_randomize_env_cfg.py index c9700331c462..07b483953cd9 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/contrib/stack/config/franka/stack_joint_pos_instance_randomize_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/contrib/stack/config/franka/stack_joint_pos_instance_randomize_env_cfg.py @@ -25,7 +25,7 @@ # Pre-defined configs ## from isaaclab.markers.config import FRAME_MARKER_CFG # isort: skip -from isaaclab_assets.robots.franka import FRANKA_PANDA_CFG # isort: skip +from isaaclab_assets.robots.franka import FRANKA_PANDA_FLAT_CFG # isort: skip @configclass @@ -73,7 +73,7 @@ def __post_init__(self): self.events = EventCfg() # Set Franka as robot - self.scene.robot = replace(FRANKA_PANDA_CFG, prim_path="{ENV_REGEX_NS}/Robot") + self.scene.robot = replace(FRANKA_PANDA_FLAT_CFG, prim_path="{ENV_REGEX_NS}/Robot") # Set actions for the specific robot type (franka) self.actions.arm_action = mdp.JointPositionActionCfg( @@ -175,26 +175,26 @@ def __post_init__(self): marker_cfg.markers["frame"].scale = (0.1, 0.1, 0.1) marker_cfg.prim_path = "/Visuals/FrameTransformer" self.scene.ee_frame = FrameTransformerCfg( - prim_path="{ENV_REGEX_NS}/Robot/panda_link0", + prim_path="{ENV_REGEX_NS}/Robot/(Geometry/)?panda_link0", debug_vis=False, visualizer_cfg=marker_cfg, target_frames=[ FrameTransformerCfg.FrameCfg( - prim_path="{ENV_REGEX_NS}/Robot/panda_hand", + prim_path="{ENV_REGEX_NS}/Robot/(Geometry/.*/)?panda_hand", name="end_effector", offset=OffsetCfg( pos=[0.0, 0.0, 0.1034], ), ), FrameTransformerCfg.FrameCfg( - prim_path="{ENV_REGEX_NS}/Robot/panda_rightfinger", + prim_path="{ENV_REGEX_NS}/Robot/(Geometry/.*/)?panda_rightfinger", name="tool_rightfinger", offset=OffsetCfg( pos=(0.0, 0.0, 0.046), ), ), FrameTransformerCfg.FrameCfg( - prim_path="{ENV_REGEX_NS}/Robot/panda_leftfinger", + prim_path="{ENV_REGEX_NS}/Robot/(Geometry/.*/)?panda_leftfinger", name="tool_leftfinger", offset=OffsetCfg( pos=(0.0, 0.0, 0.046), diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/cabinet/config/franka/cabinet_direct_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/core/cabinet/config/franka/cabinet_direct_env_cfg.py index b6f5f3e16ef0..5c924518d1af 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/cabinet/config/franka/cabinet_direct_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/cabinet/config/franka/cabinet_direct_env_cfg.py @@ -9,7 +9,9 @@ from isaaclab.utils import configclass, replace -from isaaclab_assets.robots.franka import FRANKA_PANDA_CFG +from isaaclab_tasks.utils import preset + +from isaaclab_assets.robots.franka import FRANKA_PANDA_FLAT_CFG from ...cabinet_direct_env_cfg import CabinetDirectEnvCfg, CabinetDirectSceneCfg @@ -18,7 +20,12 @@ class FrankaCabinetDirectSceneCfg(CabinetDirectSceneCfg): """Direct-workflow cabinet scene configured for the Franka robot.""" - robot = replace(FRANKA_PANDA_CFG, prim_path="{ENV_REGEX_NS}/Robot") + robot = replace(FRANKA_PANDA_FLAT_CFG, prim_path="{ENV_REGEX_NS}/Robot") + # Drawer interaction only requires hand and fingertip contacts. + robot.spawn.variants = { + "Physics": preset(default="mujoco", isaacsim_physx="physx", physx="physx", ovphysx="physx"), + "Colliders": preset(default="gripper_only", arm_collisions="primitives"), + } @configclass diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/cabinet/config/franka/joint_pos_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/core/cabinet/config/franka/joint_pos_env_cfg.py index 11fb2caee420..63fa550dce65 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/cabinet/config/franka/joint_pos_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/cabinet/config/franka/joint_pos_env_cfg.py @@ -9,7 +9,9 @@ from isaaclab.sensors.frame_transformer import OffsetCfg from isaaclab.utils import configclass, replace -from isaaclab_assets.robots.franka import FRANKA_PANDA_CFG +from isaaclab_tasks.utils import preset + +from isaaclab_assets.robots.franka import FRANKA_PANDA_FLAT_CFG from ... import mdp from ...cabinet_env_cfg import FRAME_MARKER_SMALL_CFG, CabinetEnvCfg, CabinetSceneCfg @@ -19,28 +21,33 @@ class FrankaCabinetSceneCfg(CabinetSceneCfg): """Cabinet scene configured for the Franka robot.""" - robot = replace(FRANKA_PANDA_CFG, prim_path="{ENV_REGEX_NS}/Robot") + robot = replace(FRANKA_PANDA_FLAT_CFG, prim_path="{ENV_REGEX_NS}/Robot") + # Drawer interaction only requires hand and fingertip contacts. + robot.spawn.variants = { + "Physics": preset(default="mujoco", isaacsim_physx="physx", physx="physx", ovphysx="physx"), + "Colliders": preset(default="gripper_only", arm_collisions="primitives"), + } ee_frame = FrameTransformerCfg( - prim_path="{ENV_REGEX_NS}/Robot/panda_link0", + prim_path="{ENV_REGEX_NS}/Robot/(Geometry/)?panda_link0", debug_vis=False, visualizer_cfg=replace(FRAME_MARKER_SMALL_CFG, prim_path="/Visuals/EndEffectorFrameTransformer"), target_frames=[ FrameTransformerCfg.FrameCfg( - prim_path="{ENV_REGEX_NS}/Robot/panda_hand", + prim_path="{ENV_REGEX_NS}/Robot/(Geometry/.*/)?panda_hand", name="ee_tcp", offset=OffsetCfg( pos=(0.0, 0.0, 0.1034), ), ), FrameTransformerCfg.FrameCfg( - prim_path="{ENV_REGEX_NS}/Robot/panda_leftfinger", + prim_path="{ENV_REGEX_NS}/Robot/(Geometry/.*/)?panda_leftfinger", name="tool_leftfinger", offset=OffsetCfg( pos=(0.0, 0.0, 0.046), ), ), FrameTransformerCfg.FrameCfg( - prim_path="{ENV_REGEX_NS}/Robot/panda_rightfinger", + prim_path="{ENV_REGEX_NS}/Robot/(Geometry/.*/)?panda_rightfinger", name="tool_rightfinger", offset=OffsetCfg( pos=(0.0, 0.0, 0.046), diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka/__init__.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka/__init__.py index 6b677df0f69b..64721d6e42b3 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka/__init__.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka/__init__.py @@ -3,7 +3,7 @@ # # SPDX-License-Identifier: BSD-3-Clause -"""Lift Franka reorient and lift environments.""" +"""Core Franka lifting environment.""" import gymnasium as gym @@ -13,17 +13,6 @@ # Register Gym environments. ## -gym.register( - id="Isaac-Reorient-Franka", - entry_point="isaaclab.envs:ManagerBasedRLEnv", - disable_env_checker=True, - kwargs={ - "env_cfg_entry_point": f"{__name__}.franka_env_cfg:FrankaReorientEnvCfg", - "rsl_rl_cfg_entry_point": f"{agents.__name__}.rsl_rl_ppo_cfg:FrankaPPORunnerCfg", - "default_agent": "rsl_rl", - }, -) - gym.register( id="Isaac-Lift-Franka", entry_point="isaaclab.envs:ManagerBasedRLEnv", diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka/franka_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka/franka_env_cfg.py index ceaa21249b77..5a615cc3a260 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka/franka_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/config/franka/franka_env_cfg.py @@ -3,34 +3,29 @@ # # SPDX-License-Identifier: BSD-3-Clause -"""Configuration for the Franka lift and reorient environments.""" +"""Configuration for the Franka lift environment.""" from isaaclab.actuators import ImplicitActuatorCfg from isaaclab.assets import ArticulationCfg +from isaaclab.managers import CurriculumTermCfg as CurrTerm from isaaclab.managers import EventTermCfg as EventTerm from isaaclab.managers import ObservationTermCfg as ObsTerm from isaaclab.managers import RewardTermCfg as RewTerm from isaaclab.managers import SceneEntityCfg +from isaaclab.physics import PhysxAutoCfg from isaaclab.sensors import ContactSensorCfg from isaaclab.sim import MeshCapsuleCfg, MeshCuboidCfg, MeshSphereCfg from isaaclab.utils import clone, configclass, replace -from isaaclab.utils.assets import ISAACLAB_NUCLEUS_DIR -from isaaclab_assets.robots import FRANKA_PANDA_CFG +from isaaclab_tasks.utils import preset + +from isaaclab_assets.robots import FRANKA_PANDA_FLAT_CFG from ... import lift_env_cfg as lift from ... import mdp -## -# Scene assets -## - -# The lift tasks run the menagerie-converted asset (identified inertials, authored finger coupling) with -# actuators calibrated for it, while the other Franka tasks keep the stock asset. -FRANKA_PANDA_LIFT_CFG = clone(FRANKA_PANDA_CFG) -FRANKA_PANDA_LIFT_CFG.spawn.usd_path = f"{ISAACLAB_NUCLEUS_DIR}/Robots/FrankaEmika/franka_panda.usda" -# Reset clearance was calibrated for these arm meshes; the asset's primitive colliders intersect the ground. -FRANKA_PANDA_LIFT_CFG.spawn.variants = {"Colliders": "convex_hulls"} +# Lift uses task-specific actuators calibrated for contact-rich manipulation. +FRANKA_PANDA_LIFT_CFG = clone(FRANKA_PANDA_FLAT_CFG) FRANKA_PANDA_LIFT_CFG.actuators = { # inspired by libfranka's joint_impedance_control.cpp; ``actuator_velocity_limit`` is the soft task # limit and ``joint_velocity_limit`` the separate solver request @@ -51,6 +46,7 @@ "panda_joint6": 25.0, "panda_joint7": 15.0, }, + viscous_friction=0.0, armature={ "panda_joint[1-2]": 0.6057, "panda_joint[3-4]": 0.4625, @@ -64,6 +60,7 @@ joint_velocity_limit=2.0, stiffness=350.0, damping=175.0, + viscous_friction=0.0, armature=0.1, ), "panda_finger2_passive": ImplicitActuatorCfg( @@ -73,6 +70,7 @@ joint_velocity_limit=2.0, stiffness=0.0, damping=0.0, + viscous_friction=0.0, armature=0.1, ), } @@ -96,12 +94,18 @@ @configclass class FrankaSceneCfg(lift.SceneCfg): - """Franka scene for the lift and reorient tasks.""" + """Franka scene for the lift task.""" robot: ArticulationCfg = replace(FRANKA_PANDA_LIFT_CFG, prim_path="{ENV_REGEX_NS}/Robot") def __post_init__(self): super().__post_init__() + # Lift only requires hand-object and fingertip-object contacts; the asset's + # complete primitive colliders remain available for general robot use. + self.robot.spawn.variants = { + "Physics": preset(default="mujoco", isaacsim_physx="physx", physx="physx", ovphysx="physx"), + "Colliders": preset(default="gripper_only", arm_collisions="primitives"), + } self.robot.spawn.activate_contact_sensors = True # the base is rotated by 180 degrees about z so the workspace lies at positive x self.robot.init_state.rot = (0.0, 0.0, 1.0, 0.0) @@ -157,9 +161,12 @@ class FrankaRelJointPosActionCfg: @configclass -class FrankaReorientRewardCfg(lift.RewardsCfg): +class FrankaLiftRewardCfg(lift.RewardsCfg): """Reward terms for the MDP, with the Franka finger contact sensors filled in.""" + action_rate = RewTerm(func=mdp.action_rate_l2, weight=-1e-4) + joint_vel = RewTerm(func=mdp.joint_vel_l2, weight=-1e-4, params={"asset_cfg": SceneEntityCfg("robot")}) + good_finger_contact = RewTerm( func=mdp.contacts, weight=0.75, @@ -186,6 +193,28 @@ def __post_init__(self): self.success.params["finger_names"] = FINGER_SENSORS +@configclass +class FrankaLiftCurriculumCfg(lift.CurriculumCfg): + """Franka-specific motion regularization as grasp difficulty increases.""" + + action_rate = CurrTerm( + func=mdp.modify_term_cfg, + params={ + "address": "rewards.action_rate.weight", + "modify_fn": mdp.difficulty_interpolate_float, + "modify_params": {"initial_value": -1e-4, "final_value": -1e-1, "difficulty_term_str": "adr"}, + }, + ) + joint_vel = CurrTerm( + func=mdp.modify_term_cfg, + params={ + "address": "rewards.joint_vel.weight", + "modify_fn": mdp.difficulty_interpolate_float, + "modify_params": {"initial_value": -1e-4, "final_value": -1e-1, "difficulty_term_str": "adr"}, + }, + ) + + @configclass class FrankaEventCfg(lift.EventCfg): """Franka-specific event configuration.""" @@ -243,10 +272,10 @@ def __post_init__(self): @configclass class FrankaMixinCfg: - """Franka-specific scene, observation, action, reward and event terms, mixed into the task configurations.""" + """Franka scene, observation, action, reward and event terms for the lift task.""" scene: FrankaSceneCfg = FrankaSceneCfg(num_envs=4096, env_spacing=3, replicate_physics=True) - rewards: FrankaReorientRewardCfg = FrankaReorientRewardCfg() + rewards: FrankaLiftRewardCfg = FrankaLiftRewardCfg() observations: StateObservationCfg = StateObservationCfg() actions: FrankaRelJointPosActionCfg = FrankaRelJointPosActionCfg() events: FrankaEventCfg = FrankaEventCfg() @@ -256,24 +285,40 @@ def __post_init__(self): self.commands.object_pose.body_name = "panda_hand" # Franka base is rotated 180 deg about z, so the workspace mirrors to positive x. self.commands.object_pose.ranges.pos_x = (0.3, 0.7) + # The actuator limits are nominal policy limits, not evidence of unstable physics. + # Reserve the abnormal-state termination for velocities beyond the solver contract. + self.terminations.abnormal_robot.func = mdp.abnormal_robot_state self.terminations.abnormal_robot.params["asset_cfg"] = SceneEntityCfg("robot", joint_names="panda_joint.*") -@configclass -class FrankaReorientEnvCfg(FrankaMixinCfg, lift.ReorientEnvCfg): - """Franka object reorientation environment.""" - - def play_mode(self): - super().play_mode() - # evaluate at the datasheet gripper speed: without the closing-speed randomization the hand - # damping caps closing at the real hand's jaw-speed limit of 0.2 m/s - self.events.gripper_closing_speed = None - - @configclass class FrankaLiftEnvCfg(FrankaMixinCfg, lift.LiftEnvCfg): """Franka object lifting environment.""" + curriculum: FrankaLiftCurriculumCfg | None = FrankaLiftCurriculumCfg() + + def __post_init__(self): + super().__post_init__() + # The Franka checkpoint's eight-shape bank needs Isaac Sim; OvPhysX remains explicit and cube-only. + self.scene.object.spawn.physx = clone(self.scene.object.spawn.shapes) + self.sim.physics.physx = PhysxAutoCfg(isaacsim_physx=self.sim.physics.isaacsim_physx) + reset = self.events.conditional_reset.params + terms = reset["terms"] + # The aligned opening must be written after the generic gripper-width reset. + pregrasp = terms.pop("reset_object_to_target") + terms["reset_object_to_target"] = pregrasp + pregrasp.func = mdp.reset_to_grasp + pregrasp.params.update( + probability=0.75, + gripper_cfg=SceneEntityCfg("robot", joint_names="panda_finger_joint.*"), + pose_range={"x": (-0.002, 0.002), "y": (-0.002, 0.002), "z": (0.1, 0.1)}, + gripper_joint_positions=[0.026, 0.026, 0.0135, 0.026, 0.021, 0.026, 0.026, 0.011], + asset_orientations=[(0.0, 0.0, 0.0, 1.0)] * 5 + [(0.0, 2.0**-0.5, 0.0, 2.0**-0.5)] * 3, + ) + pregrasp.params.pop("velocity_range") + # Farthest-point thinning discards valid near-grasp starts. + reset["diversity_feature"] = None + def play_mode(self): super().play_mode() # evaluate at the datasheet gripper speed: without the closing-speed randomization the hand 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 cd668dd3ec47..3ad14de79289 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 @@ -48,7 +48,7 @@ from isaaclab_tasks.utils import PresetCfg, preset from isaaclab_tasks.utils.presets import MultiBackendRendererCfg -from isaaclab_assets.robots.franka import FRANKA_PANDA_MENAGERIE_CFG +from isaaclab_assets.robots.franka import FRANKA_PANDA_FLAT_CFG from ... import mdp @@ -56,7 +56,7 @@ # Scene assets ## -# shared volume material parameters; the Newton configuration uses the equivalent Lame parameters +# Shared volume material parameters. The Newton config below uses the equivalent Lame parameters. YOUNGS_MODULUS = 2e5 POISSONS_RATIO = 0.3 @@ -190,12 +190,12 @@ class PhysicsCfg(PresetCfg): class FrankaSoftBaseSceneCfg(InteractiveSceneCfg): """Scene for the Franka deformable environment, also the base of the cloth and cable scenes.""" - robot: ArticulationCfg = replace(FRANKA_PANDA_MENAGERIE_CFG, prim_path="{ENV_REGEX_NS}/Robot") - robot.spawn.variants = preset( - default={"Physics": "mujoco"}, - isaacsim_physx={"Physics": "physx"}, - physx={"Physics": "physx"}, - ) + robot: ArticulationCfg = replace(FRANKA_PANDA_FLAT_CFG, prim_path="{ENV_REGEX_NS}/Robot") + # Deformable contact is restricted to the hand and fingertips for throughput. + robot.spawn.variants = { + "Physics": preset(default="mujoco", isaacsim_physx="physx", physx="physx", ovphysx="physx"), + "Colliders": "gripper_only", + } # end-effector frame for reward shaping ee_frame: FrameTransformerCfg = FrameTransformerCfg( @@ -257,6 +257,7 @@ def __post_init__(self): "panda_joint6": 25.0, "panda_joint7": 15.0, }, + viscous_friction=0.0, armature={ "panda_joint[1-2]": 0.6057, "panda_joint[3-4]": 0.4625, @@ -270,6 +271,7 @@ def __post_init__(self): joint_velocity_limit=2.0, stiffness=350.0, damping=175.0, + viscous_friction=0.0, armature=0.1, ), "panda_finger2_passive": ImplicitActuatorCfg( @@ -279,6 +281,7 @@ def __post_init__(self): joint_velocity_limit=2.0, stiffness=0.0, damping=0.0, + viscous_friction=0.0, armature=0.1, ), } diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/lift_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/lift_env_cfg.py index 5a31b3f878c4..ba7843144e02 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/lift_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/lift_env_cfg.py @@ -97,6 +97,8 @@ class ObjectCfg(PresetCfg): mass_props=sim_utils.MassCfg(mass=0.2), ) default = shapes + # Automatic PhysX may use the homogeneous OvPhysX replication path. + physx = cube ovphysx = cube 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 e5826e6db8f4..fcb2979c51bf 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/__init__.pyi +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/__init__.pyi @@ -31,6 +31,7 @@ __all__ = [ "deformable_ee_distance", "deformable_lifting", "deformable_outside_bounds", + "difficulty_interpolate_float", "ee_below_minimum", "fingers_contact_force_b", "get_reset_state", @@ -51,6 +52,7 @@ __all__ = [ "reset_cable_state_uniform", "reset_deformable_over_support", "reset_joints_shared_offset", + "reset_to_grasp", "reset_to_target", "set_reset_state", "slab_clearance", @@ -61,7 +63,7 @@ __all__ = [ from isaaclab_tasks.utils.success_monitor import SuccessMonitor, SuccessMonitorCfg from .commands import CableUniformPoseCommandCfg, DeformableUniformPoseCommandCfg, ObjectUniformPoseCommandCfg -from .curriculums import gravity_range_linear +from .curriculums import difficulty_interpolate_float, gravity_range_linear from .events import ( conditional_reset, grasp_travel_distance, @@ -69,6 +71,7 @@ from .events import ( reset_cable_state_uniform, reset_deformable_over_support, reset_joints_shared_offset, + reset_to_grasp, reset_to_target, slab_clearance, ) 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 4dfad1e07dd7..587adeb972c7 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/curriculums.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/curriculums.py @@ -14,6 +14,20 @@ from isaaclab.envs import ManagerBasedRLEnv +def difficulty_interpolate_float( + env: ManagerBasedRLEnv, + _env_ids: Sequence[int], + _data: float, + initial_value: float, + final_value: float, + difficulty_term_str: str, +) -> float: + """Interpolate a scalar continuously with an ADR term's success-driven difficulty.""" + difficulty_term = getattr(env.curriculum_manager.cfg, difficulty_term_str).func + fraction = min(max(difficulty_term.difficulty_frac, 0.0), 1.0) + return initial_value + fraction * (final_value - initial_value) + + def gravity_range_linear( env: ManagerBasedRLEnv, _env_ids: Sequence[int], 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 c2a39d721d16..fdbdd48c4eb4 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/events.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/events.py @@ -18,7 +18,7 @@ from isaaclab import cloner from isaaclab.managers import EventTermCfg, ManagerTermBase, ManagerTermBaseCfg, SceneEntityCfg from isaaclab.utils import instantiate -from isaaclab.utils.math import quat_apply, random_orientation, sample_uniform +from isaaclab.utils.math import quat_apply, quat_mul, random_orientation, sample_uniform from isaaclab_tasks.utils.success_monitor import SuccessMonitor, SuccessMonitorCfg @@ -147,6 +147,96 @@ def reset_to_target( asset.write_root_velocity_to_sim_index(root_velocity=velocities, env_ids=picked) +class reset_to_grasp(ManagerTermBase): + """Place selected object variants in aligned parallel-gripper pre-grasps.""" + + def __init__(self, cfg: EventTermCfg, env: ManagerBasedEnv): + super().__init__(cfg, env) + asset_cfg: SceneEntityCfg = cfg.params["asset_cfg"] + gripper_cfg: SceneEntityCfg = cfg.params["gripper_cfg"] + object_cfg = getattr(env.cfg.scene, asset_cfg.name) + plan = env.scene.clone_plan + object_prototypes = cloner.path.get_asset_prototypes(plan, object_cfg.prim_path) + if not len(object_prototypes): + raise ValueError(f"Could not find clone-plan prototypes for asset '{asset_cfg.name}'.") + variant_ids = np.full(env.num_envs, -1, dtype=np.int64) + for variant_id, prototype_id in enumerate(object_prototypes): + world_ids, _ = cloner.query.get_asset_prototype_unique_world_index(plan.topology, int(prototype_id)) + world_ids = world_ids[world_ids >= 0] + if (variant_ids[world_ids] >= 0).any(): + raise ValueError(f"Multiple object variants are assigned to asset '{asset_cfg.name}' in one world.") + variant_ids[world_ids] = variant_id + if (variant_ids < 0).any(): + raise ValueError(f"No object variant is assigned to asset '{asset_cfg.name}' in some worlds.") + self._variant_ids = torch.as_tensor(variant_ids, device=env.device) + self._gripper_joint_ids = env.scene[gripper_cfg.name].find_joints(gripper_cfg.joint_names)[0] + gripper_joint_positions = cfg.params["gripper_joint_positions"] + asset_orientations = cfg.params["asset_orientations"] + if len(gripper_joint_positions) < len(object_prototypes) or len(asset_orientations) < len(object_prototypes): + raise ValueError(f"reset_to_grasp requires one gripper pose per object variant ({len(object_prototypes)}).") + self._gripper_joint_positions = torch.as_tensor(gripper_joint_positions, device=env.device) + self._asset_orientations = torch.as_tensor(asset_orientations, device=env.device) + + def __call__( + self, + env: ManagerBasedEnv, + env_ids: torch.Tensor, + pose_range: dict[str, tuple[float, float]], + probability: float, + target_cfg: SceneEntityCfg, + gripper_cfg: SceneEntityCfg, + gripper_joint_positions: list[float], + asset_orientations: list[tuple[float, float, float, float]], + asset_cfg: SceneEntityCfg = SceneEntityCfg("object"), + ) -> None: + """Reset a fraction of environments to configured pre-grasps. + + Args: + env: The environment. + env_ids: Environments to reset. + pose_range: Object-position offsets in the target frame [m]. + probability: Per-environment probability of applying the pre-grasp. + target_cfg: Body defining the pre-grasp frame. + gripper_cfg: Parallel gripper joints to pose. + gripper_joint_positions: Joint position for each object variant [m or rad, depending on joint type]. + asset_orientations: Object orientation in the target frame for each variant, in ``(x, y, z, w)`` order. + asset_cfg: Object asset to reset. + """ + picked = env_ids[torch.rand(len(env_ids), device=env.device) < probability] + if len(picked) == 0: + return + + asset = env.scene[asset_cfg.name] + target = env.scene[target_cfg.name] + gripper = env.scene[gripper_cfg.name] + variant_ids = self._variant_ids[picked] + + target_pos = target.data.body_pos_w.torch[picked][:, target_cfg.body_ids, :].reshape(len(picked), -1)[:, :3] + target_quat = target.data.body_quat_w.torch[picked][:, target_cfg.body_ids, :].reshape(len(picked), -1)[:, :4] + offset_ranges = torch.tensor( + [tuple(pose_range.get(axis, (0.0, 0.0))) for axis in ("x", "y", "z")], device=asset.device + ) + local_offsets = sample_uniform(offset_ranges[:, 0], offset_ranges[:, 1], (len(picked), 3), device=asset.device) + positions = target_pos + quat_apply(target_quat, local_offsets) + local_orientations = self._asset_orientations[variant_ids] + orientations = quat_mul(target_quat, local_orientations) + + joint_positions = self._gripper_joint_positions[variant_ids, None].expand(-1, len(self._gripper_joint_ids)) + gripper.write_joint_position_to_sim_index( + position=joint_positions, joint_ids=self._gripper_joint_ids, env_ids=picked + ) + gripper.write_joint_velocity_to_sim_index( + velocity=torch.zeros_like(joint_positions), joint_ids=self._gripper_joint_ids, env_ids=picked + ) + gripper.set_joint_position_target_index( + target=joint_positions, joint_ids=self._gripper_joint_ids, env_ids=picked + ) + asset.write_root_pose_to_sim_index(root_pose=torch.cat((positions, orientations), dim=-1), env_ids=picked) + asset.write_root_velocity_to_sim_index( + root_velocity=torch.zeros((len(picked), 6), device=asset.device), env_ids=picked + ) + + class conditional_reset(ManagerTermBase): """Run wrapped reset terms and guarantee the resulting states satisfy a criterion. 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 5f38e5d6ea9a..683307b69612 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/terminations.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/terminations.py @@ -21,7 +21,7 @@ class out_of_bound(ManagerTermBase): - """Termination condition for when the object falls out of bound. + """Terminate when the rigid object state is non-finite or its position leaves the workspace bounds. The world-space bounds are cached and rebuilt per axis only when the corresponding ``in_bound_range`` entry changes. This keeps the hot path free of host-to-device @@ -58,7 +58,12 @@ def __call__( self._cached_axis[i] = bounds pos_w = self._object.data.root_pos_w.torch - return ((pos_w < self._lower) | (pos_w > self._upper)).any(dim=1) + quat_w = self._object.data.root_quat_w.torch + vel_w = self._object.data.root_vel_w.torch + invalid = ( + ~torch.isfinite(pos_w).all(dim=1) | ~torch.isfinite(quat_w).all(dim=1) | ~torch.isfinite(vel_w).all(dim=1) + ) + return invalid | ((pos_w < self._lower) | (pos_w > self._upper)).any(dim=1) def abnormal_robot_state(env: ManagerBasedRLEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor: @@ -67,8 +72,8 @@ def abnormal_robot_state(env: ManagerBasedRLEnv, asset_cfg: SceneEntityCfg = Sce Such violations indicate unstable physics, typically caused by aggressive actions. """ robot: Articulation = env.scene[asset_cfg.name] - joint_vel = robot.data.joint_vel.torch - joint_vel_limits = robot.data.joint_vel_limits.torch + joint_vel = robot.data.joint_vel.torch[:, asset_cfg.joint_ids] + joint_vel_limits = robot.data.joint_vel_limits.torch[:, asset_cfg.joint_ids] return (joint_vel.abs() > (joint_vel_limits * 2)).any(dim=1) diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/utils.py b/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/utils.py index 96926a118a82..8cc4b8235813 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/utils.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/lift/mdp/utils.py @@ -311,7 +311,11 @@ def collect_collision_meshes(root_prim, owner_frame_fn: Callable) -> dict[int, t mesh_types = PRIMITIVE_MESH_TYPES + ["Mesh"] mesh_prims = sim_utils.get_all_matching_child_prims( root_prim.GetPath(), - lambda prim: prim.GetTypeName() in mesh_types and prim.HasAPI(UsdPhysics.CollisionAPI), + lambda prim: ( + prim.GetTypeName() in mesh_types + and prim.HasAPI(UsdPhysics.CollisionAPI) + and UsdPhysics.CollisionAPI(prim).GetCollisionEnabledAttr().Get() + ), ) meshes_by_owner: dict[int, list[trimesh.Trimesh]] = {} diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reach/config/franka/franka_reach_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/core/reach/config/franka/franka_reach_env_cfg.py index 1fb5f4847ea5..95059747f04a 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/reach/config/franka/franka_reach_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reach/config/franka/franka_reach_env_cfg.py @@ -24,13 +24,19 @@ from isaaclab.devices.keyboard import Se3KeyboardCfg from isaaclab.devices.spacemouse import Se3SpaceMouseCfg from isaaclab.envs.mdp.actions.actions_cfg import DifferentialInverseKinematicsActionCfg +from isaaclab.managers import RewardTermCfg as RewTerm +from isaaclab.managers import SceneEntityCfg +from isaaclab.managers import TerminationTermCfg as DoneTerm from isaaclab.utils import configclass, replace from isaaclab_tasks.utils import PresetCfg, preset -from isaaclab_assets import FRANKA_PANDA_CFG, FRANKA_PANDA_MENAGERIE_CFG +## +# Pre-defined configs +## +from isaaclab_assets import FRANKA_PANDA_FLAT_CFG # isort: skip -from ...reach_env_cfg import ReachEnvCfg +from ...reach_env_cfg import ReachEnvCfg, RewardsCfg, TerminationsCfg ## # Environment configuration @@ -61,7 +67,9 @@ class FrankaArmActionCfg(PresetCfg): diffik, controller=replace(diffik.controller, use_relative_mode=False, ik_params={"lambda_val": 0.45}), body_offset=None, - scale=1.0, + # Normalize position actions around the center and half-spans of the commanded workspace. + scale=(0.15, 0.2, 0.175, 1.0, 1.0, 1.0, 1.0), + offset=(0.5, 0.0, 0.325, 0.0, 0.0, 0.0, 0.0), ) newton_ik: NewtonInverseKinematicsActionCfg = NewtonInverseKinematicsActionCfg( asset_name="robot", @@ -82,10 +90,32 @@ class FrankaArmActionCfg(PresetCfg): default: mdp.JointPositionActionCfg = joint_pos +@configclass +class FrankaReachRewardsCfg(RewardsCfg): + """Keep continuous pose-tracking rewards specific to Franka Reach.""" + + end_effector_position_tracking_fine_grained = RewTerm( + func=mdp.position_command_error_tanh, + weight=0.1, + params={"asset_cfg": SceneEntityCfg("robot", body_names="panda_hand"), "std": 0.1, "command_name": "ee_pose"}, + ) + success: RewTerm | None = None + + +@configclass +class FrankaReachTerminationsCfg(TerminationsCfg): + """Track Franka Reach success without ending the episode early.""" + + success: DoneTerm | None = None + + @configclass class FrankaReachEnvCfg(ReachEnvCfg): """Franka Reach configuration with selectable arm and physics presets.""" + rewards: FrankaReachRewardsCfg = FrankaReachRewardsCfg() + terminations: FrankaReachTerminationsCfg = FrankaReachTerminationsCfg() + def validate_config(self) -> None: """Validate the selected controller and physics backend.""" if isinstance(self.actions.arm_action, NewtonInverseKinematicsActionCfg) and not isinstance( @@ -96,14 +126,12 @@ def validate_config(self) -> None: def __post_init__(self): super().__post_init__() - # Use the collision-complete legacy asset in PhysX until the Menagerie asset is corrected. - self.scene.robot = replace(FRANKA_PANDA_MENAGERIE_CFG, prim_path="{ENV_REGEX_NS}/Robot") - self.scene.robot.spawn.usd_path = preset( - default=self.scene.robot.spawn.usd_path, - isaacsim_physx=FRANKA_PANDA_CFG.spawn.usd_path, - physx=FRANKA_PANDA_CFG.spawn.usd_path, - ovphysx=FRANKA_PANDA_CFG.spawn.usd_path, - ) + # Reach has no robot-scene contact objective, so use the fast gripper collider preset. + self.scene.robot = replace(FRANKA_PANDA_FLAT_CFG, prim_path="{ENV_REGEX_NS}/Robot") + self.scene.robot.spawn.variants = { + "Physics": preset(default="mujoco", isaacsim_physx="physx", physx="physx", ovphysx="physx"), + "Colliders": preset(default="gripper_only", arm_collisions="primitives"), + } # IK targets need backend-native gravity control to hold steady between commands. self.scene.robot.spawn.rigid_props = [ PhysxRigidBodyCfg( diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reach/config/franka/franka_reach_osc_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/core/reach/config/franka/franka_reach_osc_env_cfg.py index 09c6d8fab2cd..63739cafde7e 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/reach/config/franka/franka_reach_osc_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reach/config/franka/franka_reach_osc_env_cfg.py @@ -42,13 +42,18 @@ def __post_init__(self): super().__post_init__() # Use an explicit actuator to enforce the USD-authored effort limits for effort control. Keep the - # asset's solver velocity limit: the Menagerie USD authors none, so dropping it leaves the arm unbounded. + # asset's solver velocity limit: the canonical USD authors none, so dropping it leaves the arm unbounded. arm_actuator = self.scene.robot.actuators["panda_arm"] self.scene.robot.actuators["panda_arm"] = IdealPDActuatorCfg( joint_names_expr=arm_actuator.joint_names_expr, + joint_effort_limit=arm_actuator.joint_effort_limit, joint_velocity_limit=arm_actuator.joint_velocity_limit, stiffness=0.0, damping=0.0, + armature=arm_actuator.armature, + friction=arm_actuator.friction, + dynamic_friction=arm_actuator.dynamic_friction, + viscous_friction=arm_actuator.viscous_friction, ) for rigid_props in self.scene.robot.spawn.rigid_props: if isinstance(rigid_props, PhysxRigidBodyCfg): diff --git a/source/isaaclab_tasks/test/benchmark/test_render_benchmark_cfg.py b/source/isaaclab_tasks/test/benchmark/test_render_benchmark_cfg.py index 29f4b018c052..e836f2eba01b 100644 --- a/source/isaaclab_tasks/test/benchmark/test_render_benchmark_cfg.py +++ b/source/isaaclab_tasks/test/benchmark/test_render_benchmark_cfg.py @@ -155,18 +155,19 @@ def test_benchmark_mode_orders_joint_updates_and_rendering(mode): @pytest.mark.parametrize( - ("presets", "expected_type"), + ("presets", "expected_type", "expected_asset_physics"), [ - ((), NewtonCfg), - (("physx",), PhysxAutoCfg), - (("isaacsim_physx",), PhysxCfg), - (("ovphysx",), OvPhysxCfg), + ((), NewtonCfg, "mujoco"), + (("physx",), PhysxAutoCfg, "physx"), + (("isaacsim_physx",), PhysxCfg, "physx"), + (("ovphysx",), OvPhysxCfg, "physx"), ], ) -def test_physics_presets_resolve_to_expected_backend(presets, expected_type): +def test_physics_presets_resolve_to_expected_backend(presets, expected_type, expected_asset_physics): cfg = resolve_presets(_load_cfg(), selected=presets) assert isinstance(cfg.sim.physics, expected_type) + assert cfg.scene.robot.spawn.variants["Physics"] == expected_asset_physics @pytest.mark.parametrize("physics_preset", ["ovphysx", "physx"]) diff --git a/source/isaaclab_tasks/test/contrib/test_franka_pour_env_cfg.py b/source/isaaclab_tasks/test/contrib/test_franka_pour_env_cfg.py index 4e80cafc5d71..1728396c2f9a 100644 --- a/source/isaaclab_tasks/test/contrib/test_franka_pour_env_cfg.py +++ b/source/isaaclab_tasks/test/contrib/test_franka_pour_env_cfg.py @@ -113,7 +113,7 @@ def test_reset_dataset_contract_stores_root_relative_robot_asset_path(): robot_asset = _reset_dataset_task_contract(cfg)["robot_asset"] assert robot_asset == "Robots/FrankaEmika/franka_panda.usda" assert f"{ISAACLAB_NUCLEUS_DIR}/{robot_asset}" == FRANKA_POUR_ROBOT_ASSET_ID - assert cfg.scene.robot.spawn.variants == {"Colliders": "convex_hulls"} + assert cfg.scene.robot.spawn.variants == {"Physics": "mujoco", "Colliders": "convex_hulls"} def test_capacity_resolution_only_updates_world_dependent_solver_limits(): diff --git a/source/isaaclab_tasks/test/core/test_lift_env_cfg.py b/source/isaaclab_tasks/test/core/test_lift_env_cfg.py index 6ea417797ae9..1f9574090229 100644 --- a/source/isaaclab_tasks/test/core/test_lift_env_cfg.py +++ b/source/isaaclab_tasks/test/core/test_lift_env_cfg.py @@ -3,29 +3,41 @@ # # SPDX-License-Identifier: BSD-3-Clause -"""Behavioral tests for the unified dexterous Lift and Reorient tasks.""" +"""Behavioral tests for the dexterous Lift tasks.""" from types import SimpleNamespace +from unittest.mock import Mock +import gymnasium as gym import pytest import torch import warp as wp -from pxr import Usd +from pxr import Usd, UsdGeom, UsdPhysics +from isaaclab.cloner import make_clone_plan from isaaclab.managers import CommandTerm, ObservationTermCfg, SceneEntityCfg -from isaaclab.sim import select_usd_variants +from isaaclab.sim import select_usd_variants, use_stage from isaaclab.utils.warp import ProxyArray from isaaclab_tasks.core.lift import mdp -from isaaclab_tasks.core.lift.config.franka.franka_env_cfg import FrankaLiftEnvCfg, FrankaReorientEnvCfg +from isaaclab_tasks.core.lift.config.franka.franka_env_cfg import FrankaLiftEnvCfg from isaaclab_tasks.core.lift.config.franka_soft.franka_soft_env_cfg import FrankaSoftEnvCfg +from isaaclab_tasks.core.lift.config.kuka_allegro.kuka_allegro_camera_env_cfg import ( + KukaAllegroLiftCameraEnvCfg, + KukaAllegroReorientCameraEnvCfg, +) +from isaaclab_tasks.core.lift.config.kuka_allegro.kuka_allegro_env_cfg import ( + KukaAllegroLiftEnvCfg, + KukaAllegroReorientEnvCfg, +) from isaaclab_tasks.core.lift.mdp.commands import pose_commands from isaaclab_tasks.core.lift.mdp.commands.pose_commands import ( CableUniformPoseCommand, DeformableUniformPoseCommand, ObjectUniformPoseCommand, ) +from isaaclab_tasks.core.lift.mdp.utils import collect_collision_meshes from isaaclab_tasks.utils.hydra import resolve_presets @@ -47,6 +59,80 @@ def __init__(self, environment_ids: torch.Tensor, **assets): self.env_origins = torch.zeros((len(environment_ids), 3)) +def test_franka_reorient_is_not_registered() -> None: + """Unsupported core Franka reorientation is not registered.""" + assert "Isaac-Reorient-Franka" not in gym.registry + + +def test_kuka_allegro_lift_family_keeps_its_existing_reward_contract() -> None: + """Franka-only motion regularization must not enter Kuka state or camera tasks.""" + cfgs = ( + KukaAllegroLiftEnvCfg(), + KukaAllegroReorientEnvCfg(), + KukaAllegroLiftCameraEnvCfg().default, + KukaAllegroReorientCameraEnvCfg().default, + ) + for cfg in cfgs: + for name in ("action_rate", "joint_vel"): + assert not hasattr(cfg.rewards, name) + assert not hasattr(cfg.curriculum, name) + + +@pytest.mark.parametrize("cfg_type", [KukaAllegroLiftEnvCfg, KukaAllegroReorientEnvCfg]) +def test_kuka_allegro_automatic_physx_keeps_its_existing_cube_path(cfg_type) -> None: + """The Franka-only shape-bank choice must not change Kuka's automatic PhysX preset.""" + cfg = resolve_presets(cfg_type(), selected=("physx",)) + + assert cfg.sim.physics.ovphysx is not None + assert not hasattr(cfg.scene.object.spawn, "assets_cfg") + + +def test_rigid_lift_motion_regularization_follows_success_driven_adr() -> None: + """Franka motion penalties should grow with success, not elapsed steps.""" + cfg = FrankaLiftEnvCfg() + curriculum = cfg.curriculum + assert curriculum.adr.func is mdp.DifficultyScheduler + difficulty = SimpleNamespace(difficulty_frac=0.0) + env = SimpleNamespace( + common_step_counter=100_000, + curriculum_manager=SimpleNamespace(cfg=SimpleNamespace(adr=SimpleNamespace(func=difficulty))), + ) + + for term_name in ("action_rate", "joint_vel"): + assert getattr(cfg.rewards, term_name).weight == pytest.approx(-1e-4) + term = getattr(curriculum, term_name) + assert term.func is mdp.modify_term_cfg + assert term.params["address"] == f"rewards.{term_name}.weight" + assert term.params["modify_fn"] is mdp.difficulty_interpolate_float + params = term.params["modify_params"] + assert params == {"initial_value": -1e-4, "final_value": -1e-1, "difficulty_term_str": "adr"} + for fraction, expected in ( + (-1.0, -1e-4), + (0.0, -1e-4), + (0.02, -0.002098), + (0.5, -0.05005), + (1.0, -0.1), + (2.0, -0.1), + ): + difficulty.difficulty_frac = fraction + weight = term.params["modify_fn"](env, torch.arange(2), -1e-4, **params) + assert weight == pytest.approx(expected) + + +def test_abnormal_robot_state_ignores_nominal_velocity_excursions() -> None: + """The instability guard should leave policy exploration below twice the solver limit intact.""" + robot = SimpleNamespace( + data=SimpleNamespace( + joint_vel=SimpleNamespace(torch=torch.tensor([[15.0, 100.0], [20.0, 100.0], [20.1, 0.0]])), + joint_vel_limits=SimpleNamespace(torch=torch.full((3, 2), 10.0)), + ) + ) + env = SimpleNamespace(scene={"robot": robot}) + arm_only = SimpleNamespace(name="robot", joint_ids=[0]) + + assert mdp.abnormal_robot_state(env, arm_only).tolist() == [False, False, True] + + @pytest.mark.parametrize( ("selected_presets", "expected_physics"), [ @@ -62,19 +148,45 @@ def test_franka_soft_robot_physics_variant_matches_backend( """The Franka USD physics payload must match the selected simulation backend.""" cfg = resolve_presets(FrankaSoftEnvCfg(), selected=selected_presets) - assert cfg.scene.robot.spawn.variants == {"Physics": expected_physics} + assert cfg.scene.robot.spawn.variants == {"Physics": expected_physics, "Colliders": "gripper_only"} + + +def test_franka_lift_physx_runtimes_share_the_same_mdp() -> None: + """Isaac Sim PhysX and OvPhysX should differ only in runtime configuration.""" + isaacsim_cfg = resolve_presets(FrankaLiftEnvCfg(), selected=("isaacsim_physx", "cube")) + ovphysx_cfg = resolve_presets(FrankaLiftEnvCfg(), selected=("ovphysx", "cube")) + assert isaacsim_cfg.sim.physics.enable_external_forces_every_iteration + assert ovphysx_cfg.sim.physics.enable_external_forces_every_iteration + isaacsim = isaacsim_cfg.to_dict() + ovphysx = ovphysx_cfg.to_dict() + + for section in ("scene", "observations", "actions", "commands", "rewards", "terminations", "events", "curriculum"): + assert ovphysx[section] == isaacsim[section], section + +def test_franka_lift_object_preset_matches_physx_runtime() -> None: + """Automatic PhysX keeps the eight-shape task; explicit OvPhysX uses the qualified cube.""" + isaacsim = resolve_presets(FrankaLiftEnvCfg(), selected=("isaacsim_physx",)) + automatic = resolve_presets(FrankaLiftEnvCfg(), selected=("physx",)) + ovphysx = resolve_presets(FrankaLiftEnvCfg(), selected=("ovphysx",)) -@pytest.mark.parametrize("cfg_type", [FrankaLiftEnvCfg, FrankaReorientEnvCfg]) -def test_franka_rigid_tasks_select_collision_meshes_for_reset_clearance(cfg_type) -> None: - """Reset validation keeps the original arm meshes when the asset defaults to capsules.""" - cfg = cfg_type() + assert len(isaacsim.scene.object.spawn.assets_cfg) == 8 + assert len(automatic.scene.object.spawn.assets_cfg) == 8 + assert automatic.scene.object.spawn.to_dict() == isaacsim.scene.object.spawn.to_dict() + assert automatic.sim.physics.ovphysx is None + assert not hasattr(ovphysx.scene.object.spawn, "assets_cfg") + + +def test_franka_lift_selects_gripper_only_colliders() -> None: + """Lift avoids the arm colliders that intersect the ground during reset sampling.""" + cfg = resolve_presets(FrankaLiftEnvCfg(), selected=()) stage = Usd.Stage.CreateInMemory() robot = stage.DefinePrim("/Robot", "Xform") colliders = robot.GetVariantSets().AddVariantSet("Colliders") for selection, prim_path, prim_type in ( ("convex_hulls", "/Robot/link1_c/link1_c", "Mesh"), ("primitives", "/Robot/link1_capsule", "Capsule"), + ("gripper_only", "/Robot/gripper_capsule", "Capsule"), ): colliders.AddVariant(selection) colliders.SetVariantSelection(selection) @@ -84,10 +196,120 @@ def test_franka_rigid_tasks_select_collision_meshes_for_reset_clearance(cfg_type select_usd_variants("/Robot", cfg.scene.robot.spawn.variants or {}, stage=stage) - assert stage.GetPrimAtPath("/Robot/link1_c/link1_c").IsValid() + assert stage.GetPrimAtPath("/Robot/gripper_capsule").IsValid() + assert not stage.GetPrimAtPath("/Robot/link1_c/link1_c").IsValid() assert not stage.GetPrimAtPath("/Robot/link1_capsule").IsValid() +def test_franka_lift_retains_aligned_pregrasp_resets() -> None: + """The aligned Lift reset must run after the generic finger-width reset.""" + lift = FrankaLiftEnvCfg() + + assert lift.actions.action.joint_names == [".*"] + assert lift.terminations.abnormal_robot.func is mdp.abnormal_robot_state + lift_reset = lift.events.conditional_reset.params + lift_reset_terms = list(lift_reset["terms"]) + assert lift_reset_terms.index("reset_object_to_target") > lift_reset_terms.index("reset_gripper_width") + lift_target_reset = lift_reset["terms"]["reset_object_to_target"] + assert lift_target_reset.func is mdp.reset_to_grasp + assert lift_target_reset.params["probability"] == pytest.approx(0.75) + assert lift_target_reset.params["pose_range"] == { + "x": (-0.002, 0.002), + "y": (-0.002, 0.002), + "z": (0.1, 0.1), + } + assert lift_target_reset.params["gripper_cfg"].joint_names == "panda_finger_joint.*" + assert len(lift_target_reset.params["gripper_joint_positions"]) == 8 + assert len(lift_target_reset.params["asset_orientations"]) == 8 + assert "object_robot_clearance" in lift_reset["valid_criteria"] + assert lift_reset["diversity_feature"] is None + assert lift_reset["success_monitor"].target_success_rate == pytest.approx(0.5) + + lift.play_mode() + assert lift.events.conditional_reset.params["terms"]["reset_object_to_target"].params[ + "probability" + ] == pytest.approx(0.75) + + +def test_franka_lift_pregrasp_resolves_object_variants_from_clone_topology() -> None: + """The pre-grasp term must map heterogeneous object prototypes onto environment IDs.""" + object_path = "/World/envs/env_.*/Object" + object_cfg = SimpleNamespace(prim_path=object_path) + robot_cfg = SimpleNamespace(prim_path="/World/envs/env_.*/Robot") + plan = make_clone_plan( + (object_cfg, object_cfg, robot_cfg), + ((0, 2), (1, 2)), + 4, + ) + robot = SimpleNamespace( + find_joints=lambda _: ([0, 1], []), + data=SimpleNamespace( + body_pos_w=SimpleNamespace(torch=torch.zeros((4, 1, 3))), + body_quat_w=SimpleNamespace(torch=torch.tensor([[[0.0, 0.0, 0.0, 1.0]]] * 4)), + ), + write_joint_position_to_sim_index=Mock(), + write_joint_velocity_to_sim_index=Mock(), + set_joint_position_target_index=Mock(), + ) + object_asset = SimpleNamespace( + device="cpu", write_root_pose_to_sim_index=Mock(), write_root_velocity_to_sim_index=Mock() + ) + scene = _FakeScene(torch.arange(4), robot=robot, object=object_asset) + scene.clone_plan = plan + env = SimpleNamespace( + cfg=SimpleNamespace(scene=SimpleNamespace(object=object_cfg)), + scene=scene, + device="cpu", + num_envs=4, + ) + cfg = SimpleNamespace( + params={ + "asset_cfg": SceneEntityCfg("object"), + "gripper_cfg": SceneEntityCfg("robot", joint_names="panda_finger_joint.*"), + "target_cfg": SimpleNamespace(name="robot", body_ids=[0]), + "pose_range": {axis: (0.0, 0.0) for axis in ("x", "y", "z")}, + "probability": 1.0, + "gripper_joint_positions": [0.026, 0.0135], + "asset_orientations": [(0.0, 0.0, 0.0, 1.0), (0.0, 0.7071068, 0.0, 0.7071068)], + } + ) + + term = mdp.reset_to_grasp(cfg, env) + term(env, torch.arange(4), **cfg.params) + + torch.testing.assert_close( + robot.write_joint_position_to_sim_index.call_args.kwargs["position"], + torch.tensor([[0.026, 0.026], [0.026, 0.026], [0.0135, 0.0135], [0.0135, 0.0135]]), + ) + root_pose = object_asset.write_root_pose_to_sim_index.call_args.kwargs["root_pose"] + torch.testing.assert_close(root_pose[:, :3], torch.zeros((4, 3))) + torch.testing.assert_close( + root_pose[:, 3:], + torch.tensor([[0.0, 0.0, 0.0, 1.0]] * 2 + [[0.0, 0.7071068, 0.0, 0.7071068]] * 2), + ) + + cfg.params["asset_orientations"] = [(0.0, 0.0, 0.0, 1.0)] + with pytest.raises(ValueError, match="one gripper pose per object variant"): + mdp.reset_to_grasp(cfg, env) + + +def test_reset_clearance_ignores_disabled_collision_geometry() -> None: + """Disabled colliders and visual-only geometry must not reject reset candidates.""" + stage = Usd.Stage.CreateInMemory() + root = stage.DefinePrim("/Object", "Xform") + for name, enabled in (("default_enabled", None), ("explicit_enabled", True), ("disabled", False)): + prim = UsdGeom.Cube.Define(stage, f"/Object/{name}").GetPrim() + collision = UsdPhysics.CollisionAPI.Apply(prim) + if enabled is not None: + collision.CreateCollisionEnabledAttr(enabled) + UsdGeom.Cube.Define(stage, "/Object/visual_only") + + with use_stage(stage): + meshes = collect_collision_meshes(root, lambda prim: (prim.GetName(), root)) + + assert set(meshes) == {"default_enabled", "explicit_enabled"} + + def _make_vision_camera(data_type: str, images: torch.Tensor) -> tuple[mdp.vision_camera, SimpleNamespace]: """Build a ``vision_camera`` term around a fake single-data-type camera sensor.""" sensor = SimpleNamespace( diff --git a/source/isaaclab_tasks/test/core/test_preset_kit_decision.py b/source/isaaclab_tasks/test/core/test_preset_kit_decision.py index 5d9a125df4f4..26cefe5c493c 100644 --- a/source/isaaclab_tasks/test/core/test_preset_kit_decision.py +++ b/source/isaaclab_tasks/test/core/test_preset_kit_decision.py @@ -23,7 +23,7 @@ import isaaclab_tasks # noqa: F401 from isaaclab_tasks.utils import resolve_task_config -from isaaclab_tasks.utils.hydra import collect_presets +from isaaclab_tasks.utils.hydra import collect_presets, resolve_presets from isaaclab_tasks.utils.parse_cfg import load_cfg_from_registry from isaaclab_tasks.utils.preset_cli import enumerate_task_presets from isaaclab_tasks.utils.preset_target import PresetTarget @@ -122,11 +122,38 @@ def test_registered_task_physx_presets_keep_auto_selection_explicit(): if isinstance(default_cfg, PhysxCfg): assert default_cfg == isaacsim_cfg, location assert auto_cfg.isaacsim_physx == isaacsim_cfg, location - assert auto_cfg.ovphysx == physics_fields.get("ovphysx"), location + if task_id == "Isaac-Lift-Franka" and path == "sim.physics": + # Its automatic eight-shape task requires Isaac Sim; explicit OvPhysX uses a cube. + assert auto_cfg.ovphysx is None, location + else: + assert auto_cfg.ovphysx == physics_fields.get("ovphysx"), location elif has_auto_physx and "physx" in fields: + if task_id == "Isaac-Lift-Franka" and path == "scene.object.spawn": + assert fields["physx"] == fields["default"], location + continue + # Kuka Lift/Reorient retains its homogeneous automatic-OvPhysX path. + if ( + task_id.startswith(("Isaac-Lift-KukaAllegro", "Isaac-Reorient-KukaAllegro")) + and path == "scene.object.spawn" + ): + continue assert fields.get("isaacsim_physx") == fields["physx"], location +@pytest.mark.parametrize( + "task_id", ["IsaacContrib-Open-Drawer-Franka-IK-Abs", "IsaacContrib-Open-Drawer-Franka-IK-Rel"] +) +@pytest.mark.parametrize( + ("physics_preset", "expected_asset_physics"), + [("newton_mjwarp", "mujoco"), ("isaacsim_physx", "physx"), ("ovphysx", "physx")], +) +def test_franka_cabinet_ik_asset_variant_matches_backend(task_id, physics_preset, expected_asset_physics): + """The IK robot override must retain the cabinet task's backend-aware asset selection.""" + cfg = resolve_presets(load_cfg_from_registry(task_id, "env_cfg_entry_point"), selected=(physics_preset,)) + + assert cfg.scene.robot.spawn.variants["Physics"] == expected_asset_physics + + def test_registered_manager_based_configs_name_their_env_class(): """A manager-based task's ``class_type`` constructs the env class its registration names.""" diff --git a/source/isaaclab_tasks/test/core/test_reach_franka_presets.py b/source/isaaclab_tasks/test/core/test_reach_franka_presets.py index dcb0fe7f7728..05f10be6fec6 100644 --- a/source/isaaclab_tasks/test/core/test_reach_franka_presets.py +++ b/source/isaaclab_tasks/test/core/test_reach_franka_presets.py @@ -22,11 +22,26 @@ from isaaclab_tasks.utils.preset_cli import enumerate_task_presets from isaaclab_tasks.utils.preset_target import PresetTarget -from isaaclab_assets import FRANKA_PANDA_MENAGERIE_CFG +from isaaclab_assets import FRANKA_PANDA_FLAT_CFG _TASK = "Isaac-Reach-Franka" _OSC_TASK = "Isaac-Reach-Franka-OSC" _CONTRIB_DIFFIK_ABS_TASK = "IsaacContrib-Reach-Franka-IK-Abs" +_RIGID_FRANKA_TASKS = ( + _TASK, + _OSC_TASK, + "Isaac-Open-Drawer-Franka", + "Isaac-Open-Drawer-Franka-Direct", + "Isaac-Lift-Franka", +) +_SOFT_FRANKA_TASKS = ( + "Isaac-Lift-Soft-Franka", + "Isaac-Lift-Cloth-Franka", + "Isaac-Lift-Cable-Franka", + "Isaac-Lift-Soft-Franka-Camera", + "Isaac-Lift-Cloth-Franka-Camera", + "Isaac-Lift-Cable-Franka-Camera", +) def _load_env_cfg(*presets: str): @@ -48,6 +63,28 @@ def _without_controller_dependent_cfg(cfg): return cfg_dict +@pytest.mark.parametrize("task", _RIGID_FRANKA_TASKS + _SOFT_FRANKA_TASKS) +def test_franka_tasks_select_the_canonical_asset_and_backend_payload(task): + default_cfg = _load_reach_env_cfg(task) + physx_cfg = _load_reach_env_cfg(task, "isaacsim_physx") + + for cfg, physics_variant in ((default_cfg, "mujoco"), (physx_cfg, "physx")): + assert cfg.scene.robot.spawn.usd_path == FRANKA_PANDA_FLAT_CFG.spawn.usd_path + assert cfg.scene.robot.spawn.variants == { + "Physics": physics_variant, + "Colliders": "gripper_only", + } + + +@pytest.mark.parametrize("task", _RIGID_FRANKA_TASKS) +def test_rigid_franka_arm_collisions_are_an_independent_domain_preset(task): + newton_cfg = _load_reach_env_cfg(task, "arm_collisions") + physx_cfg = _load_reach_env_cfg(task, "arm_collisions", "isaacsim_physx") + + assert newton_cfg.scene.robot.spawn.variants == {"Physics": "mujoco", "Colliders": "primitives"} + assert physx_cfg.scene.robot.spawn.variants == {"Physics": "physx", "Colliders": "primitives"} + + def test_reach_diffik_abs_legacy_task_is_a_deprecated_alias(): spec = registry[_CONTRIB_DIFFIK_ABS_TASK] @@ -95,6 +132,10 @@ def test_reach_ur10_physics_presets_change_only_physics(): physx = _load_reach_env_cfg("Isaac-Reach-UR10", "isaacsim_physx") newton = _load_reach_env_cfg("Isaac-Reach-UR10", "newton_mjwarp") + assert physx.rewards.success.func is mdp.is_terminated_term + assert physx.terminations.success.func is mdp.pose_command_success + assert not hasattr(physx.rewards, "end_effector_position_tracking_fine_grained") + physx_cfg = to_dict(physx) newton_cfg = to_dict(newton) physx_cfg["sim"].pop("physics") @@ -136,7 +177,24 @@ def test_reach_diffik_physx_configures_teleop_physics(): assert physx_props.max_depenetration_velocity == pytest.approx(5.0) assert not cfg.scene.robot.spawn.make_uninstanceable assert cfg.scene.robot.spawn.collision_props is None - assert cfg.scene.robot.spawn.usd_path.endswith("/FrankaEmika/Legacy/panda_instanceable.usd") + assert cfg.scene.robot.spawn.usd_path.endswith("/FrankaEmika/franka_panda.usda") + assert cfg.scene.robot.spawn.variants == {"Physics": "physx", "Colliders": "gripper_only"} + + +def test_reach_diffik_abs_normalizes_position_actions_to_command_workspace(): + cfg = _load_env_cfg("diffik_abs", "isaacsim_physx") + action = cfg.actions.arm_action + ranges = cfg.commands.ee_pose.ranges + + position_scale = torch.tensor(action.scale[:3]) + position_offset = torch.tensor(action.offset[:3]) + expected_lower = torch.tensor([ranges.pos_x[0], ranges.pos_y[0], ranges.pos_z[0]]) + expected_upper = torch.tensor([ranges.pos_x[1], ranges.pos_y[1], ranges.pos_z[1]]) + + torch.testing.assert_close(position_offset - position_scale, expected_lower) + torch.testing.assert_close(position_offset + position_scale, expected_upper) + assert action.scale[3:] == (1.0, 1.0, 1.0, 1.0) + assert action.offset[3:] == (0.0, 0.0, 0.0, 0.0) def test_reach_newton_ik_configures_gravity_compensation(): @@ -146,6 +204,7 @@ def test_reach_newton_ik_configures_gravity_compensation(): mujoco_props = next(props for props in rigid_props if isinstance(props, MujocoRigidBodyCfg)) assert mujoco_props.gravcomp == pytest.approx(1.0) assert cfg.scene.robot.spawn.usd_path.endswith("/FrankaEmika/franka_panda.usda") + assert cfg.scene.robot.spawn.variants == {"Physics": "mujoco", "Colliders": "gripper_only"} # Native SE(3) command convention: one relative 6-DoF pose objective. pose_objectives = [ @@ -163,17 +222,17 @@ def test_reach_default_preset_does_not_configure_se3_teleop_devices(): assert cfg.teleop_devices.devices == {} -def test_reach_success_requires_position_and_orientation(): +def test_reach_tracks_success_without_terminating(): cfg = _load_env_cfg() - success = cfg.terminations.success assert cfg.commands.ee_pose.position_success_threshold == pytest.approx(0.05) assert cfg.commands.ee_pose.orientation_success_threshold == pytest.approx(0.2) - assert success.func is mdp.pose_command_success - assert success.params == {"command_name": "ee_pose"} - assert cfg.rewards.success.func.__name__ == "is_terminated_term" - assert cfg.rewards.success.weight == pytest.approx(10.0) - assert cfg.rewards.success.params == {"term_keys": ["success"]} + assert cfg.terminations.success is None + assert cfg.terminations.time_out.func is mdp.time_out + assert cfg.rewards.success is None + assert cfg.rewards.end_effector_position_tracking_fine_grained.func is mdp.position_command_error_tanh + assert cfg.rewards.end_effector_position_tracking_fine_grained.weight == pytest.approx(0.1) + assert cfg.rewards.end_effector_position_tracking_fine_grained.params["std"] == pytest.approx(0.1) angles = torch.tensor([0.19, 0.19, 0.21]) body_quaternions = torch.zeros(3, 1, 4) @@ -197,32 +256,32 @@ def test_reach_success_requires_position_and_orientation(): command._track_success = True command._succeeded = torch.zeros(3, dtype=torch.bool) - class CommandManager: - def get_term(self, name): - assert name == "ee_pose" - return command - - env = SimpleNamespace(command_manager=CommandManager()) - succeeded = mdp.pose_command_success(env, **success.params) + succeeded = command.compute_success() assert torch.equal(succeeded, torch.tensor([True, False, False])) assert torch.equal(command._succeeded, succeeded) command.cfg = replace(command.cfg, orientation_success_threshold=None) command._succeeded.zero_() - position_only_succeeded = mdp.pose_command_success(env, **success.params) + position_only_succeeded = command.compute_success() assert torch.equal(position_only_succeeded, torch.tensor([True, False, True])) -def test_reach_osc_effort_actuator_keeps_menagerie_velocity_limit(): - """The zero-gain effort actuator must keep the asset's solver velocity limit; the USD authors none.""" +def test_reach_osc_effort_actuator_keeps_canonical_solver_properties(): + """Replacing the arm actuator with a zero-gain effort model must preserve its solver properties.""" cfg = _load_reach_env_cfg(_OSC_TASK) arm_actuator = cfg.scene.robot.actuators["panda_arm"] + source_actuator = FRANKA_PANDA_FLAT_CFG.actuators["panda_arm"] assert isinstance(arm_actuator, IdealPDActuatorCfg) assert arm_actuator.stiffness == 0.0 and arm_actuator.damping == 0.0 - assert arm_actuator.joint_velocity_limit == FRANKA_PANDA_MENAGERIE_CFG.actuators["panda_arm"].joint_velocity_limit + assert arm_actuator.joint_effort_limit == source_actuator.joint_effort_limit + assert arm_actuator.joint_velocity_limit == source_actuator.joint_velocity_limit + assert arm_actuator.armature == source_actuator.armature + assert arm_actuator.friction == source_actuator.friction + assert arm_actuator.dynamic_friction == source_actuator.dynamic_friction + assert arm_actuator.viscous_friction == source_actuator.viscous_friction def test_reach_osc_resolves_controller_preset_values_to_defaults(): @@ -240,7 +299,7 @@ def test_reach_osc_resolves_controller_preset_values_to_defaults(): assert physx_props.disable_gravity is True assert mujoco_props.gravcomp == pytest.approx(1.0) assert cfg.teleop_devices.devices == {} - assert domain_presets == {"diffik_abs"} + assert domain_presets == {"arm_collisions", "diffik_abs"} def test_reach_osc_diffik_abs_is_a_deprecated_no_op_alias():