Compare commits

...

33 Commits

Author SHA1 Message Date
5672223677 Merge branch 'temp-usd-preview-surf-export' into temp-usd-prev-export2 2022-01-14 20:21:04 -05:00
af8e8e2ae5 USD material export code cleanup.
Fixed comment and removed unnecessary check for
empty string.
2022-01-14 20:14:47 -05:00
ff80cbbeeb USD material export format fixes. 2022-01-14 19:55:29 -05:00
e27cd0e892 USD material export code cleanup.
Moved repeated code for setting the shader
input value to a common function, as suggested
by Sybren in his review.
2022-01-14 19:48:48 -05:00
dd9dc5946c Merge branch 'temp-usd-preview-surf-export' into temp-usd-prev-export2 2022-01-12 11:12:02 -05:00
0cef709213 USD material export cleanup.
Changes based on Sybren's review:

Moved paths_equal() helper function code to common
BLI_paths_equal() function.

Using switch statement instead of if/else chain when
creating shader input based on the socket type.

Using early returns where appropriate.

Removed unnecessary function argument validity checks.

Made variables const.

Moved related functions closer together.

Additional miscellaneous changes.
2022-01-12 11:10:19 -05:00
166084ec47 Merge branch 'temp-usd-preview-surf-export' into temp-usd-prev-export2 2022-01-06 19:41:09 -05:00
03199e7f3a USD export: use new UDIM virtual filepath API.
Applied Jesse Yurkovich's patch to update the UDIM
tile texture export code to conform to the new virtual
filepath specification requirements.
2022-01-06 19:36:03 -05:00
442b8e3dcd Merge remote-tracking branch 'origin/master' into temp-usd-preview-surf-export 2022-01-06 18:02:02 -05:00
30e6a13c35 Merge remote-tracking branch 'origin/master' into temp-usd-prev-export2 2022-01-06 17:51:23 -05:00
18bf798dc6 USD material export code cleanup.
Added const where needed.

Updated conditional in get_tex_image_asset_path() to avoid
recomputing relative path when exporting textures.
2021-12-28 06:45:08 -05:00
a5f22596c1 USD Preview Surface export format fixes. 2021-12-28 00:10:59 -05:00
63c8742780 USD texture export refactor.
Updated the texture export functions to use BLI_path_join(),
additional minor cleanup.
2021-12-28 00:05:47 -05:00
3e976ef111 USD Preview Surface export refactor.
Added new get_tex_image_asset_path() which simplifies
texture path resolution logic, to replace similar functions
where the logic was fragmented.  Now calling BLI_path_join(),
to replace string concatenation for paths. Added comments,
additional minor cleanup.
2021-12-27 22:00:44 -05:00
ecbac70683 USD export: skip texture copy for equal paths.
Skip texture copy when the source and destination paths
are equivalent.
2021-12-27 14:49:16 -05:00
b748ad7488 USD material export: reorder code.
Moved related get_node_tex_image_filepath() functions closer
togther, per suggestion by Sybren in his review.
2021-12-27 14:13:41 -05:00
c80cf69a5b USD Preview Surface export report warnings.
Report warnings in UI instead of printing them to the
console.
2021-12-27 12:18:05 -05:00
5300c8430e USD export: format fixes. 2021-12-27 11:04:05 -05:00
91c39cbb6a USD Preview Surface export refactor.
Refactored the create_usd_preview_surface_material() implementation
to use a map to avoid multiple if/else statments.  Further
simplified this function by moving some of its functionality
to a new create_uvmap_shader() function.
2021-12-27 07:57:04 -05:00
cbbef8aba2 USD Preview Surface export code cleanup.
Removed excessive null argument checks at the beginning
of utility functions.  Added missing braces in if
statement.  Misc. minor cleanup.
2021-12-27 00:42:14 -05:00
fd39018f7d USD Preview Surface export typo in comments. 2021-12-26 23:31:30 -05:00
d3d053d793 USD Preview Surface export cleanup.
Misc. fixes suggested by Sybren in his review,
including:

Renamed get_bsdf_node() to find_bsdf_node().

Remove excessively defensive validation and initialization.

Remove unneeded console output.

Early returns to avoid else statements.

