Make wmJob worker thread use their own reports list instead of WM one. #113548

Closed
Bastien Montagne wants to merge 2 commits from mont29:tmp-wmjob-report-refactor into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
33 changed files with 619 additions and 327 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);
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;
}
@ -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, &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;
}

View File

@ -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);
mont29 marked this conversation as resolved Outdated

Does it mean that some reports which are currently sown in the UI will no longer be visible to regular Blender artists?

Does it mean that some reports which are currently sown in the UI will no longer be visible to regular Blender artists?

No, because currently there are no reports shown in the UI at all (locking UI seems to also block the report popup from appearing). Which is something else to be tackled too - at some point.

No, because currently there are no reports shown in the UI at all (locking UI seems to also block the report popup from appearing). Which is something else to be tackled too - at some point.

Hrrrrm actually, not sure, in the Hydra case... would need to test.

Hrrrrm actually, not sure, in the Hydra case... would need to test.

Short answer after some testing: I do not know.
I could not get a complex scene (from Pets) to render with Hydra, just gives me an empty result after a split second...

Will just add a wrapper with temp ReportList and then move it to WM at the end here I guess for now. Although this code is not hit with default Hydra settings, you need to switch from Hydra to USD export method in the Hydra Debug panel to actually use this codepath.

Short answer after some testing: I do not know. I could not get a complex scene (from Pets) to render with Hydra, just gives me an empty result after a split second... Will just add a wrapper with temp `ReportList` and then move it to WM at the end here I guess for now. Although this code is not hit with default Hydra settings, you need to switch from `Hydra` to `USD` export method in the `Hydra Debug` panel to actually use this codepath.
/* 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<pxr::UsdImagingDelegate>(render_index_, delegate_id_);
delegate_->Populate(stage_->GetPseudoRoot());
WM_reports_add(nullptr, &worker_reports);
}
} // namespace blender::io::hydra

View File

@ -10,6 +10,7 @@
#include <pxr/usd/ar/writableAsset.h>
#include "BKE_main.h"
#include "BKE_report.h"
#include "BLI_fileops.h"
#include "BLI_path_util.h"
@ -52,17 +53,18 @@ static std::pair<std::string, std::string> 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<std::string, std::string> split = pxr::ArSplitPackageRelativePathInner(src_path);
if (split.second.empty()) {
WM_reportf(RPT_WARNING,
"%s: Couldn't determine package-relative file name from path %s",
__func__,
src_path);
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<std::string, std::string> splitPath = split_udim_pattern(src_path);
if (splitPath.first.empty() || splitPath.second.empty()) {
WM_reportf(RPT_ERROR, "%s: Couldn't split UDIM pattern %s", __func__, src_path);
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<pxr::ArAsset> src_asset = ar.OpenAsset(src_path);
if (!src_asset) {
WM_reportf(
RPT_ERROR, "%s: Can't open source asset %s", __func__, src_path.GetPathString().c_str());
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<const char> buf = src_asset->GetBuffer();
if (!buf) {
WM_reportf(RPT_ERROR,
"%s: Null buffer for source asset %s",
__func__,
src_path.GetPathString().c_str());
BKE_reportf(reports,
RPT_ERROR,
"%s: Null buffer for source asset %s",
__func__,
src_path.GetPathString().c_str());
return false;
}
std::shared_ptr<pxr::ArWritableAsset> dst_asset = ar.OpenAssetForWrite(
dst_path, pxr::ArResolver::WriteMode::Replace);
if (!dst_asset) {
WM_reportf(RPT_ERROR,
"%s: Can't open destination asset %s for writing",
__func__,
src_path.GetPathString().c_str());
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)

View File

@ -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.

View File

@ -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 &params)
{
@ -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 &params,
Depsgraph *depsgraph,
const char *filepath,
wmJobWorkerStatus *worker_status)
pxr::UsdStageRefPtr export_to_stage(const USDExportParams &params,
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 &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. */
if (scene->r.cfra != orig_frame) {
@ -284,14 +290,6 @@ static pxr::UsdStageRefPtr export_to_stage(const USDExportParams &params,
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)
{
ExportJobData *data = static_cast<ExportJobData *>(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;

View File

@ -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;

View File

@ -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<void>(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();
}

View File

@ -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

View File

@ -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

View File

@ -5,6 +5,8 @@
#include "usd.h"
#include "WM_types.hh"
#include "BLI_map.hh"
#include <pxr/usd/usdShade/material.h>
@ -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;

View File

@ -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<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 = []() {
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);
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<eCustomDataType> convert_usd_type_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 = []() {
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);
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<typename T>
pxr::VtArray<T> get_prim_attribute_array(const pxr::UsdGeomPrimvar &primvar,
const double motionSampleTime)
const double motionSampleTime,
ReportList *reports)
{
pxr::VtArray<T> 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<pxr::VtArray<T>>()) {
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<pxr::GfVec3f> usd_colors = get_prim_attribute_array<pxr::GfVec3f>(primvar,
motionSampleTime);
pxr::VtArray<pxr::GfVec3f> usd_colors = get_prim_attribute_array<pxr::GfVec3f>(
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<ColorGeometry4f>(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<pxr::GfVec2f> usd_uvs = get_prim_attribute_array<pxr::GfVec2f>(primvar,
motionSampleTime);
pxr::VtArray<pxr::GfVec2f> usd_uvs = get_prim_attribute_array<pxr::GfVec2f>(
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<BlenderT> attribute)
{
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()) {
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<eAttrDomain> domain = convert_usd_varying_to_blender(varying_type);
const std::optional<eCustomDataType> type = convert_usd_type_to_blender(sdf_type);
const std::optional<eAttrDomain> domain = convert_usd_varying_to_blender(varying_type,
reports());
const std::optional<eCustomDataType> 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<bool>());
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<XformResult> 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());
}
}
}

View File

@ -9,6 +9,8 @@
#include "usd.h"
#include "WM_types.hh"
#include <pxr/usd/usd/prim.h>
#include <map>
@ -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

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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<std::string, Object *>::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;
}

