Skip to content

Drive fragment schema writers by prim path expressions - #6640

Merged
ooctipus merged 44 commits into
isaac-sim:developfrom
vidurv-nvidia:vidurv/schema-frag-regex-targeting
Aug 30, 2026
Merged

Drive fragment schema writers by prim path expressions#6640
ooctipus merged 44 commits into
isaac-sim:developfrom
vidurv-nvidia:vidurv/schema-frag-regex-targeting

Conversation

@vidurv-nvidia

@vidurv-nvidia vidurv-nvidia commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

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 has
been rebuilt on top of it: it no longer touches sim/utils/queries.py at 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 take prim_path_expr and resolve
targets 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) accept dict[str, list[<Family>Fragment]]. A key is a regular-expression
suffix appended to the prim the spawner anchors that family on, so it carries its own leading
/ when it targets descendants:

Key Selects
"" the anchor prim itself
"/[^/]+" its direct children
"/.*" all of its descendants
"(/.*)?" the anchor together with its 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.

spawn = sim_utils.UsdFileCfg(
    usd_path=...,
    rigid_props={
        "(/.*)?": [UsdPhysicsRigidBodyCfg(rigid_body_enabled=True), PhysxRigidBodyCfg(...)],
        "/.*_hand(/.*)?": [PhysxRigidBodyCfg(max_depenetration_velocity=1.0)],
    },
)

Explicit API creation. The writers no longer apply their defining USD API implicitly on a bare
prim. create_if_missing=True (and the spawner flags mass_props_create_if_missing,
articulation_props_create_if_missing, joint_drive_props_create_if_missing) applies the defining
API to matched prims that lack it.

Articulation roots. apply_articulation_root_properties authors on every matched root and warns
when 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_props given as a mapping previously failed the fragment type check and raised. It also
passed 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

  • New feature (non-breaking change which adds functionality)
  • This change requires a documentation update

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 pass
create_if_missing=True where an API was previously created implicitly.

Screenshots

N/A

Checklist

  • I have run the pre-commit checks with ./isaaclab.sh --format
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • I have updated the changelog and the corresponding version in the extension's config/extension.toml file
  • I have added my name to the CONTRIBUTORS.md or my name already exists there

Testing

Per-file, via the wrapped Python:

Suite Result
test_utils_queries 12 passed
test_schema_fragments 12 passed
test_schema_writer_nested_targets 13 passed
test_mass_fragments 9 passed
test_collision_fragments 19 passed
test_articulation_fragments 28 passed, 1 xfailed
test_joint_drive_fragments 22 passed
test_tendon_fragments 23 passed
test_mesh_converter 15 passed
test_spawn_meshes (physx) 3 passed

Note on the xfail

test_physx_fix_root_link_migrates_preauthored_newton_root_api is marked xfail. It is not caused
by this PR. develop's experience files were restructured so the schema plugin that registered
NewtonArticulationRootAPI no longer loads, making it an unregistered token schema.
PhysicsManager._relocate_articulation_root only migrates applied schemas it can resolve through
Usd.SchemaRegistry.FindAppliedAPIPrimDefinition, so on root relocation the token and its authored
newton:selfCollisionEnabled value are left stranded on the former root link. Migrating it needs a
backend-registered schema-to-namespace mapping, which is out of scope here — flagging it for a
follow-up.

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.
@github-actions github-actions Bot added the isaac-lab Related to Isaac Lab team label Jul 21, 2026
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
vidurv-nvidia marked this pull request as ready for review July 21, 2026 22:43
@vidurv-nvidia
vidurv-nvidia requested a review from a team 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.
cuboid = sim_utils.CuboidCfg(
size=(0.1, 0.1, 0.1),
rigid_props={"": [UsdPhysicsRigidBodyCfg()]},
mass_props={"": [MassCfg(mass=0.5)]},

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

       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?

usd_path=f"{ISAAC_NUCLEUS_DIR}/Robots/Franka/franka_instanceable.usd",
rigid_props={
# every rigid body: universal + PhysX + MuJoCo attributes side by side
"(/.*)?": [

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not .*

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like it can accept both dict or direct Cfg

so mass_props=MassCfg(mass=0.5), should work ?

ooctipus and others added 11 commits August 17, 2026 11:54
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.
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 vidurv-nvidia added the ci:run-docker Trigger the on-demand Docker and GPU CI workflow label Aug 27, 2026
@vidurv-nvidia
vidurv-nvidia enabled auto-merge (squash) August 27, 2026 19:56
@kellyguo11

Copy link
Copy Markdown
Contributor

run-ci

@isaaclab-bot isaaclab-bot Bot added ci:run-docker Trigger the on-demand Docker and GPU CI workflow and removed ci:run-docker Trigger the on-demand Docker and GPU CI workflow labels Aug 29, 2026
@ooctipus
ooctipus disabled auto-merge August 30, 2026 00:02
@ooctipus
ooctipus merged commit 76e9cdd into isaac-sim:develop Aug 30, 2026
46 of 48 checks passed
@vidurv-nvidia
vidurv-nvidia deleted the vidurv/schema-frag-regex-targeting branch August 31, 2026 03:34
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation isaac-lab Related to Isaac Lab team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants