Skip to content

Conversation

WenchaoHuang
Copy link
Contributor

@WenchaoHuang WenchaoHuang commented Sep 24, 2025

Description

Implemented following improvements:

  • Configure timecode rate for correct USD animation timing, enabling DCC software to accurately read the data frame rate.
  • Implement vertex and face varying UV support for USD meshes.

Newton Migration Guide

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

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

Before your PR is "Ready for review"

  • 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

  • New Features
    • Optional face-varying UV support when logging meshes; default behavior unchanged.
    • UVs now exported to USD with correct interpolation (vertex or face-varying) based on the new option.
  • Improvements
    • USD exports now set both time codes per second and display FPS for more accurate timeline handling.
  • Documentation
    • API docs and docstrings updated to describe the new UV option.

Copy link
Contributor

coderabbitai bot commented Sep 24, 2025

📝 Walkthrough

Walkthrough

Adds an optional face_varying_uv parameter to log_mesh across viewer interfaces and backends. ViewerUSD implements UV primvar creation/assignment with interpolation chosen by the flag and sets both TimeCodesPerSecond and FramesPerSecond on the USD stage. Other backends only gain the new parameter and docstrings.

Changes

Cohort / File(s) Summary of changes
API: log_mesh signature expanded
newton/_src/viewer/viewer.py, newton/_src/viewer/viewer_file.py, newton/_src/viewer/viewer_gl.py, newton/_src/viewer/viewer_null.py, newton/_src/viewer/viewer_rerun.py
Added optional boolean parameter face_varying_uv=False to log_mesh signatures and updated docstrings where present. No behavioral changes in these backends.
USD backend: UV handling and timing
newton/_src/viewer/viewer_usd.py
log_mesh now creates/updates the st primvar from uvs, selects interpolation (faceVarying when face_varying_uv=True, otherwise vertex), validates UV length accordingly, and assigns UV data. USD stage initialization now calls SetTimeCodesPerSecond(fps) and SetFramesPerSecond(fps).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Client
  participant ViewerBase
  participant ViewerUSD
  participant USDStage as "USD Stage"
  participant MeshPrim as "Mesh Prim"

  Client->>ViewerBase: log_mesh(name, points, indices, normals, uvs, ..., face_varying_uv)
  ViewerBase->>ViewerUSD: log_mesh(...)
  ViewerUSD->>USDStage: Ensure stage (SetTimeCodesPerSecond(fps), SetFramesPerSecond(fps))
  ViewerUSD->>MeshPrim: Define/get mesh prim and set geometry
  alt uvs provided
    ViewerUSD->>MeshPrim: Get/create "st" primvar
    alt face_varying_uv == true
      note right of MeshPrim: interpolation = faceVarying\nexpect len(uvs) == len(indices)
    else
      note right of MeshPrim: interpolation = vertex\nexpect len(uvs) == len(points)
    end
    ViewerUSD->>MeshPrim: Write UV data to primvar
  else no uvs
    note right of ViewerUSD: Skip UV primvar handling
  end
  ViewerUSD-->>Client: return
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • Usd viewer update #629: Modifies Viewer.log_mesh API and USD handling; likely intersects with this change to face-varying UVs.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title “USD-Exporting improvements” directly reflects the primary changes to USD export functionality, namely configuring timecode rates and adding face-varying UV support, and it remains concise and clear for anyone browsing the history.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ 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

Comment @coderabbitai help to get the list of available commands and usage tips.

indices (wp.array): Triangle indices (wp.uint32).
normals (wp.array, optional): Vertex normals (wp.vec3).
uvs (wp.array, optional): UV coordinates (wp.vec2).
uv_indices (wp.array, optional): Triangle indices (wp.uint32) for UVs
Copy link
Member

Choose a reason for hiding this comment

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

This argument doesn't exist here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

assert len(uvs) == len(indices_np)
st.SetInterpolation(UsdGeom.Tokens.faceVarying)
else:
assert len(uvs) == len(points_np)
Copy link
Member

Choose a reason for hiding this comment

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

It would be good to raise a ValueError here with a useful error message.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Addressed. Please take a look.

points (wp.array): Vertex positions as a warp array of wp.vec3.
indices (wp.array): Triangle indices as a warp array of wp.uint32.
normals (wp.array, optional): Vertex normals as a warp array of wp.vec3.
uvs (wp.array, optional): UV coordinates as a warp array of wp.vec2.
Copy link
Member

Choose a reason for hiding this comment

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

Can you clarify here the shape of these UV coordinates and how this is affected by face_varying_uv?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added

hidden (bool, optional): If True, mesh will be hidden. Default is False.
backface_culling (bool, optional): If True, enable backface culling. Default is True.
face_varying_uv (bool, optional): If True, enable face varying UV. Default is False (vertex varying).
Copy link
Member

Choose a reason for hiding this comment

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

Nit: maybe use a hyphen for "face-varying" and "vertex-varying".

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

uv_indices (wp.array, optional): Triangle indices (wp.uint32) for UVs
hidden (bool): Whether the mesh is hidden (unused).
backface_culling (bool): Whether to enable backface culling (unused).
face_varying_uv (bool): Whether to enable face varying UV (default: vertex varying).
Copy link
Member

Choose a reason for hiding this comment

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

Nit: maybe use a hyphen for "face-varying" and "vertex-varying".
Also here it would be good to clarify this flag is currently not used (always vertex-varying).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

uvs (wp.array, optional): Vertex UVs.
hidden (bool): Whether the mesh is hidden.
backface_culling (bool): Enable backface culling.
face_varying_uv (bool): Enable face varying UV.
Copy link
Member

Choose a reason for hiding this comment

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

Can you use the same docstring as for the other viewers?
Also here it would be good to clarify this flag is currently not used (always vertex-varying).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

@mmacklin mmacklin self-requested a review September 25, 2025 00:30
Signed-off-by: Wenchao Huang <[email protected]>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

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

⚠️ Outside diff range comments (1)
newton/_src/viewer/viewer_rerun.py (1)

70-76: Fix potential UnboundLocalError for server_uri when server=False

server_uri is referenced when launch_viewer=True but only assigned if server=True.

Apply this diff:

-        if self.server:
-            server_uri = rr.serve_grpc()
-
-        # Optionally launch viewer client
-        if self.launch_viewer:
-            rr.serve_web_viewer(connect_to=server_uri)
+        server_uri = rr.serve_grpc() if self.server else None
+
+        # Optionally launch viewer client
+        if self.launch_viewer:
+            rr.serve_web_viewer(connect_to=server_uri) if server_uri else rr.serve_web_viewer()
🧹 Nitpick comments (5)
newton/_src/viewer/viewer_null.py (1)

53-69: Clarify the unused flag and silence unused parameter

Align docstring with other viewers and avoid ARG002.

Apply this diff:

     def log_mesh(
         self,
         name,
         points: wp.array,
         indices: wp.array,
         normals: wp.array = None,
         uvs: wp.array = None,
         hidden=False,
         backface_culling=True,
         face_varying_uv=False,
     ):
         """
         No-op implementation for logging a mesh.
 
         Args:
             name: Name of the mesh.
             points: Vertex positions.
             indices: Mesh indices.
             normals: Vertex normals (optional).
             uvs: Texture coordinates (optional).
             hidden: Whether the mesh is hidden.
             backface_culling: Whether to enable backface culling.
-            face_varying_uv: Whether the mesh UVs are face-varying (optional).
+            face_varying_uv: Whether the mesh UVs are face-varying (currently not used; always vertex-varying).
         """
-        pass
+        # Unused in Null backend; kept for API parity
+        _ = face_varying_uv
+        pass
newton/_src/viewer/viewer_usd.py (3)

69-69: Honor the up_axis argument instead of hardcoding Z

Respect the constructor parameter to avoid incorrect orientation in Y‑up pipelines.

Apply this diff:

-        UsdGeom.SetStageUpAxis(self.stage, UsdGeom.Tokens.z)
+        axis = str(self.up_axis).upper()
+        axis_token = UsdGeom.Tokens.z if axis.startswith("Z") else UsdGeom.Tokens.y
+        UsdGeom.SetStageUpAxis(self.stage, axis_token)

148-159: Docstring: specify expected UV shapes for clarity

Explicit shapes reduce ambiguity.

Suggested docstring tweak:

  • Vertex‑varying: uvs shape should be [num_points, 2].
  • Face‑varying: uvs shape should be [num_indices, 2] (for triangles, 3*num_faces by 2).

177-205: Validate before setting primvar and raise ValueError on mismatches

Avoid partially setting invalid data and prefer ValueError for input issues.

Apply this diff:

             # Set UVs if provided (do not set every frame)
             if uvs is not None:
                 primvars_api = UsdGeom.PrimvarsAPI(mesh_prim)
 
