Skip to content

Usd examples parsing tests#572

Merged
camevor merged 15 commits into
newton-physics:mainfrom
camevor:usd-examples-parsing-tests
Sep 2, 2025
Merged

Usd examples parsing tests#572
camevor merged 15 commits into
newton-physics:mainfrom
camevor:usd-examples-parsing-tests

Conversation

@camevor

@camevor camevor commented Aug 18, 2025

Copy link
Copy Markdown
Member

Description

Newton Migration Guide

Please ensure the migration guide for warp.sim users is up-to-date with the changes made in this MR.

  • The migration guide in docs/migration.rst is up-to date

Before your PR is "Ready for review"

  • All commits are signed-off to indicate that your contribution adheres to the Developer Certificate of Origin requirements
  • Necessary tests have been added and new examples are tested (see newton/tests/test_examples.py)
  • Documentation is up-to-date
  • Code passes formatting and linting checks with pre-commit run -a

Summary by CodeRabbit

  • Tests
    • Added a comprehensive USD physics import test suite validating bodies, joints, shapes, masses/inertias, transforms, drives, collision flags, and shape morphing; includes integration runs against multiple real USD sample assets.
  • Bug Fixes
    • Import now emits warnings and skips invalid USD descriptors instead of failing, improving robustness when parsing imperfect or partial USD assets.
  • Chores
    • Normalized remeshing option key handling to be case-insensitive for more consistent import behavior.

@coderabbitai

coderabbitai Bot commented Aug 18, 2025

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Adds a USD physics validation test suite (newton/tests/test_import_usd.py) that exercises USD→Newton mappings and adds importer robustness in newton/_src/utils/import_usd.py by warning and skipping invalid USD descriptors and normalizing a remeshing key.

Changes

Cohort / File(s) Summary
USD physics import tests
newton/tests/test_import_usd.py
Adds TestImportSampleAssets with verify_usdphysics_parser(self, file, model, compare_min_max_coords, floating) plus USD-gated tests (test_g1, test_anymal, test_cartpole, test_ant, test_h1). Implements extensive USD→Newton cross-checks for bodies, shapes, joints, transforms, masses/inertias, joint DOFs/drives, collision flags, and shape morphs; updates imports (math, JointType, parse_usd, quat_between_axes).
USD parser robustness & mapping fixes
newton/_src/utils/import_usd.py
Adds warn_invalid_desc(path, descriptor) and pre-checks that warn-and-skip invalid USD descriptors (materials, rigid bodies, articulations/joints, shapes) using warnings.warn(..., stacklevel=2). Normal processing unchanged for valid descriptors. Also normalizes approximation remeshing key from "meshSimplification" to "meshsimplification". No public API signature changes.

Sequence Diagram(s)

sequenceDiagram
  participant Test as TestImportSampleAssets
  participant USD as UsdPhysics.LoadUsdPhysicsFromRange
  participant Newton as NewtonModel

  Test->>USD: LoadUsdPhysicsFromRange(file)
  USD-->>Test: physics prims (bodies, shapes, joints, materials)
  Test->>Newton: load/reference Newton model
  Test->>Test: verify_usdphysics_parser(file, model, ...)
  loop per-entity
    Test->>Test: map Newton keys ↔ USD paths
    Test->>Test: compare counts, transforms, mass/inertia
    Test->>Test: validate joint types/DOFs/drives
    Test->>Test: validate shapes, collision flags, morphs
  end
  Test-->>Test: assertions produce pass/fail
Loading
sequenceDiagram
  participant Parser as import_usd.parse_usd
  participant USD as USDStage

  Parser->>USD: iterate physics prims (materials, bodies, articulations, shapes)
  loop per-prim
    Parser->>Parser: warn_invalid_desc(path, descriptor)
    alt descriptor invalid
      Parser-->>Parser: warnings.warn(..., stacklevel=2) and skip prim
    else descriptor valid
      Parser->>Parser: process prim (material/body/joint/shape)
    end
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • vreutskyy
  • eric-heiden

📜 Recent review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 7ab1559 and bee0834.

