Merge branch 'blender-v4.2-release'
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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<InstancerData>(this, instancer_prim_id());
|
||||
world_data_ = std::make_unique<WorldData>(this, world_prim_id());
|
||||
|
||||
@@ -63,7 +63,9 @@ class HydraSceneDelegate : public pxr::HdSceneDelegate {
|
||||
std::unique_ptr<WorldData> 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 */
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <Python.h>
|
||||
#include <unicodeobject.h>
|
||||
|
||||
#include <pxr/base/tf/stringUtils.h>
|
||||
#include <pxr/imaging/hd/material.h>
|
||||
#include <pxr/imaging/hd/renderDelegate.h>
|
||||
#include <pxr/imaging/hd/tokens.h>
|
||||
@@ -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()
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -12,11 +12,15 @@
|
||||
|
||||
struct Depsgraph;
|
||||
struct Main;
|
||||
struct Image;
|
||||
struct ImageUser;
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
class USDHierarchyIterator;
|
||||
|
||||
using ExportImageFunction = std::function<std::string(Main *, Scene *, Image *, ImageUser *)>;
|
||||
|
||||
struct USDExporterContext {
|
||||
Main *bmain;
|
||||
Depsgraph *depsgraph;
|
||||
@@ -31,6 +35,7 @@ struct USDExporterContext {
|
||||
std::function<pxr::UsdTimeCode()> get_time_code;
|
||||
const USDExportParams &export_params;
|
||||
std::string export_file_path;
|
||||
ExportImageFunction export_image_fn;
|
||||
};
|
||||
|
||||
} // namespace blender::io::usd
|
||||
|
||||
@@ -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 <pxr/usd/sdf/copyUtils.h>
|
||||
# include <pxr/usd/usdMtlx/reader.h>
|
||||
# include <pxr/usd/usdMtlx/utils.h>
|
||||
#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<Image *>(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<Image *>(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<Image *>(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<Image *>(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<std::string, std::string> &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<std::string> 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<std::string> 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<std::string, std::string> 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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -6,15 +6,25 @@
|
||||
|
||||
#include <MaterialXCore/Document.h>
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
struct Depsgraph;
|
||||
struct Image;
|
||||
struct ImageUser;
|
||||
struct Main;
|
||||
struct Material;
|
||||
struct Scene;
|
||||
|
||||
class ExportImageFunction;
|
||||
|
||||
namespace blender::nodes::materialx {
|
||||
|
||||
using ExportImageFunction = std::function<std::string(Main *, Scene *, Image *, ImageUser *)>;
|
||||
|
||||
MaterialX::DocumentPtr export_to_materialx(Depsgraph *depsgraph,
|
||||
Material *material,
|
||||
const std::string &material_name,
|
||||
ExportImageFunction export_image_fn);
|
||||
|
||||
} // namespace blender::nodes::materialx
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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<std::string(Main *, Scene *, Image *, ImageUser *)>;
|
||||
|
||||
/**
|
||||
* This is base abstraction class for parsing Blender nodes into MaterialX nodes.
|
||||
* #NodeParser::compute() should be overridden in child classes.
|
||||
|
||||
@@ -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<io::hydra::HydraSceneDelegate>(render_index_.get(),
|
||||
scene_path);
|
||||
hydra_scene_delegate_->use_materialx = bl_engine_->type->flag & RE_USE_MATERIALX;
|
||||
hydra_scene_delegate_ = std::make_unique<io::hydra::HydraSceneDelegate>(
|
||||
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<io::hydra::USDSceneDelegate>(render_index_.get(),
|
||||
scene_path);
|
||||
usd_scene_delegate_ = std::make_unique<io::hydra::USDSceneDelegate>(
|
||||
render_index_.get(), scene_path, use_materialx);
|
||||
}
|
||||
usd_scene_delegate_->populate(depsgraph);
|
||||
}
|
||||
|
||||
@@ -282,6 +282,40 @@ class USDExportTest(AbstractUSDTest):
|
||||
self.check_primvar(prim, "sp_quat", "VtArray<GfQuatf>", "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
|
||||
|
||||
Reference in New Issue
Block a user