Skip to content

Commit 4e3fc52

Browse files
Adding Tendon USD data classes and writers (isaac-sim#6265)
# Description Adds the **tendons** schema-fragment API. - `FixedTendonFragment` / `SpatialTendonFragment` markers + `PhysxFixedTendonCfg` / `PhysxSpatialTendonCfg` in `isaaclab_physx`. These are data carriers that override `func` to the existing multi-instance `modify_fixed_tendon_properties` / `modify_spatial_tendon_properties` writers (tune-not-apply), since tendon attributes live under multi-instance `PhysxTendonAxisRootAPI:*` schemas that the generic `apply_namespaced` writer cannot handle. - The spawner `fixed_tendons_props` / `spatial_tendons_props` slots now also accept fragment lists. This PR is purely **additive** and self-contained: it builds only on the single-namespace schema-fragment base (`SchemaFragment` + `apply_namespaced`) already in `develop`, existing call sites are untouched (a transition bridge routes legacy single cfgs to the existing `define_`/`modify_` writers), and it does **not** depend on any other open PR. ## Type of change - New feature (non-breaking change which adds functionality) ## Screenshots N/A — non-visual API change. ## Checklist - [x] I have read and understood the [contribution guidelines](https://isaac-sim.github.io/IsaacLab/main/source/refs/contributing.html) - [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 added a changelog fragment under `source/<pkg>/changelog.d/` for every touched package (do **not** edit `CHANGELOG.rst` or bump `extension.toml` — CI handles that) - [x] I have added my name to the `CONTRIBUTORS.md` or my name already exists there
1 parent d18c334 commit 4e3fc52

16 files changed

Lines changed: 984 additions & 10 deletions

File tree

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
Added
2+
^^^^^
3+
4+
* Added the tendon schema-fragment markers
5+
:class:`~isaaclab.sim.schemas.FixedTendonFragment` and
6+
:class:`~isaaclab.sim.schemas.SpatialTendonFragment`, which type the spawner
7+
``fixed_tendons_props`` / ``spatial_tendons_props`` slots.
8+
* Added :func:`~isaaclab.sim.schemas.apply_fixed_tendon_properties` and
9+
:func:`~isaaclab.sim.schemas.apply_spatial_tendon_properties`, the family writers that
10+
dispatch a list of tendon fragments via each fragment's ``func``. Tendons are a
11+
*tune-not-apply* family, so the writers tune the existing multi-instance tendon schemas
12+
without applying a new anchor schema.
13+
14+
Changed
15+
^^^^^^^
16+
17+
* Changed the spawner ``fixed_tendons_props`` / ``spatial_tendons_props`` slots
18+
(:attr:`~isaaclab.sim.spawners.from_files.FileCfg.fixed_tendons_props`,
19+
:attr:`~isaaclab.sim.spawners.from_files.FileCfg.spatial_tendons_props`) to also accept one
20+
or more tendon fragments. Legacy single cfgs continue to work through a transition bridge in
21+
the spawn writer.
22+
* :func:`~isaaclab.sim.schemas.apply_fixed_tendon_properties` and
23+
:func:`~isaaclab.sim.schemas.apply_spatial_tendon_properties` now raise
24+
``ValueError`` when the prim at ``prim_path`` does not exist in the stage.
25+
Callers that previously relied on an implicit no-op for invalid paths must
26+
either validate the path beforehand or catch ``ValueError``.
27+
The aggregated return value is now ``False`` whenever any fragment applier
28+
reports failure; callers must not assume the return is always ``True`` even
29+
when the prim is valid.

source/isaaclab/isaaclab/sim/__init__.pyi

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ __all__ = [
4444
"ConvexHullPropertiesCfg",
4545
"DeformableBodyPropertiesBaseCfg",
4646
"DeformableBodyPropertiesCfg",
47+
"FixedTendonFragment",
4748
"FixedTendonPropertiesCfg",
4849
"JointDriveBaseCfg",
4950
"MassCfg",
@@ -64,12 +65,15 @@ __all__ = [
6465
"RigidBodyBaseCfg",
6566
"RigidBodyFragment",
6667
"SchemaFragment",
68+
"SpatialTendonFragment",
6769
"UsdPhysicsCollisionCfg",
6870
"UsdPhysicsRigidBodyCfg",
6971
"apply_collision_properties",
72+
"apply_fixed_tendon_properties",
7073
"apply_mass_properties",
7174
"apply_namespaced",
7275
"apply_rigid_body_properties",
76+
"apply_spatial_tendon_properties",
7377
"SDFMeshPropertiesCfg",
7478
"SpatialTendonPropertiesCfg",
7579
"TriangleMeshPropertiesCfg",
@@ -226,6 +230,7 @@ from .schemas import (
226230
ConvexHullPropertiesCfg,
227231
DeformableBodyPropertiesBaseCfg,
228232
DeformableBodyPropertiesCfg,
233+
FixedTendonFragment,
229234
FixedTendonPropertiesCfg,
230235
JointDriveBaseCfg,
231236
MassCfg,
@@ -238,16 +243,19 @@ from .schemas import (
238243
RigidBodyFragment,
239244
SchemaFragment,
240245
SDFMeshPropertiesCfg,
246+
SpatialTendonFragment,
241247
SpatialTendonPropertiesCfg,
242248
TriangleMeshPropertiesCfg,
243249
TriangleMeshSimplificationPropertiesCfg,
244250
UsdPhysicsCollisionCfg,
245251
UsdPhysicsRigidBodyCfg,
246252
activate_contact_sensors,
247253
apply_collision_properties,
254+
apply_fixed_tendon_properties,
248255
apply_mass_properties,
249256
apply_namespaced,
250257
apply_rigid_body_properties,
258+
apply_spatial_tendon_properties,
251259
define_articulation_root_properties,
252260
define_collision_properties,
253261
define_deformable_body_properties,

source/isaaclab/isaaclab/sim/schemas/__init__.pyi

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@ __all__ = [
99
"USD_MESH_COLLISION_CFGS",
1010
"activate_contact_sensors",
1111
"apply_collision_properties",
12+
"apply_fixed_tendon_properties",
1213
"apply_mass_properties",
1314
"apply_namespaced",
1415
"apply_rigid_body_properties",
16+
"apply_spatial_tendon_properties",
1517
"define_actuator_properties",
1618
"define_articulation_root_properties",
1719
"define_collision_properties",
@@ -35,13 +37,15 @@ __all__ = [
3537
"CollisionFragment",
3638
"DeformableBodyPropertiesBaseCfg",
3739
"DeformableBodyPropertiesCfg",
40+
"FixedTendonFragment",
3841
"JointDriveBaseCfg",
3942
"MassCfg",
4043
"MassFragment",
4144
"MassPropertiesCfg",
4245
"MeshCollisionBaseCfg",
4346
"RigidBodyFragment",
4447
"SchemaFragment",
48+
"SpatialTendonFragment",
4549
"UsdPhysicsCollisionCfg",
4650
"UsdPhysicsRigidBodyCfg",
4751
"MujocoJointDrivePropertiesCfg",
@@ -62,9 +66,11 @@ from .schemas import (
6266
USD_MESH_COLLISION_CFGS,
6367
activate_contact_sensors,
6468
apply_collision_properties,
69+
apply_fixed_tendon_properties,
6570
apply_mass_properties,
6671
apply_namespaced,
6772
apply_rigid_body_properties,
73+
apply_spatial_tendon_properties,
6874
define_articulation_root_properties,
6975
define_collision_properties,
7076
define_deformable_body_properties,
@@ -92,6 +98,7 @@ from .schemas_cfg import (
9298
CollisionFragment,
9399
DeformableBodyPropertiesBaseCfg,
94100
DeformableBodyPropertiesCfg,
101+
FixedTendonFragment,
95102
JointDriveBaseCfg,
96103
MassCfg,
97104
MassFragment,
@@ -100,6 +107,7 @@ from .schemas_cfg import (
100107
RigidBodyBaseCfg,
101108
RigidBodyFragment,
102109
SchemaFragment,
110+
SpatialTendonFragment,
103111
UsdPhysicsCollisionCfg,
104112
UsdPhysicsRigidBodyCfg,
105113
)

source/isaaclab/isaaclab/sim/schemas/schemas.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -990,6 +990,48 @@ def modify_joint_drive_properties(
990990
"""
991991

992992

993+
def apply_fixed_tendon_properties(
994+
prim_path: str, fragments: Iterable[schemas_cfg.FixedTendonFragment], stage: Usd.Stage | None = None
995+
) -> bool:
996+
"""Apply a list of fixed-tendon fragments to a prim.
997+
998+
Fixed tendons are a *tune-not-apply* family: the applied ``PhysxTendonAxisRootAPI``
999+
multi-instance schemas already exist on the prim (authored in the source asset). This writer
1000+
therefore applies no anchor schema; it only dispatches each fragment via its
1001+
:attr:`~isaaclab.sim.schemas.SchemaFragment.func`, which tunes the existing instances.
1002+
Backend fragments carry backend-specific funcs, so core never imports a backend.
1003+
1004+
Each fragment tunes only its own schema and returns ``False`` when that schema is not
1005+
present on the prim. A prim carries a single tendon backend, so compose backends across
1006+
prims rather than mixing PhysX and Mujoco fragments in one list on one prim.
1007+
1008+
Args:
1009+
prim_path: The prim path to apply the fixed-tendon schemas on.
1010+
fragments: An iterable of :class:`~isaaclab.sim.schemas.FixedTendonFragment` instances.
1011+
stage: The stage where to find the prim. Defaults to None, in which case the current
1012+
stage is used.
1013+
1014+
Returns:
1015+
True if all fragments applied successfully, False if any fragment reported failure.
1016+
1017+
Raises:
1018+
ValueError: If the prim at ``prim_path`` is not valid.
1019+
"""
1020+
if stage is None:
1021+
stage = get_current_stage()
1022+
prim = stage.GetPrimAtPath(prim_path)
1023+
# fail loudly on an invalid path (matches the sibling apply_* writers)
1024+
if not prim.IsValid():
1025+
raise ValueError(f"Prim path '{prim_path}' is not valid.")
1026+
# tune-not-apply: the PhysxTendonAxisRootAPI instances already exist; apply no anchor.
1027+
# aggregate per-fragment results so a reported failure is not silently masked.
1028+
success = True
1029+
for cfg in fragments:
1030+
func = cfg.func if callable(cfg.func) else string_to_callable(cfg.func)
1031+
success = bool(func(cfg, prim_path, stage)) and success
1032+
return success
1033+
1034+
9931035
@apply_nested
9941036
def modify_fixed_tendon_properties(
9951037
prim_path: str, cfg: schemas_cfg.PhysxFixedTendonPropertiesCfg, stage: Usd.Stage | None = None
@@ -1021,6 +1063,9 @@ def modify_fixed_tendon_properties(
10211063
Raises:
10221064
ValueError: If the input prim path is not valid.
10231065
"""
1066+
# Retained for backward compatibility with callers passing PhysxFixedTendonPropertiesCfg
1067+
# directly. Will be removed in a future release once callers adopt the fragment-based
1068+
# apply_fixed_tendon_properties path.
10241069
# get stage handle
10251070
if stage is None:
10261071
stage = get_current_stage()
@@ -1048,6 +1093,7 @@ def modify_fixed_tendon_properties(
10481093
camel_case=False,
10491094
)
10501095
else:
1096+
# NOTE: ``mjc:*`` branch (``MjcTendon`` prim) kept inline; future split candidate into isaaclab_newton.
10511097
# only stiffness and damping in the cfg map to mjc attributes
10521098
for attr_name, value in cfg.items():
10531099
safe_set_attribute_on_usd_prim(
@@ -1062,6 +1108,50 @@ def modify_fixed_tendon_properties(
10621108
"""
10631109

10641110

1111+
def apply_spatial_tendon_properties(
1112+
prim_path: str, fragments: Iterable[schemas_cfg.SpatialTendonFragment], stage: Usd.Stage | None = None
1113+
) -> bool:
1114+
"""Apply a list of spatial-tendon fragments to a prim.
1115+
1116+
Spatial tendons are a *tune-not-apply* family: the applied
1117+
``PhysxTendonAttachmentRootAPI`` / ``PhysxTendonAttachmentLeafAPI`` multi-instance schemas
1118+
already exist on the prim (authored in the source asset). This writer therefore applies no
1119+
anchor schema; it only dispatches each fragment via its
1120+
:attr:`~isaaclab.sim.schemas.SchemaFragment.func`, which tunes the existing instances.
1121+
Backend fragments carry backend-specific funcs, so core never imports a backend.
1122+
1123+
Each fragment tunes only its own schema and returns ``False`` when that schema is not
1124+
present on the prim. A prim carries a single tendon backend, so compose backends across
1125+
prims rather than mixing PhysX and Mujoco fragments in one list on one prim.
1126+
1127+
Args:
1128+
prim_path: The prim path to apply the spatial-tendon schemas on.
1129+
fragments: An iterable of :class:`~isaaclab.sim.schemas.SpatialTendonFragment` instances.
1130+
stage: The stage where to find the prim. Defaults to None, in which case the current
1131+
stage is used.
1132+
1133+
Returns:
1134+
True if all fragments applied successfully, False if any fragment reported failure.
1135+
1136+
Raises:
1137+
ValueError: If the prim at ``prim_path`` is not valid.
1138+
"""
1139+
if stage is None:
1140+
stage = get_current_stage()
1141+
prim = stage.GetPrimAtPath(prim_path)
1142+
# fail loudly on an invalid path (matches the sibling apply_* writers)
1143+
if not prim.IsValid():
1144+
raise ValueError(f"Prim path '{prim_path}' is not valid.")
1145+
# tune-not-apply: the PhysxTendonAttachmentRootAPI / PhysxTendonAttachmentLeafAPI instances
1146+
# already exist; apply no anchor.
1147+
# aggregate per-fragment results so a reported failure is not silently masked.
1148+
success = True
1149+
for cfg in fragments:
1150+
func = cfg.func if callable(cfg.func) else string_to_callable(cfg.func)
1151+
success = bool(func(cfg, prim_path, stage)) and success
1152+
return success
1153+
1154+
10651155
@apply_nested
10661156
def modify_spatial_tendon_properties(
10671157
prim_path: str, cfg: schemas_cfg.PhysxSpatialTendonPropertiesCfg, stage: Usd.Stage | None = None
@@ -1095,6 +1185,9 @@ def modify_spatial_tendon_properties(
10951185
Raises:
10961186
ValueError: If the input prim path is not valid.
10971187
"""
1188+
# Retained for backward compatibility with callers passing PhysxSpatialTendonPropertiesCfg
1189+
# directly. Will be removed in a future release once callers adopt the fragment-based
1190+
# apply_spatial_tendon_properties path.
10981191
# obtain stage
10991192
if stage is None:
11001193
stage = get_current_stage()

source/isaaclab/isaaclab/sim/schemas/schemas_cfg.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,35 @@ class CollisionFragment(SchemaFragment):
183183
pass
184184

185185

186+
@configclass
187+
class FixedTendonFragment(SchemaFragment):
188+
"""Marker base for fixed-tendon fragments; types the ``fixed_tendons_props`` slot.
189+
190+
Fixed tendons are a *tune-not-apply* family: the applied ``PhysxTendonAxisRootAPI``
191+
multi-instance schemas already exist on the prim (authored in the source asset), so the
192+
family writer (:func:`~isaaclab.sim.schemas.apply_fixed_tendon_properties`) does not apply
193+
any anchor schema; it only tunes the existing instances via each fragment's
194+
:attr:`~isaaclab.sim.schemas.SchemaFragment.func`.
195+
"""
196+
197+
pass
198+
199+
200+
@configclass
201+
class SpatialTendonFragment(SchemaFragment):
202+
"""Marker base for spatial-tendon fragments; types the ``spatial_tendons_props`` slot.
203+
204+
Spatial tendons are a *tune-not-apply* family: the applied
205+
``PhysxTendonAttachmentRootAPI`` / ``PhysxTendonAttachmentLeafAPI`` multi-instance schemas
206+
already exist on the prim (authored in the source asset), so the family writer
207+
(:func:`~isaaclab.sim.schemas.apply_spatial_tendon_properties`) does not apply any anchor
208+
schema; it only tunes the existing instances via each fragment's
209+
:attr:`~isaaclab.sim.schemas.SchemaFragment.func`.
210+
"""
211+
212+
pass
213+
214+
186215
@configclass
187216
class UsdPhysicsCollisionCfg(CollisionFragment):
188217
"""``physics:*`` collision attributes from `UsdPhysics.CollisionAPI`_.

source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -372,9 +372,33 @@ def _spawn_from_usd_file(
372372
schemas.modify_articulation_root_properties(prim_path, cfg.articulation_props)
373373
# modify tendon properties
374374
if cfg.fixed_tendons_props is not None:
375-
schemas.modify_fixed_tendon_properties(prim_path, cfg.fixed_tendons_props)
375+
# transition shim, remove later: fragment(s) -> apply_*; legacy cfg -> modify_*
376+
# normalize a single fragment to a list so the convenience form (and an empty list) route like a list
377+
fixed_tendon_frags = (
378+
[cfg.fixed_tendons_props]
379+
if isinstance(cfg.fixed_tendons_props, schemas.SchemaFragment)
380+
else cfg.fixed_tendons_props
381+
)
382+
if isinstance(fixed_tendon_frags, (list, tuple)) and all(
383+
isinstance(f, schemas.SchemaFragment) for f in fixed_tendon_frags
384+
):
385+
schemas.apply_fixed_tendon_properties(prim_path, fixed_tendon_frags)
386+
else:
387+
schemas.modify_fixed_tendon_properties(prim_path, cfg.fixed_tendons_props)
376388
if cfg.spatial_tendons_props is not None:
377-
schemas.modify_spatial_tendon_properties(prim_path, cfg.spatial_tendons_props)
389+
# transition shim, remove later: fragment(s) -> apply_*; legacy cfg -> modify_*
390+
# normalize a single fragment to a list so the convenience form (and an empty list) route like a list
391+
spatial_tendon_frags = (
392+
[cfg.spatial_tendons_props]
393+
if isinstance(cfg.spatial_tendons_props, schemas.SchemaFragment)
394+
else cfg.spatial_tendons_props
395+
)
396+
if isinstance(spatial_tendon_frags, (list, tuple)) and all(
397+
isinstance(f, schemas.SchemaFragment) for f in spatial_tendon_frags
398+
):
399+
schemas.apply_spatial_tendon_properties(prim_path, spatial_tendon_frags)
400+
else:
401+
schemas.modify_spatial_tendon_properties(prim_path, cfg.spatial_tendons_props)
378402
# define drive API on the joints
379403
# note: these are only for setting low-level simulation properties. all others should be set or are
380404
# and overridden by the articulation/actuator properties.

source/isaaclab/isaaclab/sim/spawners/from_files/from_files_cfg.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,23 @@ class FileCfg(RigidObjectSpawnerCfg, DeformableObjectSpawnerCfg):
3737
articulation_props: schemas.ArticulationRootPropertiesCfg | None = None
3838
"""Properties to apply to the articulation root."""
3939

40-
fixed_tendons_props: schemas.FixedTendonPropertiesCfg | None = None
41-
"""Properties to apply to the fixed tendons (if any)."""
40+
fixed_tendons_props: (
41+
schemas.FixedTendonPropertiesCfg | schemas.FixedTendonFragment | list[schemas.FixedTendonFragment] | None
42+
) = None
43+
"""Properties to apply to the fixed tendons (if any).
4244
43-
spatial_tendons_props: schemas.SpatialTendonPropertiesCfg | None = None
44-
"""Properties to apply to the spatial tendons (if any)."""
45+
Accepts either the legacy :class:`~isaaclab_physx.sim.schemas.PhysxFixedTendonPropertiesCfg`
46+
or one or more :class:`~isaaclab.sim.schemas.FixedTendonFragment` instances.
47+
"""
48+
49+
spatial_tendons_props: (
50+
schemas.SpatialTendonPropertiesCfg | schemas.SpatialTendonFragment | list[schemas.SpatialTendonFragment] | None
51+
) = None
52+
"""Properties to apply to the spatial tendons (if any).
53+
54+
Accepts either the legacy :class:`~isaaclab_physx.sim.schemas.PhysxSpatialTendonPropertiesCfg`
55+
or one or more :class:`~isaaclab.sim.schemas.SpatialTendonFragment` instances.
56+
"""
4557

4658
joint_drive_props: schemas.JointDriveBaseCfg | None = None
4759
"""Properties to apply to a joint.

0 commit comments

Comments
 (0)