📒 Files selected for processing (2)
  • newton/_src/utils/import_usd.py (5 hunks)
  • newton/tests/test_import_usd.py (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • newton/_src/utils/import_usd.py
  • newton/tests/test_import_usd.py
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Run GPU Unit Tests on AWS EC2 (Pull Request)
  • GitHub Check: Run GPU Benchmarks (Pull Request)
  • GitHub Check: run-newton-tests / newton-unittests (ubuntu-latest)
  • GitHub Check: run-newton-tests / newton-unittests (windows-latest)
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (8)
newton/tests/test_import_usd.py (8)

21-21: Move math import to maintain import order consistency.

The math import should be grouped with the standard library imports at the top of the file.

 import os
+import math
 import unittest

 import numpy as np
 import warp as wp
-import math
 import newton

437-437: Remove unused import and fix import placement.

The UsdGeom import is unused and the USD imports should be at the top of the file.

-        from pxr import Sdf, Usd, UsdGeom, UsdPhysics
+        from pxr import Sdf, Usd, UsdPhysics

526-526: Clean up whitespace issues throughout the method.

Multiple lines contain trailing whitespace or blank lines with whitespace that should be cleaned up.

Apply formatting fixes to remove trailing whitespace and blank lines with whitespace throughout the verify_usdphysics_parser method. Use your pre-commit hooks with pre-commit run -a as mentioned in the PR objectives to automatically fix these issues.

Also applies to: 537-537, 559-559, 561-561, 566-566, 573-573, 580-580, 583-583, 588-588, 599-599, 608-608, 614-614, 633-633, 638-638, 641-641, 646-646, 648-648, 658-658, 661-661, 671-671, 683-683, 686-686, 690-690, 698-698, 704-704, 708-708, 712-712, 716-716, 726-726, 731-731


653-653: Remove unused variable assignment.

shape_body_array is assigned but never used in the function.

-        shape_body_array = model.shape_body.numpy()

718-718: Fix import placement inside function.

The quat_between_axes import should be moved to the top of the file or the beginning of the method.

Move the import to the beginning of the verify_usdphysics_parser method:

 def verify_usdphysics_parser(self, file, model):
     """Verify model based on the UsdPhysics Parsing Utils"""
     # [1] https://openusd.org/release/api/usd_physics_page_front.html
     from pxr import Sdf, Usd, UsdPhysics
+    from newton.core import quat_between_axes

And remove it from line 718:

-                from newton.core import quat_between_axes

467-467: Rename unused loop variables to indicate they're intentionally unused.

Loop control variables that aren't used should be prefixed with underscore to indicate intentional non-use.

-        for body_path, body_desc in parsed_bodies:
+        for body_path, _ in parsed_bodies:
-            for joint_path, joint_desc in list(zip(*parsed.get(joint_objtype, ()))):
+            for joint_path, _ in list(zip(*parsed.get(joint_objtype, ()))):

Also applies to: 506-506


434-744: Consider breaking down the large verification method for better maintainability.

The verify_usdphysics_parser method is 310+ lines long with complex nested logic. Consider extracting helper methods for better readability and testability.

The method could be broken down into smaller, focused helper methods:

  • _verify_bodies(stage, parsed, model, body_key_to_idx)
  • _verify_mass_and_inertia(stage, parsed_bodies, model, body_key_to_idx)
  • _verify_joints(stage, parsed, model, joint_key_to_idx, body_key_to_idx)
  • _verify_joint_frames(model, body_positions, body_quaternions)
  • _verify_shapes(stage, parsed, model, shape_key_to_idx)

Would you like me to help refactor this into smaller, more maintainable methods?


461-490: Add error handling for USD operations that might fail.

The USD parsing operations could fail with various exceptions. Consider adding try-catch blocks to provide better error messages.

For example, wrap USD operations in error handling:

try:
    stage = Usd.Stage.Open(file)
    parsed = UsdPhysics.LoadUsdPhysicsFromRange(stage, ["/"])
except Exception as e:
    self.fail(f"Failed to parse USD file {file}: {e}")
📜 Review details

Configuration used: .coderabbit.yml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 945a55e and 4e82a729b3e662cfccd3472196af49ec8b1585e2.

📒 Files selected for processing (1)
  • newton/tests/test_import_usd.py (2 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/utils.py:30-33
Timestamp: 2025-08-12T18:07:17.267Z
Learning: In newton/_src/utils/import_usd.py, the PXR/USD modules are lazily loaded using try-except blocks within the functions rather than at module import time, making it safe to import parse_usd at the module level without causing import-time failures for users without USD dependencies.
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/utils.py:30-33
Timestamp: 2025-08-12T18:07:17.267Z
Learning: In newton/_src/utils/import_usd.py, the PXR/USD modules are lazily loaded using try-except blocks within the functions rather than at module import time, making it safe to import parse_usd at the module level without causing import-time failures for users without USD dependencies.
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/utils.py:30-33
Timestamp: 2025-08-12T18:07:17.267Z
Learning: In newton/_src/utils/import_usd.py, the PXR/USD modules are lazily loaded using try-except blocks within the functions (lines 109-111 and 1289-1291) rather than at module import time, making it safe to import parse_usd at the module level without causing import-time failures for users without USD dependencies.
📚 Learning: 2025-08-12T18:07:17.267Z
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/utils.py:30-33
Timestamp: 2025-08-12T18:07:17.267Z
Learning: In newton/_src/utils/import_usd.py, the PXR/USD modules are lazily loaded using try-except blocks within the functions rather than at module import time, making it safe to import parse_usd at the module level without causing import-time failures for users without USD dependencies.

Applied to files:

  • newton/tests/test_import_usd.py
📚 Learning: 2025-08-12T18:07:17.267Z
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/utils.py:30-33
Timestamp: 2025-08-12T18:07:17.267Z
Learning: In newton/_src/utils/import_usd.py, the PXR/USD modules are lazily loaded using try-except blocks within the functions (lines 109-111 and 1289-1291) rather than at module import time, making it safe to import parse_usd at the module level without causing import-time failures for users without USD dependencies.

Applied to files:

  • newton/tests/test_import_usd.py
🧬 Code Graph Analysis (1)
newton/tests/test_import_usd.py (3)
newton/tests/unittest_utils.py (2)
  • assert_np_equal (240-246)
  • get_test_devices (98-132)
newton/_src/utils/import_usd.py (2)
  • parse_usd (34-1274)
  • from_gfquat (172-173)
newton/_src/sim/builder.py (5)
  • body_count (499-500)
  • shape_count (495-496)
  • joint_count (503-504)
  • ModelBuilder (68-4061)
  • collapse_fixed_joints (1688-1982)
🪛 Ruff (0.12.2)
newton/tests/test_import_usd.py

437-437: import should be at the top-level of a file

(PLC0415)


437-437: pxr.UsdGeom imported but unused

Remove unused import: pxr.UsdGeom

(F401)


467-467: Loop control variable body_desc not used within loop body

(B007)


506-506: Loop control variable joint_desc not used within loop body

Rename unused joint_desc to _joint_desc

(B007)


526-526: Blank line contains whitespace

Remove whitespace from blank line

(W293)


537-537: Blank line contains whitespace

Remove whitespace from blank line

(W293)


559-559: Blank line contains whitespace

Remove whitespace from blank line

(W293)


561-561: Blank line contains whitespace

Remove whitespace from blank line

(W293)


566-566: Blank line contains whitespace

Remove whitespace from blank line

(W293)


573-573: Blank line contains whitespace

Remove whitespace from blank line

(W293)


580-580: Blank line contains whitespace

Remove whitespace from blank line

(W293)


581-581: Trailing whitespace

Remove trailing whitespace

(W291)


583-583: Blank line contains whitespace

Remove whitespace from blank line

(W293)


588-588: Blank line contains whitespace

Remove whitespace from blank line

(W293)


599-599: Blank line contains whitespace

Remove whitespace from blank line

(W293)


608-608: Blank line contains whitespace

Remove whitespace from blank line

(W293)


614-614: Blank line contains whitespace

Remove whitespace from blank line

(W293)


633-633: Blank line contains whitespace

Remove whitespace from blank line

(W293)


638-638: Blank line contains whitespace

Remove whitespace from blank line

(W293)


641-641: Blank line contains whitespace

Remove whitespace from blank line

(W293)


646-646: Blank line contains whitespace

Remove whitespace from blank line

(W293)


648-648: Blank line contains whitespace

Remove whitespace from blank line

(W293)


653-653: Local variable shape_body_array is assigned to but never used

Remove assignment to unused variable shape_body_array

(F841)


658-658: Blank line contains whitespace

Remove whitespace from blank line

(W293)


661-661: Blank line contains whitespace

Remove whitespace from blank line

(W293)


671-671: Blank line contains whitespace

Remove whitespace from blank line

(W293)


681-681: Trailing whitespace

Remove trailing whitespace

(W291)


683-683: Blank line contains whitespace

Remove whitespace from blank line

(W293)


686-686: Blank line contains whitespace

Remove whitespace from blank line

(W293)


690-690: Blank line contains whitespace

Remove whitespace from blank line

(W293)


698-698: Blank line contains whitespace

Remove whitespace from blank line

(W293)


704-704: Blank line contains whitespace

Remove whitespace from blank line

(W293)


708-708: Blank line contains whitespace

Remove whitespace from blank line

(W293)


712-712: Blank line contains whitespace

Remove whitespace from blank line

(W293)


716-716: Blank line contains whitespace

Remove whitespace from blank line

(W293)


718-718: import should be at the top-level of a file

(PLC0415)


726-726: Blank line contains whitespace

Remove whitespace from blank line

(W293)


731-731: Blank line contains whitespace

Remove whitespace from blank line

(W293)

⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Run GPU Benchmarks (Pull Request)
  • GitHub Check: Run GPU Unit Tests on AWS EC2 (Pull Request)
  • GitHub Check: run-newton-tests / newton-unittests (windows-latest)
  • GitHub Check: run-newton-tests / newton-unittests (ubuntu-latest)
🔇 Additional comments (2)
newton/tests/test_import_usd.py (2)

433-434: LGTM! Well-structured test class for USD sample assets.

The new TestImportSampleAssets class provides comprehensive USD parsing validation capabilities.


760-780: LGTM! Well-implemented test for Anymal asset.

The test correctly uses dynamic asset retrieval and proper error handling for missing files.

Comment thread newton/tests/test_import_usd.py Outdated
Comment thread newton/tests/test_import_usd.py Outdated
Comment thread newton/tests/test_import_usd.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
newton/tests/test_import_usd.py (2)

609-611: Remove debug print and compute free-joint count robustly.

Drop the debug print and avoid hard-coding “+1” for free joints. Compute the number of FREE joints present in the model.

-        print(model.joint_key)
-        self.assertEqual(len(joints_found) + 1, model.joint_count)
+        free_count = int((model.joint_type.numpy() == newton.JointType.FREE).sum())
+        self.assertEqual(len(joints_found), model.joint_count - free_count)

845-856: Replace hardcoded asset path with dynamic asset resolution (CI-friendly).

Hardcoding a local path will break in CI and on other machines. Use the same pattern as test_anymal to fetch and locate the asset.

-        asset_path = "/home/chris/Documents/usd-test-assets/isaaclab/g1.usd"
+        # Resolve G1 asset dynamically; skip if not available
+        asset_root = newton.utils.download_asset("g1_usd")
+        asset_path = None
+        for root, _, files in os.walk(asset_root):
+            if "g1.usd" in files:
+                asset_path = os.path.join(root, "g1.usd")
+                break
+        if not asset_path or not os.path.exists(asset_path):
+            raise unittest.SkipTest("G1 asset file not found")
🧹 Nitpick comments (11)
newton/tests/test_import_usd.py (11)

27-27: Avoid mixing JointType imports; use newton.JointType consistently.

Currently both newton.JointType and a direct JointType import are used. Prefer a single style to avoid confusion and improve readability.

Apply this diff:

-from newton import JointType
+# Use newton.JointType directly for consistency
-        joint_mapping = {
-            JointType.PRISMATIC: UsdPhysics.ObjectType.PrismaticJoint,
-            JointType.REVOLUTE: UsdPhysics.ObjectType.RevoluteJoint,
-            JointType.BALL: UsdPhysics.ObjectType.SphericalJoint,
-            JointType.FIXED: UsdPhysics.ObjectType.FixedJoint,
-            # JointType.FREE: None,
-            JointType.DISTANCE: UsdPhysics.ObjectType.DistanceJoint,
-            JointType.D6: UsdPhysics.ObjectType.D6Joint,
-        }
+        joint_mapping = {
+            newton.JointType.PRISMATIC: UsdPhysics.ObjectType.PrismaticJoint,
+            newton.JointType.REVOLUTE: UsdPhysics.ObjectType.RevoluteJoint,
+            newton.JointType.BALL: UsdPhysics.ObjectType.SphericalJoint,
+            newton.JointType.FIXED: UsdPhysics.ObjectType.FixedJoint,
+            # newton.JointType.FREE: None,
+            newton.JointType.DISTANCE: UsdPhysics.ObjectType.DistanceJoint,
+            newton.JointType.D6: UsdPhysics.ObjectType.D6Joint,
+        }
-            if jt == JointType.REVOLUTE:
+            if jt == newton.JointType.REVOLUTE:
                 self.assertEqual((lin, ang), (0, 1), f"{model.joint_key[j]} DOF dim mismatch")
-            elif jt == JointType.FIXED:
+            elif jt == newton.JointType.FIXED:
                 self.assertEqual((lin, ang), (0, 0), f"{model.joint_key[j]} DOF dim mismatch")
-            elif jt == JointType.FREE:
+            elif jt == newton.JointType.FREE:
                 self.assertGreater(lin + ang, 0, f"{model.joint_key[j]} expected nonzero DOFs for free joint")
-            elif jt == JointType.PRISMATIC:
+            elif jt == newton.JointType.PRISMATIC:
                 self.assertEqual((lin, ang), (1, 0), f"{model.joint_key[j]} DOF dim mismatch")
-            elif jt == JointType.BALL:
+            elif jt == newton.JointType.BALL:
                 self.assertEqual((lin, ang), (0, 3), f"{model.joint_key[j]} DOF dim mismatch")

Also applies to: 588-596, 624-633


534-537: Keep the in-function USD import (to avoid optional-dep import-time failures), but drop the unused symbol and silence the linter.

UsdGeom is unused. Keeping the import inside the function is appropriate for optional dependencies (aligned with the repo’s lazy-import pattern).

-        from pxr import Sdf, Usd, UsdGeom, UsdPhysics
+        from pxr import Sdf, Usd, UsdPhysics  # noqa: PLC0415

544-547: Use unittest assertions instead of bare assert.

Replace bare assert with self.assertIn for clearer failures and consistency with the rest of the test suite.

-            assert body_key_to_idx.get(str(body_path), None) is not None
+            self.assertIn(str(body_path), body_key_to_idx)

548-557: Collider equality may be brittle when bodies have descendant shapes.

Comment notes a TODO, but the test currently enforces strict equality. For USD assets where shapes are author-authored on descendants (common), this will fail. Consider either:

  • comparing only shapes directly attached to the body, or
  • aggregating descendant shapes for both USD and Newton before compare, or
  • relaxing to subset checks.

Would you like me to propose a helper that gathers colliders attached to the prim and descendants on both sides and compares normalized sets?


564-566: Rename unused loop variable to underscore.

body_desc is unused in this loop; rename to _ to satisfy linters and improve clarity.

-        for body_path, body_desc in parsed_bodies:
+        for body_path, _ in parsed_bodies:

603-607: Rename unused joint_desc to underscore and guard for empty parsed lists.

Minor cleanups for readability and to satisfy the linter.

-        for joint_type, joint_objtype in joint_mapping.items():
-            for joint_path, joint_desc in list(zip(*parsed.get(joint_objtype, ()))):
+        for joint_type, joint_objtype in joint_mapping.items():
+            for joint_path, _ in list(zip(*parsed.get(joint_objtype, ()))):
                 joint_idx = joint_key_to_idx.get(str(joint_path), None)
                 joints_found.append(joint_idx)
-                assert joint_key_to_idx.get(str(joint_path), None) is not None
+                self.assertIn(str(joint_path), joint_key_to_idx)
                 assert model_joint_type[joint_idx] == joint_type

612-617: Use joint_qd_start for DOF indexing instead of summing sizes.

Rely on the model’s precomputed DOF start indices to avoid off-by-ones and improve clarity.

-        body_q_array = model.body_q.numpy()
-        joint_dof_dim_array = model.joint_dof_dim.numpy()
+        body_q_array = model.body_q.numpy()
+        joint_dof_dim_array = model.joint_dof_dim.numpy()
+        joint_qd_start = model.joint_qd_start.numpy()
-            dof_index = 0 if j <= 0 else sum(int(joint_dof_dim_array[i][0] + joint_dof_dim_array[i][1]) for i in range(j))
+            dof_index = int(joint_qd_start[j])

Also applies to: 657-657


678-679: Fix trailing whitespace and make axis assertion clearer.

Eliminate trailing whitespace and format the condition for readability.

-                    self.assertTrue(all(abs(actual_axis[i] - expected_axis[i]) < 1e-6 for i in range(3)) or 
-                                  all(abs(actual_axis[i] - (-expected_axis[i])) < 1e-6 for i in range(3)))
+                    self.assertTrue(
+                        all(abs(actual_axis[i] - expected_axis[i]) < 1e-6 for i in range(3))
+                        or all(abs(actual_axis[i] - (-expected_axis[i])) < 1e-6 for i in range(3))
+                    )

750-750: Remove unused variable.

shape_body_array is assigned but never used.

-        shape_body_array = model.shape_body.numpy()

814-821: Avoid magic numbers for shape type checks; key off USD spec instead.

Using [3, 5] is brittle. Prefer checking if the USD shape has an axis attribute, which is what drives the rotation correction. Also silence the function-level import linter.

-            if newton_type in [3, 5]:
-                from newton.core import quat_between_axes
-                usd_axis = int(shape_spec.axis) if hasattr(shape_spec, 'axis') else 2
-                axis_quat = (quat_between_axes(newton.Axis.Z, newton.Axis.X) if usd_axis == 0 else
-                           quat_between_axes(newton.Axis.Z, newton.Axis.Y) if usd_axis == 1 else
-                           wp.quat_identity())
+            if hasattr(shape_spec, "axis"):
+                from newton.core import quat_between_axes  # noqa: PLC0415
+                usd_axis = int(shape_spec.axis)
+                axis_quat = (
+                    quat_between_axes(newton.Axis.Z, newton.Axis.X)
+                    if usd_axis == 0
+                    else quat_between_axes(newton.Axis.Z, newton.Axis.Y)
+                    if usd_axis == 1
+                    else wp.quat_identity()
+                )
                 expected_quat = wp.mul(usd_quat, axis_quat)
             else:
                 expected_quat = usd_quat

623-623: Trailing whitespace and whitespace-only lines.

There are multiple instances of trailing whitespace (W291) and whitespace-only blank lines (W293). Please run the project’s pre-commit hooks (ruff/black) to auto-fix.

Run:

  • pre-commit run -a
  • or: ruff --fix . && black .

Also applies to: 634-634, 656-656, 658-658, 663-663, 670-670, 677-677, 680-680, 685-685, 696-696, 705-705, 711-711, 730-730, 735-735, 738-738, 743-743, 745-745, 755-755, 758-758, 768-768, 780-780, 783-783, 787-787, 795-795, 801-801, 805-805, 809-809, 813-813, 823-823, 828-828

📜 Review details

Configuration used: .coderabbit.yml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4e82a729b3e662cfccd3472196af49ec8b1585e2 and c438ea4e0d82e233b58df0f82a9a426ca95fe0ae.

📒 Files selected for processing (1)
  • newton/tests/test_import_usd.py (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/utils.py:30-33
Timestamp: 2025-08-12T18:07:17.267Z
Learning: In newton/_src/utils/import_usd.py, the PXR/USD modules are lazily loaded using try-except blocks within the functions rather than at module import time, making it safe to import parse_usd at the module level without causing import-time failures for users without USD dependencies.
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/utils.py:30-33
Timestamp: 2025-08-12T18:07:17.267Z
Learning: In newton/_src/utils/import_usd.py, the PXR/USD modules are lazily loaded using try-except blocks within the functions rather than at module import time, making it safe to import parse_usd at the module level without causing import-time failures for users without USD dependencies.
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/utils.py:30-33
Timestamp: 2025-08-12T18:07:17.267Z
Learning: In newton/_src/utils/import_usd.py, the PXR/USD modules are lazily loaded using try-except blocks within the functions (lines 109-111 and 1289-1291) rather than at module import time, making it safe to import parse_usd at the module level without causing import-time failures for users without USD dependencies.
🧬 Code Graph Analysis (1)
newton/tests/test_import_usd.py (3)
newton/tests/unittest_utils.py (2)
  • assert_np_equal (240-246)
  • get_test_devices (98-132)
newton/_src/utils/import_usd.py (2)
  • parse_usd (34-1279)
  • from_gfquat (172-173)
newton/_src/sim/builder.py (5)
  • body_count (499-500)
  • shape_count (495-496)
  • joint_count (503-504)
  • ModelBuilder (68-4065)
  • collapse_fixed_joints (1688-1985)
🪛 Ruff (0.12.2)
newton/tests/test_import_usd.py

534-534: import should be at the top-level of a file

(PLC0415)


534-534: pxr.UsdGeom imported but unused

Remove unused import: pxr.UsdGeom

(F401)


564-564: Loop control variable body_desc not used within loop body

(B007)


603-603: Loop control variable joint_desc not used within loop body

Rename unused joint_desc to _joint_desc

(B007)


623-623: Blank line contains whitespace

Remove whitespace from blank line

(W293)


634-634: Blank line contains whitespace

Remove whitespace from blank line

(W293)


656-656: Blank line contains whitespace

Remove whitespace from blank line

(W293)


658-658: Blank line contains whitespace

Remove whitespace from blank line

(W293)


663-663: Blank line contains whitespace

Remove whitespace from blank line

(W293)


670-670: Blank line contains whitespace

Remove whitespace from blank line

(W293)


677-677: Blank line contains whitespace

Remove whitespace from blank line

(W293)


678-678: Trailing whitespace

Remove trailing whitespace

(W291)


680-680: Blank line contains whitespace

Remove whitespace from blank line

(W293)


685-685: Blank line contains whitespace

Remove whitespace from blank line

(W293)


696-696: Blank line contains whitespace

Remove whitespace from blank line

(W293)


705-705: Blank line contains whitespace

Remove whitespace from blank line

(W293)


711-711: Blank line contains whitespace

Remove whitespace from blank line

(W293)


730-730: Blank line contains whitespace

Remove whitespace from blank line

(W293)


735-735: Blank line contains whitespace

Remove whitespace from blank line

(W293)


738-738: Blank line contains whitespace

Remove whitespace from blank line

(W293)


743-743: Blank line contains whitespace

Remove whitespace from blank line

(W293)


745-745: Blank line contains whitespace

Remove whitespace from blank line

(W293)


750-750: Local variable shape_body_array is assigned to but never used

Remove assignment to unused variable shape_body_array

(F841)


755-755: Blank line contains whitespace

Remove whitespace from blank line

(W293)


758-758: Blank line contains whitespace

Remove whitespace from blank line

(W293)


768-768: Blank line contains whitespace

Remove whitespace from blank line

(W293)


778-778: Trailing whitespace

Remove trailing whitespace

(W291)


780-780: Blank line contains whitespace

Remove whitespace from blank line

(W293)


783-783: Blank line contains whitespace

Remove whitespace from blank line

(W293)


787-787: Blank line contains whitespace

Remove whitespace from blank line

(W293)


795-795: Blank line contains whitespace

Remove whitespace from blank line

(W293)


801-801: Blank line contains whitespace

Remove whitespace from blank line

(W293)


805-805: Blank line contains whitespace

Remove whitespace from blank line

(W293)


809-809: Blank line contains whitespace

Remove whitespace from blank line

(W293)


813-813: Blank line contains whitespace

Remove whitespace from blank line

(W293)


815-815: import should be at the top-level of a file

(PLC0415)


823-823: Blank line contains whitespace

Remove whitespace from blank line

(W293)


828-828: Blank line contains whitespace

Remove whitespace from blank line

(W293)

⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Run GPU Benchmarks (Pull Request)
  • GitHub Check: Run GPU Unit Tests on AWS EC2 (Pull Request)
  • GitHub Check: run-newton-tests / newton-unittests (ubuntu-latest)
  • GitHub Check: run-newton-tests / newton-unittests (windows-latest)

Comment thread newton/tests/test_import_usd.py Outdated
@camevor camevor force-pushed the usd-examples-parsing-tests branch 2 times, most recently from cea2a13 to dccb303 Compare August 21, 2025 17:38
@camevor camevor marked this pull request as ready for review August 21, 2025 18:04
@camevor camevor force-pushed the usd-examples-parsing-tests branch from 14bd1ed to 62bbe3e Compare August 21, 2025 18:04
@codecov

codecov Bot commented Aug 21, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 57.14286% with 6 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
newton/_src/utils/import_usd.py 57.14% 6 Missing ⚠️

📢 Thoughts on this report? Let us know!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (7)
newton/_src/utils/import_usd.py (2)

784-792: Make warn_invalid_desc more robust and classify the warning.

Two tweaks improve resilience and debuggability:

  • Guard against descriptors that might not expose isValid (future pxr changes) using getattr.
  • Set the warning category explicitly to UserWarning so it can be filtered independently.

Apply this diff:

-    def warn_invalid_desc(path, descriptor) -> bool:
-        if not descriptor.isValid:
-            warnings.warn(
-                f'Warning: Invalid {type(descriptor).__name__} descriptor for prim at path "{path}".',
-                stacklevel=2,
-            )
+    def warn_invalid_desc(path, descriptor) -> bool:
+        # Be defensive in case pxr changes the API and .isValid is absent
+        if not getattr(descriptor, "isValid", True):
+            warnings.warn(
+                f'Invalid {type(descriptor).__name__} descriptor for prim at path "{str(path)}".',
+                category=UserWarning,
+                stacklevel=2,
+            )
             return True
         return False

133-139: Case mismatch: meshSimplification key won’t match .lower() lookup.

You lower the input before the dict lookup (approximation.lower()), but one dict key is mixed-case. It will never match and will silently disable that remeshing path.

Apply this diff:

-    approximation_to_remeshing_method = {
+    approximation_to_remeshing_method = {
         "convexdecomposition": "coacd",
         "convexhull": "convex_hull",
         "boundingsphere": "bounding_sphere",
         "boundingcube": "bounding_box",
-        "meshSimplification": "quadratic",
+        "meshsimplification": "quadratic",
     }
newton/tests/test_import_usd.py (5)

28-29: Dual imports of parse_usd (module and qualified) — consider consistent usage.

You import parse_usd directly and also call newton.utils.parse_usd later. It’s harmless but a bit inconsistent. Prefer one style throughout the file.

- from newton.utils import parse_usd, quat_between_axes
+ from newton.utils import parse_usd, quat_between_axes
...
-        newton.utils.parse_usd(
+        parse_usd(
             str(asset_path),
             builder,

554-557: Collider set equality should ignore visual-only shapes.

model_collisions is built from all shapes attached to the body; if visual shapes are present (COLLIDE_SHAPES off), this will over-count and fail equality against USD’s collisions. Filter by the collide flag.

Apply this diff:

-            model_collisions = {model.shape_key[sk] for sk in model.body_shapes[body_idx]}
+            # Only compare physics collision shapes, not visuals
+            flags = model.shape_flags.numpy()
+            model_collisions = {
+                model.shape_key[sk]
+                for sk in model.body_shapes[body_idx]
+                if (int(flags[sk]) & int(newton.ShapeFlags.COLLIDE_SHAPES)) != 0
+            }

603-604: Free-joint accounting is brittle; compute it instead of assuming +1.

Assets may have zero or multiple automatically-added free joints (e.g., multiple floating subgraphs). Derive the count from the model.

Apply this diff:

-        self.assertEqual(len(joints_found) + 1, model.joint_count)
+        model_joint_types = model.joint_type.numpy()
+        num_free = int(np.sum(model_joint_types == JointType.FREE))
+        self.assertEqual(len(joints_found) + num_free, model.joint_count)

832-845: Account for prim-local axis scale when validating shape dimensions.

The parser multiplies radii/half-heights by the prim’s local scale components (and reorients by axis). The assertions compare against unscaled USD values, which can false-fail when assets use authored scales.

Apply this diff to incorporate per-axis scale:

-            if newton_type in [3, 5]:
+            if newton_type in [3, 5]:
                 usd_axis = int(shape_spec.axis) if hasattr(shape_spec, "axis") else 2
                 axis_quat = (
                     quat_between_axes(newton.Axis.Z, newton.Axis.X)
                     if usd_axis == 0
                     else quat_between_axes(newton.Axis.Z, newton.Axis.Y)
                     if usd_axis == 1
                     else wp.quat_identity()
                 )
                 expected_quat = wp.mul(usd_quat, axis_quat)
             else:
                 expected_quat = usd_quat
@@
-            if newton_type == newton.GeoType.CAPSULE:
-                self.assertAlmostEqual(newton_scale[0], shape_spec.radius, places=5)
-                self.assertAlmostEqual(newton_scale[1], shape_spec.halfHeight, places=5)
+            # Extract authored local scale (only last authored scale op is used in parser)
+            from pxr import UsdGeom  # noqa: PLC0415
+            xf = UsdGeom.Xform(prim)
+            local_scale = np.ones(3, dtype=np.float32)
+            for op in xf.GetOrderedXformOps():
+                if op.GetOpType() == UsdGeom.XformOp.TypeScale:
+                    local_scale = np.array(op.Get(), dtype=np.float32)
+            if newton_type == newton.GeoType.CAPSULE:
+                usd_axis = int(getattr(shape_spec, "axis", 2))
+                self.assertAlmostEqual(newton_scale[0], shape_spec.radius * local_scale[(usd_axis + 1) % 3], places=5)
+                self.assertAlmostEqual(newton_scale[1], shape_spec.halfHeight * local_scale[usd_axis], places=5)
             elif newton_type == newton.GeoType.BOX:
                 for i, (n_scale, u_extent) in enumerate(zip(newton_scale, shape_spec.halfExtents)):
                     self.assertAlmostEqual(
                         n_scale, u_extent, places=5, msg=f"Box {sid} extent[{i}]: USD={u_extent}, Newton={n_scale}"
                     )
             elif newton_type == newton.GeoType.SPHERE:
-                self.assertAlmostEqual(newton_scale[0], shape_spec.radius, places=5)
+                self.assertAlmostEqual(newton_scale[0], shape_spec.radius * local_scale[0], places=5)
             elif newton_type == newton.GeoType.CYLINDER:
-                self.assertAlmostEqual(newton_scale[0], shape_spec.radius, places=5)
-                self.assertAlmostEqual(newton_scale[1], shape_spec.halfHeight, places=5)
+                usd_axis = int(getattr(shape_spec, "axis", 2))
+                self.assertAlmostEqual(newton_scale[0], shape_spec.radius * local_scale[(usd_axis + 1) % 3], places=5)
+                self.assertAlmostEqual(newton_scale[1], shape_spec.halfHeight * local_scale[usd_axis], places=5)

Also applies to: 852-865


867-880: Asset tests (g1, anymal) are well-scoped and CI-friendly.

  • Uses newton.utils.download_asset to avoid hardcoded local paths.
  • Skips gracefully when USD is unavailable.
  • Validates via the shared verification harness.

One nit: unify the parse_usd import style (see earlier comment).

Also applies to: 882-902

📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c438ea4e0d82e233b58df0f82a9a426ca95fe0ae and 7c0b155f088d8d56c1db347e7e9a5d2ce5996616.

📒 Files selected for processing (2)
  • newton/_src/utils/import_usd.py (5 hunks)
  • newton/tests/test_import_usd.py (3 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: shi-eric
PR: newton-physics/newton#521
File: newton/examples/example_cloth_hanging.py:36-36
Timestamp: 2025-08-12T05:17:34.423Z
Learning: The Newton migration guide (docs/migration.rst) is specifically for documenting how to migrate existing warp.sim functionality to Newton equivalents. New Newton-only features that didn't exist in warp.sim do not need migration documentation.
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/utils.py:30-33
Timestamp: 2025-08-12T18:07:17.267Z
Learning: In newton/_src/utils/import_usd.py, the PXR/USD modules are lazily loaded using try-except blocks within the functions rather than at module import time, making it safe to import parse_usd at the module level without causing import-time failures for users without USD dependencies.
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/utils.py:30-33
Timestamp: 2025-08-12T18:07:17.267Z
Learning: In newton/_src/utils/import_usd.py, the PXR/USD modules are lazily loaded using try-except blocks within the functions rather than at module import time, making it safe to import parse_usd at the module level without causing import-time failures for users without USD dependencies.
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/utils.py:30-33
Timestamp: 2025-08-12T18:07:17.267Z
Learning: In newton/_src/utils/import_usd.py, the PXR/USD modules are lazily loaded using try-except blocks within the functions (lines 109-111 and 1289-1291) rather than at module import time, making it safe to import parse_usd at the module level without causing import-time failures for users without USD dependencies.
📚 Learning: 2025-08-12T18:07:17.267Z
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/utils.py:30-33
Timestamp: 2025-08-12T18:07:17.267Z
Learning: In newton/_src/utils/import_usd.py, the PXR/USD modules are lazily loaded using try-except blocks within the functions rather than at module import time, making it safe to import parse_usd at the module level without causing import-time failures for users without USD dependencies.

Applied to files:

  • newton/_src/utils/import_usd.py
📚 Learning: 2025-08-12T18:07:17.267Z
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/utils.py:30-33
Timestamp: 2025-08-12T18:07:17.267Z
Learning: In newton/_src/utils/import_usd.py, the PXR/USD modules are lazily loaded using try-except blocks within the functions (lines 109-111 and 1289-1291) rather than at module import time, making it safe to import parse_usd at the module level without causing import-time failures for users without USD dependencies.

Applied to files:

  • newton/_src/utils/import_usd.py
🧬 Code graph analysis (1)
newton/tests/test_import_usd.py (1)
newton/_src/utils/import_usd.py (2)
  • parse_usd (35-1315)
  • from_gfquat (173-174)
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Run GPU Benchmarks (Pull Request)
  • GitHub Check: Run GPU Unit Tests on AWS EC2 (Pull Request)
  • GitHub Check: run-newton-tests / newton-unittests (ubuntu-latest)
  • GitHub Check: run-newton-tests / newton-unittests (windows-latest)
🔇 Additional comments (5)
newton/_src/utils/import_usd.py (2)

21-21: Importing warnings is appropriate for parser-level soft failures.

Using the warnings subsystem (vs print) is the right call for invalid USD descriptors. No issues here.


795-797: Early-return skip points on invalid descriptors look good.

Placing the skip directly at the top of each loop is clear and prevents partial state mutation. This should reduce hard-to-reproduce errors when datasets contain malformed prims.

Also applies to: 808-810, 850-852, 990-992

newton/tests/test_import_usd.py (3)

16-16: Import math for angle/DOF checks — OK.

Needed for radians/degrees conversions below.


25-25: Importing JointType directly simplifies readability.

This reduces newton.JointType verbosity in assertions.


531-866: Great end-to-end validation utility for USD assets.

The helper cross-validates bodies, shapes, joints, transforms, and properties against pxr’s parsed descriptors. This will catch regressions in both parser and model-building logic. Nice coverage.

Comment thread newton/tests/test_import_usd.py Outdated
camevor and others added 11 commits August 22, 2025 19:28
Signed-off-by: camevor <camevor@nvidia.com>
Signed-off-by: camevor <camevor@nvidia.com>
Signed-off-by: camevor <camevor@nvidia.com>
Signed-off-by: camevor <camevor@nvidia.com>
Signed-off-by: camevor <camevor@nvidia.com>
Signed-off-by: camevor <camevor@nvidia.com>
Signed-off-by: camevor <camevor@nvidia.com>
Signed-off-by: camevor <camevor@nvidia.com>
Signed-off-by: camevor <camevor@nvidia.com>
Signed-off-by: camevor <camevor@nvidia.com>
@camevor camevor force-pushed the usd-examples-parsing-tests branch from 43e743e to 4fa219f Compare August 22, 2025 17:28
@camevor camevor linked an issue Aug 22, 2025 that may be closed by this pull request
@camevor camevor requested review from eric-heiden and kaloca August 22, 2025 17:31

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
newton/tests/test_import_usd.py (1)

282-286: Guard USD-dependent test with skipUnless

This test imports pxr modules and will fail in environments without USD. Add the same guard used elsewhere.

-    def test_mesh_approximation(self):
+    @unittest.skipUnless(USD_AVAILABLE, "Requires usd-core")
+    def test_mesh_approximation(self):
         from pxr import Gf, Usd, UsdGeom, UsdPhysics  # noqa: PLC0415
♻️ Duplicate comments (1)
newton/tests/test_import_usd.py (1)

714-728: Correct drive gains scaling: use per-joint-type factor (angular vs. linear)

Using math.degrees(drive_gain_scale) treats the scale as an angle and applies the same factor to linear joints. Angular drives should be scaled by drive_gain_scale * (180/pi), linear drives by drive_gain_scale only.

Apply this diff:

-            if ke_attr:
+            if ke_attr:
                 ke_val = ke_attr.Get() if ke_attr.HasAuthoredValue() else None
                 if ke_val is not None:
                     ke = float(ke_val)
-                    self.assertAlmostEqual(
-                        float(model.joint_target_ke.numpy()[dof_index]), ke * math.degrees(drive_gain_scale), places=2
-                    )
+                    gain_scale = (math.degrees(1.0) * drive_gain_scale) if prim.IsA(UsdPhysics.RevoluteJoint) else drive_gain_scale
+                    self.assertAlmostEqual(
+                        float(model.joint_target_ke.numpy()[dof_index]),
+                        ke * gain_scale,
+                        places=2,
+                    )
 
-            if kd_attr:
+            if kd_attr:
                 kd_val = kd_attr.Get() if kd_attr.HasAuthoredValue() else None
                 if kd_val is not None:
                     kd = float(kd_val)
-                    self.assertAlmostEqual(
-                        float(model.joint_target_kd.numpy()[dof_index]), kd * math.degrees(drive_gain_scale), places=2
-                    )
+                    gain_scale = (math.degrees(1.0) * drive_gain_scale) if prim.IsA(UsdPhysics.RevoluteJoint) else drive_gain_scale
+                    self.assertAlmostEqual(
+                        float(model.joint_target_kd.numpy()[dof_index]),
+                        kd * gain_scale,
+                        places=2,
+                    )
🧹 Nitpick comments (6)
newton/tests/test_import_usd.py (6)

605-607: Compute expected joint count from model free joints rather than a boolean

Assets can have multiple floating bodies; counting free joints directly is more robust than assuming exactly one when floating=True.

-        expected_model_joints = len(joints_found) + 1 if floating else len(joints_found)
-        self.assertEqual(model.joint_count, expected_model_joints)
+        # Count free joints as imported to avoid assumptions about # of floating bodies.
+        free_joints = int(np.count_nonzero(model.joint_type.numpy() == JointType.FREE))
+        expected_model_joints = len(joints_found) + free_joints
+        self.assertEqual(model.joint_count, expected_model_joints)

835-846: Prefer enums over magic numbers for shape types

Use newton.GeoType.CAPSULE and newton.GeoType.CYLINDER instead of [3, 5] to improve readability and reduce risk of mismatch if enum values change.

-            if newton_type in [3, 5]:
+            if newton_type in (newton.GeoType.CAPSULE, newton.GeoType.CYLINDER):

763-763: Remove no-op call

model.shape_body.numpy() is not used; drop it to reduce noise.

-        model.shape_body.numpy()

84-87: Normalize bitmask checks for collision flag

Bitwise checks mix int(enum) and raw enum usages across the file. For consistency and to avoid surprises with IntFlag vs IntEnum, wrap the enum in int(...) here, as done earlier in Line 57.

-        collision_shapes = [
-            i for i in range(builder.shape_count) if builder.shape_flags[i] & newton.ShapeFlags.COLLIDE_SHAPES
-        ]
+        collision_shapes = [
+            i for i in range(builder.shape_count)
+            if builder.shape_flags[i] & int(newton.ShapeFlags.COLLIDE_SHAPES)
+        ]

27-31: Drop unused get_test_devices and devices

devices = get_test_devices() is never used in this file. Removing the import and assignment avoids lint warnings.

-from newton.tests.unittest_utils import USD_AVAILABLE, assert_np_equal, get_test_devices
+from newton.tests.unittest_utils import USD_AVAILABLE, assert_np_equal
-
-devices = get_test_devices()

874-880: Unify parse_usd usage style

You import parse_usd at Line 28 but still call newton.utils.parse_usd in several places. Pick one style (prefer the imported name) for consistency.

Would you like me to submit a follow-up patch to switch all calls in this file to the imported parse_usd?

Also applies to: 896-902, 911-917, 926-932, 941-947

📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 7c0b155f088d8d56c1db347e7e9a5d2ce5996616 and 4fa219f.

📒 Files selected for processing (2)
  • newton/_src/utils/import_usd.py (6 hunks)
  • newton/tests/test_import_usd.py (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • newton/_src/utils/import_usd.py
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: shi-eric
PR: newton-physics/newton#521
File: newton/examples/example_cloth_hanging.py:36-36
Timestamp: 2025-08-12T05:17:34.423Z
Learning: The Newton migration guide (docs/migration.rst) is specifically for documenting how to migrate existing warp.sim functionality to Newton equivalents. New Newton-only features that didn't exist in warp.sim do not need migration documentation.
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/utils.py:30-33
Timestamp: 2025-08-12T18:07:17.267Z
Learning: In newton/_src/utils/import_usd.py, the PXR/USD modules are lazily loaded using try-except blocks within the functions rather than at module import time, making it safe to import parse_usd at the module level without causing import-time failures for users without USD dependencies.
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/utils.py:30-33
Timestamp: 2025-08-12T18:07:17.267Z
Learning: In newton/_src/utils/import_usd.py, the PXR/USD modules are lazily loaded using try-except blocks within the functions rather than at module import time, making it safe to import parse_usd at the module level without causing import-time failures for users without USD dependencies.
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/utils.py:30-33
Timestamp: 2025-08-12T18:07:17.267Z
Learning: In newton/_src/utils/import_usd.py, the PXR/USD modules are lazily loaded using try-except blocks within the functions (lines 109-111 and 1289-1291) rather than at module import time, making it safe to import parse_usd at the module level without causing import-time failures for users without USD dependencies.
📚 Learning: 2025-08-12T18:07:17.267Z
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/utils.py:30-33
Timestamp: 2025-08-12T18:07:17.267Z
Learning: In newton/_src/utils/import_usd.py, the PXR/USD modules are lazily loaded using try-except blocks within the functions rather than at module import time, making it safe to import parse_usd at the module level without causing import-time failures for users without USD dependencies.

Applied to files:

  • newton/tests/test_import_usd.py
📚 Learning: 2025-08-12T18:07:17.267Z
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/utils.py:30-33
Timestamp: 2025-08-12T18:07:17.267Z
Learning: In newton/_src/utils/import_usd.py, the PXR/USD modules are lazily loaded using try-except blocks within the functions (lines 109-111 and 1289-1291) rather than at module import time, making it safe to import parse_usd at the module level without causing import-time failures for users without USD dependencies.

Applied to files:

  • newton/tests/test_import_usd.py
🧬 Code graph analysis (1)
newton/tests/test_import_usd.py (5)
newton/tests/unittest_utils.py (1)
  • assert_np_equal (240-246)
newton/_src/utils/import_usd.py (2)
  • parse_usd (35-1315)
  • from_gfquat (173-174)
newton/_src/sim/builder.py (2)
  • ModelBuilder (67-4099)
  • collapse_fixed_joints (1739-2036)
newton/_src/geometry/types.py (1)
  • GeoType (25-36)
newton/examples/__init__.py (1)
  • get_asset (32-33)

Comment thread newton/tests/test_import_usd.py
@linux-foundation-easycla

linux-foundation-easycla Bot commented Aug 25, 2025

Copy link
Copy Markdown

CLA Signed

The committers listed above are authorized under a signed CLA.

@eric-heiden eric-heiden left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This looks good to me.
@jvonmuralt can you sign the CLA please?
@camevor can you resolve the merge conflicts, then we can get it in.

@camevor camevor enabled auto-merge (squash) September 1, 2025 12:56

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
newton/tests/test_import_usd.py (1)

272-276: Guard this test with USD availability.

This test imports PXR modules but lacks the @skipUnless(USD_AVAILABLE, ...) guard used elsewhere. Without USD, CI will error instead of skip.

+    @unittest.skipUnless(USD_AVAILABLE, "Requires usd-core")
     def test_mesh_approximation(self):
         from pxr import Gf, Usd, UsdGeom, UsdPhysics  # noqa: PLC0415
♻️ Duplicate comments (2)
newton/tests/test_import_usd.py (2)

701-716: Fix KE/KD scaling: use drive_gain_scale / radians(1) (not degrees(drive_gain_scale)).

Current assertions treat the scale as an angle. Expect scale * (180/π) for angular drives.

-                    self.assertAlmostEqual(
-                        float(model.joint_target_ke.numpy()[dof_index]), ke * math.degrees(drive_gain_scale), places=2
-                    )
+                    self.assertAlmostEqual(
+                        float(model.joint_target_ke.numpy()[dof_index]),
+                        ke * drive_gain_scale * math.degrees(1.0),
+                        places=2,
+                    )
...
-                    self.assertAlmostEqual(
-                        float(model.joint_target_kd.numpy()[dof_index]), kd * math.degrees(drive_gain_scale), places=2
-                    )
+                    self.assertAlmostEqual(
+                        float(model.joint_target_kd.numpy()[dof_index]),
+                        kd * drive_gain_scale * math.degrees(1.0),
+                        places=2,
+                    )

559-566: Fix principal-axes quaternion misuse (Gf.Quat.Normalize returns magnitude, not a quaternion).

This assigns a float to principal_axes and produces a wrong inertia rotation. Build a normalized Warp quaternion from the Gf.Quat components (or use GetNormalized) before forming the rotation matrix.

-                if mass_api.GetDiagonalInertiaAttr().HasAuthoredValue():
-                    diag_inertia = mass_api.GetDiagonalInertiaAttr().Get()
-                    principal_axes = mass_api.GetPrincipalAxesAttr().Get().Normalize()
-                    p = np.array(wp.quat_to_matrix(wp.quat(*principal_axes.imaginary, principal_axes.real))).reshape(
-                        (3, 3)
-                    )
+                if mass_api.GetDiagonalInertiaAttr().HasAuthoredValue():
+                    diag_inertia = mass_api.GetDiagonalInertiaAttr().Get()
+                    gfq = mass_api.GetPrincipalAxesAttr().Get()
+                    if gfq is not None:
+                        ix, iy, iz = gfq.GetImaginary()
+                        w = gfq.GetReal()
+                        q_wp = wp.normalize(wp.quat(ix, iy, iz, w))
+                    else:
+                        q_wp = wp.quat_identity()
+                    p = np.array(wp.quat_to_matrix(q_wp)).reshape(3, 3)
🧹 Nitpick comments (4)
newton/tests/test_import_usd.py (4)

822-833: Avoid magic numbers for GeoType; use enum members.

Using raw ints [3, 5] is brittle; prefer newton.GeoType.CAPSULE and newton.GeoType.CYLINDER.

-            if newton_type in [3, 5]:
+            if newton_type in [newton.GeoType.CAPSULE, newton.GeoType.CYLINDER]:

640-643: Use precomputed DOF start to avoid O(n²) summation and off-by-one risks.

model.joint_qd_start is available and clearer.

-            dof_index = (
-                0 if j <= 0 else sum(int(joint_dof_dim_array[i][0] + joint_dof_dim_array[i][1]) for i in range(j))
-            )
+            dof_index = int(model.joint_qd_start.numpy()[j])

539-545: Collider set equivalence may fail for bodies with child shapes.

Equality assumes all collisions are authored directly on the body prim. USD scenes often attach colliders to children; consider normalizing to the set of all descendant collider paths for comparison or limit the check to bodies without collider children.


30-31: Remove unused variable.

devices = get_test_devices() is never used.

-devices = get_test_devices()
+# devices = get_test_devices()  # Unused; remove if not needed.
📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4fa219f and 7ab1559.

📒 Files selected for processing (2)
  • newton/_src/utils/import_usd.py (6 hunks)
  • newton/tests/test_import_usd.py (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • newton/_src/utils/import_usd.py
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: shi-eric
PR: newton-physics/newton#521
File: newton/examples/example_cloth_hanging.py:36-36
Timestamp: 2025-08-12T05:17:34.423Z
Learning: The Newton migration guide (docs/migration.rst) is specifically for documenting how to migrate existing warp.sim functionality to Newton equivalents. New Newton-only features that didn't exist in warp.sim do not need migration documentation.
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/utils.py:30-33
Timestamp: 2025-08-12T18:07:17.267Z
Learning: In newton/_src/utils/import_usd.py, the PXR/USD modules are lazily loaded using try-except blocks within the functions rather than at module import time, making it safe to import parse_usd at the module level without causing import-time failures for users without USD dependencies.
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/utils.py:30-33
Timestamp: 2025-08-12T18:07:17.267Z
Learning: In newton/_src/utils/import_usd.py, the PXR/USD modules are lazily loaded using try-except blocks within the functions rather than at module import time, making it safe to import parse_usd at the module level without causing import-time failures for users without USD dependencies.
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/utils.py:30-33
Timestamp: 2025-08-12T18:07:17.267Z
Learning: In newton/_src/utils/import_usd.py, the PXR/USD modules are lazily loaded using try-except blocks within the functions (lines 109-111 and 1289-1291) rather than at module import time, making it safe to import parse_usd at the module level without causing import-time failures for users without USD dependencies.
📚 Learning: 2025-08-12T18:07:17.267Z
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/utils.py:30-33
Timestamp: 2025-08-12T18:07:17.267Z
Learning: In newton/_src/utils/import_usd.py, the PXR/USD modules are lazily loaded using try-except blocks within the functions rather than at module import time, making it safe to import parse_usd at the module level without causing import-time failures for users without USD dependencies.

Applied to files:

  • newton/tests/test_import_usd.py
📚 Learning: 2025-08-12T18:07:17.267Z
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/utils.py:30-33
Timestamp: 2025-08-12T18:07:17.267Z
Learning: In newton/_src/utils/import_usd.py, the PXR/USD modules are lazily loaded using try-except blocks within the functions (lines 109-111 and 1289-1291) rather than at module import time, making it safe to import parse_usd at the module level without causing import-time failures for users without USD dependencies.

Applied to files:

  • newton/tests/test_import_usd.py
📚 Learning: 2025-08-18T15:56:26.587Z
Learnt from: adenzler-nvidia
PR: newton-physics/newton#552
File: newton/_src/solvers/mujoco/solver_mujoco.py:0-0
Timestamp: 2025-08-18T15:56:26.587Z
Learning: In Newton's MuJoCo solver, when transforming joint axes from Newton's internal frame to MuJoCo's expected frame, use wp.quat_rotate(joint_rot, axis) not wp.quat_rotate_inv(joint_rot, axis). The joint_rot represents rotation from joint-local to body frame, so forward rotation is correct.

Applied to files:

  • newton/tests/test_import_usd.py
🧬 Code graph analysis (1)
newton/tests/test_import_usd.py (5)
newton/_src/sim/joints.py (1)
  • JointType (20-44)
newton/_src/core/spatial.py (1)
  • quat_between_axes (306-329)
newton/_src/geometry/types.py (1)
  • GeoType (25-64)
newton/_src/geometry/flags.py (1)
  • ShapeFlags (30-42)
newton/_src/core/types.py (1)
  • Axis (64-161)
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Run GPU Benchmarks (Pull Request)
  • GitHub Check: Run GPU Unit Tests on AWS EC2 (Pull Request)
🔇 Additional comments (1)
newton/tests/test_import_usd.py (1)

870-889: Nice: asset resolution is CI-friendly.

Switching to newton.utils.download_asset(...) and walking to anymal_d.usda removes machine-specific paths and will work in CI.

Comment thread newton/tests/test_import_usd.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
newton/tests/test_import_usd.py (1)

272-279: Add USD guard to prevent failures when USD isn’t installed.

This test imports pxr but lacks @skipUnless, unlike the others. It will fail in environments without USD.

+    @unittest.skipUnless(USD_AVAILABLE, "Requires usd-core")
     def test_mesh_approximation(self):
         from pxr import Gf, Usd, UsdGeom, UsdPhysics  # noqa: PLC0415
♻️ Duplicate comments (2)
newton/tests/test_import_usd.py (2)

559-566: Fix principal-axes quaternion misuse: Normalize() returns magnitude, not a quaternion.

This assigns a float to principal_axes and produces wrong inertia rotation.

-                    principal_axes = mass_api.GetPrincipalAxesAttr().Get().Normalize()
-                    p = np.array(wp.quat_to_matrix(wp.quat(*principal_axes.imaginary, principal_axes.real))).reshape(
-                        (3, 3)
-                    )
+                    gfq = mass_api.GetPrincipalAxesAttr().Get()
+                    if gfq is not None:
+                        ix, iy, iz = gfq.GetImaginary()
+                        w = gfq.GetReal()
+                        q_wp = wp.normalize(wp.quat(ix, iy, iz, w))
+                    else:
+                        q_wp = wp.quat_identity()
+                    p = np.array(wp.quat_to_matrix(q_wp)).reshape(3, 3)

701-715: Correct angular drive gain scaling: don’t use degrees(drive_gain_scale).

Importer scales angular gains by drive_gain_scale / radians(1) = drive_gain_scale * degrees(1). Current code treats the scale as an angle.

-                    self.assertAlmostEqual(
-                        float(model.joint_target_ke.numpy()[dof_index]), ke * math.degrees(drive_gain_scale), places=2
-                    )
+                    self.assertAlmostEqual(
+                        float(model.joint_target_ke.numpy()[dof_index]),
+                        ke * math.degrees(1.0) * drive_gain_scale,
+                        places=2,
+                    )
...
-                    self.assertAlmostEqual(
-                        float(model.joint_target_kd.numpy()[dof_index]), kd * math.degrees(drive_gain_scale), places=2
-                    )
+                    self.assertAlmostEqual(
+                        float(model.joint_target_kd.numpy()[dof_index]),
+                        kd * math.degrees(1.0) * drive_gain_scale,
+                        places=2,
+                    )
🧹 Nitpick comments (2)
newton/tests/test_import_usd.py (2)

640-643: Use joint_qd_start for O(1) DOF start index.

Avoid recomputing prefix sums each loop.

-            dof_index = (
-                0 if j <= 0 else sum(int(joint_dof_dim_array[i][0] + joint_dof_dim_array[i][1]) for i in range(j))
-            )
+            dof_index = int(model.joint_qd_start.numpy()[j])

750-750: Remove unused call.

model.shape_body.numpy() return value isn’t used.

-        model.shape_body.numpy()
📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4fa219f and 7ab1559.

📒 Files selected for processing (2)
  • newton/_src/utils/import_usd.py (6 hunks)
  • newton/tests/test_import_usd.py (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • newton/_src/utils/import_usd.py
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/utils.py:30-33
Timestamp: 2025-08-12T18:07:17.267Z
Learning: In newton/_src/utils/import_usd.py, the PXR/USD modules are lazily loaded using try-except blocks within the functions rather than at module import time, making it safe to import parse_usd at the module level without causing import-time failures for users without USD dependencies.
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/utils.py:30-33
Timestamp: 2025-08-12T18:07:17.267Z
Learning: In newton/_src/utils/import_usd.py, the PXR/USD modules are lazily loaded using try-except blocks within the functions rather than at module import time, making it safe to import parse_usd at the module level without causing import-time failures for users without USD dependencies.
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/utils.py:30-33
Timestamp: 2025-08-12T18:07:17.267Z
Learning: In newton/_src/utils/import_usd.py, the PXR/USD modules are lazily loaded using try-except blocks within the functions (lines 109-111 and 1289-1291) rather than at module import time, making it safe to import parse_usd at the module level without causing import-time failures for users without USD dependencies.
📚 Learning: 2025-08-18T15:56:26.587Z
Learnt from: adenzler-nvidia
PR: newton-physics/newton#552
File: newton/_src/solvers/mujoco/solver_mujoco.py:0-0
Timestamp: 2025-08-18T15:56:26.587Z
Learning: In Newton's MuJoCo solver, when transforming joint axes from Newton's internal frame to MuJoCo's expected frame, use wp.quat_rotate(joint_rot, axis) not wp.quat_rotate_inv(joint_rot, axis). The joint_rot represents rotation from joint-local to body frame, so forward rotation is correct.

Applied to files:

  • newton/tests/test_import_usd.py
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Run GPU Benchmarks (Pull Request)
  • GitHub Check: Run GPU Unit Tests on AWS EC2 (Pull Request)

Comment thread newton/tests/test_import_usd.py
@camevor camevor merged commit b8eea07 into newton-physics:main Sep 2, 2025
11 of 12 checks passed
@camevor camevor deleted the usd-examples-parsing-tests branch September 2, 2025 12:39
eric-heiden pushed a commit to eric-heiden/newton that referenced this pull request Jan 28, 2026
Signed-off-by: camevor <camevor@nvidia.com>
Co-authored-by: jprozorova <jprozorova@nvidia.com>
vidurv-nvidia pushed a commit to vidurv-nvidia/newton that referenced this pull request Mar 6, 2026
…s#572)

# Description

Jax >= 0.6.0 is incompatible with torch 2.7, but is installed by default
when running pip install skrl[jax]. We update the installation
instructions to override the version of jax to be < 0.6.0 after
installing skrl dependencies to avoid the version incompatibilities.


## Type of change

- This change requires a documentation update

## Checklist

- [x] I have run the [`pre-commit` checks](https://pre-commit.com/) with
`./isaaclab.sh --format`
- [x] I have made corresponding changes to the documentation
- [x] 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

<!--
As you go through the checklist above, you can mark something as done by
putting an x character in it

For example,
- [x] I have done this task
- [ ] I have not done this task
-->
mmacklin pushed a commit to mmacklin/newton that referenced this pull request Apr 7, 2026
Signed-off-by: camevor <camevor@nvidia.com>
Co-authored-by: jprozorova <jprozorova@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

USD Parsing Testing for P0 Examples

3 participants