Drive fragment schema writers by prim path expressions - #6640
Merged
ooctipus merged 44 commits intoAug 30, 2026
Merged
Conversation
The fragment target resolver stopped descending at the first schema-bearing prim on each branch, so on assets with nested rigid-body hierarchies (child links authored under their parent link prims, as produced by the URDF importer in Isaac Sim 6.0 and later) only the outermost link received rigid-body and mass fragment properties. The legacy writers gained full-subtree traversal for these families in isaac-sim#6377, so the fragment writers diverged from legacy parity. Add a stop_at_carrier flag to the resolver, mirroring apply_nested's stop_on_success: rigid-body and mass writers now collect every carrier in the subtree, while collision keeps the legacy stop-at-carrier traversal. Extend the nested-target regression tests with a nested child link so the flat-asset, parity, and direct-writer tests all cover the nested topology.
The rigid-body fragment writer now resolves its targets from a prim path expression via find_matching_prims instead of inferring them by subtree traversal: existing carriers matched by the expression (a trailing ** token reaches the whole subtree) are modified in place. Applying a fresh RigidBodyAPI is now explicit through the new create_if_missing flag, which anchors a single matched non-carrier prim and raises when the expression matches several. Zero matched targets warn and return False instead of silently anchoring, since inventing a body the asset's joints never reference changes its dynamics. The USD-file spawner passes an explicit subtree expression, while the shape and mesh spawners opt into creation on their bare container prims.
The converter authors a fresh RigidBodyAPI on the exact xform prim it creates, so it must opt into creation now that the fragment writer only modifies matched carriers by default.
The mass writer now matches targets with a prim path expression (a trailing ** token selects a prim and its whole subtree) instead of traversing the subtree of a single prim. Creation of UsdPhysics.MassAPI is explicit via create_if_missing: with multiple matches it only lands on prims that already carry a rigid body (non-bodies are skipped with a warning), while a single exact-prim match is anchored unconditionally since the shape and mesh spawners author mass before the rigid body. Unmatched expressions warn and return False instead of raising.
The writer now resolves its targets with find_matching_prims from an explicit prim path expression instead of inferring roots by subtree traversal; callers select the subtree with a trailing '**' token. Nested articulation roots now raise a ValueError instead of being silently pruned to the outermost root, and a fresh root anchor is only applied when requested via the new create_if_missing flag rather than implicitly when no root is found.
Target joints for apply_joint_drive_properties with an explicit prim path expression instead of a subtree traversal from a root prim; the spawner call site now passes a trailing-** expression. A new create_if_missing flag applies the axis-appropriate UsdPhysics.DriveAPI (angular for revolute, linear for prismatic) on matched joints lacking it, distinct from ensure_drives_exist which seeds a minimal stiffness on fully-passive drives. Zero matches now warn instead of raising.
The collision family writer now matches its targets with a prim path expression (trailing ** selects a prim and its whole subtree) instead of inferring them by subtree traversal. Creating the CollisionAPI anchor is opt-in via create_if_missing and, unlike rigid bodies, is allowed on multiple matched prims to cover the bare-prim shape and mesh spawner case. Mesh-collision fragments are dispatched only to geometry prims, since cooking attributes and the approximation token are meaningless elsewhere.
The core apply_fixed_tendon_properties and apply_spatial_tendon_properties writers now resolve their targets from a prim path expression (with a trailing ** token selecting a whole subtree) instead of relying on the backend fragment funcs to descend. The PhysX multi-instance tendon tuner and the MuJoCo MjcTendon tuner are strictly per-prim now; a fragment succeeds when it tunes at least one matched target, so mixed-backend target sets compose. The from-files spawner appends /** to keep tuning tendon schemas authored on descendant joint prims.
All fragment writers now resolve targets through prim path expressions, so the stop-at-carrier traversal helper has no callers left. Also accept path-like objects (e.g. Sdf.Path) in the expression matcher and fix a test passing the stage positionally into the create_if_missing slot.
Spawner cfgs gain per-family *_prim_path fields that select which prims of the spawned asset receive schema fragments. Patterns are relative to the spawn prim: each /-separated token is a per-level regex and a trailing ** matches a prim and all its descendants. None keeps the spawner default (whole subtree for USD assets, the exact authored prim for shapes/meshes) and an empty string targets the spawn prim itself. New create-if-missing flags are threaded through for the mass, articulation-root, and joint-drive writers.
6 tasks
The helper joining a spawn prim path with a cfg-relative target pattern was duplicated in the USD-file, shape, and mesh spawners. Move it to a private module shared by the spawner implementations.
Applying MassAPI is now uniform across matched prims, mirroring the legacy define path, which never required a rigid body either. Pairing the mass with a rigid body is the caller's responsibility; the parsers ignore an unpaired mass.
vidurv-nvidia
marked this pull request as ready for review
July 21, 2026 22:43
vidurv-nvidia
requested review from
AntoineRichard,
huidongc,
kellyguo11,
marcodiiga,
matthewtrepte and
ooctipus
as code owners
July 21, 2026 22:43
The prim path grammar is documented once on find_matching_prims, so the family writers and spawner fields reference it instead of restating it.
The deformable branch wrapped a mapping in a list, so it failed the fragment type check and raised. It also passed the bare body prim to the collision writer, which no longer reaches the simulation mesh authored beneath it.
ooctipus
reviewed
Aug 17, 2026
| cuboid = sim_utils.CuboidCfg( | ||
| size=(0.1, 0.1, 0.1), | ||
| rigid_props={"": [UsdPhysicsRigidBodyCfg()]}, | ||
| mass_props={"": [MassCfg(mass=0.5)]}, |
Collaborator
There was a problem hiding this comment.
rigid_props={"": [UsdPhysicsRigidBodyCfg()]},
mass_props={"": [MassCfg(mass=0.5)]},for this pattern
I remember you told me there were reason why you dont want
rigid_props=UsdPhysicsRigidBodyCfg(),
mass_props=MassCfg(mass=0.5),
can you remind me again?
ooctipus
reviewed
Aug 17, 2026
| usd_path=f"{ISAAC_NUCLEUS_DIR}/Robots/Franka/franka_instanceable.usd", | ||
| rigid_props={ | ||
| # every rigid body: universal + PhysX + MuJoCo attributes side by side | ||
| "(/.*)?": [ |
ooctipus
reviewed
Aug 17, 2026
| collision_props: ( | ||
| schemas_cfg.CollisionPropertiesCfg | schemas_cfg.CollisionFragment | list[schemas_cfg.CollisionFragment] | ||
| ) = None | ||
| collision_props: dict[str, list[schemas_cfg.CollisionFragment]] | schemas_cfg.CollisionPropertiesCfg | None = None |
Collaborator
There was a problem hiding this comment.
looks like it can accept both dict or direct Cfg
so mass_props=MassCfg(mass=0.5), should work ?
An anchor prim is usually a plain Xform carrying no family API, so including it changes nothing -- except under create_if_missing, where the optional-group form would also apply the API to the anchor. Examples and configuration field docstrings now use the descendants-only key.
…g' into vidurv/schema-frag-regex-targeting
A fragment or list of fragments on a *_props field now reads as {"": [...]},
targeting the anchor prim, so the common single-target case does not need the
mapping. Routing is centralized in a fragment_mapping normalizer.
The guard blocks still indexed the raw configuration value, so a bare fragment or list raised AttributeError on the deformable-collision and gravity-compensation checks.
Relocation resolved applied schemas through the USD schema registry, which cannot describe a schema shipped as an unregistered token, so Newton's root API and its attributes stayed on the former root link. Backends now declare the schema-to-namespace pairing through a registration hook.
An empty list carries no fragments and no targeting intent, but it was read as an anchor-targeted entry. That pinned the articulation expression to the spawn prim, so an asset whose root sits on a child link matched nothing and never had its root link fixed.
A bare fragment on a file spawner previously routed to the legacy nested writer, which tuned the spawn prim together with its subtree. Reading it as a spawn-prim entry authored nothing on assets whose bodies live beneath the spawn prim. The convenience form now takes the target pattern from the caller, so the file spawners keep their subtree reach while the shape, mesh, and converter spawners still author the prim they created.
Task configurations point UsdFileCfg at art assets that ship without any physics schemas and rely on the spawner to turn the asset into one rigid body. The legacy nested writers did that implicitly: when a subtree carried no prim with the family's defining API, they applied it to the spawn prim and authored there. The regex writers replaced that implicit step with an explicit create_if_missing flag, which the rigid-body and collision families do not expose on the spawner configuration. Those assets then reached the backend with no body at all, so Newton resolved no body id for them. Restore the fallback for the bare-fragment form on the file spawners, which is the spelling that used to route to the nested writers: when the spawn prim's subtree carries no prim with the family's defining API, author on the spawn prim and create the API there. An explicit mapping is still honored exactly as written.
The Docker + Tests workflow was never created for the previous push, so the test matrix has no result on this branch tip.
vidurv-nvidia
enabled auto-merge (squash)
August 27, 2026 19:56
ooctipus
approved these changes
Aug 27, 2026
Contributor
|
run-ci |
ooctipus
disabled auto-merge
August 30, 2026 00:02
5 tasks
kellyguo11
added a commit
that referenced
this pull request
Aug 31, 2026
…xpressions (#7441) # Description Backports #6640 onto `release/3.0.0`. This moves the experimental physics schema-fragment surface to prim-path expressions, adds per-family target mappings to spawner configs, makes schema API creation explicit, and keeps the PhysX/Newton tendon backends per-prim. It also fixes nested rigid-body and mass fragment targeting. The prerequisite whole-path regular-expression matcher from #6841 is already present on the release branch. This is an exact `cherry-pick -x` of the merged commit. ## Risk assessment - The source PR's 33 touched paths were identical between its `develop` base and the current release tip, so the commit applied without conflicts or release-specific edits; source and backport patch IDs match. - Legacy single-cfg values and the legacy `define_*` / `modify_*` writers are unchanged, and there are no in-tree fragment users. - The bounded compatibility risk is for out-of-tree users of the experimental fragment surface. They must wrap fragment lists as `{"(/.*)?": [...]}` and pass `create_if_missing=True` where API creation was previously implicit. ## Type of change - New feature - Breaking change (experimental schema-fragment surface only) - Documentation update ## Validation - Exact source/backport patch identity and clean release-branch application - `git diff --check` - Python 3.12 static compilation of the changed simulation/schema modules - Formatting, lint, spelling, license, and RST pre-commit hooks - Changelog-fragment gate against `release/3.0.0` - Upstream #6640 CI passed pre-commit, changelog, docs, core, Newton, PhysX, wheel, x86 installation, rendering, and the targeted schema/spawner suites The full project test and docs commands were not rerun locally because the project lockfile supports Linux and Windows, not this macOS host. Release-branch CI should provide the final platform validation. The source PR's unrelated Franka Pour dataset-contract failure and ARM runner-queue cancellation did not involve these paths. ## Checklist - [x] I have read and understood the contribution guidelines - [x] I have made corresponding documentation changes - [x] I have added tests that cover the changed behavior - [x] I have added a changelog fragment for every touched source package - [x] The contributor is already listed in `CONTRIBUTORS.md` Co-authored-by: vidurv-nvidia <vidurv@nvidia.com> Co-authored-by: ooctipus <zhengyuz@nvidia.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Moves the physics schema fragment surface onto prim path expressions, so a writer targets prims
by pattern instead of traversing the subtree of a single input prim.
Since this PR was opened, #6841 landed the plain-regex prim path matcher on
develop. This PR hasbeen rebuilt on top of it: it no longer touches
sim/utils/queries.pyat all, and the bespoke**recursive token it originally introduced is gone. Targeting is now expressed entirely in standard
Python regular expressions matched against whole prim paths.
What changes
Family writers take an expression.
apply_rigid_body_properties,apply_collision_properties,apply_mass_properties,apply_articulation_root_properties,apply_joint_drive_properties, and the two tendon writers now takeprim_path_exprand resolvetargets through
find_matching_prims. A bare prim path matches only that prim;f"{prim_path}/.*"reaches its descendants and
f"{prim_path}(/.*)?"reaches the prim together with its subtree.Spawner fields take a mapping. The fragment fields (
rigid_props,collision_props,mass_props,articulation_props,joint_drive_props,fixed_tendons_props,spatial_tendons_props) acceptdict[str, list[<Family>Fragment]]. A key is a regular-expressionsuffix appended to the prim the spawner anchors that family on, so it carries its own leading
/when it targets descendants:"""/[^/]+""/.*""(/.*)?"Entries apply in insertion order, so on overlapping targets later entries override earlier ones per
attribute. Legacy single-cfg values are unaffected and still route to the legacy writers.
Explicit API creation. The writers no longer apply their defining USD API implicitly on a bare
prim.
create_if_missing=True(and the spawner flagsmass_props_create_if_missing,articulation_props_create_if_missing,joint_drive_props_create_if_missing) applies the definingAPI to matched prims that lack it.
Articulation roots.
apply_articulation_root_propertiesauthors on every matched root and warnswhen roots nest, rather than silently pruning nested ones. Asset validity is the author's
responsibility.
Tendon backends de-traverse. The PhysX and Newton tendon fragment functions now author on the
given prim only; target selection is owned by the core family writers.
Deformable meshes accept the mapping too. On the mesh spawner's deformable branch,
collision_propsgiven as a mapping previously failed the fragment type check and raised. It alsopassed the bare body prim to the collision writer, which under expression matching no longer
reaches the simulation mesh authored beneath it. Mapping entries now anchor at the body prim (e.g.
{"/sim_mesh": [...]}) and a bare fragment list uses a subtree pattern to reach the collider.Fixes
Rigid-body and mass fragments previously reached only the outermost schema-bearing prim on assets
with nested rigid-body hierarchies (child links authored under their parent link prims, as produced
by the URDF importer in Isaac Sim 6.0 and later). A whole-subtree pattern now reaches every carrier.
Type of change
Breaking within the fragment surface only. The fragment path is experimental and has no
in-tree users yet; legacy
define_*/modify_*writers and legacy cfg classes are untouched.Migration for anyone already on fragments: wrap lists as
{"(/.*)?": [...]}, and passcreate_if_missing=Truewhere an API was previously created implicitly.Screenshots
N/A
Checklist
pre-commitchecks with./isaaclab.sh --formatconfig/extension.tomlfileCONTRIBUTORS.mdor my name already exists thereTesting
Per-file, via the wrapped Python:
test_utils_queriestest_schema_fragmentstest_schema_writer_nested_targetstest_mass_fragmentstest_collision_fragmentstest_articulation_fragmentstest_joint_drive_fragmentstest_tendon_fragmentstest_mesh_convertertest_spawn_meshes(physx)Note on the xfail
test_physx_fix_root_link_migrates_preauthored_newton_root_apiis markedxfail. It is not causedby this PR.
develop's experience files were restructured so the schema plugin that registeredNewtonArticulationRootAPIno longer loads, making it an unregistered token schema.PhysicsManager._relocate_articulation_rootonly migrates applied schemas it can resolve throughUsd.SchemaRegistry.FindAppliedAPIPrimDefinition, so on root relocation the token and its authorednewton:selfCollisionEnabledvalue are left stranded on the former root link. Migrating it needs abackend-registered schema-to-namespace mapping, which is out of scope here — flagging it for a
follow-up.