Skip to content

Commit 99f7d7b

Browse files
author
Horde
committed
Add MuJoCo tendon support to Newton physics replication
Newton's parse_usd has two interlocking constraints that prevented MjcTendon prims from being parsed correctly in the multi-env cloner: 1. SchemaResolverMjc.validate_custom_attributes() raises if called on a builder that has not had SolverMuJoCo.register_custom_attributes() called first. 2. register_custom_attributes registers MJC custom frequencies, which triggers Newton's stage-wide custom-frequency traversal (independent of ignore_paths). On the main builder, this traversal finds MjcTendon prims under /World/envs/... and tries to resolve their joint paths against the main builder's empty joint_label (joints are excluded via ignore_paths), producing "unknown joint path" warnings and dropping all tendons. Fix: exclude SchemaResolverMjc from the main builder's schema_resolvers. The main builder loads only scene-level prims (ground, lights) that have no MjcTendon prims, so the resolver is not needed there. Proto builders include SchemaResolverMjc and call register_custom_attributes before add_usd, so Newton correctly resolves MjcTendon joint paths against each proto's fully populated joint_label. add_builder then propagates the resolved entries into the main builder (N×T entries for N envs, T tendons each), which SolverMuJoCo handles natively by filtering on tendon_world == template_world.
1 parent 640e71b commit 99f7d7b

3 files changed

Lines changed: 364 additions & 5 deletions

File tree

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
Added
2+
^^^^^
3+
4+
* Added MuJoCo tendon parsing to Newton physics replication.
5+
:func:`~isaaclab_newton.cloner.newton_replicate._build_newton_builder_from_mapping`
6+
now includes :class:`SchemaResolverMjc` in the proto-builder resolver list and calls
7+
``SolverMuJoCo.register_custom_attributes`` on each proto builder so that
8+
``MjcTendon`` prims (e.g. Shadow Hand fixed tendons) are resolved during
9+
``add_usd`` and propagated into the main builder via ``add_builder``.
10+
11+
Fixed
12+
^^^^^
13+
14+
* Fixed MuJoCo tendon handling in Newton physics replication
15+
(:func:`~isaaclab_newton.cloner.newton_replicate._build_newton_builder_from_mapping`).
16+
Two interlocking issues prevented tendons from being included in the replicated model:
17+
18+
1. ``SchemaResolverMjc`` was included in the main builder's ``schema_resolvers``,
19+
which requires ``SolverMuJoCo.register_custom_attributes`` to be called first
20+
(or ``validate_custom_attributes`` raises). Calling it on the main builder
21+
registered MJC custom frequencies, triggering Newton's stage-wide traversal
22+
(independent of ``ignore_paths``) that tried to resolve ``MjcTendon`` joint
23+
paths against the main builder's empty ``joint_label``, producing "unknown joint
24+
path" warnings and silently dropping all tendons.
25+
26+
The fix excludes ``SchemaResolverMjc`` from the main builder's ``schema_resolvers``
27+
(the main builder loads only scene-level prims with no ``MjcTendon`` prims) and
28+
moves it to the proto builders, where ``register_custom_attributes`` is called
29+
before ``add_usd`` and the full joint hierarchy is available for resolution.
30+
Newton's ``add_builder`` then propagates the resolved tendon entries (with correct
31+
joint-index offsets and ``tendon_world`` per environment) into the main builder.
32+
The resulting N×T tendon entries across N environments are handled natively by
33+
``SolverMuJoCo``, which filters by ``tendon_world == template_world``.

source/isaaclab_newton/isaaclab_newton/cloner/newton_replicate.py

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import torch
1111
import warp as wp
1212
from newton import ModelBuilder, solvers
13-
from newton._src.usd.schemas import SchemaResolverNewton, SchemaResolverPhysx
13+
from newton._src.usd.schemas import SchemaResolverMjc, SchemaResolverNewton, SchemaResolverPhysx
1414

1515
from pxr import Usd, UsdGeom
1616

@@ -52,28 +52,48 @@ def _build_newton_builder_from_mapping(
5252
quaternions = torch.zeros((mapping.size(1), 4), device=mapping.device, dtype=torch.float32)
5353
quaternions[:, 3] = 1.0
5454

55-
schema_resolvers = [SchemaResolverNewton(), SchemaResolverPhysx()]
56-
55+
# Main builder: loads only ground plane, lights, and scene-level prims.
56+
# /World/envs (and all source asset paths) are excluded via ignore_paths.
57+
#
58+
# SchemaResolverMjc is intentionally EXCLUDED here. Two reasons:
59+
# 1. SchemaResolverMjc.validate_custom_attributes() raises if
60+
# SolverMuJoCo.register_custom_attributes() has not been called first.
61+
# 2. Calling register_custom_attributes on the main builder registers MJC
62+
# custom frequencies, which triggers Newton's stage-wide traversal
63+
# (independent of ignore_paths). That traversal finds MjcTendon prims
64+
# under /World/envs/... and tries to resolve their joint paths against
65+
# the main builder's empty joint_label (no joints were loaded), causing
66+
# "unknown joint path" warnings and silently dropping every tendon.
67+
# The main builder only loads scene-level prims (ground, lights) that have
68+
# no MjcTendon prims, so SchemaResolverMjc is not needed here.
69+
_main_resolvers = [SchemaResolverNewton(), SchemaResolverPhysx()]
5770
builder = NewtonManager.create_builder(up_axis=up_axis)
5871
stage_info = builder.add_usd(
5972
stage,
6073
ignore_paths=["/World/envs"] + sources,
61-
schema_resolvers=schema_resolvers,
74+
schema_resolvers=_main_resolvers,
6275
)
6376

77+
# Proto resolvers include SchemaResolverMjc so MjcTendon prims are parsed.
78+
_proto_resolvers = [SchemaResolverMjc(), SchemaResolverNewton(), SchemaResolverPhysx()]
79+
6480
# The prototype is built from env_0 in absolute world coordinates.
6581
# add_builder xforms are deltas from env_0 so positions don't get double-counted.
6682
env0_pos = positions[0]
6783
protos: dict[str, ModelBuilder] = {}
6884
for src_path in sources:
85+
# register_custom_attributes must be called before add_usd so that
86+
# SchemaResolverMjc.validate_custom_attributes() passes and Newton's
87+
# custom-frequency traversal can resolve MjcTendon joint paths against
88+
# this proto's fully populated joint_label.
6989
p = NewtonManager.create_builder(up_axis=up_axis)
7090
solvers.SolverMuJoCo.register_custom_attributes(p)
7191
p.add_usd(
7292
stage,
7393
root_path=src_path,
7494
load_visual_shapes=True,
7595
skip_mesh_approximation=True,
76-
schema_resolvers=schema_resolvers,
96+
schema_resolvers=_proto_resolvers,
7797
)
7898
if simplify_meshes:
7999
p.approximate_meshes("convex_hull", keep_visual_shapes=True)
@@ -112,6 +132,12 @@ def _build_newton_builder_from_mapping(
112132
# end the world context
113133
builder.end_world()
114134

135+
# Note: add_builder appends tendon custom-attribute entries from the proto
136+
# for each world, giving N×T total entries after N environments. This is
137+
# intentional: Newton's SolverMuJoCo reads tendon_world[i] for every entry
138+
# and skips any tendon whose world is not the template world (world 0).
139+
# No deduplication is needed.
140+
115141
site_index_map = {
116142
**global_site_map,
117143
**{label: (None, per_world) for label, per_world in local_site_map.items()},

0 commit comments

Comments
 (0)