USD: Use Asset Resolver to resolve texture paths

This patch uses the USD AssetResolver to deal with texture paths.
Functionally, adding this patch should make no functional differences in
the way textures are written.

If textures are specified as assets instead of file paths, at current
the file will error on load and the textures will not be assigned. These
should now be processed correctly.

See PR for example file and testing scenarios.

Co-authored-by: kiki <charles@skeletalstudios.com>
Co-authored-by: Jesse Yurkovich <jesse.y@gmail.com>
Co-authored-by: Charles Wardlaw <cwardlaw@nvidia.com>
Pull Request: https://projects.blender.org/blender/blender/pulls/122747
This commit is contained in:
Charles Wardlaw
2024-07-29 20:00:48 +02:00
committed by Jesse Yurkovich
parent 8ea4d7ed89
commit 8a97f31e76
8 changed files with 699 additions and 72 deletions

View File

@@ -156,6 +156,21 @@ const EnumPropertyItem prop_usdz_downscale_size[] = {
{0, nullptr, 0, nullptr, nullptr},
};
const EnumPropertyItem rna_enum_usd_tex_export_mode_items[] = {
{USD_TEX_EXPORT_KEEP, "KEEP", 0, "Keep", "Use original location of textures"},
{USD_TEX_EXPORT_PRESERVE,
"PRESERVE",
0,
"Preserve",
"Preserve file paths of textures from already imported USD files."
"Export remaining textures to a 'textures' folder next to the USD file"},
{USD_TEX_EXPORT_NEW_PATH,
"NEW",
0,
"New Path",
"Export textures to a 'textures' folder next to the USD file"},
{0, nullptr, 0, nullptr, nullptr}};
/* 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. */
struct eUSDOperatorOptions {
@@ -234,7 +249,6 @@ static int wm_usd_export_exec(bContext *C, wmOperator *op)
const bool generate_preview_surface = RNA_boolean_get(op->ptr, "generate_preview_surface");
const bool generate_materialx_network = RNA_boolean_get(op->ptr, "generate_materialx_network");
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_paths = RNA_boolean_get(op->ptr, "relative_paths");
@@ -269,6 +283,30 @@ static int wm_usd_export_exec(bContext *C, wmOperator *op)
const bool allow_unicode = false;
# endif
/* When the texture export settings were moved into an enum this bit
* became more involved, but it needs to stick around for API backwards
* compatibility until Blender 5.0. */
const eUSDTexExportMode textures_mode = eUSDTexExportMode(
RNA_enum_get(op->ptr, "export_textures_mode"));
bool export_textures = RNA_boolean_get(op->ptr, "export_textures");
bool use_original_paths = false;
if (!export_textures) {
switch (textures_mode) {
case eUSDTexExportMode::USD_TEX_EXPORT_PRESERVE:
export_textures = false;
use_original_paths = true;
break;
case eUSDTexExportMode::USD_TEX_EXPORT_NEW_PATH:
export_textures = true;
use_original_paths = false;
break;
default:
use_original_paths = false;
}
}
char root_prim_path[FILE_MAX];
RNA_string_get(op->ptr, "root_prim_path", root_prim_path);
process_prim_path(root_prim_path);
@@ -315,6 +353,7 @@ static int wm_usd_export_exec(bContext *C, wmOperator *op)
usdz_downscale_size,
usdz_downscale_custom_size,
allow_unicode,
use_original_paths,
};
STRNCPY(params.root_prim_path, root_prim_path);
@@ -423,21 +462,18 @@ static void wm_usd_export_draw(bContext *C, wmOperator *op)
uiItemR(col, ptr, "generate_materialx_network", UI_ITEM_NONE, nullptr, ICON_NONE);
uiItemR(col, ptr, "convert_world_material", UI_ITEM_NONE, nullptr, ICON_NONE);
const bool preview = RNA_boolean_get(ptr, "generate_preview_surface");
const bool materialx = RNA_boolean_get(ptr, "generate_materialx_network");
const bool export_tex = RNA_boolean_get(ptr, "export_textures");
col = uiLayoutColumn(panel.body, true);
uiLayoutSetPropSep(col, true);
uiLayout *row = uiLayoutRow(col, true);
uiItemR(row, ptr, "export_textures", UI_ITEM_NONE, nullptr, ICON_NONE);
uiLayoutSetActive(row, export_materials && (preview || materialx));
uiItemR(col, ptr, "export_textures_mode", UI_ITEM_NONE, nullptr, ICON_NONE);
row = uiLayoutRow(col, true);
uiItemR(row, ptr, "overwrite_textures", UI_ITEM_NONE, nullptr, ICON_NONE);
uiLayoutSetActive(row, export_tex && (preview || materialx));
const eUSDTexExportMode textures_mode = eUSDTexExportMode(
RNA_enum_get(op->ptr, "export_textures_mode"));
uiLayout *col2 = uiLayoutColumn(col, true);
uiLayoutSetPropSep(col2, true);
uiLayoutSetEnabled(col2, RNA_boolean_get(ptr, "export_textures"));
uiLayoutSetEnabled(col2, textures_mode == USD_TEX_EXPORT_NEW_PATH);
uiItemR(col2, ptr, "overwrite_textures", UI_ITEM_NONE, nullptr, ICON_NONE);
uiItemR(col2, ptr, "usdz_downscale_size", UI_ITEM_NONE, nullptr, ICON_NONE);
if (RNA_enum_get(ptr, "usdz_downscale_size") == USD_TEXTURE_SIZE_CUSTOM) {
uiItemR(col2, ptr, "usdz_downscale_custom_size", UI_ITEM_NONE, nullptr, ICON_NONE);
@@ -645,11 +681,18 @@ void WM_OT_usd_export(wmOperatorType *ot)
RNA_def_boolean(ot->srna,
"export_textures",
true,
false,
"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_enum(ot->srna,
"export_textures_mode",
rna_enum_usd_tex_export_mode_items,
USD_TEX_EXPORT_NEW_PATH,
"Export Textures",
"Texture export method");
RNA_def_boolean(ot->srna,
"overwrite_textures",
false,

View File

@@ -11,12 +11,16 @@
#include <pxr/usd/ar/writableAsset.h>
#include "BKE_appdir.hh"
#include "BKE_idprop.hh"
#include "BKE_main.hh"
#include "BKE_report.hh"
#include "BLI_fileops.hh"
#include "BLI_path_util.h"
#include "BLI_string.h"
#include "BLI_string_utils.hh"
#include "WM_api.hh"
#include <string_view>
@@ -156,7 +160,7 @@ bool copy_asset(const char *src,
return false;
}
pxr::ArResolver &ar = pxr::ArGetResolver();
const pxr::ArResolver &ar = pxr::ArGetResolver();
if (name_collision_mode != USD_TEX_NAME_COLLISION_OVERWRITE) {
if (!ar.Resolve(dst).IsEmpty()) {
@@ -325,12 +329,90 @@ std::string import_asset(const char *src,
return copy_asset_to_directory(src, dest_dir_path, name_collision_mode, reports);
}
/**
* Returns true if the parent directory of the given path exists on the
* file system.
*
* \param path: input file path
* \return true if the parent directory exists
*/
static bool parent_dir_exists_on_file_system(const std::string &path)
{
char dir_path[FILE_MAX];
BLI_path_split_dir_part(path.c_str(), dir_path, FILE_MAX);
return BLI_is_dir(dir_path);
}
bool is_udim_path(const std::string &path)
{
return path.find(UDIM_PATTERN) != std::string::npos ||
path.find(UDIM_PATTERN2) != std::string::npos;
}
std::string get_export_textures_dir(const pxr::UsdStageRefPtr stage)
{
pxr::SdfLayerHandle layer = stage->GetRootLayer();
if (layer->IsAnonymous()) {
WM_reportf(
RPT_WARNING, "%s: Can't generate a textures directory path for anonymous stage", __func__);
return "";
}
const pxr::ArResolvedPath &stage_path = layer->GetResolvedPath();
if (stage_path.empty()) {
WM_reportf(RPT_WARNING, "%s: Can't get resolved path for stage", __func__);
return "";
}
const pxr::ArResolver &ar = pxr::ArGetResolver();
/* Resolove the './textures' relative path, with the stage path as an anchor. */
std::string textures_dir = ar.CreateIdentifierForNewAsset("./textures", stage_path);
/* If parent of the stage path exists as a file system directory, try to create the
* textures directory. */
if (parent_dir_exists_on_file_system(stage_path.GetPathString())) {
BLI_dir_create_recursive(textures_dir.c_str());
}
return textures_dir;
}
bool should_import_asset(const std::string &path)
{
if (path.empty()) {
return false;
}
if (BLI_path_is_rel(path.c_str())) {
return false;
}
if (pxr::ArIsPackageRelativePath(path)) {
return true;
}
if (is_udim_path(path) && parent_dir_exists_on_file_system(path.c_str())) {
return false;
}
return !BLI_is_file(path.c_str()) && asset_exists(path.c_str());
}
bool paths_equal(const char *p1, const char *p2)
{
BLI_assert_msg(!BLI_path_is_rel(p1) && !BLI_path_is_rel(p2), "Paths arguments must be absolute");
const pxr::ArResolver &ar = pxr::ArGetResolver();
std::string resolved_p1 = ar.ResolveForNewAsset(p1).GetPathString();
std::string resolved_p2 = ar.ResolveForNewAsset(p2).GetPathString();
return resolved_p1 == resolved_p2;
}
const char *temp_textures_dir()
{
static bool inited = false;
@@ -345,4 +427,233 @@ const char *temp_textures_dir()
return temp_dir;
}
bool write_to_path(const void *data, size_t size, const char *path, ReportList *reports)
{
BLI_assert(data);
BLI_assert(path);
if (size == 0) {
return false;
}
const pxr::ArResolver &ar = pxr::ArGetResolver();
pxr::ArResolvedPath resolved_path = ar.ResolveForNewAsset(path);
if (resolved_path.IsEmpty()) {
BKE_reportf(reports, RPT_ERROR, "Can't resolve path %s for writing", path);
return false;
}
std::string why_not;
if (!ar.CanWriteAssetToPath(resolved_path, &why_not)) {
BKE_reportf(reports,
RPT_ERROR,
"Can't write to asset %s: %s",
resolved_path.GetPathString().c_str(),
why_not.c_str());
return false;
}
std::shared_ptr<pxr::ArWritableAsset> dst_asset = ar.OpenAssetForWrite(
resolved_path, pxr::ArResolver::WriteMode::Replace);
if (!dst_asset) {
BKE_reportf(reports,
RPT_ERROR,
"Can't open destination asset %s for writing",
resolved_path.GetPathString().c_str());
return false;
}
size_t bytes_written = dst_asset->Write(data, size, 0);
if (bytes_written == 0) {
BKE_reportf(reports,
RPT_ERROR,
"Error writing to destination asset %s",
resolved_path.GetPathString().c_str());
}
if (!dst_asset->Close()) {
BKE_reportf(reports,
RPT_ERROR,
"Couldn't close destination asset %s",
resolved_path.GetPathString().c_str());
return false;
}
return bytes_written > 0;
}
void ensure_usd_source_path_prop(const std::string &path, ID *id)
{
if (!id || path.empty()) {
return;
}
if (pxr::ArIsPackageRelativePath(path)) {
/* Don't record package-relative paths (e.g., images in USDZ
* archives). */
return;
}
IDProperty *idgroup = IDP_EnsureProperties(id);
if (!idgroup) {
return;
}
const char *prop_name = "usd_source_path";
if (IDP_GetPropertyFromGroup(idgroup, prop_name)) {
return;
}
IDPropertyTemplate val = {0};
val.string.str = path.c_str();
/* Note length includes null terminator. */
val.string.len = path.size() + 1;
val.string.subtype = IDP_STRING_SUB_UTF8;
IDProperty *prop = IDP_New(IDP_STRING, &val, prop_name);
IDP_AddToGroup(idgroup, prop);
}
std::string get_usd_source_path(ID *id)
{
if (!id) {
return "";
}
const IDProperty *idgroup = IDP_EnsureProperties(id);
if (!idgroup) {
return "";
}
const char *prop_name = "usd_source_path";
const IDProperty *prop = IDP_GetPropertyFromGroup(idgroup, prop_name);
if (!prop) {
return "";
}
return static_cast<const char *>(prop->data.pointer);
}
std::string get_relative_path(const std::string &path, const std::string &anchor)
{
if (path.empty() || anchor.empty()) {
return path;
}
if (path == anchor) {
return path;
}
if (BLI_path_is_rel(path.c_str())) {
return path;
}
if (pxr::ArIsPackageRelativePath(path)) {
return path;
}
if (BLI_is_file(path.c_str()) && BLI_is_file(anchor.c_str())) {
/* Treat the paths as standard files. */
char rel_path[FILE_MAX];
STRNCPY(rel_path, path.c_str());
BLI_path_rel(rel_path, anchor.c_str());
if (!BLI_path_is_rel(rel_path)) {
return path;
}
BLI_string_replace_char(rel_path, '\\', '/');
return rel_path + 2;
}
/* if we got here, the paths may be URIs or files on on the
* file system. */
/* We don't have a library to compute relative paths for URIs
* so we use the standard fielsystem calls to do so. This
* may not work for all URIs in theory, but is probably sufficient
* for the subset of URIs we are likely to encounter in practice
* currently.
* TODO(makowalski): provide better utilities for this. */
const pxr::ArResolver &ar = pxr::ArGetResolver();
std::string resolved_path = ar.Resolve(path);
std::string resolved_anchor = ar.Resolve(anchor);
if (resolved_path.empty() || resolved_anchor.empty()) {
return path;
}
std::string prefix = pxr::TfStringGetCommonPrefix(path, anchor);
if (prefix.empty()) {
return path;
}
std::replace(prefix.begin(), prefix.end(), '\\', '/');
size_t last_slash_pos = prefix.find_last_of('/');
if (last_slash_pos == std::string::npos) {
/* Unexpected: The prefix doesn't contain a slash,
* so this was not an absolute path. */
return path;
}
/* Replace the common prefix up to the last slash with
* a fake root directory to allow computing the relative path
* excluding the URI. We omit the URI because it might not
* be handled correctly by the standard filesystem path
* computaions. */
resolved_path = "/root" + resolved_path.substr(last_slash_pos);
resolved_anchor = "/root" + resolved_anchor.substr(last_slash_pos);
char anchor_parent_dir[FILE_MAX];
BLI_path_split_dir_part(resolved_anchor.c_str(), anchor_parent_dir, FILE_MAX);
if (anchor_parent_dir[0] == '\0') {
return path;
}
char result_path[FILE_MAX];
BLI_strncpy(result_path, resolved_path.c_str(), FILE_MAX);
BLI_path_rel(result_path, anchor_parent_dir);
if ((result_path[0] != '\0') && (BLI_strnlen(result_path, FILE_MAX) > 2) &&
(result_path[0] == '/') && (result_path[1] == '/'))
{
/* Strip the Blender relative path marker, and set paths to Unix-style. */
BLI_string_replace_char(result_path, '\\', '/');
return std::string(result_path + 2);
}
return path;
}
void USD_path_abs(char *path, const char *basepath, bool for_import)
{
if (!BLI_path_is_rel(path)) {
pxr::ArResolvedPath resolved_path = for_import ? pxr::ArGetResolver().Resolve(path) :
pxr::ArGetResolver().ResolveForNewAsset(path);
const std::string &path_str = resolved_path.GetPathString();
if (!path_str.empty()) {
if (path_str.length() < FILE_MAX) {
BLI_strncpy(path, path_str.c_str(), FILE_MAX);
return;
}
WM_reportf(RPT_ERROR,
"In %s: resolved path %s exceeds path buffer length.",
__func__,
path_str.c_str());
}
}
/* If we got here, the path couldn't be resolved by the ArResolver, so we
* fall back on the standard Blender absolute path resolution. */
BLI_path_abs(path, basepath);
}
} // namespace blender::io::usd

View File

@@ -61,10 +61,98 @@ std::string import_asset(const char *src,
*/
bool is_udim_path(const std::string &path);
/**
* Invoke the USD asset resolver to return an identifier for a 'textures' directory
* which is a sibling of the given stage. The resulting path is created by
* resolving the './textures' relative path with the stage's root layer path as
* the anchor. If the parent of the stage root layer path resolves to a file
* system path, the textures directory will be created, if it doesn't exist.
*
* \param stage: The stage whose root layer is a sibling of the 'textures'
* directory
* \return the path to the 'textures' directory
*/
std::string get_export_textures_dir(const pxr::UsdStageRefPtr stage);
/**
* Return true if the asset at the given path is a candidate for importing
* with the USD asset resolver. The following heuristics are currently
* applied for this test:
* - Returns false if it's a Blender relative path.
* - Returns true if the path is package-relative.
* - Returns true is the path doesn't exist on the file system but can
* nonetheles be resolved by the USD asset resolver.
* - Returns false otherwise.
*
* TODO(makowalski): the test currently requires a file-system stat.
* Consider possible ways around this, e.g., by determining if the
* path is a supported URI.
*
* \param path: input file path
* \return true if the path should be imported, false otherwise
*/
bool should_import_asset(const std::string &path);
/**
* Invokes the USD asset resolver to resolve the given paths and
* returns true if the resolved paths are equal.
*
* \param p1: first path to compare
* \param p2: second path to compare
* \return true if the resolved input paths are equal, returns
* false otherwise.
*
*/
bool paths_equal(const char *p1, const char *p2);
/**
* 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();
/**
* Invokes the USD asset resolver to write data to the given path.
*
* \param data: pointer to data to write
* \param size: number of bytes to write
* \param path: path of asset to be written
* \param reports: the storage for potential warning or error reports (generated using BKE_report
* API).
* \return true if the data was written, returns
* false otherwise.
*
*/
bool write_to_path(const void *data, size_t size, const char *path, ReportList *reports);
/**
* Add the given path as a custom property "usd_source_path" on the given id.
* If the path is a package-relative path (i.e., is relative to a USDZ archive)
* it will not be added a a property. If custom property "usd_source_path"
* already exists, this function does nothing.
*
* \param path: path to record as a custom property
* \param id: id for which to create the custom propery
*/
void ensure_usd_source_path_prop(const std::string &path, ID *id);
/**
* Return the value of the "usd_source_path" custom property on the given id.
* Return an empty string if the property does not exist.
*/
std::string get_usd_source_path(ID *id);
/**
* Return the given path as a relative path with respect to the given anchor
* path.
*
* \param path: path to make relative with respect to the anchor path
* \param anchor: the anchor path
* \return the relative path string; return the input path unchanged if it can't
* be made relative, is already a relative path or is a package-relative
* path
*
*/
std::string get_relative_path(const std::string &path, const std::string &anchor);
} // namespace blender::io::usd

View File

@@ -133,7 +133,9 @@ static Image *load_image(std::string tex_path, Main *bmain, const USDImportParam
{
/* 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);
should_import_asset(tex_path);
std::string imported_file_source_path = tex_path;
if (import_textures) {
/* If we are packing the imported textures, we first write them
@@ -155,6 +157,10 @@ static Image *load_image(std::string tex_path, Main *bmain, const USDImportParam
return nullptr;
}
if (import_textures && imported_file_source_path != tex_path) {
ensure_usd_source_path_prop(imported_file_source_path, &image->id);
}
if (import_textures && params.import_textures_mode == USD_TEX_IMPORT_PACK &&
!BKE_image_has_packedfile(image))
{

View File

@@ -21,6 +21,7 @@
#include "BLI_math_vector.h"
#include "BLI_path_util.h"
#include "BLI_string.h"
#include "BLI_string_utils.hh"
#include "BLI_vector.hh"
#include "DNA_material_types.h"
@@ -1165,11 +1166,29 @@ void USDMaterialReader::load_tex_image(const pxr::UsdShadeShader &usd_shader,
if (!file_input) {
CLOG_WARN(&LOG,
"Couldn't get file input for USD shader %s",
"Couldn't get file input property for USD shader %s",
usd_shader.GetPath().GetAsString().c_str());
return;
}
/* File input may have a connected source, e.g., if it's been overridden by
* an input on the mateial. */
if (file_input.HasConnectedSource()) {
pxr::UsdShadeConnectableAPI source;
pxr::TfToken source_name;
pxr::UsdShadeAttributeType source_type;
if (file_input.GetConnectedSource(&source, &source_name, &source_type)) {
file_input = source.GetInput(source_name);
}
else {
CLOG_WARN(&LOG,
"Couldn't get connected source for file input %s (%s)\n",
file_input.GetPrim().GetPath().GetText(),
file_input.GetFullName().GetText());
}
}
pxr::VtValue file_val;
if (!file_input.Get(&file_val) || !file_val.IsHolding<pxr::SdfAssetPath>()) {
CLOG_WARN(&LOG,
@@ -1180,31 +1199,35 @@ void USDMaterialReader::load_tex_image(const pxr::UsdShadeShader &usd_shader,
const pxr::SdfAssetPath &asset_path = file_val.Get<pxr::SdfAssetPath>();
std::string file_path = asset_path.GetResolvedPath();
if (file_path.empty()) {
/* No resolved path, so use the asset path (usually
* necessary for UDIM paths). */
/* No resolved path, so use the asset path (usually necessary for UDIM paths). */
file_path = asset_path.GetAssetPath();
/* Texture paths are frequently relative to the USD, so get
* the absolute path. */
if (pxr::SdfLayerHandle layer_handle = get_layer_handle(file_input.GetAttr())) {
file_path = layer_handle->ComputeAbsolutePath(file_path);
if (!file_path.empty() && is_udim_path(file_path)) {
/* Texture paths are frequently relative to the USD, so get the absolute path. */
if (pxr::SdfLayerHandle layer_handle = get_layer_handle(file_input.GetAttr())) {
file_path = layer_handle->ComputeAbsolutePath(file_path);
}
}
}
if (file_path.empty()) {
CLOG_WARN(&LOG,
" Couldn't resolve image asset '%s' for Texture Image node",
"Couldn't resolve image asset '%s' for Texture Image node",
asset_path.GetAssetPath().c_str());
return;
}
/* Optionally copy the asset if it's inside a USDZ package. */
const bool is_relative = pxr::ArIsPackageRelativePath(file_path);
const bool import_textures = params_.import_textures_mode != USD_TEX_IMPORT_NONE && is_relative;
const bool import_textures = params_.import_textures_mode != USD_TEX_IMPORT_NONE &&
pxr::ArIsPackageRelativePath(file_path);
std::string imported_file_source_path;
if (import_textures) {
imported_file_source_path = file_path;
/* 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 ?
@@ -1278,6 +1301,10 @@ void USDMaterialReader::load_tex_image(const pxr::UsdShadeShader &usd_shader,
NodeTexImage *storage = static_cast<NodeTexImage *>(tex_image->storage);
storage->extension = get_image_extension(usd_shader, storage->extension);
if (import_textures && imported_file_source_path != file_path) {
ensure_usd_source_path_prop(imported_file_source_path, &image->id);
}
if (import_textures && params_.import_textures_mode == USD_TEX_IMPORT_PACK &&
!BKE_image_has_packedfile(image))
{

View File

@@ -3,6 +3,7 @@
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "usd_writer_material.hh"
#include "usd_asset_utils.hh"
#include "usd_exporter_context.hh"
#include "usd_hook.hh"
#include "usd_utils.hh"
@@ -16,6 +17,7 @@
#include "BKE_report.hh"
#include "IMB_colormanagement.hh"
#include "IMB_imbuf.hh"
#include "BLI_fileops.h"
#include "BLI_listbase.h"
@@ -28,6 +30,7 @@
#include "DNA_material_types.h"
#include "DNA_node_types.h"
#include "DNA_packedFile_types.h"
#include "MEM_guardedalloc.h"
@@ -600,6 +603,16 @@ static void create_uv_input(const USDExporterContext &usd_export_context,
usd_export_context, uvmap_link, usd_material, usd_input, active_uvmap_name, reports);
}
static bool is_in_memory_texture(Image *ima)
{
return BKE_image_is_dirty(ima) || ima->source == IMA_SRC_GENERATED;
}
static bool is_packed_texture(Image *ima)
{
return BKE_image_has_packedfile(ima);
}
/* 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)
@@ -607,6 +620,7 @@ 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);
bool is_tiled = ima->source == IMA_SRC_TILED;
if (!(is_generated || is_dirty || is_packed)) {
return "";
}
@@ -627,6 +641,14 @@ static std::string get_in_memory_texture_filename(Image *ima)
BKE_image_path_ext_from_imformat_ensure(file_name, sizeof(file_name), &imageFormat);
if (is_tiled) {
/* Ensure that the UDIM tag is in. */
char file_body[FILE_MAX];
char file_ext[FILE_MAX];
BLI_string_split_suffix(file_name, FILE_MAX, file_body, file_ext);
BLI_snprintf(file_name, FILE_MAX, "%s.<UDIM>%s", file_body, file_ext);
}
return file_name;
}
@@ -874,9 +896,9 @@ static std::string get_tex_image_asset_filepath(const USDExporterContext &usd_ex
node, usd_export_context.stage, usd_export_context.export_params);
}
static std::string get_tex_image_asset_filepath(Image *ima,
const pxr::UsdStageRefPtr stage,
const USDExportParams &export_params)
std::string get_tex_image_asset_filepath(Image *ima,
const pxr::UsdStageRefPtr stage,
const USDExportParams &export_params)
{
std::string stage_path = stage->GetRootLayer()->GetRealPath();
@@ -886,17 +908,38 @@ static std::string get_tex_image_asset_filepath(Image *ima,
std::string path;
if (ima->filepath[0]) {
/* Get absolute path. */
path = get_tex_image_asset_filepath(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. */
if (is_in_memory_texture(ima)) {
path = get_in_memory_texture_filename(ima);
}
else {
if (!export_params.export_textures && export_params.use_original_paths) {
path = get_usd_source_path(&ima->id);
}
if (is_packed_texture(ima)) {
if (path.empty()) {
char file_name[FILE_MAX];
path = get_in_memory_texture_filename(ima);
BLI_path_join(file_name, FILE_MAX, ".", "textures", path.c_str());
path = file_name;
}
}
else if (ima->filepath[0] != '\0') {
/* Get absolute path. */
path = get_tex_image_asset_filepath(ima);
}
}
return get_tex_image_asset_filepath(path, stage_path, export_params);
}
/* Return a USD asset path referencing the given texture file. 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. */
std::string get_tex_image_asset_filepath(const std::string &path,
const std::string &stage_path,
const USDExportParams &export_params)
{
if (path.empty()) {
return path;
}
@@ -914,6 +957,10 @@ static std::string get_tex_image_asset_filepath(Image *ima,
}
else {
/* Create absolute path in the textures directory. */
if (stage_path.empty()) {
return path;
}
char 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);
@@ -924,15 +971,15 @@ static std::string get_tex_image_asset_filepath(Image *ima,
if (export_params.relative_paths) {
/* Get the path relative to the USD. */
char rel_path[FILE_MAX];
STRNCPY(rel_path, path.c_str());
BLI_path_rel(rel_path, stage_path.c_str());
if (!BLI_path_is_rel(rel_path)) {
if (stage_path.empty()) {
return path;
}
BLI_string_replace_char(rel_path, '\\', '/');
return rel_path + 2;
std::string rel_path = get_relative_path(path, stage_path);
if (rel_path.empty()) {
return path;
}
return rel_path;
}
return path;
@@ -942,7 +989,6 @@ std::string get_tex_image_asset_filepath(bNode *node,
const pxr::UsdStageRefPtr stage,
const USDExportParams &export_params)
{
Image *ima = reinterpret_cast<Image *>(node->id);
return get_tex_image_asset_filepath(ima, stage, export_params);
}
@@ -1073,31 +1119,6 @@ static void export_texture(Image *ima,
}
}
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;
}
Image *ima = reinterpret_cast<Image *>(node->id);
if (!ima) {
return;
}
return export_texture(ima, stage, allow_overwrite, reports);
}
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);
}
static void export_texture(const USDExporterContext &usd_export_context, Image *ima)
{
export_texture(ima,
@@ -1384,4 +1405,112 @@ pxr::UsdShadeMaterial create_usd_material(const USDExporterContext &usd_export_c
return usd_material;
}
static void export_packed_texture(Image *ima,
const std::string &export_dir,
const bool allow_overwrite,
ReportList *reports)
{
LISTBASE_FOREACH (ImagePackedFile *, imapf, &ima->packedfiles) {
if (!imapf || !imapf->packedfile || !imapf->packedfile->data || !imapf->packedfile->size) {
continue;
}
const PackedFile *pf = imapf->packedfile;
char image_abs_path[FILE_MAX];
char file_name[FILE_MAX];
if (imapf->filepath[0] != '\0') {
/* Get the file name from the original path. */
/* Make absolute source path. */
BLI_strncpy(image_abs_path, imapf->filepath, FILE_MAX);
USD_path_abs(
image_abs_path, ID_BLEND_PATH_FROM_GLOBAL(&ima->id), false /* Not for import */);
BLI_path_split_file_part(image_abs_path, file_name, FILE_MAX);
}
else {
/* The following logic is taken from unpack_generate_paths() in packedFile.cc. */
/* NOTE: we generally do not have any real way to re-create extension out of data. */
const size_t len = STRNCPY_RLEN(file_name, ima->id.name + 2);
/* For images ensure that the temporary filename contains tile number information as well as
* a file extension based on the file magic. */
enum eImbFileType ftype = eImbFileType(
IMB_ispic_type_from_memory(static_cast<const uchar *>(pf->data), pf->size));
if (ima->source == IMA_SRC_TILED) {
char tile_number[6];
SNPRINTF(tile_number, ".%d", imapf->tile_number);
BLI_strncpy(file_name + len, tile_number, sizeof(file_name) - len);
}
if (ftype != IMB_FTYPE_NONE) {
const int imtype = BKE_ftype_to_imtype(ftype, nullptr);
BKE_image_path_ext_from_imtype_ensure(file_name, sizeof(file_name), imtype);
}
}
char export_path[FILE_MAX];
BLI_path_join(export_path, FILE_MAX, export_dir.c_str(), file_name);
BLI_string_replace_char(export_path, '\\', '/');
if (!allow_overwrite && asset_exists(export_path)) {
return;
}
if (paths_equal(export_path, image_abs_path) && asset_exists(image_abs_path)) {
/* As a precaution, don't overwrite the original path. */
return;
}
CLOG_INFO(&LOG, 2, "Exporting packed texture to '%s'", export_path);
write_to_path(pf->data, pf->size, export_path, reports);
}
}
/* 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;
}
Image *ima = reinterpret_cast<Image *>(node->id);
if (!ima) {
return;
}
std::string dest_dir = get_export_textures_dir(stage);
if (dest_dir.empty()) {
CLOG_ERROR(&LOG, "Couldn't determine textures directory path");
return;
}
if (is_packed_texture(ima)) {
export_packed_texture(ima, dest_dir, allow_overwrite, reports);
}
else if (is_in_memory_texture(ima)) {
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, reports);
}
else {
copy_single_file(ima, dest_dir, allow_overwrite, reports);
}
}
} // namespace blender::io::usd

View File

@@ -8,6 +8,7 @@
#include <string>
struct bNode;
struct Image;
struct Material;
struct ReportList;
@@ -31,14 +32,12 @@ 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
/* 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
@@ -49,4 +48,12 @@ std::string get_tex_image_asset_filepath(bNode *node,
const pxr::UsdStageRefPtr stage,
const USDExportParams &export_params);
std::string get_tex_image_asset_filepath(Image *ima,
const pxr::UsdStageRefPtr stage,
const USDExportParams &export_params);
std::string get_tex_image_asset_filepath(const std::string &asset_path,
const std::string &stage_path,
const USDExportParams &export_params);
} // namespace blender::io::usd

View File

@@ -95,6 +95,15 @@ typedef enum eUSDZTextureDownscaleSize {
USD_TEXTURE_SIZE_4096 = 4096
} eUSDZTextureDownscaleSize;
/**
* Behavior when exporting textures.
*/
enum eUSDTexExportMode {
USD_TEX_EXPORT_KEEP = 0,
USD_TEX_EXPORT_PRESERVE,
USD_TEX_EXPORT_NEW_PATH,
};
struct USDExportParams {
bool export_animation = false;
bool export_hair = true;
@@ -113,7 +122,7 @@ struct USDExportParams {
enum eEvaluationMode evaluation_mode = DAG_EVAL_VIEWPORT;
bool generate_preview_surface = true;
bool generate_materialx_network = true;
bool export_textures = true;
bool export_textures = false;
bool overwrite_textures = true;
bool relative_paths = true;
bool export_custom_properties = true;
@@ -136,6 +145,7 @@ struct USDExportParams {
bool allow_unicode = false;
bool use_original_paths = false;
char root_prim_path[1024] = ""; /* FILE_MAX */
char collection[MAX_IDPROP_NAME] = "";
char custom_properties_namespace[MAX_IDPROP_NAME] = "";
@@ -227,6 +237,12 @@ int USD_get_version();
/* USD Import and Mesh Cache interface. */
/* Similar to BLI_path_abs(), but also invokes the USD asset resolver
* to determine the absolute path. This is necessary for resolving
* paths with URIs that BLI_path_abs() would otherwise alter when
* attempting to normalize the path. */
void USD_path_abs(char *path, const char *basepath, bool for_import);
CacheArchiveHandle *USD_create_handle(Main *bmain, const char *filepath, ListBase *object_paths);
void USD_free_handle(CacheArchiveHandle *handle);