-                # Get or create the 'st' primvar
-                if not primvars_api.GetPrimvar("st"):
-                    st = primvars_api.CreatePrimvar("st", Sdf.ValueTypeNames.TexCoord2fArray, UsdGeom.Tokens.vertex)
-                else:
-                    st = primvars_api.GetPrimvar("st")
-
-                st.Set(uvs.numpy().astype(np.float32))
-
                 if face_varying_uv:
                     if len(uvs) != len(indices_np):
-                        raise RuntimeError(
+                        raise ValueError(
                             "UV count mismatch: when using face-varying UVs, "
                             "the length of 'uvs' must equal the length of 'indices'."
                         )
-                    else:
-                        st.SetInterpolation(UsdGeom.Tokens.faceVarying)
+                    interp = UsdGeom.Tokens.faceVarying
                 else:
                     if len(uvs) != len(points_np):
-                        raise RuntimeError(
+                        raise ValueError(
                             "UV count mismatch: when using vertex-varying UVs, "
                             "the length of 'uvs' must equal the length of 'points'."
                         )
-                    else:
-                        st.SetInterpolation(UsdGeom.Tokens.vertex)
+                    interp = UsdGeom.Tokens.vertex
+
+                # Get or create the 'st' primvar and then set values/interp
+                st = primvars_api.GetPrimvar("st")
+                if not st:
+                    st = primvars_api.CreatePrimvar("st", Sdf.ValueTypeNames.TexCoord2fArray, interp)
+                else:
+                    st.SetInterpolation(interp)
+                st.Set(uvs.numpy().astype(np.float32))
newton/_src/viewer/viewer_rerun.py (1)

90-105: Silence unused face_varying_uv and keep docstring aligned

Rerun backend doesn’t use this arg; mark it used and keep API parity.

Apply this diff:

     def log_mesh(
         self,
         name,
         points: wp.array,
         indices: wp.array,
         normals: wp.array = None,
         uvs: wp.array = None,
         hidden=False,
         backface_culling=True,
         face_varying_uv=False,
     ):
@@
         assert normals is None or isinstance(normals, wp.array)
         assert uvs is None or isinstance(uvs, wp.array)
 
+        # Unused in Rerun backend; kept for API parity
+        _ = face_varying_uv
📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between de4d60e and 5b99bfd.

📒 Files selected for processing (4)
  • newton/_src/viewer/viewer_gl.py (2 hunks)
  • newton/_src/viewer/viewer_null.py (2 hunks)
  • newton/_src/viewer/viewer_rerun.py (2 hunks)
  • newton/_src/viewer/viewer_usd.py (4 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-17T21:47:26.087Z
Learnt from: gdaviet
PR: newton-physics/newton#750
File: newton/examples/mpm/example_mpm_granular.py:162-165
Timestamp: 2025-09-17T21:47:26.087Z
Learning: wp.Mesh constructor takes parameters in the order: points, indices, velocities (optional), support_winding_number, bvh_constructor. The second positional parameter is indices, not velocities.

Applied to files:

  • newton/_src/viewer/viewer_rerun.py
🪛 Ruff (0.13.1)
newton/_src/viewer/viewer_rerun.py

90-90: Unused method argument: face_varying_uv

(ARG002)

newton/_src/viewer/viewer_usd.py

191-194: Avoid specifying long messages outside the exception class

(TRY003)


199-202: Avoid specifying long messages outside the exception class

(TRY003)

newton/_src/viewer/viewer_gl.py

203-203: Unused method argument: face_varying_uv

(ARG002)

🔇 Additional comments (2)
newton/_src/viewer/viewer_gl.py (1)

203-231: Silence unused face_varying_uv (Ruff ARG002) and keep docstring consistent across viewers

GL backend doesn’t use this arg; silence the linter and note API parity.

Apply this diff:

     def log_mesh(
         self,
         name,
         points: wp.array,
         indices: wp.array,
         normals: wp.array = None,
         uvs: wp.array = None,
         hidden=False,
         backface_culling=True,
         face_varying_uv=False,
     ):
@@
         assert isinstance(points, wp.array)
         assert isinstance(indices, wp.array)
         assert normals is None or isinstance(normals, wp.array)
         assert uvs is None or isinstance(uvs, wp.array)
 
+        # Unused in GL backend; kept for API parity with other viewers
+        _ = face_varying_uv

Also, ensure this docstring matches other viewers verbatim for the face_varying_uv description.

newton/_src/viewer/viewer_usd.py (1)

65-69: Setting both TimeCodesPerSecond and FramesPerSecond is correct

Good addition for accurate DCC playback.

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.

2 participants