View File

@ -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<USDPrimReader *> &readers() const

View File

@ -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<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)) {
@ -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<ID *>(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;
}

View File

@ -3,6 +3,7 @@
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "DNA_windowmanager_types.h"
#include <map>
#include <pxr/usd/usd/prim.h>
#include <pxr/usd/usdSkel/skeletonQuery.h>
@ -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

View File

@ -9,6 +9,8 @@
#include <pxr/usd/usdGeom/bboxCache.h>
#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;
}

View File

@ -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;

View File

@ -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<float> &widths,
const pxr::VtArray<int> &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<float> &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<float> &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<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);
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<float> &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), &current_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<double> knots;

View File

@ -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;
}

View File

@ -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<pxr::SdfValueTypeName> 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<pxr::SdfValueTypeName> 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<pxr::TfToken> 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<pxr::TfToken> 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<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(
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;
}

View File

@ -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");
}
}

View File

@ -110,7 +110,7 @@ TEST_F(UsdCurvesTest, usd_export_curves)
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.";
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.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!";
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(), &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;
pxr::UsdStageRefPtr stage = pxr::UsdStage::Open(output_filename);

View File

@ -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, &params, false);
bool result = USD_export(context, output_filepath, &params, false, nullptr);
ASSERT_TRUE(result) << "usd export to " << output_filepath << " failed.";
pxr::UsdStageRefPtr stage = pxr::UsdStage::Open(output_filepath);

View File

@ -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);

View File

