Fix USD I/O crashing on reports by using new wmJob report system.

Calling `WM_report` & co API from wmJob worker thread is utterly unsafe,
and should never have been done. It 'worked' so far presumably because
worker threads were barely (if ever) reporting anything that way, but
now USD IO code is spamming reports in some cases, leading to fairly
common crashes.

Pull Request: https://projects.blender.org/blender/blender/pulls/113883
This commit is contained in:
Bastien Montagne 2023-10-13 11:46:02 +02:00 committed by Gitea
parent 21c8af467d
commit b684864561
29 changed files with 560 additions and 312 deletions

View File

@ -179,7 +179,7 @@ static int wm_usd_export_exec(bContext *C, wmOperator *op)
STRNCPY(params.root_prim_path, root_prim_path); STRNCPY(params.root_prim_path, root_prim_path);
bool ok = USD_export(C, filepath, &params, as_background_job); bool ok = USD_export(C, filepath, &params, as_background_job, op->reports);
return as_background_job || ok ? OPERATOR_FINISHED : OPERATOR_CANCELLED; return as_background_job || ok ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
} }
@ -515,7 +515,7 @@ static int wm_usd_import_exec(bContext *C, wmOperator *op)
STRNCPY(params.import_textures_dir, import_textures_dir); STRNCPY(params.import_textures_dir, import_textures_dir);
const bool ok = USD_import(C, filepath, &params, as_background_job); const bool ok = USD_import(C, filepath, &params, as_background_job, op->reports);
return as_background_job || ok ? OPERATOR_FINISHED : OPERATOR_CANCELLED; return as_background_job || ok ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
} }

View File

@ -9,9 +9,13 @@
#include "BLI_string.h" #include "BLI_string.h"
#include "BKE_appdir.h" #include "BKE_appdir.h"
#include "BKE_report.h"
#include "DEG_depsgraph_query.hh" #include "DEG_depsgraph_query.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "usd.h" #include "usd.h"
#include "usd.hh" #include "usd.hh"
@ -50,6 +54,14 @@ void USDSceneDelegate::populate(Depsgraph *depsgraph)
params.export_textures = false; /* Don't copy all textures, is slow. */ params.export_textures = false; /* Don't copy all textures, is slow. */
params.evaluation_mode = DEG_get_mode(depsgraph); params.evaluation_mode = DEG_get_mode(depsgraph);
/* NOTE: Since the reports list will be `nullptr` here, reports generated by export code from
* this call will only be printed to console. */
wmJobWorkerStatus worker_status = {};
ReportList worker_reports = {};
BKE_reports_init(&worker_reports, RPT_PRINT | RPT_STORE);
worker_status.reports = &worker_reports;
params.worker_status = &worker_status;
/* Create clean directory for export. */ /* Create clean directory for export. */
BLI_delete(temp_dir_.c_str(), true, true); BLI_delete(temp_dir_.c_str(), true, true);
BLI_dir_create_recursive(temp_dir_.c_str()); BLI_dir_create_recursive(temp_dir_.c_str());
@ -62,6 +74,8 @@ void USDSceneDelegate::populate(Depsgraph *depsgraph)
stage_ = io::usd::export_to_stage(params, depsgraph, temp_file_.c_str()); stage_ = io::usd::export_to_stage(params, depsgraph, temp_file_.c_str());
delegate_ = std::make_unique<pxr::UsdImagingDelegate>(render_index_, delegate_id_); delegate_ = std::make_unique<pxr::UsdImagingDelegate>(render_index_, delegate_id_);
delegate_->Populate(stage_->GetPseudoRoot()); delegate_->Populate(stage_->GetPseudoRoot());
WM_reports_from_reports_move(nullptr, &worker_reports);
} }
} // namespace blender::io::hydra } // namespace blender::io::hydra

View File

