WIP: Brush assets project #106303

Draft
Julian Eisel wants to merge 352 commits from brush-assets-project into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
24 changed files with 811 additions and 5 deletions
Showing only changes of commit da0a06ec17 - Show all commits

View File

@ -6,6 +6,7 @@
#include "BKE_bake_data_block_map.hh"
#include "BKE_geometry_set.hh"
#include "BKE_volume_grid_fwd.hh"
namespace blender::bke::bake {
@ -81,6 +82,17 @@ class AttributeBakeItem : public BakeItem {
}
};
#ifdef WITH_OPENVDB
class VolumeGridBakeItem : public BakeItem {
public:
/** Using #unique_ptr so that `BKE_volume_grid_fwd.hh` can be used. */
std::unique_ptr<GVolumeGrid> grid;
VolumeGridBakeItem(std::unique_ptr<GVolumeGrid> grid);
~VolumeGridBakeItem();
};
#endif
/** Storage for a single value of a trivial type like `float`, `int`, etc. */
class PrimitiveBakeItem : public BakeItem {
private:

View File

@ -121,6 +121,11 @@ class SocketValueVariant {
*/
bool is_context_dependent_field() const;
/**
* The stored value is a volume grid.
*/
bool is_volume_grid() const;
/**
* Convert the stored value into a single value. For simple value access, this is not necessary,
* because #get` does the conversion implicitly. However, it is necessary if one wants to use

View File

@ -9,6 +9,7 @@
#include "BKE_mesh.hh"
#include "BKE_pointcloud.hh"
#include "BKE_volume.hh"
#include "BKE_volume_grid.hh"
#include "BLI_math_matrix_types.hh"
@ -134,6 +135,14 @@ void GeometryBakeItem::try_restore_data_blocks(GeometrySet &main_geometry,
});
}
#ifdef WITH_OPENVDB
VolumeGridBakeItem::VolumeGridBakeItem(std::unique_ptr<GVolumeGrid> grid) : grid(std::move(grid))
{
}
VolumeGridBakeItem::~VolumeGridBakeItem() = default;
#endif
PrimitiveBakeItem::PrimitiveBakeItem(const CPPType &type, const void *value) : type_(type)
{
value_ = MEM_mallocN_aligned(type.size(), type.alignment(), __func__);

View File

@ -1197,6 +1197,23 @@ static void serialize_bake_item(const BakeItem &item,
r_io_item.append_str("type", "ATTRIBUTE");
r_io_item.append_str("name", attribute_state_item->name());
}
#ifdef WITH_OPENVDB
else if (const auto *grid_state_item = dynamic_cast<const VolumeGridBakeItem *>(&item)) {
r_io_item.append_str("type", "GRID");
const GVolumeGrid &grid = *grid_state_item->grid;
auto io_vdb = blob_writer
.write_as_stream(".vdb",
[&](std::ostream &stream) {
openvdb::GridCPtrVec vdb_grids;
bke::VolumeTreeAccessToken tree_token;
vdb_grids.push_back(grid->grid_ptr(tree_token));
openvdb::io::Stream vdb_stream(stream);
vdb_stream.write(vdb_grids);
})
.serialize();
r_io_item.append("vdb", std::move(io_vdb));
}
#endif
else if (const auto *string_state_item = dynamic_cast<const StringBakeItem *>(&item)) {
r_io_item.append_str("type", "STRING");
const StringRefNull str = string_state_item->value();
@ -1246,6 +1263,39 @@ static std::unique_ptr<BakeItem> deserialize_bake_item(const DictionaryValue &io
}
return std::make_unique<AttributeBakeItem>(std::move(*name));
}
#ifdef WITH_OPENVDB
if (*state_item_type == StringRef("GRID")) {
const DictionaryValue &io_grid = io_item;
const auto *io_vdb = io_grid.lookup_dict("vdb");
if (!io_vdb) {
return {};
}
std::optional<BlobSlice> vdb_slice = BlobSlice::deserialize(*io_vdb);
if (!vdb_slice) {
return {};
}
openvdb::GridPtrVecPtr vdb_grids;
if (!blob_reader.read_as_stream(*vdb_slice, [&](std::istream &stream) {
try {
openvdb::io::Stream vdb_stream{stream};
vdb_grids = vdb_stream.getGrids();
return true;
}
catch (...) {
return false;
}
}))
{
return {};
}
if (vdb_grids->size() != 1) {
return {};
}
std::shared_ptr<openvdb::GridBase> vdb_grid = std::move((*vdb_grids)[0]);
GVolumeGrid grid{std::move(vdb_grid)};
return std::make_unique<VolumeGridBakeItem>(std::make_unique<GVolumeGrid>(grid));
}
#endif
if (*state_item_type == StringRef("STRING")) {
const std::shared_ptr<io::serialize::Value> *io_data = io_item.lookup("data");
if (!io_data) {

View File

@ -6,6 +6,7 @@
#include "BKE_geometry_fields.hh"
#include "BKE_node.hh"
#include "BKE_node_socket_value.hh"
#include "BKE_volume_grid.hh"
namespace blender::bke::bake {
@ -80,6 +81,14 @@ Array<std::unique_ptr<BakeItem>> move_socket_values_to_bake_items(const Span<voi
}
bake_items[i] = std::make_unique<AttributeBakeItem>(attribute_name);
}
#ifdef WITH_OPENVDB
else if (value_variant.is_volume_grid()) {
bke::GVolumeGrid grid = value_variant.get<bke::GVolumeGrid>();
grid.get_for_write().set_name(config.names[i]);
bake_items[i] = std::make_unique<VolumeGridBakeItem>(
std::make_unique<bke::GVolumeGrid>(std::move(grid)));
}
#endif
else {
value_variant.convert_to_single();
GPointer value = value_variant.get_single_ptr();
@ -152,6 +161,22 @@ Array<std::unique_ptr<BakeItem>> move_socket_values_to_bake_items(const Span<voi
r_attribute_map.add(item->name(), attribute_id);
return true;
}
#ifdef WITH_OPENVDB
if (const auto *item = dynamic_cast<const VolumeGridBakeItem *>(&bake_item)) {
const GVolumeGrid &grid = *item->grid;
const VolumeGridType grid_type = grid->grid_type();
const std::optional<eNodeSocketDatatype> grid_socket_type = grid_type_to_socket_type(
grid_type);
if (!grid_socket_type) {
return false;
}
if (grid_socket_type == socket_type) {
new (r_value) SocketValueVariant(*item->grid);
return true;
}
return false;
}
#endif
return false;
}
case SOCK_STRING: {

View File

@ -229,6 +229,11 @@ bool SocketValueVariant::is_context_dependent_field() const
return field.node().depends_on_input();
}
bool SocketValueVariant::is_volume_grid() const
{
return kind_ == Kind::Grid;
}
void SocketValueVariant::convert_to_single()
{
switch (kind_) {

View File

@ -74,6 +74,24 @@ const EnumPropertyItem rna_enum_usd_mtl_name_collision_mode_items[] = {
{0, nullptr, 0, nullptr, nullptr},
};
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"},
{USD_ATTR_IMPORT_ALL,
"ALL",
0,
"All Custom",
"Import all USD custom attributes as Blender custom properties. "
"Namespaces will be retained in the property names"},
{0, nullptr, 0, nullptr, nullptr},
};
const EnumPropertyItem rna_enum_usd_tex_import_mode_items[] = {
{USD_TEX_IMPORT_NONE, "IMPORT_NONE", 0, "None", "Don't import textures"},
{USD_TEX_IMPORT_PACK, "IMPORT_PACK", 0, "Packed", "Import textures as packed data"},
@ -189,6 +207,9 @@ static int wm_usd_export_exec(bContext *C, wmOperator *op)
const bool export_shapekeys = RNA_boolean_get(op->ptr, "export_shapekeys");
const bool only_deform_bones = RNA_boolean_get(op->ptr, "only_deform_bones");
const bool export_custom_properties = RNA_boolean_get(op->ptr, "export_custom_properties");
const bool author_blender_name = RNA_boolean_get(op->ptr, "author_blender_name");
char root_prim_path[FILE_MAX];
RNA_string_get(op->ptr, "root_prim_path", root_prim_path);
process_prim_path(root_prim_path);
@ -212,6 +233,8 @@ static int wm_usd_export_exec(bContext *C, wmOperator *op)
export_textures,
overwrite_textures,
relative_paths,
export_custom_properties,
author_blender_name,
};
STRNCPY(params.root_prim_path, root_prim_path);
@ -238,6 +261,13 @@ static void wm_usd_export_draw(bContext *C, wmOperator *op)
uiItemR(col, ptr, "visible_objects_only", UI_ITEM_NONE, nullptr, ICON_NONE);
}
col = uiLayoutColumn(box, true);
uiItemR(col, ptr, "export_custom_properties", UI_ITEM_NONE, nullptr, ICON_NONE);
col = uiLayoutColumn(box, true);
uiItemR(col, ptr, "author_blender_name", UI_ITEM_NONE, nullptr, ICON_NONE);
uiLayoutSetActive(col, RNA_boolean_get(op->ptr, "export_custom_properties"));
col = uiLayoutColumn(box, true);
uiItemR(col, ptr, "export_animation", UI_ITEM_NONE, nullptr, ICON_NONE);
uiItemR(col, ptr, "export_hair", UI_ITEM_NONE, nullptr, ICON_NONE);
@ -280,8 +310,8 @@ static void wm_usd_export_draw(bContext *C, wmOperator *op)
uiItemR(col, ptr, "relative_paths", UI_ITEM_NONE, nullptr, ICON_NONE);
box = uiLayoutBox(layout);
uiItemL(box, IFACE_("Experimental"), ICON_NONE);
uiItemR(box, ptr, "use_instancing", UI_ITEM_NONE, nullptr, ICON_NONE);
col = uiLayoutColumnWithHeading(box, true, IFACE_("Experimental"));
uiItemR(col, ptr, "use_instancing", UI_ITEM_NONE, nullptr, ICON_NONE);
}
static void free_operator_customdata(wmOperator *op)
@ -453,6 +483,18 @@ void WM_OT_usd_export(wmOperatorType *ot)
"Root Prim",
"If set, add a transform primitive with the given path to the stage "
"as the parent of all exported data");
RNA_def_boolean(ot->srna,
"export_custom_properties",
true,
"Export Custom Properties",
"When checked, custom properties will be exported as USD User Properties");
RNA_def_boolean(ot->srna,
"author_blender_name",
true,
"Author Blender Name",
"When checked, custom userProperties will be authored to allow a round trip");
}
/* ====== USD Import ====== */
@ -533,6 +575,9 @@ static int wm_usd_import_exec(bContext *C, wmOperator *op)
const eUSDMtlNameCollisionMode mtl_name_collision_mode = eUSDMtlNameCollisionMode(
RNA_enum_get(op->ptr, "mtl_name_collision_mode"));
const eUSDAttrImportMode attr_import_mode = eUSDAttrImportMode(
RNA_enum_get(op->ptr, "attr_import_mode"));
/* TODO(makowalski): Add support for sequences. */
const bool is_sequence = false;
int offset = 0;
@ -589,6 +634,7 @@ static int wm_usd_import_exec(bContext *C, wmOperator *op)
params.import_textures_mode = import_textures_mode;
params.tex_name_collision_mode = tex_name_collision_mode;
params.import_all_materials = import_all_materials;
params.attr_import_mode = attr_import_mode;
STRNCPY(params.import_textures_dir, import_textures_dir);
@ -641,6 +687,7 @@ static void wm_usd_import_draw(bContext * /*C*/, wmOperator *op)
uiItemR(col, ptr, "relative_path", UI_ITEM_NONE, nullptr, ICON_NONE);
uiItemR(col, ptr, "create_collection", UI_ITEM_NONE, nullptr, ICON_NONE);
uiItemR(box, ptr, "light_intensity_scale", UI_ITEM_NONE, nullptr, ICON_NONE);
uiItemR(col, ptr, "attr_import_mode", UI_ITEM_NONE, nullptr, ICON_NONE);
box = uiLayoutBox(layout);
col = uiLayoutColumnWithHeading(box, true, IFACE_("Materials"));
@ -830,6 +877,13 @@ void WM_OT_usd_import(wmOperatorType *ot)
USD_TEX_NAME_COLLISION_USE_EXISTING,
"File Name Collision",
"Behavior when the name of an imported texture file conflicts with an existing file");
RNA_def_enum(ot->srna,
"attr_import_mode",
rna_enum_usd_attr_import_mode_items,
USD_ATTR_IMPORT_ALL,
"Import Custom Properties",
"Behavior when importing USD attributes as Blender custom properties");
}
namespace blender::ed::io {

View File

@ -4,9 +4,11 @@
#pragma once
#include <openvdb_fwd.hh>
#ifdef WITH_OPENVDB
#include "BKE_volume_grid_fwd.hh"
# include <openvdb_fwd.hh>
# include "BKE_volume_grid_fwd.hh"
namespace blender::geometry {
@ -16,3 +18,5 @@ openvdb::FloatGrid &resample_sdf_grid_if_necessary(bke::VolumeGrid<float> &volum
std::shared_ptr<openvdb::FloatGrid> &storage);
} // namespace blender::geometry
#endif

View File

@ -120,6 +120,7 @@ set(SRC
intern/usd_reader_stage.cc
intern/usd_reader_volume.cc
intern/usd_reader_xform.cc
intern/usd_reader_utils.cc
intern/usd_skel_convert.cc
intern/usd_skel_root_utils.cc
@ -160,6 +161,7 @@ set(SRC
intern/usd_reader_stage.hh
intern/usd_reader_volume.hh
intern/usd_reader_xform.hh
intern/usd_reader_utils.hh
intern/usd_skel_convert.hh
intern/usd_skel_root_utils.hh
)

View File

@ -3,6 +3,7 @@
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "usd_reader_material.hh"
#include "usd_reader_utils.hh"
#include "usd_asset_utils.hh"
@ -493,6 +494,9 @@ Material *USDMaterialReader::add_material(const pxr::UsdShadeMaterial &usd_mater
}
}
/* Load custom properties directly from the Material's prim. */
set_id_props_from_prim(&mtl->id, usd_material.GetPrim());
return mtl;
}

