Skip to content

Commit efa372a

Browse files
committed
Add material interfaces and make materials instanceable
- Use GetValueProducingAttributes() in the material tests - this verifies that we can get the shader input values regardless of whether we're using material interfaces or not - Fix material interface texture paths when there's no layer structure
1 parent 18217af commit efa372a

File tree

4 files changed

+54
-28
lines changed

4 files changed

+54
-28
lines changed

mujoco_usd_converter/_impl/_flatten.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,18 @@ def export_flattened(asset_stage: Usd.Stage, output_dir: str, asset_dir: str, as
1717
asset_identifier = f"{output_path.absolute().as_posix()}/{asset_stem}.{asset_format}"
1818
usdex.core.exportLayer(layer, asset_identifier, get_authoring_metadata(), comment)
1919

20-
# fix all PreviewMaterial inputs:file to ./Textures/xxx
20+
# fix all PreviewMaterial material interface asset inputs from abs to rel paths (./Textures/xxx)
2121
stage = Usd.Stage.Open(asset_identifier)
2222
for prim in stage.Traverse():
23-
if prim.IsA(UsdShade.Shader):
24-
shader = UsdShade.Shader(prim)
25-
file_input = shader.GetInput("file")
26-
if file_input and file_input.Get() is not None:
27-
file_path = pathlib.Path(file_input.Get().path if hasattr(file_input.Get(), "path") else file_input.Get())
28-
tmpdir = pathlib.Path(tempfile.gettempdir())
29-
if file_path.is_relative_to(tmpdir):
30-
new_path = f"./{Tokens.Textures}/{file_path.name}"
31-
file_input.Set(Sdf.AssetPath(new_path))
23+
if prim.IsA(UsdShade.Material):
24+
material = UsdShade.Material(prim)
25+
for input in material.GetInputs(onlyAuthored=True):
26+
if input.GetTypeName() == Sdf.ValueTypeNames.Asset:
27+
file_path = pathlib.Path(input.Get().path)
28+
tmpdir = pathlib.Path(tempfile.gettempdir())
29+
if file_path.is_relative_to(tmpdir):
30+
new_path = f"./{Tokens.Textures}/{file_path.name}"
31+
input.Set(Sdf.AssetPath(new_path))
3232
stage.Save()
3333
# copy texture to output dir
3434
temp_textures_dir = pathlib.Path(asset_dir) / Tokens.Payload / Tokens.Textures

mujoco_usd_converter/_impl/material.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# SPDX-FileCopyrightText: Copyright (c) 2025 The Newton Developers
1+
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 The Newton Developers
22
# SPDX-License-Identifier: Apache-2.0
33
import pathlib
44
import shutil
@@ -82,6 +82,13 @@ def convert_material(parent: Usd.Prim, name: str, material: mujoco.MjsMaterial,
8282

8383
if not material_prim:
8484
Tf.RaiseRuntimeError(f'Failed to convert material "{name}"')
85+
86+
result = usdex.core.addPreviewMaterialInterface(material_prim)
87+
if not result:
88+
Tf.RaiseRuntimeError(f'Failed to add material instance to material prim "{material_prim.GetPath()}"')
89+
90+
material_prim.GetPrim().SetInstanceable(True)
91+
8592
return material_prim
8693

8794

tests/testAssetStructure.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,10 @@ def test_no_layer_structure_material_texture(self):
380380

381381
texture_input: UsdShade.Input = shader.GetInput("diffuseColor")
382382
connected_source = texture_input.GetConnectedSource()
383-
texture_prim = connected_source[0].GetPrim()
384-
texture_file_attr = texture_prim.GetAttribute("inputs:file")
383+
texture_shader_prim = UsdShade.Shader(connected_source[0].GetPrim())
384+
385+
# The values are defined in the material interface, not in the shader
386+
value_attrs = UsdShade.Utils.GetValueProducingAttributes(texture_shader_prim.GetInput("file"))
387+
self.assertEqual(value_attrs[0].GetPrim(), material_prim)
388+
texture_file_attr = value_attrs[0]
385389
self.assertEqual(texture_file_attr.Get().path, "./Textures/grid.png")

tests/testMaterial.py

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# SPDX-FileCopyrightText: Copyright (c) 2025 The Newton Developers
1+
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 The Newton Developers
22
# SPDX-License-Identifier: Apache-2.0
33

44
import pathlib
@@ -31,23 +31,31 @@ def _get_shader(self, material_name: str) -> UsdShade.Shader:
3131
self.assertTrue(material_prim)
3232
return usdex.core.computeEffectivePreviewSurfaceShader(UsdShade.Material(material_prim))
3333

34+
def _get_input_value(self, shader: UsdShade.Shader, input_name: str):
35+
value_attrs = UsdShade.Utils.GetValueProducingAttributes(shader.GetInput(input_name))
36+
37+
# The values are defined in the material interface, not in the shader
38+
self.assertEqual(value_attrs[0].GetPrim(), shader.GetPrim().GetParent())
39+
40+
return value_attrs[0].Get()
41+
3442
def test_metallic_material(self):
3543
shader = self._get_shader("BlueMetallic")
36-
self.assertEqual(shader.GetInput("diffuseColor").Get(), Gf.Vec3f(0, 0, 1))
37-
self.assertEqual(shader.GetInput("opacity").Get(), 1)
38-
self.assertAlmostEqual(shader.GetInput("roughness").Get(), 0.7)
39-
self.assertAlmostEqual(shader.GetInput("metallic").Get(), 0.8)
44+
self.assertEqual(self._get_input_value(shader, "diffuseColor"), Gf.Vec3f(0, 0, 1))
45+
self.assertEqual(self._get_input_value(shader, "opacity"), 1)
46+
self.assertAlmostEqual(self._get_input_value(shader, "roughness"), 0.7)
47+
self.assertAlmostEqual(self._get_input_value(shader, "metallic"), 0.8)
4048
self.assertFalse(shader.GetInput("useSpecularWorkflow"))
4149

4250
def test_specular_material(self):
4351
shader = self._get_shader("GreenSpecular")
44-
self.assertEqual(shader.GetInput("useSpecularWorkflow").Get(), 1)
45-
self.assertAlmostEqual(shader.GetInput("specularColor").Get(), Gf.Vec3f(0.8))
52+
self.assertEqual(self._get_input_value(shader, "useSpecularWorkflow"), 1)
53+
self.assertAlmostEqual(self._get_input_value(shader, "specularColor"), Gf.Vec3f(0.8))
4654

4755
def test_emissive_material(self):
4856
shader = self._get_shader("RedEmissive")
49-
self.assertEqual(shader.GetInput("diffuseColor").Get(), Gf.Vec3f(1, 0, 0))
50-
self.assertEqual(shader.GetInput("emissiveColor").Get(), Gf.Vec3f(0.5, 0, 0))
57+
self.assertEqual(self._get_input_value(shader, "diffuseColor"), Gf.Vec3f(1, 0, 0))
58+
self.assertEqual(self._get_input_value(shader, "emissiveColor"), Gf.Vec3f(0.5, 0, 0))
5159

5260
def test_textured_material(self):
5361
shader = self._get_shader("Grid")
@@ -60,9 +68,8 @@ def test_textured_material(self):
6068

6169
# Check that the connected source is a relative asset path to the expected texture
6270
connected_source = texture_input.GetConnectedSource()
63-
texture_prim = connected_source[0].GetPrim()
64-
texture_file_attr = texture_prim.GetAttribute("inputs:file")
65-
self.assertEqual(texture_file_attr.Get().path, "./Textures/grid.png")
71+
texture_shader = UsdShade.Shader(connected_source[0].GetPrim())
72+
self.assertEqual(self._get_input_value(texture_shader, "file").path, "./Textures/grid.png")
6673

6774
def test_material_binding(self):
6875
textured_box_prim = self.stage.GetPrimAtPath(f"/{self.model_name}/Geometry/TexturedBox")
@@ -79,13 +86,21 @@ def test_material_binding(self):
7986
# materials are references to the material library layer
8087
self.assertTrue(material.GetPrim().HasAuthoredReferences())
8188

89+
def test_instanceable_material(self):
90+
shader = self._get_shader("BlueMetallic")
91+
self.assertTrue(shader)
92+
material_prim = shader.GetPrim().GetParent()
93+
self.assertTrue(material_prim)
94+
self.assertTrue(material_prim.GetPrim().IsInstanceable())
95+
self.assertTrue(material_prim.GetPrim().IsInstance())
96+
self.assertTrue(shader.GetPrim().IsInstanceProxy())
97+
8298
def test_unnamed_texture_material(self):
8399
shader = self._get_shader("UnnamedTexture")
84100
self.assertTrue(shader)
85101
texture_input: UsdShade.Input = shader.GetInput("diffuseColor")
86102
self.assertTrue(texture_input.HasConnectedSource())
87103

88104
connected_source = texture_input.GetConnectedSource()
89-
texture_prim = connected_source[0].GetPrim()
90-
texture_file_attr = texture_prim.GetAttribute("inputs:file")
91-
self.assertEqual(texture_file_attr.Get().path, "./Textures/grid.png")
105+
texture_shader = UsdShade.Shader(connected_source[0].GetPrim())
106+
self.assertEqual(self._get_input_value(texture_shader, "file").path, "./Textures/grid.png")

0 commit comments

Comments
 (0)