USD export: option to add a root prim. #107855

Merged
Michael Kowalski merged 12 commits from makowalski/blender:usd-root-prim into main 2023-05-15 16:01:06 +02:00
6 changed files with 127 additions and 4 deletions

View File

@ -96,6 +96,21 @@ typedef struct eUSDOperatorOptions {
bool as_background_job;
} eUSDOperatorOptions;
/* Ensure that the prim_path is not set to
* the absolute root path '/'. */
static void process_prim_path(char *prim_path)
{
if (prim_path == NULL || prim_path[0] == '\0') {
return;
}
/* The absolute root "/" path indicates a no-op,
* so clear the string. */
if (prim_path[0] == '/' && strlen(prim_path) == 1) {
prim_path[0] = '\0';
}
}
static int wm_usd_export_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
{
eUSDOperatorOptions *options = MEM_callocN(sizeof(eUSDOperatorOptions), "eUSDOperatorOptions");
@ -138,6 +153,10 @@ static int wm_usd_export_exec(bContext *C, wmOperator *op)
const bool overwrite_textures = RNA_boolean_get(op->ptr, "overwrite_textures");
const bool relative_paths = RNA_boolean_get(op->ptr, "relative_paths");
char root_prim_path[FILE_MAX];
RNA_string_get(op->ptr, "root_prim_path", root_prim_path);
process_prim_path(root_prim_path);
brecht marked this conversation as resolved
Review

I see this is same convention used everywhere else, so not a problem for this PR, but just a question: it seems RNA_string_get doesn't concern itself with potential buffer overflow or null termination (which could impact the strlen operation in process_prim_path, among other things). Is this somehow handled in a clever way that I'm missing?

I see this is same convention used everywhere else, so not a problem for this PR, but just a question: it seems `RNA_string_get` doesn't concern itself with potential buffer overflow or null termination (which could impact the `strlen` operation in `process_prim_path`, among other things). Is this somehow handled in a clever way that I'm missing?

The RNA_def_string for this property sets the maximum length to FILE_MAX, so this is ok.

The `RNA_def_string` for this property sets the maximum length to `FILE_MAX`, so this is ok.
struct USDExportParams params = {
export_animation,
export_hair,
@ -154,6 +173,8 @@ static int wm_usd_export_exec(bContext *C, wmOperator *op)
relative_paths,
};
STRNCPY(params.root_prim_path, root_prim_path);
bool ok = USD_export(C, filename, &params, as_background_job);
return as_background_job || ok ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
@ -179,6 +200,7 @@ static void wm_usd_export_draw(bContext *UNUSED(C), wmOperator *op)
uiItemR(col, ptr, "export_uvmaps", 0, NULL, ICON_NONE);
uiItemR(col, ptr, "export_normals", 0, NULL, ICON_NONE);
uiItemR(col, ptr, "export_materials", 0, NULL, ICON_NONE);
uiItemR(col, ptr, "root_prim_path", 0, NULL, ICON_NONE);
col = uiLayoutColumn(box, true);
uiItemR(col, ptr, "evaluation_mode", 0, NULL, ICON_NONE);
@ -337,6 +359,14 @@ void WM_OT_usd_export(struct wmOperatorType *ot)
"Relative Paths",
"Use relative paths to reference external files (i.e. textures, volumes) in "
"USD, otherwise use absolute paths");
RNA_def_string(ot->srna,
"root_prim_path",
NULL,
FILE_MAX,
"Root Prim",
"If set, add a transform primitive with the given path to the stage "
"as the parent of all exported data");
}
/* ====== USD Import ====== */

View File

@ -11,6 +11,7 @@
#include <pxr/usd/usd/primRange.h>
#include <pxr/usd/usd/stage.h>
#include <pxr/usd/usdGeom/tokens.h>
#include <pxr/usd/usdGeom/xform.h>
#include <pxr/usd/usdUtils/dependencies.h>
#include "MEM_guardedalloc.h"
@ -64,6 +65,70 @@ struct ExportJobData {
}
};
/* Returns true if the given prim path is valid, per
* the requirements of the prim path manipulation logic
* of the exporter. Also returns true if the path is
* the empty string. Returns false otherwise. */
static bool prim_path_valid(const char *path)
{
BLI_assert(path);
if (path[0] == '\0') {
/* Empty paths are ignored in the code,
* so they can be passed through. */
return true;
}
/* Check path syntax. */
std::string errMsg;
if (!pxr::SdfPath::IsValidPathString(path, &errMsg)) {
WM_reportf(RPT_ERROR, "USD Export: invalid path string '%s': %s", path, errMsg.c_str());
return false;
}
/* Verify that an absolute prim path can be constructed
* from this path string. */
pxr::SdfPath sdf_path(path);
if (!sdf_path.IsAbsolutePath()) {
WM_reportf(RPT_ERROR, "USD Export: path '%s' is not an absolute path", path);
return false;
}
if (!sdf_path.IsPrimPath()) {
WM_reportf(RPT_ERROR, "USD Export: path string '%s' is not a prim path", path);
return false;
}
return true;
}
/* Perform validation of export parameter settings. Returns
* true if the paramters are valid; returns false otherwise. */
static bool export_params_valid(const USDExportParams &params)
{
bool valid = true;
if (!prim_path_valid(params.root_prim_path)) {
valid = false;
}
brecht marked this conversation as resolved
Review

I wonder if it might be better to remove this WM_reportf? If every failure path in prim_path_valid already reports an error, then this seems superfluous, and also the error reported from prim_path_valid provides more explicit guidance to the user about the problem.

I wonder if it might be better to remove this `WM_reportf`? If every failure path in `prim_path_valid` already reports an error, then this seems superfluous, and also the error reported from `prim_path_valid` provides more explicit guidance to the user about the problem.
Review

I agree and will remove the report statement. Thanks very much for the review!

I agree and will remove the report statement. Thanks very much for the review!
return valid;
}
/* Create the root Xform primitive, if the Root Prim path has been set
* in the export options. In the future, this function can be extended
* to author transforms and additional schema data (e.g., model Kind)
* on the root prim. */
static void ensure_root_prim(pxr::UsdStageRefPtr stage, const USDExportParams &params)
{
if (params.root_prim_path[0] == '\0') {
return;
}
pxr::UsdGeomXform::Define(stage, pxr::SdfPath(params.root_prim_path));
}
static void report_job_duration(const ExportJobData *data)
{
timeit::Nanoseconds duration = timeit::Clock::now() - data->start_time;
@ -185,6 +250,8 @@ static void export_startjob(void *customdata,
usd_stage->SetEndTimeCode(scene->r.efra);
}
ensure_root_prim(usd_stage, data->params);
USDHierarchyIterator iter(data->bmain, data->depsgraph, usd_stage, data->params);
if (data->params.export_animation) {
@ -324,6 +391,10 @@ bool USD_export(bContext *C,
const USDExportParams *params,
bool as_background_job)
{
if (!blender::io::usd::export_params_valid(*params)) {
return false;
}
ViewLayer *view_layer = CTX_data_view_layer(C);
Scene *scene = CTX_data_scene(C);

View File

@ -77,8 +77,15 @@ const pxr::UsdTimeCode &USDHierarchyIterator::get_export_time_code() const
USDExporterContext USDHierarchyIterator::create_usd_export_context(const HierarchyContext *context)
{
return USDExporterContext{
bmain_, depsgraph_, stage_, pxr::SdfPath(context->export_path), this, params_};
pxr::SdfPath path;
if (params_.root_prim_path[0] != '\0') {
path = pxr::SdfPath(params_.root_prim_path + context->export_path);
}
else {
path = pxr::SdfPath(context->export_path);
}
return USDExporterContext{bmain_, depsgraph_, stage_, path, this, params_};
}
AbstractHierarchyWriter *USDHierarchyIterator::create_transform_writer(

View File

@ -87,15 +87,27 @@ const pxr::SdfPath &USDAbstractWriter::usd_path() const
return usd_export_context_.usd_path;
}
pxr::SdfPath USDAbstractWriter::get_material_library_path() const
{
static std::string material_library_path("/_materials");
const char *root_prim_path = usd_export_context_.export_params.root_prim_path;
if (root_prim_path[0] != '\0') {
return pxr::SdfPath(root_prim_path + material_library_path);
}
return pxr::SdfPath(material_library_path);
}
pxr::UsdShadeMaterial USDAbstractWriter::ensure_usd_material(const HierarchyContext &context,
Material *material)
{
static pxr::SdfPath material_library_path("/_materials");
pxr::UsdStageRefPtr stage = usd_export_context_.stage;
/* Construct the material. */
pxr::TfToken material_name(usd_export_context_.hierarchy_iterator->get_id_name(&material->id));
pxr::SdfPath usd_path = material_library_path.AppendChild(material_name);
pxr::SdfPath usd_path = get_material_library_path().AppendChild(material_name);
pxr::UsdShadeMaterial usd_material = pxr::UsdShadeMaterial::Get(stage, usd_path);
if (usd_material) {
return usd_material;

View File

@ -55,6 +55,8 @@ class USDAbstractWriter : public AbstractHierarchyWriter {
std::string get_export_file_path() const;
pxr::UsdTimeCode get_export_time_code() const;
/* Returns the parent path of exported materials. */
pxr::SdfPath get_material_library_path() const;
pxr::UsdShadeMaterial ensure_usd_material(const HierarchyContext &context, Material *material);
void write_visibility(const HierarchyContext &context,

View File

@ -50,6 +50,7 @@ struct USDExportParams {
bool export_textures;
bool overwrite_textures;
bool relative_paths;
char root_prim_path[1024]; /* FILE_MAX */
};
struct USDImportParams {