@ -10,6 +10,7 @@
#include <pxr/usd/ar/writableAsset.h> #include <pxr/usd/ar/writableAsset.h>
#include "BKE_main.h" #include "BKE_main.h"
#include "BKE_report.h"
#include "BLI_fileops.h" #include "BLI_fileops.h"
#include "BLI_path_util.h" #include "BLI_path_util.h"
@ -52,14 +53,15 @@ static std::pair<std::string, std::string> split_udim_pattern(const std::string
/* Return the asset file base name, with special handling of /* Return the asset file base name, with special handling of
* package relative paths. */ * package relative paths. */
static std::string get_asset_base_name(const char *src_path) static std::string get_asset_base_name(const char *src_path, ReportList *reports)
{ {
char base_name[FILE_MAXFILE]; char base_name[FILE_MAXFILE];
if (pxr::ArIsPackageRelativePath(src_path)) { if (pxr::ArIsPackageRelativePath(src_path)) {
std::pair<std::string, std::string> split = pxr::ArSplitPackageRelativePathInner(src_path); std::pair<std::string, std::string> split = pxr::ArSplitPackageRelativePathInner(src_path);
if (split.second.empty()) { if (split.second.empty()) {
WM_reportf(RPT_WARNING, BKE_reportf(reports,
RPT_WARNING,
"%s: Couldn't determine package-relative file name from path %s", "%s: Couldn't determine package-relative file name from path %s",
__func__, __func__,
src_path); src_path);
@ -77,9 +79,10 @@ static std::string get_asset_base_name(const char *src_path)
/* Copy an asset to a destination directory. */ /* Copy an asset to a destination directory. */
static std::string copy_asset_to_directory(const char *src_path, static std::string copy_asset_to_directory(const char *src_path,
const char *dest_dir_path, const char *dest_dir_path,
eUSDTexNameCollisionMode name_collision_mode) eUSDTexNameCollisionMode name_collision_mode,
ReportList *reports)
{ {
std::string base_name = get_asset_base_name(src_path); std::string base_name = get_asset_base_name(src_path, reports);
char dest_file_path[FILE_MAX]; 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_join(dest_file_path, sizeof(dest_file_path), dest_dir_path, base_name.c_str());
@ -89,8 +92,13 @@ static std::string copy_asset_to_directory(const char *src_path,
return dest_file_path; return dest_file_path;
} }
if (!copy_asset(src_path, dest_file_path, name_collision_mode)) { if (!copy_asset(src_path, dest_file_path, name_collision_mode, reports)) {
WM_reportf(RPT_WARNING, "%s: Couldn't copy file %s to %s", __func__, src_path, dest_file_path); BKE_reportf(reports,
RPT_WARNING,
"%s: Couldn't copy file %s to %s",
__func__,
src_path,
dest_file_path);
return src_path; return src_path;
} }
@ -99,12 +107,13 @@ static std::string copy_asset_to_directory(const char *src_path,
static std::string copy_udim_asset_to_directory(const char *src_path, static std::string copy_udim_asset_to_directory(const char *src_path,
const char *dest_dir_path, const char *dest_dir_path,
eUSDTexNameCollisionMode name_collision_mode) eUSDTexNameCollisionMode name_collision_mode,
ReportList *reports)
{ {
/* Get prefix and suffix from udim pattern. */ /* Get prefix and suffix from udim pattern. */
std::pair<std::string, std::string> splitPath = split_udim_pattern(src_path); std::pair<std::string, std::string> splitPath = split_udim_pattern(src_path);
if (splitPath.first.empty() || splitPath.second.empty()) { if (splitPath.first.empty() || splitPath.second.empty()) {
WM_reportf(RPT_ERROR, "%s: Couldn't split UDIM pattern %s", __func__, src_path); BKE_reportf(reports, RPT_ERROR, "%s: Couldn't split UDIM pattern %s", __func__, src_path);
return src_path; return src_path;
} }
@ -118,11 +127,11 @@ static std::string copy_udim_asset_to_directory(const char *src_path,
for (int i = UDIM_START_TILE; i < UDIM_END_TILE; ++i) { for (int i = UDIM_START_TILE; i < UDIM_END_TILE; ++i) {
const std::string src_udim = splitPath.first + std::to_string(i) + splitPath.second; const std::string src_udim = splitPath.first + std::to_string(i) + splitPath.second;
if (asset_exists(src_udim.c_str())) { if (asset_exists(src_udim.c_str())) {
copy_asset_to_directory(src_udim.c_str(), dest_dir_path, name_collision_mode); copy_asset_to_directory(src_udim.c_str(), dest_dir_path, name_collision_mode, reports);
} }
} }
const std::string src_file_name = get_asset_base_name(src_path); const std::string src_file_name = get_asset_base_name(src_path, reports);
char ret_udim_path[FILE_MAX]; char ret_udim_path[FILE_MAX];
BLI_path_join(ret_udim_path, sizeof(ret_udim_path), dest_dir_path, src_file_name.c_str()); BLI_path_join(ret_udim_path, sizeof(ret_udim_path), dest_dir_path, src_file_name.c_str());
@ -131,14 +140,17 @@ static std::string copy_udim_asset_to_directory(const char *src_path,
* path has the former. */ * path has the former. */
splitPath = split_udim_pattern(ret_udim_path); splitPath = split_udim_pattern(ret_udim_path);
if (splitPath.first.empty() || splitPath.second.empty()) { if (splitPath.first.empty() || splitPath.second.empty()) {
WM_reportf(RPT_ERROR, "%s: Couldn't split UDIM pattern %s", __func__, ret_udim_path); BKE_reportf(reports, RPT_ERROR, "%s: Couldn't split UDIM pattern %s", __func__, ret_udim_path);
return ret_udim_path; return ret_udim_path;
} }
return splitPath.first + UDIM_PATTERN + splitPath.second; return splitPath.first + UDIM_PATTERN + splitPath.second;
} }
bool copy_asset(const char *src, const char *dst, eUSDTexNameCollisionMode name_collision_mode) bool copy_asset(const char *src,
const char *dst,
eUSDTexNameCollisionMode name_collision_mode,
ReportList *reports)
{ {
if (!(src && dst)) { if (!(src && dst)) {
return false; return false;
@ -149,7 +161,7 @@ bool copy_asset(const char *src, const char *dst, eUSDTexNameCollisionMode name_
if (name_collision_mode != USD_TEX_NAME_COLLISION_OVERWRITE) { if (name_collision_mode != USD_TEX_NAME_COLLISION_OVERWRITE) {
if (!ar.Resolve(dst).IsEmpty()) { if (!ar.Resolve(dst).IsEmpty()) {
/* The asset exists, so this is a no-op. */ /* The asset exists, so this is a no-op. */
WM_reportf(RPT_INFO, "%s: Will not overwrite existing asset %s", __func__, dst); BKE_reportf(reports, RPT_INFO, "%s: Will not overwrite existing asset %s", __func__, dst);
return true; return true;
} }
} }
@ -157,19 +169,20 @@ bool copy_asset(const char *src, const char *dst, eUSDTexNameCollisionMode name_
pxr::ArResolvedPath src_path = ar.Resolve(src); pxr::ArResolvedPath src_path = ar.Resolve(src);
if (src_path.IsEmpty()) { if (src_path.IsEmpty()) {
WM_reportf(RPT_ERROR, "%s: Can't resolve path %s", __func__, src); BKE_reportf(reports, RPT_ERROR, "%s: Can't resolve path %s", __func__, src);
return false; return false;
} }
pxr::ArResolvedPath dst_path = ar.ResolveForNewAsset(dst); pxr::ArResolvedPath dst_path = ar.ResolveForNewAsset(dst);
if (dst_path.IsEmpty()) { if (dst_path.IsEmpty()) {
WM_reportf(RPT_ERROR, "%s: Can't resolve path %s for writing", __func__, dst); BKE_reportf(reports, RPT_ERROR, "%s: Can't resolve path %s for writing", __func__, dst);
return false; return false;
} }
if (src_path == dst_path) { if (src_path == dst_path) {
WM_reportf(RPT_ERROR, BKE_reportf(reports,
RPT_ERROR,
"%s: Can't copy %s. The source and destination paths are the same", "%s: Can't copy %s. The source and destination paths are the same",
__func__, __func__,
src_path.GetPathString().c_str()); src_path.GetPathString().c_str());
@ -178,7 +191,8 @@ bool copy_asset(const char *src, const char *dst, eUSDTexNameCollisionMode name_
std::string why_not; std::string why_not;
if (!ar.CanWriteAssetToPath(dst_path, &why_not)) { if (!ar.CanWriteAssetToPath(dst_path, &why_not)) {
WM_reportf(RPT_ERROR, BKE_reportf(reports,
RPT_ERROR,
"%s: Can't write to asset %s: %s", "%s: Can't write to asset %s: %s",
__func__, __func__,
dst_path.GetPathString().c_str(), dst_path.GetPathString().c_str(),
@ -188,15 +202,19 @@ bool copy_asset(const char *src, const char *dst, eUSDTexNameCollisionMode name_
std::shared_ptr<pxr::ArAsset> src_asset = ar.OpenAsset(src_path); std::shared_ptr<pxr::ArAsset> src_asset = ar.OpenAsset(src_path);
if (!src_asset) { if (!src_asset) {
WM_reportf( BKE_reportf(reports,
RPT_ERROR, "%s: Can't open source asset %s", __func__, src_path.GetPathString().c_str()); RPT_ERROR,
"%s: Can't open source asset %s",
__func__,
src_path.GetPathString().c_str());
return false; return false;
} }
const size_t size = src_asset->GetSize(); const size_t size = src_asset->GetSize();
if (size == 0) { if (size == 0) {
WM_reportf(RPT_WARNING, BKE_reportf(reports,
RPT_WARNING,
"%s: Will not copy zero size source asset %s", "%s: Will not copy zero size source asset %s",
__func__, __func__,
src_path.GetPathString().c_str()); src_path.GetPathString().c_str());
@ -206,7 +224,8 @@ bool copy_asset(const char *src, const char *dst, eUSDTexNameCollisionMode name_
std::shared_ptr<const char> buf = src_asset->GetBuffer(); std::shared_ptr<const char> buf = src_asset->GetBuffer();
if (!buf) { if (!buf) {
WM_reportf(RPT_ERROR, BKE_reportf(reports,
RPT_ERROR,
"%s: Null buffer for source asset %s", "%s: Null buffer for source asset %s",
__func__, __func__,
src_path.GetPathString().c_str()); src_path.GetPathString().c_str());
@ -216,7 +235,8 @@ bool copy_asset(const char *src, const char *dst, eUSDTexNameCollisionMode name_
std::shared_ptr<pxr::ArWritableAsset> dst_asset = ar.OpenAssetForWrite( std::shared_ptr<pxr::ArWritableAsset> dst_asset = ar.OpenAssetForWrite(
dst_path, pxr::ArResolver::WriteMode::Replace); dst_path, pxr::ArResolver::WriteMode::Replace);
if (!dst_asset) { if (!dst_asset) {
WM_reportf(RPT_ERROR, BKE_reportf(reports,
RPT_ERROR,
"%s: Can't open destination asset %s for writing", "%s: Can't open destination asset %s for writing",
__func__, __func__,
src_path.GetPathString().c_str()); src_path.GetPathString().c_str());
@ -226,14 +246,16 @@ bool copy_asset(const char *src, const char *dst, eUSDTexNameCollisionMode name_
size_t bytes_written = dst_asset->Write(src_asset->GetBuffer().get(), src_asset->GetSize(), 0); size_t bytes_written = dst_asset->Write(src_asset->GetBuffer().get(), src_asset->GetSize(), 0);
if (bytes_written == 0) { if (bytes_written == 0) {
WM_reportf(RPT_ERROR, BKE_reportf(reports,
RPT_ERROR,
"%s: Error writing to destination asset %s", "%s: Error writing to destination asset %s",
__func__, __func__,
dst_path.GetPathString().c_str()); dst_path.GetPathString().c_str());
} }
if (!dst_asset->Close()) { if (!dst_asset->Close()) {
WM_reportf(RPT_ERROR, BKE_reportf(reports,
RPT_ERROR,
"%s: Couldn't close destination asset %s", "%s: Couldn't close destination asset %s",
__func__, __func__,
dst_path.GetPathString().c_str()); dst_path.GetPathString().c_str());
@ -250,11 +272,15 @@ bool asset_exists(const char *path)
std::string import_asset(const char *src, std::string import_asset(const char *src,
const char *import_dir, const char *import_dir,
eUSDTexNameCollisionMode name_collision_mode) eUSDTexNameCollisionMode name_collision_mode,
ReportList *reports)
{ {
if (import_dir[0] == '\0') { if (import_dir[0] == '\0') {
WM_reportf( BKE_reportf(reports,
RPT_ERROR, "%s: Texture import directory path empty, couldn't import %s", __func__, src); RPT_ERROR,
"%s: Texture import directory path empty, couldn't import %s",
__func__,
src);
return src; return src;
} }
@ -267,7 +293,8 @@ std::string import_asset(const char *src,
basepath = BKE_main_blendfile_path_from_global(); basepath = BKE_main_blendfile_path_from_global();
if (!basepath || basepath[0] == '\0') { if (!basepath || basepath[0] == '\0') {
WM_reportf(RPT_ERROR, BKE_reportf(reports,
RPT_ERROR,
"%s: import directory is relative " "%s: import directory is relative "
"but the blend file path is empty. " "but the blend file path is empty. "
"Please save the blend file before importing the USD " "Please save the blend file before importing the USD "
@ -283,16 +310,19 @@ std::string import_asset(const char *src,
BLI_path_normalize(dest_dir_path); BLI_path_normalize(dest_dir_path);
if (!BLI_dir_create_recursive(dest_dir_path)) { if (!BLI_dir_create_recursive(dest_dir_path)) {
WM_reportf( BKE_reportf(reports,
RPT_ERROR, "%s: Couldn't create texture import directory %s", __func__, dest_dir_path); RPT_ERROR,
"%s: Couldn't create texture import directory %s",
__func__,
dest_dir_path);
return src; return src;
} }
if (is_udim_path(src)) { if (is_udim_path(src)) {
return copy_udim_asset_to_directory(src, dest_dir_path, name_collision_mode); return copy_udim_asset_to_directory(src, dest_dir_path, name_collision_mode, reports);
} }
return copy_asset_to_directory(src, dest_dir_path, name_collision_mode); return copy_asset_to_directory(src, dest_dir_path, name_collision_mode, reports);
} }
bool is_udim_path(const std::string &path) bool is_udim_path(const std::string &path)

View File

@ -17,9 +17,14 @@ namespace blender::io::usd {
* \param src: source path of the asset to copy * \param src: source path of the asset to copy
* \param dst: destination path of the copy * \param dst: destination path of the copy
* \param name_collision_mode: behavior when `dst` already exists * \param name_collision_mode: behavior when `dst` already exists
* \param reports: the storage for potential warning or error reports (generated using BKE_report
* API).
* \return true if the copy succeeded, false otherwise * \return true if the copy succeeded, false otherwise
*/ */
bool copy_asset(const char *src, const char *dst, eUSDTexNameCollisionMode name_collision_mode); bool copy_asset(const char *src,
const char *dst,
eUSDTexNameCollisionMode name_collision_mode,
ReportList *reports);
/** /**
* Invoke the USD asset resolver to determine if the * Invoke the USD asset resolver to determine if the
@ -41,11 +46,14 @@ bool asset_exists(const char *path);
* \param src: source path of the asset to import * \param src: source path of the asset to import
* \param import_dir: path to the destination directory * \param import_dir: path to the destination directory
* \param name_collision_mode: behavior when a file of the same name already exists * \param name_collision_mode: behavior when a file of the same name already exists
* \param reports: the storage for potential warning or error reports (generated using BKE_report
* API).
* \return path to copied file or the original `src` path if there was an error * \return path to copied file or the original `src` path if there was an error
*/ */
std::string import_asset(const char *src, std::string import_asset(const char *src,
const char *import_dir, const char *import_dir,
eUSDTexNameCollisionMode name_collision_mode); eUSDTexNameCollisionMode name_collision_mode,
ReportList *reports);
/** /**
* Check if the given path contains a UDIM token. * Check if the given path contains a UDIM token.

View File

@ -28,6 +28,7 @@
#include "BKE_blender_version.h" #include "BKE_blender_version.h"
#include "BKE_context.h" #include "BKE_context.h"
#include "BKE_global.h" #include "BKE_global.h"
#include "BKE_report.h"
#include "BKE_scene.h" #include "BKE_scene.h"
#include "BLI_fileops.h" #include "BLI_fileops.h"
@ -108,6 +109,8 @@ static bool prim_path_valid(const char *path)
/** /**
* Perform validation of export parameter settings. * Perform validation of export parameter settings.
* \return true if the parameters are valid; returns false otherwise. * \return true if the parameters are valid; returns false otherwise.
*
* \warning Do not call from worker thread, only from main thread (i.e. before starting the wmJob).
*/ */
static bool export_params_valid(const USDExportParams &params) static bool export_params_valid(const USDExportParams &params)
{ {
@ -183,14 +186,17 @@ static bool perform_usdz_conversion(const ExportJobData *data)
if (BLI_exists(data->usdz_filepath)) { if (BLI_exists(data->usdz_filepath)) {
result = BLI_delete(data->usdz_filepath, false, false); result = BLI_delete(data->usdz_filepath, false, false);
if (result != 0) { if (result != 0) {
WM_reportf( BKE_reportf(data->params.worker_status->reports,
RPT_ERROR, "USD Export: Unable to delete existing usdz file %s", data->usdz_filepath); RPT_ERROR,
"USD Export: Unable to delete existing usdz file %s",
data->usdz_filepath);
return false; return false;
} }
} }
result = BLI_path_move(usdz_temp_full_path, data->usdz_filepath); result = BLI_path_move(usdz_temp_full_path, data->usdz_filepath);
if (result != 0) { if (result != 0) {
WM_reportf(RPT_ERROR, BKE_reportf(data->params.worker_status->reports,
RPT_ERROR,
"USD Export: Couldn't move new usdz file from temporary location %s to %s", "USD Export: Couldn't move new usdz file from temporary location %s to %s",
usdz_temp_full_path, usdz_temp_full_path,
data->usdz_filepath); data->usdz_filepath);
@ -200,16 +206,16 @@ static bool perform_usdz_conversion(const ExportJobData *data)
return true; return true;
} }
static pxr::UsdStageRefPtr export_to_stage(const USDExportParams &params, pxr::UsdStageRefPtr export_to_stage(const USDExportParams &params,
Depsgraph *depsgraph, Depsgraph *depsgraph,
const char *filepath, const char *filepath)
wmJobWorkerStatus *worker_status)
{ {
pxr::UsdStageRefPtr usd_stage = pxr::UsdStage::CreateNew(filepath); pxr::UsdStageRefPtr usd_stage = pxr::UsdStage::CreateNew(filepath);
if (!usd_stage) { if (!usd_stage) {
return usd_stage; return usd_stage;
} }
wmJobWorkerStatus *worker_status = params.worker_status;
Scene *scene = DEG_get_input_scene(depsgraph); Scene *scene = DEG_get_input_scene(depsgraph);
Main *bmain = DEG_get_bmain(depsgraph); Main *bmain = DEG_get_bmain(depsgraph);
@ -273,7 +279,7 @@ static pxr::UsdStageRefPtr export_to_stage(const USDExportParams &params,
} }
} }
call_export_hooks(usd_stage, depsgraph); call_export_hooks(usd_stage, depsgraph, params.worker_status->reports);
/* Finish up by going back to the keyframe that was current before we started. */ /* Finish up by going back to the keyframe that was current before we started. */
if (scene->r.cfra != orig_frame) { if (scene->r.cfra != orig_frame) {
@ -284,14 +290,6 @@ static pxr::UsdStageRefPtr export_to_stage(const USDExportParams &params,
return usd_stage; return usd_stage;
} }
pxr::UsdStageRefPtr export_to_stage(const USDExportParams &params,
Depsgraph *depsgraph,
const char *filepath)
{
wmJobWorkerStatus worker_status = {};
return export_to_stage(params, depsgraph, filepath, &worker_status);
}
static void export_startjob(void *customdata, wmJobWorkerStatus *worker_status) static void export_startjob(void *customdata, wmJobWorkerStatus *worker_status)
{ {
ExportJobData *data = static_cast<ExportJobData *>(customdata); ExportJobData *data = static_cast<ExportJobData *>(customdata);
@ -315,14 +313,16 @@ static void export_startjob(void *customdata, wmJobWorkerStatus *worker_status)
worker_status->progress = 0.0f; worker_status->progress = 0.0f;
worker_status->do_update = true; worker_status->do_update = true;
data->params.worker_status = worker_status;
pxr::UsdStageRefPtr usd_stage = export_to_stage( pxr::UsdStageRefPtr usd_stage = export_to_stage(
data->params, data->depsgraph, data->unarchived_filepath, worker_status); data->params, data->depsgraph, data->unarchived_filepath);
if (!usd_stage) { if (!usd_stage) {
/* This happens when the USD JSON files cannot be found. When that happens, /* This happens when the USD JSON files cannot be found. When that happens,
* the USD library doesn't know it has the functionality to write USDA and * the USD library doesn't know it has the functionality to write USDA and
* USDC files, and creating a new UsdStage fails. */ * USDC files, and creating a new UsdStage fails. */
WM_reportf(RPT_ERROR, BKE_reportf(worker_status->reports,
RPT_ERROR,
"USD Export: unable to find suitable USD plugin to write %s", "USD Export: unable to find suitable USD plugin to write %s",
data->unarchived_filepath); data->unarchived_filepath);
return; return;
@ -420,7 +420,8 @@ static void set_job_filepath(blender::io::usd::ExportJobData *job, const char *f
bool USD_export(bContext *C, bool USD_export(bContext *C,
const char *filepath, const char *filepath,
const USDExportParams *params, const USDExportParams *params,
bool as_background_job) bool as_background_job,
ReportList *reports)
{ {
if (!blender::io::usd::export_params_valid(*params)) { if (!blender::io::usd::export_params_valid(*params)) {
return false; return false;
@ -469,6 +470,9 @@ bool USD_export(bContext *C,
} }
else { else {
wmJobWorkerStatus worker_status = {}; wmJobWorkerStatus worker_status = {};
/* Use the operator's reports in non-background case. */
worker_status.reports = reports;
blender::io::usd::export_startjob(job, &worker_status); blender::io::usd::export_startjob(job, &worker_status);
blender::io::usd::export_endjob(job); blender::io::usd::export_endjob(job);
export_ok = job->export_ok; export_ok = job->export_ok;

View File

@ -21,6 +21,7 @@
#include "BKE_main.h" #include "BKE_main.h"
#include "BKE_node.hh" #include "BKE_node.hh"
#include "BKE_object.hh" #include "BKE_object.hh"
#include "BKE_report.h"
#include "BKE_scene.h" #include "BKE_scene.h"
#include "BKE_world.h" #include "BKE_world.h"
@ -157,6 +158,8 @@ static void import_startjob(void *customdata, wmJobWorkerStatus *worker_status)
data->archive = nullptr; data->archive = nullptr;
data->start_time = timeit::Clock::now(); data->start_time = timeit::Clock::now();
data->params.worker_status = worker_status;
WM_set_locked_interface(data->wm, true); WM_set_locked_interface(data->wm, true);
G.is_break = false; G.is_break = false;
@ -221,7 +224,10 @@ static void import_startjob(void *customdata, wmJobWorkerStatus *worker_status)
pxr::UsdStage::OpenMasked(data->filepath, pop_mask); pxr::UsdStage::OpenMasked(data->filepath, pop_mask);
if (!stage) { if (!stage) {
WM_reportf(RPT_ERROR, "USD Import: unable to open stage to read %s", data->filepath); BKE_reportf(worker_status->reports,
RPT_ERROR,
"USD Import: unable to open stage to read %s",
data->filepath);
data->import_ok = false; data->import_ok = false;
data->error_code = USD_ARCHIVE_FAIL; data->error_code = USD_ARCHIVE_FAIL;
return; return;
@ -392,7 +398,9 @@ static void import_endjob(void *customdata)
data->import_ok = !data->was_canceled; data->import_ok = !data->was_canceled;
break; break;
case USD_ARCHIVE_FAIL: case USD_ARCHIVE_FAIL:
WM_report(RPT_ERROR, "Could not open USD archive for reading, see console for detail"); BKE_report(data->params.worker_status->reports,
RPT_ERROR,
"Could not open USD archive for reading, see console for detail");
break; break;
} }
@ -417,7 +425,8 @@ using namespace blender::io::usd;
bool USD_import(bContext *C, bool USD_import(bContext *C,
const char *filepath, const char *filepath,
const USDImportParams *params, const USDImportParams *params,
bool as_background_job) bool as_background_job,
ReportList *reports)
{ {
/* Using new here since `MEM_*` functions do not call constructor to properly initialize data. */ /* Using new here since `MEM_*` functions do not call constructor to properly initialize data. */
ImportJobData *job = new ImportJobData(); ImportJobData *job = new ImportJobData();
@ -460,6 +469,9 @@ bool USD_import(bContext *C,
} }
else { else {
wmJobWorkerStatus worker_status = {}; wmJobWorkerStatus worker_status = {};
/* Use the operator's reports in non-background case. */
worker_status.reports = reports;
import_startjob(job, &worker_status); import_startjob(job, &worker_status);
import_endjob(job); import_endjob(job);
import_ok = job->import_ok; import_ok = job->import_ok;

View File

@ -15,6 +15,8 @@
#include "BLI_listbase.h" #include "BLI_listbase.h"
#include "BKE_report.h"
#include "RNA_access.hh" #include "RNA_access.hh"
#include "RNA_prototypes.h" #include "RNA_prototypes.h"
#include "RNA_types.hh" #include "RNA_types.hh"
@ -152,7 +154,7 @@ void register_export_hook_converters()
} }
/* Retrieve and report the current Python error. */ /* Retrieve and report the current Python error. */
static void handle_python_error(USDHook *hook) static void handle_python_error(USDHook *hook, ReportList *reports)
{ {
if (!PyErr_Occurred()) { if (!PyErr_Occurred()) {
return; return;
@ -160,7 +162,8 @@ static void handle_python_error(USDHook *hook)
PyErr_Print(); PyErr_Print();
WM_reportf(RPT_ERROR, BKE_reportf(reports,
RPT_ERROR,
"An exception occurred invoking USD hook '%s'. Please see the console for details", "An exception occurred invoking USD hook '%s'. Please see the console for details",
hook->name); hook->name);
} }
@ -207,10 +210,11 @@ class USDHookInvoker {
call_hook(hook_obj); call_hook(hook_obj);
} }
catch (python::error_already_set const &) { catch (python::error_already_set const &) {
handle_python_error(hook); handle_python_error(hook, reports_);
} }
catch (...) { catch (...) {
WM_reportf(RPT_ERROR, "An exception occurred invoking USD hook '%s'", hook->name); BKE_reportf(
reports_, RPT_ERROR, "An exception occurred invoking USD hook '%s'", hook->name);
} }
} }
@ -225,6 +229,9 @@ class USDHookInvoker {
* *
* python::call_method<void>(hook_obj, function_name(), arg1, arg2); */ * python::call_method<void>(hook_obj, function_name(), arg1, arg2); */
virtual void call_hook(PyObject *hook_obj) const = 0; virtual void call_hook(PyObject *hook_obj) const = 0;
/* Reports list provided when constructing the subclass, used by #call() to store reports. */
ReportList *reports_;
}; };
class OnExportInvoker : public USDHookInvoker { class OnExportInvoker : public USDHookInvoker {
@ -232,9 +239,10 @@ class OnExportInvoker : public USDHookInvoker {
USDSceneExportContext hook_context_; USDSceneExportContext hook_context_;
public: public:
OnExportInvoker(pxr::UsdStageRefPtr stage, Depsgraph *depsgraph) OnExportInvoker(pxr::UsdStageRefPtr stage, Depsgraph *depsgraph, ReportList *reports)
: hook_context_(stage, depsgraph) : hook_context_(stage, depsgraph)
{ {
reports_ = reports;
} }
protected: protected:
@ -258,10 +266,12 @@ class OnMaterialExportInvoker : public USDHookInvoker {
public: public:
OnMaterialExportInvoker(pxr::UsdStageRefPtr stage, OnMaterialExportInvoker(pxr::UsdStageRefPtr stage,
Material *material, Material *material,
pxr::UsdShadeMaterial &usd_material) pxr::UsdShadeMaterial &usd_material,
ReportList *reports)
: hook_context_(stage), usd_material_(usd_material) : hook_context_(stage), usd_material_(usd_material)
{ {
material_ptr_ = RNA_pointer_create(nullptr, &RNA_Material, material); material_ptr_ = RNA_pointer_create(nullptr, &RNA_Material, material);
reports_ = reports;
} }
protected: protected:
@ -277,25 +287,26 @@ class OnMaterialExportInvoker : public USDHookInvoker {
} }
}; };
void call_export_hooks(pxr::UsdStageRefPtr stage, Depsgraph *depsgraph) void call_export_hooks(pxr::UsdStageRefPtr stage, Depsgraph *depsgraph, ReportList *reports)
{ {
if (g_usd_hooks.empty()) { if (g_usd_hooks.empty()) {
return; return;
} }
OnExportInvoker on_export(stage, depsgraph); OnExportInvoker on_export(stage, depsgraph, reports);
on_export.call(); on_export.call();
} }
void call_material_export_hooks(pxr::UsdStageRefPtr stage, void call_material_export_hooks(pxr::UsdStageRefPtr stage,
Material *material, Material *material,
pxr::UsdShadeMaterial &usd_material) pxr::UsdShadeMaterial &usd_material,
ReportList *reports)
{ {
if (g_usd_hooks.empty()) { if (g_usd_hooks.empty()) {
return; return;
} }
OnMaterialExportInvoker on_material_export(stage, material, usd_material); OnMaterialExportInvoker on_material_export(stage, material, usd_material, reports);
on_material_export.call(); on_material_export.call();
} }

View File

@ -11,6 +11,7 @@
struct Depsgraph; struct Depsgraph;
struct ExportJobData; struct ExportJobData;
struct Material; struct Material;
struct ReportList;
struct USDExportParams; struct USDExportParams;
namespace blender::io::usd { namespace blender::io::usd {
@ -19,11 +20,12 @@ namespace blender::io::usd {
void register_export_hook_converters(); void register_export_hook_converters();
/** Call the 'on_export' chaser function defined in the registered USDHook classes. */ /** Call the 'on_export' chaser function defined in the registered USDHook classes. */
void call_export_hooks(pxr::UsdStageRefPtr stage, Depsgraph *depsgraph); void call_export_hooks(pxr::UsdStageRefPtr stage, Depsgraph *depsgraph, ReportList *reports);
/** Call the 'on_material_export' hook functions defined in the registered #USDHook classes. */ /** Call the 'on_material_export' hook functions defined in the registered #USDHook classes. */
void call_material_export_hooks(pxr::UsdStageRefPtr stage, void call_material_export_hooks(pxr::UsdStageRefPtr stage,
Material *material, Material *material,
pxr::UsdShadeMaterial &usd_material); pxr::UsdShadeMaterial &usd_material,
ReportList *reports);
} // namespace blender::io::usd } // namespace blender::io::usd

View File

@ -760,7 +760,7 @@ void USDMaterialReader::load_tex_image(const pxr::UsdShadeShader &usd_shader,
USD_TEX_NAME_COLLISION_OVERWRITE : USD_TEX_NAME_COLLISION_OVERWRITE :
params_.tex_name_collision_mode; params_.tex_name_collision_mode;
file_path = import_asset(file_path.c_str(), textures_dir, name_collision_mode); file_path = import_asset(file_path.c_str(), textures_dir, name_collision_mode, reports());
} }
/* If this is a UDIM texture, this will store the /* If this is a UDIM texture, this will store the

View File

@ -5,6 +5,8 @@
#include "usd.h" #include "usd.h"
#include "WM_types.hh"
#include "BLI_map.hh" #include "BLI_map.hh"
#include <pxr/usd/usdShade/material.h> #include <pxr/usd/usdShade/material.h>
@ -86,6 +88,12 @@ class USDMaterialReader {
Material *add_material(const pxr::UsdShadeMaterial &usd_material) const; Material *add_material(const pxr::UsdShadeMaterial &usd_material) const;
/** Get the wmJobWorkerStatus-provided `reports` list pointer, to use with the BKE_report API. */
ReportList *reports() const
{
return params_.worker_status->reports;
}
protected: protected:
/** Create the Principled BSDF shader node network. */ /** Create the Principled BSDF shader node network. */
void import_usd_preview(Material *mtl, const pxr::UsdShadeShader &usd_shader) const; void import_usd_preview(Material *mtl, const pxr::UsdShadeShader &usd_shader) const;

View File

@ -15,6 +15,7 @@
#include "BKE_material.h" #include "BKE_material.h"
#include "BKE_mesh.hh" #include "BKE_mesh.hh"
#include "BKE_object.hh" #include "BKE_object.hh"
#include "BKE_report.h"
#include "BLI_math_color.hh" #include "BLI_math_color.hh"
#include "BLI_math_geom.h" #include "BLI_math_geom.h"
@ -165,7 +166,7 @@ USDMeshReader::USDMeshReader(const pxr::UsdPrim &prim,
} }
static std::optional<eCustomDataType> convert_usd_type_to_blender( static std::optional<eCustomDataType> convert_usd_type_to_blender(
const pxr::SdfValueTypeName usd_type) const pxr::SdfValueTypeName usd_type, ReportList *reports)
{ {
static const blender::Map<pxr::SdfValueTypeName, eCustomDataType> type_map = []() { static const blender::Map<pxr::SdfValueTypeName, eCustomDataType> type_map = []() {
blender::Map<pxr::SdfValueTypeName, eCustomDataType> map; blender::Map<pxr::SdfValueTypeName, eCustomDataType> map;
@ -194,7 +195,10 @@ static std::optional<eCustomDataType> convert_usd_type_to_blender(
const eCustomDataType *value = type_map.lookup_ptr(usd_type); const eCustomDataType *value = type_map.lookup_ptr(usd_type);
if (value == nullptr) { if (value == nullptr) {
WM_reportf(RPT_WARNING, "Unsupported type %s for mesh data", usd_type.GetAsToken().GetText()); BKE_reportf(reports,
RPT_WARNING,
"Unsupported type %s for mesh data",
usd_type.GetAsToken().GetText());
return std::nullopt; return std::nullopt;
} }
@ -202,7 +206,7 @@ static std::optional<eCustomDataType> convert_usd_type_to_blender(
} }
static const std::optional<eAttrDomain> convert_usd_varying_to_blender( static const std::optional<eAttrDomain> convert_usd_varying_to_blender(
const pxr::TfToken usd_domain) const pxr::TfToken usd_domain, ReportList *reports)
{ {
static const blender::Map<pxr::TfToken, eAttrDomain> domain_map = []() { static const blender::Map<pxr::TfToken, eAttrDomain> domain_map = []() {
blender::Map<pxr::TfToken, eAttrDomain> map; blender::Map<pxr::TfToken, eAttrDomain> map;
@ -221,7 +225,8 @@ static const std::optional<eAttrDomain> convert_usd_varying_to_blender(
const eAttrDomain *value = domain_map.lookup_ptr(usd_domain); const eAttrDomain *value = domain_map.lookup_ptr(usd_domain);
if (value == nullptr) { if (value == nullptr) {
WM_reportf(RPT_WARNING, "Unsupported domain for mesh data type %s", usd_domain.GetText()); BKE_reportf(
reports, RPT_WARNING, "Unsupported domain for mesh data type %s", usd_domain.GetText());
return std::nullopt; return std::nullopt;
} }
@ -271,11 +276,11 @@ void USDMeshReader::read_object_data(Main *bmain, const double motionSampleTime)
} }
if (import_params_.import_blendshapes) { if (import_params_.import_blendshapes) {
import_blendshapes(bmain, object_, prim_); import_blendshapes(bmain, object_, prim_, reports());
} }
if (import_params_.import_skeletons) { if (import_params_.import_skeletons) {
import_mesh_skel_bindings(bmain, object_, prim_); import_mesh_skel_bindings(bmain, object_, prim_, reports());
} }
USDXformReader::read_object_data(bmain, motionSampleTime); USDXformReader::read_object_data(bmain, motionSampleTime);
@ -349,20 +354,24 @@ void USDMeshReader::read_mpolys(Mesh *mesh)
template<typename T> template<typename T>
pxr::VtArray<T> get_prim_attribute_array(const pxr::UsdGeomPrimvar &primvar, pxr::VtArray<T> get_prim_attribute_array(const pxr::UsdGeomPrimvar &primvar,
const double motionSampleTime) const double motionSampleTime,
ReportList *reports)
{ {
pxr::VtArray<T> array; pxr::VtArray<T> array;
pxr::VtValue primvar_val; pxr::VtValue primvar_val;
if (!primvar.ComputeFlattened(&primvar_val, motionSampleTime)) { if (!primvar.ComputeFlattened(&primvar_val, motionSampleTime)) {
WM_reportf( BKE_reportf(reports,
RPT_WARNING, "Unable to get array values for primvar %s", primvar.GetName().GetText()); RPT_WARNING,
"Unable to get array values for primvar %s",
primvar.GetName().GetText());
return array; return array;
} }
if (!primvar_val.CanCast<pxr::VtArray<T>>()) { if (!primvar_val.CanCast<pxr::VtArray<T>>()) {
WM_reportf(RPT_WARNING, BKE_reportf(reports,
RPT_WARNING,
"USD Import: can't cast attribute '%s' to array", "USD Import: can't cast attribute '%s' to array",
primvar.GetName().GetText()); primvar.GetName().GetText());
return array; return array;
@ -380,8 +389,8 @@ void USDMeshReader::read_color_data_primvar(Mesh *mesh,
return; return;
} }
pxr::VtArray<pxr::GfVec3f> usd_colors = get_prim_attribute_array<pxr::GfVec3f>(primvar, pxr::VtArray<pxr::GfVec3f> usd_colors = get_prim_attribute_array<pxr::GfVec3f>(
motionSampleTime); primvar, motionSampleTime, reports());
if (usd_colors.empty()) { if (usd_colors.empty()) {
return; return;
@ -395,7 +404,9 @@ void USDMeshReader::read_color_data_primvar(Mesh *mesh,
(interp == pxr::UsdGeomTokens->constant && usd_colors.size() != 1) || (interp == pxr::UsdGeomTokens->constant && usd_colors.size() != 1) ||
(interp == pxr::UsdGeomTokens->uniform && usd_colors.size() != mesh->faces_num)) (interp == pxr::UsdGeomTokens->uniform && usd_colors.size() != mesh->faces_num))
{ {
WM_reportf(RPT_WARNING, BKE_reportf(
reports(),
RPT_WARNING,
"USD Import: color attribute value '%s' count inconsistent with interpolation type", "USD Import: color attribute value '%s' count inconsistent with interpolation type",
primvar.GetName().GetText()); primvar.GetName().GetText());
return; return;
@ -418,7 +429,8 @@ void USDMeshReader::read_color_data_primvar(Mesh *mesh,
color_data = attributes.lookup_or_add_for_write_only_span<ColorGeometry4f>(primvar_name, color_data = attributes.lookup_or_add_for_write_only_span<ColorGeometry4f>(primvar_name,
color_domain); color_domain);
if (!color_data) { if (!color_data) {
WM_reportf(RPT_WARNING, BKE_reportf(reports(),
RPT_WARNING,
"USD Import: couldn't add color attribute '%s'", "USD Import: couldn't add color attribute '%s'",
primvar.GetBaseName().GetText()); primvar.GetBaseName().GetText());
return; return;
@ -494,8 +506,8 @@ void USDMeshReader::read_uv_data_primvar(Mesh *mesh,
{ {
const StringRef primvar_name(primvar.StripPrimvarsName(primvar.GetName()).GetString()); const StringRef primvar_name(primvar.StripPrimvarsName(primvar.GetName()).GetString());
pxr::VtArray<pxr::GfVec2f> usd_uvs = get_prim_attribute_array<pxr::GfVec2f>(primvar, pxr::VtArray<pxr::GfVec2f> usd_uvs = get_prim_attribute_array<pxr::GfVec2f>(
motionSampleTime); primvar, motionSampleTime, reports());
if (usd_uvs.empty()) { if (usd_uvs.empty()) {
return; return;
@ -511,7 +523,8 @@ void USDMeshReader::read_uv_data_primvar(Mesh *mesh,
(varying_type == pxr::UsdGeomTokens->vertex && usd_uvs.size() != mesh->totvert) || (varying_type == pxr::UsdGeomTokens->vertex && usd_uvs.size() != mesh->totvert) ||
(varying_type == pxr::UsdGeomTokens->varying && usd_uvs.size() != mesh->totloop)) (varying_type == pxr::UsdGeomTokens->varying && usd_uvs.size() != mesh->totloop))
{ {
WM_reportf(RPT_WARNING, BKE_reportf(reports(),
RPT_WARNING,
"USD Import: UV attribute value '%s' count inconsistent with interpolation type", "USD Import: UV attribute value '%s' count inconsistent with interpolation type",
primvar.GetName().GetText()); primvar.GetName().GetText());
return; return;
@ -522,7 +535,8 @@ void USDMeshReader::read_uv_data_primvar(Mesh *mesh,
primvar_name, ATTR_DOMAIN_CORNER); primvar_name, ATTR_DOMAIN_CORNER);
if (!uv_data) { if (!uv_data) {
WM_reportf(RPT_WARNING, BKE_reportf(reports(),
RPT_WARNING,
"USD Import: couldn't add UV attribute '%s'", "USD Import: couldn't add UV attribute '%s'",
primvar.GetBaseName().GetText()); primvar.GetBaseName().GetText());
return; return;
@ -588,10 +602,13 @@ void USDMeshReader::copy_prim_array_to_blender_attribute(const Mesh *mesh,
MutableSpan<BlenderT> attribute) MutableSpan<BlenderT> attribute)
{ {
const pxr::TfToken interp = primvar.GetInterpolation(); const pxr::TfToken interp = primvar.GetInterpolation();
pxr::VtArray<USDT> primvar_array = get_prim_attribute_array<USDT>(primvar, motionSampleTime); pxr::VtArray<USDT> primvar_array = get_prim_attribute_array<USDT>(
primvar, motionSampleTime, reports());
if (primvar_array.empty()) { if (primvar_array.empty()) {
WM_reportf( BKE_reportf(reports(),
RPT_WARNING, "Unable to get array values for primvar %s", primvar.GetName().GetText()); RPT_WARNING,
"Unable to get array values for primvar %s",
primvar.GetName().GetText());
return; return;
} }
@ -642,8 +659,9 @@ void USDMeshReader::read_generic_data_primvar(Mesh *mesh,
const pxr::TfToken varying_type = primvar.GetInterpolation(); const pxr::TfToken varying_type = primvar.GetInterpolation();
const pxr::TfToken name = pxr::UsdGeomPrimvar::StripPrimvarsName(primvar.GetPrimvarName()); const pxr::TfToken name = pxr::UsdGeomPrimvar::StripPrimvarsName(primvar.GetPrimvarName());
const std::optional<eAttrDomain> domain = convert_usd_varying_to_blender(varying_type); const std::optional<eAttrDomain> domain = convert_usd_varying_to_blender(varying_type,
const std::optional<eCustomDataType> type = convert_usd_type_to_blender(sdf_type); reports());
const std::optional<eCustomDataType> type = convert_usd_type_to_blender(sdf_type, reports());
if (!domain.has_value() || !type.has_value()) { if (!domain.has_value() || !type.has_value()) {
return; return;
@ -678,7 +696,8 @@ void USDMeshReader::read_generic_data_primvar(Mesh *mesh,
mesh, primvar, motionSampleTime, attribute.span.typed<bool>()); mesh, primvar, motionSampleTime, attribute.span.typed<bool>());
break; break;
default: default:
WM_reportf(RPT_ERROR, BKE_reportf(reports(),
RPT_ERROR,
"Generic primvar %s: invalid type %s", "Generic primvar %s: invalid type %s",
primvar.GetName().GetText(), primvar.GetName().GetText(),
sdf_type.GetAsToken().GetText()); sdf_type.GetAsToken().GetText());
@ -878,7 +897,8 @@ void USDMeshReader::read_custom_data(const ImportSettings *settings,
/* Convert primvars to custom layer data. */ /* Convert primvars to custom layer data. */
for (pxr::UsdGeomPrimvar &pv : primvars) { for (pxr::UsdGeomPrimvar &pv : primvars) {
if (!pv.HasValue()) { if (!pv.HasValue()) {
WM_reportf(RPT_WARNING, BKE_reportf(reports(),
RPT_WARNING,
"Skipping primvar %s, mesh %s -- no value", "Skipping primvar %s, mesh %s -- no value",
pv.GetName().GetText(), pv.GetName().GetText(),
&mesh->id.name[2]); &mesh->id.name[2]);
@ -898,7 +918,7 @@ void USDMeshReader::read_custom_data(const ImportSettings *settings,
} }
/* Read Color primvars. */ /* Read Color primvars. */
if (convert_usd_type_to_blender(type) == CD_PROP_COLOR) { if (convert_usd_type_to_blender(type, reports()) == CD_PROP_COLOR) {
if ((settings->read_flag & MOD_MESHSEQ_READ_COLOR) != 0) { if ((settings->read_flag & MOD_MESHSEQ_READ_COLOR) != 0) {
/* Set the active color name to 'displayColor', if a color primvar /* Set the active color name to 'displayColor', if a color primvar
* with this name exists. Otherwise, use the name of the first * with this name exists. Otherwise, use the name of the first
@ -916,7 +936,7 @@ void USDMeshReader::read_custom_data(const ImportSettings *settings,
pxr::UsdGeomTokens->vertex, pxr::UsdGeomTokens->vertex,
pxr::UsdGeomTokens->faceVarying, pxr::UsdGeomTokens->faceVarying,
pxr::UsdGeomTokens->varying) && pxr::UsdGeomTokens->varying) &&
convert_usd_type_to_blender(type) == CD_PROP_FLOAT2) convert_usd_type_to_blender(type, reports()) == CD_PROP_FLOAT2)
{ {
if ((settings->read_flag & MOD_MESHSEQ_READ_UV) != 0) { if ((settings->read_flag & MOD_MESHSEQ_READ_UV) != 0) {
/* Set the active uv set name to 'st', if a uv set primvar /* Set the active uv set name to 'st', if a uv set primvar
@ -1139,7 +1159,8 @@ std::optional<XformResult> USDMeshReader::get_local_usd_xform(const float time)
return XformResult(pxr::GfMatrix4f(bind_xf), true); return XformResult(pxr::GfMatrix4f(bind_xf), true);
} }
else { else {
WM_reportf(RPT_WARNING, BKE_reportf(reports(),
RPT_WARNING,
"%s: Couldn't compute geom bind transform for %s", "%s: Couldn't compute geom bind transform for %s",
__func__, __func__,
prim_.GetPath().GetAsString().c_str()); prim_.GetPath().GetAsString().c_str());

View File

@ -9,6 +9,8 @@
#include "usd.h" #include "usd.h"
#include "WM_types.hh"
#include <pxr/usd/usd/prim.h> #include <pxr/usd/usd/prim.h>
#include <map> #include <map>
@ -113,6 +115,12 @@ class USDPrimReader {
parent_reader_ = parent; parent_reader_ = parent;
} }
/** Get the wmJobWorkerStatus-provided `reports` list pointer, to use with the BKE_report API. */
ReportList *reports() const
{
return import_params_.worker_status->reports;
}
/* Since readers might be referenced through handles /* Since readers might be referenced through handles
* maintained by modifiers and constraints, we provide * maintained by modifiers and constraints, we provide
* a reference count to facilitate managing the object * a reference count to facilitate managing the object

View File

@ -6,6 +6,7 @@
#include "BKE_mesh.hh" #include "BKE_mesh.hh"
#include "BKE_modifier.h" #include "BKE_modifier.h"
#include "BKE_object.hh" #include "BKE_object.hh"
#include "BKE_report.h"
#include "DNA_cachefile_types.h" #include "DNA_cachefile_types.h"
#include "DNA_mesh_types.h" #include "DNA_mesh_types.h"
@ -118,7 +119,8 @@ bool USDShapeReader::read_mesh_values(double motionSampleTime,
return true; return true;
} }
WM_reportf(RPT_ERROR, BKE_reportf(reports(),
RPT_ERROR,
"Unhandled Gprim type: %s (%s)", "Unhandled Gprim type: %s (%s)",
prim_.GetTypeName().GetText(), prim_.GetTypeName().GetText(),
prim_.GetPath().GetText()); prim_.GetPath().GetText());
@ -230,7 +232,8 @@ bool USDShapeReader::is_time_varying()
return geom.GetRadiusAttr().ValueMightBeTimeVarying(); return geom.GetRadiusAttr().ValueMightBeTimeVarying();
} }
WM_reportf(RPT_ERROR, BKE_reportf(reports(),
RPT_ERROR,
"Unhandled Gprim type: %s (%s)", "Unhandled Gprim type: %s (%s)",
prim_.GetTypeName().GetText(), prim_.GetTypeName().GetText(),
prim_.GetPath().GetText()); prim_.GetPath().GetText());

View File

@ -39,7 +39,7 @@ void USDSkeletonReader::read_object_data(Main *bmain, const double motionSampleT
return; return;
} }
import_skeleton(bmain, object_, skel_); import_skeleton(bmain, object_, skel_, reports());
USDXformReader::read_object_data(bmain, motionSampleTime); USDXformReader::read_object_data(bmain, motionSampleTime);
} }

View File

@ -44,6 +44,7 @@
#include "BKE_lib_id.h" #include "BKE_lib_id.h"
#include "BKE_modifier.h" #include "BKE_modifier.h"
#include "BKE_report.h"
#include "DNA_material_types.h" #include "DNA_material_types.h"
@ -366,7 +367,8 @@ void USDStageReader::process_armature_modifiers() const
std::string skel_path = mesh_reader->get_skeleton_path(); std::string skel_path = mesh_reader->get_skeleton_path();
std::map<std::string, Object *>::const_iterator it = usd_path_to_armature.find(skel_path); std::map<std::string, Object *>::const_iterator it = usd_path_to_armature.find(skel_path);
if (it == usd_path_to_armature.end()) { if (it == usd_path_to_armature.end()) {
WM_reportf(RPT_WARNING, BKE_reportf(reports(),
RPT_WARNING,
"%s: Couldn't find armature object corresponding to USD skeleton %s", "%s: Couldn't find armature object corresponding to USD skeleton %s",
__func__, __func__,
skel_path.c_str()); skel_path.c_str());

View File

@ -5,6 +5,8 @@
struct Main; struct Main;
#include "WM_types.hh"
#include "usd.h" #include "usd.h"
#include "usd_reader_prim.h" #include "usd_reader_prim.h"
@ -79,6 +81,12 @@ class USDStageReader {
return settings_; return settings_;
} }
/** Get the wmJobWorkerStatus-provided `reports` list pointer, to use with the BKE_report API. */
ReportList *reports() const
{
return params_.worker_status->reports;
}
void clear_readers(); void clear_readers();
const std::vector<USDPrimReader *> &readers() const const std::vector<USDPrimReader *> &readers() const

View File

@ -32,6 +32,7 @@
#include "BKE_modifier.h" #include "BKE_modifier.h"
#include "BKE_object.hh" #include "BKE_object.hh"
#include "BKE_object_deform.h" #include "BKE_object_deform.h"
#include "BKE_report.h"
#include "BLI_math_vector.h" #include "BLI_math_vector.h"
#include "BLI_string.h" #include "BLI_string.h"
@ -103,11 +104,14 @@ void add_bezt(FCurve *fcu,
* \param arm_obj: Armature object to which the action will be added * \param arm_obj: Armature object to which the action will be added
* \param skel_query: The USD skeleton query for reading the animation * \param skel_query: The USD skeleton query for reading the animation
* \param joint_to_bone_map: Map a USD skeleton joint name to a bone name * \param joint_to_bone_map: Map a USD skeleton joint name to a bone name
* \param reports: the storage for potential warning or error reports (generated using BKE_report
* API).
*/ */
void import_skeleton_curves(Main *bmain, void import_skeleton_curves(Main *bmain,
Object *arm_obj, Object *arm_obj,
const pxr::UsdSkelSkeletonQuery &skel_query, const pxr::UsdSkelSkeletonQuery &skel_query,
const std::map<pxr::TfToken, std::string> &joint_to_bone_map) const std::map<pxr::TfToken, std::string> &joint_to_bone_map,
ReportList *reports)
{ {
if (!(bmain && arm_obj && skel_query)) { if (!(bmain && arm_obj && skel_query)) {
@ -218,7 +222,8 @@ void import_skeleton_curves(Main *bmain,
/* Get the world space joint transforms at bind time. */ /* Get the world space joint transforms at bind time. */
pxr::VtMatrix4dArray bind_xforms; pxr::VtMatrix4dArray bind_xforms;
if (!skel_query.GetJointWorldBindTransforms(&bind_xforms)) { if (!skel_query.GetJointWorldBindTransforms(&bind_xforms)) {
WM_reportf(RPT_WARNING, BKE_reportf(reports,
RPT_WARNING,
"%s: Couldn't get world bind transforms for skeleton %s", "%s: Couldn't get world bind transforms for skeleton %s",
__func__, __func__,
skel_query.GetSkeleton().GetPrim().GetPath().GetAsString().c_str()); skel_query.GetSkeleton().GetPrim().GetPath().GetAsString().c_str());
@ -226,7 +231,8 @@ void import_skeleton_curves(Main *bmain,
} }
if (bind_xforms.size() != joint_order.size()) { if (bind_xforms.size() != joint_order.size()) {
WM_reportf(RPT_WARNING, BKE_reportf(reports,
RPT_WARNING,
"%s: Number of bind transforms doesn't match the number of joints for skeleton %s", "%s: Number of bind transforms doesn't match the number of joints for skeleton %s",
__func__, __func__,
skel_query.GetSkeleton().GetPrim().GetPath().GetAsString().c_str()); skel_query.GetSkeleton().GetPrim().GetPath().GetAsString().c_str());
@ -334,6 +340,7 @@ namespace blender::io::usd {
void import_blendshapes(Main *bmain, void import_blendshapes(Main *bmain,
Object *mesh_obj, Object *mesh_obj,
const pxr::UsdPrim &prim, const pxr::UsdPrim &prim,
ReportList *reports,
const bool import_anim) const bool import_anim)
{ {
if (!(mesh_obj && mesh_obj->data && mesh_obj->type == OB_MESH && prim)) { if (!(mesh_obj && mesh_obj->data && mesh_obj->type == OB_MESH && prim)) {
@ -363,7 +370,8 @@ void import_blendshapes(Main *bmain,
pxr::SdfPathVector targets; pxr::SdfPathVector targets;
if (!skel_api.GetBlendShapeTargetsRel().GetTargets(&targets)) { if (!skel_api.GetBlendShapeTargetsRel().GetTargets(&targets)) {
WM_reportf(RPT_WARNING, BKE_reportf(reports,
RPT_WARNING,
"%s: Couldn't get blendshape targets for prim %s", "%s: Couldn't get blendshape targets for prim %s",
__func__, __func__,
prim.GetPath().GetAsString().c_str()); prim.GetPath().GetAsString().c_str());
@ -390,7 +398,8 @@ void import_blendshapes(Main *bmain,
/* Sanity check. */ /* Sanity check. */
if (targets.size() != blendshapes.size()) { if (targets.size() != blendshapes.size()) {
WM_reportf(RPT_WARNING, BKE_reportf(reports,
RPT_WARNING,
"%s: Number of blendshapes doesn't match number of blendshape targets for prim %s", "%s: Number of blendshapes doesn't match number of blendshape targets for prim %s",
__func__, __func__,
prim.GetPath().GetAsString().c_str()); prim.GetPath().GetAsString().c_str());
@ -400,7 +409,8 @@ void import_blendshapes(Main *bmain,
pxr::UsdStageRefPtr stage = prim.GetStage(); pxr::UsdStageRefPtr stage = prim.GetStage();
if (!stage) { if (!stage) {
WM_reportf(RPT_WARNING, BKE_reportf(reports,
RPT_WARNING,
"%s: Couldn't get stage for prim %s", "%s: Couldn't get stage for prim %s",
__func__, __func__,
prim.GetPath().GetAsString().c_str()); prim.GetPath().GetAsString().c_str());
@ -440,7 +450,8 @@ void import_blendshapes(Main *bmain,
pxr::VtVec3fArray offsets; pxr::VtVec3fArray offsets;
if (!blendshape.GetOffsetsAttr().Get(&offsets)) { if (!blendshape.GetOffsetsAttr().Get(&offsets)) {
WM_reportf(RPT_WARNING, BKE_reportf(reports,
RPT_WARNING,
"%s: Couldn't get offsets for blend shape %s", "%s: Couldn't get offsets for blend shape %s",
__func__, __func__,
path.GetAsString().c_str()); path.GetAsString().c_str());
@ -448,8 +459,11 @@ void import_blendshapes(Main *bmain,
} }
if (offsets.empty()) { if (offsets.empty()) {
WM_reportf( BKE_reportf(reports,
RPT_WARNING, "%s: No offsets for blend shape %s", __func__, path.GetAsString().c_str()); RPT_WARNING,
"%s: No offsets for blend shape %s",
__func__,
path.GetAsString().c_str());
continue; continue;
} }
@ -473,7 +487,8 @@ void import_blendshapes(Main *bmain,
* offset to the key block point. */ * offset to the key block point. */
for (int a = 0; a < kb->totelem; ++a, fp += 3) { for (int a = 0; a < kb->totelem; ++a, fp += 3) {
if (a >= offsets.size()) { if (a >= offsets.size()) {
WM_reportf( BKE_reportf(
reports,
RPT_WARNING, RPT_WARNING,
"%s: Number of offsets greater than number of mesh vertices for blend shape %s", "%s: Number of offsets greater than number of mesh vertices for blend shape %s",
__func__, __func__,
@ -495,7 +510,8 @@ void import_blendshapes(Main *bmain,
continue; continue;
} }
if (a >= offsets.size()) { if (a >= offsets.size()) {
WM_reportf( BKE_reportf(
reports,
RPT_WARNING, RPT_WARNING,
"%s: Number of offsets greater than number of mesh vertices for blend shape %s", "%s: Number of offsets greater than number of mesh vertices for blend shape %s",
__func__, __func__,
@ -621,6 +637,7 @@ void import_blendshapes(Main *bmain,
void import_skeleton(Main *bmain, void import_skeleton(Main *bmain,
Object *arm_obj, Object *arm_obj,
const pxr::UsdSkelSkeleton &skel, const pxr::UsdSkelSkeleton &skel,
ReportList *reports,
const bool import_anim) const bool import_anim)
{ {
if (!(arm_obj && arm_obj->data && arm_obj->type == OB_ARMATURE)) { if (!(arm_obj && arm_obj->data && arm_obj->type == OB_ARMATURE)) {
@ -631,7 +648,8 @@ void import_skeleton(Main *bmain,
pxr::UsdSkelSkeletonQuery skel_query = skel_cache.GetSkelQuery(skel); pxr::UsdSkelSkeletonQuery skel_query = skel_cache.GetSkelQuery(skel);
if (!skel_query.IsValid()) { if (!skel_query.IsValid()) {
WM_reportf(RPT_WARNING, BKE_reportf(reports,
RPT_WARNING,
"%s: Couldn't query skeleton %s", "%s: Couldn't query skeleton %s",
__func__, __func__,
skel.GetPath().GetAsString().c_str()); skel.GetPath().GetAsString().c_str());
@ -643,7 +661,8 @@ void import_skeleton(Main *bmain,
pxr::VtTokenArray joint_order = skel_query.GetJointOrder(); pxr::VtTokenArray joint_order = skel_query.GetJointOrder();
if (joint_order.size() != skel_topology.size()) { if (joint_order.size() != skel_topology.size()) {
WM_reportf(RPT_WARNING, BKE_reportf(reports,
RPT_WARNING,
"%s: Topology and joint order size mismatch for skeleton %s", "%s: Topology and joint order size mismatch for skeleton %s",
__func__, __func__,
skel.GetPath().GetAsString().c_str()); skel.GetPath().GetAsString().c_str());
@ -668,8 +687,11 @@ void import_skeleton(Main *bmain,
std::string name = pxr::SdfPath(joint).GetName(); std::string name = pxr::SdfPath(joint).GetName();
EditBone *bone = ED_armature_ebone_add(arm, name.c_str()); EditBone *bone = ED_armature_ebone_add(arm, name.c_str());
if (!bone) { if (!bone) {
WM_reportf( BKE_reportf(reports,
RPT_WARNING, "%s: Couldn't add bone for joint %s", __func__, joint.GetString().c_str()); RPT_WARNING,
"%s: Couldn't add bone for joint %s",
__func__,
joint.GetString().c_str());
edit_bones.push_back(nullptr); edit_bones.push_back(nullptr);
continue; continue;
} }
@ -680,7 +702,8 @@ void import_skeleton(Main *bmain,
/* Sanity check: we should have created a bone for each joint. */ /* Sanity check: we should have created a bone for each joint. */
const size_t num_joints = skel_topology.GetNumJoints(); const size_t num_joints = skel_topology.GetNumJoints();
if (edit_bones.size() != num_joints) { if (edit_bones.size() != num_joints) {
WM_reportf(RPT_WARNING, BKE_reportf(reports,
RPT_WARNING,
"%s: Mismatch in bone and joint counts for skeleton %s", "%s: Mismatch in bone and joint counts for skeleton %s",
__func__, __func__,
skel.GetPath().GetAsString().c_str()); skel.GetPath().GetAsString().c_str());
@ -690,7 +713,8 @@ void import_skeleton(Main *bmain,
/* Get the world space joint transforms at bind time. */ /* Get the world space joint transforms at bind time. */
pxr::VtMatrix4dArray bind_xforms; pxr::VtMatrix4dArray bind_xforms;
if (!skel_query.GetJointWorldBindTransforms(&bind_xforms)) { if (!skel_query.GetJointWorldBindTransforms(&bind_xforms)) {
WM_reportf(RPT_WARNING, BKE_reportf(reports,
RPT_WARNING,
"%s: Couldn't get world bind transforms for skeleton %s", "%s: Couldn't get world bind transforms for skeleton %s",
__func__, __func__,
skel.GetPath().GetAsString().c_str()); skel.GetPath().GetAsString().c_str());
@ -698,7 +722,8 @@ void import_skeleton(Main *bmain,
} }
if (bind_xforms.size() != num_joints) { if (bind_xforms.size() != num_joints) {
WM_reportf(RPT_WARNING, BKE_reportf(reports,
RPT_WARNING,
"%s: Mismatch in bind xforms and joint counts for skeleton %s", "%s: Mismatch in bind xforms and joint counts for skeleton %s",
__func__, __func__,
skel.GetPath().GetAsString().c_str()); skel.GetPath().GetAsString().c_str());
@ -746,7 +771,8 @@ void import_skeleton(Main *bmain,
bool valid_skeleton = true; bool valid_skeleton = true;
if (negative_determinant) { if (negative_determinant) {
valid_skeleton = false; valid_skeleton = false;
WM_reportf( BKE_reportf(
reports,
RPT_WARNING, RPT_WARNING,
"USD Skeleton Import: bone matrices with negative determinants detected in prim %s. " "USD Skeleton Import: bone matrices with negative determinants detected in prim %s. "
"Such matrices may indicate negative scales, possibly due to mirroring operations, " "Such matrices may indicate negative scales, possibly due to mirroring operations, "
@ -850,11 +876,14 @@ void import_skeleton(Main *bmain,
ED_armature_edit_free(arm); ED_armature_edit_free(arm);
if (import_anim && valid_skeleton) { if (import_anim && valid_skeleton) {
import_skeleton_curves(bmain, arm_obj, skel_query, joint_to_bone_map); import_skeleton_curves(bmain, arm_obj, skel_query, joint_to_bone_map, reports);
} }
} }
void import_mesh_skel_bindings(Main *bmain, Object *mesh_obj, const pxr::UsdPrim &prim) void import_mesh_skel_bindings(Main *bmain,
Object *mesh_obj,
const pxr::UsdPrim &prim,
ReportList *reports)
{ {
if (!(bmain && mesh_obj && mesh_obj->type == OB_MESH && prim)) { if (!(bmain && mesh_obj && mesh_obj->type == OB_MESH && prim)) {
return; return;
@ -912,7 +941,8 @@ void import_mesh_skel_bindings(Main *bmain, Object *mesh_obj, const pxr::UsdPrim
/* We expect the element counts to match. */ /* We expect the element counts to match. */
if (joint_indices_elem_size != joint_weights_elem_size) { if (joint_indices_elem_size != joint_weights_elem_size) {
WM_reportf(RPT_WARNING, BKE_reportf(reports,
RPT_WARNING,
"%s: Joint weights and joint indices element size mismatch for prim %s", "%s: Joint weights and joint indices element size mismatch for prim %s",
__func__, __func__,
prim.GetPath().GetAsString().c_str()); prim.GetPath().GetAsString().c_str());
@ -931,7 +961,8 @@ void import_mesh_skel_bindings(Main *bmain, Object *mesh_obj, const pxr::UsdPrim
} }
if (joint_indices.size() != joint_weights.size()) { if (joint_indices.size() != joint_weights.size()) {
WM_reportf(RPT_WARNING, BKE_reportf(reports,
RPT_WARNING,
"%s: Joint weights and joint indices size mismatch for prim %s", "%s: Joint weights and joint indices size mismatch for prim %s",
__func__, __func__,
prim.GetPath().GetAsString().c_str()); prim.GetPath().GetAsString().c_str());
@ -944,7 +975,8 @@ void import_mesh_skel_bindings(Main *bmain, Object *mesh_obj, const pxr::UsdPrim
/* Sanity check: we expect only vertex or constant interpolation. */ /* Sanity check: we expect only vertex or constant interpolation. */
if (!ELEM(interp, pxr::UsdGeomTokens->vertex, pxr::UsdGeomTokens->constant)) { if (!ELEM(interp, pxr::UsdGeomTokens->vertex, pxr::UsdGeomTokens->constant)) {
WM_reportf(RPT_WARNING, BKE_reportf(reports,
RPT_WARNING,
"%s: Unexpected joint weights interpolation type %s for prim %s", "%s: Unexpected joint weights interpolation type %s for prim %s",
__func__, __func__,
interp.GetString().c_str(), interp.GetString().c_str(),
@ -956,7 +988,8 @@ void import_mesh_skel_bindings(Main *bmain, Object *mesh_obj, const pxr::UsdPrim
if (interp == pxr::UsdGeomTokens->vertex && if (interp == pxr::UsdGeomTokens->vertex &&
joint_weights.size() != mesh->totvert * joint_weights_elem_size) joint_weights.size() != mesh->totvert * joint_weights_elem_size)
{ {
WM_reportf(RPT_WARNING, BKE_reportf(reports,
RPT_WARNING,
"%s: Joint weights of unexpected size for vertex interpolation for prim %s", "%s: Joint weights of unexpected size for vertex interpolation for prim %s",
__func__, __func__,
prim.GetPath().GetAsString().c_str()); prim.GetPath().GetAsString().c_str());
@ -964,7 +997,8 @@ void import_mesh_skel_bindings(Main *bmain, Object *mesh_obj, const pxr::UsdPrim
} }
if (interp == pxr::UsdGeomTokens->constant && joint_weights.size() != joint_weights_elem_size) { if (interp == pxr::UsdGeomTokens->constant && joint_weights.size() != joint_weights_elem_size) {
WM_reportf(RPT_WARNING, BKE_reportf(reports,
RPT_WARNING,
"%s: Joint weights of unexpected size for constant interpolation for prim %s", "%s: Joint weights of unexpected size for constant interpolation for prim %s",
__func__, __func__,
prim.GetPath().GetAsString().c_str()); prim.GetPath().GetAsString().c_str());
@ -989,7 +1023,8 @@ void import_mesh_skel_bindings(Main *bmain, Object *mesh_obj, const pxr::UsdPrim
} }
if (BKE_object_defgroup_data_create(static_cast<ID *>(mesh_obj->data)) == nullptr) { if (BKE_object_defgroup_data_create(static_cast<ID *>(mesh_obj->data)) == nullptr) {
WM_reportf(RPT_WARNING, BKE_reportf(reports,
RPT_WARNING,
"%s: Error creating deform group data for mesh %s", "%s: Error creating deform group data for mesh %s",
__func__, __func__,
mesh_obj->id.name + 2); mesh_obj->id.name + 2);

View File

@ -3,6 +3,7 @@
* SPDX-License-Identifier: GPL-2.0-or-later */ * SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once #pragma once
#include "DNA_windowmanager_types.h"
#include <map> #include <map>
#include <pxr/usd/usd/prim.h> #include <pxr/usd/usd/prim.h>
#include <pxr/usd/usdSkel/skeletonQuery.h> #include <pxr/usd/usdSkel/skeletonQuery.h>
@ -35,12 +36,15 @@ struct ImportSettings;
* \param bmain: Main pointer * \param bmain: Main pointer
* \param mesh_obj: Mesh object to which imported shape keys will be added * \param mesh_obj: Mesh object to which imported shape keys will be added
* \param prim: The USD primitive from which blend-shapes will be imported * \param prim: The USD primitive from which blend-shapes will be imported
* \param reports: the storage for potential warning or error reports (generated using BKE_report
* API).
* \param import_anim: Whether to import time-sampled weights as shape key * \param import_anim: Whether to import time-sampled weights as shape key
* animation curves * animation curves
*/ */
void import_blendshapes(Main *bmain, void import_blendshapes(Main *bmain,
Object *mesh_obj, Object *mesh_obj,
const pxr::UsdPrim &prim, const pxr::UsdPrim &prim,
ReportList *reports,
bool import_anim = true); bool import_anim = true);
/** /**
@ -51,12 +55,15 @@ void import_blendshapes(Main *bmain,
* \param bmain: Main pointer * \param bmain: Main pointer
* \param arm_obj: Armature object to which the bone hierarchy will be added * \param arm_obj: Armature object to which the bone hierarchy will be added
* \param skel: The USD skeleton from which bones and animation will be imported * \param skel: The USD skeleton from which bones and animation will be imported
* \param reports: the storage for potential warning or error reports (generated using BKE_report
* API).
* \param import_anim: Whether to import time-sampled joint transforms as bone * \param import_anim: Whether to import time-sampled joint transforms as bone
* animation curves * animation curves
*/ */
void import_skeleton(Main *bmain, void import_skeleton(Main *bmain,
Object *arm_obj, Object *arm_obj,
const pxr::UsdSkelSkeleton &skel, const pxr::UsdSkelSkeleton &skel,
ReportList *reports,
bool import_anim = true); bool import_anim = true);
/** /**
* Import skinning data from a source USD prim as deform groups and an armature * Import skinning data from a source USD prim as deform groups and an armature
@ -66,7 +73,12 @@ void import_skeleton(Main *bmain,
* \param bmain: Main pointer * \param bmain: Main pointer
* \param obj: Mesh object to which an armature modifier will be added * \param obj: Mesh object to which an armature modifier will be added
* \param prim: The USD primitive from which skinning data will be imported * \param prim: The USD primitive from which skinning data will be imported
* \param reports: the storage for potential warning or error reports (generated using BKE_report
* API).
*/ */
void import_mesh_skel_bindings(Main *bmain, Object *mesh_obj, const pxr::UsdPrim &prim); void import_mesh_skel_bindings(Main *bmain,
Object *mesh_obj,
const pxr::UsdPrim &prim,
ReportList *reports);
} // namespace blender::io::usd } // namespace blender::io::usd

View File

@ -9,6 +9,8 @@
#include <pxr/usd/usdGeom/bboxCache.h> #include <pxr/usd/usdGeom/bboxCache.h>
#include "BKE_customdata.h" #include "BKE_customdata.h"
#include "BKE_report.h"
#include "BLI_assert.h" #include "BLI_assert.h"
#include "DNA_mesh_types.h" #include "DNA_mesh_types.h"
@ -167,7 +169,8 @@ void USDAbstractWriter::author_extent(const pxr::UsdTimeCode timecode, pxr::UsdG
pxr::GfBBox3d bounds = bboxCache.ComputeLocalBound(prim.GetPrim()); pxr::GfBBox3d bounds = bboxCache.ComputeLocalBound(prim.GetPrim());
if (pxr::GfBBox3d() == bounds) { if (pxr::GfBBox3d() == bounds) {
/* This will occur, for example, if a mesh does not have any vertices. */ /* This will occur, for example, if a mesh does not have any vertices. */
WM_reportf(RPT_WARNING, BKE_reportf(reports(),
RPT_WARNING,
"USD Export: no bounds could be computed for %s", "USD Export: no bounds could be computed for %s",
prim.GetPrim().GetName().GetText()); prim.GetPrim().GetName().GetText());
return; return;

View File

@ -16,7 +16,10 @@
#include "DEG_depsgraph_query.hh" #include "DEG_depsgraph_query.hh"
#include "WM_types.hh"
#include "DNA_material_types.h" #include "DNA_material_types.h"
#include "DNA_windowmanager_types.h"
struct Material; struct Material;
@ -51,6 +54,12 @@ class USDAbstractWriter : public AbstractHierarchyWriter {
const pxr::SdfPath &usd_path() const; const pxr::SdfPath &usd_path() const;
/** Get the wmJobWorkerStatus-provided `reports` list pointer, to use with the BKE_report API. */
ReportList *reports() const
{
return usd_export_context_.export_params.worker_status->reports;
}
protected: protected:
virtual void do_write(HierarchyContext &context) = 0; virtual void do_write(HierarchyContext &context) = 0;
std::string get_export_file_path() const; std::string get_export_file_path() const;

View File

@ -18,6 +18,7 @@
#include "BKE_curves.hh" #include "BKE_curves.hh"
#include "BKE_lib_id.h" #include "BKE_lib_id.h"
#include "BKE_material.h" #include "BKE_material.h"
#include "BKE_report.h"
#include "BLI_math_geom.h" #include "BLI_math_geom.h"
#include "BLT_translation.h" #include "BLT_translation.h"
@ -26,7 +27,6 @@
#include "RNA_enum_types.hh" #include "RNA_enum_types.hh"
#include "WM_api.hh" #include "WM_api.hh"
#include "WM_types.hh"
namespace blender::io::usd { namespace blender::io::usd {
@ -85,7 +85,8 @@ static void populate_curve_widths(const bke::CurvesGeometry &geometry, pxr::VtAr
static pxr::TfToken get_curve_width_interpolation(const pxr::VtArray<float> &widths, static pxr::TfToken get_curve_width_interpolation(const pxr::VtArray<float> &widths,
const pxr::VtArray<int> &segments, const pxr::VtArray<int> &segments,
const pxr::VtIntArray &control_point_counts, const pxr::VtIntArray &control_point_counts,
const bool is_cyclic) const bool is_cyclic,
ReportList *reports)
{ {
if (widths.empty()) { if (widths.empty()) {
return pxr::TfToken(); return pxr::TfToken();
@ -110,7 +111,7 @@ static pxr::TfToken get_curve_width_interpolation(const pxr::VtArray<float> &wid
return pxr::UsdGeomTokens->varying; return pxr::UsdGeomTokens->varying;
} }
WM_report(RPT_WARNING, "Curve width size not supported for USD interpolation"); BKE_report(reports, RPT_WARNING, "Curve width size not supported for USD interpolation");
return pxr::TfToken(); return pxr::TfToken();
} }
@ -158,7 +159,8 @@ static void populate_curve_props(const bke::CurvesGeometry &geometry,
pxr::VtArray<float> &widths, pxr::VtArray<float> &widths,
pxr::TfToken &interpolation, pxr::TfToken &interpolation,
const bool is_cyclic, const bool is_cyclic,
const bool is_cubic) const bool is_cubic,
ReportList *reports)
{ {
const int num_curves = geometry.curve_num; const int num_curves = geometry.curve_num;
const Span<float3> positions = geometry.positions(); const Span<float3> positions = geometry.positions();
@ -169,7 +171,8 @@ static void populate_curve_props(const bke::CurvesGeometry &geometry,
geometry, positions, verts, control_point_counts, segments, is_cyclic, is_cubic); geometry, positions, verts, control_point_counts, segments, is_cyclic, is_cubic);
populate_curve_widths(geometry, widths); populate_curve_widths(geometry, widths);
interpolation = get_curve_width_interpolation(widths, segments, control_point_counts, is_cyclic); interpolation = get_curve_width_interpolation(
widths, segments, control_point_counts, is_cyclic, reports);
} }
static void populate_curve_verts_for_bezier(const bke::CurvesGeometry &geometry, static void populate_curve_verts_for_bezier(const bke::CurvesGeometry &geometry,
@ -246,7 +249,8 @@ static void populate_curve_props_for_bezier(const bke::CurvesGeometry &geometry,
pxr::VtIntArray &control_point_counts, pxr::VtIntArray &control_point_counts,
pxr::VtArray<float> &widths, pxr::VtArray<float> &widths,
pxr::TfToken &interpolation, pxr::TfToken &interpolation,
const bool is_cyclic) const bool is_cyclic,
ReportList *reports)
{ {
const int num_curves = geometry.curve_num; const int num_curves = geometry.curve_num;
@ -262,7 +266,8 @@ static void populate_curve_props_for_bezier(const bke::CurvesGeometry &geometry,
geometry, positions, handles_l, handles_r, verts, control_point_counts, segments, is_cyclic); geometry, positions, handles_l, handles_r, verts, control_point_counts, segments, is_cyclic);
populate_curve_widths(geometry, widths); populate_curve_widths(geometry, widths);
interpolation = get_curve_width_interpolation(widths, segments, control_point_counts, is_cyclic); interpolation = get_curve_width_interpolation(
widths, segments, control_point_counts, is_cyclic, reports);
} }
static void populate_curve_props_for_nurbs(const bke::CurvesGeometry &geometry, static void populate_curve_props_for_nurbs(const bke::CurvesGeometry &geometry,
@ -397,7 +402,8 @@ void USDCurvesWriter::do_write(HierarchyContext &context)
}); });
if (number_of_curve_types > 1) { if (number_of_curve_types > 1) {
WM_report(RPT_WARNING, "Cannot export mixed curve types in the same Curves object"); BKE_report(
reports(), RPT_WARNING, "Cannot export mixed curve types in the same Curves object");
return; return;
} }
@ -413,7 +419,8 @@ void USDCurvesWriter::do_write(HierarchyContext &context)
} }
if (!all_same_cyclic_type) { if (!all_same_cyclic_type) {
WM_report(RPT_WARNING, BKE_report(reports(),
RPT_WARNING,
"Cannot export mixed cyclic and non-cyclic curves in the same Curves object"); "Cannot export mixed cyclic and non-cyclic curves in the same Curves object");
return; return;
} }
@ -441,7 +448,8 @@ void USDCurvesWriter::do_write(HierarchyContext &context)
RNA_enum_name_from_value( RNA_enum_name_from_value(
rna_enum_curves_type_items, int(curve_type), &current_curve_type_name); rna_enum_curves_type_items, int(curve_type), &current_curve_type_name);
WM_reportf(RPT_WARNING, BKE_reportf(reports(),
RPT_WARNING,
"USD does not support animating curve types. The curve type changes from %s to " "USD does not support animating curve types. The curve type changes from %s to "
"%s on frame %f", "%s on frame %f",
IFACE_(first_frame_curve_type_name), IFACE_(first_frame_curve_type_name),
@ -454,22 +462,34 @@ void USDCurvesWriter::do_write(HierarchyContext &context)
case CURVE_TYPE_POLY: case CURVE_TYPE_POLY:
usd_curves = DefineUsdGeomBasisCurves(pxr::VtValue(), is_cyclic, false); usd_curves = DefineUsdGeomBasisCurves(pxr::VtValue(), is_cyclic, false);
populate_curve_props( populate_curve_props(geometry,
geometry, verts, control_point_counts, widths, interpolation, is_cyclic, false); verts,
control_point_counts,
widths,
interpolation,
is_cyclic,
false,
reports());
break; break;
case CURVE_TYPE_CATMULL_ROM: case CURVE_TYPE_CATMULL_ROM:
usd_curves = DefineUsdGeomBasisCurves( usd_curves = DefineUsdGeomBasisCurves(
pxr::VtValue(pxr::UsdGeomTokens->catmullRom), is_cyclic, true); pxr::VtValue(pxr::UsdGeomTokens->catmullRom), is_cyclic, true);
populate_curve_props( populate_curve_props(geometry,
geometry, verts, control_point_counts, widths, interpolation, is_cyclic, true); verts,
control_point_counts,
widths,
interpolation,
is_cyclic,
true,
reports());
break; break;
case CURVE_TYPE_BEZIER: case CURVE_TYPE_BEZIER:
usd_curves = DefineUsdGeomBasisCurves( usd_curves = DefineUsdGeomBasisCurves(
pxr::VtValue(pxr::UsdGeomTokens->bezier), is_cyclic, true); pxr::VtValue(pxr::UsdGeomTokens->bezier), is_cyclic, true);
populate_curve_props_for_bezier( populate_curve_props_for_bezier(
geometry, verts, control_point_counts, widths, interpolation, is_cyclic); geometry, verts, control_point_counts, widths, interpolation, is_cyclic, reports());
break; break;
case CURVE_TYPE_NURBS: { case CURVE_TYPE_NURBS: {
pxr::VtArray<double> knots; pxr::VtArray<double> knots;

View File

@ -13,6 +13,7 @@
#include "BKE_main.h" #include "BKE_main.h"
#include "BKE_node.hh" #include "BKE_node.hh"
#include "BKE_node_runtime.hh" #include "BKE_node_runtime.hh"
#include "BKE_report.h"
#include "IMB_colormanagement.h" #include "IMB_colormanagement.h"
@ -428,7 +429,8 @@ static std::string get_in_memory_texture_filename(Image *ima)
static void export_in_memory_texture(Image *ima, static void export_in_memory_texture(Image *ima,
const std::string &export_dir, const std::string &export_dir,
const bool allow_overwrite) const bool allow_overwrite,
ReportList *reports)
{ {
char image_abs_path[FILE_MAX]; char image_abs_path[FILE_MAX];
@ -472,7 +474,8 @@ static void export_in_memory_texture(Image *ima,
std::cout << "Exporting in-memory texture to " << export_path << std::endl; std::cout << "Exporting in-memory texture to " << export_path << std::endl;
if (BKE_imbuf_write_as(imbuf, export_path, &imageFormat, true) == 0) { if (BKE_imbuf_write_as(imbuf, export_path, &imageFormat, true) == 0) {
WM_reportf(RPT_WARNING, "USD export: couldn't export in-memory texture to %s", export_path); BKE_reportf(
reports, RPT_WARNING, "USD export: couldn't export in-memory texture to %s", export_path);
} }
} }
@ -702,7 +705,8 @@ static std::string get_tex_image_asset_filepath(const USDExporterContext &usd_ex
* destination directory. */ * destination directory. */
static void copy_tiled_textures(Image *ima, static void copy_tiled_textures(Image *ima,
const std::string &dest_dir, const std::string &dest_dir,
const bool allow_overwrite) const bool allow_overwrite,
ReportList *reports)
{ {
char src_path[FILE_MAX]; char src_path[FILE_MAX];
get_absolute_path(ima, src_path); get_absolute_path(ima, src_path);
@ -743,7 +747,8 @@ static void copy_tiled_textures(Image *ima,
/* Copy the file. */ /* Copy the file. */
if (BLI_copy(src_tile_path, dest_tile_path) != 0) { if (BLI_copy(src_tile_path, dest_tile_path) != 0) {
WM_reportf(RPT_WARNING, BKE_reportf(reports,
RPT_WARNING,
"USD export: could not copy texture tile from %s to %s", "USD export: could not copy texture tile from %s to %s",
src_tile_path, src_tile_path,
dest_tile_path); dest_tile_path);
@ -753,7 +758,10 @@ static void copy_tiled_textures(Image *ima,
} }
/* Copy the given image to the destination directory. */ /* Copy the given image to the destination directory. */
static void copy_single_file(Image *ima, const std::string &dest_dir, const bool allow_overwrite) static void copy_single_file(Image *ima,
const std::string &dest_dir,
const bool allow_overwrite,
ReportList *reports)
{ {
char source_path[FILE_MAX]; char source_path[FILE_MAX];
get_absolute_path(ima, source_path); get_absolute_path(ima, source_path);
@ -777,8 +785,11 @@ static void copy_single_file(Image *ima, const std::string &dest_dir, const bool
/* Copy the file. */ /* Copy the file. */
if (BLI_copy(source_path, dest_path) != 0) { if (BLI_copy(source_path, dest_path) != 0) {
WM_reportf( BKE_reportf(reports,
RPT_WARNING, "USD export: could not copy texture from %s to %s", source_path, dest_path); RPT_WARNING,
"USD export: could not copy texture from %s to %s",
source_path,
dest_path);
} }
} }
@ -816,13 +827,16 @@ static void export_texture(const USDExporterContext &usd_export_context, bNode *
std::string dest_dir(tex_dir_path); std::string dest_dir(tex_dir_path);
if (is_generated || is_dirty || is_packed) { if (is_generated || is_dirty || is_packed) {
export_in_memory_texture(ima, dest_dir, allow_overwrite); export_in_memory_texture(
ima, dest_dir, allow_overwrite, usd_export_context.export_params.worker_status->reports);
} }
else if (ima->source == IMA_SRC_TILED) { else if (ima->source == IMA_SRC_TILED) {
copy_tiled_textures(ima, dest_dir, allow_overwrite); copy_tiled_textures(
ima, dest_dir, allow_overwrite, usd_export_context.export_params.worker_status->reports);
} }
else { else {
copy_single_file(ima, dest_dir, allow_overwrite); copy_single_file(
ima, dest_dir, allow_overwrite, usd_export_context.export_params.worker_status->reports);
} }
} }
@ -853,7 +867,10 @@ pxr::UsdShadeMaterial create_usd_material(const USDExporterContext &usd_export_c
create_usd_viewport_material(usd_export_context, material, usd_material); create_usd_viewport_material(usd_export_context, material, usd_material);
} }
call_material_export_hooks(usd_export_context.stage, material, usd_material); call_material_export_hooks(usd_export_context.stage,
material,
usd_material,
usd_export_context.export_params.worker_status->reports);
return usd_material; return usd_material;
} }

View File

@ -24,6 +24,7 @@
#include "BKE_mesh_wrapper.hh" #include "BKE_mesh_wrapper.hh"
#include "BKE_modifier.h" #include "BKE_modifier.h"
#include "BKE_object.hh" #include "BKE_object.hh"
#include "BKE_report.h"
#include "DEG_depsgraph.hh" #include "DEG_depsgraph.hh"
@ -128,7 +129,7 @@ void USDGenericMeshWriter::write_custom_data(const Mesh *mesh, pxr::UsdGeomMesh
} }
static std::optional<pxr::SdfValueTypeName> convert_blender_type_to_usd( static std::optional<pxr::SdfValueTypeName> convert_blender_type_to_usd(
const eCustomDataType blender_type) const eCustomDataType blender_type, ReportList *reports)
{ {
switch (blender_type) { switch (blender_type) {
case CD_PROP_FLOAT: case CD_PROP_FLOAT:
@ -147,13 +148,13 @@ static std::optional<pxr::SdfValueTypeName> convert_blender_type_to_usd(
case CD_PROP_QUATERNION: case CD_PROP_QUATERNION:
return pxr::SdfValueTypeNames->QuatfArray; return pxr::SdfValueTypeNames->QuatfArray;
default: default:
WM_reportf(RPT_WARNING, "Unsupported type for mesh data"); BKE_reportf(reports, RPT_WARNING, "Unsupported type for mesh data");
return std::nullopt; return std::nullopt;
} }
} }
static const std::optional<pxr::TfToken> convert_blender_domain_to_usd( static const std::optional<pxr::TfToken> convert_blender_domain_to_usd(
const eAttrDomain blender_domain) const eAttrDomain blender_domain, ReportList *reports)
{ {
switch (blender_domain) { switch (blender_domain) {
case ATTR_DOMAIN_CORNER: case ATTR_DOMAIN_CORNER:
@ -165,7 +166,7 @@ static const std::optional<pxr::TfToken> convert_blender_domain_to_usd(
/* Notice: Edge types are not supported in USD! */ /* Notice: Edge types are not supported in USD! */
default: default:
WM_reportf(RPT_WARNING, "Unsupported type for mesh data"); BKE_reportf(reports, RPT_WARNING, "Unsupported type for mesh data");
return std::nullopt; return std::nullopt;
} }
} }
@ -231,9 +232,10 @@ void USDGenericMeshWriter::write_generic_data(const Mesh *mesh,
const pxr::UsdGeomPrimvarsAPI pvApi = pxr::UsdGeomPrimvarsAPI(usd_mesh); const pxr::UsdGeomPrimvarsAPI pvApi = pxr::UsdGeomPrimvarsAPI(usd_mesh);
/* Varying type depends on original domain. */ /* Varying type depends on original domain. */
const std::optional<pxr::TfToken> prim_varying = convert_blender_domain_to_usd(meta_data.domain); const std::optional<pxr::TfToken> prim_varying = convert_blender_domain_to_usd(meta_data.domain,
reports());
const std::optional<pxr::SdfValueTypeName> prim_attr_type = convert_blender_type_to_usd( const std::optional<pxr::SdfValueTypeName> prim_attr_type = convert_blender_type_to_usd(
meta_data.data_type); meta_data.data_type, reports());
const GVArraySpan attribute = *mesh->attributes().lookup( const GVArraySpan attribute = *mesh->attributes().lookup(
attribute_id, meta_data.domain, meta_data.data_type); attribute_id, meta_data.domain, meta_data.data_type);
@ -242,7 +244,8 @@ void USDGenericMeshWriter::write_generic_data(const Mesh *mesh,
} }
if (!prim_varying || !prim_attr_type) { if (!prim_varying || !prim_attr_type) {
WM_reportf(RPT_WARNING, BKE_reportf(reports(),
RPT_WARNING,
"Mesh %s, Attribute %s cannot be converted to USD", "Mesh %s, Attribute %s cannot be converted to USD",
&mesh->id.name[2], &mesh->id.name[2],
attribute_id.name().data()); attribute_id.name().data());

View File

@ -11,6 +11,7 @@
#include "DNA_volume_types.h" #include "DNA_volume_types.h"
#include "DNA_windowmanager_types.h" #include "DNA_windowmanager_types.h"
#include "BKE_report.h"
#include "BKE_volume.h" #include "BKE_volume.h"
#include "BLI_fileops.h" #include "BLI_fileops.h"
@ -47,7 +48,8 @@ void USDVolumeWriter::do_write(HierarchyContext &context)
auto vdb_file_path = resolve_vdb_file(volume); auto vdb_file_path = resolve_vdb_file(volume);
if (!vdb_file_path.has_value()) { if (!vdb_file_path.has_value()) {
WM_reportf(RPT_WARNING, BKE_reportf(reports(),
RPT_WARNING,
"USD Export: failed to resolve .vdb file for object: %s", "USD Export: failed to resolve .vdb file for object: %s",
volume->id.name + 2); volume->id.name + 2);
return; return;
@ -58,7 +60,8 @@ void USDVolumeWriter::do_write(HierarchyContext &context)
vdb_file_path = relative_vdb_file_path; vdb_file_path = relative_vdb_file_path;
} }
else { else {
WM_reportf(RPT_WARNING, BKE_reportf(reports(),
RPT_WARNING,
"USD Export: couldn't construct relative file path for .vdb file, absolute path " "USD Export: couldn't construct relative file path for .vdb file, absolute path "
"will be used instead"); "will be used instead");
} }

View File

@ -110,7 +110,7 @@ TEST_F(UsdCurvesTest, usd_export_curves)
USDExportParams params; USDExportParams params;
const bool result = USD_export(context, output_filename.c_str(), &params, false); const bool result = USD_export(context, output_filename.c_str(), &params, false, nullptr);
EXPECT_TRUE(result) << "USD export should succed."; EXPECT_TRUE(result) << "USD export should succed.";
pxr::UsdStageRefPtr stage = pxr::UsdStage::Open(output_filename); pxr::UsdStageRefPtr stage = pxr::UsdStage::Open(output_filename);

View File

@ -214,7 +214,7 @@ TEST_F(UsdExportTest, usd_export_rain_mesh)
params.export_uvmaps = false; params.export_uvmaps = false;
params.visible_objects_only = true; params.visible_objects_only = true;
bool result = USD_export(context, output_filename.c_str(), &params, false); bool result = USD_export(context, output_filename.c_str(), &params, false, nullptr);
ASSERT_TRUE(result) << "Writing to " << output_filename << " failed!"; ASSERT_TRUE(result) << "Writing to " << output_filename << " failed!";
pxr::UsdStageRefPtr stage = pxr::UsdStage::Open(output_filename); pxr::UsdStageRefPtr stage = pxr::UsdStage::Open(output_filename);
@ -280,7 +280,7 @@ TEST_F(UsdExportTest, usd_export_material)
params.generate_preview_surface = true; params.generate_preview_surface = true;
params.relative_paths = false; params.relative_paths = false;
const bool result = USD_export(context, output_filename.c_str(), &params, false); const bool result = USD_export(context, output_filename.c_str(), &params, false, nullptr);
ASSERT_TRUE(result) << "Unable to export stage to " << output_filename; ASSERT_TRUE(result) << "Unable to export stage to " << output_filename;
pxr::UsdStageRefPtr stage = pxr::UsdStage::Open(output_filename); pxr::UsdStageRefPtr stage = pxr::UsdStage::Open(output_filename);

View File

@ -98,7 +98,7 @@ TEST_F(UsdUsdzExportTest, usdz_export)
params.export_materials = false; params.export_materials = false;
params.visible_objects_only = false; params.visible_objects_only = false;
bool result = USD_export(context, output_filepath, &params, false); bool result = USD_export(context, output_filepath, &params, false, nullptr);
ASSERT_TRUE(result) << "usd export to " << output_filepath << " failed."; ASSERT_TRUE(result) << "usd export to " << output_filepath << " failed.";
pxr::UsdStageRefPtr stage = pxr::UsdStage::Open(output_filepath); pxr::UsdStageRefPtr stage = pxr::UsdStage::Open(output_filepath);

View File

@ -16,6 +16,7 @@ struct CacheArchiveHandle;
struct CacheReader; struct CacheReader;
struct Object; struct Object;
struct bContext; struct bContext;
struct wmJobWorkerStatus;
/* Behavior when the name of an imported material /* Behavior when the name of an imported material
* conflicts with an existing material. */ * conflicts with an existing material. */
@ -55,6 +56,10 @@ struct USDExportParams {
bool overwrite_textures = true; bool overwrite_textures = true;
bool relative_paths = true; bool relative_paths = true;
char root_prim_path[1024] = ""; /* FILE_MAX */ char root_prim_path[1024] = ""; /* FILE_MAX */
/** Communication structure between the wmJob management code and the worker code. Currently used
* to generate safely reports from the worker thread. */
wmJobWorkerStatus *worker_status;
}; };
struct USDImportParams { struct USDImportParams {
@ -91,6 +96,10 @@ struct USDImportParams {
char import_textures_dir[768]; /* FILE_MAXDIR */ char import_textures_dir[768]; /* FILE_MAXDIR */
eUSDTexNameCollisionMode tex_name_collision_mode; eUSDTexNameCollisionMode tex_name_collision_mode;
bool import_all_materials; bool import_all_materials;
/** Communication structure between the wmJob management code and the worker code. Currently used
* to generate safely reports from the worker thread. */
wmJobWorkerStatus *worker_status;
}; };
/* This struct is in place to store the mesh sequence parameters needed when reading a data from a /* This struct is in place to store the mesh sequence parameters needed when reading a data from a
@ -115,12 +124,14 @@ USDMeshReadParams create_mesh_read_params(double motion_sample_time, int read_fl
bool USD_export(struct bContext *C, bool USD_export(struct bContext *C,
const char *filepath, const char *filepath,
const struct USDExportParams *params, const struct USDExportParams *params,
bool as_background_job); bool as_background_job,
ReportList *reports);
bool USD_import(struct bContext *C, bool USD_import(struct bContext *C,
const char *filepath, const char *filepath,
const struct USDImportParams *params, const struct USDImportParams *params,
bool as_background_job); bool as_background_job,
ReportList *reports);
int USD_get_version(void); int USD_get_version(void);

View File

@ -46,8 +46,12 @@ class USDImportTest(AbstractUSDTest):
self.assertEqual({'FINISHED'}, res, f"Unable to import USD file {infile}") self.assertEqual({'FINISHED'}, res, f"Unable to import USD file {infile}")
infile = str(self.testdir / "this_file_doesn't_exist.usda") infile = str(self.testdir / "this_file_doesn't_exist.usda")
# RPT_ERROR Reports from operators generate `RuntimeError` python exceptions.
try:
res = bpy.ops.wm.usd_import(filepath=infile) res = bpy.ops.wm.usd_import(filepath=infile)
self.assertEqual({'CANCELLED'}, res, "Was somehow able to import a non-existent USD file!") self.assertEqual({'CANCELLED'}, res, "Was somehow able to import a non-existent USD file!")
except RuntimeError as e:
self.assertTrue(e.args[0].startswith("Error: USD Import: unable to open stage to read"))
def test_import_prim_hierarchy(self): def test_import_prim_hierarchy(self):
"""Test importing a simple object hierarchy from a USDA file.""" """Test importing a simple object hierarchy from a USDA file."""