Skip to content

Material color extraction#2311

Open
nblauch wants to merge 5 commits intonewton-physics:mainfrom
nblauch:nblauch/material-color-extraction
Open

Material color extraction#2311
nblauch wants to merge 5 commits intonewton-physics:mainfrom
nblauch:nblauch/material-color-extraction

Conversation

@nblauch
Copy link
Copy Markdown

@nblauch nblauch commented Apr 3, 2026

Description

Fix material color extraction from USD so that OmniPBR/MDL materials, primitive shapes, and collision shapes render with correct colors instead of falling back to the shape palette.

Four gaps addressed:

  1. OmniPBR MDL shaders use diffuse_tint for their base color, but _extract_shader_properties didn't include it in the fallback name list.
  2. Prims without material bindings (e.g. collision-only ground planes) never reached the displayColor primvar fallback.
  3. Visual primitive shapes (box, sphere, plane, etc.) and collision shapes never extracted material colors — they always got palette colors.
  4. Textured cube primitives couldn't render textures because box shapes lack UVs. Cubes with textured materials are now auto-converted to meshes with per-face UVs.

Checklist

  • New or existing tests cover these changes
  • The documentation is up to date with these changes
  • CHANGELOG.md has been updated (if user-facing change)

Test plan

Verified end-to-end with a dextrah Kuka-Allegro manipulation scene (64 envs, Newton Warp renderer) containing:

  • Robot meshes with OmniPBR materials (arm_gray, arm_orange, allegro_black) — now render with correct material colors instead of palette rainbow
  • Table (cube primitive with OmniPBR texture) — now renders with walnut plank texture via cube-to-mesh conversion
  • Ground plane (collision-only, no material binding) — now picks up displayColor instead of palette blue
  • Grasp objects with UsdPreviewSurface materials — continue to render correctly

Bug fix

Steps to reproduce:

  1. Load a USD scene with OmniPBR materials (e.g. a robot URDF imported through Isaac Sim)
  2. Build a Newton model via ModelBuilder.add_usd(stage)
  3. Render with SensorTiledCamera with enable_textures=True
  4. Observe: robot meshes show cycling palette colors (blue, cyan, green, yellow...) instead of their material colors. Primitive shapes (table box, ground plane) also show palette colors. Textured cubes
    render without textures.

Minimal reproduction:

import newton                                                                                                                                                                                                  
from newton import ModelBuilder

builder = ModelBuilder()
# Load any USD scene with OmniPBR materials (e.g. robot + table)
builder.add_usd("scene_with_omnipbr_materials.usd", load_visual_shapes=True)
model = builder.finalize(device="cuda:0")

sensor = newton.sensors.SensorTiledCamera(model)
sensor.render_config.enable_textures = True

# Inspect shape colors — without fix, OmniPBR shapes get palette colors
colors = model.shape_color.numpy()
# colors[i] will be palette values like (0.267, 0.467, 0.667) instead of
# the material's diffuse_tint (e.g. (0.7, 0.7, 0.7) for grey arm)


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

* **New Features**
* Objects without bound materials can now extract color from display properties.
* Cube primitives now support textured mesh rendering.
* Visual and collision shapes properly display resolved material colors.

* **Bug Fixes**
* Textured objects no longer discard textures when UV data is missing; projected UVs are applied instead.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

nblauch added 4 commits April 3, 2026 12:41
OmniPBR MDL shaders use diffuse_tint for their base color, but
_extract_shader_properties only searched diffuse_color_constant and
similar names.  Add diffuse_tint to the fallback list so robot meshes
with OmniPBR materials get their correct colors.

Also add a last-resort displayColor primvar check in
resolve_material_properties_for_prim for prims without material
bindings (e.g. collision-only ground planes whose color is set via
displayColor rather than a shader).
Visual primitive shapes (box, sphere, plane, capsule, cylinder, cone)
in _load_visual_shapes_impl never extracted material colors, falling
back to the shape palette.  Resolve the material color (with parent-
prim fallback for inherited bindings) and pass color= to each
add_shape_* call.

