USD: Export Cycles Shader networks as USD material networks. #119180

Open
Charles Wardlaw wants to merge 5 commits from CharlesWardlaw/blender:feature/usd_cycles_shaders into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
8 changed files with 1687 additions and 29 deletions

View File

@ -189,6 +189,8 @@ static int wm_usd_export_exec(bContext *C, wmOperator *op)
const bool export_shapekeys = RNA_boolean_get(op->ptr, "export_shapekeys");
const bool only_deform_bones = RNA_boolean_get(op->ptr, "only_deform_bones");
const bool generate_cycles_shaders = RNA_boolean_get(op->ptr, "generate_cycles_shaders");
char root_prim_path[FILE_MAX];
RNA_string_get(op->ptr, "root_prim_path", root_prim_path);
process_prim_path(root_prim_path);
@ -212,6 +214,7 @@ static int wm_usd_export_exec(bContext *C, wmOperator *op)
export_textures,
overwrite_textures,
relative_paths,
generate_cycles_shaders,
};
STRNCPY(params.root_prim_path, root_prim_path);
@ -259,6 +262,7 @@ 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_cycles_shaders", UI_ITEM_NONE, nullptr, ICON_NONE);
const bool export_mtl = RNA_boolean_get(ptr, "export_materials");
uiLayoutSetActive(col, export_mtl);
@ -420,6 +424,12 @@ void WM_OT_usd_export(wmOperatorType *ot)
"Generate an approximate USD Preview Surface shader "
"representation of a Principled BSDF node network");
RNA_def_boolean(ot->srna,
"generate_cycles_shaders",
false,
"Export Cycles Shaders",
"Export Cycles shader nodes to USD");
RNA_def_boolean(ot->srna,
"export_textures",
true,

View File

@ -97,6 +97,7 @@ set(SRC
intern/usd_writer_armature.cc
intern/usd_writer_camera.cc
intern/usd_writer_curves.cc
intern/usd_writer_cycles.cc
intern/usd_writer_hair.cc
intern/usd_writer_light.cc
intern/usd_writer_material.cc
@ -137,6 +138,7 @@ set(SRC
intern/usd_writer_armature.hh
intern/usd_writer_camera.hh
intern/usd_writer_curves.hh
intern/usd_writer_cycles.hh
intern/usd_writer_hair.hh
intern/usd_writer_light.hh
intern/usd_writer_material.hh

View File

@ -3,13 +3,13 @@
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "usd_asset_utils.hh"
#include "usd.hh"
#include <pxr/usd/ar/asset.h>
#include <pxr/usd/ar/packageUtils.h>
#include <pxr/usd/ar/resolver.h>
#include <pxr/usd/ar/writableAsset.h>
#include "BKE_appdir.hh"
#include "BKE_main.hh"
#include "BKE_report.hh"
@ -17,6 +17,8 @@
#include "BLI_path_util.h"
#include "BLI_string.h"
#include "WM_api.hh"
#include <string_view>
static const char UDIM_PATTERN[] = "<UDIM>";
@ -330,4 +332,29 @@ bool is_udim_path(const std::string &path)
path.find(UDIM_PATTERN2) != std::string::npos;
}
void USD_path_abs(char *path, const char *basepath, bool for_import)
{
if (!BLI_path_is_rel(path)) {
pxr::ArResolvedPath resolved_path = for_import ? pxr::ArGetResolver().Resolve(path) :
pxr::ArGetResolver().ResolveForNewAsset(path);
std::string path_str = resolved_path.GetPathString();
if (!path_str.empty()) {
if (path_str.length() < FILE_MAX) {
BLI_strncpy(path, path_str.c_str(), FILE_MAX);
return;
}
WM_reportf(RPT_ERROR,
"In %s: resolved path %s exceeds path buffer length.",
__func__,
path_str.c_str());
}
}
/* If we got here, the path couldn't be resolved by the ArResolver, so we
* fall back on the standard Blender absolute path resolution. */
BLI_path_abs(path, basepath);
}
} // namespace blender::io::usd

View File

@ -61,4 +61,6 @@ std::string import_asset(const char *src,
*/
bool is_udim_path(const std::string &path);
void USD_path_abs(char *path, const char *basepath, bool for_import);
} // namespace blender::io::usd

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,30 @@
#ifndef BLENDER_USD_WRITER_CYCLES_H
#define BLENDER_USD_WRITER_CYCLES_H
#include "DNA_material_types.h"
#include "DNA_node_types.h"
#include <pxr/usd/usdShade/material.h>
namespace blender::io::usd {
void create_usd_cycles_material(pxr::UsdStageRefPtr a_stage,
Material *material,
pxr::UsdShadeMaterial &usd_material,
const USDExportParams &export_params);
void create_usd_cycles_material(pxr::UsdStageRefPtr a_stage,
bNodeTree *ntree,
pxr::UsdShadeMaterial &usd_material,
const USDExportParams &export_params);
pxr::UsdShadeShader create_cycles_shader_node(pxr::UsdStageRefPtr a_stage,
pxr::SdfPath &shaderPath,
bNode *node,
const USDExportParams &export_params);
}; // namespace namespace blender::io::usd
#endif // BLENDER_USD_WRITER_CYCLES_H

View File

@ -4,19 +4,28 @@
#include "usd_writer_material.hh"
#include "usd.hh"
#include "usd_asset_utils.hh"
#include "usd_exporter_context.hh"
#include "usd_hook.hh"
#include "usd_writer_cycles.hh"
#include "BKE_appdir.hh"
#include "BKE_colorband.hh"
#include "BKE_colortools.hh"
#include "BKE_global.hh"
#include "BKE_image.h"
#include "BKE_image_format.h"
#include "BKE_main.hh"
#include "BKE_node.hh"
#include "BKE_node_runtime.hh"
#include "BKE_node_tree_update.hh"
#include "BKE_report.hh"
#include "IMB_colormanagement.hh"
#include "BLI_fileops.h"
#include "BLI_linklist.h"
#include "BLI_listbase.h"
#include "BLI_map.hh"
#include "BLI_memory_utils.hh"
@ -25,14 +34,19 @@
#include "BLI_string_utils.hh"
#include "DNA_material_types.h"
#include "DNA_node_types.h"
#include "DNA_packedFile_types.h"
#include "IMB_imbuf.hh"
#include "IMB_imbuf_types.hh"
#include "MEM_guardedalloc.h"
#include "WM_types.hh"
#include "WM_api.hh"
#include <pxr/base/tf/stringUtils.h>
#include <cctype>
#include "CLG_log.h"
static CLG_LogRef LOG = {"io.usd"};
@ -79,12 +93,12 @@ static const pxr::TfToken clamp("clamp", pxr::TfToken::Immortal);
static const pxr::TfToken repeat("repeat", pxr::TfToken::Immortal);
static const pxr::TfToken wrapS("wrapS", pxr::TfToken::Immortal);
static const pxr::TfToken wrapT("wrapT", pxr::TfToken::Immortal);
static const pxr::TfToken emissiveColor("emissiveColor", pxr::TfToken::Immortal);
static const pxr::TfToken in("in", pxr::TfToken::Immortal);
static const pxr::TfToken translation("translation", pxr::TfToken::Immortal);
static const pxr::TfToken rotation("rotation", pxr::TfToken::Immortal);
} // namespace usdtokens
/* Cycles specific tokens. */
namespace cyclestokens {
static const pxr::TfToken UVMap("UVMap", pxr::TfToken::Immortal);
} // namespace cyclestokens
@ -121,8 +135,15 @@ static void create_uv_input(const USDExporterContext &usd_export_context,
static void export_texture(const USDExporterContext &usd_export_context, bNode *node);
static bNode *find_bsdf_node(Material *material);
static void get_absolute_path(Image *ima, char *r_path);
static std::string get_tex_image_asset_filepath(const USDExporterContext &usd_export_context,
bNode *node);
std::string get_tex_image_asset_filepath(const USDExporterContext &usd_export_context,
bNode *node);
std::string get_tex_image_asset_filepath(Image *ima);
std::string get_tex_image_asset_filepath(bNode *node,
const pxr::UsdStageRefPtr stage,
const USDExportParams &export_params);
std::string get_tex_image_asset_filepath(const std::string &path,
const pxr::UsdStageRefPtr stage,
const USDExportParams &export_params);
static const InputSpecMap &preview_surface_input_map();
static bNodeLink *traverse_channel(bNodeSocket *input, short target_type);
@ -454,7 +475,6 @@ static void create_transform2d_shader(const USDExporterContext &usd_export_conte
pxr::UsdShadeInput &usd_input,
const pxr::TfToken &default_uv,
ReportList *reports)
{
bNode *mapping_node = (mapping_link && mapping_link->fromnode ? mapping_link->fromnode :
nullptr);
@ -624,6 +644,7 @@ static void export_in_memory_texture(Image *ima,
char export_path[FILE_MAX];
BLI_path_join(export_path, FILE_MAX, export_dir.c_str(), file_name);
BLI_string_replace_char(export_path, '\\', '/');
if (!allow_overwrite && BLI_exists(export_path)) {
return;
@ -648,8 +669,7 @@ static void get_absolute_path(Image *ima, char *r_path)
{
/* Make absolute source path. */
BLI_strncpy(r_path, ima->filepath, FILE_MAX);
BLI_path_abs(r_path, ID_BLEND_PATH_FROM_GLOBAL(&ima->id));
BLI_path_normalize(r_path);
USD_path_abs(r_path, ID_BLEND_PATH_FROM_GLOBAL(&ima->id), false /* Not for import */);
}
static pxr::TfToken get_node_tex_image_color_space(bNode *node)
@ -716,6 +736,10 @@ static bNodeLink *traverse_channel(bNodeSocket *input, const short target_type)
/* Recursively traverse the linked node's sockets. */
LISTBASE_FOREACH (bNodeSocket *, sock, &linked_node->inputs) {
/* Apply heuristics to skip certain inputs. */
if (strcmp(sock->name, "Factor") == 0) {
continue;
}
if (bNodeLink *found_link = traverse_channel(sock, target_type)) {
return found_link;
}
@ -744,7 +768,7 @@ static pxr::UsdShadeShader create_usd_preview_shader(const USDExporterContext &u
const int type)
{
pxr::SdfPath shader_path = material.GetPath().AppendChild(
pxr::TfToken(pxr::TfMakeValidIdentifier(name)));
pxr::TfToken("preview_" + pxr::TfMakeValidIdentifier(name)));
pxr::UsdShadeShader shader = pxr::UsdShadeShader::Define(usd_export_context.stage, shader_path);
switch (type) {
@ -816,7 +840,17 @@ static pxr::UsdShadeShader create_usd_preview_shader(const USDExporterContext &u
return shader;
}
static std::string get_tex_image_asset_filepath(Image *ima)
static bool is_in_memory_texture(Image *ima)
{
return BKE_image_is_dirty(ima) || ima->source == IMA_SRC_GENERATED;
}
static bool is_packed_texture(Image *ima)
{
return BKE_image_has_packedfile(ima);
}
std::string get_tex_image_asset_filepath(Image *ima)
{
char filepath[FILE_MAX];
get_absolute_path(ima, filepath);
@ -824,6 +858,12 @@ static std::string get_tex_image_asset_filepath(Image *ima)
return std::string(filepath);
}
std::string get_tex_image_asset_filepath(const USDExporterContext &usd_export_context, bNode *node)
{
return get_tex_image_asset_filepath(
node, usd_export_context.stage, usd_export_context.export_params);
}
/* Gets an asset path for the given texture image node. The resulting path
* may be absolute, relative to the USD file, or in a 'textures' directory
* in the same directory as the USD file, depending on the export parameters.
@ -831,8 +871,9 @@ static std::string get_tex_image_asset_filepath(Image *ima)
* generated based on the image name for in-memory textures when exporting textures.
* This function may return an empty string if the image does not have a filepath
* assigned and no asset path could be determined. */
static std::string get_tex_image_asset_filepath(const USDExporterContext &usd_export_context,
bNode *node)
std::string get_tex_image_asset_filepath(bNode *node,
const pxr::UsdStageRefPtr stage,
const USDExportParams &export_params)
{
Image *ima = reinterpret_cast<Image *>(node->id);
if (!ima) {
@ -841,22 +882,29 @@ static std::string get_tex_image_asset_filepath(const USDExporterContext &usd_ex
std::string path;
if (strlen(ima->filepath) > 0) {
if (is_in_memory_texture(ima) || is_packed_texture(ima)) {
path = get_in_memory_texture_filename(ima);
}
else if (strlen(ima->filepath) > 0) {
/* Get absolute path. */
path = get_tex_image_asset_filepath(ima);
}
else if (usd_export_context.export_params.export_textures) {
/* Image has no filepath, but since we are exporting textures,
* check if this is an in-memory texture for which we can
* generate a file name. */
path = get_in_memory_texture_filename(ima);
}
return get_tex_image_asset_filepath(path, stage, export_params);
}
/* Return a USD asset path referencing the given texture file. The resulting path
* may be absolute, relative to the USD file, or in a 'textures' directory
* in the same directory as the USD file, depending on the export parameters. */
std::string get_tex_image_asset_filepath(const std::string &path,
const pxr::UsdStageRefPtr stage,
const USDExportParams &export_params)
{
if (path.empty()) {
return path;
}
if (usd_export_context.export_params.export_textures) {
if (export_params.export_textures) {
/* The texture is exported to a 'textures' directory next to the
* USD root layer. */
@ -864,35 +912,37 @@ static std::string get_tex_image_asset_filepath(const USDExporterContext &usd_ex
char file_path[FILE_MAX];
BLI_path_split_file_part(path.c_str(), file_path, FILE_MAX);
if (usd_export_context.export_params.relative_paths) {
if (export_params.relative_paths) {
BLI_path_join(exp_path, FILE_MAX, ".", "textures", file_path);
}
else {
/* Create absolute path in the textures directory. */
std::string export_path = usd_export_context.export_file_path;
if (export_path.empty()) {
pxr::SdfLayerHandle layer = stage->GetRootLayer();
std::string stage_path = layer->GetRealPath();
if (stage_path.empty()) {
return path;
}
char dir_path[FILE_MAX];
BLI_path_split_dir_part(export_path.c_str(), dir_path, FILE_MAX);
BLI_path_split_dir_part(stage_path.c_str(), dir_path, FILE_MAX);
BLI_path_join(exp_path, FILE_MAX, dir_path, "textures", file_path);
}
BLI_string_replace_char(exp_path, '\\', '/');
return exp_path;
}
if (usd_export_context.export_params.relative_paths) {
if (export_params.relative_paths) {
/* Get the path relative to the USD. */
std::string export_path = usd_export_context.export_file_path;
if (export_path.empty()) {
pxr::SdfLayerHandle layer = stage->GetRootLayer();
std::string stage_path = layer->GetRealPath();
if (stage_path.empty()) {
return path;
}
char rel_path[FILE_MAX];
STRNCPY(rel_path, path.c_str());
BLI_path_rel(rel_path, export_path.c_str());
BLI_path_rel(rel_path, stage_path.c_str());
if (!BLI_path_is_rel(rel_path)) {
return path;
}
@ -1062,6 +1112,10 @@ pxr::UsdShadeMaterial create_usd_material(const USDExporterContext &usd_export_c
pxr::UsdShadeMaterial usd_material = pxr::UsdShadeMaterial::Define(usd_export_context.stage,
usd_path);
if (material->use_nodes && usd_export_context.export_params.generate_cycles_shaders) {
create_usd_cycles_material(
usd_export_context.stage, material, usd_material, usd_export_context.export_params);
}
if (material->use_nodes && usd_export_context.export_params.generate_preview_surface) {
create_usd_preview_surface_material(
usd_export_context, material, usd_material, active_uv, reports);

View File

@ -84,6 +84,7 @@ struct USDExportParams {
bool export_textures = true;
bool overwrite_textures = true;
bool relative_paths = true;
bool generate_cycles_shaders = false;
char root_prim_path[1024] = ""; /* FILE_MAX */
/** Communication structure between the wmJob management code and the worker code. Currently used