Files
test/source/blender/io/usd/intern/usd_reader_utils.cc
Charles Wardlaw 60bc34b494 USD: Import and export custom properties
Adding support for converting between Blender custom properties and
USD user-defined custom attributes. Custom attributes on Xforms, many
data types, and materials are all supported for round-tripping.

Please see the USD attributes documentation for more information on
custom attributes.

Properties are exported with a userProperties: namespace for simple
filtering in external apps. This namespace is stripped on import,
but other namespace are allowed to persist.

An "Import Attributes" parameter has been added with options "None" (do
not import attributes), "User" (import attributes in the 'userProperties'
namespace only), "All custom" (import all USD custom attributes, the
default).

An "Export Custom Properties" export option has been added.

The property conversion code handles float, double, string and bool
types, as well as tuples of size 2, 3 and 4. Note that USD quaternions
and arrays of arbitrary length are not yet supported.

There is currently no attempt to set the Blender property subtype based
on the USD type "role" (e.g., specifying Color or XYZ vector subtypes).
This can be addressed in future work.

In addition to exporting custom properties, the original Blender object
and data names are now saved as USD custom string attributes
"userProperties:blender:object_name" and "userProperties:blender:data_name",
respectively, on the corresponding USD prims. This feature is enabled
with the "Author Blender Name" export option.

If a Blender custom string property is named "displayName", it's handled
in a special way on export in that its value is used to set the USD
prim's "displayName" metadata.

Co-authored-by: kiki <charles@skeletalstudios.com>
Co-authored-by: Michael Kowalski <makowalski@nvidia.com>
Co-authored-by: Charles Wardlaw <kattkieru@users.noreply.github.com>
Pull Request: https://projects.blender.org/blender/blender/pulls/118938
2024-04-23 19:27:40 +02:00

291 lines
8.8 KiB
C++