Similarly, the collision shape loading path never included color in
shape_params.  Add material color extraction with parent fallback for
visible collision shapes.
Cube primitives with textured materials cannot render textures in
Newton because box shapes lack UVs and texture coordinate support.
When a cube's material has a texture, convert it to a 24-vertex box
mesh with per-face UVs via _cube_to_textured_mesh(), so Newton's
texture pipeline can sample the texture.

Untextured cubes continue to use add_shape_box as before.
Stop stripping mesh.texture when UVs are missing — the texture is
kept so that renderers with projected-UV support (triplanar sampling)
can use it.  The warning is updated to reflect this.

When a texture is present, set mesh.color to white (1,1,1) so the
texture renders at full brightness.  The previous behavior used
diffuse_color_constant as a multiplier, which is the untextured
fallback in OmniPBR and incorrectly darkened textured surfaces.
@linux-foundation-easycla
Copy link
Copy Markdown

linux-foundation-easycla bot commented Apr 3, 2026

CLA Not Signed

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 3, 2026

📝 Walkthrough

Walkthrough

The changes enhance USD material property resolution and mesh import by adding fallback mechanisms for shader inputs, supporting "displayColor" primvars as a final color source, and improving texture and color propagation for visual shapes and collision rendering.

Changes

Cohort / File(s) Summary
Material Property Resolution
newton/_src/usd/utils.py
Extended _get_input_value fallback list to include "diffuse_tint" shader input. Added displayColor primvar fallback in resolve_material_properties_for_prim to extract color from non-material-bound prims via UsdGeom.PrimvarsAPI.
Visual Shape Material Handling
newton/_src/utils/import_usd.py
Added _cube_to_textured_mesh function for textured cube generation. Enhanced _get_mesh_with_visual_material texture handling and color assignment logic. Improved _load_visual_shapes_impl with primitive material resolution, parent-walking fallbacks, and per-shape color propagation for cubes, spheres, planes, capsules, and cylinders. Updated collision-shape rendering to resolve and apply material color from collider prims.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Suggested labels

1.0-release

Suggested reviewers

  • eric-heiden
  • adenzler-nvidia
🚥 Pre-merge checks | ✅ 3
✅ 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 'Material color extraction' directly and clearly describes the main objective of the pull request—fixing material color extraction from USD across multiple contexts.
Docstring Coverage ✅ Passed Docstring coverage is 85.71% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@nblauch nblauch had a problem deploying to external-pr-approval April 3, 2026 20:30 — with GitHub Actions Error
@nblauch nblauch had a problem deploying to external-pr-approval April 3, 2026 20:30 — with GitHub Actions Error
@nblauch nblauch requested a deployment to external-pr-approval April 3, 2026 20:33 — with GitHub Actions Waiting
@nblauch nblauch requested a deployment to external-pr-approval April 3, 2026 20:33 — with GitHub Actions Waiting
@nblauch nblauch changed the title Nblauch/material color extraction Material color extraction Apr 3, 2026
Copy link
Copy Markdown
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: 1

Caution

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

⚠️ Outside diff range comments (1)
newton/_src/utils/import_usd.py (1)

652-681: ⚠️ Potential issue | 🟠 Major

Pass _prim_color through the cone branch too.

This is the only primitive case in _load_visual_shapes_impl() that still omits color=_prim_color, so visual cones keep falling back to the palette instead of the resolved USD material color.

💡 Suggested fix
                 shape_id = builder.add_shape_cone(
                     parent_body_id,
                     xform,
                     radius,
                     half_height,
                     cfg=visual_shape_cfg,
                     as_site=is_site,
+                    color=_prim_color,
                     label=path_name,
                 )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@newton/_src/utils/import_usd.py` around lines 652 - 681, In
_load_visual_shapes_impl(), the cone primitive branch calls
builder.add_shape_cone without passing the resolved USD material color, so cones
fall back to the palette; update the builder.add_shape_cone call (in the cone
branch) to include color=_prim_color (matching how builder.add_shape_cylinder is
invoked) and keep the existing cfg/as_site/label args intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@newton/_src/utils/import_usd.py`:
- Around line 2225-2240: The code is incorrectly forcing an explicit "color"
into shape_params which overrides textured mesh albedo; change the logic around
_collider_color so you only add "color" to shape_params when the resolved
material has no texture (i.e., when _get_material_props_cached(prim) or its
parent returns a color and does not contain a texture entry). In practice,
inspect the material props returned by _get_material_props_cached (used here and
by _get_mesh_with_visual_material()) and only set shape_params["color"] when
that material has no texture; otherwise omit the "color" key so
ModelBuilder.add_shape() will use src.color (preserving textures).

