Compare commits
33 Commits
temp-image
...
temp-usd-p
Author | SHA1 | Date | |
---|---|---|---|
5672223677 | |||
af8e8e2ae5 | |||
ff80cbbeeb | |||
e27cd0e892 | |||
dd9dc5946c | |||
0cef709213 | |||
166084ec47 | |||
03199e7f3a | |||
442b8e3dcd | |||
30e6a13c35 | |||
18bf798dc6 | |||
a5f22596c1 | |||
63c8742780 | |||
3e976ef111 | |||
ecbac70683 | |||
b748ad7488 | |||
c80cf69a5b | |||
5300c8430e | |||
91c39cbb6a | |||
cbbef8aba2 | |||
fd39018f7d | |||
d3d053d793 | |||
08553b4c9f | |||
cbff81ac9b | |||
5bebc3cfb6 | |||
ffdf6a85bf | |||
e0bd3f03f5 | |||
03c0bb75c5 | |||
2156f238b0 | |||
5c9875f668 | |||
02d6550dd0 | |||
75af1395e1 | |||
3c8131fde3 |
@@ -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_clone_layer(const struct CustomData *data, int type);
|
||||||
int CustomData_get_stencil_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
|
* 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.
|
* no effect if there is no layer of type.
|
||||||
|
@@ -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;
|
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)
|
void CustomData_set_layer_active(CustomData *data, int type, int n)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < data->totlayer; i++) {
|
for (int i = 0; i < data->totlayer; i++) {
|
||||||
|
@@ -381,6 +381,11 @@ void BLI_path_normalize_unc_16(wchar_t *path_16);
|
|||||||
void BLI_path_normalize_unc(char *path_16, int maxlen);
|
void BLI_path_normalize_unc(char *path_16, int maxlen);
|
||||||
#endif
|
#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
|
* Appends a suffix to the string, fitting it before the extension
|
||||||
*
|
*
|
||||||
|
@@ -1829,3 +1829,21 @@ void BLI_path_slash_native(char *path)
|
|||||||
BLI_str_replace_char(path + BLI_path_unc_prefix_len(path), ALTSEP, SEP);
|
BLI_str_replace_char(path + BLI_path_unc_prefix_len(path), ALTSEP, SEP);
|
||||||
#endif
|
#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;
|
||||||
|
}
|
||||||
|
@@ -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 use_instancing = RNA_boolean_get(op->ptr, "use_instancing");
|
||||||
const bool evaluation_mode = RNA_enum_get(op->ptr, "evaluation_mode");
|
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 = {
|
struct USDExportParams params = {
|
||||||
export_animation,
|
export_animation,
|
||||||
export_hair,
|
export_hair,
|
||||||
@@ -141,6 +146,10 @@ static int wm_usd_export_exec(bContext *C, wmOperator *op)
|
|||||||
visible_objects_only,
|
visible_objects_only,
|
||||||
use_instancing,
|
use_instancing,
|
||||||
evaluation_mode,
|
evaluation_mode,
|
||||||
|
generate_preview_surface,
|
||||||
|
export_textures,
|
||||||
|
overwrite_textures,
|
||||||
|
relative_texture_paths,
|
||||||
};
|
};
|
||||||
|
|
||||||
bool ok = USD_export(C, filename, ¶ms, as_background_job);
|
bool ok = USD_export(C, filename, ¶ms, as_background_job);
|
||||||
@@ -172,6 +181,26 @@ static void wm_usd_export_draw(bContext *UNUSED(C), wmOperator *op)
|
|||||||
col = uiLayoutColumn(box, true);
|
col = uiLayoutColumn(box, true);
|
||||||
uiItemR(col, ptr, "evaluation_mode", 0, NULL, ICON_NONE);
|
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);
|
box = uiLayoutBox(layout);
|
||||||
uiItemL(box, IFACE_("Experimental"), ICON_NONE);
|
uiItemL(box, IFACE_("Experimental"), ICON_NONE);
|
||||||
uiItemR(box, ptr, "use_instancing", 0, NULL, 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",
|
"Use Settings for",
|
||||||
"Determines visibility of objects, modifier settings, and other areas where there "
|
"Determines visibility of objects, modifier settings, and other areas where there "
|
||||||
"are different settings for viewport and rendering");
|
"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 ====== */
|
/* ====== USD Import ====== */
|
||||||
|
@@ -64,6 +64,7 @@ set(SRC
|
|||||||
intern/usd_writer_camera.cc
|
intern/usd_writer_camera.cc
|
||||||
intern/usd_writer_hair.cc
|
intern/usd_writer_hair.cc
|
||||||
intern/usd_writer_light.cc
|
intern/usd_writer_light.cc
|
||||||
|
intern/usd_writer_material.cc
|
||||||
intern/usd_writer_mesh.cc
|
intern/usd_writer_mesh.cc
|
||||||
intern/usd_writer_metaball.cc
|
intern/usd_writer_metaball.cc
|
||||||
intern/usd_writer_transform.cc
|
intern/usd_writer_transform.cc
|
||||||
@@ -89,6 +90,7 @@ set(SRC
|
|||||||
intern/usd_writer_camera.h
|
intern/usd_writer_camera.h
|
||||||
intern/usd_writer_hair.h
|
intern/usd_writer_hair.h
|
||||||
intern/usd_writer_light.h
|
intern/usd_writer_light.h
|
||||||
|
intern/usd_writer_material.h
|
||||||
intern/usd_writer_mesh.h
|
intern/usd_writer_mesh.h
|
||||||
intern/usd_writer_metaball.h
|
intern/usd_writer_metaball.h
|
||||||
intern/usd_writer_transform.h
|
intern/usd_writer_transform.h
|
||||||
|
@@ -18,11 +18,15 @@
|
|||||||
*/
|
*/
|
||||||
#include "usd_writer_abstract.h"
|
#include "usd_writer_abstract.h"
|
||||||
#include "usd_hierarchy_iterator.h"
|
#include "usd_hierarchy_iterator.h"
|
||||||
|
#include "usd_writer_material.h"
|
||||||
|
|
||||||
#include <pxr/base/tf/stringUtils.h>
|
#include <pxr/base/tf/stringUtils.h>
|
||||||
|
|
||||||
|
#include "BKE_customdata.h"
|
||||||
#include "BLI_assert.h"
|
#include "BLI_assert.h"
|
||||||
|
|
||||||
|
#include "DNA_mesh_types.h"
|
||||||
|
|
||||||
/* TfToken objects are not cheap to construct, so we do it once. */
|
/* TfToken objects are not cheap to construct, so we do it once. */
|
||||||
namespace usdtokens {
|
namespace usdtokens {
|
||||||
/* Materials */
|
/* Materials */
|
||||||
@@ -34,6 +38,19 @@ static const pxr::TfToken roughness("roughness", pxr::TfToken::Immortal);
|
|||||||
static const pxr::TfToken surface("surface", pxr::TfToken::Immortal);
|
static const pxr::TfToken surface("surface", pxr::TfToken::Immortal);
|
||||||
} // namespace usdtokens
|
} // 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 {
|
namespace blender::io::usd {
|
||||||
|
|
||||||
USDAbstractWriter::USDAbstractWriter(const USDExporterContext &usd_export_context)
|
USDAbstractWriter::USDAbstractWriter(const USDExporterContext &usd_export_context)
|
||||||
@@ -78,7 +95,8 @@ const pxr::SdfPath &USDAbstractWriter::usd_path() const
|
|||||||
return usd_export_context_.usd_path;
|
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");
|
static pxr::SdfPath material_library_path("/_materials");
|
||||||
pxr::UsdStageRefPtr stage = usd_export_context_.stage;
|
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);
|
usd_material = pxr::UsdShadeMaterial::Define(stage, usd_path);
|
||||||
|
|
||||||
/* Construct the shader. */
|
if (material->use_nodes && this->usd_export_context_.export_params.generate_preview_surface) {
|
||||||
pxr::SdfPath shader_path = usd_path.AppendChild(usdtokens::preview_shader);
|
std::string active_uv = get_mesh_active_uvlayer_name(context.object);
|
||||||
pxr::UsdShadeShader shader = pxr::UsdShadeShader::Define(stage, shader_path);
|
create_usd_preview_surface_material(
|
||||||
shader.CreateIdAttr(pxr::VtValue(usdtokens::preview_surface));
|
this->usd_export_context_, material, usd_material, active_uv);
|
||||||
shader.CreateInput(usdtokens::diffuse_color, pxr::SdfValueTypeNames->Color3f)
|
}
|
||||||
.Set(pxr::GfVec3f(material->r, material->g, material->b));
|
else {
|
||||||
shader.CreateInput(usdtokens::roughness, pxr::SdfValueTypeNames->Float).Set(material->roughness);
|
create_usd_viewport_material(this->usd_export_context_, material, usd_material);
|
||||||
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_material;
|
return usd_material;
|
||||||
}
|
}
|
||||||
|
@@ -69,7 +69,7 @@ class USDAbstractWriter : public AbstractHierarchyWriter {
|
|||||||
virtual void do_write(HierarchyContext &context) = 0;
|
virtual void do_write(HierarchyContext &context) = 0;
|
||||||
pxr::UsdTimeCode get_export_time_code() const;
|
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,
|
void write_visibility(const HierarchyContext &context,
|
||||||
const pxr::UsdTimeCode timecode,
|
const pxr::UsdTimeCode timecode,
|
||||||
|
767
source/blender/io/usd/intern/usd_writer_material.cc
Normal file
767
source/blender/io/usd/intern/usd_writer_material.cc
Normal 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
|
55
source/blender/io/usd/intern/usd_writer_material.h
Normal file
55
source/blender/io/usd/intern/usd_writer_material.h
Normal 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
|
@@ -319,7 +319,7 @@ void USDGenericMeshWriter::assign_materials(const HierarchyContext &context,
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
pxr::UsdShadeMaterial usd_material = ensure_usd_material(material);
|
pxr::UsdShadeMaterial usd_material = ensure_usd_material(context, material);
|
||||||
material_binding_api.Bind(usd_material);
|
material_binding_api.Bind(usd_material);
|
||||||
|
|
||||||
/* USD seems to support neither per-material nor per-face-group double-sidedness, so we just
|
/* 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;
|
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::TfToken material_name = usd_material.GetPath().GetNameToken();
|
||||||
|
|
||||||
pxr::UsdGeomSubset usd_face_subset = material_binding_api.CreateMaterialBindSubset(
|
pxr::UsdGeomSubset usd_face_subset = material_binding_api.CreateMaterialBindSubset(
|
||||||
|
@@ -41,6 +41,10 @@ struct USDExportParams {
|
|||||||
bool visible_objects_only;
|
bool visible_objects_only;
|
||||||
bool use_instancing;
|
bool use_instancing;
|
||||||
enum eEvaluationMode evaluation_mode;
|
enum eEvaluationMode evaluation_mode;
|
||||||
|
bool generate_preview_surface;
|
||||||
|
bool export_textures;
|
||||||
|
bool overwrite_textures;
|
||||||
|
bool relative_texture_paths;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct USDImportParams {
|
struct USDImportParams {
|
||||||
|
Reference in New Issue
Block a user