@@ -1629,18 +1629,49 @@ def modify_joint_drive_properties(
16291629"""
16301630
16311631
1632- def _is_fixed_tendon_target (prim : Usd .Prim ) -> bool :
1633- """Whether a prim carries a fixed-tendon representation (PhysX multi-apply instance or MjcTendon prim)."""
1634- if prim .GetTypeName () == "MjcTendon" :
1635- return True
1636- return any ("PhysxTendonAxisRootAPI" in s for s in prim .GetAppliedSchemas ())
1632+ _FIXED_TENDON_SCHEMAS = ("PhysxTendonAxisRootAPI" , "PhysxTendonAxisAPI" )
1633+ _SPATIAL_TENDON_SCHEMAS = ("PhysxTendonAttachmentRootAPI" ,)
1634+
16371635
1636+ def _write_tendon_properties (prim , values , schema_type ):
1637+ authored = False
1638+ for schema in prim .GetAppliedSchemas ():
1639+ applied_type , instance = Usd .SchemaRegistry .GetTypeNameAndInstance (str (schema ))
1640+ if applied_type != schema_type or not instance :
1641+ continue
1642+ authored = True
1643+ for name , value in values .items ():
1644+ attribute = f"physxTendon:{ instance } :{ to_camel_case (name , 'cC' )} "
1645+ safe_set_attribute_on_usd_prim (prim , attribute , value , camel_case = False )
1646+ return authored
16381647
1639- def _is_spatial_tendon_target (prim : Usd .Prim ) -> bool :
1640- """Whether a prim carries a spatial-tendon multi-apply instance."""
1641- return any (
1642- "PhysxTendonAttachmentRootAPI" in s or "PhysxTendonAttachmentLeafAPI" in s for s in prim .GetAppliedSchemas ()
1648+
1649+ def _apply_tendon_fragments (prim_path_expr , fragments , schema_types , prim_types , family , stage ):
1650+ fragments = list (fragments )
1651+ if stage is None :
1652+ stage = get_current_stage ()
1653+ if not fragments :
1654+ return True
1655+ targets , _ , any_skipped = _match_fragment_targets (
1656+ prim_path_expr ,
1657+ lambda prim : prim .GetTypeName () in prim_types
1658+ or any (
1659+ Usd .SchemaRegistry .GetTypeNameAndInstance (str (schema ))[0 ] in schema_types
1660+ for schema in prim .GetAppliedSchemas ()
1661+ ),
1662+ stage ,
16431663 )
1664+ if not targets :
1665+ logger .warning ("No %s-tendon targets matched expression '%s'; nothing was authored." , family , prim_path_expr )
1666+ return False
1667+ success = not any_skipped
1668+ for cfg in fragments :
1669+ func = cfg .func if callable (cfg .func ) else string_to_callable (cfg .func )
1670+ fragment_hit = False
1671+ for target in targets :
1672+ fragment_hit |= bool (func (cfg , target .GetPath ().pathString , stage ))
1673+ success = fragment_hit and success
1674+ return success
16441675
16451676
16461677def apply_fixed_tendon_properties (
@@ -1651,8 +1682,8 @@ def apply_fixed_tendon_properties(
16511682 The prims to author on are matched with :func:`~isaaclab.sim.utils.find_matching_prims`:
16521683 ``prim_path_expr`` is a plain regular expression over whole prim paths, so ``[^/]+``
16531684 selects one path segment and ``/World/Robot/.*`` every descendant of a prim. A matched prim is a
1654- fixed-tendon target when it carries an applied ``PhysxTendonAxisRootAPI`` multi-apply
1655- instance or is a ``MjcTendon`` prim.
1685+ fixed-tendon target when it carries an applied ``PhysxTendonAxisRootAPI`` or
1686+ ``PhysxTendonAxisAPI`` instance, or is a ``MjcTendon`` prim.
16561687
16571688 Fixed tendons are a *tune-not-apply* family: the tendon topology is authored in the source
16581689 asset, so this writer never creates instances -- it only dispatches each fragment via its
@@ -1675,26 +1706,7 @@ def apply_fixed_tendon_properties(
16751706 Returns:
16761707 True if every fragment tuned at least one target and no instanced prim was skipped.
16771708 """
1678- fragments = list (fragments )
1679- if stage is None :
1680- stage = get_current_stage ()
1681- if not fragments :
1682- return True
1683- targets , _ , any_skipped = _match_fragment_targets (prim_path_expr , _is_fixed_tendon_target , stage )
1684- if not targets :
1685- logger .warning ("No fixed-tendon targets matched expression '%s'; nothing was authored." , prim_path_expr )
1686- return False
1687- # per-fragment any-target aggregation: a fragment fails only when it tuned no target at all,
1688- # since each backend's func legitimately no-ops on the other backend's tendon prims.
1689- success = not any_skipped
1690- for cfg in fragments :
1691- func = cfg .func if callable (cfg .func ) else string_to_callable (cfg .func )
1692- fragment_hit = False
1693- for target in targets :
1694- if func (cfg , target .GetPath ().pathString , stage ):
1695- fragment_hit = True
1696- success = fragment_hit and success
1697- return success
1709+ return _apply_tendon_fragments (prim_path_expr , fragments , _FIXED_TENDON_SCHEMAS , ("MjcTendon" ,), "fixed" , stage )
16981710
16991711
17001712@apply_nested
@@ -1735,36 +1747,12 @@ def modify_fixed_tendon_properties(
17351747 if stage is None :
17361748 stage = get_current_stage ()
17371749
1738- # get USD prim
17391750 tendon_prim = stage .GetPrimAtPath (prim_path )
1740- # check if prim has fixed tendon applied on it or if the mjc tendon prim exiss
1741- applied_schemas = tendon_prim .GetAppliedSchemas ()
1742- prim_type = tendon_prim .GetTypeName ()
1743- if not any ("PhysxTendonAxisRootAPI" in s for s in applied_schemas ) and prim_type != "MjcTendon" :
1744- return False
1745-
1746- # resolve all available instances of the schema since it is multi-instance
1747- cfg = cfg .to_dict ()
1748- if prim_type != "MjcTendon" :
1749- for schema_name in applied_schemas :
1750- if "PhysxTendonAxisRootAPI" not in schema_name :
1751- continue
1752- # set into PhysX API by attribute prefix schema_name: (e.g. PhysxTendonAxisRootAPI:default:stiffness)
1753- for attr_name , value in cfg .items ():
1754- safe_set_attribute_on_usd_prim (
1755- tendon_prim ,
1756- f"{ schema_name } :{ to_camel_case (attr_name , 'cC' )} " ,
1757- value ,
1758- camel_case = False ,
1759- )
1760- else :
1761- # NOTE: ``mjc:*`` branch (``MjcTendon`` prim) kept inline; future split candidate into isaaclab_newton.
1762- # only stiffness and damping in the cfg map to mjc attributes
1763- for attr_name , value in cfg .items ():
1764- safe_set_attribute_on_usd_prim (
1765- tendon_prim , f"mjc:{ to_camel_case (attr_name , 'cC' )} " , value , camel_case = False
1766- )
1767- # success
1751+ values = cfg .to_dict ()
1752+ if tendon_prim .GetTypeName () != "MjcTendon" :
1753+ return _write_tendon_properties (tendon_prim , values , "PhysxTendonAxisRootAPI" )
1754+ for name in ("stiffness" , "damping" ):
1755+ safe_set_attribute_on_usd_prim (tendon_prim , f"mjc:{ name } " , values .get (name ), camel_case = False )
17681756 return True
17691757
17701758
@@ -1781,8 +1769,7 @@ def apply_spatial_tendon_properties(
17811769 The prims to author on are matched with :func:`~isaaclab.sim.utils.find_matching_prims`:
17821770 ``prim_path_expr`` is a plain regular expression over whole prim paths, so ``[^/]+``
17831771 selects one path segment and ``/World/Robot/.*`` every descendant of a prim. A matched prim is a
1784- spatial-tendon target when it carries an applied ``PhysxTendonAttachmentRootAPI`` or
1785- ``PhysxTendonAttachmentLeafAPI`` multi-apply instance.
1772+ spatial-tendon target when it carries an applied ``PhysxTendonAttachmentRootAPI`` instance.
17861773
17871774 Spatial tendons are a *tune-not-apply* family: the tendon topology is authored in the
17881775 source asset, so this writer never creates instances -- it only dispatches each fragment
@@ -1805,26 +1792,7 @@ def apply_spatial_tendon_properties(
18051792 Returns:
18061793 True if every fragment tuned at least one target and no instanced prim was skipped.
18071794 """
1808- fragments = list (fragments )
1809- if stage is None :
1810- stage = get_current_stage ()
1811- if not fragments :
1812- return True
1813- targets , _ , any_skipped = _match_fragment_targets (prim_path_expr , _is_spatial_tendon_target , stage )
1814- if not targets :
1815- logger .warning ("No spatial-tendon targets matched expression '%s'; nothing was authored." , prim_path_expr )
1816- return False
1817- # per-fragment any-target aggregation: a fragment fails only when it tuned no target at all,
1818- # since each backend's func legitimately no-ops on the other backend's tendon prims.
1819- success = not any_skipped
1820- for cfg in fragments :
1821- func = cfg .func if callable (cfg .func ) else string_to_callable (cfg .func )
1822- fragment_hit = False
1823- for target in targets :
1824- if func (cfg , target .GetPath ().pathString , stage ):
1825- fragment_hit = True
1826- success = fragment_hit and success
1827- return success
1795+ return _apply_tendon_fragments (prim_path_expr , fragments , _SPATIAL_TENDON_SCHEMAS , (), "spatial" , stage )
18281796
18291797
18301798@apply_nested
@@ -1837,16 +1805,14 @@ def modify_spatial_tendon_properties(
18371805 through length and limit constraints. For instance, it can be used to set up an equality constraint
18381806 between a driven and passive revolute joints.
18391807
1840- The schema comprises of attributes that belong to the `PhysxTendonAxisRootAPI `_ schema.
1808+ The schema comprises attributes that belong to the `PhysxTendonAttachmentRootAPI `_ schema.
18411809
18421810 .. note::
18431811 This function is decorated with :func:`apply_nested` that sets the properties to all the prims
18441812 (that have the schema applied on them) under the input prim path.
18451813
18461814 .. _spatial tendon: https://nvidia-omniverse.github.io/PhysX/physx/5.4.1/_api_build/classPxArticulationSpatialTendon.html
1847- .. _PhysxTendonAxisRootAPI: https://docs.omniverse.nvidia.com/kit/docs/omni_usd_schema_physics/104.2/class_physx_schema_physx_tendon_axis_root_a_p_i.html
18481815 .. _PhysxTendonAttachmentRootAPI: https://docs.omniverse.nvidia.com/kit/docs/omni_usd_schema_physics/104.2/class_physx_schema_physx_tendon_attachment_root_a_p_i.html
1849- .. _PhysxTendonAttachmentLeafAPI: https://docs.omniverse.nvidia.com/kit/docs/omni_usd_schema_physics/104.2/class_physx_schema_physx_tendon_attachment_leaf_a_p_i.html
18501816
18511817 Args:
18521818 prim_path: The prim path to the tendon attachment.
@@ -1866,29 +1832,8 @@ def modify_spatial_tendon_properties(
18661832 # obtain stage
18671833 if stage is None :
18681834 stage = get_current_stage ()
1869- # get USD prim
18701835 tendon_prim = stage .GetPrimAtPath (prim_path )
1871- # check if prim has spatial tendon applied on it
1872- applied_schemas = tendon_prim .GetAppliedSchemas ()
1873- has_spatial = any (
1874- "PhysxTendonAttachmentRootAPI" in s or "PhysxTendonAttachmentLeafAPI" in s for s in applied_schemas
1875- )
1876- if not has_spatial :
1877- return False
1878-
1879- cfg = cfg .to_dict ()
1880- for schema_name in applied_schemas :
1881- if "PhysxTendonAttachmentRootAPI" not in schema_name and "PhysxTendonAttachmentLeafAPI" not in schema_name :
1882- continue
1883- for attr_name , value in cfg .items ():
1884- safe_set_attribute_on_usd_prim (
1885- tendon_prim ,
1886- f"{ schema_name } :{ to_camel_case (attr_name , 'cC' )} " ,
1887- value ,
1888- camel_case = False ,
1889- )
1890- # success
1891- return True
1836+ return _write_tendon_properties (tendon_prim , cfg .to_dict (), "PhysxTendonAttachmentRootAPI" )
18921837
18931838
18941839"""
0 commit comments