Compare commits

...

3 Commits

Author SHA1 Message Date
6cf4929f6a USD export: fixed typo in comparison. 2021-12-21 11:36:06 -05:00
75af1395e1 USD export format fixes. 2021-12-21 08:34:32 -05:00
3c8131fde3 USD Export: USD Preview Surface conversion.
Added "USD Preview Surface From Nodes" export option,
to convert a Principled BSDF material node tree to an
approximate USD Preview Surface shader representation.

Also added the following options for texture export.

Export Textures: If converting Preview Surface, export
textures referenced by shader nodes to a 'textures'
directory next to the USD file.

Overwrite Textures: Allow overwriting existing texture
files when exporting textures (this option is off by
default).

Relative Texture Paths: Save material texture asset
paths as relative paths in the USD.
2021-12-21 08:24:45 -05:00
8 changed files with 1115 additions and 15 deletions

View File

@@ -131,6 +131,11 @@ static int wm_usd_export_exec(bContext *C, wmOperator *op)
const bool use_instancing = RNA_boolean_get(op->ptr, "use_instancing");
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 export_textures = RNA_boolean_get(op->ptr, "export_textures");
const bool overwrite_textures = RNA_boolean_get(op->ptr, "overwrite_textures");
const bool relative_texture_paths = RNA_boolean_get(op->ptr, "relative_texture_paths");
struct USDExportParams params = {
export_animation,
export_hair,
@@ -141,6 +146,10 @@ static int wm_usd_export_exec(bContext *C, wmOperator *op)
visible_objects_only,
use_instancing,
evaluation_mode,
generate_preview_surface,
export_textures,
overwrite_textures,
relative_texture_paths,
};
bool ok = USD_export(C, filename, &params, as_background_job);
@@ -172,6 +181,27 @@ static void wm_usd_export_draw(bContext *UNUSED(C), wmOperator *op)
col = uiLayoutColumn(box, true);
uiItemR(col, ptr, "evaluation_mode", 0, NULL, ICON_NONE);
box = uiLayoutBox(layout);
col = uiLayoutColumnWithHeading(box, true, IFACE_("Materials"));
uiItemR(col, ptr, "generate_preview_surface", 0, NULL, ICON_NONE);
bool export_mtl = RNA_boolean_get(ptr, "export_materials");
uiLayoutSetEnabled(col, export_mtl);
uiLayout *row = uiLayoutRow(col, true);
uiItemR(row, ptr, "export_textures", 0, NULL, ICON_NONE);
bool preview = RNA_boolean_get(ptr, "generate_preview_surface");
uiLayoutSetEnabled(row, export_mtl && preview);
row = uiLayoutRow(col, true);
uiItemR(row, ptr, "overwrite_textures", 0, NULL, ICON_NONE);
bool export_tex = RNA_boolean_get(ptr, "export_textures");
uiLayoutSetEnabled(row, export_mtl && preview && export_tex);
row = uiLayoutRow(col, true);
uiItemR(row, ptr, "relative_texture_paths", 0, NULL, ICON_NONE);
uiLayoutSetEnabled(row, export_mtl && preview);
box = uiLayoutBox(layout);
uiItemL(box, IFACE_("Experimental"), ICON_NONE);
uiItemR(box, ptr, "use_instancing", 0, NULL, ICON_NONE);
@@ -249,6 +279,33 @@ void WM_OT_usd_export(struct wmOperatorType *ot)
"Use Settings for",
"Determines visibility of objects, modifier settings, and other areas where there "
"are different settings for viewport and rendering");
RNA_def_boolean(ot->srna,
"generate_preview_surface",
true,
"USD Preview Surface From Nodes",
"Generate an approximate USD Preview Surface shader "
"representation of a Principled BSDF node network ");
RNA_def_boolean(ot->srna,
"export_textures",
true,
"Export Textures",
"If exporting materials, export textures referenced by material nodes "
"to a 'textures' directory in the same directory as the USD");
RNA_def_boolean(ot->srna,
"overwrite_textures",
false,
"Overwrite Textures",
"Allow overwriting existing texture files when exporting textures");
RNA_def_boolean(
ot->srna,
"relative_texture_paths",
true,
"Relative Texture Paths",
"When checked, material texture asset paths will be saved as relative paths in the USD");
}
/* ====== USD Import ====== */

View File