---

Outside diff comments:
In `@newton/_src/utils/import_usd.py`:
- Around line 652-681: In _load_visual_shapes_impl(), the cone primitive branch
calls builder.add_shape_cone without passing the resolved USD material color, so
cones fall back to the palette; update the builder.add_shape_cone call (in the
cone branch) to include color=_prim_color (matching how
builder.add_shape_cylinder is invoked) and keep the existing cfg/as_site/label
args intact.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 964ada78-e61d-4f31-88be-aba62c965edd

📥 Commits

Reviewing files that changed from the base of the PR and between 8532e01 and 328a653.

📒 Files selected for processing (2)
  • newton/_src/usd/utils.py
  • newton/_src/utils/import_usd.py

Comment on lines +2225 to +2240
# Resolve material color for visible collision shapes so they
# render with the correct colour instead of the palette fallback.
# Walk up to the parent if the prim itself has no material.
_collider_color = None
if collider_is_visible:
_coll_mat = _get_material_props_cached(prim)
_collider_color = _coll_mat.get("color")
if _collider_color is None:
_coll_parent = prim.GetParent()
if _coll_parent and _coll_parent.IsValid():
_collider_color = _get_material_props_cached(_coll_parent).get("color")

shape_params = {
"body": body_id,
"xform": shape_xform,
"color": _collider_color,
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.

⚠️ Potential issue | 🟠 Major

Don’t override textured mesh collider albedo with an explicit shape color.

For visible MeshShape colliders, _get_mesh_with_visual_material() already forces mesh.color to white when a texture exists. ModelBuilder.add_shape() prefers the explicit color argument over src.color, so wiring _collider_color into shape_params re-tints/darkens textured colliders.

💡 Suggested fix
-                _collider_color = None
+                _collider_color = None
+                _collider_texture = None
                 if collider_is_visible:
                     _coll_mat = _get_material_props_cached(prim)
                     _collider_color = _coll_mat.get("color")
+                    _collider_texture = _coll_mat.get("texture")
                     if _collider_color is None:
                         _coll_parent = prim.GetParent()
                         if _coll_parent and _coll_parent.IsValid():
-                            _collider_color = _get_material_props_cached(_coll_parent).get("color")
+                            _coll_parent_props = _get_material_props_cached(_coll_parent)
+                            _collider_color = _coll_parent_props.get("color")
+                            _collider_texture = _collider_texture or _coll_parent_props.get("texture")

                 shape_params = {
                     "body": body_id,
                     "xform": shape_xform,
-                    "color": _collider_color,
+                    "color": None
+                    if key == UsdPhysics.ObjectType.MeshShape and _collider_texture is not None
+                    else _collider_color,
                     "cfg": ModelBuilder.ShapeConfig(
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@newton/_src/utils/import_usd.py` around lines 2225 - 2240, The code is
incorrectly forcing an explicit "color" into shape_params which overrides
textured mesh albedo; change the logic around _collider_color so you only add
"color" to shape_params when the resolved material has no texture (i.e., when
_get_material_props_cached(prim) or its parent returns a color and does not
contain a texture entry). In practice, inspect the material props returned by
_get_material_props_cached (used here and by _get_mesh_with_visual_material())
and only set shape_params["color"] when that material has no texture; otherwise
omit the "color" key so ModelBuilder.add_shape() will use src.color (preserving
textures).

@preist-nvidia preist-nvidia added this to the 1.2 Release milestone Apr 7, 2026
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