This repository has been archived on 2023-10-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
blender-archive/source/blender/io/alembic/exporter/abc_custom_props.cc
Sybren A. Stüvel ee97add4c4 Alembic export: write custom properties
Write custom properties (aka ID properties) to Alembic, to the
`.userProperties` compound property.

Manifest Task: https://developer.blender.org/T50725

Scalar properties (so single-value/non-array properties) are written as
single-element array properties to Alembic. This is also what's done by
Houdini and Maya exporters, so it seems to be the standard way of doing
things. It also simplifies the implementation.

Two-dimensional arrays are flattened by concatenating all the numbers
into a single array. This is because ID properties have a limited type
system. This means that a 3x3 "matrix" could just as well be a list of
three 3D vectors.

Alembic has two container properties to store custom data:
- `.userProperties`, which is meant for properties that aren't
  necessarily understood by other software packages, and
- `.arbGeomParams`, which can contain the same kind of data as
  `.userProperties`, but can also specify that these vary per face of a
  mesh. This property is mostly intended for renderers.

Most industry packages write their custom data to `.arbGeomParams`.
However, given their goals I feel that `.userProperties` is the more
appropriate one for Blender's ID Properties.

The code is a bit more involved than I would have liked. An
`ABCAbstractWriter` has a `uniqueptr` to its `CustomPropertiesExporter`,
but the `CustomPropertiesExporter` also has a pointer back to its owning
`ABCAbstractWriter`. It's the latter pointer that I'm not too happy
with, but it has a reason. Getting the aforementioned `.userProperties`
from the Alembic library will automatically create it if it doesn't
exist already. If it's not used to actually add custom properties to, it
will crash the Alembic CLI tools (and maybe others too). This is what
the pointer back to the `ABCAbstractWriter` is used for: to get the
`.userProperties` at the last moment, when it's 100% sure at least one
custom property will be written.

Differential Revision: https://developer.blender.org/D8869

Reviewed by: sergey, dbystedt
2020-09-14 12:49:27 +02:00

269 lines
9.0 KiB
C++

