From b68486456142d769cd05c2c9ef206259a0acd333 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Fri, 13 Oct 2023 11:46:02 +0200 Subject: [PATCH] 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 --- source/blender/editors/io/io_usd.cc | 4 +- .../io/usd/hydra/usd_scene_delegate.cc | 14 ++ .../blender/io/usd/intern/usd_asset_utils.cc | 158 ++++++++------ .../blender/io/usd/intern/usd_asset_utils.h | 12 +- .../blender/io/usd/intern/usd_capi_export.cc | 52 ++--- .../blender/io/usd/intern/usd_capi_import.cc | 18 +- source/blender/io/usd/intern/usd_hook.cc | 35 ++-- source/blender/io/usd/intern/usd_hook.h | 6 +- .../io/usd/intern/usd_reader_material.cc | 2 +- .../io/usd/intern/usd_reader_material.h | 8 + .../blender/io/usd/intern/usd_reader_mesh.cc | 115 ++++++----- .../blender/io/usd/intern/usd_reader_prim.h | 8 + .../blender/io/usd/intern/usd_reader_shape.cc | 19 +- .../io/usd/intern/usd_reader_skeleton.cc | 2 +- .../blender/io/usd/intern/usd_reader_stage.cc | 10 +- .../blender/io/usd/intern/usd_reader_stage.h | 8 + .../blender/io/usd/intern/usd_skel_convert.cc | 193 +++++++++++------- .../blender/io/usd/intern/usd_skel_convert.h | 14 +- .../io/usd/intern/usd_writer_abstract.cc | 9 +- .../io/usd/intern/usd_writer_abstract.h | 9 + .../io/usd/intern/usd_writer_curves.cc | 62 ++++-- .../io/usd/intern/usd_writer_material.cc | 45 ++-- .../blender/io/usd/intern/usd_writer_mesh.cc | 23 ++- .../io/usd/intern/usd_writer_volume.cc | 15 +- .../blender/io/usd/tests/usd_curves_test.cc | 2 +- .../blender/io/usd/tests/usd_export_test.cc | 4 +- .../io/usd/tests/usd_usdz_export_test.cc | 2 +- source/blender/io/usd/usd.h | 15 +- tests/python/bl_usd_import_test.py | 8 +- 29 files changed, 560 insertions(+), 312 deletions(-) diff --git a/source/blender/editors/io/io_usd.cc b/source/blender/editors/io/io_usd.cc index e8810b5e7d4..91427d84104 100644 --- a/source/blender/editors/io/io_usd.cc +++ b/source/blender/editors/io/io_usd.cc @@ -179,7 +179,7 @@ static int wm_usd_export_exec(bContext *C, wmOperator *op) STRNCPY(params.root_prim_path, root_prim_path); - bool ok = USD_export(C, filepath, ¶ms, as_background_job); + bool ok = USD_export(C, filepath, ¶ms, as_background_job, op->reports); 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); - const bool ok = USD_import(C, filepath, ¶ms, as_background_job); + const bool ok = USD_import(C, filepath, ¶ms, as_background_job, op->reports); return as_background_job || ok ? OPERATOR_FINISHED : OPERATOR_CANCELLED; } diff --git a/source/blender/io/usd/hydra/usd_scene_delegate.cc b/source/blender/io/usd/hydra/usd_scene_delegate.cc index 90acbd271a6..c069626a6f9 100644 --- a/source/blender/io/usd/hydra/usd_scene_delegate.cc +++ b/source/blender/io/usd/hydra/usd_scene_delegate.cc @@ -9,9 +9,13 @@ #include "BLI_string.h" #include "BKE_appdir.h" +#include "BKE_report.h" #include "DEG_depsgraph_query.hh" +#include "WM_api.hh" +#include "WM_types.hh" + #include "usd.h" #include "usd.hh" @@ -50,6 +54,14 @@ void USDSceneDelegate::populate(Depsgraph *depsgraph) params.export_textures = false; /* Don't copy all textures, is slow. */ 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. */ BLI_delete(temp_dir_.c_str(), true, true); 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()); delegate_ = std::make_unique(render_index_, delegate_id_); delegate_->Populate(stage_->GetPseudoRoot()); + + WM_reports_from_reports_move(nullptr, &worker_reports); } } // namespace blender::io::hydra diff --git a/source/blender/io/usd/intern/usd_asset_utils.cc b/source/blender/io/usd/intern/usd_asset_utils.cc index db547e9385f..5e9a79db6ff 100644 --- a/source/blender/io/usd/intern/usd_asset_utils.cc +++ b/source/blender/io/usd/intern/usd_asset_utils.cc @@ -10,6 +10,7 @@ #include #include "BKE_main.h" +#include "BKE_report.h" #include "BLI_fileops.h" #include "BLI_path_util.h" @@ -52,17 +53,18 @@ static std::pair split_udim_pattern(const std::string /* Return the asset file base name, with special handling of * package relative paths. */ -static std::string get_asset_base_name(const char *src_path) +static std::string get_asset_base_name(const char *src_path, ReportList *reports) { char base_name[FILE_MAXFILE]; if (pxr::ArIsPackageRelativePath(src_path)) { std::pair split = pxr::ArSplitPackageRelativePathInner(src_path); if (split.second.empty()) { - WM_reportf(RPT_WARNING, - "%s: Couldn't determine package-relative file name from path %s", - __func__, - src_path); + BKE_reportf(reports, + RPT_WARNING, + "%s: Couldn't determine package-relative file name from path %s", + __func__, + src_path); return src_path; } BLI_path_split_file_part(split.second.c_str(), base_name, sizeof(base_name)); @@ -77,9 +79,10 @@ static std::string get_asset_base_name(const char *src_path) /* Copy an asset to a destination directory. */ static std::string copy_asset_to_directory(const char *src_path, const char *dest_dir_path, - eUSDTexNameCollisionMode name_collision_mode) + 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]; 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; } - if (!copy_asset(src_path, dest_file_path, name_collision_mode)) { - WM_reportf(RPT_WARNING, "%s: Couldn't copy file %s to %s", __func__, src_path, dest_file_path); + if (!copy_asset(src_path, dest_file_path, name_collision_mode, reports)) { + BKE_reportf(reports, + RPT_WARNING, + "%s: Couldn't copy file %s to %s", + __func__, + src_path, + dest_file_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, const char *dest_dir_path, - eUSDTexNameCollisionMode name_collision_mode) + eUSDTexNameCollisionMode name_collision_mode, + ReportList *reports) { /* Get prefix and suffix from udim pattern. */ std::pair splitPath = split_udim_pattern(src_path); if (splitPath.first.empty() || splitPath.second.empty()) { - WM_reportf(RPT_ERROR, "%s: Couldn't split UDIM pattern %s", __func__, src_path); + BKE_reportf(reports, RPT_ERROR, "%s: Couldn't split UDIM pattern %s", __func__, 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) { const std::string src_udim = splitPath.first + std::to_string(i) + splitPath.second; if (asset_exists(src_udim.c_str())) { - copy_asset_to_directory(src_udim.c_str(), dest_dir_path, name_collision_mode); + 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]; 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. */ splitPath = split_udim_pattern(ret_udim_path); if (splitPath.first.empty() || splitPath.second.empty()) { - WM_reportf(RPT_ERROR, "%s: Couldn't split UDIM pattern %s", __func__, ret_udim_path); + BKE_reportf(reports, RPT_ERROR, "%s: Couldn't split UDIM pattern %s", __func__, ret_udim_path); return ret_udim_path; } return splitPath.first + UDIM_PATTERN + splitPath.second; } -bool copy_asset(const char *src, const char *dst, eUSDTexNameCollisionMode name_collision_mode) +bool copy_asset(const char *src, + const char *dst, + eUSDTexNameCollisionMode name_collision_mode, + ReportList *reports) { if (!(src && dst)) { 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 (!ar.Resolve(dst).IsEmpty()) { /* The asset exists, so this is a no-op. */ - WM_reportf(RPT_INFO, "%s: Will not overwrite existing asset %s", __func__, dst); + BKE_reportf(reports, RPT_INFO, "%s: Will not overwrite existing asset %s", __func__, dst); return true; } } @@ -157,86 +169,96 @@ bool copy_asset(const char *src, const char *dst, eUSDTexNameCollisionMode name_ pxr::ArResolvedPath src_path = ar.Resolve(src); 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; } pxr::ArResolvedPath dst_path = ar.ResolveForNewAsset(dst); if (dst_path.IsEmpty()) { - WM_reportf(RPT_ERROR, "%s: Can't resolve path %s for writing", __func__, dst); + BKE_reportf(reports, RPT_ERROR, "%s: Can't resolve path %s for writing", __func__, dst); return false; } if (src_path == dst_path) { - WM_reportf(RPT_ERROR, - "%s: Can't copy %s. The source and destination paths are the same", - __func__, - src_path.GetPathString().c_str()); + BKE_reportf(reports, + RPT_ERROR, + "%s: Can't copy %s. The source and destination paths are the same", + __func__, + src_path.GetPathString().c_str()); return false; } std::string why_not; if (!ar.CanWriteAssetToPath(dst_path, &why_not)) { - WM_reportf(RPT_ERROR, - "%s: Can't write to asset %s: %s", - __func__, - dst_path.GetPathString().c_str(), - why_not.c_str()); + BKE_reportf(reports, + RPT_ERROR, + "%s: Can't write to asset %s: %s", + __func__, + dst_path.GetPathString().c_str(), + why_not.c_str()); return false; } std::shared_ptr src_asset = ar.OpenAsset(src_path); if (!src_asset) { - WM_reportf( - RPT_ERROR, "%s: Can't open source asset %s", __func__, src_path.GetPathString().c_str()); + BKE_reportf(reports, + RPT_ERROR, + "%s: Can't open source asset %s", + __func__, + src_path.GetPathString().c_str()); return false; } const size_t size = src_asset->GetSize(); if (size == 0) { - WM_reportf(RPT_WARNING, - "%s: Will not copy zero size source asset %s", - __func__, - src_path.GetPathString().c_str()); + BKE_reportf(reports, + RPT_WARNING, + "%s: Will not copy zero size source asset %s", + __func__, + src_path.GetPathString().c_str()); return false; } std::shared_ptr buf = src_asset->GetBuffer(); if (!buf) { - WM_reportf(RPT_ERROR, - "%s: Null buffer for source asset %s", - __func__, - src_path.GetPathString().c_str()); + BKE_reportf(reports, + RPT_ERROR, + "%s: Null buffer for source asset %s", + __func__, + src_path.GetPathString().c_str()); return false; } std::shared_ptr dst_asset = ar.OpenAssetForWrite( dst_path, pxr::ArResolver::WriteMode::Replace); if (!dst_asset) { - WM_reportf(RPT_ERROR, - "%s: Can't open destination asset %s for writing", - __func__, - src_path.GetPathString().c_str()); + BKE_reportf(reports, + RPT_ERROR, + "%s: Can't open destination asset %s for writing", + __func__, + src_path.GetPathString().c_str()); return false; } size_t bytes_written = dst_asset->Write(src_asset->GetBuffer().get(), src_asset->GetSize(), 0); if (bytes_written == 0) { - WM_reportf(RPT_ERROR, - "%s: Error writing to destination asset %s", - __func__, - dst_path.GetPathString().c_str()); + BKE_reportf(reports, + RPT_ERROR, + "%s: Error writing to destination asset %s", + __func__, + dst_path.GetPathString().c_str()); } if (!dst_asset->Close()) { - WM_reportf(RPT_ERROR, - "%s: Couldn't close destination asset %s", - __func__, - dst_path.GetPathString().c_str()); + BKE_reportf(reports, + RPT_ERROR, + "%s: Couldn't close destination asset %s", + __func__, + dst_path.GetPathString().c_str()); return false; } @@ -250,11 +272,15 @@ bool asset_exists(const char *path) std::string import_asset(const char *src, const char *import_dir, - eUSDTexNameCollisionMode name_collision_mode) + eUSDTexNameCollisionMode name_collision_mode, + ReportList *reports) { if (import_dir[0] == '\0') { - WM_reportf( - RPT_ERROR, "%s: Texture import directory path empty, couldn't import %s", __func__, src); + BKE_reportf(reports, + RPT_ERROR, + "%s: Texture import directory path empty, couldn't import %s", + __func__, + src); return src; } @@ -267,14 +293,15 @@ std::string import_asset(const char *src, basepath = BKE_main_blendfile_path_from_global(); if (!basepath || basepath[0] == '\0') { - WM_reportf(RPT_ERROR, - "%s: import directory is relative " - "but the blend file path is empty. " - "Please save the blend file before importing the USD " - "or provide an absolute import directory path. " - "Can't import %s", - __func__, - src); + BKE_reportf(reports, + RPT_ERROR, + "%s: import directory is relative " + "but the blend file path is empty. " + "Please save the blend file before importing the USD " + "or provide an absolute import directory path. " + "Can't import %s", + __func__, + src); return src; } BLI_path_abs(dest_dir_path, basepath); @@ -283,16 +310,19 @@ std::string import_asset(const char *src, BLI_path_normalize(dest_dir_path); if (!BLI_dir_create_recursive(dest_dir_path)) { - WM_reportf( - RPT_ERROR, "%s: Couldn't create texture import directory %s", __func__, dest_dir_path); + BKE_reportf(reports, + RPT_ERROR, + "%s: Couldn't create texture import directory %s", + __func__, + dest_dir_path); return src; } if (is_udim_path(src)) { - return copy_udim_asset_to_directory(src, dest_dir_path, name_collision_mode); + return copy_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) diff --git a/source/blender/io/usd/intern/usd_asset_utils.h b/source/blender/io/usd/intern/usd_asset_utils.h index 2061342a398..b5c1b1d3733 100644 --- a/source/blender/io/usd/intern/usd_asset_utils.h +++ b/source/blender/io/usd/intern/usd_asset_utils.h @@ -17,9 +17,14 @@ namespace blender::io::usd { * \param src: source path of the asset to copy * \param dst: destination path of the copy * \param name_collision_mode: behavior when `dst` already exists + * \param reports: the storage for potential warning or error reports (generated using BKE_report + * API). * \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 @@ -41,11 +46,14 @@ bool asset_exists(const char *path); * \param src: source path of the asset to import * \param import_dir: path to the destination directory * \param name_collision_mode: behavior when a file of the same name already exists + * \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 */ std::string import_asset(const char *src, const char *import_dir, - eUSDTexNameCollisionMode name_collision_mode); + eUSDTexNameCollisionMode name_collision_mode, + ReportList *reports); /** * Check if the given path contains a UDIM token. diff --git a/source/blender/io/usd/intern/usd_capi_export.cc b/source/blender/io/usd/intern/usd_capi_export.cc index fe81218f54d..353730a6095 100644 --- a/source/blender/io/usd/intern/usd_capi_export.cc +++ b/source/blender/io/usd/intern/usd_capi_export.cc @@ -28,6 +28,7 @@ #include "BKE_blender_version.h" #include "BKE_context.h" #include "BKE_global.h" +#include "BKE_report.h" #include "BKE_scene.h" #include "BLI_fileops.h" @@ -108,6 +109,8 @@ static bool prim_path_valid(const char *path) /** * Perform validation of export parameter settings. * \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 ¶ms) { @@ -183,33 +186,36 @@ static bool perform_usdz_conversion(const ExportJobData *data) if (BLI_exists(data->usdz_filepath)) { result = BLI_delete(data->usdz_filepath, false, false); if (result != 0) { - WM_reportf( - RPT_ERROR, "USD Export: Unable to delete existing usdz file %s", data->usdz_filepath); + BKE_reportf(data->params.worker_status->reports, + RPT_ERROR, + "USD Export: Unable to delete existing usdz file %s", + data->usdz_filepath); return false; } } result = BLI_path_move(usdz_temp_full_path, data->usdz_filepath); if (result != 0) { - WM_reportf(RPT_ERROR, - "USD Export: Couldn't move new usdz file from temporary location %s to %s", - usdz_temp_full_path, - data->usdz_filepath); + BKE_reportf(data->params.worker_status->reports, + RPT_ERROR, + "USD Export: Couldn't move new usdz file from temporary location %s to %s", + usdz_temp_full_path, + data->usdz_filepath); return false; } return true; } -static pxr::UsdStageRefPtr export_to_stage(const USDExportParams ¶ms, - Depsgraph *depsgraph, - const char *filepath, - wmJobWorkerStatus *worker_status) +pxr::UsdStageRefPtr export_to_stage(const USDExportParams ¶ms, + Depsgraph *depsgraph, + const char *filepath) { pxr::UsdStageRefPtr usd_stage = pxr::UsdStage::CreateNew(filepath); if (!usd_stage) { return usd_stage; } + wmJobWorkerStatus *worker_status = params.worker_status; Scene *scene = DEG_get_input_scene(depsgraph); Main *bmain = DEG_get_bmain(depsgraph); @@ -273,7 +279,7 @@ static pxr::UsdStageRefPtr export_to_stage(const USDExportParams ¶ms, } } - 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. */ if (scene->r.cfra != orig_frame) { @@ -284,14 +290,6 @@ static pxr::UsdStageRefPtr export_to_stage(const USDExportParams ¶ms, return usd_stage; } -pxr::UsdStageRefPtr export_to_stage(const USDExportParams ¶ms, - 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) { ExportJobData *data = static_cast(customdata); @@ -315,16 +313,18 @@ static void export_startjob(void *customdata, wmJobWorkerStatus *worker_status) worker_status->progress = 0.0f; worker_status->do_update = true; + data->params.worker_status = worker_status; 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) { /* 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 * USDC files, and creating a new UsdStage fails. */ - WM_reportf(RPT_ERROR, - "USD Export: unable to find suitable USD plugin to write %s", - data->unarchived_filepath); + BKE_reportf(worker_status->reports, + RPT_ERROR, + "USD Export: unable to find suitable USD plugin to write %s", + data->unarchived_filepath); return; } @@ -420,7 +420,8 @@ static void set_job_filepath(blender::io::usd::ExportJobData *job, const char *f bool USD_export(bContext *C, const char *filepath, const USDExportParams *params, - bool as_background_job) + bool as_background_job, + ReportList *reports) { if (!blender::io::usd::export_params_valid(*params)) { return false; @@ -469,6 +470,9 @@ bool USD_export(bContext *C, } else { 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_endjob(job); export_ok = job->export_ok; diff --git a/source/blender/io/usd/intern/usd_capi_import.cc b/source/blender/io/usd/intern/usd_capi_import.cc index b86a904b1f9..402c666c113 100644 --- a/source/blender/io/usd/intern/usd_capi_import.cc +++ b/source/blender/io/usd/intern/usd_capi_import.cc @@ -21,6 +21,7 @@ #include "BKE_main.h" #include "BKE_node.hh" #include "BKE_object.hh" +#include "BKE_report.h" #include "BKE_scene.h" #include "BKE_world.h" @@ -157,6 +158,8 @@ static void import_startjob(void *customdata, wmJobWorkerStatus *worker_status) data->archive = nullptr; data->start_time = timeit::Clock::now(); + data->params.worker_status = worker_status; + WM_set_locked_interface(data->wm, true); G.is_break = false; @@ -221,7 +224,10 @@ static void import_startjob(void *customdata, wmJobWorkerStatus *worker_status) pxr::UsdStage::OpenMasked(data->filepath, pop_mask); 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->error_code = USD_ARCHIVE_FAIL; return; @@ -392,7 +398,9 @@ static void import_endjob(void *customdata) data->import_ok = !data->was_canceled; break; 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; } @@ -417,7 +425,8 @@ using namespace blender::io::usd; bool USD_import(bContext *C, const char *filepath, 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. */ ImportJobData *job = new ImportJobData(); @@ -460,6 +469,9 @@ bool USD_import(bContext *C, } else { wmJobWorkerStatus worker_status = {}; + /* Use the operator's reports in non-background case. */ + worker_status.reports = reports; + import_startjob(job, &worker_status); import_endjob(job); import_ok = job->import_ok; diff --git a/source/blender/io/usd/intern/usd_hook.cc b/source/blender/io/usd/intern/usd_hook.cc index a60ecf679ae..f075bdc7123 100644 --- a/source/blender/io/usd/intern/usd_hook.cc +++ b/source/blender/io/usd/intern/usd_hook.cc @@ -15,6 +15,8 @@ #include "BLI_listbase.h" +#include "BKE_report.h" + #include "RNA_access.hh" #include "RNA_prototypes.h" #include "RNA_types.hh" @@ -152,7 +154,7 @@ void register_export_hook_converters() } /* 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()) { return; @@ -160,9 +162,10 @@ static void handle_python_error(USDHook *hook) PyErr_Print(); - WM_reportf(RPT_ERROR, - "An exception occurred invoking USD hook '%s'. Please see the console for details", - hook->name); + BKE_reportf(reports, + RPT_ERROR, + "An exception occurred invoking USD hook '%s'. Please see the console for details", + hook->name); } /* Abstract base class to facilitate calling a function with a given @@ -207,10 +210,11 @@ class USDHookInvoker { call_hook(hook_obj); } catch (python::error_already_set const &) { - handle_python_error(hook); + handle_python_error(hook, reports_); } 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(hook_obj, function_name(), arg1, arg2); */ 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 { @@ -232,9 +239,10 @@ class OnExportInvoker : public USDHookInvoker { USDSceneExportContext hook_context_; public: - OnExportInvoker(pxr::UsdStageRefPtr stage, Depsgraph *depsgraph) + OnExportInvoker(pxr::UsdStageRefPtr stage, Depsgraph *depsgraph, ReportList *reports) : hook_context_(stage, depsgraph) { + reports_ = reports; } protected: @@ -258,10 +266,12 @@ class OnMaterialExportInvoker : public USDHookInvoker { public: OnMaterialExportInvoker(pxr::UsdStageRefPtr stage, Material *material, - pxr::UsdShadeMaterial &usd_material) + pxr::UsdShadeMaterial &usd_material, + ReportList *reports) : hook_context_(stage), usd_material_(usd_material) { material_ptr_ = RNA_pointer_create(nullptr, &RNA_Material, material); + reports_ = reports; } 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()) { return; } - OnExportInvoker on_export(stage, depsgraph); + OnExportInvoker on_export(stage, depsgraph, reports); on_export.call(); } void call_material_export_hooks(pxr::UsdStageRefPtr stage, Material *material, - pxr::UsdShadeMaterial &usd_material) + pxr::UsdShadeMaterial &usd_material, + ReportList *reports) { if (g_usd_hooks.empty()) { return; } - OnMaterialExportInvoker on_material_export(stage, material, usd_material); + OnMaterialExportInvoker on_material_export(stage, material, usd_material, reports); on_material_export.call(); } diff --git a/source/blender/io/usd/intern/usd_hook.h b/source/blender/io/usd/intern/usd_hook.h index 8f53fd2c399..e8c3c3a5b32 100644 --- a/source/blender/io/usd/intern/usd_hook.h +++ b/source/blender/io/usd/intern/usd_hook.h @@ -11,6 +11,7 @@ struct Depsgraph; struct ExportJobData; struct Material; +struct ReportList; struct USDExportParams; namespace blender::io::usd { @@ -19,11 +20,12 @@ namespace blender::io::usd { void register_export_hook_converters(); /** 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. */ void call_material_export_hooks(pxr::UsdStageRefPtr stage, Material *material, - pxr::UsdShadeMaterial &usd_material); + pxr::UsdShadeMaterial &usd_material, + ReportList *reports); } // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_material.cc b/source/blender/io/usd/intern/usd_reader_material.cc index 25b804af9e0..c9534114a74 100644 --- a/source/blender/io/usd/intern/usd_reader_material.cc +++ b/source/blender/io/usd/intern/usd_reader_material.cc @@ -760,7 +760,7 @@ void USDMaterialReader::load_tex_image(const pxr::UsdShadeShader &usd_shader, USD_TEX_NAME_COLLISION_OVERWRITE : 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 diff --git a/source/blender/io/usd/intern/usd_reader_material.h b/source/blender/io/usd/intern/usd_reader_material.h index 0b570bfc735..53fcc4b01d1 100644 --- a/source/blender/io/usd/intern/usd_reader_material.h +++ b/source/blender/io/usd/intern/usd_reader_material.h @@ -5,6 +5,8 @@ #include "usd.h" +#include "WM_types.hh" + #include "BLI_map.hh" #include @@ -86,6 +88,12 @@ class USDMaterialReader { 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: /** Create the Principled BSDF shader node network. */ void import_usd_preview(Material *mtl, const pxr::UsdShadeShader &usd_shader) const; diff --git a/source/blender/io/usd/intern/usd_reader_mesh.cc b/source/blender/io/usd/intern/usd_reader_mesh.cc index 56fea8a7fea..3e91c36235a 100644 --- a/source/blender/io/usd/intern/usd_reader_mesh.cc +++ b/source/blender/io/usd/intern/usd_reader_mesh.cc @@ -15,6 +15,7 @@ #include "BKE_material.h" #include "BKE_mesh.hh" #include "BKE_object.hh" +#include "BKE_report.h" #include "BLI_math_color.hh" #include "BLI_math_geom.h" @@ -165,7 +166,7 @@ USDMeshReader::USDMeshReader(const pxr::UsdPrim &prim, } static std::optional convert_usd_type_to_blender( - const pxr::SdfValueTypeName usd_type) + const pxr::SdfValueTypeName usd_type, ReportList *reports) { static const blender::Map type_map = []() { blender::Map map; @@ -194,7 +195,10 @@ static std::optional convert_usd_type_to_blender( const eCustomDataType *value = type_map.lookup_ptr(usd_type); 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; } @@ -202,7 +206,7 @@ static std::optional convert_usd_type_to_blender( } static const std::optional convert_usd_varying_to_blender( - const pxr::TfToken usd_domain) + const pxr::TfToken usd_domain, ReportList *reports) { static const blender::Map domain_map = []() { blender::Map map; @@ -221,7 +225,8 @@ static const std::optional convert_usd_varying_to_blender( const eAttrDomain *value = domain_map.lookup_ptr(usd_domain); 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; } @@ -271,11 +276,11 @@ void USDMeshReader::read_object_data(Main *bmain, const double motionSampleTime) } if (import_params_.import_blendshapes) { - import_blendshapes(bmain, object_, prim_); + import_blendshapes(bmain, object_, prim_, reports()); } 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); @@ -349,22 +354,26 @@ void USDMeshReader::read_mpolys(Mesh *mesh) template pxr::VtArray get_prim_attribute_array(const pxr::UsdGeomPrimvar &primvar, - const double motionSampleTime) + const double motionSampleTime, + ReportList *reports) { pxr::VtArray array; pxr::VtValue primvar_val; if (!primvar.ComputeFlattened(&primvar_val, motionSampleTime)) { - WM_reportf( - RPT_WARNING, "Unable to get array values for primvar %s", primvar.GetName().GetText()); + BKE_reportf(reports, + RPT_WARNING, + "Unable to get array values for primvar %s", + primvar.GetName().GetText()); return array; } if (!primvar_val.CanCast>()) { - WM_reportf(RPT_WARNING, - "USD Import: can't cast attribute '%s' to array", - primvar.GetName().GetText()); + BKE_reportf(reports, + RPT_WARNING, + "USD Import: can't cast attribute '%s' to array", + primvar.GetName().GetText()); return array; } @@ -380,8 +389,8 @@ void USDMeshReader::read_color_data_primvar(Mesh *mesh, return; } - pxr::VtArray usd_colors = get_prim_attribute_array(primvar, - motionSampleTime); + pxr::VtArray usd_colors = get_prim_attribute_array( + primvar, motionSampleTime, reports()); if (usd_colors.empty()) { return; @@ -395,9 +404,11 @@ void USDMeshReader::read_color_data_primvar(Mesh *mesh, (interp == pxr::UsdGeomTokens->constant && usd_colors.size() != 1) || (interp == pxr::UsdGeomTokens->uniform && usd_colors.size() != mesh->faces_num)) { - WM_reportf(RPT_WARNING, - "USD Import: color attribute value '%s' count inconsistent with interpolation type", - primvar.GetName().GetText()); + BKE_reportf( + reports(), + RPT_WARNING, + "USD Import: color attribute value '%s' count inconsistent with interpolation type", + primvar.GetName().GetText()); return; } @@ -418,9 +429,10 @@ void USDMeshReader::read_color_data_primvar(Mesh *mesh, color_data = attributes.lookup_or_add_for_write_only_span(primvar_name, color_domain); if (!color_data) { - WM_reportf(RPT_WARNING, - "USD Import: couldn't add color attribute '%s'", - primvar.GetBaseName().GetText()); + BKE_reportf(reports(), + RPT_WARNING, + "USD Import: couldn't add color attribute '%s'", + primvar.GetBaseName().GetText()); return; } @@ -494,8 +506,8 @@ void USDMeshReader::read_uv_data_primvar(Mesh *mesh, { const StringRef primvar_name(primvar.StripPrimvarsName(primvar.GetName()).GetString()); - pxr::VtArray usd_uvs = get_prim_attribute_array(primvar, - motionSampleTime); + pxr::VtArray usd_uvs = get_prim_attribute_array( + primvar, motionSampleTime, reports()); if (usd_uvs.empty()) { return; @@ -511,9 +523,10 @@ void USDMeshReader::read_uv_data_primvar(Mesh *mesh, (varying_type == pxr::UsdGeomTokens->vertex && usd_uvs.size() != mesh->totvert) || (varying_type == pxr::UsdGeomTokens->varying && usd_uvs.size() != mesh->totloop)) { - WM_reportf(RPT_WARNING, - "USD Import: UV attribute value '%s' count inconsistent with interpolation type", - primvar.GetName().GetText()); + BKE_reportf(reports(), + RPT_WARNING, + "USD Import: UV attribute value '%s' count inconsistent with interpolation type", + primvar.GetName().GetText()); return; } @@ -522,9 +535,10 @@ void USDMeshReader::read_uv_data_primvar(Mesh *mesh, primvar_name, ATTR_DOMAIN_CORNER); if (!uv_data) { - WM_reportf(RPT_WARNING, - "USD Import: couldn't add UV attribute '%s'", - primvar.GetBaseName().GetText()); + BKE_reportf(reports(), + RPT_WARNING, + "USD Import: couldn't add UV attribute '%s'", + primvar.GetBaseName().GetText()); return; } @@ -588,10 +602,13 @@ void USDMeshReader::copy_prim_array_to_blender_attribute(const Mesh *mesh, MutableSpan attribute) { const pxr::TfToken interp = primvar.GetInterpolation(); - pxr::VtArray primvar_array = get_prim_attribute_array(primvar, motionSampleTime); + pxr::VtArray primvar_array = get_prim_attribute_array( + primvar, motionSampleTime, reports()); if (primvar_array.empty()) { - WM_reportf( - RPT_WARNING, "Unable to get array values for primvar %s", primvar.GetName().GetText()); + BKE_reportf(reports(), + RPT_WARNING, + "Unable to get array values for primvar %s", + primvar.GetName().GetText()); return; } @@ -642,8 +659,9 @@ void USDMeshReader::read_generic_data_primvar(Mesh *mesh, const pxr::TfToken varying_type = primvar.GetInterpolation(); const pxr::TfToken name = pxr::UsdGeomPrimvar::StripPrimvarsName(primvar.GetPrimvarName()); - const std::optional domain = convert_usd_varying_to_blender(varying_type); - const std::optional type = convert_usd_type_to_blender(sdf_type); + const std::optional domain = convert_usd_varying_to_blender(varying_type, + reports()); + const std::optional type = convert_usd_type_to_blender(sdf_type, reports()); if (!domain.has_value() || !type.has_value()) { return; @@ -678,10 +696,11 @@ void USDMeshReader::read_generic_data_primvar(Mesh *mesh, mesh, primvar, motionSampleTime, attribute.span.typed()); break; default: - WM_reportf(RPT_ERROR, - "Generic primvar %s: invalid type %s", - primvar.GetName().GetText(), - sdf_type.GetAsToken().GetText()); + BKE_reportf(reports(), + RPT_ERROR, + "Generic primvar %s: invalid type %s", + primvar.GetName().GetText(), + sdf_type.GetAsToken().GetText()); break; } attribute.finish(); @@ -878,10 +897,11 @@ void USDMeshReader::read_custom_data(const ImportSettings *settings, /* Convert primvars to custom layer data. */ for (pxr::UsdGeomPrimvar &pv : primvars) { if (!pv.HasValue()) { - WM_reportf(RPT_WARNING, - "Skipping primvar %s, mesh %s -- no value", - pv.GetName().GetText(), - &mesh->id.name[2]); + BKE_reportf(reports(), + RPT_WARNING, + "Skipping primvar %s, mesh %s -- no value", + pv.GetName().GetText(), + &mesh->id.name[2]); continue; } @@ -898,7 +918,7 @@ void USDMeshReader::read_custom_data(const ImportSettings *settings, } /* 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) { /* Set the active color name to 'displayColor', if a color primvar * 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->faceVarying, 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) { /* Set the active uv set name to 'st', if a uv set primvar @@ -1139,10 +1159,11 @@ std::optional USDMeshReader::get_local_usd_xform(const float time) return XformResult(pxr::GfMatrix4f(bind_xf), true); } else { - WM_reportf(RPT_WARNING, - "%s: Couldn't compute geom bind transform for %s", - __func__, - prim_.GetPath().GetAsString().c_str()); + BKE_reportf(reports(), + RPT_WARNING, + "%s: Couldn't compute geom bind transform for %s", + __func__, + prim_.GetPath().GetAsString().c_str()); } } } diff --git a/source/blender/io/usd/intern/usd_reader_prim.h b/source/blender/io/usd/intern/usd_reader_prim.h index df896f09dec..62b5add840a 100644 --- a/source/blender/io/usd/intern/usd_reader_prim.h +++ b/source/blender/io/usd/intern/usd_reader_prim.h @@ -9,6 +9,8 @@ #include "usd.h" +#include "WM_types.hh" + #include #include @@ -113,6 +115,12 @@ class USDPrimReader { 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 * maintained by modifiers and constraints, we provide * a reference count to facilitate managing the object diff --git a/source/blender/io/usd/intern/usd_reader_shape.cc b/source/blender/io/usd/intern/usd_reader_shape.cc index 50d1a0b32e0..e09b47ea9fa 100644 --- a/source/blender/io/usd/intern/usd_reader_shape.cc +++ b/source/blender/io/usd/intern/usd_reader_shape.cc @@ -6,6 +6,7 @@ #include "BKE_mesh.hh" #include "BKE_modifier.h" #include "BKE_object.hh" +#include "BKE_report.h" #include "DNA_cachefile_types.h" #include "DNA_mesh_types.h" @@ -118,10 +119,11 @@ bool USDShapeReader::read_mesh_values(double motionSampleTime, return true; } - WM_reportf(RPT_ERROR, - "Unhandled Gprim type: %s (%s)", - prim_.GetTypeName().GetText(), - prim_.GetPath().GetText()); + BKE_reportf(reports(), + RPT_ERROR, + "Unhandled Gprim type: %s (%s)", + prim_.GetTypeName().GetText(), + prim_.GetPath().GetText()); return false; } @@ -230,10 +232,11 @@ bool USDShapeReader::is_time_varying() return geom.GetRadiusAttr().ValueMightBeTimeVarying(); } - WM_reportf(RPT_ERROR, - "Unhandled Gprim type: %s (%s)", - prim_.GetTypeName().GetText(), - prim_.GetPath().GetText()); + BKE_reportf(reports(), + RPT_ERROR, + "Unhandled Gprim type: %s (%s)", + prim_.GetTypeName().GetText(), + prim_.GetPath().GetText()); return false; } diff --git a/source/blender/io/usd/intern/usd_reader_skeleton.cc b/source/blender/io/usd/intern/usd_reader_skeleton.cc index 28f55bc11fa..797330a90f5 100644 --- a/source/blender/io/usd/intern/usd_reader_skeleton.cc +++ b/source/blender/io/usd/intern/usd_reader_skeleton.cc @@ -39,7 +39,7 @@ void USDSkeletonReader::read_object_data(Main *bmain, const double motionSampleT return; } - import_skeleton(bmain, object_, skel_); + import_skeleton(bmain, object_, skel_, reports()); USDXformReader::read_object_data(bmain, motionSampleTime); } diff --git a/source/blender/io/usd/intern/usd_reader_stage.cc b/source/blender/io/usd/intern/usd_reader_stage.cc index a908e5b11d6..cb6e6735412 100644 --- a/source/blender/io/usd/intern/usd_reader_stage.cc +++ b/source/blender/io/usd/intern/usd_reader_stage.cc @@ -44,6 +44,7 @@ #include "BKE_lib_id.h" #include "BKE_modifier.h" +#include "BKE_report.h" #include "DNA_material_types.h" @@ -366,10 +367,11 @@ void USDStageReader::process_armature_modifiers() const std::string skel_path = mesh_reader->get_skeleton_path(); std::map::const_iterator it = usd_path_to_armature.find(skel_path); if (it == usd_path_to_armature.end()) { - WM_reportf(RPT_WARNING, - "%s: Couldn't find armature object corresponding to USD skeleton %s", - __func__, - skel_path.c_str()); + BKE_reportf(reports(), + RPT_WARNING, + "%s: Couldn't find armature object corresponding to USD skeleton %s", + __func__, + skel_path.c_str()); } amd->object = it->second; } diff --git a/source/blender/io/usd/intern/usd_reader_stage.h b/source/blender/io/usd/intern/usd_reader_stage.h index b0d50f0902a..cc409271ff9 100644 --- a/source/blender/io/usd/intern/usd_reader_stage.h +++ b/source/blender/io/usd/intern/usd_reader_stage.h @@ -5,6 +5,8 @@ struct Main; +#include "WM_types.hh" + #include "usd.h" #include "usd_reader_prim.h" @@ -79,6 +81,12 @@ class USDStageReader { 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(); const std::vector &readers() const diff --git a/source/blender/io/usd/intern/usd_skel_convert.cc b/source/blender/io/usd/intern/usd_skel_convert.cc index d0da2a68606..de60076a648 100644 --- a/source/blender/io/usd/intern/usd_skel_convert.cc +++ b/source/blender/io/usd/intern/usd_skel_convert.cc @@ -32,6 +32,7 @@ #include "BKE_modifier.h" #include "BKE_object.hh" #include "BKE_object_deform.h" +#include "BKE_report.h" #include "BLI_math_vector.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 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 reports: the storage for potential warning or error reports (generated using BKE_report + * API). */ void import_skeleton_curves(Main *bmain, Object *arm_obj, const pxr::UsdSkelSkeletonQuery &skel_query, - const std::map &joint_to_bone_map) + const std::map &joint_to_bone_map, + ReportList *reports) { if (!(bmain && arm_obj && skel_query)) { @@ -218,18 +222,20 @@ void import_skeleton_curves(Main *bmain, /* Get the world space joint transforms at bind time. */ pxr::VtMatrix4dArray bind_xforms; if (!skel_query.GetJointWorldBindTransforms(&bind_xforms)) { - WM_reportf(RPT_WARNING, - "%s: Couldn't get world bind transforms for skeleton %s", - __func__, - skel_query.GetSkeleton().GetPrim().GetPath().GetAsString().c_str()); + BKE_reportf(reports, + RPT_WARNING, + "%s: Couldn't get world bind transforms for skeleton %s", + __func__, + skel_query.GetSkeleton().GetPrim().GetPath().GetAsString().c_str()); return; } if (bind_xforms.size() != joint_order.size()) { - WM_reportf(RPT_WARNING, - "%s: Number of bind transforms doesn't match the number of joints for skeleton %s", - __func__, - skel_query.GetSkeleton().GetPrim().GetPath().GetAsString().c_str()); + BKE_reportf(reports, + RPT_WARNING, + "%s: Number of bind transforms doesn't match the number of joints for skeleton %s", + __func__, + skel_query.GetSkeleton().GetPrim().GetPath().GetAsString().c_str()); return; } @@ -334,6 +340,7 @@ namespace blender::io::usd { void import_blendshapes(Main *bmain, Object *mesh_obj, const pxr::UsdPrim &prim, + ReportList *reports, const bool import_anim) { if (!(mesh_obj && mesh_obj->data && mesh_obj->type == OB_MESH && prim)) { @@ -363,10 +370,11 @@ void import_blendshapes(Main *bmain, pxr::SdfPathVector targets; if (!skel_api.GetBlendShapeTargetsRel().GetTargets(&targets)) { - WM_reportf(RPT_WARNING, - "%s: Couldn't get blendshape targets for prim %s", - __func__, - prim.GetPath().GetAsString().c_str()); + BKE_reportf(reports, + RPT_WARNING, + "%s: Couldn't get blendshape targets for prim %s", + __func__, + prim.GetPath().GetAsString().c_str()); return; } @@ -390,20 +398,22 @@ void import_blendshapes(Main *bmain, /* Sanity check. */ if (targets.size() != blendshapes.size()) { - WM_reportf(RPT_WARNING, - "%s: Number of blendshapes doesn't match number of blendshape targets for prim %s", - __func__, - prim.GetPath().GetAsString().c_str()); + BKE_reportf(reports, + RPT_WARNING, + "%s: Number of blendshapes doesn't match number of blendshape targets for prim %s", + __func__, + prim.GetPath().GetAsString().c_str()); return; } pxr::UsdStageRefPtr stage = prim.GetStage(); if (!stage) { - WM_reportf(RPT_WARNING, - "%s: Couldn't get stage for prim %s", - __func__, - prim.GetPath().GetAsString().c_str()); + BKE_reportf(reports, + RPT_WARNING, + "%s: Couldn't get stage for prim %s", + __func__, + prim.GetPath().GetAsString().c_str()); return; } @@ -440,16 +450,20 @@ void import_blendshapes(Main *bmain, pxr::VtVec3fArray offsets; if (!blendshape.GetOffsetsAttr().Get(&offsets)) { - WM_reportf(RPT_WARNING, - "%s: Couldn't get offsets for blend shape %s", - __func__, - path.GetAsString().c_str()); + BKE_reportf(reports, + RPT_WARNING, + "%s: Couldn't get offsets for blend shape %s", + __func__, + path.GetAsString().c_str()); continue; } if (offsets.empty()) { - WM_reportf( - RPT_WARNING, "%s: No offsets for blend shape %s", __func__, path.GetAsString().c_str()); + BKE_reportf(reports, + RPT_WARNING, + "%s: No offsets for blend shape %s", + __func__, + path.GetAsString().c_str()); continue; } @@ -473,7 +487,8 @@ void import_blendshapes(Main *bmain, * offset to the key block point. */ for (int a = 0; a < kb->totelem; ++a, fp += 3) { if (a >= offsets.size()) { - WM_reportf( + BKE_reportf( + reports, RPT_WARNING, "%s: Number of offsets greater than number of mesh vertices for blend shape %s", __func__, @@ -495,7 +510,8 @@ void import_blendshapes(Main *bmain, continue; } if (a >= offsets.size()) { - WM_reportf( + BKE_reportf( + reports, RPT_WARNING, "%s: Number of offsets greater than number of mesh vertices for blend shape %s", __func__, @@ -621,6 +637,7 @@ void import_blendshapes(Main *bmain, void import_skeleton(Main *bmain, Object *arm_obj, const pxr::UsdSkelSkeleton &skel, + ReportList *reports, const bool import_anim) { if (!(arm_obj && arm_obj->data && arm_obj->type == OB_ARMATURE)) { @@ -631,10 +648,11 @@ void import_skeleton(Main *bmain, pxr::UsdSkelSkeletonQuery skel_query = skel_cache.GetSkelQuery(skel); if (!skel_query.IsValid()) { - WM_reportf(RPT_WARNING, - "%s: Couldn't query skeleton %s", - __func__, - skel.GetPath().GetAsString().c_str()); + BKE_reportf(reports, + RPT_WARNING, + "%s: Couldn't query skeleton %s", + __func__, + skel.GetPath().GetAsString().c_str()); return; } @@ -643,10 +661,11 @@ void import_skeleton(Main *bmain, pxr::VtTokenArray joint_order = skel_query.GetJointOrder(); if (joint_order.size() != skel_topology.size()) { - WM_reportf(RPT_WARNING, - "%s: Topology and joint order size mismatch for skeleton %s", - __func__, - skel.GetPath().GetAsString().c_str()); + BKE_reportf(reports, + RPT_WARNING, + "%s: Topology and joint order size mismatch for skeleton %s", + __func__, + skel.GetPath().GetAsString().c_str()); return; } @@ -668,8 +687,11 @@ void import_skeleton(Main *bmain, std::string name = pxr::SdfPath(joint).GetName(); EditBone *bone = ED_armature_ebone_add(arm, name.c_str()); if (!bone) { - WM_reportf( - RPT_WARNING, "%s: Couldn't add bone for joint %s", __func__, joint.GetString().c_str()); + BKE_reportf(reports, + RPT_WARNING, + "%s: Couldn't add bone for joint %s", + __func__, + joint.GetString().c_str()); edit_bones.push_back(nullptr); continue; } @@ -680,28 +702,31 @@ void import_skeleton(Main *bmain, /* Sanity check: we should have created a bone for each joint. */ const size_t num_joints = skel_topology.GetNumJoints(); if (edit_bones.size() != num_joints) { - WM_reportf(RPT_WARNING, - "%s: Mismatch in bone and joint counts for skeleton %s", - __func__, - skel.GetPath().GetAsString().c_str()); + BKE_reportf(reports, + RPT_WARNING, + "%s: Mismatch in bone and joint counts for skeleton %s", + __func__, + skel.GetPath().GetAsString().c_str()); return; } /* Get the world space joint transforms at bind time. */ pxr::VtMatrix4dArray bind_xforms; if (!skel_query.GetJointWorldBindTransforms(&bind_xforms)) { - WM_reportf(RPT_WARNING, - "%s: Couldn't get world bind transforms for skeleton %s", - __func__, - skel.GetPath().GetAsString().c_str()); + BKE_reportf(reports, + RPT_WARNING, + "%s: Couldn't get world bind transforms for skeleton %s", + __func__, + skel.GetPath().GetAsString().c_str()); return; } if (bind_xforms.size() != num_joints) { - WM_reportf(RPT_WARNING, - "%s: Mismatch in bind xforms and joint counts for skeleton %s", - __func__, - skel.GetPath().GetAsString().c_str()); + BKE_reportf(reports, + RPT_WARNING, + "%s: Mismatch in bind xforms and joint counts for skeleton %s", + __func__, + skel.GetPath().GetAsString().c_str()); return; } @@ -746,7 +771,8 @@ void import_skeleton(Main *bmain, bool valid_skeleton = true; if (negative_determinant) { valid_skeleton = false; - WM_reportf( + BKE_reportf( + reports, RPT_WARNING, "USD Skeleton Import: bone matrices with negative determinants detected in prim %s. " "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); 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)) { return; @@ -912,10 +941,11 @@ void import_mesh_skel_bindings(Main *bmain, Object *mesh_obj, const pxr::UsdPrim /* We expect the element counts to match. */ if (joint_indices_elem_size != joint_weights_elem_size) { - WM_reportf(RPT_WARNING, - "%s: Joint weights and joint indices element size mismatch for prim %s", - __func__, - prim.GetPath().GetAsString().c_str()); + BKE_reportf(reports, + RPT_WARNING, + "%s: Joint weights and joint indices element size mismatch for prim %s", + __func__, + prim.GetPath().GetAsString().c_str()); return; } @@ -931,10 +961,11 @@ void import_mesh_skel_bindings(Main *bmain, Object *mesh_obj, const pxr::UsdPrim } if (joint_indices.size() != joint_weights.size()) { - WM_reportf(RPT_WARNING, - "%s: Joint weights and joint indices size mismatch for prim %s", - __func__, - prim.GetPath().GetAsString().c_str()); + BKE_reportf(reports, + RPT_WARNING, + "%s: Joint weights and joint indices size mismatch for prim %s", + __func__, + prim.GetPath().GetAsString().c_str()); return; } @@ -944,11 +975,12 @@ void import_mesh_skel_bindings(Main *bmain, Object *mesh_obj, const pxr::UsdPrim /* Sanity check: we expect only vertex or constant interpolation. */ if (!ELEM(interp, pxr::UsdGeomTokens->vertex, pxr::UsdGeomTokens->constant)) { - WM_reportf(RPT_WARNING, - "%s: Unexpected joint weights interpolation type %s for prim %s", - __func__, - interp.GetString().c_str(), - prim.GetPath().GetAsString().c_str()); + BKE_reportf(reports, + RPT_WARNING, + "%s: Unexpected joint weights interpolation type %s for prim %s", + __func__, + interp.GetString().c_str(), + prim.GetPath().GetAsString().c_str()); return; } @@ -956,18 +988,20 @@ void import_mesh_skel_bindings(Main *bmain, Object *mesh_obj, const pxr::UsdPrim if (interp == pxr::UsdGeomTokens->vertex && joint_weights.size() != mesh->totvert * joint_weights_elem_size) { - WM_reportf(RPT_WARNING, - "%s: Joint weights of unexpected size for vertex interpolation for prim %s", - __func__, - prim.GetPath().GetAsString().c_str()); + BKE_reportf(reports, + RPT_WARNING, + "%s: Joint weights of unexpected size for vertex interpolation for prim %s", + __func__, + prim.GetPath().GetAsString().c_str()); return; } if (interp == pxr::UsdGeomTokens->constant && joint_weights.size() != joint_weights_elem_size) { - WM_reportf(RPT_WARNING, - "%s: Joint weights of unexpected size for constant interpolation for prim %s", - __func__, - prim.GetPath().GetAsString().c_str()); + BKE_reportf(reports, + RPT_WARNING, + "%s: Joint weights of unexpected size for constant interpolation for prim %s", + __func__, + prim.GetPath().GetAsString().c_str()); return; } @@ -989,10 +1023,11 @@ void import_mesh_skel_bindings(Main *bmain, Object *mesh_obj, const pxr::UsdPrim } if (BKE_object_defgroup_data_create(static_cast(mesh_obj->data)) == nullptr) { - WM_reportf(RPT_WARNING, - "%s: Error creating deform group data for mesh %s", - __func__, - mesh_obj->id.name + 2); + BKE_reportf(reports, + RPT_WARNING, + "%s: Error creating deform group data for mesh %s", + __func__, + mesh_obj->id.name + 2); return; } diff --git a/source/blender/io/usd/intern/usd_skel_convert.h b/source/blender/io/usd/intern/usd_skel_convert.h index aa130980b15..a2fd237a877 100644 --- a/source/blender/io/usd/intern/usd_skel_convert.h +++ b/source/blender/io/usd/intern/usd_skel_convert.h @@ -3,6 +3,7 @@ * SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once +#include "DNA_windowmanager_types.h" #include #include #include @@ -35,12 +36,15 @@ struct ImportSettings; * \param bmain: Main pointer * \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 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 * animation curves */ void import_blendshapes(Main *bmain, Object *mesh_obj, const pxr::UsdPrim &prim, + ReportList *reports, bool import_anim = true); /** @@ -51,12 +55,15 @@ void import_blendshapes(Main *bmain, * \param bmain: Main pointer * \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 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 * animation curves */ void import_skeleton(Main *bmain, Object *arm_obj, const pxr::UsdSkelSkeleton &skel, + ReportList *reports, bool import_anim = true); /** * 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 obj: Mesh object to which an armature modifier will be added * \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 diff --git a/source/blender/io/usd/intern/usd_writer_abstract.cc b/source/blender/io/usd/intern/usd_writer_abstract.cc index 4c41f8f67a0..7140f51ef3b 100644 --- a/source/blender/io/usd/intern/usd_writer_abstract.cc +++ b/source/blender/io/usd/intern/usd_writer_abstract.cc @@ -9,6 +9,8 @@ #include #include "BKE_customdata.h" +#include "BKE_report.h" + #include "BLI_assert.h" #include "DNA_mesh_types.h" @@ -167,9 +169,10 @@ void USDAbstractWriter::author_extent(const pxr::UsdTimeCode timecode, pxr::UsdG pxr::GfBBox3d bounds = bboxCache.ComputeLocalBound(prim.GetPrim()); if (pxr::GfBBox3d() == bounds) { /* This will occur, for example, if a mesh does not have any vertices. */ - WM_reportf(RPT_WARNING, - "USD Export: no bounds could be computed for %s", - prim.GetPrim().GetName().GetText()); + BKE_reportf(reports(), + RPT_WARNING, + "USD Export: no bounds could be computed for %s", + prim.GetPrim().GetName().GetText()); return; } diff --git a/source/blender/io/usd/intern/usd_writer_abstract.h b/source/blender/io/usd/intern/usd_writer_abstract.h index b1f8a0e2fb4..94f5c2224c7 100644 --- a/source/blender/io/usd/intern/usd_writer_abstract.h +++ b/source/blender/io/usd/intern/usd_writer_abstract.h @@ -16,7 +16,10 @@ #include "DEG_depsgraph_query.hh" +#include "WM_types.hh" + #include "DNA_material_types.h" +#include "DNA_windowmanager_types.h" struct Material; @@ -51,6 +54,12 @@ class USDAbstractWriter : public AbstractHierarchyWriter { 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: virtual void do_write(HierarchyContext &context) = 0; std::string get_export_file_path() const; diff --git a/source/blender/io/usd/intern/usd_writer_curves.cc b/source/blender/io/usd/intern/usd_writer_curves.cc index 77f5e07ba08..1c2d0119b11 100644 --- a/source/blender/io/usd/intern/usd_writer_curves.cc +++ b/source/blender/io/usd/intern/usd_writer_curves.cc @@ -18,6 +18,7 @@ #include "BKE_curves.hh" #include "BKE_lib_id.h" #include "BKE_material.h" +#include "BKE_report.h" #include "BLI_math_geom.h" #include "BLT_translation.h" @@ -26,7 +27,6 @@ #include "RNA_enum_types.hh" #include "WM_api.hh" -#include "WM_types.hh" 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 &widths, const pxr::VtArray &segments, const pxr::VtIntArray &control_point_counts, - const bool is_cyclic) + const bool is_cyclic, + ReportList *reports) { if (widths.empty()) { return pxr::TfToken(); @@ -110,7 +111,7 @@ static pxr::TfToken get_curve_width_interpolation(const pxr::VtArray &wid 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(); } @@ -158,7 +159,8 @@ static void populate_curve_props(const bke::CurvesGeometry &geometry, pxr::VtArray &widths, pxr::TfToken &interpolation, const bool is_cyclic, - const bool is_cubic) + const bool is_cubic, + ReportList *reports) { const int num_curves = geometry.curve_num; const Span 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); 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, @@ -246,7 +249,8 @@ static void populate_curve_props_for_bezier(const bke::CurvesGeometry &geometry, pxr::VtIntArray &control_point_counts, pxr::VtArray &widths, pxr::TfToken &interpolation, - const bool is_cyclic) + const bool is_cyclic, + ReportList *reports) { 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); 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, @@ -397,7 +402,8 @@ void USDCurvesWriter::do_write(HierarchyContext &context) }); 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; } @@ -413,8 +419,9 @@ void USDCurvesWriter::do_write(HierarchyContext &context) } if (!all_same_cyclic_type) { - WM_report(RPT_WARNING, - "Cannot export mixed cyclic and non-cyclic curves in the same Curves object"); + BKE_report(reports(), + RPT_WARNING, + "Cannot export mixed cyclic and non-cyclic curves in the same Curves object"); return; } @@ -441,12 +448,13 @@ void USDCurvesWriter::do_write(HierarchyContext &context) RNA_enum_name_from_value( rna_enum_curves_type_items, int(curve_type), ¤t_curve_type_name); - WM_reportf(RPT_WARNING, - "USD does not support animating curve types. The curve type changes from %s to " - "%s on frame %f", - IFACE_(first_frame_curve_type_name), - IFACE_(current_curve_type_name), - timecode.GetValue()); + BKE_reportf(reports(), + RPT_WARNING, + "USD does not support animating curve types. The curve type changes from %s to " + "%s on frame %f", + IFACE_(first_frame_curve_type_name), + IFACE_(current_curve_type_name), + timecode.GetValue()); return; } @@ -454,22 +462,34 @@ void USDCurvesWriter::do_write(HierarchyContext &context) case CURVE_TYPE_POLY: usd_curves = DefineUsdGeomBasisCurves(pxr::VtValue(), is_cyclic, false); - populate_curve_props( - geometry, verts, control_point_counts, widths, interpolation, is_cyclic, false); + populate_curve_props(geometry, + verts, + control_point_counts, + widths, + interpolation, + is_cyclic, + false, + reports()); break; case CURVE_TYPE_CATMULL_ROM: usd_curves = DefineUsdGeomBasisCurves( pxr::VtValue(pxr::UsdGeomTokens->catmullRom), is_cyclic, true); - populate_curve_props( - geometry, verts, control_point_counts, widths, interpolation, is_cyclic, true); + populate_curve_props(geometry, + verts, + control_point_counts, + widths, + interpolation, + is_cyclic, + true, + reports()); break; case CURVE_TYPE_BEZIER: usd_curves = DefineUsdGeomBasisCurves( pxr::VtValue(pxr::UsdGeomTokens->bezier), is_cyclic, true); 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; case CURVE_TYPE_NURBS: { pxr::VtArray knots; diff --git a/source/blender/io/usd/intern/usd_writer_material.cc b/source/blender/io/usd/intern/usd_writer_material.cc index 2b01eb0ffbf..dbff0be8fc4 100644 --- a/source/blender/io/usd/intern/usd_writer_material.cc +++ b/source/blender/io/usd/intern/usd_writer_material.cc @@ -13,6 +13,7 @@ #include "BKE_main.h" #include "BKE_node.hh" #include "BKE_node_runtime.hh" +#include "BKE_report.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, const std::string &export_dir, - const bool allow_overwrite) + const bool allow_overwrite, + ReportList *reports) { 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; 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. */ static void copy_tiled_textures(Image *ima, const std::string &dest_dir, - const bool allow_overwrite) + const bool allow_overwrite, + ReportList *reports) { char src_path[FILE_MAX]; get_absolute_path(ima, src_path); @@ -743,17 +747,21 @@ static void copy_tiled_textures(Image *ima, /* Copy the file. */ if (BLI_copy(src_tile_path, dest_tile_path) != 0) { - WM_reportf(RPT_WARNING, - "USD export: could not copy texture tile from %s to %s", - src_tile_path, - dest_tile_path); + BKE_reportf(reports, + RPT_WARNING, + "USD export: could not copy texture tile from %s to %s", + src_tile_path, + dest_tile_path); } } MEM_SAFE_FREE(udim_pattern); } /* Copy the given image to the destination directory. */ -static void copy_single_file(Image *ima, const std::string &dest_dir, const bool allow_overwrite) +static void copy_single_file(Image *ima, + const std::string &dest_dir, + const bool allow_overwrite, + ReportList *reports) { char source_path[FILE_MAX]; 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. */ if (BLI_copy(source_path, dest_path) != 0) { - WM_reportf( - RPT_WARNING, "USD export: could not copy texture from %s to %s", source_path, dest_path); + BKE_reportf(reports, + 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); 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) { - 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 { - 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); } - 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; } diff --git a/source/blender/io/usd/intern/usd_writer_mesh.cc b/source/blender/io/usd/intern/usd_writer_mesh.cc index 2b30a68a3bd..53d98944246 100644 --- a/source/blender/io/usd/intern/usd_writer_mesh.cc +++ b/source/blender/io/usd/intern/usd_writer_mesh.cc @@ -24,6 +24,7 @@ #include "BKE_mesh_wrapper.hh" #include "BKE_modifier.h" #include "BKE_object.hh" +#include "BKE_report.h" #include "DEG_depsgraph.hh" @@ -128,7 +129,7 @@ void USDGenericMeshWriter::write_custom_data(const Mesh *mesh, pxr::UsdGeomMesh } static std::optional convert_blender_type_to_usd( - const eCustomDataType blender_type) + const eCustomDataType blender_type, ReportList *reports) { switch (blender_type) { case CD_PROP_FLOAT: @@ -147,13 +148,13 @@ static std::optional convert_blender_type_to_usd( case CD_PROP_QUATERNION: return pxr::SdfValueTypeNames->QuatfArray; default: - WM_reportf(RPT_WARNING, "Unsupported type for mesh data"); + BKE_reportf(reports, RPT_WARNING, "Unsupported type for mesh data"); return std::nullopt; } } static const std::optional convert_blender_domain_to_usd( - const eAttrDomain blender_domain) + const eAttrDomain blender_domain, ReportList *reports) { switch (blender_domain) { case ATTR_DOMAIN_CORNER: @@ -165,7 +166,7 @@ static const std::optional convert_blender_domain_to_usd( /* Notice: Edge types are not supported in USD! */ default: - WM_reportf(RPT_WARNING, "Unsupported type for mesh data"); + BKE_reportf(reports, RPT_WARNING, "Unsupported type for mesh data"); return std::nullopt; } } @@ -231,9 +232,10 @@ void USDGenericMeshWriter::write_generic_data(const Mesh *mesh, const pxr::UsdGeomPrimvarsAPI pvApi = pxr::UsdGeomPrimvarsAPI(usd_mesh); /* Varying type depends on original domain. */ - const std::optional prim_varying = convert_blender_domain_to_usd(meta_data.domain); + const std::optional prim_varying = convert_blender_domain_to_usd(meta_data.domain, + reports()); const std::optional prim_attr_type = convert_blender_type_to_usd( - meta_data.data_type); + meta_data.data_type, reports()); const GVArraySpan attribute = *mesh->attributes().lookup( attribute_id, meta_data.domain, meta_data.data_type); @@ -242,10 +244,11 @@ void USDGenericMeshWriter::write_generic_data(const Mesh *mesh, } if (!prim_varying || !prim_attr_type) { - WM_reportf(RPT_WARNING, - "Mesh %s, Attribute %s cannot be converted to USD", - &mesh->id.name[2], - attribute_id.name().data()); + BKE_reportf(reports(), + RPT_WARNING, + "Mesh %s, Attribute %s cannot be converted to USD", + &mesh->id.name[2], + attribute_id.name().data()); return; } diff --git a/source/blender/io/usd/intern/usd_writer_volume.cc b/source/blender/io/usd/intern/usd_writer_volume.cc index 784f61e81e5..8d85f924735 100644 --- a/source/blender/io/usd/intern/usd_writer_volume.cc +++ b/source/blender/io/usd/intern/usd_writer_volume.cc @@ -11,6 +11,7 @@ #include "DNA_volume_types.h" #include "DNA_windowmanager_types.h" +#include "BKE_report.h" #include "BKE_volume.h" #include "BLI_fileops.h" @@ -47,9 +48,10 @@ void USDVolumeWriter::do_write(HierarchyContext &context) auto vdb_file_path = resolve_vdb_file(volume); if (!vdb_file_path.has_value()) { - WM_reportf(RPT_WARNING, - "USD Export: failed to resolve .vdb file for object: %s", - volume->id.name + 2); + BKE_reportf(reports(), + RPT_WARNING, + "USD Export: failed to resolve .vdb file for object: %s", + volume->id.name + 2); return; } @@ -58,9 +60,10 @@ void USDVolumeWriter::do_write(HierarchyContext &context) vdb_file_path = relative_vdb_file_path; } else { - WM_reportf(RPT_WARNING, - "USD Export: couldn't construct relative file path for .vdb file, absolute path " - "will be used instead"); + BKE_reportf(reports(), + RPT_WARNING, + "USD Export: couldn't construct relative file path for .vdb file, absolute path " + "will be used instead"); } } diff --git a/source/blender/io/usd/tests/usd_curves_test.cc b/source/blender/io/usd/tests/usd_curves_test.cc index ccdcc97681f..1a1aab8cdd6 100644 --- a/source/blender/io/usd/tests/usd_curves_test.cc +++ b/source/blender/io/usd/tests/usd_curves_test.cc @@ -110,7 +110,7 @@ TEST_F(UsdCurvesTest, usd_export_curves) USDExportParams params; - const bool result = USD_export(context, output_filename.c_str(), ¶ms, false); + const bool result = USD_export(context, output_filename.c_str(), ¶ms, false, nullptr); EXPECT_TRUE(result) << "USD export should succed."; pxr::UsdStageRefPtr stage = pxr::UsdStage::Open(output_filename); diff --git a/source/blender/io/usd/tests/usd_export_test.cc b/source/blender/io/usd/tests/usd_export_test.cc index 90fa3b366f4..dd93ae2a4b5 100644 --- a/source/blender/io/usd/tests/usd_export_test.cc +++ b/source/blender/io/usd/tests/usd_export_test.cc @@ -214,7 +214,7 @@ TEST_F(UsdExportTest, usd_export_rain_mesh) params.export_uvmaps = false; params.visible_objects_only = true; - bool result = USD_export(context, output_filename.c_str(), ¶ms, false); + bool result = USD_export(context, output_filename.c_str(), ¶ms, false, nullptr); ASSERT_TRUE(result) << "Writing to " << output_filename << " failed!"; pxr::UsdStageRefPtr stage = pxr::UsdStage::Open(output_filename); @@ -280,7 +280,7 @@ TEST_F(UsdExportTest, usd_export_material) params.generate_preview_surface = true; params.relative_paths = false; - const bool result = USD_export(context, output_filename.c_str(), ¶ms, false); + const bool result = USD_export(context, output_filename.c_str(), ¶ms, false, nullptr); ASSERT_TRUE(result) << "Unable to export stage to " << output_filename; pxr::UsdStageRefPtr stage = pxr::UsdStage::Open(output_filename); diff --git a/source/blender/io/usd/tests/usd_usdz_export_test.cc b/source/blender/io/usd/tests/usd_usdz_export_test.cc index d04eaae046f..d36ecf9c5f3 100644 --- a/source/blender/io/usd/tests/usd_usdz_export_test.cc +++ b/source/blender/io/usd/tests/usd_usdz_export_test.cc @@ -98,7 +98,7 @@ TEST_F(UsdUsdzExportTest, usdz_export) params.export_materials = false; params.visible_objects_only = false; - bool result = USD_export(context, output_filepath, ¶ms, false); + bool result = USD_export(context, output_filepath, ¶ms, false, nullptr); ASSERT_TRUE(result) << "usd export to " << output_filepath << " failed."; pxr::UsdStageRefPtr stage = pxr::UsdStage::Open(output_filepath); diff --git a/source/blender/io/usd/usd.h b/source/blender/io/usd/usd.h index 5908bdfae26..3b40d86cfb7 100644 --- a/source/blender/io/usd/usd.h +++ b/source/blender/io/usd/usd.h @@ -16,6 +16,7 @@ struct CacheArchiveHandle; struct CacheReader; struct Object; struct bContext; +struct wmJobWorkerStatus; /* Behavior when the name of an imported material * conflicts with an existing material. */ @@ -55,6 +56,10 @@ struct USDExportParams { bool overwrite_textures = true; bool relative_paths = true; 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 { @@ -91,6 +96,10 @@ struct USDImportParams { char import_textures_dir[768]; /* FILE_MAXDIR */ eUSDTexNameCollisionMode tex_name_collision_mode; 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 @@ -115,12 +124,14 @@ USDMeshReadParams create_mesh_read_params(double motion_sample_time, int read_fl bool USD_export(struct bContext *C, const char *filepath, const struct USDExportParams *params, - bool as_background_job); + bool as_background_job, + ReportList *reports); bool USD_import(struct bContext *C, const char *filepath, const struct USDImportParams *params, - bool as_background_job); + bool as_background_job, + ReportList *reports); int USD_get_version(void); diff --git a/tests/python/bl_usd_import_test.py b/tests/python/bl_usd_import_test.py index ad3fbd31ec0..595600a7eb8 100644 --- a/tests/python/bl_usd_import_test.py +++ b/tests/python/bl_usd_import_test.py @@ -46,8 +46,12 @@ class USDImportTest(AbstractUSDTest): self.assertEqual({'FINISHED'}, res, f"Unable to import USD file {infile}") infile = str(self.testdir / "this_file_doesn't_exist.usda") - res = bpy.ops.wm.usd_import(filepath=infile) - self.assertEqual({'CANCELLED'}, res, "Was somehow able to import a non-existent USD file!") + # RPT_ERROR Reports from operators generate `RuntimeError` python exceptions. + try: + res = bpy.ops.wm.usd_import(filepath=infile) + 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): """Test importing a simple object hierarchy from a USDA file."""