Use LISTBASE_FOREACH macro.
2021-12-26 23:19:30 -05:00
08553b4c9f USD Preview Surface export cleanup.
Now calling standard BLI_path_slash_native()
in the path comparison function.
2021-12-26 21:53:50 -05:00
cbff81ac9b USD Preview Surface export code reorganization.
Re-ordered the code to place high-level functions first in the
file and the lower-level/helper functions later (per suggestion
by Sybren in his review).  Also removed declarartions of
helper functions from the header, as these can be static for now.
Some additional minor cleanup to formatting and comments.
2021-12-26 21:06:00 -05:00
5bebc3cfb6 USD Preview Surface export code comments.
Moved comments from usd_writer_material.cc to header (per
suggestion by Sybren in his review).
2021-12-26 20:23:04 -05:00
ffdf6a85bf USD Preview Surface export code cleanup.
Removed declarations of unused UsdToken variables.
2021-12-26 20:03:33 -05:00
e0bd3f03f5 USD Preview Surface export code cleanup.
Compacted the code by removing spaces between variable declarations
and their use (per suggestion by Sybren in his review).
2021-12-26 19:41:24 -05:00
03c0bb75c5 USD Export: shared code to get active uv name.
Added CustomData_get_active_layer_name() function to
BKE_customdata.h to return the name of the active
layer of a given type, as this functionality can
be used in both the USD export and Collada IO
utility functions when retrieving the name of a
mesh's active UV layer.  Refactored the USD Preview
Surface code to use this function.
2021-12-26 19:12:33 -05:00
2156f238b0 USD Preview Surface export code cleanup.
Following convention to make context the first
parameter.
2021-12-26 15:20:08 -05:00
5c9875f668 USD Preview Surface export UI improvements.
Updates based on suggestions by Hans Goudey in his
review.

Made variables const.

Now calling uiLayoutSetActive() instead of uiLayoutSetEnabled().

Shortened the 'USD Preview Surface From Nodes' property name
and improved property descriptions.
2021-12-26 14:20:09 -05:00
02d6550dd0 USD Preview Surface material export.
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.

USD export format fixes.

USD export: fixed typo in comparison.

Differential Revision: https://developer.blender.org/D13647
2021-12-21 11:51:24 -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
12 changed files with 949 additions and 15 deletions

View File