@@ -64,6 +64,7 @@ set(SRC
intern/usd_writer_camera.cc
intern/usd_writer_hair.cc
intern/usd_writer_light.cc
intern/usd_writer_material.cc
intern/usd_writer_mesh.cc
intern/usd_writer_metaball.cc
intern/usd_writer_transform.cc
@@ -89,6 +90,7 @@ set(SRC
intern/usd_writer_camera.h
intern/usd_writer_hair.h
intern/usd_writer_light.h
intern/usd_writer_material.h
intern/usd_writer_mesh.h
intern/usd_writer_metaball.h
intern/usd_writer_transform.h

View File

@@ -18,11 +18,15 @@
*/
#include "usd_writer_abstract.h"
#include "usd_hierarchy_iterator.h"
#include "usd_writer_material.h"
#include <pxr/base/tf/stringUtils.h>
#include "BKE_customdata.h"
#include "BLI_assert.h"
#include "DNA_mesh_types.h"
/* TfToken objects are not cheap to construct, so we do it once. */
namespace usdtokens {
/* Materials */
@@ -36,6 +40,47 @@ static const pxr::TfToken surface("surface", pxr::TfToken::Immortal);
namespace blender::io::usd {
/* The following three utility function for getting active layers
* were coppied from the collada utils. */
/* Returns name of the active layer of the given type or null
* if no such active layer is defined. */
static const char *customData_get_active_layer_name(const CustomData *data, int type)
{
/* get the layer index of the active layer of type */
int layer_index = CustomData_get_active_layer_index(data, type);
if (layer_index < 0) {
return nullptr;
}
return data->layers[layer_index].name;
}
/* Returns name of the active UV layer or the empty string if no active
* UV Layer defined. */
static std::string get_active_uvlayer_name(Mesh *me)
{
int num_layers = CustomData_number_of_layers(&me->ldata, CD_MLOOPUV);
if (num_layers) {
const char *layer_name = customData_get_active_layer_name(&me->ldata, CD_MLOOPUV);
if (layer_name) {
return std::string(layer_name);
}
}
return "";
}
/* Returns name of the acive Layer or empty String if no active UV Layer defined,
* assuming the Object is of type MESH. */
static std::string get_active_uvlayer_name(Object *ob)
{
if (!ob || ob->type != OB_MESH) {
return "";
}
Mesh *me = static_cast<Mesh *>(ob->data);
return get_active_uvlayer_name(me);
}
USDAbstractWriter::USDAbstractWriter(const USDExporterContext &usd_export_context)
: usd_export_context_(usd_export_context), frame_has_been_written_(false), is_animated_(false)
{
@@ -78,7 +123,8 @@ const pxr::SdfPath &USDAbstractWriter::usd_path() const
return usd_export_context_.usd_path;
}
pxr::UsdShadeMaterial USDAbstractWriter::ensure_usd_material(Material *material)
pxr::UsdShadeMaterial USDAbstractWriter::ensure_usd_material(Material *material,
const HierarchyContext &context)
{
static pxr::SdfPath material_library_path("/_materials");
pxr::UsdStageRefPtr stage = usd_export_context_.stage;
@@ -92,17 +138,14 @@ pxr::UsdShadeMaterial USDAbstractWriter::ensure_usd_material(Material *material)
}
usd_material = pxr::UsdShadeMaterial::Define(stage, usd_path);
/* Construct the shader. */
pxr::SdfPath shader_path = usd_path.AppendChild(usdtokens::preview_shader);
pxr::UsdShadeShader shader = pxr::UsdShadeShader::Define(stage, shader_path);
shader.CreateIdAttr(pxr::VtValue(usdtokens::preview_surface));
shader.CreateInput(usdtokens::diffuse_color, pxr::SdfValueTypeNames->Color3f)
.Set(pxr::GfVec3f(material->r, material->g, material->b));
shader.CreateInput(usdtokens::roughness, pxr::SdfValueTypeNames->Float).Set(material->roughness);
shader.CreateInput(usdtokens::metallic, pxr::SdfValueTypeNames->Float).Set(material->metallic);
/* Connect the shader and the material together. */
usd_material.CreateSurfaceOutput().ConnectToSource(shader, usdtokens::surface);
if (material->use_nodes && this->usd_export_context_.export_params.generate_preview_surface) {
std::string active_uv_name = get_active_uvlayer_name(context.object);
create_usd_preview_surface_material(
this->usd_export_context_, material, usd_material, active_uv_name);
}
else {
create_usd_viewport_material(this->usd_export_context_, material, usd_material);
}
return usd_material;
}

View File

@@ -69,7 +69,7 @@ class USDAbstractWriter : public AbstractHierarchyWriter {
virtual void do_write(HierarchyContext &context) = 0;
pxr::UsdTimeCode get_export_time_code() const;
pxr::UsdShadeMaterial ensure_usd_material(Material *material);
pxr::UsdShadeMaterial ensure_usd_material(Material *material, const HierarchyContext &context);
void write_visibility(const HierarchyContext &context,
const pxr::UsdTimeCode timecode,

View File

@@ -0,0 +1,941 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "usd_writer_material.h"
#include "usd.h"
#include "usd_exporter_context.h"
#include "BKE_image.h"
#include "BKE_main.h"
#include "BKE_node.h"
#include "BLI_fileops.h"
#include "BLI_linklist.h"
#include "BLI_listbase.h"
#include "BLI_math.h"
#include "BLI_path_util.h"
#include "BLI_string.h"
#include "DNA_material_types.h"
#include "MEM_guardedalloc.h"
#include <pxr/base/tf/stringUtils.h>
#include <pxr/pxr.h>
#include <pxr/usd/usdGeom/scope.h>
#include <iostream>
/* TfToken objects are not cheap to construct, so we do it once. */
namespace usdtokens {
// Materials
static const pxr::TfToken clearcoat("clearcoat", pxr::TfToken::Immortal);
static const pxr::TfToken clearcoatRoughness("clearcoatRoughness", pxr::TfToken::Immortal);
static const pxr::TfToken diffuse_color("diffuseColor", pxr::TfToken::Immortal);
static const pxr::TfToken metallic("metallic", pxr::TfToken::Immortal);
static const pxr::TfToken preview_shader("previewShader", pxr::TfToken::Immortal);
static const pxr::TfToken preview_surface("UsdPreviewSurface", pxr::TfToken::Immortal);
static const pxr::TfToken uv_texture("UsdUVTexture", pxr::TfToken::Immortal);
static const pxr::TfToken primvar_float2("UsdPrimvarReader_float2", pxr::TfToken::Immortal);
static const pxr::TfToken roughness("roughness", pxr::TfToken::Immortal);
static const pxr::TfToken specular("specular", pxr::TfToken::Immortal);
static const pxr::TfToken opacity("opacity", pxr::TfToken::Immortal);
static const pxr::TfToken surface("surface", pxr::TfToken::Immortal);
static const pxr::TfToken perspective("perspective", pxr::TfToken::Immortal);
static const pxr::TfToken orthographic("orthographic", pxr::TfToken::Immortal);
static const pxr::TfToken rgb("rgb", pxr::TfToken::Immortal);
static const pxr::TfToken r("r", pxr::TfToken::Immortal);
static const pxr::TfToken g("g", pxr::TfToken::Immortal);
static const pxr::TfToken b("b", pxr::TfToken::Immortal);
static const pxr::TfToken st("st", pxr::TfToken::Immortal);
static const pxr::TfToken result("result", pxr::TfToken::Immortal);
static const pxr::TfToken varname("varname", pxr::TfToken::Immortal);
static const pxr::TfToken mdl("mdl", pxr::TfToken::Immortal);
static const pxr::TfToken out("out", pxr::TfToken::Immortal);
static const pxr::TfToken normal("normal", pxr::TfToken::Immortal);
static const pxr::TfToken ior("ior", pxr::TfToken::Immortal);
static const pxr::TfToken file("file", pxr::TfToken::Immortal);
static const pxr::TfToken preview("preview", pxr::TfToken::Immortal);
static const pxr::TfToken raw("raw", pxr::TfToken::Immortal);
static const pxr::TfToken sRGB("sRGB", pxr::TfToken::Immortal);
static const pxr::TfToken sourceColorSpace("sourceColorSpace", pxr::TfToken::Immortal);
static const pxr::TfToken Shader("Shader", pxr::TfToken::Immortal);
} // namespace usdtokens
/* Cycles specific tokens (Blender Importer and HdCycles) */
namespace cyclestokens {
static const pxr::TfToken cycles("cycles", pxr::TfToken::Immortal);
static const pxr::TfToken UVMap("UVMap", pxr::TfToken::Immortal);
static const pxr::TfToken filename("filename", pxr::TfToken::Immortal);
static const pxr::TfToken interpolation("interpolation", pxr::TfToken::Immortal);
static const pxr::TfToken projection("projection", pxr::TfToken::Immortal);
static const pxr::TfToken extension("extension", pxr::TfToken::Immortal);
static const pxr::TfToken colorspace("colorspace", pxr::TfToken::Immortal);
static const pxr::TfToken attribute("attribute", pxr::TfToken::Immortal);
static const pxr::TfToken bsdf("bsdf", pxr::TfToken::Immortal);
static const pxr::TfToken closure("closure", pxr::TfToken::Immortal);
static const pxr::TfToken vector("vector", pxr::TfToken::Immortal);
} // namespace cyclestokens
namespace blender::io::usd {
/* Returns true if the given paths are equal,
* returns false otherwise. */
static bool paths_equal(const char *p1, const char *p2)
{
/* Normalize the paths so we can compare them. */
char norm_p1[FILE_MAX];
char norm_p2[FILE_MAX];
BLI_strncpy(norm_p1, p1, sizeof(norm_p1));
BLI_strncpy(norm_p2, p2, sizeof(norm_p2));
#ifdef WIN32
/* On Windows, BLI_path_normalize() expects
* backslash separators, so we caonvert all
* forward slashes to backslashes.
* TODO(makowalski): consider alternatives to
* avoid this. */
BLI_str_replace_char(norm_p1, '/', '\\');
BLI_str_replace_char(norm_p2, '/', '\\');
#endif
BLI_path_normalize(nullptr, norm_p1);
BLI_path_normalize(nullptr, norm_p2);
return BLI_path_cmp(norm_p1, norm_p2) == 0;
}
/* Generate a file name for an in-memory image that doesn't have a
* filepath already defined. */
static std::string get_in_memory_texture_filename(bNode *node)
{
if (!node) {
return "";
}
Image *ima = reinterpret_cast<Image *>(node->id);
if (!ima) {
return "";
}
if (strlen(ima->filepath) > 0) {
/* We only generate a filename if the image
* doesn't already have one. */
return "";
}
bool is_dirty = BKE_image_is_dirty(ima);
bool is_generated = ima->source == IMA_SRC_GENERATED;
bool is_packed = BKE_image_has_packedfile(ima);
if (!(is_generated || is_dirty || is_packed)) {
return "";
}
char file_name[FILE_MAX];
file_name[0] = '\0';
/* Try using the iamge name for the file name. */
/* Sanity check that the id name isn't empty. */
if (strlen(ima->id.name) < 3) {
return "";
}
strcpy(file_name, ima->id.name + 2);
/* Determine the correct file extension from the image format. */
ImBuf *imbuf = BKE_image_acquire_ibuf(ima, nullptr, nullptr);
if (!imbuf) {
return "";
}
ImageFormatData imageFormat;
BKE_imbuf_to_image_format(&imageFormat, imbuf);
BKE_image_path_ensure_ext_from_imformat(file_name, &imageFormat);
return file_name;
}
static void export_in_memory_texture(Image *ima,
const std::string &export_dir,
const bool allow_overwrite)
{
if (!ima) {
return;
}
char file_name[FILE_MAX] = {0};
if (strlen(ima->filepath) > 0) {
BLI_split_file_part(ima->filepath, file_name, FILE_MAX);
}
else {
/* Try using the image name for the file name. */
strcpy(file_name, ima->id.name + 2);
}
if (strlen(file_name) == 0) {
printf("WARNING: Couldn't retrieve in memory texture file name.\n");
return;
}
ImBuf *imbuf = BKE_image_acquire_ibuf(ima, nullptr, nullptr);
if (!imbuf) {
return;
}
ImageFormatData imageFormat;
BKE_imbuf_to_image_format(&imageFormat, imbuf);
/* This image in its current state only exists in Blender memory.
* So we have to export it. The export will keep the image state intact,
* so the exported file will not be associated with the image. */
BKE_image_path_ensure_ext_from_imformat(file_name, &imageFormat);
std::string export_path = export_dir;
if (export_path.back() != '/' && export_path.back() != '\\') {
export_path += "/";
}
export_path += std::string(file_name);
if (!allow_overwrite && BLI_exists(export_path.c_str())) {
return;
}
std::cout << "Exporting in-memory texture to " << export_path << std::endl;
if (BKE_imbuf_write_as(imbuf, export_path.c_str(), &imageFormat, true) == 0) {
std::cout << "WARNING: couldn't export in-memory texture to " << export_path << std::endl;
}
}
/* Get the absolute filepath of the given image. */
static void get_absolute_path(Image *ima, size_t path_len, char *r_path)
{
if (!r_path || path_len == 0) {
return;
}
if (!ima) {
r_path[0] = '\0';
return;
}
/* make absolute source path */
BLI_strncpy(r_path, ima->filepath, path_len);
BLI_path_abs(r_path, ID_BLEND_PATH_FROM_GLOBAL(&ima->id));
BLI_path_normalize(nullptr, r_path);
}
/* If the given image is tiled, copy the image tiles to the given
* destination directory. */
static void copy_tiled_textures(Image *ima,
const std::string &in_dest_dir,
const bool allow_overwrite)
{
if (!ima || in_dest_dir.empty()) {
return;
}
if (ima->source != IMA_SRC_TILED) {
return;
}
std::string dest_dir = in_dest_dir;
if (dest_dir.back() != '/' && dest_dir.back() != '\\') {
dest_dir += "/";
}
char src_path[FILE_MAX];
get_absolute_path(ima, sizeof(src_path), src_path);
char src_dir[FILE_MAX];
char src_file[FILE_MAX];
BLI_split_dirfile(src_path, src_dir, src_file, FILE_MAX, FILE_MAX);
char head[FILE_MAX], tail[FILE_MAX];
unsigned short numlen;
BLI_path_sequence_decode(src_file, head, tail, &numlen);
/* Copy all tiles. */
LISTBASE_FOREACH (ImageTile *, tile, &ima->tiles) {
char tile_file[FILE_MAX];
/* Build filepath of the tile. */
BLI_path_sequence_encode(tile_file, head, tail, numlen, tile->tile_number);
std::string dest_tile_path = dest_dir + std::string(tile_file);
if (!allow_overwrite && BLI_exists(dest_tile_path.c_str())) {
continue;
}
std::string src_tile_path = std::string(src_dir) + std::string(tile_file);
if (allow_overwrite && paths_equal(src_tile_path.c_str(), dest_tile_path.c_str())) {
/* Source and destination paths are the same, don't copy. */
continue;
}
std::cout << "Copying texture tile from " << src_tile_path << " to " << dest_tile_path
<< std::endl;
/* Copy the file. */
if (BLI_copy(src_tile_path.c_str(), dest_tile_path.c_str()) != 0) {
std::cout << "WARNING: couldn't copy texture tile from " << src_tile_path << " to "
<< dest_tile_path << std::endl;
}
}
}
/* Copy the given image to the destination directory. */
static void copy_single_file(Image *ima, const std::string &dest_dir, const bool allow_overwrite)
{
if (!ima || dest_dir.empty()) {
return;
}
char source_path[FILE_MAX];
get_absolute_path(ima, sizeof(source_path), source_path);
char file_name[FILE_MAX];
BLI_split_file_part(source_path, file_name, FILE_MAX);
std::string dest_path = dest_dir;
if (dest_path.back() != '/' && dest_path.back() != '\\') {
dest_path += "/";
}
dest_path += std::string(file_name);
if (!allow_overwrite && BLI_exists(dest_path.c_str())) {
return;
}
if (allow_overwrite && paths_equal(source_path, dest_path.c_str())) {
/* Source and destination paths are the same, don't copy. */
return;
}
std::cout << "Copying texture from " << source_path << " to " << dest_path << std::endl;
/* Copy the file. */
if (BLI_copy(source_path, dest_path.c_str()) != 0) {
std::cout << "WARNING: couldn't copy texture from " << source_path << " to " << dest_path
<< std::endl;
}
}
/* Gets a NodeTexImage's filepath */
static std::string get_node_tex_image_filepath(bNode *node)
{
NodeTexImage *tex_original = (NodeTexImage *)node->storage;
Image *ima = (Image *)node->id;
if (!ima)
return "";
if (sizeof(ima->filepath) == 0)
return "";
char filepath[1024] = "\0";
strncpy(filepath, ima->filepath, sizeof(ima->filepath));
BKE_image_user_file_path(&tex_original->iuser, ima, filepath);
BLI_str_replace_char(filepath, '\\', '/');
if (ima->source == IMA_SRC_TILED) {
char head[FILE_MAX], tail[FILE_MAX];
unsigned short numlen;
BLI_path_sequence_decode(filepath, head, tail, &numlen);
return (std::string(head) + "<UDIM>" + std::string(tail));
}
return std::string(filepath);
}
static pxr::TfToken get_node_tex_image_color_space(bNode *node)
{
if (node->type != SH_NODE_TEX_IMAGE) {
std::cout << "get_node_tex_image_color_space() called with unexpected type.\n";
return pxr::TfToken();
}
if (node->id == nullptr) {
return pxr::TfToken();
}
Image *ima = reinterpret_cast<Image *>(node->id);
pxr::TfToken color_space;
if (strcmp(ima->colorspace_settings.name, "Raw") == 0) {
color_space = usdtokens::raw;
}
else if (strcmp(ima->colorspace_settings.name, "Non-Color") == 0) {
color_space = usdtokens::raw;
}
else if (strcmp(ima->colorspace_settings.name, "sRGB") == 0) {
color_space = usdtokens::sRGB;
}
return color_space;
}
/* Search the upstream nodes connected to the given socket and return the first occurrance
* of the node of the given type. Return null if no node of this type was found. */
static bNode *traverse_channel(bNodeSocket *input, short target_type = SH_NODE_TEX_IMAGE)
{
if (input->link) {
bNode *linked_node = input->link->fromnode;
if (linked_node->type == target_type) {
/* Return match. */
return linked_node;
}
/* Recursively traverse the linked node's sockets. */
for (bNodeSocket *sock = static_cast<bNodeSocket *>(linked_node->inputs.first); sock;
sock = sock->next) {
if (bNode *found_node = traverse_channel(sock)) {
return found_node;
}
}
}
return nullptr;
}
/* Returns the first occurence of a principled bsdf or a diffuse bsdf node found in the given
* material's node tree. Returns null if no instance of either type was found.*/
static bNode *get_bsdf_node(Material *material)
{
if (!material) {
return nullptr;
}
LISTBASE_FOREACH (bNode *, node, &material->nodetree->nodes) {
if (node->type == SH_NODE_BSDF_PRINCIPLED || node->type == SH_NODE_BSDF_DIFFUSE) {
return node;
}
}
return nullptr;
}
/* Creates a USD Preview Surface shader based on the given cycles node name and type. */
static pxr::UsdShadeShader create_usd_preview_shader(const USDExporterContext &usd_export_context,
pxr::UsdShadeMaterial &material,
const char *name,
int type)
{
if (!name || !material) {
return pxr::UsdShadeShader();
}
pxr::SdfPath shader_path = material.GetPath()
.AppendChild(usdtokens::preview)
.AppendChild(pxr::TfToken(pxr::TfMakeValidIdentifier(name)));
pxr::UsdShadeShader shader = pxr::UsdShadeShader::Define(usd_export_context.stage, shader_path);
switch (type) {
case SH_NODE_TEX_IMAGE: {
shader.CreateIdAttr(pxr::VtValue(usdtokens::uv_texture));
break;
}
case SH_NODE_TEX_COORD:
case SH_NODE_UVMAP: {
shader.CreateIdAttr(pxr::VtValue(usdtokens::primvar_float2));
break;
}
case SH_NODE_BSDF_DIFFUSE:
case SH_NODE_BSDF_PRINCIPLED: {
shader.CreateIdAttr(pxr::VtValue(usdtokens::preview_surface));
material.CreateSurfaceOutput().ConnectToSource(shader, usdtokens::surface);
break;
}
default:
break;
}
return shader;
}
/* Creates a USD Preview Surface shader based on the given cycles shading node. */
static pxr::UsdShadeShader create_usd_preview_shader(const USDExporterContext &usd_export_context,
pxr::UsdShadeMaterial &material,
bNode *node)
{
if (!node) {
return pxr::UsdShadeShader();
}
pxr::UsdShadeShader shader = create_usd_preview_shader(
usd_export_context, material, node->name, node->type);
if (node->type == SH_NODE_TEX_IMAGE) {
/* For texture image nodes we set the image path and color space. */
std::string imagePath = get_node_tex_image_filepath(
node, usd_export_context.stage, usd_export_context.export_params);
if (!imagePath.empty()) {
shader.CreateInput(usdtokens::file, pxr::SdfValueTypeNames->Asset)
.Set(pxr::SdfAssetPath(imagePath));
}
pxr::TfToken colorSpace = get_node_tex_image_color_space(node);
if (!colorSpace.IsEmpty()) {
shader.CreateInput(usdtokens::sourceColorSpace, pxr::SdfValueTypeNames->Token)
.Set(colorSpace);
}
}
return shader;
}
/* Entry point to create an approximate USD Preview Surface network from a Cycles node graph.
* Due to the limited nodes in the USD Preview Surface specification, only the following nodes
* are supported:
* - UVMap
* - Texture Coordinate
* - Image Texture
* - Principled BSDF
* More may be added in the future.
*
* The 'default_uv' paramter is used as the default UV set name sampled by the primvar
* reader shaders generated for image texture nodes shat don't have an attached UVMAp node. */
void create_usd_preview_surface_material(const USDExporterContext &usd_export_context,
Material *material,
pxr::UsdShadeMaterial &usd_material,
const std::string &default_uv)
{
/* Define a 'preview' scope beneath the material which will contain the preview shaders. */
pxr::UsdGeomScope::Define(usd_export_context.stage,
usd_material.GetPath().AppendChild(usdtokens::preview));
pxr::TfToken default_uv_sampler = default_uv.empty() ? cyclestokens::UVMap :
pxr::TfToken(default_uv);
/* We only handle the first instance of either principled or
* diffuse bsdf nodes in the material's node tree, because
* USD Preview Surface has no concept of layering materials. */
bNode *node = get_bsdf_node(material);
if (!node) {
return;
}
pxr::UsdShadeShader preview_surface = create_usd_preview_shader(
usd_export_context, usd_material, node);
/* Set the preview surface inputs. */
LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) {
bNode *input_node = nullptr;
pxr::UsdShadeShader created_shader;
if (STREQ(sock->name, "Base Color") || STREQ(sock->name, "Color")) {
input_node = traverse_channel(sock);
if (input_node) {
/* Create connection. */
created_shader = create_usd_preview_shader(usd_export_context, usd_material, input_node);
preview_surface.CreateInput(usdtokens::diffuse_color, pxr::SdfValueTypeNames->Float3)
.ConnectToSource(created_shader, usdtokens::rgb);
}
else {
/* Set hardcoded value. */
bNodeSocketValueRGBA *socket_data = (bNodeSocketValueRGBA *)sock->default_value;
preview_surface.CreateInput(usdtokens::diffuse_color, pxr::SdfValueTypeNames->Float3)
.Set(pxr::VtValue(pxr::GfVec3f(
socket_data->value[0], socket_data->value[1], socket_data->value[2])));
}
}
else if (STREQ(sock->name, "Roughness")) {
input_node = traverse_channel(sock);
if (input_node) {
/* Create connection. */
created_shader = create_usd_preview_shader(usd_export_context, usd_material, input_node);
preview_surface.CreateInput(usdtokens::roughness, pxr::SdfValueTypeNames->Float)
.ConnectToSource(created_shader, usdtokens::r);
}
else {
/* Set hardcoded value. */
bNodeSocketValueFloat *socket_data = (bNodeSocketValueFloat *)sock->default_value;
preview_surface.CreateInput(usdtokens::roughness, pxr::SdfValueTypeNames->Float)
.Set(pxr::VtValue(socket_data->value));
}
}
else if (STREQ(sock->name, "Metallic")) {
input_node = traverse_channel(sock);
if (input_node) {
/* Create connection. */
created_shader = create_usd_preview_shader(usd_export_context, usd_material, input_node);
preview_surface.CreateInput(usdtokens::metallic, pxr::SdfValueTypeNames->Float)
.ConnectToSource(created_shader, usdtokens::r);
}
else {
/* Set hardcoded value. */
bNodeSocketValueFloat *socket_data = (bNodeSocketValueFloat *)sock->default_value;
preview_surface.CreateInput(usdtokens::metallic, pxr::SdfValueTypeNames->Float)
.Set(pxr::VtValue(socket_data->value));
}
}
else if (STREQ(sock->name, "Specular")) {
input_node = traverse_channel(sock);
if (input_node) {
/* Create connection. */
created_shader = create_usd_preview_shader(usd_export_context, usd_material, input_node);
preview_surface.CreateInput(usdtokens::specular, pxr::SdfValueTypeNames->Float)
.ConnectToSource(created_shader, usdtokens::r);
}
else {
/* Set hardcoded value. */
bNodeSocketValueFloat *socket_data = (bNodeSocketValueFloat *)sock->default_value;
preview_surface.CreateInput(usdtokens::specular, pxr::SdfValueTypeNames->Float)
.Set(pxr::VtValue(socket_data->value));
}
}
else if (STREQ(sock->name, "Alpha")) {
input_node = traverse_channel(sock);
if (input_node) {
/* Create connection. */
created_shader = create_usd_preview_shader(usd_export_context, usd_material, input_node);
preview_surface.CreateInput(usdtokens::opacity, pxr::SdfValueTypeNames->Float)
.ConnectToSource(created_shader, usdtokens::r);
}
else {
/* Set hardcoded value. */
bNodeSocketValueFloat *socket_data = (bNodeSocketValueFloat *)sock->default_value;
preview_surface.CreateInput(usdtokens::opacity, pxr::SdfValueTypeNames->Float)
.Set(pxr::VtValue(socket_data->value));
}
}
else if (STREQ(sock->name, "IOR")) {
input_node = traverse_channel(sock);
if (input_node) {
/* Create connection. */
created_shader = create_usd_preview_shader(usd_export_context, usd_material, input_node);
preview_surface.CreateInput(usdtokens::ior, pxr::SdfValueTypeNames->Float)
.ConnectToSource(created_shader, usdtokens::r);
}
else {
/* Set hardcoded value. */
bNodeSocketValueFloat *socket_data = (bNodeSocketValueFloat *)sock->default_value;
preview_surface.CreateInput(usdtokens::ior, pxr::SdfValueTypeNames->Float)
.Set(pxr::VtValue(socket_data->value));
}
}
else if (STREQ(sock->name, "Normal")) {
input_node = traverse_channel(sock);
if (input_node) {
created_shader = create_usd_preview_shader(usd_export_context, usd_material, input_node);
preview_surface.CreateInput(usdtokens::normal, pxr::SdfValueTypeNames->Float3)
.ConnectToSource(created_shader, usdtokens::rgb);
}
/* We don't handle hardcoded value. */
}
else if (STREQ(sock->name, "Clearcoat")) {
input_node = traverse_channel(sock);
if (input_node) {
/* Create connection. */
created_shader = create_usd_preview_shader(usd_export_context, usd_material, input_node);
preview_surface.CreateInput(usdtokens::clearcoat, pxr::SdfValueTypeNames->Float)
.ConnectToSource(created_shader, usdtokens::r);
}
else {
/* Set hardcoded value. */
bNodeSocketValueFloat *socket_data = (bNodeSocketValueFloat *)sock->default_value;
preview_surface.CreateInput(usdtokens::clearcoat, pxr::SdfValueTypeNames->Float)
.Set(pxr::VtValue(socket_data->value));
}
}
else if (STREQ(sock->name, "Clearcoat Roughness")) {
input_node = traverse_channel(sock);
if (input_node) {
/* Create connection. */
created_shader = create_usd_preview_shader(usd_export_context, usd_material, input_node);
preview_surface.CreateInput(usdtokens::clearcoatRoughness, pxr::SdfValueTypeNames->Float)
.ConnectToSource(created_shader, usdtokens::r);
}
else {
/* Set hardcoded value. */
bNodeSocketValueFloat *socket_data = (bNodeSocketValueFloat *)sock->default_value;
preview_surface.CreateInput(usdtokens::clearcoatRoughness, pxr::SdfValueTypeNames->Float)
.Set(pxr::VtValue(socket_data->value));
}
}
/* If any input texture node has been found, export the texture, if necessary,
* and look for a connected uv node. */
if (created_shader && input_node && input_node->type == SH_NODE_TEX_IMAGE) {
if (usd_export_context.export_params.export_textures) {
export_texture(input_node,
usd_export_context.stage,
usd_export_context.export_params.overwrite_textures);
}
bool found_uv_node = false;
/* Find UV input to the texture node. */
LISTBASE_FOREACH (bNodeSocket *, input_node_sock, &input_node->inputs) {
if (!input_node_sock || !input_node_sock->link ||
!STREQ(input_node_sock->name, "Vector")) {
continue;
}
bNode *uv_node = traverse_channel(input_node_sock, SH_NODE_UVMAP);
if (uv_node == NULL) {
continue;
}
pxr::UsdShadeShader uv_shader = create_usd_preview_shader(
usd_export_context, usd_material, uv_node);
if (!uv_shader.GetPrim().IsValid())
continue;
found_uv_node = true;
if (NodeShaderUVMap *shader_uv_map = static_cast<NodeShaderUVMap *>(uv_node->storage)) {
/* We need to make valid here because actual uv primvar has been. */
std::string uv_set = pxr::TfMakeValidIdentifier(shader_uv_map->uv_map);
uv_shader.CreateInput(usdtokens::varname, pxr::SdfValueTypeNames->Token)
.Set(pxr::TfToken(uv_set));
created_shader.CreateInput(usdtokens::st, pxr::SdfValueTypeNames->Float2)
.ConnectToSource(uv_shader, usdtokens::result);
}
else {
uv_shader.CreateInput(usdtokens::varname, pxr::SdfValueTypeNames->Token)
.Set(default_uv_sampler);
created_shader.CreateInput(usdtokens::st, pxr::SdfValueTypeNames->Float2)
.ConnectToSource(uv_shader, usdtokens::result);
}
}
if (!found_uv_node) {
/* No UVMAP node was linked to the texture node. However, we generate
* a primvar reader node that specifies the UV set to sample, as some
* DCCs require this. */
pxr::UsdShadeShader uv_shader = create_usd_preview_shader(
usd_export_context, usd_material, "uvmap", SH_NODE_TEX_COORD);
if (uv_shader.GetPrim().IsValid()) {
uv_shader.CreateInput(usdtokens::varname, pxr::SdfValueTypeNames->Token)
.Set(default_uv_sampler);
created_shader.CreateInput(usdtokens::st, pxr::SdfValueTypeNames->Float2)
.ConnectToSource(uv_shader, usdtokens::result);
}
}
}
}
}
/* Entry point to create USD Shade Material network from Blender "Viewport Display". */
void create_usd_viewport_material(const USDExporterContext &usd_export_context,
Material *material,
pxr::UsdShadeMaterial &usd_material)
{
/* Construct the shader. */
pxr::SdfPath shader_path = usd_material.GetPath().AppendChild(usdtokens::preview_shader);
pxr::UsdShadeShader shader = pxr::UsdShadeShader::Define(usd_export_context.stage, shader_path);
shader.CreateIdAttr(pxr::VtValue(usdtokens::preview_surface));
shader.CreateInput(usdtokens::diffuse_color, pxr::SdfValueTypeNames->Color3f)
.Set(pxr::GfVec3f(material->r, material->g, material->b));
shader.CreateInput(usdtokens::roughness, pxr::SdfValueTypeNames->Float).Set(material->roughness);
shader.CreateInput(usdtokens::metallic, pxr::SdfValueTypeNames->Float).Set(material->metallic);
/* Connect the shader and the material together. */
usd_material.CreateSurfaceOutput().ConnectToSource(shader, usdtokens::surface);
}
/* Gets a NodeTexImage's filepath, returning a path in the texture export directory or a relative
* path, if the export parameters require it. */
std::string get_node_tex_image_filepath(bNode *node,
const pxr::UsdStageRefPtr stage,
const USDExportParams &export_params)
{
std::string image_path = get_node_tex_image_filepath(node);
if (image_path.empty() && export_params.export_textures) {
/* The path may be empty because this is an in-memory texture.
* Since we are exporting textures, check if this is an
* in-memory texture for which we can generate a file name. */
image_path = get_in_memory_texture_filename(node);
}
return get_texture_filepath(image_path, stage, export_params);
}
/* Export the given texture node's image to a 'textures' directory
* next to given stage's root layer USD.
* Based on ImagesExporter::export_UV_Image() */
void export_texture(bNode *node, const pxr::UsdStageRefPtr stage, const bool allow_overwrite)
{
if (!stage || !node ||
(node->type != SH_NODE_TEX_IMAGE && node->type != SH_NODE_TEX_ENVIRONMENT)) {
return;
}
// Get the path relative to the USD.
// TODO(makowalski): avoid recomputing the USD path, if possible.
pxr::SdfLayerHandle layer = stage->GetRootLayer();
std::string stage_path = layer->GetRealPath();
if (stage_path.empty()) {
return;
}
Image *ima = reinterpret_cast<Image *>(node->id);
if (!ima) {
return;
}
char usd_dir_path[FILE_MAX];
BLI_split_dir_part(stage_path.c_str(), usd_dir_path, FILE_MAX);
std::string dest_dir(usd_dir_path);
dest_dir += "textures";
BLI_dir_create_recursive(dest_dir.c_str());
dest_dir += "/";
bool is_dirty = BKE_image_is_dirty(ima);
bool is_generated = ima->source == IMA_SRC_GENERATED;
bool is_packed = BKE_image_has_packedfile(ima);
if (is_generated || is_dirty || is_packed) {
export_in_memory_texture(ima, dest_dir, allow_overwrite);
}
else if (ima->source == IMA_SRC_TILED) {
copy_tiled_textures(ima, dest_dir, allow_overwrite);
}
else {
copy_single_file(ima, dest_dir, allow_overwrite);
}
}
/* Process the given file path 'in_path' to convert it to an export path
* for textures or to make the path relative to the given stage's root USD
* layer. */
std::string get_texture_filepath(const std::string &in_path,
const pxr::UsdStageRefPtr stage,
const USDExportParams &export_params)
{
/* Do nothing if we are not exporting textures or using relative texture paths. */
if (!(export_params.relative_texture_paths || export_params.export_textures)) {
return in_path;
}
if (in_path.empty() || !stage) {
return in_path;
}
pxr::SdfLayerHandle layer = stage->GetRootLayer();
std::string stage_path = layer->GetRealPath();
if (stage_path.empty()) {
return in_path;
}
/* If we are exporting textures, set the textures directory in the path. */
if (export_params.export_textures) {
/* The texture is exported to a 'textures' directory next to the
* USD root layer. */
char dir_path[FILE_MAX];
char file_path[FILE_MAX];
BLI_split_dir_part(stage_path.c_str(), dir_path, FILE_MAX);
BLI_split_file_part(in_path.c_str(), file_path, FILE_MAX);
BLI_str_replace_char(dir_path, '\\', '/');
std::string result;
if (export_params.relative_texture_paths) {
result = "./textures/";
}
else {
result = std::string(dir_path);
if (result.back() != '/' && result.back() != '\\') {
result += "/";
}
result += "textures/";
}
result += std::string(file_path);
return result;
}
// Get the path relative to the USD.
char rel_path[FILE_MAX];
strcpy(rel_path, in_path.c_str());
BLI_path_rel(rel_path, stage_path.c_str());
/* BLI_path_rel adds '//' as a prefix to the path, if
* generating the relative path was successful. */
if (rel_path[0] != '/' || rel_path[1] != '/') {
/* No relative path generated. */
return in_path;
}
int offset = 0;
if (rel_path[2] != '.') {
rel_path[0] = '.';
}
else {
offset = 2;
}
BLI_str_replace_char(rel_path, '\\', '/');
return std::string(rel_path + offset);
}
} // namespace blender::io::usd

View File

@@ -0,0 +1,53 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#pragma once
#include <pxr/pxr.h>
#include <pxr/usd/usd/prim.h>
#include <pxr/usd/usd/stage.h>
#include <pxr/usd/usdShade/material.h>
#include <string>
struct bNode;
struct bNodeTree;
struct Material;
struct USDExportParams;
namespace blender::io::usd {
struct USDExporterContext;
void create_usd_preview_surface_material(const USDExporterContext &usd_export_context,
Material *material,
pxr::UsdShadeMaterial &usd_material,
const std::string &default_uv = "");
void create_usd_viewport_material(const USDExporterContext &usd_export_context,
Material *material,
pxr::UsdShadeMaterial &usd_material);
void export_texture(bNode *node, const pxr::UsdStageRefPtr stage, bool allow_overwrite = false);
std::string get_node_tex_image_filepath(bNode *node,
const pxr::UsdStageRefPtr stage,
const USDExportParams &export_params);
std::string get_texture_filepath(const std::string &tex_filepath,
const pxr::UsdStageRefPtr stage,
const USDExportParams &export_params);
} // Namespace blender::io::usd

View File

@@ -319,7 +319,7 @@ void USDGenericMeshWriter::assign_materials(const HierarchyContext &context,
continue;
}
pxr::UsdShadeMaterial usd_material = ensure_usd_material(material);
pxr::UsdShadeMaterial usd_material = ensure_usd_material(material, context);
material_binding_api.Bind(usd_material);
/* USD seems to support neither per-material nor per-face-group double-sidedness, so we just
@@ -353,7 +353,7 @@ void USDGenericMeshWriter::assign_materials(const HierarchyContext &context,
continue;
}
pxr::UsdShadeMaterial usd_material = ensure_usd_material(material);
pxr::UsdShadeMaterial usd_material = ensure_usd_material(material, context);
pxr::TfToken material_name = usd_material.GetPath().GetNameToken();
pxr::UsdGeomSubset usd_face_subset = material_binding_api.CreateMaterialBindSubset(

View File

@@ -41,6 +41,10 @@ struct USDExportParams {
bool visible_objects_only;
bool use_instancing;
enum eEvaluationMode evaluation_mode;
bool generate_preview_surface;
bool export_textures;
bool overwrite_textures;
bool relative_texture_paths;
};
struct USDImportParams {