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/isaaclab/envs/mdp/events.py b/source/isaaclab/isaaclab/envs/mdp/events.py index f6cfddc2a64e..491c8d983950 100644 --- a/source/isaaclab/isaaclab/envs/mdp/events.py +++ b/source/isaaclab/isaaclab/envs/mdp/events.py @@ -1565,6 +1565,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) @@ -1606,80 +1608,91 @@ 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 _backend == "physx": + 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 _backend == "physx": + 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 _backend == "physx": + 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 _backend == "physx": + 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 - ) + 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) diff --git a/source/isaaclab/isaaclab/sim/schemas/schemas.py b/source/isaaclab/isaaclab/sim/schemas/schemas.py index 8bd2c314bf93..23f53b105cb3 100644 --- a/source/isaaclab/isaaclab/sim/schemas/schemas.py +++ b/source/isaaclab/isaaclab/sim/schemas/schemas.py @@ -882,23 +882,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, + tendon_prim, f"mjc:{to_camel_case(attr_name, 'cC')}", value, camel_case=False ) # success return True 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_newton/isaaclab_newton/assets/articulation/articulation.py b/source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation.py index f921001cb8cf..e5b85927d471 100644 --- a/source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation.py +++ b/source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation.py @@ -150,7 +150,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: @@ -187,7 +187,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]: @@ -2768,18 +2768,53 @@ 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) + 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, @@ -2798,16 +2833,21 @@ 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, @@ -2823,18 +2863,54 @@ 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. """ - raise NotImplementedError() + # 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(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, @@ -2853,16 +2929,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, @@ -2925,6 +3006,7 @@ def set_fixed_tendon_position_limit_index( 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. @@ -2933,16 +3015,18 @@ 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() @@ -2963,13 +3047,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() @@ -3100,7 +3183,33 @@ def write_fixed_tendon_properties_to_sim_index( (all fixed tendons). env_ids: Environment indices. If None, then all indices are used. """ - raise NotImplementedError() + # 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]), + 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, @@ -3116,7 +3225,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, @@ -3852,12 +3963,12 @@ def _create_lab_actuator( 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. @@ -4049,10 +4160,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.") 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 2ddb3a13d3e4..69327890a430 100644 --- a/source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation_data.py +++ b/source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation_data.py @@ -480,7 +480,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: @@ -489,7 +489,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: @@ -525,7 +525,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. @@ -1431,8 +1431,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] @@ -1547,6 +1547,23 @@ 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(), + )[:, 0] + 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 + ) + # 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"): @@ -1620,6 +1637,14 @@ 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) + 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) + # Initialize the lazy buffers. # -- link frame w.r.t. world frame self._root_link_vel_w = TimestampedBuffer( @@ -1824,6 +1849,8 @@ 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) # Category 2: TimestampedBuffer properties self._root_link_vel_w_ta = ProxyArray(self._root_link_vel_w.data) 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 a75647624e5b..c2f7bdfe6c8a 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 @@ -201,6 +201,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, @@ -220,6 +221,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 @@ -296,6 +298,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.""" 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 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 bfe7da0aa435..c4d785115555 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: @@ -136,8 +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", + # newton/mujoco have separate usd schema + 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, @@ -145,7 +158,8 @@ 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( pos=(0.0, 0.0, 0.5), @@ -155,7 +169,7 @@ class ShadowHandRobotCfg(PresetCfg): # discards the root body's native USD orientation, so we must re-apply it here as a # spawn rotation. PhysX or USD does not have this issue. Remove once Newton fixes root joint # transform handling in import_usd.py. - rot=(0.0, 0.0, 0.0, 1.0), + rot=(0.0, 0.0, -0.70710678118, 0.70710678118), joint_pos={".*": 0.0}, ), actuators={