/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation.
* All rights reserved.
*/
/** \file
* \ingroup Alembic
*/
#include "abc_custom_props.h"
#include "abc_writer_abstract.h"
#include <functional>
#include <iostream>
#include <memory>
#include <string>
#include <Alembic/Abc/OTypedArrayProperty.h>
#include <Alembic/Abc/OTypedScalarProperty.h>
#include "BKE_idprop.h"
#include "DNA_ID.h"
using Alembic::Abc::ArraySample;
using Alembic::Abc::OArrayProperty;
using Alembic::Abc::OBoolArrayProperty;
using Alembic::Abc::OCompoundProperty;
using Alembic::Abc::ODoubleArrayProperty;
using Alembic::Abc::OFloatArrayProperty;
using Alembic::Abc::OInt32ArrayProperty;
using Alembic::Abc::OStringArrayProperty;
namespace blender::io::alembic {
CustomPropertiesExporter::CustomPropertiesExporter(ABCAbstractWriter *owner) : owner_(owner)
{
}
CustomPropertiesExporter::~CustomPropertiesExporter()
{
}
void CustomPropertiesExporter::write_all(const IDProperty *group)
{
if (group == nullptr) {
return;
}
BLI_assert(group->type == IDP_GROUP);
/* Loop over the properties, just like IDP_foreach_property() does, but without the recursion. */
LISTBASE_FOREACH (IDProperty *, id_property, &group->data.group) {
if (STREQ(id_property->name, "_RNA_UI")) {
continue;
}
write(id_property);
}
}
void CustomPropertiesExporter::write(const IDProperty *id_property)
{
BLI_assert(id_property->name[0] != '\0');
switch (id_property->type) {
case IDP_STRING: {
/* The Alembic library doesn't accept NULL-terminated character arrays. */
const std::string prop_value(IDP_String(id_property), id_property->len - 1);
set_scalar_property<OStringArrayProperty, std::string>(id_property->name, prop_value);
break;
}
case IDP_INT:
static_assert(sizeof(int) == sizeof(int32_t), "Expecting 'int' to be 32-bit");
set_scalar_property<OInt32ArrayProperty, int32_t>(id_property->name, IDP_Int(id_property));
break;
case IDP_FLOAT:
set_scalar_property<OFloatArrayProperty, float>(id_property->name, IDP_Float(id_property));
break;
case IDP_DOUBLE:
set_scalar_property<ODoubleArrayProperty, double>(id_property->name,
IDP_Double(id_property));
break;
case IDP_ARRAY:
write_array(id_property);
break;
case IDP_IDPARRAY:
write_idparray(id_property);
break;
}
}
void CustomPropertiesExporter::write_array(const IDProperty *id_property)
{
BLI_assert(id_property->type == IDP_ARRAY);
switch (id_property->subtype) {
case IDP_INT: {
const int *array = (int *)IDP_Array(id_property);
static_assert(sizeof(int) == sizeof(int32_t), "Expecting 'int' to be 32-bit");
set_array_property<OInt32ArrayProperty, int32_t>(id_property->name, array, id_property->len);
break;
}
case IDP_FLOAT: {
const float *array = (float *)IDP_Array(id_property);
set_array_property<OFloatArrayProperty, float>(id_property->name, array, id_property->len);
break;
}
case IDP_DOUBLE: {
const double *array = (double *)IDP_Array(id_property);
set_array_property<ODoubleArrayProperty, double>(id_property->name, array, id_property->len);
break;
}
}
}
void CustomPropertiesExporter::write_idparray(const IDProperty *idp_array)
{
BLI_assert(idp_array->type == IDP_IDPARRAY);
if (idp_array->len == 0) {
/* Don't bother writing dataless arrays. */
return;
}
IDProperty *idp_elements = (IDProperty *)IDP_Array(idp_array);
#ifndef NDEBUG
/* Sanity check that all elements of the array have the same type.
* Blender should already enforce this, hence it's only used in debug mode. */
for (int i = 1; i < idp_array->len; i++) {
if (idp_elements[i].type == idp_elements[0].type) {
continue;
}
std::cerr << "Custom property " << idp_array->name << " has elements of varying type";
BLI_assert(!"Mixed type IDP_ARRAY custom property found");
}
#endif
switch (idp_elements[0].type) {
case IDP_STRING:
write_idparray_of_strings(idp_array);
break;
case IDP_ARRAY:
write_idparray_of_numbers(idp_array);
break;
}
}
void CustomPropertiesExporter::write_idparray_of_strings(const IDProperty *idp_array)
{
BLI_assert(idp_array->type == IDP_IDPARRAY);
BLI_assert(idp_array->len > 0);
/* Convert to an array of std::strings, because Alembic doesn't like zero-delimited strings. */
IDProperty *idp_elements = (IDProperty *)IDP_Array(idp_array);
std::vector<std::string> strings(idp_array->len);
for (int i = 0; i < idp_array->len; i++) {
BLI_assert(idp_elements[i].type == IDP_STRING);
strings[i] = IDP_String(&idp_elements[i]);
}
/* Alembic needs a pointer to the first value of the array. */
const std::string *array_of_strings = &strings[0];
set_array_property<OStringArrayProperty, std::string>(
idp_array->name, array_of_strings, strings.size());
}
void CustomPropertiesExporter::write_idparray_of_numbers(const IDProperty *idp_array)
{
BLI_assert(idp_array->type == IDP_IDPARRAY);
BLI_assert(idp_array->len > 0);
/* This must be an array of arrays. */
IDProperty *idp_rows = (IDProperty *)IDP_Array(idp_array);
BLI_assert(idp_rows[0].type == IDP_ARRAY);
const int subtype = idp_rows[0].subtype;
if (!ELEM(subtype, IDP_INT, IDP_FLOAT, IDP_DOUBLE)) {
/* Non-numerical types are not supported. */
return;
}
switch (subtype) {
case IDP_INT:
static_assert(sizeof(int) == sizeof(int32_t), "Expecting 'int' to be 32-bit");
write_idparray_flattened_typed<OInt32ArrayProperty, int32_t>(idp_array);
break;
case IDP_FLOAT:
write_idparray_flattened_typed<OFloatArrayProperty, float>(idp_array);
break;
case IDP_DOUBLE:
write_idparray_flattened_typed<ODoubleArrayProperty, double>(idp_array);
break;
}
}
template<typename ABCPropertyType, typename BlenderValueType>
void CustomPropertiesExporter::write_idparray_flattened_typed(const IDProperty *idp_array)
{
BLI_assert(idp_array->type == IDP_IDPARRAY);
BLI_assert(idp_array->len > 0);
const IDProperty *idp_rows = (IDProperty *)IDP_Array(idp_array);
BLI_assert(idp_rows[0].type == IDP_ARRAY);
BLI_assert(ELEM(idp_rows[0].subtype, IDP_INT, IDP_FLOAT, IDP_DOUBLE));
const uint64_t num_rows = idp_array->len;
std::vector<BlenderValueType> matrix_values;
for (size_t row_idx = 0; row_idx < num_rows; ++row_idx) {
const BlenderValueType *row = (BlenderValueType *)IDP_Array(&idp_rows[row_idx]);
for (size_t col_idx = 0; col_idx < idp_rows[row_idx].len; col_idx++) {
matrix_values.push_back(row[col_idx]);
}
}
set_array_property<ABCPropertyType, BlenderValueType>(
idp_array->name, &matrix_values[0], matrix_values.size());
}
template<typename ABCPropertyType, typename BlenderValueType>
void CustomPropertiesExporter::set_scalar_property(const StringRef property_name,
const BlenderValueType property_value)
{
set_array_property<ABCPropertyType, BlenderValueType>(property_name, &property_value, 1);
}
template<typename ABCPropertyType, typename BlenderValueType>
void CustomPropertiesExporter::set_array_property(const StringRef property_name,
const BlenderValueType *array_values,
const size_t num_array_items)
{
auto create_callback = [this, property_name]() -> OArrayProperty {
return create_abc_property<ABCPropertyType>(property_name);
};
OArrayProperty array_prop = abc_properties_.lookup_or_add_cb(property_name, create_callback);
Alembic::Util::Dimensions array_dimensions(num_array_items);
ArraySample sample(array_values, array_prop.getDataType(), array_dimensions);
array_prop.set(sample);
}
template<typename ABCPropertyType>
OArrayProperty CustomPropertiesExporter::create_abc_property(const StringRef property_name)
{
/* Get the necessary info from our owner. */
OCompoundProperty abc_prop_for_custom_props = owner_->abc_prop_for_custom_props();
const uint32_t timesample_index = owner_->timesample_index();
/* Construct the Alembic property. */
ABCPropertyType abc_property(abc_prop_for_custom_props, property_name);
abc_property.setTimeSampling(timesample_index);
return abc_property;
}
} // namespace blender::io::alembic