@@ -441,6 +441,12 @@ int CustomData_get_render_layer(const struct CustomData *data, int type);
int CustomData_get_clone_layer(const struct CustomData *data, int type);
int CustomData_get_stencil_layer(const struct CustomData *data, int type);
/**
* Returns name of the active layer of the given type or NULL
* if no such active layer is defined.
*/
const char *CustomData_get_active_layer_name(const struct CustomData *data, int type);
/**
* Copies the data from source to the data element at index in the first layer of type
* no effect if there is no layer of type.

View File

@@ -2443,6 +2443,13 @@ int CustomData_get_stencil_layer(const CustomData *data, int type)
return (layer_index != -1) ? data->layers[layer_index].active_mask : -1;
}
const char *CustomData_get_active_layer_name(const struct CustomData *data, const int type)
{
/* Get the layer index of the active layer of this type. */
const int layer_index = CustomData_get_active_layer_index(data, type);
return layer_index < 0 ? NULL : data->layers[layer_index].name;
}
void CustomData_set_layer_active(CustomData *data, int type, int n)
{
for (int i = 0; i < data->totlayer; i++) {

View File

@@ -381,6 +381,11 @@ void BLI_path_normalize_unc_16(wchar_t *path_16);
void BLI_path_normalize_unc(char *path_16, int maxlen);
#endif
/**
* Returns true if the given paths are equal.
*/
bool BLI_paths_equal(const char *p1, const char *p2);
/**
* Appends a suffix to the string, fitting it before the extension
*

View File

@@ -1829,3 +1829,21 @@ void BLI_path_slash_native(char *path)
BLI_str_replace_char(path + BLI_path_unc_prefix_len(path), ALTSEP, SEP);
#endif
}
bool BLI_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));
BLI_path_slash_native(norm_p1);
BLI_path_slash_native(norm_p2);
BLI_path_normalize(NULL, norm_p1);
BLI_path_normalize(NULL, norm_p2);
return BLI_path_cmp(norm_p1, norm_p2) == 0;
}

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,26 @@ 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);
const bool export_mtl = RNA_boolean_get(ptr, "export_materials");
uiLayoutSetActive(col, export_mtl);
uiLayout *row = uiLayoutRow(col, true);
uiItemR(row, ptr, "export_textures", 0, NULL, ICON_NONE);
const bool preview = RNA_boolean_get(ptr, "generate_preview_surface");
uiLayoutSetActive(row, export_mtl && preview);
row = uiLayoutRow(col, true);
uiItemR(row, ptr, "overwrite_textures", 0, NULL, ICON_NONE);
const bool export_tex = RNA_boolean_get(ptr, "export_textures");
uiLayoutSetActive(row, export_mtl && preview && export_tex);
row = uiLayoutRow(col, true);
uiItemR(row, ptr, "relative_texture_paths", 0, NULL, ICON_NONE);
uiLayoutSetActive(row, export_mtl && preview);
box = uiLayoutBox(layout);
uiItemL(box, IFACE_("Experimental"), ICON_NONE);
uiItemR(box, ptr, "use_instancing", 0, NULL, ICON_NONE);
@@ -249,6 +278,32 @@ 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,
"To USD Preview Surface",
"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 file");
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",
"Make texture asset paths relative to the USD file");
}
/* ====== 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 */
@@ -34,6 +38,19 @@ static const pxr::TfToken roughness("roughness", pxr::TfToken::Immortal);
static const pxr::TfToken surface("surface", pxr::TfToken::Immortal);
} // namespace usdtokens
static std::string get_mesh_active_uvlayer_name(const Object *ob)
{
if (!ob || ob->type != OB_MESH || !ob->data) {
return "";
}
const Mesh *me = static_cast<Mesh *>(ob->data);
const char *name = CustomData_get_active_layer_name(&me->ldata, CD_MLOOPUV);
return name ? name : "";
}
namespace blender::io::usd {
USDAbstractWriter::USDAbstractWriter(const USDExporterContext &usd_export_context)
@@ -78,7 +95,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(const HierarchyContext &context,
Material *material)
{
static pxr::SdfPath material_library_path("/_materials");
pxr::UsdStageRefPtr stage = usd_export_context_.stage;
@@ -92,17 +110,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 = get_mesh_active_uvlayer_name(context.object);
create_usd_preview_surface_material(
this->usd_export_context_, material, usd_material, active_uv);
}
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(const HierarchyContext &context, Material *material);
void write_visibility(const HierarchyContext &context,
const pxr::UsdTimeCode timecode,

View File

@@ -0,0 +1,767 @@
/*
* 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 "WM_api.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 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. */
namespace cyclestokens {
static const pxr::TfToken UVMap("UVMap", pxr::TfToken::Immortal);
} // namespace cyclestokens
namespace blender::io::usd {
/* Preview surface input specification. */
struct InputSpec {
pxr::TfToken input_name;
pxr::SdfValueTypeName input_type;
pxr::TfToken source_name;
/* Whether a default value should be set
* if the node socket has not input. Usually
* false for the Normal input. */
bool set_default_value;
};
/* Map Blender socket names to USD Preview Surface InputSpec structs. */
typedef std::map<std::string, InputSpec> InputSpecMap;
/* Static function forward declarations. */
static pxr::UsdShadeShader create_usd_preview_shader(const USDExporterContext &usd_export_context,
pxr::UsdShadeMaterial &material,
const char *name,
int type);
static pxr::UsdShadeShader create_usd_preview_shader(const USDExporterContext &usd_export_context,
pxr::UsdShadeMaterial &material,
bNode *node);
static void create_uvmap_shader(const USDExporterContext &usd_export_context,
bNode *tex_node,
pxr::UsdShadeMaterial &usd_material,
pxr::UsdShadeShader &usd_tex_shader,
const pxr::TfToken &default_uv);
static void export_texture(bNode *node,
const pxr::UsdStageRefPtr stage,
const bool allow_overwrite = false);
static bNode *find_bsdf_node(Material *material);
static void get_absolute_path(Image *ima, char *r_path);
static std::string get_tex_image_asset_path(bNode *node,
const pxr::UsdStageRefPtr stage,
const USDExportParams &export_params);
static InputSpecMap &preview_surface_input_map();
static bNode *traverse_channel(bNodeSocket *input, short target_type);
template<typename T1, typename T2>
void create_input(pxr::UsdShadeShader &shader, const InputSpec &spec, const void *value);
void create_usd_preview_surface_material(const USDExporterContext &usd_export_context,
Material *material,
pxr::UsdShadeMaterial &usd_material,
const std::string &default_uv)
{
if (!material) {
return;
}
/* 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));
/* Default map when creating UV primvar reader shaders. */
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 = find_bsdf_node(material);
if (!node) {
return;
}
pxr::UsdShadeShader preview_surface = create_usd_preview_shader(
usd_export_context, usd_material, node);
const InputSpecMap &input_map = preview_surface_input_map();
/* Set the preview surface inputs. */
LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) {
/* Check if this socket is mapped to a USD preview shader input. */
const InputSpecMap::const_iterator it = input_map.find(sock->name);
if (it == input_map.end()) {
continue;
}
pxr::UsdShadeShader created_shader;
bNode *input_node = traverse_channel(sock, SH_NODE_TEX_IMAGE);
const InputSpec &input_spec = it->second;
if (input_node) {
/* Create connection. */
created_shader = create_usd_preview_shader(usd_export_context, usd_material, input_node);
preview_surface.CreateInput(input_spec.input_name, input_spec.input_type)
.ConnectToSource(created_shader, input_spec.source_name);
}
else if (input_spec.set_default_value) {
/* Set hardcoded value. */
switch (sock->type) {
case SOCK_FLOAT: {
create_input<bNodeSocketValueFloat, float>(
preview_surface, input_spec, sock->default_value);
} break;
case SOCK_VECTOR: {
create_input<bNodeSocketValueVector, pxr::GfVec3f>(
preview_surface, input_spec, sock->default_value);
} break;
case SOCK_RGBA: {
create_input<bNodeSocketValueRGBA, pxr::GfVec3f>(
preview_surface, input_spec, sock->default_value);
} break;
default:
break;
}
}
/* 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)) {
continue;
}
if (usd_export_context.export_params.export_textures) {
export_texture(input_node,
usd_export_context.stage,
usd_export_context.export_params.overwrite_textures);
}
create_uvmap_shader(
usd_export_context, input_node, usd_material, created_shader, default_uv_sampler);
}
}
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);
}
/* Return USD Preview Surface input map singleton. */
static InputSpecMap &preview_surface_input_map()
{
static InputSpecMap input_map = {
{"Base Color",
{usdtokens::diffuse_color, pxr::SdfValueTypeNames->Float3, usdtokens::rgb, true}},
{"Color", {usdtokens::diffuse_color, pxr::SdfValueTypeNames->Float3, usdtokens::rgb, true}},
{"Roughness", {usdtokens::roughness, pxr::SdfValueTypeNames->Float, usdtokens::r, true}},
{"Metallic", {usdtokens::metallic, pxr::SdfValueTypeNames->Float, usdtokens::r, true}},
{"Specular", {usdtokens::specular, pxr::SdfValueTypeNames->Float, usdtokens::r, true}},
{"Alpha", {usdtokens::opacity, pxr::SdfValueTypeNames->Float, usdtokens::r, true}},
{"IOR", {usdtokens::ior, pxr::SdfValueTypeNames->Float, usdtokens::r, true}},
/* Note that for the Normal input set_default_value is false. */
{"Normal", {usdtokens::normal, pxr::SdfValueTypeNames->Float3, usdtokens::rgb, false}},
{"Clearcoat", {usdtokens::clearcoat, pxr::SdfValueTypeNames->Float, usdtokens::r, true}},
{"Clearcoat Roughness",
{usdtokens::clearcoatRoughness, pxr::SdfValueTypeNames->Float, usdtokens::r, true}},
};
return input_map;
}
/* Create an input on the given shader with name and type
* provided by the InputSpec and assign the given value to the
* input. Parameters T1 and T2 indicate the Blender and USD
* value types, respectively. */
template<typename T1, typename T2>
void create_input(pxr::UsdShadeShader &shader, const InputSpec &spec, const void *value)
{
const T1 *cast_value = static_cast<const T1 *>(value);
shader.CreateInput(spec.input_name, spec.input_type).Set(T2(cast_value->value));
}
/* Find the UVMAP node input to the given texture image node and convert it
* to a USD primvar reader shader. If no UVMAP node is found, create a primvar
* reader for the given default uv set. The primvar reader will be attached to
* the 'st' input of the given USD texture shader. */
static void create_uvmap_shader(const USDExporterContext &usd_export_context,
bNode *tex_node,
pxr::UsdShadeMaterial &usd_material,
pxr::UsdShadeShader &usd_tex_shader,
const pxr::TfToken &default_uv)
{
bool found_uv_node = false;
/* Find UV input to the texture node. */
LISTBASE_FOREACH (bNodeSocket *, tex_node_sock, &tex_node->inputs) {
if (!tex_node_sock || !tex_node_sock->link || !STREQ(tex_node_sock->name, "Vector")) {
continue;
}
bNode *uv_node = traverse_channel(tex_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));
usd_tex_shader.CreateInput(usdtokens::st, pxr::SdfValueTypeNames->Float2)
.ConnectToSource(uv_shader, usdtokens::result);
}
else {
uv_shader.CreateInput(usdtokens::varname, pxr::SdfValueTypeNames->Token).Set(default_uv);
usd_tex_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);
usd_tex_shader.CreateInput(usdtokens::st, pxr::SdfValueTypeNames->Float2)
.ConnectToSource(uv_shader, usdtokens::result);
}
}
}
/* 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(Image *ima)
{
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 "";
}
/* 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);
char file_name[FILE_MAX];
/* Use the image name for the file name. */
strcpy(file_name, ima->id.name + 2);
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)
{
char image_abs_path[FILE_MAX];
char file_name[FILE_MAX];
if (strlen(ima->filepath) > 0) {
get_absolute_path(ima, image_abs_path);
BLI_split_file_part(image_abs_path, file_name, FILE_MAX);
}
else {
/* Use the image name for the file name. */
strcpy(file_name, ima->id.name + 2);
}
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);
char export_path[FILE_MAX];
BLI_path_join(export_path, FILE_MAX, export_dir.c_str(), file_name, NULL);
if (!allow_overwrite && BLI_exists(export_path)) {
return;
}
if (BLI_paths_equal(export_path, image_abs_path) && BLI_exists(image_abs_path)) {
/* As a precaution, don't overwrite the original path. */
return;
}
std::cout << "Exporting in-memory texture to " << export_path << std::endl;
if (BKE_imbuf_write_as(imbuf, export_path, &imageFormat, true) == 0) {
WM_reportf(RPT_WARNING, "USD export: couldn't export in-memory texture to %s", export_path);
}
}
/* Get the absolute filepath of the given image. Assumes
* r_path result array is of length FILE_MAX. */
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(nullptr, r_path);
}
static pxr::TfToken get_node_tex_image_color_space(bNode *node)
{
if (!node->id) {
return pxr::TfToken();
}
Image *ima = reinterpret_cast<Image *>(node->id);
if (strcmp(ima->colorspace_settings.name, "Raw") == 0) {
return usdtokens::raw;
}
if (strcmp(ima->colorspace_settings.name, "Non-Color") == 0) {
return usdtokens::raw;
}
if (strcmp(ima->colorspace_settings.name, "sRGB") == 0) {
return usdtokens::sRGB;
}
return pxr::TfToken();
}
/* 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, const short target_type)
{
if (!input->link) {
return nullptr;
}
bNode *linked_node = input->link->fromnode;
if (linked_node->type == target_type) {
/* Return match. */
return linked_node;
}
/* Recursively traverse the linked node's sockets. */
LISTBASE_FOREACH (bNodeSocket *, sock, &linked_node->inputs) {
if (bNode *found_node = traverse_channel(sock, target_type)) {
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 *find_bsdf_node(Material *material)
{
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,
const int type)
{
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)
{
pxr::UsdShadeShader shader = create_usd_preview_shader(
usd_export_context, material, node->name, node->type);
if (node->type != SH_NODE_TEX_IMAGE) {
return shader;
}
/* For texture image nodes we set the image path and color space. */
std::string imagePath = get_tex_image_asset_path(
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;
}
static std::string get_tex_image_asset_path(Image *ima)
{
char filepath[FILE_MAX];
get_absolute_path(ima, filepath);
return std::string(filepath);
}
/* 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.
* The filename is typically the image filepath but might also be automatically
* 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_path(bNode *node,
const pxr::UsdStageRefPtr stage,
const USDExportParams &export_params)
{
Image *ima = reinterpret_cast<Image *>(node->id);
if (!ima) {
return "";
}
std::string path;
if (strlen(ima->filepath) > 0) {
/* Get absolute path. */
path = get_tex_image_asset_path(ima);
}
else if (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);
}
if (path.empty()) {
return path;
}
if (export_params.export_textures) {
/* The texture is exported to a 'textures' directory next to the
* USD root layer. */
char exp_path[FILE_MAX];
char file_path[FILE_MAX];
BLI_split_file_part(path.c_str(), file_path, FILE_MAX);
if (export_params.relative_texture_paths) {
BLI_path_join(exp_path, FILE_MAX, ".", "textures", file_path, NULL);
}
else {
/* Create absolute path in the textures directory. */
pxr::SdfLayerHandle layer = stage->GetRootLayer();
std::string stage_path = layer->GetRealPath();
if (stage_path.empty()) {
return path;
}
char dir_path[FILE_MAX];
BLI_split_dir_part(stage_path.c_str(), dir_path, FILE_MAX);
BLI_path_join(exp_path, FILE_MAX, dir_path, "textures", file_path, NULL);
}
return exp_path;
}
if (export_params.relative_texture_paths) {
/* Get the path relative to the USD. */
pxr::SdfLayerHandle layer = stage->GetRootLayer();
std::string stage_path = layer->GetRealPath();
if (stage_path.empty()) {
return path;
}
char rel_path[FILE_MAX];
strcpy(rel_path, 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 path;
}
return rel_path + 2;
}
return 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 &dest_dir,
const bool allow_overwrite)
{
char src_path[FILE_MAX];
get_absolute_path(ima, src_path);
eUDIM_TILE_FORMAT tile_format;
char *udim_pattern = BKE_image_get_tile_strformat(src_path, &tile_format);
/* Only <UDIM> tile formats are supported by USD right now. */
if (tile_format != UDIM_TILE_FORMAT_UDIM) {
std::cout << "WARNING: unsupported tile format for `" << src_path << "`" << std::endl;
MEM_SAFE_FREE(udim_pattern);
return;
}
/* Copy all tiles. */
LISTBASE_FOREACH (ImageTile *, tile, &ima->tiles) {
char src_tile_path[FILE_MAX];
BKE_image_set_filepath_from_tile_number(
src_tile_path, udim_pattern, tile_format, tile->tile_number);
char dest_filename[FILE_MAXFILE];
BLI_split_file_part(src_tile_path, dest_filename, sizeof(dest_filename));
char dest_tile_path[FILE_MAX];
BLI_path_join(dest_tile_path, FILE_MAX, dest_dir.c_str(), dest_filename, nullptr);
if (!allow_overwrite && BLI_exists(dest_tile_path)) {
continue;
}
if (BLI_paths_equal(src_tile_path, dest_tile_path)) {
/* 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, dest_tile_path) != 0) {
WM_reportf(RPT_WARNING,
"USD export: couldn't copy texture tile from %s to %s",
src_tile_path,
dest_tile_path);
}
}
MEM_SAFE_FREE(udim_pattern);
}
/* Copy the given image to the destination directory. */
static void copy_single_file(Image *ima, const std::string &dest_dir, const bool allow_overwrite)
{
char source_path[FILE_MAX];
get_absolute_path(ima, source_path);
char file_name[FILE_MAX];
BLI_split_file_part(source_path, file_name, FILE_MAX);
char dest_path[FILE_MAX];
BLI_path_join(dest_path, FILE_MAX, dest_dir.c_str(), file_name, NULL);
if (!allow_overwrite && BLI_exists(dest_path)) {
return;
}
if (BLI_paths_equal(source_path, dest_path)) {
/* 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) != 0) {
WM_reportf(
RPT_WARNING, "USD export: couldn't copy texture from %s to %s", source_path, dest_path);
}
}
/* 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() */
static void export_texture(bNode *node,
const pxr::UsdStageRefPtr stage,
const bool allow_overwrite)
{
if (node->type != SH_NODE_TEX_IMAGE && node->type != SH_NODE_TEX_ENVIRONMENT) {
return;
}
Image *ima = reinterpret_cast<Image *>(node->id);
if (!ima) {
return;
}
pxr::SdfLayerHandle layer = stage->GetRootLayer();
std::string stage_path = layer->GetRealPath();
if (stage_path.empty()) {
return;
}
char usd_dir_path[FILE_MAX];
BLI_split_dir_part(stage_path.c_str(), usd_dir_path, FILE_MAX);
char tex_dir_path[FILE_MAX];
BLI_path_join(tex_dir_path, FILE_MAX, usd_dir_path, "textures", SEP_STR, NULL);
BLI_dir_create_recursive(tex_dir_path);
const bool is_dirty = BKE_image_is_dirty(ima);
const bool is_generated = ima->source == IMA_SRC_GENERATED;
const bool is_packed = BKE_image_has_packedfile(ima);
std::string dest_dir(tex_dir_path);
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);
}
}
} // namespace blender::io::usd

View File

@@ -0,0 +1,55 @@
/*
* 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;
/* 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 that 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 = "");
/* Entry point to create USD Shade Material network from Blender viewport display settings. */
void create_usd_viewport_material(const USDExporterContext &usd_export_context,
Material *material,
pxr::UsdShadeMaterial &usd_material);
} // 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(context, material);
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(context, material);
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 {