USD: Add support for UsdPrimvarReader_TYPE in materials

Add support for the UsdPrimvarReader_TYPE templates for both import and
export. These are used by several USD test assets and support here
represents the last major piece of the UsdPreviewSurface spec to be
implemented.

On import these become `Attribute` nodes and on export the `Attribute`
nodes will become `UsdPrimvarReader_TYPE`'s accordingly.

Import:
- `UsdPrimvarReader_float` and `UsdPrimvarReader_int` will use the `Fac`
  output
- `UsdPrimvarReader_float3` and `UsdPrimvarReader_float4` will use the
  `Color` output
- `UsdPrimvarReader_vector`, `UsdPrimvarReader_normal`, and
  `UsdPrimvarReader_point` will use the `Vector` output

Export (only `Geometry` Attribute types are considered):
- `Fac` will use `UsdPrimvarReader_float`
- `Color` will use `UsdPrimvarReader_float3`
- `Vector` will use `UsdPrimvarReader_vector`
- `Alpha` is not considered

MaterialX note:
Hydra-native support is a bit more involved and will have to be done
separately. Hydra w/USD sync is trivial to implement but those changes
have been left out here.

Pull Request: https://projects.blender.org/blender/blender/pulls/135143
This commit is contained in:
Jesse Yurkovich
2025-05-19 19:47:22 +02:00
committed by Jesse Yurkovich
parent dc213cd79e
commit 46ec277713
6 changed files with 233 additions and 9 deletions

View File

@@ -461,6 +461,32 @@ class USDExportTest(AbstractUSDTest):
input_displacement = shader_surface.GetInput('displacement')
self.assertTrue(input_displacement.Get() is None)
def test_export_material_attributes(self):
"""Validate correct export of Attribute information to UsdPrimvarReaders"""
# Use the common materials .blend file
bpy.ops.wm.open_mainfile(filepath=str(self.testdir / "usd_materials_attributes.blend"))
export_path = self.tempdir / "usd_materials_attributes.usda"
self.export_and_validate(filepath=str(export_path), export_materials=True)
stage = Usd.Stage.Open(str(export_path))
shader_attr = UsdShade.Shader(stage.GetPrimAtPath("/root/_materials/Material/Attribute"))
shader_attr1 = UsdShade.Shader(stage.GetPrimAtPath("/root/_materials/Material/Attribute_001"))
shader_attr2 = UsdShade.Shader(stage.GetPrimAtPath("/root/_materials/Material/Attribute_002"))
self.assertEqual(shader_attr.GetIdAttr().Get(), "UsdPrimvarReader_float3")
self.assertEqual(shader_attr1.GetIdAttr().Get(), "UsdPrimvarReader_float")
self.assertEqual(shader_attr2.GetIdAttr().Get(), "UsdPrimvarReader_vector")
self.assertEqual(shader_attr.GetInput("varname").Get(), "displayColor")
self.assertEqual(shader_attr1.GetInput("varname").Get(), "f_float")
self.assertEqual(shader_attr2.GetInput("varname").Get(), "f_vec")
self.assertEqual(shader_attr.GetOutput("result").GetTypeName().type.typeName, "GfVec3f")
self.assertEqual(shader_attr1.GetOutput("result").GetTypeName().type.typeName, "float")
self.assertEqual(shader_attr2.GetOutput("result").GetTypeName().type.typeName, "GfVec3f")
def test_export_metaballs(self):
"""Validate correct export of Metaball objects. These are written out as Meshes."""

View File

@@ -471,6 +471,42 @@ class USDImportTest(AbstractUSDTest):
mat = bpy.data.materials["mid_0_0_scale_0_3"]
assert_displacement(mat, None, 0.0, 0.3)
def test_import_material_attributes(self):
"""Validate correct import of Attribute information from UsdPrimvarReaders"""
# Use the existing materials test file to create the USD file
# for import. It is validated as part of the bl_usd_export test.
bpy.ops.wm.open_mainfile(filepath=str(self.testdir / "usd_materials_attributes.blend"))
testfile = str(self.tempdir / "usd_materials_attributes.usda")
res = bpy.ops.wm.usd_export(filepath=str(testfile), export_materials=True)
self.assertEqual({'FINISHED'}, res, f"Unable to export to {testfile}")
# Reload the empty file and import back in
bpy.ops.wm.open_mainfile(filepath=str(self.testdir / "empty.blend"))
res = bpy.ops.wm.usd_import(filepath=testfile)
self.assertEqual({'FINISHED'}, res, f"Unable to import USD file {testfile}")
# Most shader graph validation should occur through the Hydra render test suite. Here we
# will only check some high-level criteria for each expected node graph.
def assert_attribute(mat, attribute_name, from_socket, to_socket):
nodes = [n for n in mat.node_tree.nodes if n.type == 'ATTRIBUTE' and n.attribute_name == attribute_name]
self.assertTrue(len(nodes) == 1)
outputs = [o for o in nodes[0].outputs if o.identifier == from_socket]
self.assertTrue(len(outputs) == 1)
self.assertTrue(len(outputs[0].links) == 1)
link = outputs[0].links[0]
self.assertEqual(link.from_socket.identifier, from_socket)
self.assertEqual(link.to_socket.identifier, to_socket)
mat = bpy.data.materials["Material"]
self.assert_all_nodes_present(
mat, ["Principled BSDF", "Attribute", "Attribute.001", "Attribute.002", "Material Output"])
assert_attribute(mat, "displayColor", "Color", "Base Color")
assert_attribute(mat, "f_vec", "Vector", "Normal")
assert_attribute(mat, "f_float", "Fac", "Roughness")
def test_import_shader_varname_with_connection(self):
"""Test importing USD shader where uv primvar is a connection"""