@ -591,6 +591,15 @@ void WM_report_banner_show(wmWindowManager *wm, wmWindow *win) ATTR_NONNULL(1);
* Hide all currently displayed banners and abort their timer.
*/
void WM_report_banners_cancel(Main *bmain);
/** Add a whole list of reports to the WM ReportList, and show the banner.
*
* \note In case the given \a reports is a `nullptr`, or has its #RPT_OP_HOLD flag set, this
* function does nothing.
*
* \params reports The #ReportList from which to move reports to the WM one, may be `nullptr`.
* \params wm the WindowManager to add given \a reports to. If `nullptr`, the first WM of current
* #G_MAIN will be used. */
void WM_reports_add(wmWindowManager *wm, ReportList *reports);
void WM_report(eReportType type, const char *message);
void WM_reportf(eReportType type, const char *format, ...) ATTR_PRINTF_FORMAT(2, 3);

View File

@ -99,6 +99,7 @@ struct bContext;
struct bContextStore;
struct GreasePencil;
struct GreasePencilLayer;
struct ReportList;
struct wmDrag;
struct wmDropBox;
struct wmEvent;
@ -943,6 +944,10 @@ struct wmJobWorkerStatus {
/** OUTPUT - Progress as reported by the worker, from `0.0f` to `1.0f`. */
float progress;
/** OUTPUT - Storage of reports generated during this job's run. Contains its own locking for
* thread-safety. */
ReportList *reports;
};
struct wmOperatorType {

View File

@ -30,6 +30,7 @@
#include "BLI_blenlib.h"
#include "BLI_ghash.h"
#include "BLI_threads.h"
#include "BLI_timer.h"
#include "BLI_utildefines.h"
@ -928,27 +929,33 @@ void WM_ndof_deadzone_set(float deadzone)
}
#endif
static void wm_add_reports(ReportList *reports)
void WM_reports_add(wmWindowManager *wm, ReportList *reports)
{
/* If the caller owns them, handle this. */
if (reports->list.first && (reports->flag & RPT_OP_HOLD) == 0) {
wmWindowManager *wm = static_cast<wmWindowManager *>(G_MAIN->wm.first);
/* Add reports to the global list, otherwise they are not seen. */
BKE_reports_move_to_reports(&wm->reports, reports);
WM_report_banner_show(wm, nullptr);
if (!reports || BLI_listbase_is_empty(&reports->list) || (reports->flag & RPT_OP_HOLD) != 0) {
return;
}
if (!wm) {
wm = static_cast<wmWindowManager *>(G_MAIN->wm.first);
}
/* Add reports to the global list, otherwise they are not seen. */
BKE_reports_move_to_reports(&wm->reports, reports);
WM_report_banner_show(wm, nullptr);
}
mont29 marked this conversation as resolved Outdated

NOTE: This will only be committed once all wmJobs using it have been updated.

NOTE: This will only be committed once all wmJobs using it have been updated.

This will only be committed once all wmJobs using WM_report have been updated to use the new wmJob-dedicated reports instead.

This will only be committed once all wmJobs using `WM_report` have been updated to use the new wmJob-dedicated reports instead.
void WM_report(eReportType type, const char *message)
{
BLI_assert_msg(BLI_thread_is_main(), "WM_report should only be called from the main thread");
ReportList reports;
BKE_reports_init(&reports, RPT_STORE | RPT_PRINT);
BKE_report_print_level_set(&reports, RPT_WARNING);
BKE_report(&reports, type, message);
wm_add_reports(&reports);
WM_reports_add(nullptr, &reports);
BKE_reports_free(&reports);
}
@ -1112,7 +1119,7 @@ static void wm_operator_reports(bContext *C,
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_INFO_REPORT, nullptr);
}
/* If the caller owns them, handle this. */
wm_add_reports(op->reports);
WM_reports_add(CTX_wm_manager(C), op->reports);
}
/**
@ -2498,7 +2505,7 @@ static eHandlerActionFlag wm_handler_operator_call(bContext *C,
else {
/* Not very common, but modal operators may report before finishing. */
if (!BLI_listbase_is_empty(&op->reports->list)) {
wm_add_reports(op->reports);
WM_reports_add(wm, op->reports);
}
}
@ -2831,7 +2838,7 @@ static eHandlerActionFlag wm_handler_fileselect_do(bContext *C,
BKE_report_print_level_set(handler->op->reports, RPT_WARNING);
UI_popup_menu_reports(C, handler->op->reports);
wm_add_reports(handler->op->reports);
WM_reports_add(CTX_wm_manager(C), handler->op->reports);
CTX_wm_window_set(C, win_prev);
CTX_wm_area_set(C, area_prev);

View File

@ -20,6 +20,7 @@
#include "BKE_context.h"
#include "BKE_global.h"
#include "BKE_report.h"
#include "SEQ_prefetch.h"
@ -206,6 +207,10 @@ wmJob *WM_jobs_get(wmWindowManager *wm,
wm_job->main_thread_mutex = BLI_ticket_mutex_alloc();
WM_job_main_thread_lock_acquire(wm_job);
wm_job->worker_status.reports = MEM_new<ReportList>(__func__);
BKE_reports_init(wm_job->worker_status.reports, RPT_STORE | RPT_PRINT);
BKE_report_print_level_set(wm_job->worker_status.reports, RPT_WARNING);
}
/* else: a running job, be careful */
@ -381,6 +386,11 @@ void WM_jobs_callbacks_ex(wmJob *wm_job,
wm_job->canceled = canceled;
}
static void wm_jobs_reports_update(wmWindowManager *wm, wmJob *wm_job)
{
WM_reports_add(wm, wm_job->worker_status.reports);
}
static void *do_job_thread(void *job_v)
{
wmJob *wm_job = static_cast<wmJob *>(job_v);
@ -496,7 +506,7 @@ void WM_jobs_start(wmWindowManager *wm, wmJob *wm_job)
}
}
static void wm_job_end(wmJob *wm_job)
static void wm_job_end(wmWindowManager *wm, wmJob *wm_job)
{
BLI_assert_msg(BLI_thread_is_main(), "wm_job_end should only be called from the main thread");
if (wm_job->endjob) {
@ -512,6 +522,9 @@ static void wm_job_end(wmJob *wm_job)
if (final_callback) {
final_callback(wm_job->run_customdata);
}
/* Ensure all reports have been moved to WM. */
wm_jobs_reports_update(wm, wm_job);
}
static void wm_job_free(wmWindowManager *wm, wmJob *wm_job)
@ -519,6 +532,10 @@ static void wm_job_free(wmWindowManager *wm, wmJob *wm_job)
BLI_remlink(&wm->jobs, wm_job);
WM_job_main_thread_lock_release(wm_job);
BLI_ticket_mutex_free(wm_job->main_thread_mutex);
BLI_assert(BLI_listbase_is_empty(&wm_job->worker_status.reports->list));
BKE_reports_free(wm_job->worker_status.reports);
MEM_delete(wm_job->worker_status.reports);
MEM_freeN(wm_job);
}
@ -534,7 +551,7 @@ static void wm_jobs_kill_job(wmWindowManager *wm, wmJob *wm_job)
WM_job_main_thread_lock_release(wm_job);
BLI_threadpool_end(&wm_job->threads);
WM_job_main_thread_lock_acquire(wm_job);
wm_job_end(wm_job);
wm_job_end(wm, wm_job);
}
if (wm_job->wt) {
@ -644,7 +661,7 @@ void wm_jobs_timer(wmWindowManager *wm, wmTimer *wt)
}
if (wm_job->ready) {
wm_job_end(wm_job);
wm_job_end(wm, wm_job);
/* free own data */
wm_job->run_free(wm_job->run_customdata);
@ -689,12 +706,18 @@ void wm_jobs_timer(wmWindowManager *wm, wmTimer *wt)
/* remove wm_job */
wm_job_free(wm, wm_job);
wm_job = nullptr;
}
}
}
else if (wm_job->suspended) {
WM_jobs_start(wm, wm_job);
}
/* Move pending reports generated by the worker thread to the WM main list. */
if (wm_job) {
wm_jobs_reports_update(wm, wm_job);
}
}
/* Update progress bars in windows. */

View File

@ -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."""