diff --git a/models/cube-mtlx-texture.usda b/models/cube-mtlx-texture.usda new file mode 100755 index 00000000..abede5c7 --- /dev/null +++ b/models/cube-mtlx-texture.usda @@ -0,0 +1,218 @@ +#usda 1.0 +( + defaultPrim = "root" + doc = "Blender v4.5.4 LTS" + metersPerUnit = 1 + upAxis = "Z" +) + +def Xform "root" ( + customData = { + dictionary Blender = { + bool generated = 1 + } + } +) +{ + def Xform "Cube" + { + custom string userProperties:blender:object_name = "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" + custom string userProperties:blender:data_name = "Cube" + } + } + + def Scope "_materials" + { + def Material "Material" ( + prepend apiSchemas = ["MaterialXConfigAPI"] + ) + { + string config:mtlx:version = "1.39" + token outputs:mtlx:surface.connect = + token outputs:surface.connect = + custom string userProperties:blender:data_name = "Material" + + 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 Shader "Image_Texture" + { + uniform token info:id = "UsdUVTexture" + asset inputs:file = @./textures/texture-cat.jpg@ + token inputs:sourceColorSpace = "sRGB" + float2 inputs:st.connect = + token inputs:wrapS = "repeat" + token inputs:wrapT = "repeat" + float3 outputs:rgb + } + + def Shader "uvmap" + { + uniform token info:id = "UsdPrimvarReader_float2" + string inputs:varname = "st" + float2 outputs:result + } + + def Shader "Principled_BSDF_mtlx1" + { + uniform token info:id = "ND_open_pbr_surface_surfaceshader" + color3f inputs:base_color.connect = + float inputs:base_diffuse_roughness = 0 + float inputs:base_metalness = 0 + float inputs:base_weight = 1 + color3f inputs:coat_color = (1, 1, 1) + float inputs:coat_darkening + float inputs:coat_ior = 1.5 + float inputs:coat_roughness = 0.03 + float inputs:coat_roughness_anisotropy + float inputs:coat_weight = 0 + color3f inputs:emission_color = (1, 1, 1) + float inputs:emission_luminance = 0 + color3f inputs:fuzz_color = (1, 1, 1) + float inputs:fuzz_roughness = 0.5 + float inputs:fuzz_weight = 0 + float3 inputs:geometry_coat_normal + float3 inputs:geometry_coat_tangent + float3 inputs:geometry_normal + float inputs:geometry_opacity = 1 + float3 inputs:geometry_tangent.connect = + bool inputs:geometry_thin_walled + color3f inputs:specular_color = (1, 1, 1) + float inputs:specular_ior = 1.45 + float inputs:specular_roughness = 0.5 + float inputs:specular_roughness_anisotropy = 0 + float inputs:specular_weight = 1 + color3f inputs:subsurface_color.connect = + float inputs:subsurface_radius = 0.05 + color3f inputs:subsurface_radius_scale = (1, 0.2, 0.1) + float inputs:subsurface_scatter_anisotropy = 0 + float inputs:subsurface_weight = 0 + float inputs:thin_film_ior = 1.33 + float inputs:thin_film_thickness = 0 + float inputs:thin_film_weight = 0 + color3f inputs:transmission_color.connect = + float inputs:transmission_depth + float inputs:transmission_dispersion_abbe_number + float inputs:transmission_dispersion_scale + color3f inputs:transmission_scatter + float inputs:transmission_scatter_anisotropy + float inputs:transmission_weight = 0 + token outputs:surface + } + + def NodeGraph "NodeGraphs" + { + float3 outputs:node_004_out.connect = + color3f outputs:node_out.connect = + + def Shader "node_texcoord" + { + uniform token info:id = "ND_texcoord_vector2" + float2 outputs:out + } + + def Shader "Image_Texture_Color" + { + uniform token info:id = "ND_image_color4" + asset inputs:file = @./textures/texture-cat.jpg@ ( + colorSpace = "srgb_texture" + ) + string inputs:filtertype = "linear" + float2 inputs:texcoord.connect = + string inputs:uaddressmode = "periodic" + string inputs:vaddressmode = "periodic" + color4f outputs:out + } + + def Shader "node" + { + uniform token info:id = "ND_convert_color4_color3" + color4f inputs:in.connect = + color3f outputs:out + } + + def Shader "node_001" + { + uniform token info:id = "ND_normal_vector3" + string inputs:space = "world" + float3 outputs:out + } + + def Shader "node_002" + { + uniform token info:id = "ND_normalize_vector3" + float3 inputs:in.connect = + float3 outputs:out + } + + def Shader "node_003" + { + uniform token info:id = "ND_tangent_vector3" + string inputs:space = "world" + float3 outputs:out + } + + def Shader "node_004" + { + uniform token info:id = "ND_normalize_vector3" + float3 inputs:in.connect = + float3 outputs:out + } + + def Shader "node_005" + { + uniform token info:id = "ND_rotate3d_vector3" + float inputs:amount = -90 + float3 inputs:axis.connect = + float3 inputs:in.connect = + float3 outputs:out + } + + def Shader "node_006" + { + uniform token info:id = "ND_normalize_vector3" + float3 inputs:in.connect = + float3 outputs:out + } + } + } + } + + def DomeLight "env_light" + { + float inputs:intensity = 1 + asset inputs:texture:file = @.\textures\color_121212.hdr@ + float3 xformOp:rotateXYZ = (90, 1.2722219e-14, 90) + uniform token[] xformOpOrder = ["xformOp:rotateXYZ"] + } +} diff --git a/src/external/yyjson.c b/src/external/yyjson.c index 6dc1fb22..63e3a4ae 100644 --- a/src/external/yyjson.c +++ b/src/external/yyjson.c @@ -35,6 +35,9 @@ # pragma clang diagnostic ignored "-Wunused-label" # pragma clang diagnostic ignored "-Wunused-macros" # pragma clang diagnostic ignored "-Wunused-variable" +# pragma clang diagnostic ignored "-Wreserved-macro-identifier" +# pragma clang diagnostic ignored "-Wold-style-cast" +# pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" #elif defined(__GNUC__) # pragma GCC diagnostic ignored "-Wunused-function" # pragma GCC diagnostic ignored "-Wunused-parameter" diff --git a/src/pprinter.cc b/src/pprinter.cc index 28548546..859af5cf 100644 --- a/src/pprinter.cc +++ b/src/pprinter.cc @@ -1401,7 +1401,9 @@ std::string print_prop(const Property &prop, const std::string &prop_name, // timeSamples and connect cannot have attrMeta // - if (attr.metas().authored() || attr.has_value()) { + // Print attribute if it has metadata, has a value, OR is just typed + // NOTE: Some attributes (like outputs:out) may be typed but not have a value + if (attr.metas().authored() || attr.has_value() || !attr.type_name().empty()) { ss << pprint::Indent(indent); @@ -4065,6 +4067,86 @@ static std::string print_shader_params(const UsdUVTexture &shader, return ss.str(); } +static std::string print_shader_params(const MtlxOpenPBRSurface &shader, + const uint32_t indent) { + std::stringstream ss; + + // Base properties + ss << print_typed_attr(shader.base_weight, "inputs:base_weight", indent); + ss << print_typed_attr(shader.base_color, "inputs:base_color", indent); + ss << print_typed_attr(shader.base_metalness, "inputs:base_metalness", indent); + ss << print_typed_attr(shader.base_diffuse_roughness, "inputs:base_diffuse_roughness", indent); + + // Specular properties + ss << print_typed_attr(shader.specular_weight, "inputs:specular_weight", indent); + ss << print_typed_attr(shader.specular_color, "inputs:specular_color", indent); + ss << print_typed_attr(shader.specular_roughness, "inputs:specular_roughness", indent); + ss << print_typed_attr(shader.specular_ior, "inputs:specular_ior", indent); + ss << print_typed_attr(shader.specular_anisotropy, "inputs:specular_anisotropy", indent); + ss << print_typed_attr(shader.specular_rotation, "inputs:specular_rotation", indent); + ss << print_typed_attr(shader.specular_roughness_anisotropy, "inputs:specular_roughness_anisotropy", indent); + + // Transmission properties + ss << print_typed_attr(shader.transmission_weight, "inputs:transmission_weight", indent); + ss << print_typed_attr(shader.transmission_color, "inputs:transmission_color", indent); + ss << print_typed_attr(shader.transmission_depth, "inputs:transmission_depth", indent); + ss << print_typed_attr(shader.transmission_scatter, "inputs:transmission_scatter", indent); + ss << print_typed_attr(shader.transmission_scatter_anisotropy, "inputs:transmission_scatter_anisotropy", indent); + ss << print_typed_attr(shader.transmission_dispersion, "inputs:transmission_dispersion", indent); + ss << print_typed_attr(shader.transmission_dispersion_abbe_number, "inputs:transmission_dispersion_abbe_number", indent); + ss << print_typed_attr(shader.transmission_dispersion_scale, "inputs:transmission_dispersion_scale", indent); + + // Subsurface properties + ss << print_typed_attr(shader.subsurface_weight, "inputs:subsurface_weight", indent); + ss << print_typed_attr(shader.subsurface_color, "inputs:subsurface_color", indent); + ss << print_typed_attr(shader.subsurface_radius, "inputs:subsurface_radius", indent); + ss << print_typed_attr(shader.subsurface_radius_scale, "inputs:subsurface_radius_scale", indent); + ss << print_typed_attr(shader.subsurface_scale, "inputs:subsurface_scale", indent); + ss << print_typed_attr(shader.subsurface_anisotropy, "inputs:subsurface_anisotropy", indent); + ss << print_typed_attr(shader.subsurface_scatter_anisotropy, "inputs:subsurface_scatter_anisotropy", indent); + + // Coat properties + ss << print_typed_attr(shader.coat_weight, "inputs:coat_weight", indent); + ss << print_typed_attr(shader.coat_color, "inputs:coat_color", indent); + ss << print_typed_attr(shader.coat_roughness, "inputs:coat_roughness", indent); + ss << print_typed_attr(shader.coat_anisotropy, "inputs:coat_anisotropy", indent); + ss << print_typed_attr(shader.coat_rotation, "inputs:coat_rotation", indent); + ss << print_typed_attr(shader.coat_roughness_anisotropy, "inputs:coat_roughness_anisotropy", indent); + ss << print_typed_attr(shader.coat_ior, "inputs:coat_ior", indent); + ss << print_typed_attr(shader.coat_darkening, "inputs:coat_darkening", indent); + ss << print_typed_attr(shader.coat_affect_color, "inputs:coat_affect_color", indent); + ss << print_typed_attr(shader.coat_affect_roughness, "inputs:coat_affect_roughness", indent); + + // Fuzz properties + ss << print_typed_attr(shader.fuzz_weight, "inputs:fuzz_weight", indent); + ss << print_typed_attr(shader.fuzz_color, "inputs:fuzz_color", indent); + ss << print_typed_attr(shader.fuzz_roughness, "inputs:fuzz_roughness", indent); + + // Thin film properties + ss << print_typed_attr(shader.thin_film_thickness, "inputs:thin_film_thickness", indent); + ss << print_typed_attr(shader.thin_film_ior, "inputs:thin_film_ior", indent); + ss << print_typed_attr(shader.thin_film_weight, "inputs:thin_film_weight", indent); + + // Emission properties + ss << print_typed_attr(shader.emission_luminance, "inputs:emission_luminance", indent); + ss << print_typed_attr(shader.emission_color, "inputs:emission_color", indent); + + // Geometry properties + ss << print_typed_attr(shader.geometry_opacity, "inputs:geometry_opacity", indent); + ss << print_typed_attr(shader.geometry_thin_walled, "inputs:geometry_thin_walled", indent); + ss << print_typed_attr(shader.geometry_normal, "inputs:geometry_normal", indent); + ss << print_typed_attr(shader.geometry_tangent, "inputs:geometry_tangent", indent); + ss << print_typed_attr(shader.geometry_coat_normal, "inputs:geometry_coat_normal", indent); + ss << print_typed_attr(shader.geometry_coat_tangent, "inputs:geometry_coat_tangent", indent); + + // Output + ss << print_typed_terminal_attr(shader.surface, "outputs:surface", indent); + + ss << print_common_shader_params(shader, indent); + + return ss.str(); +} + std::string to_string(const Shader &shader, const uint32_t indent, bool closing_brace) { // generic Shader class @@ -4111,7 +4193,7 @@ std::string to_string(const Shader &shader, const uint32_t indent, ss << print_shader_params(pvs.value(), indent + 1); } else if (auto mtlx_opbr = shader.value.get_value()) { // Blender v4.5 MaterialX OpenPBR Surface - ss << print_common_shader_params(mtlx_opbr.value(), indent + 1); + ss << print_shader_params(mtlx_opbr.value(), indent + 1); } else if (auto pvsn = shader.value.get_value()) { // Generic ShaderNode ss << print_common_shader_params(pvsn.value(), indent + 1); diff --git a/src/tydra/render-data.cc b/src/tydra/render-data.cc index 1cfc2473..0fed0a49 100644 --- a/src/tydra/render-data.cc +++ b/src/tydra/render-data.cc @@ -5122,6 +5122,311 @@ nonstd::expected GetConnectedUVTexture( prim->prim_type_name())); } +// Helper function to find ND_image_color4 texture nodes in a MaterialX NodeGraph +// by traversing connections from the given output +template +nonstd::expected GetConnectedMtlxTexture( + const Stage &stage, const TypedAnimatableAttributeWithFallback &src, + Path *tex_abs_path, const Shader **image_shader_out, + std::string *st_varname_out, const AssetInfo **assetInfo_out) { + + if (!src.is_connection()) { + return nonstd::make_unexpected("Attribute must be connection.\n"); + } + + if (src.get_connections().size() != 1) { + return nonstd::make_unexpected( + "Attribute connections must be single connection Path.\n"); + } + + const Path &path = src.get_connections()[0]; + const std::string prim_part = path.prim_part(); + const std::string prop_part = path.prop_part(); + + DCOUT("Checking MaterialX connection: " << path.full_path_name()); + DCOUT(" prim_part: " << prim_part); + DCOUT(" prop_part: " << prop_part); + + // The prim_part should be the NodeGraph path itself + // For , + // prim_part = "/root/_materials/Material/NodeGraphs" + + // First, try to find via stage lookup + const Prim *ng_prim{nullptr}; + std::string err; + bool found_in_stage = stage.find_prim_at_path(Path(prim_part, ""), ng_prim, &err); + + // If not found in stage lookup, try to navigate through Material's children + if (!found_in_stage || !ng_prim) { + DCOUT("Prim not found in stage lookup, trying Material children approach"); + + // Extract Material path - it should be everything before the last element + size_t last_slash = prim_part.rfind('/'); + if (last_slash == std::string::npos) { + return nonstd::make_unexpected( + fmt::format("Invalid NodeGraph path structure: {}\n", prim_part)); + } + + std::string material_path = prim_part.substr(0, last_slash); + std::string nodegraph_name = prim_part.substr(last_slash + 1); + + DCOUT("Looking for Material at: " << material_path); + DCOUT("NodeGraph name: " << nodegraph_name); + + // Find the Material + const Prim *mat_prim{nullptr}; + if (!stage.find_prim_at_path(Path(material_path, ""), mat_prim, &err)) { + return nonstd::make_unexpected( + fmt::format("Material {} not found: {}\n", material_path, err)); + } + + // Look for NodeGraph child + if (mat_prim) { + std::string children_info = "Material has " + std::to_string(mat_prim->children().size()) + " children: "; + for (const auto& child : mat_prim->children()) { + std::string child_name = child.element_name(); + std::string child_type = child.data().type_name(); + children_info += "'" + child_name + "'(" + child_type + ") "; + + // Check if this is a NodeGraph (by type, since name might be empty) + if (child_type == "NodeGraph") { + // If the child has no name but is the right type, use it + // This handles the case where the NodeGraph doesn't have element_name set + ng_prim = &child; + break; + } else if (child_name == nodegraph_name) { + // Also check by exact name match + ng_prim = &child; + break; + } + } + + if (!ng_prim) { + return nonstd::make_unexpected( + fmt::format("NodeGraph '{}' not found. {}\n", nodegraph_name, children_info)); + } + } else { + return nonstd::make_unexpected( + fmt::format("Material prim is null\n")); + } + } + + DCOUT("Found prim: " << prim_part << ", type: " << (ng_prim ? ng_prim->data().type_name() : "null")); + + const NodeGraph *ng = ng_prim ? ng_prim->as() : nullptr; + if (!ng) { + // Debug output to understand why it's not a NodeGraph + if (ng_prim) { + return nonstd::make_unexpected( + fmt::format("{} is not a NodeGraph, prim_type: {}\n", prim_part, ng_prim->data().type_name())); + } + return nonstd::make_unexpected( + fmt::format("{} is not a NodeGraph\n", prim_part)); + } + + // Find the output connection we're looking for + // The prop_part should be like "outputs:node_out" + std::string output_name = prop_part; + if (startsWith(output_name, "outputs:")) { + output_name = output_name.substr(8); // Remove "outputs:" prefix + } + + // Look for the connection in props + // Try both with and without ".connect" suffix + std::string conn_prop_name = "outputs:" + output_name + ".connect"; + auto it = ng->props.find(conn_prop_name); + + if (it == ng->props.end()) { + // Try without .connect suffix + conn_prop_name = "outputs:" + output_name; + it = ng->props.find(conn_prop_name); + + if (it == ng->props.end()) { + // List available props for debugging + std::string available_props = "Available props: "; + for (const auto& prop : ng->props) { + available_props += prop.first + " "; + } + return nonstd::make_unexpected( + fmt::format("Output connection '{}' not found in NodeGraph. {}\n", + conn_prop_name, available_props)); + } + } + + // NodeGraph outputs can be stored as attributes or relationships + Path current_path; + bool found_connection = false; + + if (it->second.is_attribute()) { + // It's an attribute - look for connections on the attribute + const Attribute &attr = it->second.get_attribute(); + if (attr.has_connections() && !attr.connections().empty()) { + current_path = attr.connections()[0]; + found_connection = true; + } + } else if (it->second.is_relationship()) { + // Also support relationship format + auto targets = it->second.get_relationTargets(); + if (!targets.empty()) { + current_path = targets[0]; + found_connection = true; + } + } + + if (!found_connection) { + return nonstd::make_unexpected( + fmt::format("Output {} has no connection targets\n", conn_prop_name)); + } + const Shader *image_shader = nullptr; + + // Traverse the node connections to find ND_image_color4 + // Maximum depth to prevent infinite loops + int max_depth = 10; + std::string traversal_log = "Traversal: "; + while (max_depth-- > 0) { + std::string current_prim_part = current_path.prim_part(); + + const Prim *current_prim{nullptr}; + + // First, try regular stage lookup + bool found_in_stage = stage.find_prim_at_path(Path(current_prim_part, ""), current_prim, &err); + + // If not found and this is under a NodeGraph, look in NodeGraph children + if (!found_in_stage || !current_prim) { + // Check if this path is under the NodeGraph we found earlier + size_t last_slash = current_prim_part.rfind('/'); + if (last_slash != std::string::npos) { + std::string parent_path = current_prim_part.substr(0, last_slash); + std::string child_name = current_prim_part.substr(last_slash + 1); + + // Check if parent is our NodeGraph + if (ng_prim && parent_path.find("NodeGraphs") != std::string::npos) { + // Look for the child in the NodeGraph prim + for (const auto& child : ng_prim->children()) { + if (child.element_name() == child_name) { + current_prim = &child; + break; + } + } + } + } + + if (!current_prim) { + return nonstd::make_unexpected( + fmt::format("Shader {} not found\n", current_prim_part)); + } + } + + const Shader *current_shader = current_prim ? current_prim->as() : nullptr; + if (!current_shader) { + return nonstd::make_unexpected( + fmt::format("{} is not a Shader. {}\n", current_prim_part, traversal_log)); + } + + // Log this node + traversal_log += current_shader->info_id + " -> "; + + // Check if this is an ND_image_color4 node + if (current_shader->info_id == "ND_image_color4" || + current_shader->info_id == "ND_image_color3") { + image_shader = current_shader; + if (tex_abs_path) { + *tex_abs_path = current_path; + } + if (image_shader_out) { + *image_shader_out = image_shader; + } + if (assetInfo_out) { + // get_assetInfo returns AssetInfo converted from customData/assetInfo + bool authored = false; + const AssetInfo &info = current_shader->metas().get_assetInfo(&authored); + if (authored) { + *assetInfo_out = &info; + } + } + + // For MaterialX, we don't have an explicit st varname, + // so we'll use "st" as default (same as UsdPreviewSurface) + if (st_varname_out) { + *st_varname_out = "st"; + } + + return true; + } + + // Check if this node has an input connection we should follow + // For ND_convert_color4_color3, follow inputs:in + bool found_next = false; + DCOUT("Checking shader " << current_shader->info_id << " at " << current_prim_part); + + // Debug: log all properties from both Shader and ShaderNode + std::string props_list = "ShaderProps: "; + for (const auto& prop : current_shader->props) { + props_list += prop.first + " "; + } + + // Check if the shader has a ShaderNode value with properties + const ShaderNode *shader_node = current_shader->value.as(); + if (shader_node && !shader_node->props.empty()) { + props_list += " NodeProps: "; + for (const auto& prop : shader_node->props) { + props_list += prop.first + " "; + } + } + traversal_log += "[" + props_list + "] "; + + // Helper lambda to check for connections in a property map + auto find_connection = [&](const std::map& props_map) -> bool { + for (const auto& prop : props_map) { + if (startsWith(prop.first, "inputs:")) { + bool is_connection = false; + Path next_path; + + if (endsWith(prop.first, ".connect")) { + // Explicit .connect suffix + is_connection = true; + if (prop.second.is_relationship()) { + auto next_targets = prop.second.get_relationTargets(); + if (!next_targets.empty()) { + next_path = next_targets[0]; + } + } + } else if (prop.second.is_attribute()) { + // Check if attribute has connections + const Attribute &attr = prop.second.get_attribute(); + if (attr.has_connections() && !attr.connections().empty()) { + is_connection = true; + next_path = attr.connections()[0]; + } + } + + if (is_connection && !next_path.full_path_name().empty()) { + DCOUT(" Following connection from " << prop.first << " to " << next_path); + current_path = next_path; + return true; + } + } + } + return false; + }; + + // Try shader_node->props first, then fall back to current_shader->props + if (shader_node && !shader_node->props.empty()) { + found_next = find_connection(shader_node->props); + } + if (!found_next) { + found_next = find_connection(current_shader->props); + } + + if (!found_next) { + break; + } + } + + return nonstd::make_unexpected( + fmt::format("No ND_image_color4 texture node found. {}\n", traversal_log)); +} + static bool RawAssetRead( const value::AssetPath &assetPath, const AssetInfo &assetInfo, const AssetResolutionResolver &assetResolver, @@ -5764,7 +6069,8 @@ template bool RenderSceneConverter::ConvertPreviewSurfaceShaderParam( const RenderSceneConverterEnv &env, const Path &shader_abs_path, const TypedAttributeWithFallback> ¶m, - const std::string ¶m_name, ShaderParam &dst_param) { + const std::string ¶m_name, ShaderParam &dst_param, + bool is_materialx) { if (!param.authored()) { return true; } @@ -5774,6 +6080,119 @@ bool RenderSceneConverter::ConvertPreviewSurfaceShaderParam( } else if (param.is_connection()) { DCOUT(fmt::format("{} is attribute connection.", param_name)); + // Check if this is a MaterialX connection to a NodeGraph + if (is_materialx && param.get_connections().size() == 1) { + const Path &conn_path = param.get_connections()[0]; + if (conn_path.prim_part().find("/NodeGraphs") != std::string::npos) { + // This is a MaterialX NodeGraph connection, traverse to find texture + const Shader *image_shader{nullptr}; + Path texPath; + std::string st_varname; + const AssetInfo *assetInfo{nullptr}; + + auto mtlx_result = GetConnectedMtlxTexture( + env.stage, param, &texPath, &image_shader, &st_varname, &assetInfo); + + if (mtlx_result) { + // Found a MaterialX texture node + DCOUT("Found MaterialX texture node: " << texPath); + + // Extract the file path from the image shader + value::AssetPath texAssetPath; + bool found_file = false; + + for (const auto& prop : image_shader->props) { + if (prop.first == "inputs:file" && prop.second.is_attribute()) { + const Attribute &attr = prop.second.get_attribute(); + if (attr.has_value()) { + auto asset_val = attr.get_value(); + if (asset_val) { + texAssetPath = *asset_val; + found_file = true; + break; + } + } + } + } + + if (!found_file) { + PUSH_WARN(fmt::format("MaterialX image node {} has no file input", texPath.prim_part())); + return true; + } + + // Create a synthetic UsdUVTexture to pass to ConvertUVTexture + UsdUVTexture synth_tex; + synth_tex.file.set_value(texAssetPath); + + // Map MaterialX wrap modes to USD + for (const auto& prop : image_shader->props) { + if (prop.first == "inputs:uaddressmode" && prop.second.is_attribute()) { + const Attribute &attr = prop.second.get_attribute(); + if (attr.has_value()) { + auto val = attr.get_value(); + if (val) { + if (*val == "periodic") { + synth_tex.wrapS.set_value(UsdUVTexture::Wrap::Repeat); + } else if (*val == "clamp") { + synth_tex.wrapS.set_value(UsdUVTexture::Wrap::Clamp); + } + } + } + } + if (prop.first == "inputs:vaddressmode" && prop.second.is_attribute()) { + const Attribute &attr = prop.second.get_attribute(); + if (attr.has_value()) { + auto val = attr.get_value(); + if (val) { + if (*val == "periodic") { + synth_tex.wrapT.set_value(UsdUVTexture::Wrap::Repeat); + } else if (*val == "clamp") { + synth_tex.wrapT.set_value(UsdUVTexture::Wrap::Clamp); + } + } + } + } + } + + // Use ConvertUVTexture to properly handle the texture + UVTexture rtex; + AssetInfo mtlx_assetInfo; // Use the assetInfo if available + if (assetInfo) { + mtlx_assetInfo = *assetInfo; + } + + // Handle colorSpace from attribute metadata if available + // AssetInfo doesn't have set_string, so we'll need to handle this differently + // For now, just use the assetInfo as-is + + if (!ConvertUVTexture(env, texPath, mtlx_assetInfo, synth_tex, &rtex)) { + PUSH_ERROR_AND_RETURN(fmt::format( + "Failed to convert MaterialX texture for {}", param_name)); + } + + // Set the connected output channel and UV primvar name + rtex.connectedOutputChannel = tydra::UVTexture::Channel::RGB; + rtex.varname_uv = st_varname; + + uint64_t texId = textures.size(); + textures.push_back(rtex); + + textureMap.add(texId, shader_abs_path.prim_part() + "." + param_name); + + DCOUT(fmt::format("MaterialX TexId {}.{} = {}", + shader_abs_path.prim_part(), param_name, texId)); + + dst_param.texture_id = int32_t(texId); + + return true; + } else { + PUSH_WARN(fmt::format("Failed to find MaterialX texture for {}: {}", + param_name, mtlx_result.error())); + } + } + } + + // Fall back to standard UsdUVTexture handling const UsdUVTexture *ptex{nullptr}; const Shader *pshader{nullptr}; Path texPath; @@ -5958,22 +6377,26 @@ bool RenderSceneConverter::ConvertOpenPBRSurfaceShader( // Convert base layer parameters if (!ConvertPreviewSurfaceShaderParam( env, shader_abs_path, shader.base_weight, "base_weight", - rshader.base_weight)) { + rshader.base_weight, true)) { + PushWarn(fmt::format("Failed to convert base_weight parameter for shader: {}", shader_abs_path.prim_part())); return false; } if (!ConvertPreviewSurfaceShaderParam( env, shader_abs_path, shader.base_color, "base_color", - rshader.base_color)) { + rshader.base_color, true)) { + PushWarn(fmt::format("Failed to convert base_color parameter for shader: {}", shader_abs_path.prim_part())); return false; } if (!ConvertPreviewSurfaceShaderParam( env, shader_abs_path, shader.base_roughness, "base_roughness", rshader.base_roughness)) { + PushWarn(fmt::format("Failed to convert base_roughness parameter for shader: {}", shader_abs_path.prim_part())); return false; } if (!ConvertPreviewSurfaceShaderParam( env, shader_abs_path, shader.base_metalness, "base_metalness", rshader.base_metalness)) { + PushWarn(fmt::format("Failed to convert base_metalness parameter for shader: {}", shader_abs_path.prim_part())); return false; } @@ -5981,36 +6404,43 @@ bool RenderSceneConverter::ConvertOpenPBRSurfaceShader( if (!ConvertPreviewSurfaceShaderParam( env, shader_abs_path, shader.specular_weight, "specular_weight", rshader.specular_weight)) { + PushWarn(fmt::format("Failed to convert specular_weight parameter for shader: {}", shader_abs_path.prim_part())); return false; } if (!ConvertPreviewSurfaceShaderParam( env, shader_abs_path, shader.specular_color, "specular_color", rshader.specular_color)) { + PushWarn(fmt::format("Failed to convert specular_color parameter for shader: {}", shader_abs_path.prim_part())); return false; } if (!ConvertPreviewSurfaceShaderParam( env, shader_abs_path, shader.specular_roughness, "specular_roughness", rshader.specular_roughness)) { + PushWarn(fmt::format("Failed to convert specular_roughness parameter for shader: {}", shader_abs_path.prim_part())); return false; } if (!ConvertPreviewSurfaceShaderParam( env, shader_abs_path, shader.specular_ior, "specular_ior", rshader.specular_ior)) { + PushWarn(fmt::format("Failed to convert specular_ior parameter for shader: {}", shader_abs_path.prim_part())); return false; } if (!ConvertPreviewSurfaceShaderParam( env, shader_abs_path, shader.specular_ior_level, "specular_ior_level", rshader.specular_ior_level)) { + PushWarn(fmt::format("Failed to convert specular_ior_level parameter for shader: {}", shader_abs_path.prim_part())); return false; } if (!ConvertPreviewSurfaceShaderParam( env, shader_abs_path, shader.specular_anisotropy, "specular_anisotropy", rshader.specular_anisotropy)) { + PushWarn(fmt::format("Failed to convert specular_anisotropy parameter for shader: {}", shader_abs_path.prim_part())); return false; } if (!ConvertPreviewSurfaceShaderParam( env, shader_abs_path, shader.specular_rotation, "specular_rotation", rshader.specular_rotation)) { + PushWarn(fmt::format("Failed to convert specular_rotation parameter for shader: {}", shader_abs_path.prim_part())); return false; } @@ -6018,31 +6448,37 @@ bool RenderSceneConverter::ConvertOpenPBRSurfaceShader( if (!ConvertPreviewSurfaceShaderParam( env, shader_abs_path, shader.transmission_weight, "transmission_weight", rshader.transmission_weight)) { + PushWarn(fmt::format("Failed to convert transmission_weight parameter for shader: {}", shader_abs_path.prim_part())); return false; } if (!ConvertPreviewSurfaceShaderParam( env, shader_abs_path, shader.transmission_color, "transmission_color", - rshader.transmission_color)) { + rshader.transmission_color, true)) { + PushWarn(fmt::format("Failed to convert transmission_color parameter for shader: {}", shader_abs_path.prim_part())); return false; } if (!ConvertPreviewSurfaceShaderParam( env, shader_abs_path, shader.transmission_depth, "transmission_depth", rshader.transmission_depth)) { + PushWarn(fmt::format("Failed to convert transmission_depth parameter for shader: {}", shader_abs_path.prim_part())); return false; } if (!ConvertPreviewSurfaceShaderParam( env, shader_abs_path, shader.transmission_scatter, "transmission_scatter", rshader.transmission_scatter)) { + PushWarn(fmt::format("Failed to convert transmission_scatter parameter for shader: {}", shader_abs_path.prim_part())); return false; } if (!ConvertPreviewSurfaceShaderParam( env, shader_abs_path, shader.transmission_scatter_anisotropy, "transmission_scatter_anisotropy", rshader.transmission_scatter_anisotropy)) { + PushWarn(fmt::format("Failed to convert transmission_scatter_anisotropy parameter for shader: {}", shader_abs_path.prim_part())); return false; } if (!ConvertPreviewSurfaceShaderParam( env, shader_abs_path, shader.transmission_dispersion, "transmission_dispersion", rshader.transmission_dispersion)) { + PushWarn(fmt::format("Failed to convert transmission_dispersion parameter for shader: {}", shader_abs_path.prim_part())); return false; } @@ -6050,26 +6486,31 @@ bool RenderSceneConverter::ConvertOpenPBRSurfaceShader( if (!ConvertPreviewSurfaceShaderParam( env, shader_abs_path, shader.subsurface_weight, "subsurface_weight", rshader.subsurface_weight)) { + PushWarn(fmt::format("Failed to convert subsurface_weight parameter for shader: {}", shader_abs_path.prim_part())); return false; } if (!ConvertPreviewSurfaceShaderParam( env, shader_abs_path, shader.subsurface_color, "subsurface_color", - rshader.subsurface_color)) { + rshader.subsurface_color, true)) { + PushWarn(fmt::format("Failed to convert subsurface_color parameter for shader: {}", shader_abs_path.prim_part())); return false; } if (!ConvertPreviewSurfaceShaderParam( env, shader_abs_path, shader.subsurface_radius, "subsurface_radius", rshader.subsurface_radius)) { + PushWarn(fmt::format("Failed to convert subsurface_radius parameter for shader: {}", shader_abs_path.prim_part())); return false; } if (!ConvertPreviewSurfaceShaderParam( env, shader_abs_path, shader.subsurface_scale, "subsurface_scale", rshader.subsurface_scale)) { + PushWarn(fmt::format("Failed to convert subsurface_scale parameter for shader: {}", shader_abs_path.prim_part())); return false; } if (!ConvertPreviewSurfaceShaderParam( env, shader_abs_path, shader.subsurface_anisotropy, "subsurface_anisotropy", rshader.subsurface_anisotropy)) { + PushWarn(fmt::format("Failed to convert subsurface_anisotropy parameter for shader: {}", shader_abs_path.prim_part())); return false; } @@ -6077,16 +6518,19 @@ bool RenderSceneConverter::ConvertOpenPBRSurfaceShader( if (!ConvertPreviewSurfaceShaderParam( env, shader_abs_path, shader.sheen_weight, "sheen_weight", rshader.sheen_weight)) { + PushWarn(fmt::format("Failed to convert sheen_weight parameter for shader: {}", shader_abs_path.prim_part())); return false; } if (!ConvertPreviewSurfaceShaderParam( env, shader_abs_path, shader.sheen_color, "sheen_color", rshader.sheen_color)) { + PushWarn(fmt::format("Failed to convert sheen_color parameter for shader: {}", shader_abs_path.prim_part())); return false; } if (!ConvertPreviewSurfaceShaderParam( env, shader_abs_path, shader.sheen_roughness, "sheen_roughness", rshader.sheen_roughness)) { + PushWarn(fmt::format("Failed to convert sheen_roughness parameter for shader: {}", shader_abs_path.prim_part())); return false; } @@ -6094,41 +6538,49 @@ bool RenderSceneConverter::ConvertOpenPBRSurfaceShader( if (!ConvertPreviewSurfaceShaderParam( env, shader_abs_path, shader.coat_weight, "coat_weight", rshader.coat_weight)) { + PushWarn(fmt::format("Failed to convert coat_weight parameter for shader: {}", shader_abs_path.prim_part())); return false; } if (!ConvertPreviewSurfaceShaderParam( env, shader_abs_path, shader.coat_color, "coat_color", rshader.coat_color)) { + PushWarn(fmt::format("Failed to convert coat_color parameter for shader: {}", shader_abs_path.prim_part())); return false; } if (!ConvertPreviewSurfaceShaderParam( env, shader_abs_path, shader.coat_roughness, "coat_roughness", rshader.coat_roughness)) { + PushWarn(fmt::format("Failed to convert coat_roughness parameter for shader: {}", shader_abs_path.prim_part())); return false; } if (!ConvertPreviewSurfaceShaderParam( env, shader_abs_path, shader.coat_anisotropy, "coat_anisotropy", rshader.coat_anisotropy)) { + PushWarn(fmt::format("Failed to convert coat_anisotropy parameter for shader: {}", shader_abs_path.prim_part())); return false; } if (!ConvertPreviewSurfaceShaderParam( env, shader_abs_path, shader.coat_rotation, "coat_rotation", rshader.coat_rotation)) { + PushWarn(fmt::format("Failed to convert coat_rotation parameter for shader: {}", shader_abs_path.prim_part())); return false; } if (!ConvertPreviewSurfaceShaderParam( env, shader_abs_path, shader.coat_ior, "coat_ior", rshader.coat_ior)) { + PushWarn(fmt::format("Failed to convert coat_ior parameter for shader: {}", shader_abs_path.prim_part())); return false; } if (!ConvertPreviewSurfaceShaderParam( env, shader_abs_path, shader.coat_affect_color, "coat_affect_color", rshader.coat_affect_color)) { + PushWarn(fmt::format("Failed to convert coat_affect_color parameter for shader: {}", shader_abs_path.prim_part())); return false; } if (!ConvertPreviewSurfaceShaderParam( env, shader_abs_path, shader.coat_affect_roughness, "coat_affect_roughness", rshader.coat_affect_roughness)) { + PushWarn(fmt::format("Failed to convert coat_affect_roughness parameter for shader: {}", shader_abs_path.prim_part())); return false; } @@ -6136,11 +6588,13 @@ bool RenderSceneConverter::ConvertOpenPBRSurfaceShader( if (!ConvertPreviewSurfaceShaderParam( env, shader_abs_path, shader.emission_luminance, "emission_luminance", rshader.emission_luminance)) { + PushWarn(fmt::format("Failed to convert emission_luminance parameter for shader: {}", shader_abs_path.prim_part())); return false; } if (!ConvertPreviewSurfaceShaderParam( env, shader_abs_path, shader.emission_color, "emission_color", rshader.emission_color)) { + PushWarn(fmt::format("Failed to convert emission_color parameter for shader: {}", shader_abs_path.prim_part())); return false; } @@ -6148,16 +6602,19 @@ bool RenderSceneConverter::ConvertOpenPBRSurfaceShader( if (!ConvertPreviewSurfaceShaderParam( env, shader_abs_path, shader.opacity, "opacity", rshader.opacity)) { + PushWarn(fmt::format("Failed to convert opacity parameter for shader: {}", shader_abs_path.prim_part())); return false; } if (!ConvertPreviewSurfaceShaderParam( env, shader_abs_path, shader.normal, "normal", rshader.normal)) { + PushWarn(fmt::format("Failed to convert normal parameter for shader: {}", shader_abs_path.prim_part())); return false; } if (!ConvertPreviewSurfaceShaderParam( env, shader_abs_path, shader.tangent, "tangent", rshader.tangent)) { + PushWarn(fmt::format("Failed to convert tangent parameter for shader: {}", shader_abs_path.prim_part())); return false; } @@ -8352,6 +8809,8 @@ bool InferColorSpace(const value::token &tok, ColorSpace *cty) { return true; } +#if 0 // Deprecated: Use implementation in render-scene-dump.cc instead + namespace { template @@ -9062,6 +9521,8 @@ std::string DumpRenderScene(const RenderScene &scene, return ss.str(); } +#endif // Deprecated dump functions + // Memory usage estimation implementations size_t RenderMesh::estimate_memory_usage() const { diff --git a/src/tydra/render-data.hh b/src/tydra/render-data.hh index 5ae746b2..6035e0ae 100644 --- a/src/tydra/render-data.hh +++ b/src/tydra/render-data.hh @@ -2778,7 +2778,8 @@ class RenderSceneConverter { bool ConvertPreviewSurfaceShaderParam( const RenderSceneConverterEnv &env, const Path &shader_abs_path, const TypedAttributeWithFallback> ¶m, - const std::string ¶m_name, ShaderParam &dst_param); + const std::string ¶m_name, ShaderParam &dst_param, + bool is_materialx = false); /// /// Build (single) vertex indices for RenderMesh. @@ -2817,9 +2818,9 @@ class RenderSceneConverter { const XformNode &node, Node &out_rnode); - void PushInfo(const std::string &msg) { _info += msg; } - void PushWarn(const std::string &msg) { _warn += msg; } - void PushError(const std::string &msg) { _err += msg; } + void PushInfo(const std::string &msg) { _info += msg + "\n"; } + void PushWarn(const std::string &msg) { _warn += msg + "\n"; } + void PushError(const std::string &msg) { _err += msg + "\n"; } /// /// Call progress callback if set. diff --git a/src/usda-reader.cc b/src/usda-reader.cc index 81e9fff2..1abdfd21 100644 --- a/src/usda-reader.cc +++ b/src/usda-reader.cc @@ -1679,6 +1679,7 @@ bool USDAReader::Impl::Read(const uint32_t state_flags, bool as_primspec) { RegisterReconstructCallback(); RegisterReconstructCallback(); + RegisterReconstructCallback(); RegisterReconstructCallback(); diff --git a/src/usdc-reader.cc b/src/usdc-reader.cc index 68ac800b..e692fca5 100644 --- a/src/usdc-reader.cc +++ b/src/usdc-reader.cc @@ -1898,6 +1898,7 @@ nonstd::optional USDCReader::Impl::ReconstructPrimFromTypeName( RECONSTRUCT_PRIM(SkelAnimation, typeName, prim_name, spec) RECONSTRUCT_PRIM(BlendShape, typeName, prim_name, spec) RECONSTRUCT_PRIM(Shader, typeName, prim_name, spec) + RECONSTRUCT_PRIM(NodeGraph, typeName, prim_name, spec) RECONSTRUCT_PRIM(Material, typeName, prim_name, spec) { PUSH_WARN("TODO or unsupported prim type: " << typeName); if (is_unsupported_prim) {