View File

@ -6,11 +6,52 @@
* Adapted from the Blender Alembic importer implementation. */
#include "usd_reader_prim.hh"
#include "usd_reader_utils.hh"
#include "usd.hh"
#include "DNA_object_types.h"
#include <pxr/usd/usd/prim.h>
#include "BLI_assert.h"
namespace blender::io::usd {
void USDPrimReader::set_props(const bool merge_with_parent,
const pxr::UsdTimeCode motionSampleTime)
{
if (!prim_ || !object_) {
return;
}
eUSDAttrImportMode attr_import_mode = this->import_params_.attr_import_mode;
if (attr_import_mode == USD_ATTR_IMPORT_NONE) {
return;
}
if (merge_with_parent) {
/* This object represents a parent Xform merged with its child prim.
* Set the parent prim's custom properties on the Object ID. */
if (const pxr::UsdPrim parent_prim = prim_.GetParent()) {
set_id_props_from_prim(&object_->id, parent_prim, attr_import_mode, motionSampleTime);
}
}
if (!object_->data) {
/* If the object has no data, set the prim's custom properties on the object.
* This applies to Xforms that have been converted to Empty objects. */
set_id_props_from_prim(&object_->id, prim_, attr_import_mode, motionSampleTime);
}
if (object_->data) {
/* If the object has data, the data represents the USD prim, so set the prim's custom
* properties on the data directly. */
set_id_props_from_prim(
static_cast<ID *>(object_->data), prim_, attr_import_mode, motionSampleTime);
}
}
USDPrimReader::USDPrimReader(const pxr::UsdPrim &prim,
const USDImportParams &import_params,
const ImportSettings &settings)

View File

@ -16,6 +16,7 @@
#include <pxr/usd/sdf/path.h>
#include <pxr/usd/usd/prim.h>
#include <optional>
#include <string>
struct CacheFile;
@ -161,6 +162,28 @@ class USDPrimReader {
}
bool is_in_proto() const;
protected:
/**
* Convert custom attributes on the encapsulated USD prim (or on its parent)
* to custom properties on the generated object and/or data. This function
* assumes create_object() and read_object_data() have been called.
*
* If the generated object has instantiated data, it's assumed that the data
* represents the USD prim, and the prim properties will be set on the data ID.
* If the object data is null (which would be the case when a USD Xform is
* converted to an Empty object), then the prim properties will be set on the
* object ID. Finally, a true value for the 'merge_with_parent' argument indicates
* that the object represents a USD Xform and its child prim that were merged
* on import, and the properties of the prim's parent will be set on the object
* ID.
*
* \param merge_with_parent: If true, set the properties of the prim's parent
* on the object ID
* \param motionSampleTime: The time code for sampling tha USD attributes
*/
void set_props(bool merge_with_parent = false,
pxr::UsdTimeCode motionSampleTime = pxr::UsdTimeCode::Default());
};
} // namespace blender::io::usd

