[Bf-blender-cvs] [ee97add4c40] master: Alembic export: write custom properties

Sybren A. Stüvel noreply at git.blender.org
Mon Sep 14 13:04:33 CEST 2020


Commit: ee97add4c404a414addab5cbb6107fac002da77a
Author: Sybren A. Stüvel
Date:   Fri Sep 11 14:06:13 2020 +0200
Branches: master
https://developer.blender.org/rBee97add4c404a414addab5cbb6107fac002da77a

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

===================================================================

M	source/blender/editors/io/io_alembic.c
M	source/blender/io/alembic/ABC_alembic.h
M	source/blender/io/alembic/CMakeLists.txt
A	source/blender/io/alembic/exporter/abc_custom_props.cc
A	source/blender/io/alembic/exporter/abc_custom_props.h
M	source/blender/io/alembic/exporter/abc_writer_abstract.cc
M	source/blender/io/alembic/exporter/abc_writer_abstract.h
M	source/blender/io/alembic/exporter/abc_writer_camera.cc
M	source/blender/io/alembic/exporter/abc_writer_camera.h
M	source/blender/io/alembic/exporter/abc_writer_curves.cc
M	source/blender/io/alembic/exporter/abc_writer_curves.h
M	source/blender/io/alembic/exporter/abc_writer_hair.cc
M	source/blender/io/alembic/exporter/abc_writer_hair.h
M	source/blender/io/alembic/exporter/abc_writer_instance.cc
M	source/blender/io/alembic/exporter/abc_writer_instance.h
M	source/blender/io/alembic/exporter/abc_writer_mesh.cc
M	source/blender/io/alembic/exporter/abc_writer_mesh.h
M	source/blender/io/alembic/exporter/abc_writer_nurbs.cc
M	source/blender/io/alembic/exporter/abc_writer_nurbs.h
M	source/blender/io/alembic/exporter/abc_writer_points.cc
M	source/blender/io/alembic/exporter/abc_writer_points.h
M	source/blender/io/alembic/exporter/abc_writer_transform.cc
M	source/blender/io/alembic/exporter/abc_writer_transform.h
M	tests/python/alembic_export_tests.py

===================================================================

diff --git a/source/blender/editors/io/io_alembic.c b/source/blender/editors/io/io_alembic.c
index 0c4064b5693..292d8e6066c 100644
--- a/source/blender/editors/io/io_alembic.c
+++ b/source/blender/editors/io/io_alembic.c
@@ -133,6 +133,7 @@ static int wm_alembic_export_exec(bContext *C, wmOperator *op)
       .use_subdiv_schema = RNA_boolean_get(op->ptr, "subdiv_schema"),
       .export_hair = RNA_boolean_get(op->ptr, "export_hair"),
       .export_particles = RNA_boolean_get(op->ptr, "export_particles"),
+      .export_custom_properties = RNA_boolean_get(op->ptr, "export_custom_properties"),
       .use_instancing = RNA_boolean_get(op->ptr, "use_instancing"),
       .packuv = RNA_boolean_get(op->ptr, "packuv"),
       .triangulate = RNA_boolean_get(op->ptr, "triangulate"),
@@ -191,6 +192,8 @@ static void ui_alembic_export_settings(uiLayout *layout, PointerRNA *imfptr)
 
   uiItemR(col, imfptr, "flatten", 0, NULL, ICON_NONE);
   uiItemR(sub, imfptr, "use_instancing", 0, IFACE_("Use Instancing"), ICON_NONE);
+  uiItemR(sub, imfptr, "export_custom_properties", 0, IFACE_("Custom Properties"), ICON_NONE);
+
   sub = uiLayoutColumnWithHeading(col, true, IFACE_("Only"));
   uiItemR(sub, imfptr, "selected", 0, IFACE_("Selected Objects"), ICON_NONE);
   uiItemR(sub, imfptr, "renderable_only", 0, IFACE_("Renderable Objects"), ICON_NONE);
@@ -449,6 +452,12 @@ void WM_OT_alembic_export(wmOperatorType *ot)
   RNA_def_boolean(
       ot->srna, "export_particles", 1, "Export Particles", "Exports non-hair particle systems");
 
+  RNA_def_boolean(ot->srna,
+                  "export_custom_properties",
+                  true,
+                  "Export Custom Properties",
+                  "Export custom properties to Alembic .userProperties");
+
   RNA_def_boolean(
       ot->srna,
       "as_background_job",
diff --git a/source/blender/io/alembic/ABC_alembic.h b/source/blender/io/alembic/ABC_alembic.h
index 9a2c74c64a3..9785f6d68ab 100644
--- a/source/blender/io/alembic/ABC_alembic.h
+++ b/source/blender/io/alembic/ABC_alembic.h
@@ -60,6 +60,7 @@ struct AlembicExportParams {
   bool triangulate;
   bool export_hair;
   bool export_particles;
+  bool export_custom_properties;
   bool use_instancing;
 
   /* See MOD_TRIANGULATE_NGON_xxx and MOD_TRIANGULATE_QUAD_xxx
diff --git a/source/blender/io/alembic/CMakeLists.txt b/source/blender/io/alembic/CMakeLists.txt
index 2b44146e475..d55f2382a9b 100644
--- a/source/blender/io/alembic/CMakeLists.txt
+++ b/source/blender/io/alembic/CMakeLists.txt
@@ -56,6 +56,7 @@ set(SRC
   intern/alembic_capi.cc
 
   exporter/abc_archive.cc
+  exporter/abc_custom_props.cc
   exporter/abc_export_capi.cc
   exporter/abc_hierarchy_iterator.cc
   exporter/abc_subdiv_disabler.cc
@@ -84,6 +85,7 @@ set(SRC
   intern/abc_util.h
 
   exporter/abc_archive.h
+  exporter/abc_custom_props.h
   exporter/abc_hierarchy_iterator.h
   exporter/abc_subdiv_disabler.h
   exporter/abc_writer_abstract.h
diff --git a/source/blender/io/alembic/exporter/abc_custom_props.cc b/source/blender/io/alembic/exporter/abc_custom_props.cc
new file mode 100644
index 00000000000..382afdc294d
--- /dev/null
+++ b/source/blender/io/alembic/exporter/abc_custom_props.cc
@@ -0,0 +1,268 @@
+/*
+ * 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_a

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-blender-cvs mailing list