USD: dome light IO
This commit adds logic to convert between USD dome lights and Blender world materials. The USD dome light rotation is represented in a mapping node input to the environment texture. If the dome light has a color specified in addition to the texture map, the color will be converted to a vector multiply on the the environment texture output. I the imported USD has multiple dome lights, only the first dome light will be converted to a world material. Co-authored-by: kiki <charles@skeletalstudios.com> Co-authored-by: Michael Kowalski <makowalski@nvidia.com> Pull Request: https://projects.blender.org/blender/blender/pulls/121800
This commit is contained in:
committed by
Michael Kowalski
parent
3ed825f981
commit
e1a6749b3d
@@ -242,6 +242,8 @@ static int wm_usd_export_exec(bContext *C, wmOperator *op)
|
||||
const int global_forward = RNA_enum_get(op->ptr, "export_global_forward_selection");
|
||||
const int global_up = RNA_enum_get(op->ptr, "export_global_up_selection");
|
||||
|
||||
const bool convert_world_material = RNA_boolean_get(op->ptr, "convert_world_material");
|
||||
|
||||
const eUSDXformOpMode xform_op_mode = eUSDXformOpMode(RNA_enum_get(op->ptr, "xform_op_mode"));
|
||||
|
||||
char root_prim_path[FILE_MAX];
|
||||
@@ -275,6 +277,7 @@ static int wm_usd_export_exec(bContext *C, wmOperator *op)
|
||||
convert_orientation,
|
||||
eIOAxis(global_forward),
|
||||
eIOAxis(global_up),
|
||||
convert_world_material,
|
||||
xform_op_mode,
|
||||
export_meshes,
|
||||
export_lights,
|
||||
@@ -320,6 +323,8 @@ static void wm_usd_export_draw(bContext *C, wmOperator *op)
|
||||
uiItemR(row, ptr, "author_blender_name", UI_ITEM_NONE, nullptr, ICON_NONE);
|
||||
uiLayoutSetActive(row, RNA_boolean_get(op->ptr, "export_custom_properties"));
|
||||
|
||||
uiItemR(col, ptr, "convert_world_material", UI_ITEM_NONE, nullptr, ICON_NONE);
|
||||
|
||||
col = uiLayoutColumn(box, true);
|
||||
uiItemR(col, ptr, "triangulate_meshes", UI_ITEM_NONE, nullptr, ICON_NONE);
|
||||
|
||||
@@ -608,6 +613,15 @@ void WM_OT_usd_export(wmOperatorType *ot)
|
||||
"Author USD custom attributes containing the original Blender object and "
|
||||
"object data names");
|
||||
|
||||
RNA_def_boolean(
|
||||
ot->srna,
|
||||
"convert_world_material",
|
||||
true,
|
||||
"Convert World Material",
|
||||
"Convert the world material to a USD dome light. "
|
||||
"Currently works for simple materials, consisting of an environment texture "
|
||||
"connected to a background shader, with an optional vector multiply of the texture color");
|
||||
|
||||
RNA_def_boolean(ot->srna, "export_meshes", true, "Meshes", "Export all meshes");
|
||||
|
||||
RNA_def_boolean(ot->srna, "export_lights", true, "Lights", "Export all lights");
|
||||
@@ -725,6 +739,8 @@ static int wm_usd_import_exec(bContext *C, wmOperator *op)
|
||||
|
||||
const bool validate_meshes = RNA_boolean_get(op->ptr, "validate_meshes");
|
||||
|
||||
const bool create_world_material = RNA_boolean_get(op->ptr, "create_world_material");
|
||||
|
||||
/* TODO(makowalski): Add support for sequences. */
|
||||
const bool is_sequence = false;
|
||||
int offset = 0;
|
||||
@@ -783,6 +799,7 @@ static int wm_usd_import_exec(bContext *C, wmOperator *op)
|
||||
params.tex_name_collision_mode = tex_name_collision_mode;
|
||||
params.import_all_materials = import_all_materials;
|
||||
params.attr_import_mode = attr_import_mode;
|
||||
params.create_world_material = create_world_material;
|
||||
|
||||
STRNCPY(params.import_textures_dir, import_textures_dir);
|
||||
|
||||
@@ -837,7 +854,8 @@ static void wm_usd_import_draw(bContext * /*C*/, wmOperator *op)
|
||||
uiItemR(col, ptr, "set_frame_range", UI_ITEM_NONE, nullptr, ICON_NONE);
|
||||
uiItemR(col, ptr, "relative_path", UI_ITEM_NONE, nullptr, ICON_NONE);
|
||||
uiItemR(col, ptr, "create_collection", UI_ITEM_NONE, nullptr, ICON_NONE);
|
||||
uiItemR(box, ptr, "light_intensity_scale", UI_ITEM_NONE, nullptr, ICON_NONE);
|
||||
uiItemR(col, ptr, "light_intensity_scale", UI_ITEM_NONE, nullptr, ICON_NONE);
|
||||
uiItemR(col, ptr, "create_world_material", UI_ITEM_NONE, nullptr, ICON_NONE);
|
||||
uiItemR(col, ptr, "attr_import_mode", UI_ITEM_NONE, nullptr, ICON_NONE);
|
||||
|
||||
box = uiLayoutBox(layout);
|
||||
@@ -1045,6 +1063,12 @@ void WM_OT_usd_import(wmOperatorType *ot)
|
||||
"Ensure the data is valid "
|
||||
"(when disabled, data may be imported which causes crashes displaying or editing)");
|
||||
|
||||
RNA_def_boolean(ot->srna,
|
||||
"create_world_material",
|
||||
true,
|
||||
"Create World Material",
|
||||
"Convert the first discovered USD dome light to a world background shader");
|
||||
|
||||
RNA_def_boolean(ot->srna,
|
||||
"import_defined_only",
|
||||
true,
|
||||
|
||||
@@ -95,7 +95,9 @@ set(SRC
|
||||
intern/usd_capi_import.cc
|
||||
intern/usd_hierarchy_iterator.cc
|
||||
intern/usd_hook.cc
|
||||
intern/usd_light_convert.cc
|
||||
intern/usd_mesh_utils.cc
|
||||
|
||||
intern/usd_writer_abstract.cc
|
||||
intern/usd_writer_armature.cc
|
||||
intern/usd_writer_camera.cc
|
||||
@@ -139,7 +141,9 @@ set(SRC
|
||||
intern/usd_hash_types.hh
|
||||
intern/usd_hierarchy_iterator.hh
|
||||
intern/usd_hook.hh
|
||||
intern/usd_light_convert.hh
|
||||
intern/usd_mesh_utils.hh
|
||||
|
||||
intern/usd_writer_abstract.hh
|
||||
intern/usd_writer_armature.hh
|
||||
intern/usd_writer_camera.hh
|
||||
|
||||
@@ -10,10 +10,11 @@
|
||||
#include <pxr/usd/ar/resolver.h>
|
||||
#include <pxr/usd/ar/writableAsset.h>
|
||||
|
||||
#include "BKE_appdir.hh"
|
||||
#include "BKE_main.hh"
|
||||
#include "BKE_report.hh"
|
||||
|
||||
#include "BLI_fileops.h"
|
||||
#include "BLI_fileops.hh"
|
||||
#include "BLI_path_util.h"
|
||||
#include "BLI_string.h"
|
||||
|
||||
@@ -330,4 +331,18 @@ bool is_udim_path(const std::string &path)
|
||||
path.find(UDIM_PATTERN2) != std::string::npos;
|
||||
}
|
||||
|
||||
const char *temp_textures_dir()
|
||||
{
|
||||
static bool inited = false;
|
||||
|
||||
static char temp_dir[FILE_MAXDIR] = {'\0'};
|
||||
|
||||
if (!inited) {
|
||||
BLI_path_join(temp_dir, sizeof(temp_dir), BKE_tempdir_session(), "usd_textures_tmp", SEP_STR);
|
||||
inited = true;
|
||||
}
|
||||
|
||||
return temp_dir;
|
||||
}
|
||||
|
||||
} // namespace blender::io::usd
|
||||
|
||||
@@ -61,4 +61,10 @@ std::string import_asset(const char *src,
|
||||
*/
|
||||
bool is_udim_path(const std::string &path);
|
||||
|
||||
/**
|
||||
* Returns path to temporary folder for saving imported textures prior to packing.
|
||||
* CAUTION: this directory is recursively deleted after material import.
|
||||
*/
|
||||
const char *temp_textures_dir();
|
||||
|
||||
} // namespace blender::io::usd
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "usd.hh"
|
||||
#include "usd_hierarchy_iterator.hh"
|
||||
#include "usd_hook.hh"
|
||||
#include "usd_light_convert.hh"
|
||||
#include "usd_private.hh"
|
||||
|
||||
#include <pxr/base/tf/token.h>
|
||||
@@ -342,6 +343,13 @@ pxr::UsdStageRefPtr export_to_stage(const USDExportParams ¶ms,
|
||||
iter.process_usd_skel();
|
||||
}
|
||||
|
||||
/* Creating dome lights should be called after writers have
|
||||
* completed, to avoid a name collision when creating the light
|
||||
* prim. */
|
||||
if (!params.selected_objects_only && params.convert_world_material) {
|
||||
world_material_to_dome_light(params, scene, usd_stage);
|
||||
}
|
||||
|
||||
/* Set the default prim if it doesn't exist */
|
||||
if (!usd_stage->GetDefaultPrim()) {
|
||||
/* Use TraverseAll since it's guaranteed to be depth first and will get the first top level
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "IO_types.hh"
|
||||
#include "usd.hh"
|
||||
#include "usd_hook.hh"
|
||||
#include "usd_light_convert.hh"
|
||||
#include "usd_reader_geom.hh"
|
||||
#include "usd_reader_prim.hh"
|
||||
#include "usd_reader_stage.hh"
|
||||
@@ -297,6 +298,13 @@ static void import_startjob(void *customdata, wmJobWorkerStatus *worker_status)
|
||||
|
||||
archive->collect_readers();
|
||||
|
||||
if (data->params.import_lights && data->params.create_world_material &&
|
||||
!archive->dome_lights().is_empty())
|
||||
{
|
||||
dome_light_to_world_material(
|
||||
data->params, data->settings, data->scene, data->bmain, archive->dome_lights().first());
|
||||
}
|
||||
|
||||
if (data->params.import_materials && data->params.import_all_materials) {
|
||||
archive->import_all_materials(data->bmain);
|
||||
}
|
||||
|
||||
562
source/blender/io/usd/intern/usd_light_convert.cc
Normal file
562
source/blender/io/usd/intern/usd_light_convert.cc
Normal file
@@ -0,0 +1,562 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "usd_light_convert.hh"
|
||||
|
||||
#include "usd.hh"
|
||||
#include "usd_asset_utils.hh"
|
||||
#include "usd_reader_prim.hh"
|
||||
#include "usd_writer_material.hh"
|
||||
|
||||
#include <pxr/base/gf/matrix4f.h>
|
||||
#include <pxr/base/gf/rotation.h>
|
||||
#include <pxr/base/gf/vec3f.h>
|
||||
|
||||
#include <pxr/usd/ar/packageUtils.h>
|
||||
#include <pxr/usd/usdGeom/scope.h>
|
||||
#include <pxr/usd/usdGeom/xformCache.h>
|
||||
#include <pxr/usd/usdGeom/xformCommonAPI.h>
|
||||
#include <pxr/usd/usdLux/domeLight.h>
|
||||
#include <pxr/usd/usdShade/material.h>
|
||||
#include <pxr/usd/usdShade/materialBindingAPI.h>
|
||||
|
||||
#include "BKE_image.h"
|
||||
#include "BKE_light.h"
|
||||
#include "BKE_main.hh"
|
||||
#include "BKE_node.hh"
|
||||
#include "BKE_node_runtime.hh"
|
||||
#include "BKE_node_tree_update.hh"
|
||||
#include "BKE_scene.hh"
|
||||
#include "BLI_fileops.h"
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_math_rotation.h"
|
||||
#include "BLI_math_vector.h"
|
||||
#include "BLI_path_util.h"
|
||||
#include "BLI_string.h"
|
||||
#include "DNA_light_types.h"
|
||||
#include "DNA_node_types.h"
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DNA_world_types.h"
|
||||
#include "ED_node.hh"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "CLG_log.h"
|
||||
static CLG_LogRef LOG = {"io.usd"};
|
||||
|
||||
namespace usdtokens {
|
||||
// Attribute names.
|
||||
static const pxr::TfToken color("color", pxr::TfToken::Immortal);
|
||||
static const pxr::TfToken intensity("intensity", pxr::TfToken::Immortal);
|
||||
static const pxr::TfToken texture_file("texture:file", pxr::TfToken::Immortal);
|
||||
} // namespace usdtokens
|
||||
|
||||
namespace {
|
||||
|
||||
/* If the given attribute has an authored value, return its value in the r_value
|
||||
* out parameter.
|
||||
*
|
||||
* We wish to support older UsdLux APIs in older versions of USD. For example,
|
||||
* in previous versions of the API, shader input attibutes did not have the
|
||||
* "inputs:" prefix. One can provide the older input attibute name in the
|
||||
* 'fallback_attr_name' argument, and that attribute will be queried if 'attr'
|
||||
* doesn't exist or doesn't have an authored value.
|
||||
*/
|
||||
template<typename T>
|
||||
bool get_authored_value(const pxr::UsdAttribute attr,
|
||||
const double motionSampleTime,
|
||||
T *r_value,
|
||||
const pxr::UsdPrim prim = pxr::UsdPrim(),
|
||||
const pxr::TfToken fallback_attr_name = pxr::TfToken())
|
||||
{
|
||||
if (attr && attr.HasAuthoredValue()) {
|
||||
return attr.Get<T>(r_value, motionSampleTime);
|
||||
}
|
||||
|
||||
if (!prim || fallback_attr_name.IsEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
pxr::UsdAttribute fallback_attr = prim.GetAttribute(fallback_attr_name);
|
||||
if (fallback_attr && fallback_attr.HasAuthoredValue()) {
|
||||
return fallback_attr.Get<T>(r_value, motionSampleTime);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Helper struct for retrieving shader information when traversing a world material
|
||||
* node chain, provided as user data for bke::nodeChainIter(). */
|
||||
struct WorldNtreeSearchResults {
|
||||
const blender::io::usd::USDExportParams params;
|
||||
pxr::UsdStageRefPtr stage;
|
||||
|
||||
float world_color[3];
|
||||
float world_intensity;
|
||||
float mapping_rot[3];
|
||||
|
||||
std::string file_path;
|
||||
|
||||
float color_mult[3];
|
||||
|
||||
bool background_found;
|
||||
bool env_tex_found;
|
||||
bool mult_found;
|
||||
bool mapping_found;
|
||||
|
||||
WorldNtreeSearchResults(const blender::io::usd::USDExportParams &in_params,
|
||||
pxr::UsdStageRefPtr in_stage)
|
||||
: params(in_params),
|
||||
stage(in_stage),
|
||||
world_intensity(0.0f),
|
||||
background_found(false),
|
||||
env_tex_found(false),
|
||||
mult_found(false)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
} // End anonymous namespace.
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
/* If the given path already exists on the given stage, return the path with
|
||||
* a numerical suffix appende to the name that ensures the path is unique. If
|
||||
* the path does not exist on the stage, it will be returned unchanged. */
|
||||
static pxr::SdfPath get_unique_path(pxr::UsdStageRefPtr stage, const std::string &path)
|
||||
{
|
||||
std::string unique_path = path;
|
||||
int suffix = 2;
|
||||
while (stage->GetPrimAtPath(pxr::SdfPath(unique_path)).IsValid()) {
|
||||
unique_path = path + std::to_string(suffix++);
|
||||
}
|
||||
return pxr::SdfPath(unique_path);
|
||||
}
|
||||
|
||||
/* Load the image at the given path. Handle packing and copying based in the import options.
|
||||
* Return the opened image on succsss or a nullptr on failure. */
|
||||
static Image *load_image(std::string tex_path, Main *bmain, const USDImportParams ¶ms)
|
||||
{
|
||||
/* Optionally copy the asset if it's inside a USDZ package. */
|
||||
const bool import_textures = params.import_textures_mode != USD_TEX_IMPORT_NONE &&
|
||||
pxr::ArIsPackageRelativePath(tex_path);
|
||||
|
||||
if (import_textures) {
|
||||
/* If we are packing the imported textures, we first write them
|
||||
* to a temporary directory. */
|
||||
const char *textures_dir = params.import_textures_mode == USD_TEX_IMPORT_PACK ?
|
||||
temp_textures_dir() :
|
||||
params.import_textures_dir;
|
||||
|
||||
const eUSDTexNameCollisionMode name_collision_mode = params.import_textures_mode ==
|
||||
USD_TEX_IMPORT_PACK ?
|
||||
USD_TEX_NAME_COLLISION_OVERWRITE :
|
||||
params.tex_name_collision_mode;
|
||||
|
||||
tex_path = import_asset(tex_path.c_str(), textures_dir, name_collision_mode, nullptr);
|
||||
}
|
||||
|
||||
Image *image = BKE_image_load_exists(bmain, tex_path.c_str());
|
||||
if (!image) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (import_textures && params.import_textures_mode == USD_TEX_IMPORT_PACK &&
|
||||
!BKE_image_has_packedfile(image))
|
||||
{
|
||||
BKE_image_packfiles(nullptr, image, ID_BLEND_PATH(bmain, &image->id));
|
||||
if (BLI_is_dir(temp_textures_dir())) {
|
||||
BLI_delete(temp_textures_dir(), true, true);
|
||||
}
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
/* Create a new node of type 'new_node_type' and connect it
|
||||
* as an upstream source to 'dst_node' with the given sockets. */
|
||||
static bNode *append_node(bNode *dst_node,
|
||||
int16_t new_node_type,
|
||||
const char *out_sock,
|
||||
const char *in_sock,
|
||||
bNodeTree *ntree,
|
||||
float offset)
|
||||
{
|
||||
bNode *src_node = bke::nodeAddStaticNode(NULL, ntree, new_node_type);
|
||||
|
||||
if (!src_node) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bke::nodeAddLink(ntree,
|
||||
src_node,
|
||||
bke::nodeFindSocket(src_node, SOCK_OUT, out_sock),
|
||||
dst_node,
|
||||
bke::nodeFindSocket(dst_node, SOCK_IN, in_sock));
|
||||
|
||||
src_node->locx = dst_node->locx - offset;
|
||||
src_node->locy = dst_node->locy;
|
||||
|
||||
return src_node;
|
||||
}
|
||||
|
||||
/* Callback function for iterating over a shader node chain to retrieve data
|
||||
* necessary for converting a world material to a USD dome light. It also
|
||||
* handles copying textures, if required. */
|
||||
static bool node_search(bNode *fromnode, bNode * /* tonode */, void *userdata, const bool reversed)
|
||||
{
|
||||
if (!(userdata && fromnode)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
WorldNtreeSearchResults *res = reinterpret_cast<WorldNtreeSearchResults *>(userdata);
|
||||
|
||||
if (!res->background_found && fromnode->type == SH_NODE_BACKGROUND) {
|
||||
/* Get light color and intensity */
|
||||
bNodeSocketValueRGBA *color_data = bke::nodeFindSocket(fromnode, SOCK_IN, "Color")
|
||||
->default_value_typed<bNodeSocketValueRGBA>();
|
||||
bNodeSocketValueFloat *strength_data = bke::nodeFindSocket(fromnode, SOCK_IN, "Strength")
|
||||
->default_value_typed<bNodeSocketValueFloat>();
|
||||
|
||||
res->background_found = true;
|
||||
res->world_intensity = strength_data->value;
|
||||
res->world_color[0] = color_data->value[0];
|
||||
res->world_color[1] = color_data->value[1];
|
||||
res->world_color[2] = color_data->value[2];
|
||||
}
|
||||
else if (!res->env_tex_found && fromnode->type == SH_NODE_TEX_ENVIRONMENT) {
|
||||
/* Get env tex path. */
|
||||
|
||||
res->file_path = get_tex_image_asset_filepath(fromnode, res->stage, res->params);
|
||||
|
||||
if (!res->file_path.empty()) {
|
||||
res->env_tex_found = true;
|
||||
if (res->params.export_textures) {
|
||||
export_texture(fromnode, res->stage, res->params.overwrite_textures);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!res->env_tex_found && !res->mult_found && fromnode->type == SH_NODE_VECTOR_MATH) {
|
||||
|
||||
if (fromnode->custom1 == NODE_VECTOR_MATH_MULTIPLY) {
|
||||
res->mult_found = true;
|
||||
|
||||
bNodeSocket *vec_sock = bke::nodeFindSocket(fromnode, SOCK_IN, "Vector");
|
||||
if (vec_sock) {
|
||||
vec_sock = vec_sock->next;
|
||||
}
|
||||
|
||||
if (vec_sock) {
|
||||
copy_v3_v3(res->color_mult, ((bNodeSocketValueVector *)vec_sock->default_value)->value);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (res->env_tex_found && fromnode->type == SH_NODE_MAPPING) {
|
||||
res->mapping_found = true;
|
||||
copy_v3_fl(res->mapping_rot, 0.0f);
|
||||
if (bNodeSocket *socket = bke::nodeFindSocket(fromnode, SOCK_IN, "Rotation")) {
|
||||
bNodeSocketValueVector *rot_value = static_cast<bNodeSocketValueVector *>(
|
||||
socket->default_value);
|
||||
copy_v3_v3(res->mapping_rot, rot_value->value);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* If the Blender scene has an environment texture,
|
||||
* export it as a USD dome light. */
|
||||
void world_material_to_dome_light(const USDExportParams ¶ms,
|
||||
const Scene *scene,
|
||||
pxr::UsdStageRefPtr stage)
|
||||
{
|
||||
if (!(stage && scene && scene->world && scene->world->use_nodes && scene->world->nodetree)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Find the world output. */
|
||||
const bNodeTree *ntree = scene->world->nodetree;
|
||||
ntree->ensure_topology_cache();
|
||||
const blender::Span<const bNode *> bsdf_nodes = ntree->nodes_by_type("ShaderNodeOutputWorld");
|
||||
const bNode *output = bsdf_nodes.is_empty() ? nullptr : bsdf_nodes.first();
|
||||
|
||||
if (!output) {
|
||||
/* No output, no valid network to convert. */
|
||||
return;
|
||||
}
|
||||
|
||||
WorldNtreeSearchResults res(params, stage);
|
||||
|
||||
bke::nodeChainIter(scene->world->nodetree, output, node_search, &res, true);
|
||||
|
||||
if (!(res.background_found || res.env_tex_found)) {
|
||||
/* No nodes to convert */
|
||||
return;
|
||||
}
|
||||
|
||||
/* Create USD dome light. */
|
||||
|
||||
pxr::SdfPath env_light_path = get_unique_path(stage,
|
||||
std::string(params.root_prim_path) + "/env_light");
|
||||
|
||||
pxr::UsdLuxDomeLight dome_light = pxr::UsdLuxDomeLight::Define(stage, env_light_path);
|
||||
|
||||
if (res.env_tex_found) {
|
||||
pxr::SdfAssetPath path(res.file_path);
|
||||
dome_light.CreateTextureFileAttr().Set(path);
|
||||
|
||||
if (res.mult_found) {
|
||||
pxr::GfVec3f color_val(res.color_mult[0], res.color_mult[1], res.color_mult[2]);
|
||||
dome_light.CreateColorAttr().Set(color_val);
|
||||
}
|
||||
}
|
||||
else {
|
||||
pxr::GfVec3f color_val(res.world_color[0], res.world_color[1], res.world_color[2]);
|
||||
dome_light.CreateColorAttr().Set(color_val);
|
||||
}
|
||||
|
||||
if (res.background_found) {
|
||||
dome_light.CreateIntensityAttr().Set(res.world_intensity);
|
||||
}
|
||||
|
||||
/* We always set a default rotation on the light, whether or not res.mapping_found
|
||||
* is true, since res.mapping_rot defaults to zeros. */
|
||||
|
||||
/* Convert radians to degrees. */
|
||||
mul_v3_fl(res.mapping_rot, 180.0f / M_PI);
|
||||
|
||||
pxr::GfMatrix4d xf =
|
||||
pxr::GfMatrix4d().SetRotate(pxr::GfRotation(pxr::GfVec3d(1.0, 0.0, 0.0), 90.0)) *
|
||||
pxr::GfMatrix4d().SetRotate(pxr::GfRotation(pxr::GfVec3d(0.0, 0.0, 1.0), 90.0)) *
|
||||
pxr::GfMatrix4d().SetRotate(
|
||||
pxr::GfRotation(pxr::GfVec3d(0.0, 0.0, 1.0), -res.mapping_rot[2])) *
|
||||
pxr::GfMatrix4d().SetRotate(
|
||||
pxr::GfRotation(pxr::GfVec3d(0.0, 1.0, 0.0), -res.mapping_rot[1])) *
|
||||
pxr::GfMatrix4d().SetRotate(
|
||||
pxr::GfRotation(pxr::GfVec3d(1.0, 0.0, 0.0), -res.mapping_rot[0]));
|
||||
|
||||
pxr::GfVec3d angles = xf.DecomposeRotation(
|
||||
pxr::GfVec3d::ZAxis(), pxr::GfVec3d::YAxis(), pxr::GfVec3d::XAxis());
|
||||
|
||||
pxr::GfVec3f rot_vec(angles[2], angles[1], angles[0]);
|
||||
|
||||
pxr::UsdGeomXformCommonAPI xform_api(dome_light);
|
||||
xform_api.SetRotate(rot_vec, pxr::UsdGeomXformCommonAPI::RotationOrderXYZ);
|
||||
}
|
||||
|
||||
/* Import the dome light as a world material. */
|
||||
|
||||
void dome_light_to_world_material(const USDImportParams ¶ms,
|
||||
const ImportSettings &settings,
|
||||
Scene *scene,
|
||||
Main *bmain,
|
||||
const pxr::UsdLuxDomeLight &dome_light,
|
||||
const double time)
|
||||
{
|
||||
if (!(scene && scene->world && dome_light)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!scene->world->use_nodes) {
|
||||
scene->world->use_nodes = true;
|
||||
}
|
||||
|
||||
if (!scene->world->nodetree) {
|
||||
scene->world->nodetree = bke::ntreeAddTree(NULL, "Shader Nodetree", "ShaderNodeTree");
|
||||
if (!scene->world->nodetree) {
|
||||
CLOG_WARN(&LOG, "Couldn't create world ntree");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bNodeTree *ntree = scene->world->nodetree;
|
||||
bNode *output = nullptr;
|
||||
bNode *bgshader = nullptr;
|
||||
|
||||
/* We never delete existing nodes, but we might disconnect them
|
||||
* and move them out of the way. */
|
||||
|
||||
/* Look for the output and background shader nodes, which we will reuse. */
|
||||
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
|
||||
if (node->type == SH_NODE_OUTPUT_WORLD) {
|
||||
output = node;
|
||||
}
|
||||
else if (node->type == SH_NODE_BACKGROUND) {
|
||||
bgshader = node;
|
||||
}
|
||||
else {
|
||||
/* Move existing node out of the way. */
|
||||
node->locy += 300;
|
||||
}
|
||||
}
|
||||
|
||||
/* Create the output and background shader nodes, if they don't exist. */
|
||||
if (!output) {
|
||||
output = bke::nodeAddStaticNode(NULL, ntree, SH_NODE_OUTPUT_WORLD);
|
||||
|
||||
if (!output) {
|
||||
CLOG_WARN(&LOG, "Couldn't create world output node");
|
||||
return;
|
||||
}
|
||||
|
||||
output->locx = 300.0f;
|
||||
output->locy = 300.0f;
|
||||
}
|
||||
|
||||
if (!bgshader) {
|
||||
bgshader = append_node(output, SH_NODE_BACKGROUND, "Background", "Surface", ntree, 200);
|
||||
|
||||
if (!bgshader) {
|
||||
CLOG_WARN(&LOG, "Couldn't create world shader node");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Set the default background color. */
|
||||
bNodeSocket *color_sock = bke::nodeFindSocket(bgshader, SOCK_IN, "Color");
|
||||
copy_v3_v3(((bNodeSocketValueRGBA *)color_sock->default_value)->value, &scene->world->horr);
|
||||
}
|
||||
|
||||
/* Make sure the first input to the shader node is disconnected. */
|
||||
bNodeSocket *shader_input = bke::nodeFindSocket(bgshader, SOCK_IN, "Color");
|
||||
|
||||
if (shader_input && shader_input->link) {
|
||||
bke::nodeRemLink(ntree, shader_input->link);
|
||||
}
|
||||
|
||||
/* Set the background shader intensity. */
|
||||
float intensity = 1.0f;
|
||||
get_authored_value(
|
||||
dome_light.GetIntensityAttr(), time, &intensity, dome_light.GetPrim(), usdtokens::intensity);
|
||||
|
||||
intensity *= params.light_intensity_scale;
|
||||
|
||||
bNodeSocket *strength_sock = bke::nodeFindSocket(bgshader, SOCK_IN, "Strength");
|
||||
((bNodeSocketValueFloat *)strength_sock->default_value)->value = intensity;
|
||||
|
||||
/* Get the dome light texture file and color. */
|
||||
pxr::SdfAssetPath tex_path;
|
||||
bool has_tex = get_authored_value(dome_light.GetTextureFileAttr(),
|
||||
time,
|
||||
&tex_path,
|
||||
dome_light.GetPrim(),
|
||||
usdtokens::texture_file);
|
||||
|
||||
pxr::GfVec3f color;
|
||||
bool has_color = get_authored_value(
|
||||
dome_light.GetColorAttr(), time, &color, dome_light.GetPrim(), usdtokens::color);
|
||||
|
||||
if (!has_tex) {
|
||||
/* No texture file is authored on the dome light. Set the color, if it was authored,
|
||||
* and return early. */
|
||||
if (has_color) {
|
||||
bNodeSocket *color_sock = bke::nodeFindSocket(bgshader, SOCK_IN, "Color");
|
||||
copy_v3_v3(((bNodeSocketValueRGBA *)color_sock->default_value)->value, color.data());
|
||||
}
|
||||
|
||||
bke::nodeSetActive(ntree, output);
|
||||
BKE_ntree_update_main_tree(bmain, ntree, nullptr);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* If the light has authored color, create a color multiply node for the environment
|
||||
* texture output. */
|
||||
bNode *mult = nullptr;
|
||||
|
||||
if (has_color) {
|
||||
mult = append_node(bgshader, SH_NODE_VECTOR_MATH, "Vector", "Color", ntree, 200);
|
||||
|
||||
if (!mult) {
|
||||
CLOG_WARN(&LOG, "Couldn't create vector multiply node");
|
||||
return;
|
||||
}
|
||||
|
||||
mult->custom1 = NODE_VECTOR_MATH_MULTIPLY;
|
||||
|
||||
/* Set the color in the vector math node's second socket. */
|
||||
bNodeSocket *vec_sock = bke::nodeFindSocket(mult, SOCK_IN, "Vector");
|
||||
if (vec_sock) {
|
||||
vec_sock = vec_sock->next;
|
||||
}
|
||||
|
||||
if (vec_sock) {
|
||||
copy_v3_v3(((bNodeSocketValueVector *)vec_sock->default_value)->value, color.data());
|
||||
}
|
||||
else {
|
||||
CLOG_WARN(&LOG, "Couldn't find vector multiply second vector socket");
|
||||
}
|
||||
}
|
||||
|
||||
bNode *tex = nullptr;
|
||||
|
||||
/* Append an environment texture node to the mult node, if it was created, or directly to
|
||||
* the background shader. */
|
||||
if (mult) {
|
||||
tex = append_node(mult, SH_NODE_TEX_ENVIRONMENT, "Color", "Vector", ntree, 400);
|
||||
}
|
||||
else {
|
||||
tex = append_node(bgshader, SH_NODE_TEX_ENVIRONMENT, "Color", "Color", ntree, 400);
|
||||
}
|
||||
|
||||
if (!tex) {
|
||||
CLOG_WARN(&LOG, "Couldn't create world environment texture node");
|
||||
return;
|
||||
}
|
||||
|
||||
bNode *mapping = append_node(tex, SH_NODE_MAPPING, "Vector", "Vector", ntree, 200);
|
||||
|
||||
if (!mapping) {
|
||||
CLOG_WARN(&LOG, "Couldn't create mapping node");
|
||||
return;
|
||||
}
|
||||
|
||||
bNode *tex_coord = append_node(mapping, SH_NODE_TEX_COORD, "Generated", "Vector", ntree, 200);
|
||||
|
||||
if (!tex_coord) {
|
||||
CLOG_WARN(&LOG, "Couldn't create texture coordinate node");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Load the texture image. */
|
||||
std::string resolved_path = tex_path.GetResolvedPath();
|
||||
|
||||
if (resolved_path.empty()) {
|
||||
CLOG_WARN(&LOG, "Couldn't get resolved path for asset %s", tex_path.GetAssetPath().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
Image *image = load_image(resolved_path, bmain, params);
|
||||
if (!image) {
|
||||
CLOG_WARN(&LOG, "Couldn't load image file %s", resolved_path.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
tex->id = &image->id;
|
||||
|
||||
/* Set the transform. */
|
||||
pxr::UsdGeomXformCache xf_cache(time);
|
||||
pxr::GfMatrix4d xf = xf_cache.GetLocalToWorldTransform(dome_light.GetPrim());
|
||||
|
||||
xf = pxr::GfMatrix4d().SetRotate(pxr::GfRotation(pxr::GfVec3d(0.0, 0.0, 1.0), -90.0)) *
|
||||
pxr::GfMatrix4d().SetRotate(pxr::GfRotation(pxr::GfVec3d(1.0, 0.0, 0.0), -90.0)) * xf;
|
||||
|
||||
pxr::GfVec3d angles = xf.DecomposeRotation(
|
||||
pxr::GfVec3d::XAxis(), pxr::GfVec3d::YAxis(), pxr::GfVec3d::ZAxis());
|
||||
pxr::GfVec3f rot_vec(-angles[0], -angles[1], -angles[2]);
|
||||
|
||||
/* Convert degrees to radians. */
|
||||
rot_vec *= M_PI / 180.0f;
|
||||
|
||||
if (bNodeSocket *socket = bke::nodeFindSocket(mapping, SOCK_IN, "Rotation")) {
|
||||
bNodeSocketValueVector *rot_value = static_cast<bNodeSocketValueVector *>(
|
||||
socket->default_value);
|
||||
copy_v3_v3(rot_value->value, rot_vec.data());
|
||||
}
|
||||
|
||||
bke::nodeSetActive(ntree, output);
|
||||
DEG_id_tag_update(&ntree->id, ID_RECALC_NTREE_OUTPUT);
|
||||
BKE_ntree_update_main_tree(bmain, ntree, nullptr);
|
||||
}
|
||||
|
||||
} // namespace blender::io::usd
|
||||
31
source/blender/io/usd/intern/usd_light_convert.hh
Normal file
31
source/blender/io/usd/intern/usd_light_convert.hh
Normal file
@@ -0,0 +1,31 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
#include <pxr/usd/usd/stage.h>
|
||||
#include <pxr/usd/usdLux/domeLight.h>
|
||||
|
||||
struct Light;
|
||||
struct Main;
|
||||
struct Scene;
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
struct USDExportParams;
|
||||
struct USDImportParams;
|
||||
|
||||
struct ImportSettings;
|
||||
|
||||
void world_material_to_dome_light(const USDExportParams ¶ms,
|
||||
const Scene *scene,
|
||||
pxr::UsdStageRefPtr stage);
|
||||
|
||||
void dome_light_to_world_material(const USDImportParams ¶ms,
|
||||
const ImportSettings &settings,
|
||||
Scene *scene,
|
||||
Main *bmain,
|
||||
const pxr::UsdLuxDomeLight &dome_light,
|
||||
const double time = 0.0);
|
||||
|
||||
} // namespace blender::io::usd
|
||||
@@ -89,23 +89,6 @@ static const pxr::TfToken UsdUVTexture("UsdUVTexture", pxr::TfToken::Immortal);
|
||||
static const pxr::TfToken UsdTransform2d("UsdTransform2d", pxr::TfToken::Immortal);
|
||||
} // namespace usdtokens
|
||||
|
||||
/* Temporary folder for saving imported textures prior to packing.
|
||||
* CAUTION: this directory is recursively deleted after material
|
||||
* import. */
|
||||
static const char *temp_textures_dir()
|
||||
{
|
||||
static bool inited = false;
|
||||
|
||||
static char temp_dir[FILE_MAXDIR] = {'\0'};
|
||||
|
||||
if (!inited) {
|
||||
BLI_path_join(temp_dir, sizeof(temp_dir), BKE_tempdir_session(), "usd_textures_tmp", SEP_STR);
|
||||
inited = true;
|
||||
}
|
||||
|
||||
return temp_dir;
|
||||
}
|
||||
|
||||
using blender::io::usd::ShaderToNodeMap;
|
||||
|
||||
/**
|
||||
|
||||
@@ -181,6 +181,10 @@ USDPrimReader *USDStageReader::create_reader_if_allowed(const pxr::UsdPrim &prim
|
||||
if (params_.import_meshes && prim.IsA<pxr::UsdGeomMesh>()) {
|
||||
return new USDMeshReader(prim, params_, settings_);
|
||||
}
|
||||
if (params_.import_lights && prim.IsA<pxr::UsdLuxDomeLight>()) {
|
||||
/* Dome lights are handled elsewhere. */
|
||||
return nullptr;
|
||||
}
|
||||
#if PXR_VERSION >= 2111
|
||||
if (params_.import_lights &&
|
||||
(prim.IsA<pxr::UsdLuxBoundableLightBase>() || prim.IsA<pxr::UsdLuxNonboundableLightBase>()))
|
||||
@@ -226,6 +230,10 @@ USDPrimReader *USDStageReader::create_reader(const pxr::UsdPrim &prim)
|
||||
if (prim.IsA<pxr::UsdGeomMesh>()) {
|
||||
return new USDMeshReader(prim, params_, settings_);
|
||||
}
|
||||
if (prim.IsA<pxr::UsdLuxDomeLight>()) {
|
||||
/* We don't handle dome lights. */
|
||||
return nullptr;
|
||||
}
|
||||
#if PXR_VERSION >= 2111
|
||||
if (prim.IsA<pxr::UsdLuxBoundableLightBase>() || prim.IsA<pxr::UsdLuxNonboundableLightBase>()) {
|
||||
#else
|
||||
@@ -364,6 +372,10 @@ USDPrimReader *USDStageReader::collect_readers(const pxr::UsdPrim &prim,
|
||||
}
|
||||
}
|
||||
|
||||
if (prim.IsA<pxr::UsdLuxDomeLight>()) {
|
||||
dome_lights_.append(pxr::UsdLuxDomeLight(prim));
|
||||
}
|
||||
|
||||
pxr::Usd_PrimFlagsConjunction filter_flags = pxr::UsdPrimIsActive && pxr::UsdPrimIsLoaded &&
|
||||
!pxr::UsdPrimIsAbstract;
|
||||
|
||||
@@ -446,6 +458,7 @@ void USDStageReader::collect_readers()
|
||||
}
|
||||
|
||||
clear_readers();
|
||||
dome_lights_.clear();
|
||||
|
||||
/* Identify paths to point instancer prototypes, as these will be converted
|
||||
* in a separate pass over the stage. */
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "usd_reader_prim.hh"
|
||||
|
||||
#include <pxr/usd/usdGeom/imageable.h>
|
||||
#include <pxr/usd/usdLux/domeLight.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
@@ -41,6 +42,10 @@ class USDStageReader {
|
||||
|
||||
blender::Vector<USDPrimReader *> readers_;
|
||||
|
||||
/* USD dome lights are converted to a world material,
|
||||
* rather than light objects, so are handled differently */
|
||||
blender::Vector<pxr::UsdLuxDomeLight> dome_lights_;
|
||||
|
||||
/* USD material prim paths encountered during stage
|
||||
* traversal, for importing unused materials. */
|
||||
blender::Vector<std::string> material_paths_;
|
||||
@@ -112,6 +117,11 @@ class USDStageReader {
|
||||
return readers_;
|
||||
};
|
||||
|
||||
const blender::Vector<pxr::UsdLuxDomeLight> &dome_lights() const
|
||||
{
|
||||
return dome_lights_;
|
||||
};
|
||||
|
||||
void sort_readers();
|
||||
|
||||
/**
|
||||
|
||||
@@ -858,16 +858,19 @@ static std::string get_tex_image_asset_filepath(Image *ima)
|
||||
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_filepath(const USDExporterContext &usd_export_context,
|
||||
bNode *node)
|
||||
{
|
||||
return get_tex_image_asset_filepath(
|
||||
node, usd_export_context.stage, usd_export_context.export_params);
|
||||
}
|
||||
|
||||
std::string get_tex_image_asset_filepath(bNode *node,
|
||||
const pxr::UsdStageRefPtr stage,
|
||||
const USDExportParams &export_params)
|
||||
{
|
||||
std::string stage_path = stage->GetRootLayer()->GetRealPath();
|
||||
|
||||
Image *ima = reinterpret_cast<Image *>(node->id);
|
||||
if (!ima) {
|
||||
return "";
|
||||
@@ -879,7 +882,7 @@ static std::string get_tex_image_asset_filepath(const USDExporterContext &usd_ex
|
||||
/* Get absolute path. */
|
||||
path = get_tex_image_asset_filepath(ima);
|
||||
}
|
||||
else if (usd_export_context.export_params.export_textures) {
|
||||
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. */
|
||||
@@ -890,7 +893,7 @@ static std::string get_tex_image_asset_filepath(const USDExporterContext &usd_ex
|
||||
return path;
|
||||
}
|
||||
|
||||
if (usd_export_context.export_params.export_textures) {
|
||||
if (export_params.export_textures) {
|
||||
/* The texture is exported to a 'textures' directory next to the
|
||||
* USD root layer. */
|
||||
|
||||
@@ -898,35 +901,25 @@ static std::string get_tex_image_asset_filepath(const USDExporterContext &usd_ex
|
||||
char file_path[FILE_MAX];
|
||||
BLI_path_split_file_part(path.c_str(), file_path, FILE_MAX);
|
||||
|
||||
if (usd_export_context.export_params.relative_paths) {
|
||||
if (export_params.relative_paths) {
|
||||
BLI_path_join(exp_path, FILE_MAX, ".", "textures", file_path);
|
||||
}
|
||||
else {
|
||||
/* Create absolute path in the textures directory. */
|
||||
std::string export_path = usd_export_context.export_file_path;
|
||||
if (export_path.empty()) {
|
||||
return path;
|
||||
}
|
||||
|
||||
char dir_path[FILE_MAX];
|
||||
BLI_path_split_dir_part(export_path.c_str(), dir_path, FILE_MAX);
|
||||
BLI_path_split_dir_part(stage_path.c_str(), dir_path, FILE_MAX);
|
||||
BLI_path_join(exp_path, FILE_MAX, dir_path, "textures", file_path);
|
||||
}
|
||||
BLI_string_replace_char(exp_path, '\\', '/');
|
||||
return exp_path;
|
||||
}
|
||||
|
||||
if (usd_export_context.export_params.relative_paths) {
|
||||
if (export_params.relative_paths) {
|
||||
/* Get the path relative to the USD. */
|
||||
std::string export_path = usd_export_context.export_file_path;
|
||||
if (export_path.empty()) {
|
||||
return path;
|
||||
}
|
||||
|
||||
char rel_path[FILE_MAX];
|
||||
STRNCPY(rel_path, path.c_str());
|
||||
|
||||
BLI_path_rel(rel_path, export_path.c_str());
|
||||
BLI_path_rel(rel_path, stage_path.c_str());
|
||||
if (!BLI_path_is_rel(rel_path)) {
|
||||
return path;
|
||||
}
|
||||
@@ -1028,9 +1021,18 @@ static void copy_single_file(Image *ima,
|
||||
}
|
||||
}
|
||||
|
||||
/* Export the given texture node's image to a 'textures' directory in the export path.
|
||||
* Based on ImagesExporter::export_UV_Image() */
|
||||
static void export_texture(const USDExporterContext &usd_export_context, bNode *node)
|
||||
{
|
||||
export_texture(node,
|
||||
usd_export_context.stage,
|
||||
usd_export_context.export_params.overwrite_textures,
|
||||
usd_export_context.export_params.worker_status->reports);
|
||||
}
|
||||
|
||||
void export_texture(bNode *node,
|
||||
const pxr::UsdStageRefPtr stage,
|
||||
const bool allow_overwrite,
|
||||
ReportList *reports)
|
||||
{
|
||||
if (!ELEM(node->type, SH_NODE_TEX_IMAGE, SH_NODE_TEX_ENVIRONMENT)) {
|
||||
return;
|
||||
@@ -1041,7 +1043,7 @@ static void export_texture(const USDExporterContext &usd_export_context, bNode *
|
||||
return;
|
||||
}
|
||||
|
||||
std::string export_path = usd_export_context.export_file_path;
|
||||
std::string export_path = stage->GetRootLayer()->GetRealPath();
|
||||
if (export_path.empty()) {
|
||||
return;
|
||||
}
|
||||
@@ -1057,21 +1059,17 @@ static void export_texture(const USDExporterContext &usd_export_context, bNode *
|
||||
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);
|
||||
const bool allow_overwrite = usd_export_context.export_params.overwrite_textures;
|
||||
|
||||
std::string dest_dir(tex_dir_path);
|
||||
|
||||
if (is_generated || is_dirty || is_packed) {
|
||||
export_in_memory_texture(
|
||||
ima, dest_dir, allow_overwrite, usd_export_context.export_params.worker_status->reports);
|
||||
export_in_memory_texture(ima, dest_dir, allow_overwrite, reports);
|
||||
}
|
||||
else if (ima->source == IMA_SRC_TILED) {
|
||||
copy_tiled_textures(
|
||||
ima, dest_dir, allow_overwrite, usd_export_context.export_params.worker_status->reports);
|
||||
copy_tiled_textures(ima, dest_dir, allow_overwrite, reports);
|
||||
}
|
||||
else {
|
||||
copy_single_file(
|
||||
ima, dest_dir, allow_overwrite, usd_export_context.export_params.worker_status->reports);
|
||||
copy_single_file(ima, dest_dir, allow_overwrite, reports);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,12 +7,14 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
struct bNode;
|
||||
struct Material;
|
||||
struct ReportList;
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
struct USDExporterContext;
|
||||
struct USDExportParams;
|
||||
|
||||
/* Create USDMaterial from Blender material.
|
||||
*
|
||||
@@ -29,4 +31,22 @@ pxr::UsdShadeMaterial create_usd_material(const USDExporterContext &usd_export_c
|
||||
* or an empty TfToken if the input name is not found in the map. */
|
||||
const pxr::TfToken token_for_input(const char *input_name);
|
||||
|
||||
/* Export the given texture node's image to a 'textures' directory in the given stage's
|
||||
* export path. */
|
||||
void export_texture(bNode *node,
|
||||
const pxr::UsdStageRefPtr stage,
|
||||
const bool allow_overwrite = false,
|
||||
ReportList *reports = nullptr);
|
||||
|
||||
/* 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. */
|
||||
std::string get_tex_image_asset_filepath(bNode *node,
|
||||
const pxr::UsdStageRefPtr stage,
|
||||
const USDExportParams &export_params);
|
||||
|
||||
} // namespace blender::io::usd
|
||||
|
||||
@@ -112,6 +112,7 @@ struct USDExportParams {
|
||||
bool convert_orientation = false;
|
||||
enum eIOAxis forward_axis = eIOAxis::IO_AXIS_NEGATIVE_Z;
|
||||
enum eIOAxis up_axis = eIOAxis::IO_AXIS_Y;
|
||||
bool convert_world_material = true;
|
||||
eUSDXformOpMode xform_op_mode = eUSDXformOpMode::USD_XFORM_OP_TRS;
|
||||
bool export_meshes = true;
|
||||
bool export_lights = true;
|
||||
@@ -164,6 +165,7 @@ struct USDImportParams {
|
||||
eUSDTexNameCollisionMode tex_name_collision_mode;
|
||||
bool import_all_materials;
|
||||
eUSDAttrImportMode attr_import_mode;
|
||||
bool create_world_material;
|
||||
|
||||
/**
|
||||
* Communication structure between the wmJob management code and the worker code. Currently used
|
||||
|
||||
@@ -92,6 +92,7 @@ class USDExportTest(AbstractUSDTest):
|
||||
filepath=str(export_path),
|
||||
export_materials=True,
|
||||
evaluation_mode="RENDER",
|
||||
convert_world_material=False,
|
||||
)
|
||||
self.assertEqual({'FINISHED'}, res, f"Unable to export to {export_path}")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user