/* SPDX-FileCopyrightText: 2024 NVIDIA Corporation. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "usd_reader_utils.hh"
#include <pxr/usd/usd/attribute.h>
#include "CLG_log.h"
static CLG_LogRef LOG = {"io.usd"};
namespace {
template<typename VECT>
void set_array_prop(IDProperty *idgroup,
const char *prop_name,
const pxr::UsdAttribute &attr,
const pxr::UsdTimeCode 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) {
CLOG_WARN(&LOG, "Invalid array length for prop %s", prop_name);
return;
}
if (std::is_same<float, typename VECT::ScalarType>()) {
val.array.type = IDP_FLOAT;
}
else if (std::is_same<pxr::GfHalf, 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 {
CLOG_WARN(&LOG, "Couldn't determine array type for prop %s", prop_name);
return;
}
IDProperty *prop = IDP_New(IDP_ARRAY, &val, prop_name);
if (!prop) {
CLOG_WARN(&LOG, "Couldn't create array prop %s", prop_name);
return;
}
if (std::is_same<pxr::GfHalf, typename VECT::ScalarType>()) {
float *prop_data = static_cast<float *>(prop->data.pointer);
for (int i = 0; i < val.array.len; ++i) {
prop_data[i] = vec[i];
}
}
else {
std::memcpy(prop->data.pointer, vec.data(), prop->len * sizeof(typename VECT::ScalarType));
}
IDP_AddToGroup(idgroup, prop);
}
bool equivalent(const pxr::SdfValueTypeName &type_name1, const pxr::SdfValueTypeName &type_name2)
{
return type_name1.GetType().IsA(type_name2.GetType());
}
} // 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_bool_prop(IDProperty *idgroup, const char *prop_name, const bool bval)
{
if (!idgroup) {
return;
}
IDPropertyTemplate val = {0};
val.i = bval;
IDProperty *prop = IDP_New(IDP_BOOLEAN, &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 set_id_props_from_prim(ID *id,
const pxr::UsdPrim &prim,
const eUSDAttrImportMode attr_import_mode,
const pxr::UsdTimeCode time_code)
{
pxr::UsdAttributeVector attribs = prim.GetAuthoredAttributes();
if (attribs.empty()) {
return;
}
bool all_custom_attrs = (attr_import_mode == USD_ATTR_IMPORT_ALL);
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] == "blender") {
continue;
}
if (!all_custom_attrs && !is_user_prop) {
continue;
}
IDProperty *idgroup = IDP_EnsureProperties(id);
/* When importing user properties, strip the namespace. */
pxr::TfToken attr_name;
if (is_user_prop) {
/* We strip the userProperties namespace, but leave others in case
* someone's custom attribute namespace is important in their pipeline. */
const std::string token = "userProperties:";
const std::string name = attr.GetName().GetString();
attr_name = pxr::TfToken(name.substr(token.size(), name.size() - token.size()));
}
else {
attr_name = attr.GetName();
}
pxr::SdfValueTypeName type_name = attr.GetTypeName();
if (type_name == pxr::SdfValueTypeNames->Int) {
int ival = 0;
if (attr.Get<int>(&ival, time_code)) {
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, time_code)) {
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, time_code)) {
set_double_prop(idgroup, attr_name.GetString().c_str(), dval);
}
}
else if (type_name == pxr::SdfValueTypeNames->Half) {
pxr::GfHalf hval = 0.0f;
if (attr.Get<pxr::GfHalf>(&hval, time_code)) {
set_float_prop(idgroup, attr_name.GetString().c_str(), hval);
}
}
else if (type_name == pxr::SdfValueTypeNames->String) {
std::string sval;
if (attr.Get<std::string>(&sval, time_code)) {
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, time_code)) {
set_string_prop(idgroup, attr_name.GetString().c_str(), tval.GetString().c_str());
}
}
else if (type_name == pxr::SdfValueTypeNames->Asset) {
pxr::SdfAssetPath aval;
if (attr.Get<pxr::SdfAssetPath>(&aval, time_code)) {
set_string_prop(idgroup, attr_name.GetString().c_str(), aval.GetAssetPath().c_str());
}
}
else if (type_name == pxr::SdfValueTypeNames->Bool) {
bool bval = false;
if (attr.Get<bool>(&bval, time_code)) {
set_bool_prop(idgroup, attr_name.GetString().c_str(), bval);
}
}
else if (equivalent(type_name, pxr::SdfValueTypeNames->Float2)) {
set_array_prop<pxr::GfVec2f>(idgroup, attr_name.GetString().c_str(), attr, time_code);
}
else if (equivalent(type_name, pxr::SdfValueTypeNames->Float3)) {
set_array_prop<pxr::GfVec3f>(idgroup, attr_name.GetString().c_str(), attr, time_code);
}
else if (equivalent(type_name, pxr::SdfValueTypeNames->Float4)) {
set_array_prop<pxr::GfVec4f>(idgroup, attr_name.GetString().c_str(), attr, time_code);
}
else if (equivalent(type_name, pxr::SdfValueTypeNames->Double2)) {
set_array_prop<pxr::GfVec2d>(idgroup, attr_name.GetString().c_str(), attr, time_code);
}
else if (equivalent(type_name, pxr::SdfValueTypeNames->Double3)) {
set_array_prop<pxr::GfVec3d>(idgroup, attr_name.GetString().c_str(), attr, time_code);
}
else if (equivalent(type_name, pxr::SdfValueTypeNames->Double4)) {
set_array_prop<pxr::GfVec4d>(idgroup, attr_name.GetString().c_str(), attr, time_code);
}
else if (equivalent(type_name, pxr::SdfValueTypeNames->Int2)) {
set_array_prop<pxr::GfVec2i>(idgroup, attr_name.GetString().c_str(), attr, time_code);
}
else if (equivalent(type_name, pxr::SdfValueTypeNames->Int3)) {
set_array_prop<pxr::GfVec3i>(idgroup, attr_name.GetString().c_str(), attr, time_code);
}
else if (equivalent(type_name, pxr::SdfValueTypeNames->Int4)) {
set_array_prop<pxr::GfVec4i>(idgroup, attr_name.GetString().c_str(), attr, time_code);
}
else if (equivalent(type_name, pxr::SdfValueTypeNames->Half2)) {
set_array_prop<pxr::GfVec2h>(idgroup, attr_name.GetString().c_str(), attr, time_code);
}
else if (equivalent(type_name, pxr::SdfValueTypeNames->Half3)) {
set_array_prop<pxr::GfVec3h>(idgroup, attr_name.GetString().c_str(), attr, time_code);
}
else if (equivalent(type_name, pxr::SdfValueTypeNames->Half4)) {
set_array_prop<pxr::GfVec4h>(idgroup, attr_name.GetString().c_str(), attr, time_code);
}
}
}
} // namespace blender::io::usd