Skip to content

Commit 5435964

Browse files
Allow usd-core 26.08 and adopt usd-exchange 3.x on aarch64 (#4022)
Co-authored-by: mzamoramora-nvidia <mzamoramora@nvidia.com>
1 parent b74df53 commit 5435964

10 files changed

Lines changed: 120 additions & 60 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Allow `usd-core` 26.08, and allow `usd-exchange` 3.x on aarch64 while keeping 2.3.0 installable, which extends USD support to Python 3.13 on aarch64.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Stop forcing `PXR_WORK_THREAD_LIMIT=1` when running the test suite against OpenUSD 26.08 or newer, where the collider-parsing race it worked around is fixed.

docs/concepts/usd_parsing.rst

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1387,16 +1387,20 @@ Limitations
13871387
-----------
13881388

13891389
Importing USD files where many (> 30) mesh colliders are under the same rigid body
1390-
can result in a crash in ``UsdPhysics.LoadUsdPhysicsFromRange``. This is a known
1391-
thread-safety issue in OpenUSD and will be fixed in a future release of
1392-
``usd-core``. It can be worked around by setting the work concurrency limit to 1
1393-
before ``pxr`` initializes its thread pool.
1390+
can result in a crash in OpenUSD's native physics parser. This is a known
1391+
thread-safety issue in OpenUSD, **fixed in OpenUSD 26.08**: no workaround is needed
1392+
when the USD runtime is 26.08 or newer, whether it comes from ``usd-core`` or from
1393+
the OpenUSD build bundled in ``usd-exchange``.
1394+
1395+
Newton still supports older ``usd-core`` releases, so the workaround below remains
1396+
relevant when running against a USD runtime older than 26.08. It can be applied by
1397+
setting the work concurrency limit to 1 before ``pxr`` initializes its thread pool.
13941398

13951399
.. note::
13961400

13971401
Setting the concurrency limit to 1 disables multi-threaded USD processing
13981402
globally and may degrade performance of other OpenUSD workloads in the same
1399-
process.
1403+
process. Prefer upgrading to OpenUSD 26.08 or newer instead.
14001404

14011405
Choose **one** of the two approaches below — do not combine them.
14021406
``PXR_WORK_THREAD_LIMIT`` is evaluated once when ``pxr`` is first imported and

newton/_src/solvers/kamino/_src/utils/io/usd.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1782,12 +1782,12 @@ def import_from(
17821782
###
17831783

17841784
# Initialize the ignore paths as an empty list if it is None
1785-
# NOTE: This is required by the LoadUsdPhysicsFromRange method
1785+
# NOTE: This is required by the native physics parser
17861786
if ignore_paths is None:
17871787
ignore_paths = []
17881788

17891789
# Load the USD file into an object dictionary
1790-
ret_dict = self.UsdPhysics.LoadUsdPhysicsFromRange(stage, [root_path], excludePaths=ignore_paths)
1790+
ret_dict = usd_utils.load_physics_from_range(stage, [root_path], ignore_paths)
17911791

17921792
# Create a new ModelBuilderKamino if not provided
17931793
if builder is None:

newton/_src/usd/utils.py

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2254,6 +2254,30 @@ def _get_surface_deformable_material(
22542254
)
22552255

22562256

2257+
def load_physics_from_range(stage, root_paths, exclude_paths=()):
2258+
"""Parse a stage's native physics descriptors, across OpenUSD versions.
2259+
2260+
OpenUSD 26.08 renamed ``UsdPhysics.LoadUsdPhysicsFromRange`` to
2261+
``UsdPhysics.UsdPhysicsLoadStageFromPrimRange`` and deprecated the old name, so calling
2262+
it emits a ``DeprecationWarning``. Both spellings take the same arguments and return the
2263+
same descriptor dict; prefer the new name where it exists.
2264+
2265+
Args:
2266+
stage: The USD stage to parse.
2267+
root_paths: Roots of the subtrees to parse.
2268+
exclude_paths: Prim paths whose subtrees should be excluded from the parse.
2269+
2270+
Returns:
2271+
The parser's object-type to descriptor mapping.
2272+
"""
2273+
from pxr import UsdPhysics
2274+
2275+
load = getattr(UsdPhysics, "UsdPhysicsLoadStageFromPrimRange", None)
2276+
if load is None:
2277+
load = UsdPhysics.LoadUsdPhysicsFromRange
2278+
return load(stage, list(root_paths), excludePaths=list(exclude_paths))
2279+
2280+
22572281
def _get_physics_material_density(material_prim) -> float | None:
22582282
"""Read a bound material's base ``UsdPhysicsMaterialAPI`` density.
22592283
@@ -2403,11 +2427,7 @@ def get_physics_scenes(
24032427
Returns:
24042428
Physics scenes in parser order.
24052429
"""
2406-
physics_results = UsdPhysics.LoadUsdPhysicsFromRange(
2407-
stage,
2408-
[root_path],
2409-
excludePaths=list(exclude_paths or ()),
2410-
)
2430+
physics_results = load_physics_from_range(stage, [root_path], exclude_paths or ())
24112431
return _get_physics_scenes_from_results(stage, physics_results)
24122432

24132433

newton/_src/utils/import_usd.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -644,7 +644,7 @@ class PhysicsMaterial:
644644
has_nonunit_linear_units = not math.isclose(linear_unit, 1.0)
645645
has_nonunit_mass_units = not math.isclose(mass_unit, 1.0)
646646
non_regex_ignore_paths = [path for path in ignore_paths if ".*" not in path]
647-
# LoadUsdPhysicsFromRange remains the native rigid/joint descriptor parser, so this
647+
# The native rigid/joint descriptor parser remains authoritative, so this
648648
# pre-pass supplies its deformable exclusions before it runs. The same walk also
649649
# collects static visual leaves when requested, avoiding a third stage traversal.
650650
root_prim = stage.GetPrimAtPath(root_path)
@@ -658,7 +658,7 @@ class PhysicsMaterial:
658658
native_exclude_paths = list(
659659
dict.fromkeys([*non_regex_ignore_paths, *_deformable_prims.native_physics_exclude_paths])
660660
)
661-
ret_dict = UsdPhysics.LoadUsdPhysicsFromRange(stage, [root_path], excludePaths=native_exclude_paths)
661+
ret_dict = usd.load_physics_from_range(stage, [root_path], native_exclude_paths)
662662
physics_scenes = usd._get_physics_scenes_from_results(stage, ret_dict)
663663
physics_scene_prim = physics_scenes[0].GetPrim() if physics_scenes else None
664664

newton/tests/test_import_usd.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
SOLREF_MODE_RAW,
3333
)
3434
from newton._src.solvers.mujoco.utils import MjcEqualityTargetKind
35+
from newton._src.usd import utils as usd_utils
3536
from newton._src.utils.color import color_linear_to_srgb
3637
from newton._src.utils.import_usd import _is_uniform_scale
3738
from newton.math import quat_between_axes
@@ -47,7 +48,7 @@
4748
def _expect_jointless_articulation_warning(test):
4849
"""Require the benign jointless-articulation warning on OpenUSD < 26.0.
4950

50-
``UsdPhysics.LoadUsdPhysicsFromRange`` in OpenUSD < 26.0 (e.g. the
51+
``UsdPhysics``'s native physics parser in OpenUSD < 26.0 (e.g. the
5152
``usd-exchange`` build resolved on ``aarch64``) reports an articulation root
5253
that has no joints as an invalid ``ArticulationDesc``, which
5354
:func:`~newton.utils.parse_usd` surfaces as a ``UserWarning``; usd-core
@@ -4897,7 +4898,7 @@ def verify_usdphysics_parser(test, file, model, compare_min_max_coords, floating
48974898
from pxr import Gf, Sdf, Usd, UsdPhysics
48984899

48994900
stage = Usd.Stage.Open(file)
4900-
parsed = UsdPhysics.LoadUsdPhysicsFromRange(stage, ["/"])
4901+
parsed = usd_utils.load_physics_from_range(stage, ["/"])
49014902
# since the key is generated from USD paths we can assume that keys are unique
49024903
body_key_to_idx = dict(zip(model.body_label, range(model.body_count), strict=False))
49034904
shape_key_to_idx = dict(zip(model.shape_label, range(model.shape_count), strict=False))
@@ -10547,8 +10548,10 @@ def test_scene_path(self):
1054710548
scene = UsdPhysics.Scene.Define(stage, "/Scene")
1054810549
scene.CreateGravityMagnitudeAttr(2.0)
1054910550

10550-
load_physics = UsdPhysics.LoadUsdPhysicsFromRange
10551-
with mock.patch.object(UsdPhysics, "LoadUsdPhysicsFromRange", wraps=load_physics) as load_physics_mock:
10551+
# Patch Newton's compat wrapper rather than the OpenUSD entry point, whose name
10552+
# differs across OpenUSD versions.
10553+
load_physics = usd_utils.load_physics_from_range
10554+
with mock.patch.object(usd_utils, "load_physics_from_range", wraps=load_physics) as load_physics_mock:
1055210555
result = newton.ModelBuilder().add_usd(stage)
1055310556

1055410557
load_physics_mock.assert_called_once()
@@ -13197,8 +13200,8 @@ def test_scenes(self):
1319713200
first.CreateGravityMagnitudeAttr(2.0)
1319813201
second = UsdPhysics.Scene.Define(stage, "/World/SecondScene")
1319913202

13200-
load_physics = UsdPhysics.LoadUsdPhysicsFromRange
13201-
with mock.patch.object(UsdPhysics, "LoadUsdPhysicsFromRange", wraps=load_physics) as load_physics_mock:
13203+
load_physics = usd_utils.load_physics_from_range
13204+
with mock.patch.object(usd_utils, "load_physics_from_range", wraps=load_physics) as load_physics_mock:
1320213205
scenes = usd.get_physics_scenes(stage)
1320313206

1320413207
load_physics_mock.assert_called_once()

newton/tests/thirdparty/unittest_parallel.py

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@
1010

1111
import argparse
1212
import concurrent.futures # NVIDIA Modification
13+
import importlib.metadata
1314
import multiprocessing
1415
import os
16+
import re
1517
import sys
1618
import tempfile
1719
import time
@@ -20,11 +22,34 @@
2022
from contextlib import contextmanager
2123
from io import StringIO
2224

23-
# Work around a known OpenUSD thread-safety crash in
24-
# UsdPhysics.LoadUsdPhysicsFromRange for collider-dense assets. OpenUSD reads
25-
# this once when pxr initializes, so set it before test modules import pxr and
26-
# preserve any caller-provided override.
27-
os.environ.setdefault("PXR_WORK_THREAD_LIMIT", "1")
25+
# Work around a known OpenUSD thread-safety crash in the native physics parser for
26+
# collider-dense assets: concurrent descriptor appends could race when several colliders
27+
# shared one rigid body. Fixed in OpenUSD 26.08, so only older runtimes are constrained.
28+
#
29+
# OpenUSD reads this once when pxr initializes, so it must be set before test modules import
30+
# pxr. That rules out reading Usd.GetVersion(), and also rules out importing any newton USD
31+
# module, since newton_usd_schemas imports pxr at module scope. Distribution metadata gives
32+
# the runtime version without initializing OpenUSD: usd-core is versioned directly, while
33+
# usd-exchange bundles its own OpenUSD build and advertises it as a `usd<major><minor>` extra
34+
# (e.g. `usd2608`). A runtime that cannot be identified is treated as affected, and any
35+
# caller-provided override is preserved.
36+
try:
37+
_USD_VERSION = tuple(int(part) for part in importlib.metadata.version("usd-core").split(".")[:2])
38+
except (importlib.metadata.PackageNotFoundError, ValueError):
39+
try:
40+
_USD_VERSION = next(
41+
(int(match.group(1)), int(match.group(2)))
42+
for match in (
43+
re.fullmatch(r"usd(\d{2})(\d{2})", extra)
44+
for extra in importlib.metadata.metadata("usd-exchange").get_all("Provides-Extra") or []
45+
)
46+
if match
47+
)
48+
except (importlib.metadata.PackageNotFoundError, StopIteration):
49+
_USD_VERSION = (0, 0)
50+
51+
if _USD_VERSION < (26, 8):
52+
os.environ.setdefault("PXR_WORK_THREAD_LIMIT", "1")
2853

2954
from newton.tests.unittest_utils import ( # NVIDIA modification
3055
AllocationCleanupTestResultMixin,

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ importers = [
6262
"open3d>=0.19.0; python_version < '3.13' and (sys_platform != 'linux' or platform_machine != 'aarch64')",
6363

6464
# USD core libraries
65-
"usd-core>=25.5,<26.5 ; platform_machine != 'aarch64' and python_version < '3.14'",
66-
"usd-exchange>=2.2.0,<3 ; platform_machine == 'aarch64' and python_version < '3.13'",
65+
"usd-core>=25.5,<26.9 ; platform_machine != 'aarch64' and python_version < '3.14'",
66+
"usd-exchange>=2.3.0,<4 ; platform_machine == 'aarch64' and python_version < '3.14'",
6767

6868
# Newton USD Schemas
6969
"newton-usd-schemas>=0.5.0",

0 commit comments

Comments
 (0)