View File

@ -0,0 +1,290 @@
/* 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

View File

@ -0,0 +1,19 @@
/* SPDX-FileCopyrightText: 2024 NVIDIA Corporation. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "usd.hh"
#include <pxr/usd/usd/prim.h>
#include "BKE_idprop.hh"
namespace blender::io::usd {
void set_id_props_from_prim(ID *id,
const pxr::UsdPrim &prim,
eUSDAttrImportMode attr_import_mode = USD_ATTR_IMPORT_ALL,
pxr::UsdTimeCode time_code = pxr::UsdTimeCode::Default());
} // namespace blender::io::usd

View File

@ -54,6 +54,9 @@ void USDXformReader::read_object_data(Main * /*bmain*/, const double motionSampl
}
BKE_object_apply_mat4(object_, transform_from_usd, true, false);
/* Make sure to collect custom attributes */
set_props(use_parent_xform(), motionSampleTime);
}
void USDXformReader::read_matrix(float r_mat[4][4] /* local matrix */,

View File

@ -27,6 +27,7 @@ static const pxr::TfToken preview_shader("previewShader", pxr::TfToken::Immortal
static const pxr::TfToken preview_surface("UsdPreviewSurface", pxr::TfToken::Immortal);
static const pxr::TfToken roughness("roughness", pxr::TfToken::Immortal);
static const pxr::TfToken surface("surface", pxr::TfToken::Immortal);
static const pxr::TfToken blender_ns("userProperties:blender", pxr::TfToken::Immortal);
} // namespace usdtokens
static std::string get_mesh_active_uvlayer_name(const Object *ob)
@ -42,8 +43,109 @@ static std::string get_mesh_active_uvlayer_name(const Object *ob)
return name ? name : "";
}
template<typename USDT>
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) {
CLOG_WARN(&LOG,
"Couldn't create USD attribute for array property %s",
prop_token.GetString().c_str());
return false;
}
USDT vec_value(static_cast<typename USDT::ScalarType *>(prop->data.pointer));
return vec_attr.Set(vec_value, timecode);
}
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) {
CLOG_WARN(&LOG,
"Property %s is not an array type and can't be converted to a vector attribute",
prop->name);
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) {
CLOG_WARN(&LOG,
"Couldn't determine USD type name for array property %s",
prop_token.GetString().c_str());
return;
}
if (!success) {
CLOG_WARN(
&LOG, "Couldn't set USD attribute from array property %s", 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)
{
@ -121,7 +223,14 @@ pxr::UsdShadeMaterial USDAbstractWriter::ensure_usd_material(const HierarchyCont
}
std::string active_uv = get_mesh_active_uvlayer_name(context.object);
return create_usd_material(usd_export_context_, usd_path, material, active_uv, reports());
usd_material = create_usd_material(
usd_export_context_, usd_path, material, active_uv, reports());
auto prim = usd_material.GetPrim();
write_id_properties(prim, material->id, get_export_time_code());
return usd_material;
}
void USDAbstractWriter::write_visibility(const HierarchyContext &context,
@ -165,6 +274,111 @@ bool USDAbstractWriter::mark_as_instance(const HierarchyContext &context, const
return true;
}
void USDAbstractWriter::write_id_properties(const pxr::UsdPrim &prim,
const ID &id,
pxr::UsdTimeCode timecode) const
{
if (!usd_export_context_.export_params.export_custom_properties) {
return;
}
if (usd_export_context_.export_params.author_blender_name) {
if (GS(id.name) == ID_OB) {
/* Author property of original blender Object name. */
prim.CreateAttribute(pxr::TfToken(usdtokens::blender_ns.GetString() + ":object_name"),
pxr::SdfValueTypeNames->String,
true)
.Set<std::string>(std::string(id.name + 2));
}
else {
prim.CreateAttribute(pxr::TfToken(usdtokens::blender_ns.GetString() + ":data_name"),
pxr::SdfValueTypeNames->String,
true)
.Set<std::string>(std::string(id.name + 2));
}
}
if (id.properties) {
write_user_properties(prim, id.properties, timecode);
}
}
void USDAbstractWriter::write_user_properties(const pxr::UsdPrim &prim,
IDProperty *properties,
pxr::UsdTimeCode timecode) const
{
if (properties == nullptr) {
return;
}
if (properties->type != IDP_GROUP) {
return;
}
const StringRef displayName_identifier = "displayName";
for (IDProperty *prop = (IDProperty *)properties->data.group.first; prop; prop = prop->next) {
if (displayName_identifier == prop->name) {
if (prop->type == IDP_STRING && prop->data.pointer) {
prim.SetDisplayName(static_cast<char *>(prop->data.pointer));
}
continue;
}
std::string prop_name = pxr::TfMakeValidIdentifier(prop->name);
std::string full_prop_name = "userProperties:" + prop_name;
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;
}
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_BOOLEAN:
if (pxr::UsdAttribute bool_attr = prim.CreateAttribute(
prop_token, pxr::SdfValueTypeNames->Bool, true))
{
bool_attr.Set<bool>(prop->data.val, timecode);
}
break;
case IDP_ARRAY:
create_vector_attrib(prim, prop, prop_token, timecode);
break;
}
}
}
void USDAbstractWriter::author_extent(const pxr::UsdTimeCode timecode, pxr::UsdGeomBoundable &prim)
{
/* Do not use any existing `extentsHint` that may be authored, instead recompute the extent when

View File

@ -67,6 +67,13 @@ class USDAbstractWriter : public AbstractHierarchyWriter {
pxr::SdfPath get_material_library_path() const;
pxr::UsdShadeMaterial ensure_usd_material(const HierarchyContext &context, Material *material);
void write_id_properties(const pxr::UsdPrim &prim,
const ID &id,
pxr::UsdTimeCode = pxr::UsdTimeCode::Default()) const;
void write_user_properties(const pxr::UsdPrim &prim,
IDProperty *properties,
pxr::UsdTimeCode = pxr::UsdTimeCode::Default()) const;
void write_visibility(const HierarchyContext &context,
const pxr::UsdTimeCode timecode,
pxr::UsdGeomImageable &usd_geometry);

View File

@ -97,6 +97,9 @@ void USDCameraWriter::do_write(HierarchyContext &context)
float focus_distance = BKE_camera_object_dof_distance(context.object);
usd_camera.CreateFocusDistanceAttr().Set(focus_distance, timecode);
}
auto prim = usd_camera.GetPrim();
write_id_properties(prim, camera->id, timecode);
}
} // namespace blender::io::usd

View File

@ -505,6 +505,9 @@ void USDCurvesWriter::do_write(HierarchyContext &context)
set_writer_attributes(usd_curves, verts, control_point_counts, widths, timecode, interpolation);
assign_materials(context, usd_curves);
auto prim = usd_curves.GetPrim();
write_id_properties(prim, curves->id, timecode);
}
void USDCurvesWriter::assign_materials(const HierarchyContext &context,

View File

@ -62,6 +62,11 @@ void USDHairWriter::do_write(HierarchyContext &context)
curves.CreateDisplayColorAttr(pxr::VtValue(colors));
}
if (psys->part) {
auto prim = curves.GetPrim();
write_id_properties(prim, psys->part->id, timecode);
}
this->author_extent(timecode, curves);
}

View File

@ -126,6 +126,9 @@ void USDLightWriter::do_write(HierarchyContext &context)
usd_light_api.CreateSpecularAttr().Set(light->spec_fac, timecode);
usd_light_api.CreateNormalizeAttr().Set(true, timecode);
auto prim = usd_light_api.GetPrim();
write_id_properties(prim, light->id, timecode);
set_light_extents(usd_light_api.GetPrim(), timecode);
}

View File

@ -112,6 +112,12 @@ void USDGenericMeshWriter::do_write(HierarchyContext &context)
}
throw;
}
auto prim = usd_export_context_.stage->GetPrimAtPath(usd_export_context_.usd_path);
if (prim.IsValid() && object_eval) {
prim.SetActive((object_eval->duplicator_visibility_flag & OB_DUPLI_FLAG_RENDER) != 0);
write_id_properties(prim, mesh->id, get_export_time_code());
}
}
void USDGenericMeshWriter::write_custom_data(const Object *obj,

View File

@ -30,6 +30,11 @@ void USDTransformWriter::do_write(HierarchyContext &context)
pxr::GfMatrix4d mat_val(parent_relative_matrix);
usd_value_writer_.SetAttribute(xformOp_.GetAttr(), mat_val, get_export_time_code());
if (context.object) {
auto prim = xform.GetPrim();
write_id_properties(prim, context.object->id, get_export_time_code());
}
}
bool USDTransformWriter::check_is_animated(const HierarchyContext &context) const

View File

@ -34,6 +34,17 @@ enum eUSDMtlNameCollisionMode {
USD_MTL_NAME_COLLISION_REFERENCE_EXISTING = 1,
};
/**
* Behavior for importing of custom
* attributes / properties outside
* a prim's regular schema.
*/
typedef enum eUSDAttrImportMode {
USD_ATTR_IMPORT_NONE = 0,
USD_ATTR_IMPORT_USER = 1,
USD_ATTR_IMPORT_ALL = 2,
} eUSDAttrImportMode;
/**
* Behavior when importing textures from a package
* (e.g., USDZ archive) or from a URI path.
@ -84,6 +95,8 @@ struct USDExportParams {
bool export_textures = true;
bool overwrite_textures = true;
bool relative_paths = true;
bool export_custom_properties = true;
bool author_blender_name = true;
char root_prim_path[1024] = ""; /* FILE_MAX */
char collection[MAX_IDPROP_NAME] = "";
@ -126,6 +139,7 @@ struct USDImportParams {
char import_textures_dir[768]; /* FILE_MAXDIR */
eUSDTexNameCollisionMode tex_name_collision_mode;
bool import_all_materials;
eUSDAttrImportMode attr_import_mode;
/**
* Communication structure between the wmJob management code and the worker code. Currently used