diff --git a/source/blender/editors/io/io_usd.cc b/source/blender/editors/io/io_usd.cc index b59a90bb31f..2fd5eda1516 100644 --- a/source/blender/editors/io/io_usd.cc +++ b/source/blender/editors/io/io_usd.cc @@ -232,6 +232,7 @@ static int wm_usd_export_exec(bContext *C, wmOperator *op) const bool evaluation_mode = RNA_enum_get(op->ptr, "evaluation_mode"); const bool generate_preview_surface = RNA_boolean_get(op->ptr, "generate_preview_surface"); + const bool generate_materialx_network = RNA_boolean_get(op->ptr, "generate_materialx_network"); const bool export_textures = RNA_boolean_get(op->ptr, "export_textures"); const bool overwrite_textures = RNA_boolean_get(op->ptr, "overwrite_textures"); const bool relative_paths = RNA_boolean_get(op->ptr, "relative_paths"); @@ -287,6 +288,7 @@ static int wm_usd_export_exec(bContext *C, wmOperator *op) use_instancing, eEvaluationMode(evaluation_mode), generate_preview_surface, + generate_materialx_network, export_textures, overwrite_textures, relative_paths, @@ -383,13 +385,15 @@ static void wm_usd_export_draw(bContext *C, wmOperator *op) box = uiLayoutBox(layout); col = uiLayoutColumnWithHeading(box, true, IFACE_("Materials")); uiItemR(col, ptr, "generate_preview_surface", UI_ITEM_NONE, nullptr, ICON_NONE); + uiItemR(col, ptr, "generate_materialx_network", UI_ITEM_NONE, nullptr, ICON_NONE); const bool export_mtl = RNA_boolean_get(ptr, "export_materials"); uiLayoutSetActive(col, export_mtl); row = uiLayoutRow(col, true); uiItemR(row, ptr, "export_textures", UI_ITEM_NONE, nullptr, ICON_NONE); const bool preview = RNA_boolean_get(ptr, "generate_preview_surface"); - uiLayoutSetActive(row, export_mtl && preview); + const bool materialx = RNA_boolean_get(ptr, "generate_materialx_network"); + uiLayoutSetActive(row, export_mtl && (preview || materialx)); uiLayout *col2 = uiLayoutColumn(col, true); uiLayoutSetPropSep(col2, true); @@ -578,10 +582,16 @@ void WM_OT_usd_export(wmOperatorType *ot) RNA_def_boolean(ot->srna, "generate_preview_surface", true, - "To USD Preview Surface", + "USD Preview Surface Network", "Generate an approximate USD Preview Surface shader " "representation of a Principled BSDF node network"); + RNA_def_boolean(ot->srna, + "generate_materialx_network", + false, + "MaterialX Network", + "Generate a MaterialX network representation of the materials"); + RNA_def_boolean(ot->srna, "convert_orientation", false, diff --git a/source/blender/io/usd/hydra/hydra_scene_delegate.cc b/source/blender/io/usd/hydra/hydra_scene_delegate.cc index 1c3ed2e41b1..c4752f4af9c 100644 --- a/source/blender/io/usd/hydra/hydra_scene_delegate.cc +++ b/source/blender/io/usd/hydra/hydra_scene_delegate.cc @@ -33,8 +33,9 @@ bool HydraSceneDelegate::ShadingSettings::operator==(const ShadingSettings &othe } HydraSceneDelegate::HydraSceneDelegate(pxr::HdRenderIndex *parent_index, - pxr::SdfPath const &delegate_id) - : HdSceneDelegate(parent_index, delegate_id) + pxr::SdfPath const &delegate_id, + const bool use_materialx) + : HdSceneDelegate(parent_index, delegate_id), use_materialx(use_materialx) { instancer_data_ = std::make_unique(this, instancer_prim_id()); world_data_ = std::make_unique(this, world_prim_id()); diff --git a/source/blender/io/usd/hydra/hydra_scene_delegate.hh b/source/blender/io/usd/hydra/hydra_scene_delegate.hh index 14aa275129d..59e5a168629 100644 --- a/source/blender/io/usd/hydra/hydra_scene_delegate.hh +++ b/source/blender/io/usd/hydra/hydra_scene_delegate.hh @@ -63,7 +63,9 @@ class HydraSceneDelegate : public pxr::HdSceneDelegate { std::unique_ptr world_data_; public: - HydraSceneDelegate(pxr::HdRenderIndex *parent_index, pxr::SdfPath const &delegate_id); + HydraSceneDelegate(pxr::HdRenderIndex *parent_index, + pxr::SdfPath const &delegate_id, + bool use_materialx); ~HydraSceneDelegate() override = default; /* Delegate methods */ diff --git a/source/blender/io/usd/hydra/material.cc b/source/blender/io/usd/hydra/material.cc index 43b7e613c58..e04b5bc2c53 100644 --- a/source/blender/io/usd/hydra/material.cc +++ b/source/blender/io/usd/hydra/material.cc @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -79,13 +80,15 @@ void MaterialData::init() material_library_path, get_time_code, export_params, - image_cache_file_path()}; + image_cache_file_path(), + cache_or_get_image_file}; /* Create USD material. */ pxr::UsdShadeMaterial usd_material; #ifdef WITH_MATERIALX if (scene_delegate_->use_materialx) { + std::string material_name = pxr::TfMakeValidIdentifier(id->name); MaterialX::DocumentPtr doc = blender::nodes::materialx::export_to_materialx( - scene_delegate_->depsgraph, (Material *)id, cache_or_get_image_file); + scene_delegate_->depsgraph, (Material *)id, material_name, cache_or_get_image_file); pxr::UsdMtlxRead(doc, stage); /* Logging stage: creating lambda stage_str() to not call stage->ExportToString() diff --git a/source/blender/io/usd/hydra/usd_scene_delegate.cc b/source/blender/io/usd/hydra/usd_scene_delegate.cc index d96debf16a0..c867e8066fe 100644 --- a/source/blender/io/usd/hydra/usd_scene_delegate.cc +++ b/source/blender/io/usd/hydra/usd_scene_delegate.cc @@ -24,8 +24,9 @@ using namespace blender::io::usd; namespace blender::io::hydra { USDSceneDelegate::USDSceneDelegate(pxr::HdRenderIndex *render_index, - pxr::SdfPath const &delegate_id) - : render_index_(render_index), delegate_id_(delegate_id) + pxr::SdfPath const &delegate_id, + const bool use_materialx) + : render_index_(render_index), delegate_id_(delegate_id), use_materialx(use_materialx) { /* Temporary directory to write any additional files to, like image or VDB files. */ char unique_name[FILE_MAXFILE]; @@ -55,6 +56,8 @@ void USDSceneDelegate::populate(Depsgraph *depsgraph) params.relative_paths = false; /* Unnecessary. */ params.export_textures = false; /* Don't copy all textures, is slow. */ params.evaluation_mode = DEG_get_mode(depsgraph); + params.generate_preview_surface = !use_materialx; + params.generate_materialx_network = use_materialx; /* NOTE: Since the reports list will be `nullptr` here, reports generated by export code from * this call will only be printed to console. */ diff --git a/source/blender/io/usd/hydra/usd_scene_delegate.hh b/source/blender/io/usd/hydra/usd_scene_delegate.hh index ba0f4d92ab1..f9a490d0f61 100644 --- a/source/blender/io/usd/hydra/usd_scene_delegate.hh +++ b/source/blender/io/usd/hydra/usd_scene_delegate.hh @@ -24,8 +24,12 @@ class USDSceneDelegate { std::string temp_dir_; std::string temp_file_; + bool use_materialx = true; + public: - USDSceneDelegate(pxr::HdRenderIndex *render_index, pxr::SdfPath const &delegate_id); + USDSceneDelegate(pxr::HdRenderIndex *render_index, + pxr::SdfPath const &delegate_id, + bool use_materialx); ~USDSceneDelegate(); void populate(Depsgraph *depsgraph); diff --git a/source/blender/io/usd/intern/usd_exporter_context.hh b/source/blender/io/usd/intern/usd_exporter_context.hh index 04b31d98424..7fc4485d852 100644 --- a/source/blender/io/usd/intern/usd_exporter_context.hh +++ b/source/blender/io/usd/intern/usd_exporter_context.hh @@ -12,11 +12,15 @@ struct Depsgraph; struct Main; +struct Image; +struct ImageUser; namespace blender::io::usd { class USDHierarchyIterator; +using ExportImageFunction = std::function; + struct USDExporterContext { Main *bmain; Depsgraph *depsgraph; @@ -31,6 +35,7 @@ struct USDExporterContext { std::function get_time_code; const USDExportParams &export_params; std::string export_file_path; + ExportImageFunction export_image_fn; }; } // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_writer_material.cc b/source/blender/io/usd/intern/usd_writer_material.cc index dbd14cdda4b..c6f1ce19de6 100644 --- a/source/blender/io/usd/intern/usd_writer_material.cc +++ b/source/blender/io/usd/intern/usd_writer_material.cc @@ -22,6 +22,7 @@ #include "BLI_map.hh" #include "BLI_memory_utils.hh" #include "BLI_path_util.h" +#include "BLI_set.hh" #include "BLI_string.h" #include "BLI_string_utils.hh" @@ -37,6 +38,14 @@ #include "CLG_log.h" static CLG_LogRef LOG = {"io.usd"}; +#ifdef WITH_MATERIALX +# include "shader/materialx/material.h" +# include "shader/materialx/node_parser.h" +# include +# include +# include +#endif + /* `TfToken` objects are not cheap to construct, so we do it once. */ namespace usdtokens { /* Materials. */ @@ -867,13 +876,12 @@ static std::string get_tex_image_asset_filepath(const USDExporterContext &usd_ex node, usd_export_context.stage, usd_export_context.export_params); } -std::string get_tex_image_asset_filepath(bNode *node, - const pxr::UsdStageRefPtr stage, - const USDExportParams &export_params) +static std::string get_tex_image_asset_filepath(Image *ima, + const pxr::UsdStageRefPtr stage, + const USDExportParams &export_params) { std::string stage_path = stage->GetRootLayer()->GetRealPath(); - Image *ima = reinterpret_cast(node->id); if (!ima) { return ""; } @@ -932,6 +940,15 @@ std::string get_tex_image_asset_filepath(bNode *node, return path; } +std::string get_tex_image_asset_filepath(bNode *node, + const pxr::UsdStageRefPtr stage, + const USDExportParams &export_params) +{ + + Image *ima = reinterpret_cast(node->id); + return get_tex_image_asset_filepath(ima, stage, export_params); +} + /* If the given image is tiled, copy the image tiles to the given * destination directory. */ static void copy_tiled_textures(Image *ima, @@ -1023,28 +1040,11 @@ static void copy_single_file(Image *ima, } } -static void export_texture(const USDExporterContext &usd_export_context, bNode *node) +static void export_texture(Image *ima, + const pxr::UsdStageRefPtr stage, + const bool allow_overwrite, + ReportList *reports) { - export_texture(node, - usd_export_context.stage, - usd_export_context.export_params.overwrite_textures, - usd_export_context.export_params.worker_status->reports); -} - -void export_texture(bNode *node, - const pxr::UsdStageRefPtr stage, - const bool allow_overwrite, - ReportList *reports) -{ - if (!ELEM(node->type, SH_NODE_TEX_IMAGE, SH_NODE_TEX_ENVIRONMENT)) { - return; - } - - Image *ima = reinterpret_cast(node->id); - if (!ima) { - return; - } - std::string export_path = stage->GetRootLayer()->GetRealPath(); if (export_path.empty()) { return; @@ -1075,6 +1075,39 @@ void export_texture(bNode *node, } } +void export_texture(bNode *node, + const pxr::UsdStageRefPtr stage, + const bool allow_overwrite, + ReportList *reports) +{ + if (!ELEM(node->type, SH_NODE_TEX_IMAGE, SH_NODE_TEX_ENVIRONMENT)) { + return; + } + + Image *ima = reinterpret_cast(node->id); + if (!ima) { + return; + } + + return export_texture(ima, stage, allow_overwrite, reports); +} + +static void export_texture(const USDExporterContext &usd_export_context, bNode *node) +{ + export_texture(node, + usd_export_context.stage, + usd_export_context.export_params.overwrite_textures, + usd_export_context.export_params.worker_status->reports); +} + +static void export_texture(const USDExporterContext &usd_export_context, Image *ima) +{ + export_texture(ima, + usd_export_context.stage, + usd_export_context.export_params.overwrite_textures, + usd_export_context.export_params.worker_status->reports); +} + const pxr::TfToken token_for_input(const char *input_name) { const InputSpecMap &input_map = preview_surface_input_map(); @@ -1087,6 +1120,235 @@ const pxr::TfToken token_for_input(const char *input_name) return spec->input_name; } +#ifdef WITH_MATERIALX +/* A wrapper for the MaterialX code to re-use the standard Texture export code */ +static std::string materialx_export_image( + const USDExporterContext &usd_export_context, Main *, Scene *, Image *ima, ImageUser *) +{ + auto tex_path = get_tex_image_asset_filepath( + ima, usd_export_context.stage, usd_export_context.export_params); + + export_texture(usd_export_context, ima); + return tex_path; +} + +/* Utility function to reflow connections and paths within the temporary document + * to their final location in the USD document. */ +static pxr::SdfPath reflow_materialx_paths(pxr::SdfPath input_path, + pxr::SdfPath temp_path, + const pxr::SdfPath &target_path, + const Map &rename_pairs) +{ + + auto input_path_string = input_path.GetString(); + /* First we see if the path is in the rename_pairs, + * otherwise we check if it starts with any items in the list plus a path separator (/ or .) . + * Checking for the path separators, removes false positives from other prefixed elements. */ + auto value_lookup_ptr = rename_pairs.lookup_ptr(input_path_string); + if (value_lookup_ptr) { + input_path = pxr::SdfPath(*value_lookup_ptr); + } + else { + for (const auto &pair : rename_pairs.items()) { + if (input_path_string.length() > pair.key.length() && + pxr::TfStringStartsWith(input_path_string, pair.key) && + (input_path_string[pair.key.length()] == '/' || + input_path_string[pair.key.length()] == '.')) + { + input_path = input_path.ReplacePrefix(pxr::SdfPath(pair.key), pxr::SdfPath(pair.value)); + break; + } + } + } + + return input_path.ReplacePrefix(temp_path, target_path); +} + +/* Exports the material as a MaterialX nodegraph within the USD layer. */ +static void create_usd_materialx_material(const USDExporterContext &usd_export_context, + pxr::SdfPath usd_path, + Material *material, + pxr::UsdShadeMaterial &usd_material) +{ + + /* We want to re-use the same MaterialX document generation code as used by the renderer. + * While the graph is traversed, we also want it to export the textures out. */ + ExportImageFunction export_image_fn = (usd_export_context.export_image_fn) ? + usd_export_context.export_image_fn : + std::bind(materialx_export_image, + usd_export_context, + std::placeholders::_1, + std::placeholders::_2, + std::placeholders::_3, + std::placeholders::_4); + std::string material_name = usd_path.GetElementString(); + MaterialX::DocumentPtr doc = blender::nodes::materialx::export_to_materialx( + usd_export_context.depsgraph, material, material_name, export_image_fn); + + /* We want to merge the MaterialX graph under the same Material as the USDPreviewSurface + * This allows for the same material assignment to have two levels of complexity so other + * applications and renderers can easily pick which one they want. + * This does mean that we need to pre-process the resulting graph so that there are no + * name conflicts. + * So we first gather all the existing names in this namespace to avoid that. */ + Set used_names; + auto material_prim = usd_material.GetPrim(); + for (const auto &child : material_prim.GetChildren()) { + used_names.add(child.GetName().GetString()); + } + + /* usdMtlx assumes a workflow where the mtlx file is referenced in, + * but the resulting structure is not ideal for when the file is inlined. + * Some of the issues include turning every shader input into a separate constant, which + * leads to very unwieldy shader graphs in other applications. There are also extra nodes + * that are only needed when referencing in the file that make editing the graph harder. + * Therefore, we opt to copy just what we need over. + * + * To do this, we first open a temporary stage to process the structure inside */ + + auto temp_stage = pxr::UsdStage::CreateInMemory(); + pxr::UsdMtlxRead(doc, temp_stage, pxr::SdfPath("/root")); + + /* Next we need to find the Material that matches this materials name */ + auto temp_material_path = pxr::SdfPath("/root/Materials"); + temp_material_path = temp_material_path.AppendChild(material_prim.GetName()); + auto temp_material_prim = temp_stage->GetPrimAtPath(temp_material_path); + if (!temp_material_prim) { + return; + } + + pxr::UsdShadeMaterial temp_material{temp_material_prim}; + if (!temp_material) { + return; + } + + /* Once we have the material, we need to prepare for renaming any conflicts. + * However, we must make sure any new names don't conflict with names in the temp stage either */ + Set temp_used_names; + for (const auto &child : temp_material_prim.GetChildren()) { + temp_used_names.add(child.GetName().GetString()); + } + + /* We loop through the top level children of the material, and make sure that the names are + * unique across both the destination stage, and this temporary stage. + * This is stored for later use so that we can reflow any connections */ + Map rename_pairs; + for (const auto &temp_material_child : temp_material_prim.GetChildren()) { + uint32_t conflict_counter = 0; + auto name = temp_material_child.GetName().GetString(); + auto target_name = name; + while (used_names.contains(target_name)) { + ++conflict_counter; + target_name = name + "_mtlx" + std::to_string(conflict_counter); + + while (temp_used_names.contains(target_name)) { + ++conflict_counter; + target_name = name + "_mtlx" + std::to_string(conflict_counter); + } + } + + if (conflict_counter == 0) { + continue; + } + + temp_used_names.add(target_name); + auto original_path = temp_material_child.GetPath().GetString(); + auto new_path = + temp_material_child.GetPath().ReplaceName(pxr::TfToken(target_name)).GetString(); + + rename_pairs.add_overwrite(original_path, new_path); + } + + /* We now need to find the connections from the material to the surface shader + * and modify it to match the final target location */ + for (auto &temp_material_output : temp_material.GetOutputs()) { + pxr::SdfPathVector output_paths; + + temp_material_output.GetAttr().GetConnections(&output_paths); + if (output_paths.size() == 1) { + output_paths[0] = reflow_materialx_paths( + output_paths[0], temp_material_path, usd_path, rename_pairs); + + auto target_material_output = usd_material.CreateOutput(temp_material_output.GetBaseName(), + temp_material_output.GetTypeName()); + target_material_output.GetAttr().SetConnections(output_paths); + } + } + + /* Next we need to iterate through every shader descendant recursively, to process them */ + for (const auto &temp_child : temp_material_prim.GetAllDescendants()) { + /* We only care about shader children */ + auto temp_shader = pxr::UsdShadeShader(temp_child); + if (!temp_shader) { + continue; + } + + /* First, we process any inputs */ + for (auto &shader_input : temp_shader.GetInputs()) { + pxr::SdfPathVector connection_paths; + shader_input.GetAttr().GetConnections(&connection_paths); + + if (connection_paths.size() != 1) { + continue; + } + + auto connection_path = connection_paths[0]; + + auto connection_source = pxr::UsdShadeConnectionSourceInfo(temp_stage, connection_path); + auto connection_source_prim = connection_source.source.GetPrim(); + if (connection_source_prim == temp_material_prim) { + /* If it's connected to the material prim, we should just bake down the value. + * usdMtlx connects them to constants because it wants to maximize separation between the + * input mtlx file and the resulting graph, but this isn't the ideal structure when the + * graph is inlined. + * Baking the values down makes this much more usable. */ + auto connection_source_attr = temp_stage->GetAttributeAtPath(connection_path); + if (connection_source_attr && shader_input.DisconnectSource()) { + pxr::VtValue val; + if (connection_source_attr.Get(&val) && !val.IsEmpty()) { + shader_input.GetAttr().Set(val); + } + } + } + else { + /* If it's connected to another prim, then we should fix the path to that prim + * SdfCopySpec below will handle some cases, but only if the target path exists first + * which is impossible to guarantee in a graph. */ + + connection_paths[0] = reflow_materialx_paths( + connection_paths[0], temp_material_path, usd_path, rename_pairs); + shader_input.GetAttr().SetConnections(connection_paths); + } + } + + /* Next we iterate through the outputs */ + for (auto &shader_output : temp_shader.GetOutputs()) { + pxr::SdfPathVector connection_paths; + shader_output.GetAttr().GetConnections(&connection_paths); + + if (connection_paths.size() != 1) { + continue; + } + + connection_paths[0] = reflow_materialx_paths( + connection_paths[0], temp_material_path, usd_path, rename_pairs); + shader_output.GetAttr().SetConnections(connection_paths); + } /* Iterate through outputs */ + + } /* Iterate through material prim children */ + + auto temp_layer = temp_stage->Flatten(); + + /* Copy the primspecs from the temporary stage over to the target stage */ + auto target_root_layer = usd_export_context.stage->GetRootLayer(); + for (const auto &temp_material_child : temp_material_prim.GetChildren()) { + auto target_path = reflow_materialx_paths( + temp_material_child.GetPath(), temp_material_path, usd_path, rename_pairs); + pxr::SdfCopySpec(temp_layer, temp_material_child.GetPath(), target_root_layer, target_path); + } +} +#endif + pxr::UsdShadeMaterial create_usd_material(const USDExporterContext &usd_export_context, pxr::SdfPath usd_path, Material *material, @@ -1104,6 +1366,12 @@ pxr::UsdShadeMaterial create_usd_material(const USDExporterContext &usd_export_c create_usd_viewport_material(usd_export_context, material, usd_material); } +#ifdef WITH_MATERIALX + if (material->use_nodes && usd_export_context.export_params.generate_materialx_network) { + create_usd_materialx_material(usd_export_context, usd_path, material, usd_material); + } +#endif + call_material_export_hooks(usd_export_context.stage, material, usd_material, diff --git a/source/blender/io/usd/usd.hh b/source/blender/io/usd/usd.hh index d6c9497dd9c..d64855fa3cc 100644 --- a/source/blender/io/usd/usd.hh +++ b/source/blender/io/usd/usd.hh @@ -111,6 +111,7 @@ struct USDExportParams { bool use_instancing = false; enum eEvaluationMode evaluation_mode = DAG_EVAL_VIEWPORT; bool generate_preview_surface = true; + bool generate_materialx_network = true; bool export_textures = true; bool overwrite_textures = true; bool relative_paths = true; diff --git a/source/blender/nodes/shader/materialx/material.cc b/source/blender/nodes/shader/materialx/material.cc index e6ce223f079..d9449278cdc 100644 --- a/source/blender/nodes/shader/materialx/material.cc +++ b/source/blender/nodes/shader/materialx/material.cc @@ -51,11 +51,14 @@ class DefaultMaterialNodeParser : public NodeParser { MaterialX::DocumentPtr export_to_materialx(Depsgraph *depsgraph, Material *material, + const std::string &material_name, ExportImageFunction export_image_fn) { CLOG_INFO(LOG_MATERIALX_SHADER, 0, "Material: %s", material->id.name); MaterialX::DocumentPtr doc = MaterialX::createDocument(); + NodeItem output_item; + if (material->use_nodes) { material->nodetree->ensure_topology_cache(); bNode *output_node = ntreeShaderOutputNode(material->nodetree, SHD_OUTPUT_ALL); @@ -68,29 +71,34 @@ MaterialX::DocumentPtr export_to_materialx(Depsgraph *depsgraph, NodeItem(doc.get()), export_image_fn}; output_node->typeinfo->materialx_fn(&data, output_node, nullptr); + output_item = data.result; } else { - DefaultMaterialNodeParser(doc.get(), - depsgraph, - material, - nullptr, - nullptr, - NodeItem::Type::Material, - nullptr, - export_image_fn) - .compute_error(); + output_item = DefaultMaterialNodeParser(doc.get(), + depsgraph, + material, + nullptr, + nullptr, + NodeItem::Type::Material, + nullptr, + export_image_fn) + .compute_error(); } } else { - DefaultMaterialNodeParser(doc.get(), - depsgraph, - material, - nullptr, - nullptr, - NodeItem::Type::Material, - nullptr, - export_image_fn) - .compute(); + output_item = DefaultMaterialNodeParser(doc.get(), + depsgraph, + material, + nullptr, + nullptr, + NodeItem::Type::Material, + nullptr, + export_image_fn) + .compute(); + } + + if (output_item.node) { + output_item.node->setName(material_name); } CLOG_INFO(LOG_MATERIALX_SHADER, diff --git a/source/blender/nodes/shader/materialx/material.h b/source/blender/nodes/shader/materialx/material.h index 7c62d4fe361..4ee90246adb 100644 --- a/source/blender/nodes/shader/materialx/material.h +++ b/source/blender/nodes/shader/materialx/material.h @@ -6,15 +6,25 @@ #include +#include +#include + struct Depsgraph; +struct Image; +struct ImageUser; +struct Main; struct Material; +struct Scene; class ExportImageFunction; namespace blender::nodes::materialx { +using ExportImageFunction = std::function; + MaterialX::DocumentPtr export_to_materialx(Depsgraph *depsgraph, Material *material, + const std::string &material_name, ExportImageFunction export_image_fn); } // namespace blender::nodes::materialx diff --git a/source/blender/nodes/shader/materialx/node_item.cc b/source/blender/nodes/shader/materialx/node_item.cc index 9176eb7d51f..d9025ffd20d 100644 --- a/source/blender/nodes/shader/materialx/node_item.cc +++ b/source/blender/nodes/shader/materialx/node_item.cc @@ -764,7 +764,14 @@ NodeItem NodeItem::create_node(const std::string &category, Type type) const std::string type_str = this->type(type); CLOG_INFO(LOG_MATERIALX_SHADER, 2, "<%s type=%s>", category.c_str(), type_str.c_str()); NodeItem res = empty(); - res.node = graph_->addNode(category, MaterialX::EMPTY_STRING, type_str); + /* Surfaceshader nodes and materials are added directly to the document, + * otherwise to thenodegraph */ + if (type == Type::SurfaceShader || type == Type::Material) { + res.node = graph_->getDocument()->addNode(category, MaterialX::EMPTY_STRING, type_str); + } + else { + res.node = graph_->addNode(category, MaterialX::EMPTY_STRING, type_str); + } return res; } @@ -816,7 +823,24 @@ void NodeItem::set_input(const std::string &in_name, const NodeItem &item) } } else if (item.node) { - node->setConnectedNode(in_name, item.node); + if (type() == Type::SurfaceShader) { + auto output_name = item.node->getName() + "_out"; + + auto output = graph_->getOutput(output_name); + if (!output) { + auto output_type = MaterialX::DEFAULT_TYPE_STRING; + if (item.node->getType() == "BSDF") { + output_type = "BSDF"; + } + output = graph_->addOutput(output_name, output_type); + } + + output->setConnectedNode(item.node); + node->setConnectedOutput(in_name, output); + } + else { + node->setConnectedNode(in_name, item.node); + } } else if (item.input) { node->setAttribute("interfacename", item.input->getName()); diff --git a/source/blender/nodes/shader/materialx/node_parser.h b/source/blender/nodes/shader/materialx/node_parser.h index 5f76b8caaf2..893cc557355 100644 --- a/source/blender/nodes/shader/materialx/node_parser.h +++ b/source/blender/nodes/shader/materialx/node_parser.h @@ -4,6 +4,7 @@ #pragma once +#include "material.h" #include "node_item.h" #include "DEG_depsgraph.hh" @@ -18,8 +19,6 @@ extern struct CLG_LogRef *LOG_MATERIALX_SHADER; class GroupNodeParser; -using ExportImageFunction = std::function; - /** * This is base abstraction class for parsing Blender nodes into MaterialX nodes. * #NodeParser::compute() should be overridden in child classes. diff --git a/source/blender/render/hydra/engine.cc b/source/blender/render/hydra/engine.cc index 053e0e9cad8..e302da81a3e 100644 --- a/source/blender/render/hydra/engine.cc +++ b/source/blender/render/hydra/engine.cc @@ -81,15 +81,16 @@ void Engine::sync(Depsgraph *depsgraph, bContext *context) context_ = context; scene_ = DEG_get_evaluated_scene(depsgraph); + const bool use_materialx = bl_engine_->type->flag & RE_USE_MATERIALX; + if (scene_->hydra.export_method == SCE_HYDRA_EXPORT_HYDRA) { /* Fast path. */ usd_scene_delegate_.reset(); if (!hydra_scene_delegate_) { pxr::SdfPath scene_path = pxr::SdfPath::AbsoluteRootPath().AppendElementString("scene"); - hydra_scene_delegate_ = std::make_unique(render_index_.get(), - scene_path); - hydra_scene_delegate_->use_materialx = bl_engine_->type->flag & RE_USE_MATERIALX; + hydra_scene_delegate_ = std::make_unique( + render_index_.get(), scene_path, use_materialx); } hydra_scene_delegate_->populate(depsgraph, context ? CTX_wm_view3d(context) : nullptr); } @@ -103,8 +104,8 @@ void Engine::sync(Depsgraph *depsgraph, bContext *context) if (!usd_scene_delegate_) { pxr::SdfPath scene_path = pxr::SdfPath::AbsoluteRootPath().AppendElementString("usd_scene"); - usd_scene_delegate_ = std::make_unique(render_index_.get(), - scene_path); + usd_scene_delegate_ = std::make_unique( + render_index_.get(), scene_path, use_materialx); } usd_scene_delegate_->populate(depsgraph); } diff --git a/tests/python/bl_usd_export_test.py b/tests/python/bl_usd_export_test.py index 7a09856047b..6aab81753fe 100644 --- a/tests/python/bl_usd_export_test.py +++ b/tests/python/bl_usd_export_test.py @@ -282,6 +282,40 @@ class USDExportTest(AbstractUSDTest): self.check_primvar(prim, "sp_quat", "VtArray", "uniform", 3) self.check_primvar_missing(prim, "sp_mat4x4") + def test_materialx_network(self): + """Test exporting that a MaterialX export makes it out alright""" + bpy.ops.wm.open_mainfile( + filepath=str(self.testdir / "usd_materials_export.blend") + ) + export_path = self.tempdir / "materialx.usda" + res = bpy.ops.wm.usd_export( + filepath=str(export_path), + export_materials=True, + generate_materialx_network=True, + evaluation_mode="RENDER", + ) + self.assertEqual({'FINISHED'}, res, f"Unable to export to {export_path}") + + stage = Usd.Stage.Open(str(export_path)) + material_prim = stage.GetPrimAtPath("/root/_materials/Material") + self.assertTrue(material_prim, "Could not find Material prim") + + material = UsdShade.Material(material_prim) + mtlx_output = material.GetOutput("mtlx:surface") + self.assertTrue(mtlx_output, "Could not find mtlx output") + + connection, source_name, _ = UsdShade.ConnectableAPI.GetConnectedSource( + mtlx_output + ) or [None, None, None] + + self.assertTrue((connection and source_name), "Could not find mtlx output source") + + shader = UsdShade.Shader(connection.GetPrim()) + self.assertTrue(shader, "Connected prim is not a shader") + + shader_id = shader.GetIdAttr().Get() + self.assertEqual(shader_id, "ND_standard_surface_surfaceshader", "Shader is not a Standard Surface") + def main(): global args