diff --git a/source/blender/io/usd/intern/usd_reader_material.cc b/source/blender/io/usd/intern/usd_reader_material.cc index 4619643ab4e..2bdae4aa87b 100644 --- a/source/blender/io/usd/intern/usd_reader_material.cc +++ b/source/blender/io/usd/intern/usd_reader_material.cc @@ -918,6 +918,33 @@ static void configure_displacement(const pxr::UsdShadeShader &usd_shader, bNode ((bNodeSocketValueFloat *)sock_scale->default_value)->value = scale_avg; } +static pxr::UsdShadeShader node_graph_output_source(const pxr::UsdShadeNodeGraph &node_graph, + const pxr::TfToken &output_name) +{ + // Check that we have a legit output + pxr::UsdShadeOutput output = node_graph.GetOutput(output_name); + if (!output) { + return pxr::UsdShadeShader(); + } + + pxr::UsdShadeAttributeVector attrs = pxr::UsdShadeUtils::GetValueProducingAttributes(output); + if (attrs.empty()) { + return pxr::UsdShadeShader(); + } + + pxr::UsdAttribute attr = attrs[0]; + + std::pair name_and_type = + pxr::UsdShadeUtils::GetBaseNameAndType(attr.GetName()); + + pxr::UsdShadeShader shader(attr.GetPrim()); + if (name_and_type.second != pxr::UsdShadeAttributeType::Output || !shader) { + return pxr::UsdShadeShader(); + } + + return shader; +} + bool USDMaterialReader::follow_connection(const pxr::UsdShadeInput &usd_input, bNode *dest_node, const StringRefNull dest_socket_name, @@ -936,11 +963,19 @@ bool USDMaterialReader::follow_connection(const pxr::UsdShadeInput &usd_input, usd_input.GetConnectedSource(&source, &source_name, &source_type); - if (!(source && source.GetPrim().IsA())) { + if (!source) { return false; } - pxr::UsdShadeShader source_shader(source.GetPrim()); + const pxr::UsdPrim source_prim = source.GetPrim(); + pxr::UsdShadeShader source_shader; + if (source_prim.IsA()) { + source_shader = pxr::UsdShadeShader(source_prim); + } + else if (source_prim.IsA()) { + pxr::UsdShadeNodeGraph node_graph(source_prim); + source_shader = node_graph_output_source(node_graph, source_name); + } if (!source_shader) { return false; @@ -948,9 +983,9 @@ bool USDMaterialReader::follow_connection(const pxr::UsdShadeInput &usd_input, pxr::TfToken shader_id; if (!source_shader.GetShaderId(&shader_id)) { - CLOG_ERROR(&LOG, - "Couldn't get shader id for source shader %s", - source_shader.GetPath().GetAsString().c_str()); + CLOG_WARN(&LOG, + "Couldn't get shader id for source shader %s", + source_shader.GetPath().GetAsString().c_str()); return false; } diff --git a/tests/files/usd/usd_materials_node_graph.usda b/tests/files/usd/usd_materials_node_graph.usda new file mode 100644 index 00000000000..ec6794edbb3 --- /dev/null +++ b/tests/files/usd/usd_materials_node_graph.usda @@ -0,0 +1,91 @@ +#usda 1.0 +( + defaultPrim = "root" + doc = "Blender v4.5.0 Beta" + metersPerUnit = 1 + upAxis = "Z" +) + +def Xform "root" ( + customData = { + dictionary Blender = { + bool generated = 1 + } + } +) +{ + def Xform "Cube" + { + def Mesh "Cube" ( + active = true + prepend apiSchemas = ["MaterialBindingAPI"] + ) + { + uniform bool doubleSided = 1 + float3[] extent = [(-1, -1, -1), (1, 1, 1)] + int[] faceVertexCounts = [4, 4, 4, 4, 4, 4] + int[] faceVertexIndices = [0, 4, 6, 2, 3, 2, 6, 7, 7, 6, 4, 5, 5, 1, 3, 7, 1, 0, 2, 3, 5, 4, 0, 1] + rel material:binding = + normal3f[] normals = [(0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, -1, 0), (0, -1, 0), (0, -1, 0), (0, -1, 0), (-1, 0, 0), (-1, 0, 0), (-1, 0, 0), (-1, 0, 0), (0, 0, -1), (0, 0, -1), (0, 0, -1), (0, 0, -1), (1, 0, 0), (1, 0, 0), (1, 0, 0), (1, 0, 0), (0, 1, 0), (0, 1, 0), (0, 1, 0), (0, 1, 0)] ( + interpolation = "faceVarying" + ) + point3f[] points = [(1, 1, 1), (1, 1, -1), (1, -1, 1), (1, -1, -1), (-1, 1, 1), (-1, 1, -1), (-1, -1, 1), (-1, -1, -1)] + texCoord2f[] primvars:st = [(0.625, 0.5), (0.875, 0.5), (0.875, 0.75), (0.625, 0.75), (0.375, 0.75), (0.625, 0.75), (0.625, 1), (0.375, 1), (0.375, 0), (0.625, 0), (0.625, 0.25), (0.375, 0.25), (0.125, 0.5), (0.375, 0.5), (0.375, 0.75), (0.125, 0.75), (0.375, 0.5), (0.625, 0.5), (0.625, 0.75), (0.375, 0.75), (0.375, 0.25), (0.625, 0.25), (0.625, 0.5), (0.375, 0.5)] ( + interpolation = "faceVarying" + ) + uniform token subdivisionScheme = "none" + } + } + + def Scope "_materials" + { + def Material "Material" + { + token outputs:surface.connect = + + def Shader "Principled_BSDF" + { + uniform token info:id = "UsdPreviewSurface" + float inputs:clearcoat = 0 + float inputs:clearcoatRoughness = 0.03 + color3f inputs:diffuseColor.connect = + float inputs:ior = 1.45 + float inputs:metallic = 0 + float inputs:opacity = 1 + float inputs:roughness = 0.5 + float inputs:specular = 0.5 + token outputs:surface + } + + def NodeGraph "node_graph_BaseColor" ( + prepend references = + ) + { + } + } + + def NodeGraph "node_graph_BaseColor" + { + string inputs:frame:st = "st" + float3 outputs:rgb.connect = + + def Shader "node_graph_BaseColor" + { + uniform token info:id = "UsdUVTexture" + asset inputs:file = @./textures/test_single.png@ + float2 inputs:st.connect = + token inputs:wrapS = "repeat" + token inputs:wrapT = "repeat" + float3 outputs:rgb + } + + def Shader "PrimvarReader_st" + { + uniform token info:id = "UsdPrimvarReader_float2" + string inputs:varname.connect = + float2 outputs:result + } + } + } +} + diff --git a/tests/python/bl_usd_import_test.py b/tests/python/bl_usd_import_test.py index ad35fa69fb2..f6a76081350 100644 --- a/tests/python/bl_usd_import_test.py +++ b/tests/python/bl_usd_import_test.py @@ -533,6 +533,17 @@ class USDImportTest(AbstractUSDTest): assert_attribute(mat, "f_vec", "Vector", "Normal") assert_attribute(mat, "f_float", "Fac", "Roughness") + def test_import_material_node_graph(self): + """Verify we can follow connections through NodeGraph defs.""" + + testfile = str(self.testdir / "usd_materials_node_graph.usda") + res = bpy.ops.wm.usd_import(filepath=testfile) + self.assertEqual({'FINISHED'}, res) + + # If NodeGraph traversal is missing or broken, the Image Texture and UV Map nodes will be missing + mat = bpy.data.materials["Material"] + self.assert_all_nodes_present(mat, ["Principled BSDF", "Image Texture", "UV Map", "Material Output"]) + def test_import_shader_varname_with_connection(self): """Test importing USD shader where uv primvar is a connection"""