USD import: Support importing USDZ.
This addressed feature request T99811. Added the following features to fully support importing USDZ archives: - Added .usdz to the list of supported extensions. - Added new USD import options to copy textures from USDZ archives. The textures may be imported as packed data (the default) or to a directory on disk. - Extended the USD material import logic to handle package-relative texture assets paths by invoking the USD asset resolver to copy the textures from the USDZ archive to a directory on disk. When importing in Packed mode, the textures are first saved to Blender's temporary session directory prior to packing. The new USD import options are - Import Textures: Behavior when importing textures from a USDZ archive - Textures Directory: Path to the directory where imported textures will be copied - File Name Collision: Behavior when the name of an imported texture file conflicts with an existing file Import Textures menu options: - None: Don't import textures - Packed: Import textures as packed data (the default) - Copy: Copy files to Textures Directory File Name Collision menu options: - Use Existing: If a file with the same name already exists, use that instead of copying (the default) - Overwrite: Overwrite existing files Reviewed by: Bastien Differential Revision: https://developer.blender.org/D17074
This commit is contained in:
parent
9a4c54e8b0
commit
cdef135f6f
|
@ -468,7 +468,7 @@ class TOPBAR_MT_file_import(Menu):
|
|||
self.layout.operator("wm.alembic_import", text="Alembic (.abc)")
|
||||
if bpy.app.build_options.usd:
|
||||
self.layout.operator(
|
||||
"wm.usd_import", text="Universal Scene Description (.usd, .usdc, .usda)")
|
||||
"wm.usd_import", text="Universal Scene Description (.usd*)")
|
||||
|
||||
if bpy.app.build_options.io_gpencil:
|
||||
self.layout.operator("wm.gpencil_import_svg", text="SVG as Grease Pencil")
|
||||
|
|
|
@ -366,7 +366,7 @@ void BKE_cachefile_eval(Main *bmain, Depsgraph *depsgraph, CacheFile *cache_file
|
|||
}
|
||||
#endif
|
||||
#ifdef WITH_USD
|
||||
if (BLI_path_extension_check_glob(filepath, "*.usd;*.usda;*.usdc")) {
|
||||
if (BLI_path_extension_check_glob(filepath, "*.usd;*.usda;*.usdc;*.usdz")) {
|
||||
cache_file->type = CACHEFILE_TYPE_USD;
|
||||
cache_file->handle = USD_create_handle(bmain, filepath, &cache_file->object_paths);
|
||||
BLI_strncpy(cache_file->handle_filepath, filepath, FILE_MAX);
|
||||
|
|
|
@ -72,6 +72,23 @@ const EnumPropertyItem rna_enum_usd_mtl_name_collision_mode_items[] = {
|
|||
{0, NULL, 0, NULL, NULL},
|
||||
};
|
||||
|
||||
const EnumPropertyItem rna_enum_usd_tex_import_mode_items[] = {
|
||||
{USD_TEX_IMPORT_NONE, "IMPORT_NONE", 0, "None", "Don't import textures"},
|
||||
{USD_TEX_IMPORT_PACK, "IMPORT_PACK", 0, "Packed", "Import textures as packed data"},
|
||||
{USD_TEX_IMPORT_COPY, "IMPORT_COPY", 0, "Copy", "Copy files to Textures Directory"},
|
||||
{0, NULL, 0, NULL, NULL},
|
||||
};
|
||||
|
||||
const EnumPropertyItem rna_enum_usd_tex_name_collision_mode_items[] = {
|
||||
{USD_TEX_NAME_COLLISION_USE_EXISTING,
|
||||
"USE_EXISTING",
|
||||
0,
|
||||
"Use Existing",
|
||||
"If a file with the same name already exists, use that instead of copying"},
|
||||
{USD_TEX_NAME_COLLISION_OVERWRITE, "OVERWRITE", 0, "Overwrite", "Overwrite existing files"},
|
||||
{0, NULL, 0, NULL, NULL},
|
||||
};
|
||||
|
||||
/* Stored in the wmOperator's customdata field to indicate it should run as a background job.
|
||||
* This is set when the operator is invoked, and not set when it is only executed. */
|
||||
enum { AS_BACKGROUND_JOB = 1 };
|
||||
|
@ -405,6 +422,14 @@ static int wm_usd_import_exec(bContext *C, wmOperator *op)
|
|||
const bool validate_meshes = false;
|
||||
const bool use_instancing = false;
|
||||
|
||||
const eUSDTexImportMode import_textures_mode = RNA_enum_get(op->ptr, "import_textures_mode");
|
||||
|
||||
char import_textures_dir[FILE_MAXDIR];
|
||||
RNA_string_get(op->ptr, "import_textures_dir", import_textures_dir);
|
||||
|
||||
const eUSDTexNameCollisionMode tex_name_collision_mode = RNA_enum_get(op->ptr,
|
||||
"tex_name_collision_mode");
|
||||
|
||||
struct USDImportParams params = {.scale = scale,
|
||||
.is_sequence = is_sequence,
|
||||
.set_frame_range = set_frame_range,
|
||||
|
@ -430,9 +455,12 @@ static int wm_usd_import_exec(bContext *C, wmOperator *op)
|
|||
.set_material_blend = set_material_blend,
|
||||
.light_intensity_scale = light_intensity_scale,
|
||||
.mtl_name_collision_mode = mtl_name_collision_mode,
|
||||
.import_textures_mode = import_textures_mode,
|
||||
.tex_name_collision_mode = tex_name_collision_mode,
|
||||
.import_all_materials = import_all_materials};
|
||||
|
||||
STRNCPY(params.prim_path_mask, prim_path_mask);
|
||||
STRNCPY(params.import_textures_dir, import_textures_dir);
|
||||
|
||||
const bool ok = USD_import(C, filename, ¶ms, as_background_job);
|
||||
|
||||
|
@ -490,6 +518,18 @@ static void wm_usd_import_draw(bContext *UNUSED(C), wmOperator *op)
|
|||
uiItemR(row, ptr, "set_material_blend", 0, NULL, ICON_NONE);
|
||||
uiLayoutSetEnabled(row, RNA_boolean_get(ptr, "import_usd_preview"));
|
||||
uiItemR(col, ptr, "mtl_name_collision_mode", 0, NULL, ICON_NONE);
|
||||
|
||||
box = uiLayoutBox(layout);
|
||||
col = uiLayoutColumn(box, true);
|
||||
uiItemR(col, ptr, "import_textures_mode", 0, NULL, ICON_NONE);
|
||||
bool copy_textures = RNA_enum_get(op->ptr, "import_textures_mode") == USD_TEX_IMPORT_COPY;
|
||||
row = uiLayoutRow(col, true);
|
||||
uiItemR(row, ptr, "import_textures_dir", 0, NULL, ICON_NONE);
|
||||
uiLayoutSetEnabled(row, copy_textures);
|
||||
row = uiLayoutRow(col, true);
|
||||
uiItemR(row, ptr, "tex_name_collision_mode", 0, NULL, ICON_NONE);
|
||||
uiLayoutSetEnabled(row, copy_textures);
|
||||
uiLayoutSetEnabled(col, RNA_boolean_get(ptr, "import_materials"));
|
||||
}
|
||||
|
||||
void WM_OT_usd_import(struct wmOperatorType *ot)
|
||||
|
@ -622,6 +662,28 @@ void WM_OT_usd_import(struct wmOperatorType *ot)
|
|||
USD_MTL_NAME_COLLISION_MAKE_UNIQUE,
|
||||
"Material Name Collision",
|
||||
"Behavior when the name of an imported material conflicts with an existing material");
|
||||
|
||||
RNA_def_enum(ot->srna,
|
||||
"import_textures_mode",
|
||||
rna_enum_usd_tex_import_mode_items,
|
||||
USD_TEX_IMPORT_PACK,
|
||||
"Import Textures",
|
||||
"Behavior when importing textures from a USDZ archive");
|
||||
|
||||
RNA_def_string(ot->srna,
|
||||
"import_textures_dir",
|
||||
"//textures/",
|
||||
FILE_MAXDIR,
|
||||
"Textures Directory",
|
||||
"Path to the directory where imported textures will be copied ");
|
||||
|
||||
RNA_def_enum(
|
||||
ot->srna,
|
||||
"tex_name_collision_mode",
|
||||
rna_enum_usd_tex_name_collision_mode_items,
|
||||
USD_TEX_NAME_COLLISION_USE_EXISTING,
|
||||
"File Name Collision",
|
||||
"Behavior when the name of an imported texture file conflicts with an existing file");
|
||||
}
|
||||
|
||||
#endif /* WITH_USD */
|
||||
|
|
|
@ -2667,7 +2667,7 @@ int ED_path_extension_type(const char *path)
|
|||
if (BLI_path_extension_check(path, ".abc")) {
|
||||
return FILE_TYPE_ALEMBIC;
|
||||
}
|
||||
if (BLI_path_extension_check_n(path, ".usd", ".usda", ".usdc", nullptr)) {
|
||||
if (BLI_path_extension_check_n(path, ".usd", ".usda", ".usdc", ".usdz", nullptr)) {
|
||||
return FILE_TYPE_USD;
|
||||
}
|
||||
if (BLI_path_extension_check(path, ".vdb")) {
|
||||
|
|
|
@ -60,6 +60,7 @@ set(INC_SYS
|
|||
)
|
||||
|
||||
set(SRC
|
||||
intern/usd_asset_utils.cc
|
||||
intern/usd_capi_export.cc
|
||||
intern/usd_capi_import.cc
|
||||
intern/usd_common.cc
|
||||
|
@ -88,6 +89,7 @@ set(SRC
|
|||
|
||||
usd.h
|
||||
|
||||
intern/usd_asset_utils.h
|
||||
intern/usd_common.h
|
||||
intern/usd_exporter_context.h
|
||||
intern/usd_hierarchy_iterator.h
|
||||
|
|
|
@ -0,0 +1,301 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
* Copyright 2023 NVIDIA Corportation. All rights reserved. */
|
||||
|
||||
#include "usd_asset_utils.h"
|
||||
|
||||
#include <pxr/usd/ar/asset.h>
|
||||
#include <pxr/usd/ar/packageUtils.h>
|
||||
#include <pxr/usd/ar/resolver.h>
|
||||
#include <pxr/usd/ar/writableAsset.h>
|
||||
|
||||
#include "BKE_main.h"
|
||||
|
||||
#include "BLI_fileops.h"
|
||||
#include "BLI_path_util.h"
|
||||
#include "BLI_string.h"
|
||||
|
||||
#include "WM_api.h"
|
||||
#include "WM_types.h"
|
||||
|
||||
static const char UDIM_PATTERN[] = "<UDIM>";
|
||||
static const char UDIM_PATTERN2[] = "%3CUDIM%3E";
|
||||
|
||||
/* Maximum range of UDIM tiles, per the
|
||||
* UsdPreviewSurface specifications. See
|
||||
* https://graphics.pixar.com/usd/release/spec_usdpreviewsurface.html#texture-reader
|
||||
*/
|
||||
static const int UDIM_START_TILE = 1001;
|
||||
static const int UDIM_END_TILE = 1100;
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
/* The following is copied from _SplitUdimPattern() in
|
||||
* USD library source file materialParamsUtils.cpp.
|
||||
* Split a udim file path such as /someDir/myFile.<UDIM>.exr into a
|
||||
* prefix (/someDir/myFile.) and suffix (.exr). */
|
||||
static std::pair<std::string, std::string> split_udim_pattern(const std::string &path)
|
||||
{
|
||||
static const std::vector<std::string> patterns = {UDIM_PATTERN, UDIM_PATTERN2};
|
||||
|
||||
for (const std::string &pattern : patterns) {
|
||||
const std::string::size_type pos = path.find(pattern);
|
||||
if (pos != std::string::npos) {
|
||||
return {path.substr(0, pos), path.substr(pos + pattern.size())};
|
||||
}
|
||||
}
|
||||
|
||||
return {std::string(), std::string()};
|
||||
}
|
||||
|
||||
/* Return the asset file base name, with special handling of
|
||||
* package relative paths. */
|
||||
static std::string get_asset_base_name(const char *src_path)
|
||||
{
|
||||
char base_name[FILE_MAXFILE];
|
||||
|
||||
if (pxr::ArIsPackageRelativePath(src_path)) {
|
||||
std::pair<std::string, std::string> split = pxr::ArSplitPackageRelativePathInner(src_path);
|
||||
if (split.second.empty()) {
|
||||
WM_reportf(RPT_WARNING,
|
||||
"%s: Couldn't determine package-relative file name from path %s",
|
||||
__func__,
|
||||
src_path);
|
||||
return src_path;
|
||||
}
|
||||
BLI_split_file_part(split.second.c_str(), base_name, sizeof(base_name));
|
||||
}
|
||||
else {
|
||||
BLI_split_file_part(src_path, base_name, sizeof(base_name));
|
||||
}
|
||||
|
||||
return base_name;
|
||||
}
|
||||
|
||||
/* Copy an asset to a destination directory. */
|
||||
static std::string copy_asset_to_directory(const char *src_path,
|
||||
const char *dest_dir_path,
|
||||
eUSDTexNameCollisionMode name_collision_mode)
|
||||
{
|
||||
std::string base_name = get_asset_base_name(src_path);
|
||||
|
||||
char dest_file_path[FILE_MAX];
|
||||
BLI_path_join(dest_file_path, sizeof(dest_file_path), dest_dir_path, base_name.c_str());
|
||||
BLI_path_normalize(NULL, dest_file_path);
|
||||
|
||||
if (name_collision_mode == USD_TEX_NAME_COLLISION_USE_EXISTING && BLI_is_file(dest_file_path)) {
|
||||
return dest_file_path;
|
||||
}
|
||||
|
||||
if (!copy_asset(src_path, dest_file_path, name_collision_mode)) {
|
||||
WM_reportf(
|
||||
RPT_WARNING, "%s: Couldn't copy file %s to %s.", __func__, src_path, dest_file_path);
|
||||
return src_path;
|
||||
}
|
||||
|
||||
return dest_file_path;
|
||||
}
|
||||
|
||||
static std::string copy_udim_asset_to_directory(const char *src_path,
|
||||
const char *dest_dir_path,
|
||||
eUSDTexNameCollisionMode name_collision_mode)
|
||||
{
|
||||
/* Get prefix and suffix from udim pattern. */
|
||||
std::pair<std::string, std::string> splitPath = split_udim_pattern(src_path);
|
||||
if (splitPath.first.empty() || splitPath.second.empty()) {
|
||||
WM_reportf(RPT_ERROR, "%s: Couldn't split UDIM pattern %s", __func__, src_path);
|
||||
return src_path;
|
||||
}
|
||||
|
||||
/* Copy the individual UDIM tiles. Since there is currently no way to query the contents
|
||||
* of a directory using the USD resolver, we must take a brute force approach. We iterate
|
||||
* over the allowed range of tile indices and copy any tiles that exist. The USDPreviewSurface
|
||||
* specification stipulates "a maximum of ten tiles in the U direction" and that
|
||||
* "the tiles must be within the range [1001, 1099]". See
|
||||
* https://graphics.pixar.com/usd/release/spec_usdpreviewsurface.html#texture-reader
|
||||
*/
|
||||
for (int i = UDIM_START_TILE; i < UDIM_END_TILE; ++i) {
|
||||
const std::string src_udim = splitPath.first + std::to_string(i) + splitPath.second;
|
||||
if (asset_exists(src_udim.c_str())) {
|
||||
copy_asset_to_directory(src_udim.c_str(), dest_dir_path, name_collision_mode);
|
||||
}
|
||||
}
|
||||
|
||||
const std::string src_file_name = get_asset_base_name(src_path);
|
||||
char ret_udim_path[FILE_MAX];
|
||||
BLI_path_join(ret_udim_path, sizeof(ret_udim_path), dest_dir_path, src_file_name.c_str());
|
||||
|
||||
/* Blender only recognizes the <UDIM> pattern, not the
|
||||
* alternative UDIM_PATTERN2, so we make sure the returned
|
||||
* path has the former. */
|
||||
splitPath = split_udim_pattern(ret_udim_path);
|
||||
if (splitPath.first.empty() || splitPath.second.empty()) {
|
||||
WM_reportf(RPT_ERROR, "%s: Couldn't split UDIM pattern %s", __func__, ret_udim_path);
|
||||
return ret_udim_path;
|
||||
}
|
||||
|
||||
return splitPath.first + UDIM_PATTERN + splitPath.second;
|
||||
}
|
||||
|
||||
bool copy_asset(const char *src, const char *dst, eUSDTexNameCollisionMode name_collision_mode)
|
||||
{
|
||||
if (!(src && dst)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
pxr::ArResolver &ar = pxr::ArGetResolver();
|
||||
|
||||
if (name_collision_mode != USD_TEX_NAME_COLLISION_OVERWRITE) {
|
||||
if (!ar.Resolve(dst).IsEmpty()) {
|
||||
/* The asset exists, so this is a no-op. */
|
||||
WM_reportf(RPT_INFO, "%s: Will not overwrite existing asset %s", __func__, dst);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
pxr::ArResolvedPath src_path = ar.Resolve(src);
|
||||
|
||||
if (src_path.IsEmpty()) {
|
||||
WM_reportf(RPT_ERROR, "%s: Can't resolve path %s", __func__, src);
|
||||
return false;
|
||||
}
|
||||
|
||||
pxr::ArResolvedPath dst_path = ar.ResolveForNewAsset(dst);
|
||||
|
||||
if (dst_path.IsEmpty()) {
|
||||
WM_reportf(RPT_ERROR, "%s: Can't resolve path %s for writing", __func__, dst);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (src_path == dst_path) {
|
||||
WM_reportf(RPT_ERROR,
|
||||
"%s: Can't copy %s. The source and destination paths are the same",
|
||||
__func__,
|
||||
src_path.GetPathString().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string why_not;
|
||||
if (!ar.CanWriteAssetToPath(dst_path, &why_not)) {
|
||||
WM_reportf(RPT_ERROR,
|
||||
"%s: Can't write to asset %s. %s.",
|
||||
__func__,
|
||||
dst_path.GetPathString().c_str(),
|
||||
why_not.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
std::shared_ptr<pxr::ArAsset> src_asset = ar.OpenAsset(src_path);
|
||||
if (!src_asset) {
|
||||
WM_reportf(
|
||||
RPT_ERROR, "%s: Can't open source asset %s", __func__, src_path.GetPathString().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
const size_t size = src_asset->GetSize();
|
||||
|
||||
if (size == 0) {
|
||||
WM_reportf(RPT_WARNING,
|
||||
"%s: Will not copy zero size source asset %s",
|
||||
__func__,
|
||||
src_path.GetPathString().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
std::shared_ptr<const char> buf = src_asset->GetBuffer();
|
||||
|
||||
if (!buf) {
|
||||
WM_reportf(RPT_ERROR,
|
||||
"%s: Null buffer for source asset %s",
|
||||
__func__,
|
||||
src_path.GetPathString().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
std::shared_ptr<pxr::ArWritableAsset> dst_asset = ar.OpenAssetForWrite(
|
||||
dst_path, pxr::ArResolver::WriteMode::Replace);
|
||||
if (!dst_asset) {
|
||||
WM_reportf(RPT_ERROR,
|
||||
"%s: Can't open destination asset %s for writing",
|
||||
__func__,
|
||||
src_path.GetPathString().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t bytes_written = dst_asset->Write(src_asset->GetBuffer().get(), src_asset->GetSize(), 0);
|
||||
|
||||
if (bytes_written == 0) {
|
||||
WM_reportf(RPT_ERROR,
|
||||
"%s: Error writing to destination asset %s",
|
||||
__func__,
|
||||
dst_path.GetPathString().c_str());
|
||||
}
|
||||
|
||||
if (!dst_asset->Close()) {
|
||||
WM_reportf(RPT_ERROR,
|
||||
"%s: Couldn't close destination asset %s",
|
||||
__func__,
|
||||
dst_path.GetPathString().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
return bytes_written > 0;
|
||||
}
|
||||
|
||||
bool asset_exists(const char *path)
|
||||
{
|
||||
return path && !pxr::ArGetResolver().Resolve(path).IsEmpty();
|
||||
}
|
||||
|
||||
std::string import_asset(const char *src,
|
||||
const char *import_dir,
|
||||
eUSDTexNameCollisionMode name_collision_mode)
|
||||
{
|
||||
if (import_dir[0] == '\0') {
|
||||
WM_reportf(
|
||||
RPT_ERROR, "%s: Texture import directory path empty, couldn't import %s", __func__, src);
|
||||
return src;
|
||||
}
|
||||
|
||||
char dest_dir_path[FILE_MAXDIR];
|
||||
STRNCPY(dest_dir_path, import_dir);
|
||||
|
||||
const char *basepath = nullptr;
|
||||
|
||||
if (BLI_path_is_rel(import_dir)) {
|
||||
basepath = BKE_main_blendfile_path_from_global();
|
||||
|
||||
if (!basepath || basepath[0] == '\0') {
|
||||
WM_reportf(RPT_ERROR,
|
||||
"%s: import directory is relative "
|
||||
"but the blend file path is empty. "
|
||||
"Please save the blend file before importing the USD "
|
||||
"or provide an absolute import directory path. "
|
||||
"Can't import %s",
|
||||
__func__,
|
||||
src);
|
||||
return src;
|
||||
}
|
||||
}
|
||||
|
||||
BLI_path_normalize(basepath, dest_dir_path);
|
||||
|
||||
if (!BLI_dir_create_recursive(dest_dir_path)) {
|
||||
WM_reportf(
|
||||
RPT_ERROR, "%s: Couldn't create texture import directory %s", __func__, dest_dir_path);
|
||||
return src;
|
||||
}
|
||||
|
||||
if (is_udim_path(src)) {
|
||||
return copy_udim_asset_to_directory(src, dest_dir_path, name_collision_mode);
|
||||
}
|
||||
|
||||
return copy_asset_to_directory(src, dest_dir_path, name_collision_mode);
|
||||
}
|
||||
|
||||
bool is_udim_path(const std::string &path)
|
||||
{
|
||||
return path.find(UDIM_PATTERN) != std::string::npos ||
|
||||
path.find(UDIM_PATTERN2) != std::string::npos;
|
||||
}
|
||||
|
||||
} // namespace blender::io::usd
|
|
@ -0,0 +1,57 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
* Copyright 2023 NVIDIA Corporation. All rights reserved. */
|
||||
#pragma once
|
||||
|
||||
#include "usd.h"
|
||||
|
||||
#include <pxr/usd/usd/stage.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
/**
|
||||
* Invoke the USD asset resolver to copy an asset.
|
||||
*
|
||||
* \param src: source path of the asset to copy
|
||||
* \param dst: destination path of the copy
|
||||
* \param name_collision_mode: behavior when `dst` already exists
|
||||
* \return true if the copy succeeded, false otherwise
|
||||
*/
|
||||
bool copy_asset(const char *src, const char *dst, eUSDTexNameCollisionMode name_collision_mode);
|
||||
|
||||
/**
|
||||
* Invoke the USD asset resolver to determine if the
|
||||
* asset with the given path exists.
|
||||
*
|
||||
* \param path: the path to resolve
|
||||
* \return true if the asset exists, false otherwise
|
||||
*/
|
||||
bool asset_exists(const char *path);
|
||||
|
||||
/**
|
||||
* Invoke the USD asset resolver to copy an asset to a destination
|
||||
* directory and return the path to the copied file. This function may
|
||||
* be used to copy textures from a USDZ archive to a directory on disk.
|
||||
* The destination directory will be created if it doesn't already exist.
|
||||
* If the copy was unsuccessful, this function will log an error and
|
||||
* return the original source file path unmodified.
|
||||
*
|
||||
* \param src: source path of the asset to import
|
||||
* \param import_dir: path to the destination directory
|
||||
* \param name_collision_mode: behavior when a file of the same name already exists
|
||||
* \return path to copied file or the original `src` path if there was an error
|
||||
*/
|
||||
std::string import_asset(const char *src,
|
||||
const char *import_dir,
|
||||
eUSDTexNameCollisionMode name_collision_mode);
|
||||
|
||||
/**
|
||||
* Check if the given path contains a UDIM token.
|
||||
*
|
||||
* \param path: the path to check
|
||||
* \return true if the path contains a UDIM token, false otherwise
|
||||
*/
|
||||
bool is_udim_path(const std::string &path);
|
||||
|
||||
} // namespace blender::io::usd
|
|
@ -3,6 +3,9 @@
|
|||
|
||||
#include "usd_reader_material.h"
|
||||
|
||||
#include "usd_asset_utils.h"
|
||||
|
||||
#include "BKE_appdir.h"
|
||||
#include "BKE_image.h"
|
||||
#include "BKE_lib_id.h"
|
||||
#include "BKE_main.h"
|
||||
|
@ -19,6 +22,7 @@
|
|||
#include "DNA_material_types.h"
|
||||
|
||||
#include <pxr/base/gf/vec3f.h>
|
||||
#include <pxr/usd/ar/packageUtils.h>
|
||||
#include <pxr/usd/usdShade/material.h>
|
||||
#include <pxr/usd/usdShade/shader.h>
|
||||
|
||||
|
@ -63,6 +67,23 @@ static const pxr::TfToken UsdPrimvarReader_float2("UsdPrimvarReader_float2",
|
|||
static const pxr::TfToken UsdUVTexture("UsdUVTexture", 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;
|
||||
}
|
||||
|
||||
/* Add a node of the given type at the given location coordinates. */
|
||||
static bNode *add_node(
|
||||
const bContext *C, bNodeTree *ntree, const int type, const float locx, const float locy)
|
||||
|
@ -112,11 +133,6 @@ static pxr::SdfLayerHandle get_layer_handle(const pxr::UsdAttribute &attribute)
|
|||
return pxr::SdfLayerHandle();
|
||||
}
|
||||
|
||||
static bool is_udim_path(const std::string &path)
|
||||
{
|
||||
return path.find("<UDIM>") != std::string::npos;
|
||||
}
|
||||
|
||||
/* For the given UDIM path (assumed to contain the UDIM token), returns an array
|
||||
* containing valid tile indices. */
|
||||
static blender::Vector<int> get_udim_tiles(const std::string &file_path)
|
||||
|
@ -676,6 +692,26 @@ void USDMaterialReader::load_tex_image(const pxr::UsdShadeShader &usd_shader,
|
|||
return;
|
||||
}
|
||||
|
||||
/* 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(file_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;
|
||||
|
||||
file_path = import_asset(file_path.c_str(), textures_dir, name_collision_mode);
|
||||
}
|
||||
|
||||
/* If this is a UDIM texture, this will store the
|
||||
* UDIM tile indices. */
|
||||
blender::Vector<int> udim_tiles;
|
||||
|
@ -712,6 +748,14 @@ void USDMaterialReader::load_tex_image(const pxr::UsdShadeShader &usd_shader,
|
|||
if (ELEM(color_space, usdtokens::RAW, usdtokens::raw)) {
|
||||
STRNCPY(image->colorspace_settings.name, "Raw");
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void USDMaterialReader::convert_usd_primvar_reader_float2(
|
||||
|
|
|
@ -21,6 +21,21 @@ typedef enum eUSDMtlNameCollisionMode {
|
|||
USD_MTL_NAME_COLLISION_REFERENCE_EXISTING = 1,
|
||||
} eUSDMtlNameCollisionMode;
|
||||
|
||||
/* Behavior when importing textures from a package
|
||||
* (e.g., USDZ archive) or from a URI path. */
|
||||
typedef enum eUSDTexImportMode {
|
||||
USD_TEX_IMPORT_NONE = 0,
|
||||
USD_TEX_IMPORT_PACK,
|
||||
USD_TEX_IMPORT_COPY,
|
||||
} eUSDTexImportMode;
|
||||
|
||||
/* Behavior when the name of an imported texture
|
||||
* file conflicts with an existing file. */
|
||||
typedef enum eUSDTexNameCollisionMode {
|
||||
USD_TEX_NAME_COLLISION_USE_EXISTING = 0,
|
||||
USD_TEX_NAME_COLLISION_OVERWRITE = 1,
|
||||
} eUSDTexNameCollisionMode;
|
||||
|
||||
struct USDExportParams {
|
||||
bool export_animation;
|
||||
bool export_hair;
|
||||
|
@ -64,6 +79,9 @@ struct USDImportParams {
|
|||
bool set_material_blend;
|
||||
float light_intensity_scale;
|
||||
eUSDMtlNameCollisionMode mtl_name_collision_mode;
|
||||
eUSDTexImportMode import_textures_mode;
|
||||
char import_textures_dir[768]; /* FILE_MAXDIR */
|
||||
eUSDTexNameCollisionMode tex_name_collision_mode;
|
||||
bool import_all_materials;
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue