USD export: option to add a root prim. #107855
|
@ -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
|
||||
|
||||
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, ¶ms, 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 ====== */
|
||||
|
|
|
@ -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 ¶ms)
|
||||
{
|
||||
bool valid = true;
|
||||
|
||||
if (!prim_path_valid(params.root_prim_path)) {
|
||||
valid = false;
|
||||
}
|
||||
brecht marked this conversation as resolved
Matt McLin
commented
I wonder if it might be better to remove this 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.
Michael Kowalski
commented
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 ¶ms)
|
||||
{
|
||||
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);
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -50,6 +50,7 @@ struct USDExportParams {
|
|||
bool export_textures;
|
||||
bool overwrite_textures;
|
||||
bool relative_paths;
|
||||
char root_prim_path[1024]; /* FILE_MAX */
|
||||
};
|
||||
|
||||
struct USDImportParams {
|
||||
|
|
Loading…
Reference in New Issue
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 thestrlen
operation inprocess_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 toFILE_MAX
, so this is ok.