From 80cf81f851496215fd53e8671aff6938cb39d669 Mon Sep 17 00:00:00 2001 From: Ryan Gresia Date: Wed, 6 May 2026 09:42:02 -0700 Subject: [PATCH 01/12] adds newton tend props rando --- source/isaaclab/isaaclab/envs/mdp/events.py | 250 ++++++++++++++++---- 1 file changed, 203 insertions(+), 47 deletions(-) diff --git a/source/isaaclab/isaaclab/envs/mdp/events.py b/source/isaaclab/isaaclab/envs/mdp/events.py index 9483d2d2d0cd..f230a2074e32 100644 --- a/source/isaaclab/isaaclab/envs/mdp/events.py +++ b/source/isaaclab/isaaclab/envs/mdp/events.py @@ -1424,53 +1424,12 @@ def __call__( limits=joint_pos_limits, joint_ids=joint_ids, env_ids=env_ids, warn_limit_violation=False ) - -class randomize_fixed_tendon_parameters(ManagerTermBase): - """Randomize the simulated fixed tendon parameters of an articulation by adding, scaling, or setting random values. - - This function allows randomizing the fixed tendon parameters of the asset. - These correspond to the physics engine tendon properties that affect the joint behavior. - - The function samples random values from the given distribution parameters and applies the operation to - the tendon properties. It then sets the values into the physics simulation. If the distribution parameters - are not provided for a particular property, the function does not modify the property. - """ - - def __init__(self, cfg: EventTermCfg, env: ManagerBasedEnv): - """Initialize the term. - - Args: - cfg: The configuration of the event term. - env: The environment instance. - - Raises: - TypeError: If `params` is not a tuple of two numbers. - ValueError: If the operation is not supported. - ValueError: If the lower bound is negative or zero when not allowed. - ValueError: If the upper bound is less than the lower bound. - """ - super().__init__(cfg, env) - - # extract the used quantities (to enable type-hinting) - self.asset_cfg: SceneEntityCfg = cfg.params["asset_cfg"] - self.asset: RigidObject | Articulation = env.scene[self.asset_cfg.name] - # check for valid operation - if cfg.params["operation"] == "scale": - if "stiffness_distribution_params" in cfg.params: - _validate_scale_range( - cfg.params["stiffness_distribution_params"], "stiffness_distribution_params", allow_zero=False - ) - if "damping_distribution_params" in cfg.params: - _validate_scale_range(cfg.params["damping_distribution_params"], "damping_distribution_params") - if "limit_stiffness_distribution_params" in cfg.params: - _validate_scale_range( - cfg.params["limit_stiffness_distribution_params"], "limit_stiffness_distribution_params" - ) - elif cfg.params["operation"] not in ("abs", "add"): - raise ValueError( - "Randomization term 'randomize_fixed_tendon_parameters' does not support operation:" - f" '{cfg.params['operation']}'." - ) +class _RandomizeFixedTendonPropertiesPhysx(ManagerTermBase): + def __init__( + self, cfg: EventTermCfg, env: ManagerBasedEnv, asset: RigidObject | Articulation, asset_cfg: SceneEntityCfg + ): + self.asset_cfg = asset_cfg + self.asset = asset def __call__( self, @@ -1606,6 +1565,203 @@ def __call__( # write the fixed tendon properties into the simulation self.asset.write_fixed_tendon_properties_to_sim_index(env_ids=env_ids) +class _RandomizeFixedTendonPropertiesNewton(ManagerTermBase): + def __init__( + self, cfg: EventTermCfg, env: ManagerBasedEnv, asset: RigidObject | Articulation, asset_cfg: SceneEntityCfg + ): + self.asset = asset + self.asset_cfg = asset_cfg + + def __call__( + self, + env: ManagerBasedEnv, + env_ids: torch.Tensor | None, + asset_cfg: SceneEntityCfg, + stiffness_distribution_params: tuple[float, float] | None = None, + damping_distribution_params: tuple[float, float] | None = None, + limit_stiffness_distribution_params: tuple[float, float] | None = None, + lower_limit_distribution_params: tuple[float, float] | None = None, + upper_limit_distribution_params: tuple[float, float] | None = None, + rest_length_distribution_params: tuple[float, float] | None = None, + offset_distribution_params: tuple[float, float] | None = None, + operation: Literal["add", "scale", "abs"] = "abs", + distribution: Literal["uniform", "log_uniform", "gaussian"] = "uniform", + ): + # resolve environment ids + if env_ids is None: + env_ids = torch.arange(env.scene.num_envs, device=self.asset.device) + + # resolve joint indices + if self.asset_cfg.fixed_tendon_ids == slice(None): + tendon_ids = slice(None) # for optimization purposes + else: + tendon_ids = torch.tensor(self.asset_cfg.fixed_tendon_ids, dtype=torch.int, device=self.asset.device) + + # sample tendon properties from the given ranges and set into the physics simulation + # stiffness + if stiffness_distribution_params is not None: + stiffness = _randomize_prop_by_op( + self.asset.data.fixed_tendon_stiffness.torch.clone(), + stiffness_distribution_params, + env_ids, + tendon_ids, + operation=operation, + distribution=distribution, + ) + self.asset.set_fixed_tendon_stiffness_index( + stiffness=stiffness[env_ids[:, None], tendon_ids], fixed_tendon_ids=tendon_ids, env_ids=env_ids + ) + + # damping + if damping_distribution_params is not None: + damping = _randomize_prop_by_op( + self.asset.data.fixed_tendon_damping.torch.clone(), + damping_distribution_params, + env_ids, + tendon_ids, + operation=operation, + distribution=distribution, + ) + self.asset.set_fixed_tendon_damping_index( + damping=damping[env_ids[:, None], tendon_ids], fixed_tendon_ids=tendon_ids, env_ids=env_ids + ) + + # position limits + if lower_limit_distribution_params is not None or upper_limit_distribution_params is not None: + limit = self.asset.data.fixed_tendon_pos_limits.torch.clone() + # -- lower limit + if lower_limit_distribution_params is not None: + limit[..., 0] = _randomize_prop_by_op( + limit[..., 0], + lower_limit_distribution_params, + env_ids, + tendon_ids, + operation=operation, + distribution=distribution, + ) + # -- upper limit + if upper_limit_distribution_params is not None: + limit[..., 1] = _randomize_prop_by_op( + limit[..., 1], + upper_limit_distribution_params, + env_ids, + tendon_ids, + operation=operation, + distribution=distribution, + ) + + # check if the limits are valid + tendon_limits = limit[env_ids[:, None], tendon_ids] + if (tendon_limits[..., 0] > tendon_limits[..., 1]).any(): + raise ValueError( + "Randomization term 'randomize_fixed_tendon_parameters' is setting lower tendon limits that are" + " greater than upper tendon limits." + ) + self.asset.set_fixed_tendon_position_limit_index( + limit=tendon_limits, fixed_tendon_ids=tendon_ids, env_ids=env_ids + ) + + # rest length + if rest_length_distribution_params is not None: + rest_length = _randomize_prop_by_op( + self.asset.data.fixed_tendon_rest_length.torch.clone(), + rest_length_distribution_params, + env_ids, + tendon_ids, + operation=operation, + distribution=distribution, + ) + self.asset.set_fixed_tendon_rest_length_index( + rest_length=rest_length[env_ids[:, None], tendon_ids], fixed_tendon_ids=tendon_ids, env_ids=env_ids + ) + + # write the fixed tendon properties into the simulation + self.asset.write_fixed_tendon_properties_to_sim_index(env_ids=env_ids) + + +class randomize_fixed_tendon_parameters(ManagerTermBase): + """Randomize the simulated fixed tendon parameters of an articulation by adding, scaling, or setting random values. + + This function allows randomizing the fixed tendon parameters of the asset. + These correspond to the physics engine tendon properties that affect the joint behavior. + + The function samples random values from the given distribution parameters and applies the operation to + the tendon properties. It then sets the values into the physics simulation. If the distribution parameters + are not provided for a particular property, the function does not modify the property. + """ + + def __init__(self, cfg: EventTermCfg, env: ManagerBasedEnv): + """Initialize the term. + + Args: + cfg: The configuration of the event term. + env: The environment instance. + + Raises: + TypeError: If `params` is not a tuple of two numbers. + ValueError: If the operation is not supported. + ValueError: If the lower bound is negative or zero when not allowed. + ValueError: If the upper bound is less than the lower bound. + """ + super().__init__(cfg, env) + + # extract the used quantities (to enable type-hinting) + self.asset_cfg: SceneEntityCfg = cfg.params["asset_cfg"] + self.asset: RigidObject | Articulation = env.scene[self.asset_cfg.name] + # check for valid operation + if cfg.params["operation"] == "scale": + if "stiffness_distribution_params" in cfg.params: + _validate_scale_range( + cfg.params["stiffness_distribution_params"], "stiffness_distribution_params", allow_zero=False + ) + if "damping_distribution_params" in cfg.params: + _validate_scale_range(cfg.params["damping_distribution_params"], "damping_distribution_params") + if "limit_stiffness_distribution_params" in cfg.params: + _validate_scale_range( + cfg.params["limit_stiffness_distribution_params"], "limit_stiffness_distribution_params" + ) + elif cfg.params["operation"] not in ("abs", "add"): + raise ValueError( + "Randomization term 'randomize_fixed_tendon_parameters' does not support operation:" + f" '{cfg.params['operation']}'." + ) + + manager_name = env.sim.physics_manager.__name__.lower() + if "newton" in manager_name: + self._impl = _RandomizeFixedTendonPropertiesNewton(cfg, env, self.asset, self.asset_cfg) + else: + self._impl = _RandomizeFixedTendonPropertiesPhysx(cfg, env, self.asset, self.asset_cfg) + + def __call__( + self, + env: ManagerBasedEnv, + env_ids: torch.Tensor | None, + asset_cfg: SceneEntityCfg, + stiffness_distribution_params: tuple[float, float] | None = None, + damping_distribution_params: tuple[float, float] | None = None, + limit_stiffness_distribution_params: tuple[float, float] | None = None, + lower_limit_distribution_params: tuple[float, float] | None = None, + upper_limit_distribution_params: tuple[float, float] | None = None, + rest_length_distribution_params: tuple[float, float] | None = None, + offset_distribution_params: tuple[float, float] | None = None, + operation: Literal["add", "scale", "abs"] = "abs", + distribution: Literal["uniform", "log_uniform", "gaussian"] = "uniform", + ): + self._impl( + env, + env_ids, + asset_cfg, + stiffness_distribution_params, + damping_distribution_params, + limit_stiffness_distribution_params, + lower_limit_distribution_params, + upper_limit_distribution_params, + rest_length_distribution_params, + offset_distribution_params, + operation, + distribution, + ) + def apply_external_force_torque( env: ManagerBasedEnv, From ceb6579bde7b68a1e528408611c894a42330e96e Mon Sep 17 00:00:00 2001 From: Ryan Gresia Date: Wed, 6 May 2026 10:15:18 -0700 Subject: [PATCH 02/12] adds tendon buffers and bindings --- .../assets/articulation/articulation_data.py | 51 +++++++++++++++++-- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation_data.py b/source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation_data.py index a22ba73e1725..e0320c3b2a14 100644 --- a/source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation_data.py +++ b/source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation_data.py @@ -460,7 +460,7 @@ def fixed_tendon_stiffness(self) -> ProxyArray: Shape is (num_instances, num_fixed_tendons), dtype = wp.float32. In torch this resolves to (num_instances, num_fixed_tendons). """ - raise NotImplementedError + return self._fixed_tendon_stiffness_ta @property def fixed_tendon_damping(self) -> ProxyArray: @@ -469,7 +469,7 @@ def fixed_tendon_damping(self) -> ProxyArray: Shape is (num_instances, num_fixed_tendons), dtype = wp.float32. In torch this resolves to (num_instances, num_fixed_tendons). """ - raise NotImplementedError + return self._fixed_tendon_damping_ta @property def fixed_tendon_limit_stiffness(self) -> ProxyArray: @@ -1289,8 +1289,8 @@ def _create_simulation_bindings(self) -> None: self._num_instances = self._root_view.count self._num_joints = self._root_view.joint_dof_count self._num_bodies = self._root_view.link_count - self._num_fixed_tendons = 0 # self._root_view.max_fixed_tendons - self._num_spatial_tendons = 0 # self._root_view.max_spatial_tendons + self._num_fixed_tendons = self._root_view.tendon_count + self._num_spatial_tendons = 0 # spatial tendons not supported # -- root properties self._sim_bind_root_link_pose_w = self._root_view.get_root_transforms(SimulationManager.get_state_0())[:, 0] @@ -1400,6 +1400,34 @@ def _create_simulation_bindings(self) -> None: (self._num_instances, 0), dtype=wp.float32, device=self.device ) + if self._root_view.tendon_count > 0: + self._sim_bind_fixed_tendon_stiffness = self._root_view.get_attribute( + "mujoco.tendon_stiffness", SimulationManager.get_model() + ) + self._sim_bind_fixed_tendon_damping = self._root_view.get_attribute( + "mujoco.tendon_damping", SimulationManager.get_model(), + ) + self._sim_bind_fixed_tendon_rest_length = self._root_view.get_attribute( + "mujoco.tendon_springlength", SimulationManager.get_model() + ) + self._sim_bind_fixed_tendon_pos_limits = self._root_view.get_attribute( + "mujoco.tendon_range", SimulationManager.get_model() + ) + + else: + self._sim_bind_fixed_tendon_stiffness = wp.zeros( + (self._num_instances, 0), dtype=wp.float32, device=self.device + ) + self._sim_bind_fixed_tendon_damping = wp.zeros( + (self._num_instances, 0), dtype=wp.float32, device=self.device + ) + self._sim_bind_fixed_tendon_rest_length = wp.zeros( + (self._num_instances, 0), dtype=wp.float32, device=self.device + ) + self._sim_bind_fixed_tendon_pos_limits = wp.zeros( + (self._num_instances, 0), dtype=wp.float32, device=self.device + ) + # Re-pin ProxyArray wrappers to the newly created sim bindings. # On first init, _create_buffers() handles this after all buffers exist. if hasattr(self, "_root_link_pose_w_ta"): @@ -1473,6 +1501,18 @@ def _create_buffers(self) -> None: self._previous_joint_vel = wp.zeros((self._num_instances, 0), dtype=wp.float32, device=self.device) self._previous_body_com_vel = wp.clone(self._sim_bind_body_com_vel_w) + # staging buffers to write all tendon params to sim at once + if self._num_fixed_tendons > 0: + self._fixed_tendon_stiffness = wp.clone(self._sim_bind_fixed_tendon_stiffness) + self._fixed_tendon_damping = wp.clone(self._sim_bind_fixed_tendon_damping) + # self._fixed_tendon_rest_length = wp.clone(self._sim_bind_fixed_tendon_rest_length) + # self._fixed_tendon_pos_limits = wp.clone(self._sim_bind_fixed_tendon_pos_limits) + else: + self._fixed_tendon_stiffness = wp.zeros((self._num_instances, 0), dtype=wp.float32, device=self.device) + self._fixed_tendon_damping = wp.zeros((self._num_instances, 0), dtype=wp.float32, device=self.device) + # self._fixed_tendon_rest_length = wp.zeros((self._num_instances, 0), dtype=wp.float32, device=self.device) + # self._fixed_tendon_pos_limits = wp.clone(self._sim_bind_fixed_tendon_pos_limits) + # Initialize the lazy buffers. # -- link frame w.r.t. world frame self._root_link_vel_w = TimestampedBuffer( @@ -1607,6 +1647,9 @@ def _pin_proxy_arrays(self) -> None: self._body_mass_ta = ProxyArray(self._sim_bind_body_mass) self._body_inertia_ta = ProxyArray(self._sim_bind_body_inertia) self._body_com_pos_b_ta = ProxyArray(self._sim_bind_body_com_pos_b) + self._fixed_tendon_stiffness_ta = ProxyArray(self._sim_bind_fixed_tendon_stiffness) + self._fixed_tendon_damping_ta = ProxyArray(self._sim_bind_fixed_tendon_damping) + self._fixed_tendon_rest_length = ProxyArray(self._sim_bind_fixed_tendon_rest_length) # Category 2: TimestampedBuffer properties self._root_link_vel_w_ta = ProxyArray(self._root_link_vel_w.data) From 683486724591e08d5a427cced2bdef61b6854761 Mon Sep 17 00:00:00 2001 From: Ryan Gresia Date: Wed, 6 May 2026 12:35:54 -0700 Subject: [PATCH 03/12] adds process tendon --- source/isaaclab/isaaclab/envs/mdp/events.py | 49 ---- .../assets/articulation/articulation.py | 261 +++++++++++++----- .../assets/articulation/articulation_data.py | 25 +- 3 files changed, 195 insertions(+), 140 deletions(-) diff --git a/source/isaaclab/isaaclab/envs/mdp/events.py b/source/isaaclab/isaaclab/envs/mdp/events.py index f230a2074e32..27ca9b33e0fc 100644 --- a/source/isaaclab/isaaclab/envs/mdp/events.py +++ b/source/isaaclab/isaaclab/envs/mdp/events.py @@ -1626,55 +1626,6 @@ def __call__( damping=damping[env_ids[:, None], tendon_ids], fixed_tendon_ids=tendon_ids, env_ids=env_ids ) - # position limits - if lower_limit_distribution_params is not None or upper_limit_distribution_params is not None: - limit = self.asset.data.fixed_tendon_pos_limits.torch.clone() - # -- lower limit - if lower_limit_distribution_params is not None: - limit[..., 0] = _randomize_prop_by_op( - limit[..., 0], - lower_limit_distribution_params, - env_ids, - tendon_ids, - operation=operation, - distribution=distribution, - ) - # -- upper limit - if upper_limit_distribution_params is not None: - limit[..., 1] = _randomize_prop_by_op( - limit[..., 1], - upper_limit_distribution_params, - env_ids, - tendon_ids, - operation=operation, - distribution=distribution, - ) - - # check if the limits are valid - tendon_limits = limit[env_ids[:, None], tendon_ids] - if (tendon_limits[..., 0] > tendon_limits[..., 1]).any(): - raise ValueError( - "Randomization term 'randomize_fixed_tendon_parameters' is setting lower tendon limits that are" - " greater than upper tendon limits." - ) - self.asset.set_fixed_tendon_position_limit_index( - limit=tendon_limits, fixed_tendon_ids=tendon_ids, env_ids=env_ids - ) - - # rest length - if rest_length_distribution_params is not None: - rest_length = _randomize_prop_by_op( - self.asset.data.fixed_tendon_rest_length.torch.clone(), - rest_length_distribution_params, - env_ids, - tendon_ids, - operation=operation, - distribution=distribution, - ) - self.asset.set_fixed_tendon_rest_length_index( - rest_length=rest_length[env_ids[:, None], tendon_ids], fixed_tendon_ids=tendon_ids, env_ids=env_ids - ) - # write the fixed tendon properties into the simulation self.asset.write_fixed_tendon_properties_to_sim_index(env_ids=env_ids) diff --git a/source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation.py b/source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation.py index c3c6eca044f7..9519d6ec7b71 100644 --- a/source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation.py +++ b/source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation.py @@ -140,7 +140,7 @@ def num_joints(self) -> int: @property def num_fixed_tendons(self) -> int: """Number of fixed tendons in articulation.""" - return 0 + return self.root_view.tendon_count @property def num_spatial_tendons(self) -> int: @@ -177,7 +177,7 @@ def joint_names(self) -> list[str]: @property def fixed_tendon_names(self) -> list[str]: """Ordered names of fixed tendons in articulation.""" - return [] + return self.root_view.tendon_names @property def spatial_tendon_names(self) -> list[str]: @@ -2597,11 +2597,12 @@ def set_joint_effort_target_mask( """ def set_fixed_tendon_stiffness_index( - self, - *, - stiffness: float | torch.Tensor | wp.array, - fixed_tendon_ids: Sequence[int] | torch.Tensor | wp.array | None = None, - env_ids: Sequence[int] | torch.Tensor | wp.array | None = None, + self, + *, + stiffness: float | torch.Tensor | wp.array, + fixed_tendon_ids: Sequence[int] | torch.Tensor | wp.array | None = None, + env_ids: Sequence[int] | torch.Tensor | wp.array | None = None, + full_data: bool = False, ) -> None: """Set fixed tendon stiffness into internal buffers using indices. @@ -2610,25 +2611,67 @@ def set_fixed_tendon_stiffness_index( :meth:`write_fixed_tendon_properties_to_sim_index` method. .. note:: - This method expects partial data. + This method expects partial data or full data. .. tip:: - Both the index and mask methods have dedicated optimized implementations. Performance is similar for both. - However, to allow graphed pipelines, the mask method must be used. + For maximum performance we recommend using the index method. This is because in PhysX, the tensor API + is only supporting indexing, hence masks need to be converted to indices. Args: - stiffness: Fixed tendon stiffness. Shape is (len(env_ids), len(fixed_tendon_ids)). + stiffness: Fixed tendon stiffness. Shape is (len(env_ids), len(fixed_tendon_ids)) or + (num_instances, num_fixed_tendons) if full_data. fixed_tendon_ids: The tendon indices to set the stiffness for. Defaults to None (all fixed tendons). env_ids: Environment indices. If None, then all indices are used. + full_data: Whether to expect full data. Defaults to False. """ - raise NotImplementedError() + # resolve indices + env_ids = self._resolve_env_ids(env_ids) + fixed_tendon_ids = self._resolve_fixed_tendon_ids(fixed_tendon_ids) + if full_data: + self.assert_shape_and_dtype( + stiffness, (self.num_instances, self.num_fixed_tendons), wp.float32, "stiffness" + ) + else: + self.assert_shape_and_dtype( + stiffness, (env_ids.shape[0], fixed_tendon_ids.shape[0]), wp.float32, "stiffness" + ) + # Warp kernels can ingest torch tensors directly, so we don't need to convert to warp arrays here. + if isinstance(stiffness, float): + wp.launch( + articulation_kernels.float_data_to_buffer_with_indices, + dim=(env_ids.shape[0], fixed_tendon_ids.shape[0]), + inputs=[ + stiffness, + env_ids, + fixed_tendon_ids, + ], + outputs=[ + self.data._fixed_tendon_stiffness, + ], + device=self.device, + ) + else: + wp.launch( + shared_kernels.write_2d_data_to_buffer_with_indices, + dim=(env_ids.shape[0], fixed_tendon_ids.shape[0]), + inputs=[ + stiffness, + env_ids, + fixed_tendon_ids, + ], + outputs=[ + self.data._fixed_tendon_stiffness, + ], + device=self.device, + ) + # Only updates internal buffers, does not apply the stiffness to the simulation. def set_fixed_tendon_stiffness_mask( - self, - *, - stiffness: float | torch.Tensor | wp.array, - fixed_tendon_mask: wp.array | None = None, - env_mask: wp.array | None = None, + self, + *, + stiffness: float | torch.Tensor | wp.array, + fixed_tendon_mask: wp.array | None = None, + env_mask: wp.array | None = None, ) -> None: """Set fixed tendon stiffness into internal buffers using masks. @@ -2640,23 +2683,29 @@ def set_fixed_tendon_stiffness_mask( This method expects full data. .. tip:: - Both the index and mask methods have dedicated optimized implementations. Performance is similar for both. - However, to allow graphed pipelines, the mask method must be used. + For maximum performance we recommend using the index method. This is because in PhysX, the tensor API + is only supporting indexing, hence masks need to be converted to indices. Args: stiffness: Fixed tendon stiffness. Shape is (num_instances, num_fixed_tendons). fixed_tendon_mask: Fixed tendon mask. If None, then all fixed tendons are used. - Shape is (num_fixed_tendons,). env_mask: Environment mask. If None, then all the instances are updated. Shape is (num_instances,). """ - raise NotImplementedError() + # Resolve masks. + env_ids = self._resolve_env_mask(env_mask) + fixed_tendon_ids = self._resolve_fixed_tendon_mask(fixed_tendon_mask) + # Set full data to True to ensure the right code path is taken inside the kernel. + self.set_fixed_tendon_stiffness_index( + stiffness=stiffness, fixed_tendon_ids=fixed_tendon_ids, env_ids=env_ids, full_data=True + ) def set_fixed_tendon_damping_index( - self, - *, - damping: float | torch.Tensor | wp.array, - fixed_tendon_ids: Sequence[int] | torch.Tensor | wp.array | None = None, - env_ids: Sequence[int] | torch.Tensor | wp.array | None = None, + self, + *, + damping: float | torch.Tensor | wp.array, + fixed_tendon_ids: Sequence[int] | torch.Tensor | wp.array | None = None, + env_ids: Sequence[int] | torch.Tensor | wp.array | None = None, + full_data: bool = False, ) -> None: """Set fixed tendon damping into internal buffers using indices. @@ -2665,25 +2714,63 @@ def set_fixed_tendon_damping_index( function. .. note:: - This method expects partial data. + This method expects partial data or full data. .. tip:: - Both the index and mask methods have dedicated optimized implementations. Performance is similar for both. - However, to allow graphed pipelines, the mask method must be used. + For maximum performance we recommend using the index method. This is because in PhysX, the tensor API + is only supporting indexing, hence masks need to be converted to indices. Args: - damping: Fixed tendon damping. Shape is (len(env_ids), len(fixed_tendon_ids)). + damping: Fixed tendon damping. Shape is (len(env_ids), len(fixed_tendon_ids)) or + (num_instances, num_fixed_tendons) if full_data. fixed_tendon_ids: The tendon indices to set the damping for. Defaults to None (all fixed tendons). env_ids: Environment indices. If None, then all indices are used. + full_data: Whether to expect full data. Defaults to False. """ - raise NotImplementedError() + # resolve indices + env_ids = self._resolve_env_ids(env_ids) + fixed_tendon_ids = self._resolve_fixed_tendon_ids(fixed_tendon_ids) + if full_data: + self.assert_shape_and_dtype(damping, (self.num_instances, self.num_fixed_tendons), wp.float32, "damping") + else: + self.assert_shape_and_dtype(damping, (env_ids.shape[0], fixed_tendon_ids.shape[0]), wp.float32, "damping") + # Warp kernels can ingest torch tensors directly, so we don't need to convert to warp arrays here. + if isinstance(damping, float): + wp.launch( + articulation_kernels.float_data_to_buffer_with_indices, + dim=(env_ids.shape[0], fixed_tendon_ids.shape[0]), + inputs=[ + damping, + env_ids, + fixed_tendon_ids, + ], + outputs=[ + self.data._fixed_tendon_damping, + ], + device=self.device, + ) + else: + wp.launch( + shared_kernels.write_2d_data_to_buffer_with_indices, + dim=(env_ids.shape[0], fixed_tendon_ids.shape[0]), + inputs=[ + damping, + env_ids, + fixed_tendon_ids, + ], + outputs=[ + self.data._fixed_tendon_damping, + ], + device=self.device, + ) + # Only updates internal buffers, does not apply the damping to the simulation. def set_fixed_tendon_damping_mask( - self, - *, - damping: float | torch.Tensor | wp.array, - fixed_tendon_mask: wp.array | None = None, - env_mask: wp.array | None = None, + self, + *, + damping: float | torch.Tensor | wp.array, + fixed_tendon_mask: wp.array | None = None, + env_mask: wp.array | None = None, ) -> None: """Set fixed tendon damping into internal buffers using masks. @@ -2695,16 +2782,21 @@ def set_fixed_tendon_damping_mask( This method expects full data. .. tip:: - Both the index and mask methods have dedicated optimized implementations. Performance is similar for both. - However, to allow graphed pipelines, the mask method must be used. + For maximum performance we recommend using the index method. This is because in PhysX, the tensor API + is only supporting indexing, hence masks need to be converted to indices. Args: damping: Fixed tendon damping. Shape is (num_instances, num_fixed_tendons). fixed_tendon_mask: Fixed tendon mask. If None, then all fixed tendons are used. - Shape is (num_fixed_tendons,). env_mask: Environment mask. If None, then all the instances are updated. Shape is (num_instances,). """ - raise NotImplementedError() + # Resolve masks. + env_ids = self._resolve_env_mask(env_mask) + fixed_tendon_ids = self._resolve_fixed_tendon_mask(fixed_tendon_mask) + # Set full data to True to ensure the right code path is taken inside the kernel. + self.set_fixed_tendon_damping_index( + damping=damping, fixed_tendon_ids=fixed_tendon_ids, env_ids=env_ids, full_data=True + ) def set_fixed_tendon_limit_stiffness_index( self, @@ -2762,11 +2854,12 @@ def set_fixed_tendon_limit_stiffness_mask( raise NotImplementedError() def set_fixed_tendon_position_limit_index( - self, - *, - limit: float | torch.Tensor | wp.array, - fixed_tendon_ids: Sequence[int] | torch.Tensor | wp.array | None = None, - env_ids: Sequence[int] | torch.Tensor | wp.array | None = None, + self, + *, + limit: float | torch.Tensor | wp.array, + fixed_tendon_ids: Sequence[int] | torch.Tensor | wp.array | None = None, + env_ids: Sequence[int] | torch.Tensor | wp.array | None = None, + full_data: bool = False, ) -> None: """Set fixed tendon position limit into internal buffers using indices. @@ -2775,25 +2868,27 @@ def set_fixed_tendon_position_limit_index( :meth:`write_fixed_tendon_properties_to_sim_index` method. .. note:: - This method expects partial data. + This method expects partial data or full data. .. tip:: - Both the index and mask methods have dedicated optimized implementations. Performance is similar for both. - However, to allow graphed pipelines, the mask method must be used. + For maximum performance we recommend using the index method. This is because in PhysX, the tensor API + is only supporting indexing, hence masks need to be converted to indices. Args: - limit: Fixed tendon position limit. Shape is (len(env_ids), len(fixed_tendon_ids)). + limit: Fixed tendon position limit. Shape is (len(env_ids), len(fixed_tendon_ids)) or + (num_instances, num_fixed_tendons) if full_data. fixed_tendon_ids: The tendon indices to set the position limit for. Defaults to None (all fixed tendons). env_ids: Environment indices. If None, then all indices are used. + full_data: Whether to expect full data. Defaults to False. """ raise NotImplementedError() def set_fixed_tendon_position_limit_mask( - self, - *, - limit: float | torch.Tensor | wp.array, - fixed_tendon_mask: wp.array | None = None, - env_mask: wp.array | None = None, + self, + *, + limit: float | torch.Tensor | wp.array, + fixed_tendon_mask: wp.array | None = None, + env_mask: wp.array | None = None, ) -> None: """Set fixed tendon position limit into internal buffers using masks. @@ -2805,13 +2900,12 @@ def set_fixed_tendon_position_limit_mask( This method expects full data. .. tip:: - Both the index and mask methods have dedicated optimized implementations. Performance is similar for both. - However, to allow graphed pipelines, the mask method must be used. + For maximum performance we recommend using the index method. This is because in PhysX, the tensor API + is only supporting indexing, hence masks need to be converted to indices. Args: limit: Fixed tendon position limit. Shape is (num_instances, num_fixed_tendons). fixed_tendon_mask: Fixed tendon mask. If None, then all fixed tendons are used. - Shape is (num_fixed_tendons,). env_mask: Environment mask. If None, then all the instances are updated. Shape is (num_instances,). """ raise NotImplementedError() @@ -2927,9 +3021,9 @@ def set_fixed_tendon_offset_mask( raise NotImplementedError() def write_fixed_tendon_properties_to_sim_index( - self, - *, - env_ids: Sequence[int] | torch.Tensor | wp.array | None = None, + self, + *, + env_ids: Sequence[int] | torch.Tensor | wp.array | None = None, ) -> None: """Write fixed tendon properties into the simulation using indices. @@ -2942,7 +3036,32 @@ def write_fixed_tendon_properties_to_sim_index( (all fixed tendons). env_ids: Environment indices. If None, then all indices are used. """ - raise NotImplementedError() + wp.launch( + shared_kernels.write_2d_data_to_buffer_with_indices, + dim=(env_ids.shape[0], self._ALL_FIXED_TENDON_INDICES.shape[0]), + inputs=[ + self.data._fixed_tendon_damping, + env_ids, + self._ALL_FIXED_TENDON_INDICES, + ], + outputs=[ + self.data._sim_bind_fixed_tendon_damping, + ], + device=self.device, + ) + wp.launch( + shared_kernels.write_2d_data_to_buffer_with_indices, + dim=(env_ids.shape[0], self._ALL_FIXED_TENDON_INDICES.shape[0]), + inputs=[ + self.data._fixed_tendon_stiffness, + env_ids, + self._ALL_FIXED_TENDON_INDICES, + ], + outputs=[ + self.data._sim_bind_fixed_tendon_stiffness, + ], + device=self.device, + ) def write_fixed_tendon_properties_to_sim_mask( self, @@ -2958,7 +3077,9 @@ def write_fixed_tendon_properties_to_sim_mask( Args: env_mask: Environment mask. If None, then all the instances are updated. Shape is (num_instances,). """ - raise NotImplementedError() + env_ids = self._resolve_mask(env_mask) + + self.write_fixed_tendon_properties_to_sim_index(env_ids) def set_spatial_tendon_stiffness_index( self, @@ -3545,12 +3666,12 @@ def _process_actuators_cfg(self): def _process_tendons(self): """Process fixed and spatial tendons.""" - # create a list to store the fixed tendon names - self._fixed_tendon_names = list() - self._spatial_tendon_names = list() - # parse fixed tendons properties if they exist - if self.num_fixed_tendons > 0 or self.num_spatial_tendons > 0: - raise NotImplementedError("Fixed and spatial tendons are not supported yet.") + if self._root_view.tendon_count > 0: + tendon_types = wp.to_torch( + self._root_view.get_attribute("mujoco.tendon_type", SimulationManager.get_model()) + ) + if tendon_types.sum() > 0: + raise NotImplementedError("Spatial tendons are not supported yet.") def _apply_actuator_model(self): """Processes joint commands for the articulation by forwarding them to the actuators. @@ -3743,8 +3864,8 @@ def format_limits(_, v: tuple[float, float]) -> str: logger.info(f"Simulation parameters for joints in {self.cfg.prim_path}:\n" + joint_table.get_string()) # read out all fixed tendon parameters from simulation - if self.num_fixed_tendons > 0: - raise NotImplementedError("Fixed tendons are not supported yet.") + # if self.num_fixed_tendons > 0: + # raise NotImplementedError("Fixed tendons are not supported yet.") if self.num_spatial_tendons > 0: raise NotImplementedError("Spatial tendons are not supported yet.") diff --git a/source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation_data.py b/source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation_data.py index e0320c3b2a14..fa93c3e85ae5 100644 --- a/source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation_data.py +++ b/source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation_data.py @@ -505,7 +505,7 @@ def fixed_tendon_pos_limits(self) -> ProxyArray: Shape is (num_instances, num_fixed_tendons, 2), dtype = wp.vec2f. In torch this resolves to (num_instances, num_fixed_tendons, 2). """ - raise NotImplementedError + return self._fixed_tendon_pos_limits_ta """ Spatial tendon properties. @@ -1400,20 +1400,14 @@ def _create_simulation_bindings(self) -> None: (self._num_instances, 0), dtype=wp.float32, device=self.device ) + # assumes all tendons are fixed and only one arti in scene if self._root_view.tendon_count > 0: self._sim_bind_fixed_tendon_stiffness = self._root_view.get_attribute( "mujoco.tendon_stiffness", SimulationManager.get_model() - ) + )[:,0] self._sim_bind_fixed_tendon_damping = self._root_view.get_attribute( "mujoco.tendon_damping", SimulationManager.get_model(), - ) - self._sim_bind_fixed_tendon_rest_length = self._root_view.get_attribute( - "mujoco.tendon_springlength", SimulationManager.get_model() - ) - self._sim_bind_fixed_tendon_pos_limits = self._root_view.get_attribute( - "mujoco.tendon_range", SimulationManager.get_model() - ) - + )[:,0] else: self._sim_bind_fixed_tendon_stiffness = wp.zeros( (self._num_instances, 0), dtype=wp.float32, device=self.device @@ -1421,12 +1415,6 @@ def _create_simulation_bindings(self) -> None: self._sim_bind_fixed_tendon_damping = wp.zeros( (self._num_instances, 0), dtype=wp.float32, device=self.device ) - self._sim_bind_fixed_tendon_rest_length = wp.zeros( - (self._num_instances, 0), dtype=wp.float32, device=self.device - ) - self._sim_bind_fixed_tendon_pos_limits = wp.zeros( - (self._num_instances, 0), dtype=wp.float32, device=self.device - ) # Re-pin ProxyArray wrappers to the newly created sim bindings. # On first init, _create_buffers() handles this after all buffers exist. @@ -1505,13 +1493,9 @@ def _create_buffers(self) -> None: if self._num_fixed_tendons > 0: self._fixed_tendon_stiffness = wp.clone(self._sim_bind_fixed_tendon_stiffness) self._fixed_tendon_damping = wp.clone(self._sim_bind_fixed_tendon_damping) - # self._fixed_tendon_rest_length = wp.clone(self._sim_bind_fixed_tendon_rest_length) - # self._fixed_tendon_pos_limits = wp.clone(self._sim_bind_fixed_tendon_pos_limits) else: self._fixed_tendon_stiffness = wp.zeros((self._num_instances, 0), dtype=wp.float32, device=self.device) self._fixed_tendon_damping = wp.zeros((self._num_instances, 0), dtype=wp.float32, device=self.device) - # self._fixed_tendon_rest_length = wp.zeros((self._num_instances, 0), dtype=wp.float32, device=self.device) - # self._fixed_tendon_pos_limits = wp.clone(self._sim_bind_fixed_tendon_pos_limits) # Initialize the lazy buffers. # -- link frame w.r.t. world frame @@ -1649,7 +1633,6 @@ def _pin_proxy_arrays(self) -> None: self._body_com_pos_b_ta = ProxyArray(self._sim_bind_body_com_pos_b) self._fixed_tendon_stiffness_ta = ProxyArray(self._sim_bind_fixed_tendon_stiffness) self._fixed_tendon_damping_ta = ProxyArray(self._sim_bind_fixed_tendon_damping) - self._fixed_tendon_rest_length = ProxyArray(self._sim_bind_fixed_tendon_rest_length) # Category 2: TimestampedBuffer properties self._root_link_vel_w_ta = ProxyArray(self._root_link_vel_w.data) From 6e82fe55abdcd3ac11e5625d95c1a68541fdbbd3 Mon Sep 17 00:00:00 2001 From: Ryan Gresia Date: Wed, 6 May 2026 12:38:57 -0700 Subject: [PATCH 04/12] adds fixed tendon props prim --- .../isaaclab/isaaclab/sim/schemas/schemas.py | 31 ++++++++++++------- .../direct/shadow_hand/shadow_hand_env_cfg.py | 21 +++++++++++-- 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/source/isaaclab/isaaclab/sim/schemas/schemas.py b/source/isaaclab/isaaclab/sim/schemas/schemas.py index 0f97b542e031..d76a9e9df82a 100644 --- a/source/isaaclab/isaaclab/sim/schemas/schemas.py +++ b/source/isaaclab/isaaclab/sim/schemas/schemas.py @@ -765,24 +765,31 @@ def modify_fixed_tendon_properties( # get USD prim tendon_prim = stage.GetPrimAtPath(prim_path) - # check if prim has fixed tendon applied on it + # check if prim has fixed tendon applied on it or if the mjc tendon prim exiss applied_schemas = tendon_prim.GetAppliedSchemas() - if not any("PhysxTendonAxisRootAPI" in s for s in applied_schemas): + prim_type = tendon_prim.GetTypeName() + if not any("PhysxTendonAxisRootAPI" in s for s in applied_schemas) and prim_type != "MjcTendon": return False # resolve all available instances of the schema since it is multi-instance cfg = cfg.to_dict() - for schema_name in applied_schemas: - if "PhysxTendonAxisRootAPI" not in schema_name: - continue - # set into PhysX API by attribute prefix schema_name: (e.g. PhysxTendonAxisRootAPI:default:stiffness) + if prim_type != "MjcTendon": + for schema_name in applied_schemas: + if "PhysxTendonAxisRootAPI" not in schema_name: + continue + # set into PhysX API by attribute prefix schema_name: (e.g. PhysxTendonAxisRootAPI:default:stiffness) + for attr_name, value in cfg.items(): + safe_set_attribute_on_usd_prim( + tendon_prim, + f"{schema_name}:{to_camel_case(attr_name, 'cC')}", + value, + camel_case=False, + ) + else: + # only stiffness and damping in the cfg map to mjc attributes for attr_name, value in cfg.items(): - safe_set_attribute_on_usd_prim( - tendon_prim, - f"{schema_name}:{to_camel_case(attr_name, 'cC')}", - value, - camel_case=False, - ) + safe_set_attribute_on_usd_prim(tendon_prim, f"mjc:{to_camel_case(attr_name, 'cC')}", value, + camel_case=False) # success return True diff --git a/source/isaaclab_tasks/isaaclab_tasks/direct/shadow_hand/shadow_hand_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/direct/shadow_hand/shadow_hand_env_cfg.py index 0a2258d0b0b1..f3b29296b353 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/direct/shadow_hand/shadow_hand_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/direct/shadow_hand/shadow_hand_env_cfg.py @@ -73,6 +73,19 @@ class NewtonEventCfg: }, ) + robot_tendon_properties = EventTerm( + func=mdp.randomize_fixed_tendon_parameters, + min_step_count_between_reset=720, + mode="reset", + params={ + "asset_cfg": SceneEntityCfg("robot", fixed_tendon_names=".*"), + "stiffness_distribution_params": (0.75, 1.5), + "damping_distribution_params": (0.3, 3.0), + "operation": "scale", + "distribution": "log_uniform", + }, + ) + @configclass class PhysxEventCfg: @@ -137,7 +150,8 @@ class ShadowHandRobotCfg(PresetCfg): prim_path="/World/envs/env_.*/Robot", spawn=sim_utils.UsdFileCfg( # newton requires implicitactuators be specified in usd and there's a bug with physx tendons - usd_path=f"{ISAAC_NUCLEUS_DIR}/Robots/ShadowRobot/ShadowHand/shadow_hand_instanceable_newton.usd", + #usd_path=f"{ISAAC_NUCLEUS_DIR}/Robots/ShadowRobot/ShadowHand/shadow_hand_instanceable_newton.usd", + usd_path=f"/home/rgresia/Repositories/mujoco_menagerie/shadow_hand/right_hand.usd/right_shadow_hand.usda", activate_contact_sensors=False, rigid_props=sim_utils.RigidBodyPropertiesCfg( disable_gravity=True, @@ -146,9 +160,10 @@ class ShadowHandRobotCfg(PresetCfg): ), articulation_props=sim_utils.ArticulationRootPropertiesCfg(enabled_self_collisions=True), joint_drive_props=sim_utils.JointDrivePropertiesCfg(drive_type="force"), + fixed_tendons_props=sim_utils.FixedTendonPropertiesCfg(damping=0.1), ), init_state=ArticulationCfg.InitialStateCfg( - pos=(0.0, 0.0, 0.5), + pos=(-0.33, -0.36, 0.3), # WARNING(Octi): Newton's import_usd.py bakes the USD body xformOp rotation into # joint_X_p for the root fixed joint, which cancels with the matching localPose1 # rotation in joint_X_c during FK (joint_X_p * inv(joint_X_c) ≈ identity). This @@ -248,7 +263,7 @@ class ShadowHandSceneCfg(PresetCfg): newton_mjwarp: InteractiveSceneCfg = InteractiveSceneCfg( num_envs=8192, env_spacing=0.75, replicate_physics=True, clone_in_fabric=False ) - default: InteractiveSceneCfg = physx + default: InteractiveSceneCfg = newton_mjwarp @configclass From 27a551be0c2b1a2e4a86e58541ed86b0411c7a38 Mon Sep 17 00:00:00 2001 From: Ryan Gresia Date: Thu, 14 May 2026 14:48:45 -0700 Subject: [PATCH 05/12] refactor tendon randomization event --- source/isaaclab/isaaclab/envs/mdp/events.py | 338 +++++++------------- 1 file changed, 124 insertions(+), 214 deletions(-) diff --git a/source/isaaclab/isaaclab/envs/mdp/events.py b/source/isaaclab/isaaclab/envs/mdp/events.py index 27ca9b33e0fc..df675bf7032b 100644 --- a/source/isaaclab/isaaclab/envs/mdp/events.py +++ b/source/isaaclab/isaaclab/envs/mdp/events.py @@ -1424,12 +1424,59 @@ def __call__( limits=joint_pos_limits, joint_ids=joint_ids, env_ids=env_ids, warn_limit_violation=False ) -class _RandomizeFixedTendonPropertiesPhysx(ManagerTermBase): - def __init__( - self, cfg: EventTermCfg, env: ManagerBasedEnv, asset: RigidObject | Articulation, asset_cfg: SceneEntityCfg - ): - self.asset_cfg = asset_cfg - self.asset = asset + +class randomize_fixed_tendon_parameters(ManagerTermBase): + """Randomize the simulated fixed tendon parameters of an articulation by adding, scaling, or setting random values. + + This function allows randomizing the fixed tendon parameters of the asset. + These correspond to the physics engine tendon properties that affect the joint behavior. + + The function samples random values from the given distribution parameters and applies the operation to + the tendon properties. It then sets the values into the physics simulation. If the distribution parameters + are not provided for a particular property, the function does not modify the property. + """ + + def __init__(self, cfg: EventTermCfg, env: ManagerBasedEnv): + """Initialize the term. + + Args: + cfg: The configuration of the event term. + env: The environment instance. + + Raises: + TypeError: If `params` is not a tuple of two numbers. + ValueError: If the operation is not supported. + ValueError: If the lower bound is negative or zero when not allowed. + ValueError: If the upper bound is less than the lower bound. + """ + super().__init__(cfg, env) + + # extract the used quantities (to enable type-hinting) + self.asset_cfg: SceneEntityCfg = cfg.params["asset_cfg"] + self.asset: RigidObject | Articulation = env.scene[self.asset_cfg.name] + # check for valid operation + if cfg.params["operation"] == "scale": + if "stiffness_distribution_params" in cfg.params: + _validate_scale_range( + cfg.params["stiffness_distribution_params"], "stiffness_distribution_params", allow_zero=False + ) + if "damping_distribution_params" in cfg.params: + _validate_scale_range(cfg.params["damping_distribution_params"], "damping_distribution_params") + if "limit_stiffness_distribution_params" in cfg.params: + _validate_scale_range( + cfg.params["limit_stiffness_distribution_params"], "limit_stiffness_distribution_params" + ) + elif cfg.params["operation"] not in ("abs", "add"): + raise ValueError( + "Randomization term 'randomize_fixed_tendon_parameters' does not support operation:" + f" '{cfg.params['operation']}'." + ) + + manager_name = env.sim.physics_manager.__name__.lower() + if "newton" in manager_name: + self.newton = True + else: + self.newton = False def __call__( self, @@ -1487,233 +1534,96 @@ def __call__( # limit stiffness if limit_stiffness_distribution_params is not None: - limit_stiffness = _randomize_prop_by_op( - self.asset.data.fixed_tendon_limit_stiffness.torch.clone(), - limit_stiffness_distribution_params, - env_ids, - tendon_ids, - operation=operation, - distribution=distribution, - ) - self.asset.set_fixed_tendon_limit_stiffness( - limit_stiffness[env_ids[:, None], tendon_ids], tendon_ids, env_ids - ) + if not self.newton: + limit_stiffness = _randomize_prop_by_op( + self.asset.data.fixed_tendon_limit_stiffness.torch.clone(), + limit_stiffness_distribution_params, + env_ids, + tendon_ids, + operation=operation, + distribution=distribution, + ) + self.asset.set_fixed_tendon_limit_stiffness( + limit_stiffness[env_ids[:, None], tendon_ids], tendon_ids, env_ids + ) + else: + raise NotImplementedError("Limit stiffness is not support in Newton.") # position limits if lower_limit_distribution_params is not None or upper_limit_distribution_params is not None: - limit = self.asset.data.fixed_tendon_pos_limits.torch.clone() - # -- lower limit - if lower_limit_distribution_params is not None: - limit[..., 0] = _randomize_prop_by_op( - limit[..., 0], - lower_limit_distribution_params, + if not self.newton: + limit = self.asset.data.fixed_tendon_pos_limits.torch.clone() + # -- lower limit + if lower_limit_distribution_params is not None: + limit[..., 0] = _randomize_prop_by_op( + limit[..., 0], + lower_limit_distribution_params, + env_ids, + tendon_ids, + operation=operation, + distribution=distribution, + ) + # -- upper limit + if upper_limit_distribution_params is not None: + limit[..., 1] = _randomize_prop_by_op( + limit[..., 1], + upper_limit_distribution_params, + env_ids, + tendon_ids, + operation=operation, + distribution=distribution, + ) + + # check if the limits are valid + tendon_limits = limit[env_ids[:, None], tendon_ids] + if (tendon_limits[..., 0] > tendon_limits[..., 1]).any(): + raise ValueError( + "Randomization term 'randomize_fixed_tendon_parameters' is setting lower tendon limits that are" + " greater than upper tendon limits." + ) + self.asset.set_fixed_tendon_position_limit_index( + limit=tendon_limits, fixed_tendon_ids=tendon_ids, env_ids=env_ids + ) + else: + raise NotImplementedError("Position limits is not yet implemented with Newton.") + + # rest length + if rest_length_distribution_params is not None: + if not self.newton: + rest_length = _randomize_prop_by_op( + self.asset.data.fixed_tendon_rest_length.torch.clone(), + rest_length_distribution_params, env_ids, tendon_ids, operation=operation, distribution=distribution, ) - # -- upper limit - if upper_limit_distribution_params is not None: - limit[..., 1] = _randomize_prop_by_op( - limit[..., 1], - upper_limit_distribution_params, + self.asset.set_fixed_tendon_rest_length_index( + rest_length=rest_length[env_ids[:, None], tendon_ids], fixed_tendon_ids=tendon_ids, env_ids=env_ids + ) + else: + raise NotImplementedError("Rest length is not yet implemented with Newton.") + # offset + if offset_distribution_params is not None: + if not self.newton: + offset = _randomize_prop_by_op( + self.asset.data.fixed_tendon_offset.torch.clone(), + offset_distribution_params, env_ids, tendon_ids, operation=operation, distribution=distribution, ) - - # check if the limits are valid - tendon_limits = limit[env_ids[:, None], tendon_ids] - if (tendon_limits[..., 0] > tendon_limits[..., 1]).any(): - raise ValueError( - "Randomization term 'randomize_fixed_tendon_parameters' is setting lower tendon limits that are" - " greater than upper tendon limits." + self.asset.set_fixed_tendon_offset_index( + offset=offset[env_ids[:, None], tendon_ids], fixed_tendon_ids=tendon_ids, env_ids=env_ids ) - self.asset.set_fixed_tendon_position_limit_index( - limit=tendon_limits, fixed_tendon_ids=tendon_ids, env_ids=env_ids - ) - - # rest length - if rest_length_distribution_params is not None: - rest_length = _randomize_prop_by_op( - self.asset.data.fixed_tendon_rest_length.torch.clone(), - rest_length_distribution_params, - env_ids, - tendon_ids, - operation=operation, - distribution=distribution, - ) - self.asset.set_fixed_tendon_rest_length_index( - rest_length=rest_length[env_ids[:, None], tendon_ids], fixed_tendon_ids=tendon_ids, env_ids=env_ids - ) - - # offset - if offset_distribution_params is not None: - offset = _randomize_prop_by_op( - self.asset.data.fixed_tendon_offset.torch.clone(), - offset_distribution_params, - env_ids, - tendon_ids, - operation=operation, - distribution=distribution, - ) - self.asset.set_fixed_tendon_offset_index( - offset=offset[env_ids[:, None], tendon_ids], fixed_tendon_ids=tendon_ids, env_ids=env_ids - ) - - # write the fixed tendon properties into the simulation - self.asset.write_fixed_tendon_properties_to_sim_index(env_ids=env_ids) - -class _RandomizeFixedTendonPropertiesNewton(ManagerTermBase): - def __init__( - self, cfg: EventTermCfg, env: ManagerBasedEnv, asset: RigidObject | Articulation, asset_cfg: SceneEntityCfg - ): - self.asset = asset - self.asset_cfg = asset_cfg - - def __call__( - self, - env: ManagerBasedEnv, - env_ids: torch.Tensor | None, - asset_cfg: SceneEntityCfg, - stiffness_distribution_params: tuple[float, float] | None = None, - damping_distribution_params: tuple[float, float] | None = None, - limit_stiffness_distribution_params: tuple[float, float] | None = None, - lower_limit_distribution_params: tuple[float, float] | None = None, - upper_limit_distribution_params: tuple[float, float] | None = None, - rest_length_distribution_params: tuple[float, float] | None = None, - offset_distribution_params: tuple[float, float] | None = None, - operation: Literal["add", "scale", "abs"] = "abs", - distribution: Literal["uniform", "log_uniform", "gaussian"] = "uniform", - ): - # resolve environment ids - if env_ids is None: - env_ids = torch.arange(env.scene.num_envs, device=self.asset.device) - - # resolve joint indices - if self.asset_cfg.fixed_tendon_ids == slice(None): - tendon_ids = slice(None) # for optimization purposes - else: - tendon_ids = torch.tensor(self.asset_cfg.fixed_tendon_ids, dtype=torch.int, device=self.asset.device) - - # sample tendon properties from the given ranges and set into the physics simulation - # stiffness - if stiffness_distribution_params is not None: - stiffness = _randomize_prop_by_op( - self.asset.data.fixed_tendon_stiffness.torch.clone(), - stiffness_distribution_params, - env_ids, - tendon_ids, - operation=operation, - distribution=distribution, - ) - self.asset.set_fixed_tendon_stiffness_index( - stiffness=stiffness[env_ids[:, None], tendon_ids], fixed_tendon_ids=tendon_ids, env_ids=env_ids - ) - - # damping - if damping_distribution_params is not None: - damping = _randomize_prop_by_op( - self.asset.data.fixed_tendon_damping.torch.clone(), - damping_distribution_params, - env_ids, - tendon_ids, - operation=operation, - distribution=distribution, - ) - self.asset.set_fixed_tendon_damping_index( - damping=damping[env_ids[:, None], tendon_ids], fixed_tendon_ids=tendon_ids, env_ids=env_ids - ) + else: + raise NotImplementedError("Offset is not supported in Newton.") # write the fixed tendon properties into the simulation self.asset.write_fixed_tendon_properties_to_sim_index(env_ids=env_ids) -class randomize_fixed_tendon_parameters(ManagerTermBase): - """Randomize the simulated fixed tendon parameters of an articulation by adding, scaling, or setting random values. - - This function allows randomizing the fixed tendon parameters of the asset. - These correspond to the physics engine tendon properties that affect the joint behavior. - - The function samples random values from the given distribution parameters and applies the operation to - the tendon properties. It then sets the values into the physics simulation. If the distribution parameters - are not provided for a particular property, the function does not modify the property. - """ - - def __init__(self, cfg: EventTermCfg, env: ManagerBasedEnv): - """Initialize the term. - - Args: - cfg: The configuration of the event term. - env: The environment instance. - - Raises: - TypeError: If `params` is not a tuple of two numbers. - ValueError: If the operation is not supported. - ValueError: If the lower bound is negative or zero when not allowed. - ValueError: If the upper bound is less than the lower bound. - """ - super().__init__(cfg, env) - - # extract the used quantities (to enable type-hinting) - self.asset_cfg: SceneEntityCfg = cfg.params["asset_cfg"] - self.asset: RigidObject | Articulation = env.scene[self.asset_cfg.name] - # check for valid operation - if cfg.params["operation"] == "scale": - if "stiffness_distribution_params" in cfg.params: - _validate_scale_range( - cfg.params["stiffness_distribution_params"], "stiffness_distribution_params", allow_zero=False - ) - if "damping_distribution_params" in cfg.params: - _validate_scale_range(cfg.params["damping_distribution_params"], "damping_distribution_params") - if "limit_stiffness_distribution_params" in cfg.params: - _validate_scale_range( - cfg.params["limit_stiffness_distribution_params"], "limit_stiffness_distribution_params" - ) - elif cfg.params["operation"] not in ("abs", "add"): - raise ValueError( - "Randomization term 'randomize_fixed_tendon_parameters' does not support operation:" - f" '{cfg.params['operation']}'." - ) - - manager_name = env.sim.physics_manager.__name__.lower() - if "newton" in manager_name: - self._impl = _RandomizeFixedTendonPropertiesNewton(cfg, env, self.asset, self.asset_cfg) - else: - self._impl = _RandomizeFixedTendonPropertiesPhysx(cfg, env, self.asset, self.asset_cfg) - - def __call__( - self, - env: ManagerBasedEnv, - env_ids: torch.Tensor | None, - asset_cfg: SceneEntityCfg, - stiffness_distribution_params: tuple[float, float] | None = None, - damping_distribution_params: tuple[float, float] | None = None, - limit_stiffness_distribution_params: tuple[float, float] | None = None, - lower_limit_distribution_params: tuple[float, float] | None = None, - upper_limit_distribution_params: tuple[float, float] | None = None, - rest_length_distribution_params: tuple[float, float] | None = None, - offset_distribution_params: tuple[float, float] | None = None, - operation: Literal["add", "scale", "abs"] = "abs", - distribution: Literal["uniform", "log_uniform", "gaussian"] = "uniform", - ): - self._impl( - env, - env_ids, - asset_cfg, - stiffness_distribution_params, - damping_distribution_params, - limit_stiffness_distribution_params, - lower_limit_distribution_params, - upper_limit_distribution_params, - rest_length_distribution_params, - offset_distribution_params, - operation, - distribution, - ) - - def apply_external_force_torque( env: ManagerBasedEnv, env_ids: torch.Tensor, From f5f43d4d17d35bde25ee0ae18af84de6adda56c2 Mon Sep 17 00:00:00 2001 From: Ryan Gresia Date: Thu, 14 May 2026 15:19:10 -0700 Subject: [PATCH 06/12] articulation cleanup --- .../assets/articulation/articulation.py | 32 +++++++------------ 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation.py b/source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation.py index 8ea266aac9d3..f1d7e17f0a21 100644 --- a/source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation.py +++ b/source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation.py @@ -2626,7 +2626,6 @@ def set_fixed_tendon_stiffness_index( stiffness: float | torch.Tensor | wp.array, fixed_tendon_ids: Sequence[int] | torch.Tensor | wp.array | None = None, env_ids: Sequence[int] | torch.Tensor | wp.array | None = None, - full_data: bool = False, ) -> None: """Set fixed tendon stiffness into internal buffers using indices. @@ -2651,14 +2650,9 @@ def set_fixed_tendon_stiffness_index( # resolve indices env_ids = self._resolve_env_ids(env_ids) fixed_tendon_ids = self._resolve_fixed_tendon_ids(fixed_tendon_ids) - if full_data: - self.assert_shape_and_dtype( - stiffness, (self.num_instances, self.num_fixed_tendons), wp.float32, "stiffness" - ) - else: - self.assert_shape_and_dtype( - stiffness, (env_ids.shape[0], fixed_tendon_ids.shape[0]), wp.float32, "stiffness" - ) + self.assert_shape_and_dtype( + stiffness, (env_ids.shape[0], fixed_tendon_ids.shape[0]), wp.float32, "stiffness" + ) # Warp kernels can ingest torch tensors directly, so we don't need to convert to warp arrays here. if isinstance(stiffness, float): wp.launch( @@ -2729,7 +2723,6 @@ def set_fixed_tendon_damping_index( damping: float | torch.Tensor | wp.array, fixed_tendon_ids: Sequence[int] | torch.Tensor | wp.array | None = None, env_ids: Sequence[int] | torch.Tensor | wp.array | None = None, - full_data: bool = False, ) -> None: """Set fixed tendon damping into internal buffers using indices. @@ -2749,15 +2742,13 @@ def set_fixed_tendon_damping_index( (num_instances, num_fixed_tendons) if full_data. fixed_tendon_ids: The tendon indices to set the damping for. Defaults to None (all fixed tendons). env_ids: Environment indices. If None, then all indices are used. - full_data: Whether to expect full data. Defaults to False. """ # resolve indices env_ids = self._resolve_env_ids(env_ids) fixed_tendon_ids = self._resolve_fixed_tendon_ids(fixed_tendon_ids) - if full_data: - self.assert_shape_and_dtype(damping, (self.num_instances, self.num_fixed_tendons), wp.float32, "damping") - else: - self.assert_shape_and_dtype(damping, (env_ids.shape[0], fixed_tendon_ids.shape[0]), wp.float32, "damping") + + self.assert_shape_and_dtype(damping, (env_ids.shape[0], fixed_tendon_ids.shape[0]), wp.float32, "damping") + # Warp kernels can ingest torch tensors directly, so we don't need to convert to warp arrays here. if isinstance(damping, float): wp.launch( @@ -2790,11 +2781,11 @@ def set_fixed_tendon_damping_index( # Only updates internal buffers, does not apply the damping to the simulation. def set_fixed_tendon_damping_mask( - self, - *, - damping: float | torch.Tensor | wp.array, - fixed_tendon_mask: wp.array | None = None, - env_mask: wp.array | None = None, + self, + *, + damping: float | torch.Tensor | wp.array, + fixed_tendon_mask: wp.array | None = None, + env_mask: wp.array | None = None, ) -> None: """Set fixed tendon damping into internal buffers using masks. @@ -3060,6 +3051,7 @@ def write_fixed_tendon_properties_to_sim_index( (all fixed tendons). env_ids: Environment indices. If None, then all indices are used. """ + # TODO: Combine into one wp.launch( shared_kernels.write_2d_data_to_buffer_with_indices, dim=(env_ids.shape[0], self._ALL_FIXED_TENDON_INDICES.shape[0]), From 3a54517b9f99871890dcf1daab84a7b9501e06ed Mon Sep 17 00:00:00 2001 From: Ryan Gresia Date: Thu, 14 May 2026 15:24:33 -0700 Subject: [PATCH 07/12] cleanup --- .../isaaclab/isaaclab/sim/schemas/schemas.py | 5 +- .../assets/articulation/articulation.py | 62 +++++++++---------- .../assets/articulation/articulation_data.py | 7 ++- .../direct/shadow_hand/shadow_hand_env_cfg.py | 9 ++- 4 files changed, 41 insertions(+), 42 deletions(-) diff --git a/source/isaaclab/isaaclab/sim/schemas/schemas.py b/source/isaaclab/isaaclab/sim/schemas/schemas.py index 144774a6bec1..85fe7f3d6aca 100644 --- a/source/isaaclab/isaaclab/sim/schemas/schemas.py +++ b/source/isaaclab/isaaclab/sim/schemas/schemas.py @@ -896,8 +896,9 @@ def modify_fixed_tendon_properties( else: # only stiffness and damping in the cfg map to mjc attributes for attr_name, value in cfg.items(): - safe_set_attribute_on_usd_prim(tendon_prim, f"mjc:{to_camel_case(attr_name, 'cC')}", value, - camel_case=False) + safe_set_attribute_on_usd_prim( + tendon_prim, f"mjc:{to_camel_case(attr_name, 'cC')}", value, camel_case=False + ) # success return True diff --git a/source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation.py b/source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation.py index f1d7e17f0a21..e162ec286f21 100644 --- a/source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation.py +++ b/source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation.py @@ -2621,11 +2621,11 @@ def set_joint_effort_target_mask( """ def set_fixed_tendon_stiffness_index( - self, - *, - stiffness: float | torch.Tensor | wp.array, - fixed_tendon_ids: Sequence[int] | torch.Tensor | wp.array | None = None, - env_ids: Sequence[int] | torch.Tensor | wp.array | None = None, + self, + *, + stiffness: float | torch.Tensor | wp.array, + fixed_tendon_ids: Sequence[int] | torch.Tensor | wp.array | None = None, + env_ids: Sequence[int] | torch.Tensor | wp.array | None = None, ) -> None: """Set fixed tendon stiffness into internal buffers using indices. @@ -2650,9 +2650,7 @@ def set_fixed_tendon_stiffness_index( # resolve indices env_ids = self._resolve_env_ids(env_ids) fixed_tendon_ids = self._resolve_fixed_tendon_ids(fixed_tendon_ids) - self.assert_shape_and_dtype( - stiffness, (env_ids.shape[0], fixed_tendon_ids.shape[0]), wp.float32, "stiffness" - ) + self.assert_shape_and_dtype(stiffness, (env_ids.shape[0], fixed_tendon_ids.shape[0]), wp.float32, "stiffness") # Warp kernels can ingest torch tensors directly, so we don't need to convert to warp arrays here. if isinstance(stiffness, float): wp.launch( @@ -2685,11 +2683,11 @@ def set_fixed_tendon_stiffness_index( # Only updates internal buffers, does not apply the stiffness to the simulation. def set_fixed_tendon_stiffness_mask( - self, - *, - stiffness: float | torch.Tensor | wp.array, - fixed_tendon_mask: wp.array | None = None, - env_mask: wp.array | None = None, + self, + *, + stiffness: float | torch.Tensor | wp.array, + fixed_tendon_mask: wp.array | None = None, + env_mask: wp.array | None = None, ) -> None: """Set fixed tendon stiffness into internal buffers using masks. @@ -2718,11 +2716,11 @@ def set_fixed_tendon_stiffness_mask( ) def set_fixed_tendon_damping_index( - self, - *, - damping: float | torch.Tensor | wp.array, - fixed_tendon_ids: Sequence[int] | torch.Tensor | wp.array | None = None, - env_ids: Sequence[int] | torch.Tensor | wp.array | None = None, + self, + *, + damping: float | torch.Tensor | wp.array, + fixed_tendon_ids: Sequence[int] | torch.Tensor | wp.array | None = None, + env_ids: Sequence[int] | torch.Tensor | wp.array | None = None, ) -> None: """Set fixed tendon damping into internal buffers using indices. @@ -2869,12 +2867,12 @@ def set_fixed_tendon_limit_stiffness_mask( raise NotImplementedError() def set_fixed_tendon_position_limit_index( - self, - *, - limit: float | torch.Tensor | wp.array, - fixed_tendon_ids: Sequence[int] | torch.Tensor | wp.array | None = None, - env_ids: Sequence[int] | torch.Tensor | wp.array | None = None, - full_data: bool = False, + self, + *, + limit: float | torch.Tensor | wp.array, + fixed_tendon_ids: Sequence[int] | torch.Tensor | wp.array | None = None, + env_ids: Sequence[int] | torch.Tensor | wp.array | None = None, + full_data: bool = False, ) -> None: """Set fixed tendon position limit into internal buffers using indices. @@ -2899,11 +2897,11 @@ def set_fixed_tendon_position_limit_index( raise NotImplementedError() def set_fixed_tendon_position_limit_mask( - self, - *, - limit: float | torch.Tensor | wp.array, - fixed_tendon_mask: wp.array | None = None, - env_mask: wp.array | None = None, + self, + *, + limit: float | torch.Tensor | wp.array, + fixed_tendon_mask: wp.array | None = None, + env_mask: wp.array | None = None, ) -> None: """Set fixed tendon position limit into internal buffers using masks. @@ -3036,9 +3034,9 @@ def set_fixed_tendon_offset_mask( raise NotImplementedError() def write_fixed_tendon_properties_to_sim_index( - self, - *, - env_ids: Sequence[int] | torch.Tensor | wp.array | None = None, + self, + *, + env_ids: Sequence[int] | torch.Tensor | wp.array | None = None, ) -> None: """Write fixed tendon properties into the simulation using indices. diff --git a/source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation_data.py b/source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation_data.py index 21951fe51a28..f9ffbf30c29a 100644 --- a/source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation_data.py +++ b/source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation_data.py @@ -1411,10 +1411,11 @@ def _create_simulation_bindings(self) -> None: if self._root_view.tendon_count > 0: self._sim_bind_fixed_tendon_stiffness = self._root_view.get_attribute( "mujoco.tendon_stiffness", SimulationManager.get_model() - )[:,0] + )[:, 0] self._sim_bind_fixed_tendon_damping = self._root_view.get_attribute( - "mujoco.tendon_damping", SimulationManager.get_model(), - )[:,0] + "mujoco.tendon_damping", + SimulationManager.get_model(), + )[:, 0] else: self._sim_bind_fixed_tendon_stiffness = wp.zeros( (self._num_instances, 0), dtype=wp.float32, device=self.device diff --git a/source/isaaclab_tasks/isaaclab_tasks/direct/shadow_hand/shadow_hand_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/direct/shadow_hand/shadow_hand_env_cfg.py index f3b29296b353..5fa5b920fe91 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/direct/shadow_hand/shadow_hand_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/direct/shadow_hand/shadow_hand_env_cfg.py @@ -149,9 +149,8 @@ class ShadowHandRobotCfg(PresetCfg): newton_mjwarp = ArticulationCfg( prim_path="/World/envs/env_.*/Robot", spawn=sim_utils.UsdFileCfg( - # newton requires implicitactuators be specified in usd and there's a bug with physx tendons - #usd_path=f"{ISAAC_NUCLEUS_DIR}/Robots/ShadowRobot/ShadowHand/shadow_hand_instanceable_newton.usd", - usd_path=f"/home/rgresia/Repositories/mujoco_menagerie/shadow_hand/right_hand.usd/right_shadow_hand.usda", + # newton/mujoco have separate usd schema + usd_path=f"{ISAAC_NUCLEUS_DIR}/Robots/ShadowRobot/ShadowHand/shadow_hand_newton.usd/right_shadow_hand.usda", activate_contact_sensors=False, rigid_props=sim_utils.RigidBodyPropertiesCfg( disable_gravity=True, @@ -159,7 +158,7 @@ class ShadowHandRobotCfg(PresetCfg): max_depenetration_velocity=1000.0, ), articulation_props=sim_utils.ArticulationRootPropertiesCfg(enabled_self_collisions=True), - joint_drive_props=sim_utils.JointDrivePropertiesCfg(drive_type="force"), + joint_drive_props=sim_utils.JointDrivePropertiesCfg(drive_type="force", ensure_drives_exist=True), fixed_tendons_props=sim_utils.FixedTendonPropertiesCfg(damping=0.1), ), init_state=ArticulationCfg.InitialStateCfg( @@ -263,7 +262,7 @@ class ShadowHandSceneCfg(PresetCfg): newton_mjwarp: InteractiveSceneCfg = InteractiveSceneCfg( num_envs=8192, env_spacing=0.75, replicate_physics=True, clone_in_fabric=False ) - default: InteractiveSceneCfg = newton_mjwarp + default: InteractiveSceneCfg = physx @configclass From 2744b3e270f224dc844865e55cfd7310ee4bf95e Mon Sep 17 00:00:00 2001 From: Ryan Gresia Date: Thu, 14 May 2026 15:43:48 -0700 Subject: [PATCH 08/12] tendon log cleanup --- .../isaaclab_newton/assets/articulation/articulation.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation.py b/source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation.py index e162ec286f21..0ac374a45191 100644 --- a/source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation.py +++ b/source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation.py @@ -3877,10 +3877,6 @@ def format_limits(_, v: tuple[float, float]) -> str: # convert table to string logger.info(f"Simulation parameters for joints in {self.cfg.prim_path}:\n" + joint_table.get_string()) - # read out all fixed tendon parameters from simulation - # if self.num_fixed_tendons > 0: - # raise NotImplementedError("Fixed tendons are not supported yet.") - if self.num_spatial_tendons > 0: raise NotImplementedError("Spatial tendons are not supported yet.") From aa87f5f8e5f3782514084b36947a896c9d8590de Mon Sep 17 00:00:00 2001 From: Ryan Gresia Date: Thu, 14 May 2026 16:07:59 -0700 Subject: [PATCH 09/12] updates changelog --- source/isaaclab/changelog.d/passive-tendons.rst | 4 ++++ source/isaaclab_newton/changelog.d/passive-tendons.rst | 3 +++ source/isaaclab_tasks/changelog.d/passive-tendons.rst | 3 +++ 3 files changed, 10 insertions(+) create mode 100644 source/isaaclab/changelog.d/passive-tendons.rst create mode 100644 source/isaaclab_newton/changelog.d/passive-tendons.rst create mode 100644 source/isaaclab_tasks/changelog.d/passive-tendons.rst diff --git a/source/isaaclab/changelog.d/passive-tendons.rst b/source/isaaclab/changelog.d/passive-tendons.rst new file mode 100644 index 000000000000..44860715e2e0 --- /dev/null +++ b/source/isaaclab/changelog.d/passive-tendons.rst @@ -0,0 +1,4 @@ +Added +^^^^^ +* Updates tendon randomization events to support newton tendons +* Adds support to modify MJC usd schema diff --git a/source/isaaclab_newton/changelog.d/passive-tendons.rst b/source/isaaclab_newton/changelog.d/passive-tendons.rst new file mode 100644 index 000000000000..b50bb2527382 --- /dev/null +++ b/source/isaaclab_newton/changelog.d/passive-tendons.rst @@ -0,0 +1,3 @@ +Added +^^^^^ +* Updates articulation to support passive tendons properties diff --git a/source/isaaclab_tasks/changelog.d/passive-tendons.rst b/source/isaaclab_tasks/changelog.d/passive-tendons.rst new file mode 100644 index 000000000000..32a7b6998ca1 --- /dev/null +++ b/source/isaaclab_tasks/changelog.d/passive-tendons.rst @@ -0,0 +1,3 @@ +Added +^^^^^ +* Updates shadow hand newton scene to use MJCF version of the hand From 5b2f87365ddfbede083a77df2b146ad1c826da97 Mon Sep 17 00:00:00 2001 From: Ryan Gresia Date: Fri, 15 May 2026 14:01:56 -0700 Subject: [PATCH 10/12] updates backend handling --- source/isaaclab/isaaclab/envs/mdp/events.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/source/isaaclab/isaaclab/envs/mdp/events.py b/source/isaaclab/isaaclab/envs/mdp/events.py index df675bf7032b..b0567320d5b7 100644 --- a/source/isaaclab/isaaclab/envs/mdp/events.py +++ b/source/isaaclab/isaaclab/envs/mdp/events.py @@ -1472,12 +1472,6 @@ def __init__(self, cfg: EventTermCfg, env: ManagerBasedEnv): f" '{cfg.params['operation']}'." ) - manager_name = env.sim.physics_manager.__name__.lower() - if "newton" in manager_name: - self.newton = True - else: - self.newton = False - def __call__( self, env: ManagerBasedEnv, @@ -1493,6 +1487,8 @@ def __call__( operation: Literal["add", "scale", "abs"] = "abs", distribution: Literal["uniform", "log_uniform", "gaussian"] = "uniform", ): + _backend = env.sim.physics_manager.__name__.lower() + # resolve environment ids if env_ids is None: env_ids = torch.arange(env.scene.num_envs, device=self.asset.device) @@ -1534,7 +1530,7 @@ def __call__( # limit stiffness if limit_stiffness_distribution_params is not None: - if not self.newton: + if _backend == "physx": limit_stiffness = _randomize_prop_by_op( self.asset.data.fixed_tendon_limit_stiffness.torch.clone(), limit_stiffness_distribution_params, @@ -1551,7 +1547,7 @@ def __call__( # position limits if lower_limit_distribution_params is not None or upper_limit_distribution_params is not None: - if not self.newton: + if _backend == "physx": limit = self.asset.data.fixed_tendon_pos_limits.torch.clone() # -- lower limit if lower_limit_distribution_params is not None: @@ -1589,7 +1585,7 @@ def __call__( # rest length if rest_length_distribution_params is not None: - if not self.newton: + if _backend == "physx": rest_length = _randomize_prop_by_op( self.asset.data.fixed_tendon_rest_length.torch.clone(), rest_length_distribution_params, @@ -1605,7 +1601,7 @@ def __call__( raise NotImplementedError("Rest length is not yet implemented with Newton.") # offset if offset_distribution_params is not None: - if not self.newton: + if _backend == "physx": offset = _randomize_prop_by_op( self.asset.data.fixed_tendon_offset.torch.clone(), offset_distribution_params, From 4b50d5f96aa45625f07fd1eecc8c8e8da89bd566 Mon Sep 17 00:00:00 2001 From: Ryan Gresia Date: Fri, 15 May 2026 15:53:03 -0700 Subject: [PATCH 11/12] adds tendon count --- .../test/mock_interfaces/views/mock_articulation_view.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/source/isaaclab_newton/isaaclab_newton/test/mock_interfaces/views/mock_articulation_view.py b/source/isaaclab_newton/isaaclab_newton/test/mock_interfaces/views/mock_articulation_view.py index 26761937667e..59f19a19fc8d 100644 --- a/source/isaaclab_newton/isaaclab_newton/test/mock_interfaces/views/mock_articulation_view.py +++ b/source/isaaclab_newton/isaaclab_newton/test/mock_interfaces/views/mock_articulation_view.py @@ -199,6 +199,7 @@ def __init__( num_instances: int = 1, num_bodies: int = 2, num_joints: int = 1, + num_tendons: int = 0, device: str = "cpu", is_fixed_base: bool = False, joint_names: list[str] | None = None, @@ -218,6 +219,7 @@ def __init__( self._count = num_instances self._link_count = num_bodies self._joint_dof_count = num_joints + self._tendon_count = num_tendons self._device = device self._is_fixed_base = is_fixed_base self._noop_setters = False @@ -293,6 +295,10 @@ def link_names(self) -> list[str]: """Alias for body_names (Newton calls bodies 'links').""" return self._body_names + @property + def tendon_count(self): + return self._tendon_count + @property def articulation_ids(self) -> wp.array: """Mapping from ``(world, arti)`` to model articulation index. Shape ``(N, 1)`` dtype=int.""" From fdf21975a31a3f3ee0adedde822b20622a7b2d85 Mon Sep 17 00:00:00 2001 From: Ryan Gresia Date: Mon, 18 May 2026 14:35:45 -0700 Subject: [PATCH 12/12] updates shadowhand newton path --- .../isaaclab_tasks/direct/shadow_hand/shadow_hand_env_cfg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/isaaclab_tasks/isaaclab_tasks/direct/shadow_hand/shadow_hand_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/direct/shadow_hand/shadow_hand_env_cfg.py index 23c7a5a5f798..46442b8c4397 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/direct/shadow_hand/shadow_hand_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/direct/shadow_hand/shadow_hand_env_cfg.py @@ -150,7 +150,7 @@ class ShadowHandRobotCfg(PresetCfg): prim_path="/World/envs/env_.*/Robot", spawn=sim_utils.UsdFileCfg( # newton/mujoco have separate usd schema - usd_path=f"{ISAAC_NUCLEUS_DIR}/Robots/ShadowRobot/ShadowHand/shadow_hand_newton.usd/right_shadow_hand.usda", + usd_path=f"{ISAAC_NUCLEUS_DIR}/Robots/ShadowRobot/ShadowHandNewton/shadow_hand_instanceable.usda", activate_contact_sensors=False, rigid_props=sim_utils.RigidBodyPropertiesCfg( disable_gravity=True,