Unverified Commit 87130f23 authored by Ossama Ahmed's avatar Ossama Ahmed Committed by GitHub

Updates Joint Friction Parameters to Isaac Sim 5.0 PhysX APIs (#557)

# Description

Updated Joint Friction Parameters to Isaac Sim 5.0/ PhysX APIs and
exposed joint's viscous_frictions and dynamic_frictions.


## Checklist

- [X] I have run the [`pre-commit` checks](https://pre-commit.com/) with
`./isaaclab.sh --format`
- [X] I have made corresponding changes to the documentation
- [X] My changes generate no new warnings
- [X] I have added tests that prove my fix is effective or that my
feature works
- [X] I have updated the changelog and the corresponding version in the
extension's `config/extension.toml` file
- [X] I have added my name to the `CONTRIBUTORS.md` or my name already
exists there
parent 2084935e
......@@ -86,7 +86,13 @@ class ActuatorBase(ABC):
"""The armature of the actuator joints. Shape is (num_envs, num_joints)."""
friction: torch.Tensor
"""The joint friction of the actuator joints. Shape is (num_envs, num_joints)."""
"""The joint static friction of the actuator joints. Shape is (num_envs, num_joints)."""
dynamic_friction: torch.Tensor
"""The joint dynamic friction of the actuator joints. Shape is (num_envs, num_joints)."""
viscous_friction: torch.Tensor
"""The joint viscous friction of the actuator joints. Shape is (num_envs, num_joints)."""
_DEFAULT_MAX_EFFORT_SIM: ClassVar[float] = 1.0e9
"""The default maximum effort for the actuator joints in the simulation. Defaults to 1.0e9.
......@@ -106,6 +112,8 @@ class ActuatorBase(ABC):
damping: torch.Tensor | float = 0.0,
armature: torch.Tensor | float = 0.0,
friction: torch.Tensor | float = 0.0,
dynamic_friction: torch.Tensor | float = 0.0,
viscous_friction: torch.Tensor | float = 0.0,
effort_limit: torch.Tensor | float = torch.inf,
velocity_limit: torch.Tensor | float = torch.inf,
):
......@@ -131,7 +139,11 @@ class ActuatorBase(ABC):
If a tensor, then the shape is (num_envs, num_joints).
armature: The default joint armature. Defaults to 0.0.
If a tensor, then the shape is (num_envs, num_joints).
friction: The default joint friction. Defaults to 0.0.
friction: The default joint static friction. Defaults to 0.0.
If a tensor, then the shape is (num_envs, num_joints).
dynamic_friction: The default joint dynamic friction. Defaults to 0.0.
If a tensor, then the shape is (num_envs, num_joints).
viscous_friction: The default joint viscous friction. Defaults to 0.0.
If a tensor, then the shape is (num_envs, num_joints).
effort_limit: The default effort limit. Defaults to infinity.
If a tensor, then the shape is (num_envs, num_joints).
......@@ -156,6 +168,8 @@ class ActuatorBase(ABC):
# parse joint armature and friction
self.armature = self._parse_joint_parameter(self.cfg.armature, armature)
self.friction = self._parse_joint_parameter(self.cfg.friction, friction)
self.dynamic_friction = self._parse_joint_parameter(self.cfg.dynamic_friction, dynamic_friction)
self.viscous_friction = self._parse_joint_parameter(self.cfg.viscous_friction, viscous_friction)
# parse joint limits
# -- velocity
self.velocity_limit_sim = self._parse_joint_parameter(self.cfg.velocity_limit_sim, velocity_limit)
......
......@@ -141,18 +141,26 @@ class ActuatorBaseCfg:
"""
friction: dict[str, float] | float | None = None
r"""The friction coefficient of the joints in the group. Defaults to None.
r"""The static friction coefficient of the joints in the group. Defaults to None.
The joint friction is a unitless quantity. It relates the magnitude of the spatial force transmitted
from the parent body to the child body to the maximal friction force that may be applied by the solver
The joint static friction is a unitless quantity. It relates the magnitude of the spatial force transmitted
from the parent body to the child body to the maximal static friction force that may be applied by the solver
to resist the joint motion.
Mathematically, this means that: :math:`F_{resist} \leq \mu F_{spatial}`, where :math:`F_{resist}`
is the resisting force applied by the solver and :math:`F_{spatial}` is the spatial force
transmitted from the parent body to the child body. The simulated friction effect is therefore
similar to static and Coulomb friction.
transmitted from the parent body to the child body. The simulated static friction effect is therefore
similar to static and Coulomb static friction.
If None, the joint friction is set to the value from the USD joint prim.
If None, the joint static friction is set to the value from the USD joint prim.
"""
dynamic_friction: dict[str, float] | float | None = None
"""The dynamic friction coefficient of the joints in the group. Defaults to None.
"""
viscous_friction: dict[str, float] | float | None = None
"""The viscous friction coefficient of the joints in the group. Defaults to None.
"""
......
......@@ -379,6 +379,8 @@ class RemotizedPDActuator(DelayedPDActuator):
damping: torch.Tensor | float = 0.0,
armature: torch.Tensor | float = 0.0,
friction: torch.Tensor | float = 0.0,
dynamic_friction: torch.Tensor | float = 0.0,
viscous_friction: torch.Tensor | float = 0.0,
effort_limit: torch.Tensor | float = torch.inf,
velocity_limit: torch.Tensor | float = torch.inf,
):
......@@ -387,7 +389,19 @@ class RemotizedPDActuator(DelayedPDActuator):
cfg.velocity_limit = torch.inf
# call the base method and set default effort_limit and velocity_limit to inf
super().__init__(
cfg, joint_names, joint_ids, num_envs, device, stiffness, damping, armature, friction, torch.inf, torch.inf
cfg,
joint_names,
joint_ids,
num_envs,
device,
stiffness,
damping,
armature,
friction,
dynamic_friction,
viscous_friction,
effort_limit,
velocity_limit,
)
self._joint_parameter_lookup = torch.tensor(cfg.joint_parameter_lookup, device=device)
# define remotized joint torque limit
......
......@@ -830,19 +830,19 @@ class Articulation(AssetBase):
joint_ids: Sequence[int] | slice | None = None,
env_ids: Sequence[int] | None = None,
):
r"""Write joint friction coefficients into the simulation.
r"""Write joint static friction coefficients into the simulation.
The joint friction is a unitless quantity. It relates the magnitude of the spatial force transmitted
from the parent body to the child body to the maximal friction force that may be applied by the solver
The joint static friction is a unitless quantity. It relates the magnitude of the spatial force transmitted
from the parent body to the child body to the maximal static friction force that may be applied by the solver
to resist the joint motion.
Mathematically, this means that: :math:`F_{resist} \leq \mu F_{spatial}`, where :math:`F_{resist}`
is the resisting force applied by the solver and :math:`F_{spatial}` is the spatial force
transmitted from the parent body to the child body. The simulated friction effect is therefore
similar to static and Coulomb friction.
transmitted from the parent body to the child body. The simulated static friction effect is therefore
similar to static and Coulomb static friction.
Args:
joint_friction: Joint friction. Shape is (len(env_ids), len(joint_ids)).
joint_friction_coeff: Joint static friction coefficient. Shape is (len(env_ids), len(joint_ids)).
joint_ids: The joint indices to set the joint torque limits for. Defaults to None (all joints).
env_ids: The environment indices to set the joint torque limits for. Defaults to None (all environments).
"""
......@@ -859,9 +859,63 @@ class Articulation(AssetBase):
# set into internal buffers
self._data.joint_friction_coeff[env_ids, joint_ids] = joint_friction_coeff
# set into simulation
self.root_physx_view.set_dof_friction_coefficients(
self._data.joint_friction_coeff.cpu(), indices=physx_env_ids.cpu()
)
if int(get_version()[2]) < 5:
self.root_physx_view.set_dof_friction_coefficients(
self._data.joint_friction_coeff.cpu(), indices=physx_env_ids.cpu()
)
else:
friction_props = self.root_physx_view.get_dof_friction_properties()
friction_props[physx_env_ids.cpu(), :, 0] = self._data.joint_friction_coeff.cpu()
def write_joint_dynamic_friction_coefficient_to_sim(
self,
joint_dynamic_friction_coeff: torch.Tensor | float,
joint_ids: Sequence[int] | slice | None = None,
env_ids: Sequence[int] | None = None,
):
if int(get_version()[2]) < 5:
omni.log.warn("Setting joint dynamic friction coefficients are not supported in Isaac Sim < 5.0")
return
# resolve indices
physx_env_ids = env_ids
if env_ids is None:
env_ids = slice(None)
physx_env_ids = self._ALL_INDICES
if joint_ids is None:
joint_ids = slice(None)
# broadcast env_ids if needed to allow double indexing
if env_ids != slice(None) and joint_ids != slice(None):
env_ids = env_ids[:, None]
# set into internal buffers
self._data.joint_dynamic_friction_coeff[env_ids, joint_ids] = joint_dynamic_friction_coeff
# set into simulation
friction_props = self.root_physx_view.get_dof_friction_properties()
friction_props[physx_env_ids.cpu(), :, 1] = self._data.joint_dynamic_friction_coeff.cpu()
def write_joint_viscous_friction_coefficient_to_sim(
self,
joint_viscous_friction_coeff: torch.Tensor | float,
joint_ids: Sequence[int] | slice | None = None,
env_ids: Sequence[int] | None = None,
):
if int(get_version()[2]) < 5:
omni.log.warn("Setting joint viscous friction coefficients are not supported in Isaac Sim < 5.0")
return
# resolve indices
physx_env_ids = env_ids
if env_ids is None:
env_ids = slice(None)
physx_env_ids = self._ALL_INDICES
if joint_ids is None:
joint_ids = slice(None)
# broadcast env_ids if needed to allow double indexing
if env_ids != slice(None) and joint_ids != slice(None):
env_ids = env_ids[:, None]
# set into internal buffers
self._data.joint_viscous_friction_coeff[env_ids, joint_ids] = joint_viscous_friction_coeff
# set into simulation
friction_props = self.root_physx_view.get_dof_friction_properties()
friction_props[physx_env_ids.cpu(), :, 2] = self._data.joint_viscous_friction_coeff.cpu()
"""
Operations - Setters.
......@@ -1446,9 +1500,17 @@ class Articulation(AssetBase):
self._data.default_joint_stiffness = self.root_physx_view.get_dof_stiffnesses().to(self.device).clone()
self._data.default_joint_damping = self.root_physx_view.get_dof_dampings().to(self.device).clone()
self._data.default_joint_armature = self.root_physx_view.get_dof_armatures().to(self.device).clone()
self._data.default_joint_friction_coeff = (
self.root_physx_view.get_dof_friction_coefficients().to(self.device).clone()
)
if int(get_version()[2]) < 5:
self._data.default_joint_friction_coeff = (
self.root_physx_view.get_dof_friction_coefficients().to(self.device).clone()
)
self._data.default_joint_dynamic_friction_coeff = None
self._data.default_joint_viscous_friction_coeff = None
else:
friction_props = self.root_physx_view.get_dof_friction_properties()
self._data.default_joint_friction_coeff = friction_props[:, :, 0].to(self.device).clone()
self._data.default_joint_dynamic_friction_coeff = friction_props[:, :, 1].to(self.device).clone()
self._data.default_joint_viscous_friction_coeff = friction_props[:, :, 2].to(self.device).clone()
self._data.joint_pos_limits = self._data.default_joint_pos_limits.clone()
self._data.joint_vel_limits = self.root_physx_view.get_dof_max_velocities().to(self.device).clone()
......@@ -1457,6 +1519,12 @@ class Articulation(AssetBase):
self._data.joint_damping = self._data.default_joint_damping.clone()
self._data.joint_armature = self._data.default_joint_armature.clone()
self._data.joint_friction_coeff = self._data.default_joint_friction_coeff.clone()
if int(get_version()[2]) < 5:
self._data.joint_dynamic_friction_coeff = None
self._data.joint_viscous_friction_coeff = None
else:
self._data.joint_dynamic_friction_coeff = self._data.default_joint_dynamic_friction_coeff.clone()
self._data.joint_viscous_friction_coeff = self._data.default_joint_viscous_friction_coeff.clone()
# -- body properties
self._data.default_mass = self.root_physx_view.get_masses().clone()
......@@ -1577,6 +1645,8 @@ class Articulation(AssetBase):
damping=self._data.default_joint_damping[:, joint_ids],
armature=self._data.default_joint_armature[:, joint_ids],
friction=self._data.default_joint_friction_coeff[:, joint_ids],
dynamic_friction=self._data.default_joint_dynamic_friction_coeff[:, joint_ids],
viscous_friction=self._data.default_joint_viscous_friction_coeff[:, joint_ids],
effort_limit=self._data.joint_effort_limits[:, joint_ids],
velocity_limit=self._data.joint_vel_limits[:, joint_ids],
)
......@@ -1605,6 +1675,13 @@ class Articulation(AssetBase):
self.write_joint_velocity_limit_to_sim(actuator.velocity_limit_sim, joint_ids=actuator.joint_indices)
self.write_joint_armature_to_sim(actuator.armature, joint_ids=actuator.joint_indices)
self.write_joint_friction_coefficient_to_sim(actuator.friction, joint_ids=actuator.joint_indices)
if int(get_version()[2]) >= 5:
self.write_joint_dynamic_friction_coefficient_to_sim(
actuator.dynamic_friction, joint_ids=actuator.joint_indices
)
self.write_joint_viscous_friction_coefficient_to_sim(
actuator.viscous_friction, joint_ids=actuator.joint_indices
)
# Store the configured values from the actuator model
# note: this is the value configured in the actuator model (for implicit and explicit actuators)
......@@ -1612,6 +1689,9 @@ class Articulation(AssetBase):
self._data.default_joint_damping[:, actuator.joint_indices] = actuator.damping
self._data.default_joint_armature[:, actuator.joint_indices] = actuator.armature
self._data.default_joint_friction_coeff[:, actuator.joint_indices] = actuator.friction
if int(get_version()[2]) >= 5:
self._data.default_joint_dynamic_friction_coeff[:, actuator.joint_indices] = actuator.dynamic_friction
self._data.default_joint_viscous_friction_coeff[:, actuator.joint_indices] = actuator.viscous_friction
# perform some sanity checks to ensure actuators are prepared correctly
total_act_joints = sum(actuator.num_joints for actuator in self.actuators.values())
......@@ -1770,7 +1850,13 @@ class Articulation(AssetBase):
dampings = self.root_physx_view.get_dof_dampings()[0].tolist()
# -- properties
armatures = self.root_physx_view.get_dof_armatures()[0].tolist()
frictions = self.root_physx_view.get_dof_friction_coefficients()[0].tolist()
if int(get_version()[2]) < 5:
static_frictions = self.root_physx_view.get_dof_friction_coefficients()[0].tolist()
else:
friction_props = self.root_physx_view.get_dof_friction_properties()
static_frictions = friction_props[:, :, 0][0].tolist()
dynamic_frictions = friction_props[:, :, 1][0].tolist()
viscous_frictions = friction_props[:, :, 2][0].tolist()
# -- limits
position_limits = self.root_physx_view.get_dof_limits()[0].tolist()
velocity_limits = self.root_physx_view.get_dof_max_velocities()[0].tolist()
......@@ -1778,34 +1864,64 @@ class Articulation(AssetBase):
# create table for term information
joint_table = PrettyTable()
joint_table.title = f"Simulation Joint Information (Prim path: {self.cfg.prim_path})"
joint_table.field_names = [
"Index",
"Name",
"Stiffness",
"Damping",
"Armature",
"Friction",
"Position Limits",
"Velocity Limits",
"Effort Limits",
]
if int(get_version()[2]) < 5:
joint_table.field_names = [
"Index",
"Name",
"Stiffness",
"Damping",
"Armature",
"Static Friction",
"Position Limits",
"Velocity Limits",
"Effort Limits",
]
else:
joint_table.field_names = [
"Index",
"Name",
"Stiffness",
"Damping",
"Armature",
"Static Friction",
"Dynamic Friction",
"Viscous Friction",
"Position Limits",
"Velocity Limits",
"Effort Limits",
]
joint_table.float_format = ".3"
joint_table.custom_format["Position Limits"] = lambda f, v: f"[{v[0]:.3f}, {v[1]:.3f}]"
# set alignment of table columns
joint_table.align["Name"] = "l"
# add info on each term
for index, name in enumerate(self.joint_names):
joint_table.add_row([
index,
name,
stiffnesses[index],
dampings[index],
armatures[index],
frictions[index],
position_limits[index],
velocity_limits[index],
effort_limits[index],
])
if int(get_version()[2]) < 5:
joint_table.add_row([
index,
name,
stiffnesses[index],
dampings[index],
armatures[index],
static_frictions[index],
position_limits[index],
velocity_limits[index],
effort_limits[index],
])
else:
joint_table.add_row([
index,
name,
stiffnesses[index],
dampings[index],
armatures[index],
static_frictions[index],
dynamic_frictions[index],
viscous_frictions[index],
position_limits[index],
velocity_limits[index],
effort_limits[index],
])
# convert table to string
omni.log.info(f"Simulation parameters for joints in {self.cfg.prim_path}:\n" + joint_table.get_string())
......
......@@ -190,13 +190,29 @@ class ArticulationData:
"""
default_joint_friction_coeff: torch.Tensor = None
"""Default joint friction coefficient of all joints. Shape is (num_instances, num_joints).
"""Default joint static friction coefficient of all joints. Shape is (num_instances, num_joints).
This quantity is configured through the actuator model's :attr:`isaaclab.actuators.ActuatorBaseCfg.friction`
parameter. If the parameter's value is None, the value parsed from the USD schema, at the time of initialization,
is used.
"""
default_joint_dynamic_friction_coeff: torch.Tensor = None
"""Default joint dynamic friction coefficient of all joints. Shape is (num_instances, num_joints).
This quantity is configured through the actuator model's :attr:`isaaclab.actuators.ActuatorBaseCfg.dynamic_friction`
parameter. If the parameter's value is None, the value parsed from the USD schema, at the time of initialization,
is used.
"""
default_joint_viscous_friction_coeff: torch.Tensor = None
"""Default joint viscous friction coefficient of all joints. Shape is (num_instances, num_joints).
This quantity is configured through the actuator model's :attr:`isaaclab.actuators.ActuatorBaseCfg.viscous_friction`
parameter. If the parameter's value is None, the value parsed from the USD schema, at the time of initialization,
is used.
"""
default_joint_pos_limits: torch.Tensor = None
"""Default joint position limits of all joints. Shape is (num_instances, num_joints, 2).
......@@ -330,7 +346,13 @@ class ArticulationData:
"""Joint armature provided to the simulation. Shape is (num_instances, num_joints)."""
joint_friction_coeff: torch.Tensor = None
"""Joint friction coefficient provided to the simulation. Shape is (num_instances, num_joints)."""
"""Joint static friction coefficient provided to the simulation. Shape is (num_instances, num_joints)."""
joint_dynamic_friction_coeff: torch.Tensor = None
"""Joint dynamic friction coefficient provided to the simulation. Shape is (num_instances, num_joints)."""
joint_viscous_friction_coeff: torch.Tensor = None
"""Joint viscous friction coefficient provided to the simulation. Shape is (num_instances, num_joints)."""
joint_pos_limits: torch.Tensor = None
"""Joint position limits provided to the simulation. Shape is (num_instances, num_joints, 2).
......
......@@ -1899,5 +1899,44 @@ def test_spatial_tendons(sim, num_articulations, device):
articulation.update(sim.cfg.dt)
@pytest.mark.parametrize("add_ground_plane", [True])
@pytest.mark.parametrize("num_articulations", [1, 2])
@pytest.mark.parametrize("device", ["cuda:0", "cpu"])
def test_write_joint_frictions_to_sim(sim, num_articulations, device, add_ground_plane):
"""Test applying of joint position target functions correctly for a robotic arm."""
articulation_cfg = generate_articulation_cfg(articulation_type="panda")
articulation, _ = generate_articulation(
articulation_cfg=articulation_cfg, num_articulations=num_articulations, device=device
)
# Play the simulator
sim.reset()
for _ in range(100):
# perform step
sim.step()
# update buffers
articulation.update(sim.cfg.dt)
# apply action to the articulation
dynamic_friction = torch.rand(num_articulations, articulation.num_joints, device=device)
viscous_friction = torch.rand(num_articulations, articulation.num_joints, device=device)
friction = torch.rand(num_articulations, articulation.num_joints, device=device)
articulation.write_joint_dynamic_friction_coefficient_to_sim(dynamic_friction)
articulation.write_joint_viscous_friction_coefficient_to_sim(viscous_friction)
articulation.write_joint_friction_coefficient_to_sim(friction)
articulation.write_data_to_sim()
for _ in range(100):
# perform step
sim.step()
# update buffers
articulation.update(sim.cfg.dt)
assert torch.allclose(articulation.data.joint_dynamic_friction_coeff, dynamic_friction)
assert torch.allclose(articulation.data.joint_viscous_friction_coeff, viscous_friction)
assert torch.allclose(articulation.data.joint_friction_coeff, friction)
if __name__ == "__main__":
pytest.main([__file__, "-v", "--maxfail=1"])
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment