USD IO: attribute conversion improvements.

Initial implementation of logic to import USD
attibutes as Blender custom properites, with options
to import all custom attributes or only those
attibutes in the 'userProperties' namespace.

New export option to add custom properties to the
'userProperties' USD attribute namespace. This
option is enabled by default.

Removed hidden functionality where custom properties named
with the prefix 'USD_' were being saved to properties on the
USD prim that have the same name, without the prefix.  This
code was not type safe and could lead to unexpected behavior
in case of accidental property name collisions.

Added support for converting between USD int, float and
double vectors and Blender array type custom properties.
This commit is contained in:
Michael Kowalski 2021-10-25 20:12:34 -04:00
parent f1828d3430
commit 265df7b3a6
6 changed files with 458 additions and 52 deletions

View File

@ -148,6 +148,32 @@ const EnumPropertyItem rna_enum_usd_mtl_name_collision_mode_items[] = {
{0, NULL, 0, NULL, NULL},
};
const EnumPropertyItem rna_enum_usd_attr_import_mode_items[] = {
{USD_ATTR_IMPORT_NONE,
"NONE",
0,
"None",
"Do not import attributes"},
{USD_ATTR_IMPORT_USER,
"USER",
0,
"User",
"Import attributes in the 'userProperties' namespace as "
"Blender custom properties. The namespace will "
"be stripped from the property names. "
"Note that attributes in the 'usdProperties:blenderName' namespace "
"will not be imported, as use of these attributes is deprecated"},
{USD_ATTR_IMPORT_ALL,
"ALL",
0,
"All Custom",
"Import all custom attributes as Blender custom properties. "
"Attribute namespaces will be retained in the property names. "
"Note that attributes in the 'usdProperties:blenderName' namespace "
"will not be imported, as use of these attributes is deprecated"},
{0, NULL, 0, NULL, NULL},
};
/* Stored in the wmOperator's customdata field to indicate it should run as a background job.
* This is set when the operator is invoked, and not set when it is only executed. */
enum { AS_BACKGROUND_JOB = 1 };
@ -237,6 +263,7 @@ static int wm_usd_export_exec(bContext *C, wmOperator *op)
const bool export_as_overs = RNA_boolean_get(op->ptr, "export_as_overs");
const bool merge_transform_and_shape = RNA_boolean_get(op->ptr, "merge_transform_and_shape");
const bool export_custom_properties = RNA_boolean_get(op->ptr, "export_custom_properties");
const bool add_properties_namespace = RNA_boolean_get(op->ptr, "add_properties_namespace");
const bool export_identity_transforms = RNA_boolean_get(op->ptr, "export_identity_transforms");
const bool apply_subdiv = RNA_boolean_get(op->ptr, "apply_subdiv");
const bool author_blender_name = RNA_boolean_get(op->ptr, "author_blender_name");
@ -325,6 +352,7 @@ static int wm_usd_export_exec(bContext *C, wmOperator *op)
export_as_overs,
merge_transform_and_shape,
export_custom_properties,
add_properties_namespace,
export_identity_transforms,
apply_subdiv,
author_blender_name,
@ -393,7 +421,6 @@ static void wm_usd_export_draw(bContext *C, wmOperator *op)
}
uiItemR(box, ptr, "export_as_overs", 0, NULL, ICON_NONE);
uiItemR(box, ptr, "merge_transform_and_shape", 0, NULL, ICON_NONE);
uiItemR(box, ptr, "export_custom_properties", 0, NULL, ICON_NONE);
uiItemR(box, ptr, "export_identity_transforms", 0, NULL, ICON_NONE);
if (RNA_boolean_get(ptr, "export_hair") || RNA_boolean_get(ptr, "export_particles")) {
@ -405,6 +432,13 @@ static void wm_usd_export_draw(bContext *C, wmOperator *op)
uiItemR(box, ptr, "vertex_data_as_face_varying", 0, NULL, ICON_NONE);
}
box = uiLayoutBox(layout);
uiItemL(box, IFACE_("Attributes:"), ICON_NONE);
uiItemR(box, ptr, "export_custom_properties", 0, NULL, ICON_NONE);
if (RNA_boolean_get(ptr, "export_custom_properties")) {
uiItemR(box, ptr, "add_properties_namespace", 0, NULL, ICON_NONE);
}
box = uiLayoutBox(layout);
uiItemL(box, IFACE_("Cycles Settings:"), ICON_NONE);
uiItemR(box, ptr, "override_shutter", 0, NULL, ICON_NONE);
@ -720,6 +754,11 @@ void WM_OT_usd_export(struct wmOperatorType *ot)
true,
"Export Custom Properties",
"When checked, custom properties will be exported as USD User Properties");
RNA_def_boolean(ot->srna,
"add_properties_namespace",
true,
"Add Properties Namespace",
"Add exported custom properties to the 'userProperties' USD attribute namespace");
RNA_def_boolean(ot->srna,
"export_identity_transforms",
false,
@ -930,6 +969,9 @@ static int wm_usd_import_exec(bContext *C, wmOperator *op)
const eUSDMtlNameCollisionMode mtl_name_collision_mode = RNA_enum_get(op->ptr,
"mtl_name_collision_mode");
const eUSDAttrImportMode attr_import_mode = RNA_enum_get(op->ptr,
"attr_import_mode");
/* TODO(makowalski): Add support for sequences. */
const bool is_sequence = false;
int offset = 0;
@ -972,7 +1014,8 @@ static int wm_usd_import_exec(bContext *C, wmOperator *op)
.convert_light_from_nits = convert_light_from_nits,
.scale_light_radius = scale_light_radius,
.create_background_shader = create_background_shader,
.mtl_name_collision_mode = mtl_name_collision_mode};
.mtl_name_collision_mode = mtl_name_collision_mode,
.attr_import_mode = attr_import_mode};
const bool ok = USD_import(C, filename, &params, as_background_job);
@ -1020,6 +1063,7 @@ static void wm_usd_import_draw(bContext *UNUSED(C), wmOperator *op)
uiItemR(box, ptr, "scale_light_radius", 0, NULL, ICON_NONE);
uiItemR(box, ptr, "create_background_shader", 0, NULL, ICON_NONE);
uiItemR(box, ptr, "mtl_name_collision_mode", 0, NULL, ICON_NONE);
uiItemR(box, ptr, "attr_import_mode", 0, NULL, ICON_NONE);
box = uiLayoutBox(layout);
col = uiLayoutColumnWithHeading(box, true, IFACE_("Experimental"));
@ -1202,6 +1246,14 @@ void WM_OT_usd_import(struct wmOperatorType *ot)
USD_MTL_NAME_COLLISION_MODIFY,
"Material Name Collision",
"Behavior when the name of an imported material conflicts with an existing material");
RNA_def_enum(
ot->srna,
"attr_import_mode",
rna_enum_usd_attr_import_mode_items,
USD_ATTR_IMPORT_NONE,
"Import Attributes",
"Behavior when importing USD attributes as Blender custom properties");
}
#endif /* WITH_USD */

View File

@ -21,10 +21,245 @@
#include "usd_reader_prim.h"
#include "BKE_idprop.h"
#include "BLI_utildefines.h"
#include "DNA_object_types.h"
#include <iostream>
#include <pxr/usd/usd/attribute.h>
namespace {
template<typename VECT>
void set_array_prop(IDProperty *idgroup,
const char *prop_name,
const pxr::UsdAttribute &attr,
const double motionSampleTime)
{
if (!idgroup || !attr) {
return;
}
VECT vec;
if (!attr.Get<VECT>(&vec, motionSampleTime)) {
return;
}
IDPropertyTemplate val = { 0 };
val.array.len = static_cast<int>(vec.dimension);
if (val.array.len <= 0) {
/* Should never happen. */
std::cout << "Invalid array length for prop " << prop_name << std::endl;
return;
}
if (std::is_same<float, typename VECT::ScalarType>()) {
val.array.type = IDP_FLOAT;
}
else if (std::is_same<double, typename VECT::ScalarType>()) {
val.array.type = IDP_DOUBLE;
}
else if (std::is_same<int, typename VECT::ScalarType>()) {
val.array.type = IDP_INT;
}
else {
std::cout << "Couldn't determine array type for prop " << prop_name << std::endl;
return;
}
IDProperty *prop = IDP_New(IDP_ARRAY, &val, prop_name);
if (!prop) {
std::cout << "Couldn't create array prop " << prop_name << std::endl;
return;
}
typename VECT::ScalarType *prop_data = static_cast<typename VECT::ScalarType *>(prop->data.pointer);
for (int i = 0; i < val.array.len; ++i) {
prop_data[i] = vec[i];
}
IDP_AddToGroup(idgroup, prop);
}
} // anonymous namespace
namespace blender::io::usd {
/* TfToken objects are not cheap to construct, so we do it once. */
namespace usdtokens {
static const pxr::TfToken userProperties("userProperties", pxr::TfToken::Immortal);
} // namespace usdtokens
static void set_string_prop(IDProperty *idgroup,
const char *prop_name,
const char *str_val)
{
if (!idgroup) {
return;
}
IDPropertyTemplate val = { 0 };
val.string.str = str_val;
/* Note length includes null terminator. */
val.string.len = strlen(str_val) + 1;
val.string.subtype = IDP_STRING_SUB_UTF8;
IDProperty *prop = IDP_New(IDP_STRING, &val, prop_name);
IDP_AddToGroup(idgroup, prop);
}
static void set_int_prop(IDProperty *idgroup,
const char *prop_name,
const int ival)
{
if (!idgroup) {
return;
}
IDPropertyTemplate val = { 0 };
val.i = ival;
IDProperty *prop = IDP_New(IDP_INT, &val, prop_name);
IDP_AddToGroup(idgroup, prop);
}
static void set_float_prop(IDProperty *idgroup,
const char *prop_name,
const float fval)
{
if (!idgroup) {
return;
}
IDPropertyTemplate val = { 0 };
val.f = fval;
IDProperty *prop = IDP_New(IDP_FLOAT, &val, prop_name);
IDP_AddToGroup(idgroup, prop);
}
static void set_double_prop(IDProperty *idgroup,
const char *prop_name,
const double dval)
{
if (!idgroup) {
return;
}
IDPropertyTemplate val = { 0 };
val.d = dval;
IDProperty *prop = IDP_New(IDP_DOUBLE, &val, prop_name);
IDP_AddToGroup(idgroup, prop);
}
void USDPrimReader::set_props(ID *id, const pxr::UsdPrim &prim, const double motionSampleTime)
{
eUSDAttrImportMode attr_import_mode = this->import_params_.attr_import_mode;
if (attr_import_mode == USD_ATTR_IMPORT_NONE) {
return;
}
IDProperty *idgroup = IDP_GetProperties(id, 1);
if (!idgroup) {
return;
}
bool all_custom_attrs = (attr_import_mode == USD_ATTR_IMPORT_ALL);
pxr::UsdAttributeVector attribs = prim.GetAuthoredAttributes();
for (const pxr::UsdAttribute &attr : attribs) {
if (!attr.IsCustom()) {
continue;
}
std::vector<std::string> attr_names = attr.SplitName();
bool is_user_prop = attr_names[0] == "userProperties";
if (attr_names.size() > 2 && is_user_prop && attr_names[1] == "blenderName") {
/* Skip the deprecated userProperties:blenderName namespace attribs. */
continue;
}
if (!all_custom_attrs && !is_user_prop) {
continue;
}
/* When importing user properties, strip the namespace. */
pxr::TfToken attr_name = (attr_import_mode == USD_ATTR_IMPORT_USER) ? attr.GetBaseName() : attr.GetName();
pxr::SdfValueTypeName type_name = attr.GetTypeName();
if (type_name == pxr::SdfValueTypeNames->Int) {
int ival = 0;
if (attr.Get<int>(&ival, motionSampleTime)) {
set_int_prop(idgroup, attr_name.GetString().c_str(), ival);
}
}
else if (type_name == pxr::SdfValueTypeNames->Float) {
float fval = 0.0f;
if (attr.Get<float>(&fval, motionSampleTime)) {
set_float_prop(idgroup, attr_name.GetString().c_str(), fval);
}
}
else if (type_name == pxr::SdfValueTypeNames->Double) {
double dval = 0.0;
if (attr.Get<double>(&dval, motionSampleTime)) {
set_double_prop(idgroup, attr_name.GetString().c_str(), dval);
}
}
else if (type_name == pxr::SdfValueTypeNames->String) {
std::string sval;
if (attr.Get<std::string>(&sval, motionSampleTime)) {
set_string_prop(idgroup, attr_name.GetString().c_str(), sval.c_str());
}
}
else if (type_name == pxr::SdfValueTypeNames->Token) {
pxr::TfToken tval;
if (attr.Get<pxr::TfToken>(&tval, motionSampleTime)) {
set_string_prop(idgroup, attr_name.GetString().c_str(), tval.GetString().c_str());
}
}
else if (type_name == pxr::SdfValueTypeNames->Float2) {
set_array_prop<pxr::GfVec2f>(idgroup, attr_name.GetString().c_str(), attr, motionSampleTime);
}
else if (type_name == pxr::SdfValueTypeNames->Float3) {
set_array_prop<pxr::GfVec3f>(idgroup, attr_name.GetString().c_str(), attr, motionSampleTime);
}
else if (type_name == pxr::SdfValueTypeNames->Float4) {
set_array_prop<pxr::GfVec4f>(idgroup, attr_name.GetString().c_str(), attr, motionSampleTime);
}
else if (type_name == pxr::SdfValueTypeNames->Double2) {
set_array_prop<pxr::GfVec2d>(idgroup, attr_name.GetString().c_str(), attr, motionSampleTime);
}
else if (type_name == pxr::SdfValueTypeNames->Double3) {
set_array_prop<pxr::GfVec3d>(idgroup, attr_name.GetString().c_str(), attr, motionSampleTime);
}
else if (type_name == pxr::SdfValueTypeNames->Double4) {
set_array_prop<pxr::GfVec4d>(idgroup, attr_name.GetString().c_str(), attr, motionSampleTime);
}
else if (type_name == pxr::SdfValueTypeNames->Int2) {
set_array_prop<pxr::GfVec2i>(idgroup, attr_name.GetString().c_str(), attr, motionSampleTime);
}
else if (type_name == pxr::SdfValueTypeNames->Int3) {
set_array_prop<pxr::GfVec3i>(idgroup, attr_name.GetString().c_str(), attr, motionSampleTime);
}
else if (type_name == pxr::SdfValueTypeNames->Int4) {
set_array_prop<pxr::GfVec4i>(idgroup, attr_name.GetString().c_str(), attr, motionSampleTime);
}
}
}
USDPrimReader::USDPrimReader(const pxr::UsdPrim &prim,
const USDImportParams &import_params,
const ImportSettings &settings)
@ -46,6 +281,17 @@ const pxr::UsdPrim &USDPrimReader::prim() const
return prim_;
}
void USDPrimReader::read_object_data(Main * /* bmain */, const double motionSampleTime)
{
if (!prim_ || !object_) {
return;
}
ID *id = object_->data ? static_cast<ID*>(object_->data) : &object_->id;
set_props(id, prim_, motionSampleTime);
}
Object *USDPrimReader::object() const
{
return object_;

View File

@ -100,9 +100,7 @@ class USDPrimReader {
virtual bool valid() const;
virtual void create_object(Main *bmain, double motionSampleTime) = 0;
virtual void read_object_data(Main * /* bmain */, double /* motionSampleTime */)
{
}
virtual void read_object_data(Main * bmain, double motionSampleTime);
virtual bool needs_cachefile()
{
@ -145,6 +143,10 @@ class USDPrimReader {
{
return prim_path_;
}
protected:
void set_props(ID *id, const pxr::UsdPrim &prim, double motionSampleTime);
};
} // namespace blender::io::usd

View File

@ -51,8 +51,14 @@ void USDXformReader::create_object(Main *bmain, const double /* motionSampleTime
object_->data = nullptr;
}
void USDXformReader::read_object_data(Main * /* bmain */, const double motionSampleTime)
void USDXformReader::read_object_data(Main * bmain, const double motionSampleTime)
{
USDPrimReader::read_object_data(bmain, motionSampleTime);
if (use_parent_xform_ && object_ && prim_) {
USDPrimReader::set_props(&object_->id, prim_.GetParent(), motionSampleTime);
}
bool is_constant;
float transform_from_usd[4][4];

View File

@ -45,8 +45,106 @@ static const pxr::TfToken surface("surface", pxr::TfToken::Immortal);
static const pxr::TfToken blenderName("userProperties:blenderName", pxr::TfToken::Immortal);
} // namespace usdtokens
namespace {
template<typename VECT>
bool set_vec_attrib(const pxr::UsdPrim &prim,
const IDProperty *prop,
const pxr::TfToken &prop_token,
const pxr::SdfValueTypeName &type_name,
const pxr::UsdTimeCode &timecode)
{
if (!prim || !prop || !prop->data.pointer || prop_token.IsEmpty() || !type_name) {
return false;
}
pxr::UsdAttribute vec_attr = prim.CreateAttribute(prop_token, type_name, true);
if (!vec_attr) {
printf("WARNING: Couldn't USD attribute for array property %s.\n", prop_token.GetString().c_str());
return false;
}
VECT vec_value(static_cast<VECT::ScalarType*>(prop->data.pointer));
return vec_attr.Set(vec_value, timecode);
}
} // anonymous namespace
namespace blender::io::usd {
static void create_vector_attrib(const pxr::UsdPrim &prim,
const IDProperty *prop,
const pxr::TfToken &prop_token,
const pxr::UsdTimeCode &timecode)
{
if (!prim || !prop || prop_token.IsEmpty()) {
return;
}
if (prop->type != IDP_ARRAY) {
printf("WARNING: Property %s is not an array type and can't be converted to a vector attribute.\n", prop_token.GetString().c_str());
return;
}
pxr::SdfValueTypeName type_name;
bool success = false;
if (prop->subtype == IDP_FLOAT) {
if (prop->len == 2) {
type_name = pxr::SdfValueTypeNames->Float2;
success = set_vec_attrib<pxr::GfVec2f>(prim, prop, prop_token, type_name, timecode);
}
else if (prop->len == 3) {
type_name = pxr::SdfValueTypeNames->Float3;
success = set_vec_attrib<pxr::GfVec3f>(prim, prop, prop_token, type_name, timecode);
}
else if (prop->len == 4) {
type_name = pxr::SdfValueTypeNames->Float4;
success = set_vec_attrib<pxr::GfVec4f>(prim, prop, prop_token, type_name, timecode);
}
}
else if (prop->subtype == IDP_DOUBLE) {
if (prop->len == 2) {
type_name = pxr::SdfValueTypeNames->Double2;
success = set_vec_attrib<pxr::GfVec2d>(prim, prop, prop_token, type_name, timecode);
}
else if (prop->len == 3) {
type_name = pxr::SdfValueTypeNames->Double3;
success = set_vec_attrib<pxr::GfVec3d>(prim, prop, prop_token, type_name, timecode);
}
else if (prop->len == 4) {
type_name = pxr::SdfValueTypeNames->Double4;
success = set_vec_attrib<pxr::GfVec4d>(prim, prop, prop_token, type_name, timecode);
}
}
else if (prop->subtype == IDP_INT) {
if (prop->len == 2) {
type_name = pxr::SdfValueTypeNames->Int2;
success = set_vec_attrib<pxr::GfVec2i>(prim, prop, prop_token, type_name, timecode);
}
else if (prop->len == 3) {
type_name = pxr::SdfValueTypeNames->Int3;
success = set_vec_attrib<pxr::GfVec3i>(prim, prop, prop_token, type_name, timecode);
}
else if (prop->len == 4) {
type_name = pxr::SdfValueTypeNames->Int4;
success = set_vec_attrib<pxr::GfVec4i>(prim, prop, prop_token, type_name, timecode);
}
}
if (!type_name) {
printf("WARNING: Couldn't determine USD type name for array property %s.\n", prop_token.GetString().c_str());
return;
}
if (!success) {
printf("WARNING: Couldn't set USD attribute from array property %s.\n", prop_token.GetString().c_str());
return;
}
}
USDAbstractWriter::USDAbstractWriter(const USDExporterContext &usd_export_context)
: usd_export_context_(usd_export_context), frame_has_been_written_(false), is_animated_(false)
{
@ -256,62 +354,56 @@ void USDAbstractWriter::write_user_properties(pxr::UsdPrim &prim,
IDProperty *properties,
pxr::UsdTimeCode timecode)
{
if (properties == nullptr)
if (properties == nullptr) {
return;
if (properties->type != IDP_GROUP)
}
if (properties->type != IDP_GROUP) {
return;
}
IDProperty *prop;
for (prop = (IDProperty *)properties->data.group.first; prop; prop = prop->next) {
std::string prop_name = pxr::TfMakeValidIdentifier(prop->name);
pxr::TfToken prop_token;
pxr::UsdAttribute prop_attr;
bool is_usd_attribute = false;
std::string full_prop_name;
if (usd_export_context_.export_params.add_properties_namespace) {
full_prop_name = "userProperties:";
}
full_prop_name += prop_name;
// If starts with USD_ treat as usd property
if (prop_name.rfind("USD_", 0) == 0) {
std::string usd_prop_name = prop_name;
usd_prop_name.erase(0, 4);
prop_token = pxr::TfToken(usd_prop_name);
prop_attr = prim.GetAttribute(prop_token);
if (prop_attr)
is_usd_attribute = true;
pxr::TfToken prop_token = pxr::TfToken(full_prop_name);
if (prim.HasAttribute(prop_token)) {
/* Don't overwrite existing attributes, as these may have been
* created by the exporter logic and shouldn't be changed. */
continue;
}
if (!is_usd_attribute) {
prop_token = pxr::TfToken("userProperties:" + prop_name);
switch (prop->type) {
case IDP_INT:
prop_attr = prim.CreateAttribute(prop_token, pxr::SdfValueTypeNames->Int, true);
break;
case IDP_FLOAT:
prop_attr = prim.CreateAttribute(prop_token, pxr::SdfValueTypeNames->Float, true);
break;
case IDP_DOUBLE:
prop_attr = prim.CreateAttribute(prop_token, pxr::SdfValueTypeNames->Double, true);
break;
case IDP_STRING:
prop_attr = prim.CreateAttribute(prop_token, pxr::SdfValueTypeNames->String, true);
break;
}
}
if (prop_attr) {
if (prop_attr.GetTypeName() == pxr::SdfValueTypeNames->Int)
prop_attr.Set<int>(prop->data.val, timecode);
else if (prop_attr.GetTypeName() == pxr::SdfValueTypeNames->Float)
prop_attr.Set<float>(*(float *)&prop->data.val, timecode);
else if (prop_attr.GetTypeName() == pxr::SdfValueTypeNames->Double)
prop_attr.Set<double>(*(double *)&prop->data.val, timecode);
else if (prop_attr.GetTypeName() == pxr::SdfValueTypeNames->String)
prop_attr.Set<std::string>((char *)prop->data.pointer, timecode);
else if (prop_attr.GetTypeName() == pxr::SdfValueTypeNames->Token)
prop_attr.Set<pxr::TfToken>(pxr::TfToken((char *)prop->data.pointer), timecode);
switch (prop->type) {
case IDP_INT:
if (pxr::UsdAttribute int_attr = prim.CreateAttribute(prop_token, pxr::SdfValueTypeNames->Int, true)) {
int_attr.Set<int>(prop->data.val, timecode);
}
break;
case IDP_FLOAT:
if (pxr::UsdAttribute float_attr = prim.CreateAttribute(prop_token, pxr::SdfValueTypeNames->Float, true)) {
float_attr.Set<float>(*reinterpret_cast<float *>(&prop->data.val), timecode);
}
break;
case IDP_DOUBLE:
if (pxr::UsdAttribute double_attr = prim.CreateAttribute(prop_token, pxr::SdfValueTypeNames->Double, true)) {
double_attr.Set<double>(*reinterpret_cast<double *>(&prop->data.val), timecode);
}
break;
case IDP_STRING:
if (pxr::UsdAttribute str_attr = prim.CreateAttribute(prop_token, pxr::SdfValueTypeNames->String, true)) {
str_attr.Set<std::string>(static_cast<const char *>(prop->data.pointer), timecode);
}
break;
case IDP_ARRAY:
create_vector_attrib(prim, prop, prop_token, timecode);
break;
}
}
}

View File

@ -69,6 +69,12 @@ typedef enum eUSDMtlNameCollisionMode {
USD_MTL_NAME_COLLISION_SKIP = 1,
} eUSDMtlNameCollisionMode;
typedef enum eUSDAttrImportMode {
USD_ATTR_IMPORT_NONE = 0,
USD_ATTR_IMPORT_USER = 1,
USD_ATTR_IMPORT_ALL = 2,
} eUSDAttrImportMode;
struct USDExportParams {
double frame_start;
double frame_end;
@ -105,6 +111,7 @@ struct USDExportParams {
bool export_as_overs;
bool merge_transform_and_shape;
bool export_custom_properties;
bool add_properties_namespace;
bool export_identity_transforms;
bool apply_subdiv;
bool author_blender_name;
@ -158,6 +165,7 @@ struct USDImportParams {
bool scale_light_radius;
bool create_background_shader;
eUSDMtlNameCollisionMode mtl_name_collision_mode;
eUSDAttrImportMode attr_import_mode;
};
/* The USD_export takes a as_background_job parameter, and returns a boolean.