diff options
author | Sybren A. Stüvel <sybren@blender.org> | 2020-09-11 15:06:13 +0300 |
---|---|---|
committer | Sybren A. Stüvel <sybren@blender.org> | 2020-09-14 13:49:27 +0300 |
commit | ee97add4c404a414addab5cbb6107fac002da77a (patch) | |
tree | f6a7adadf1ee27470b3a53dd9b247078f83a3af1 /tests | |
parent | b8a25bbd8a400b52de150a08c041e2f6f2a9dce1 (diff) |
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
Diffstat (limited to 'tests')
-rw-r--r-- | tests/python/alembic_export_tests.py | 64 |
1 files changed, 64 insertions, 0 deletions
diff --git a/tests/python/alembic_export_tests.py b/tests/python/alembic_export_tests.py index b5d6bf65f3f..1d34eb3fc81 100644 --- a/tests/python/alembic_export_tests.py +++ b/tests/python/alembic_export_tests.py @@ -111,6 +111,7 @@ class AbstractAlembicTest(AbstractBlenderRunnerTest): 'uint64_t': int, 'float64_t': float, 'float32_t': float, + 'string': str, } result = {} @@ -586,6 +587,69 @@ class InvisibleObjectExportTest(AbstractAlembicTest): test('InvisibleAnimatedCube', False) +class CustomPropertiesExportTest(AbstractAlembicTest): + """Test export of custom properties.""" + + def _run_export(self, tempdir: pathlib.Path) -> pathlib.Path: + abc = tempdir / 'custom-properties.abc' + script = "import bpy; bpy.context.scene.frame_set(1); bpy.ops.wm.alembic_export(filepath='%s', start=1, end=1)" % abc.as_posix() + self.run_blender('custom-properties.blend', script) + return abc + + @with_tempdir + def test_xform_props(self, tempdir: pathlib.Path) -> None: + abc = self._run_export(tempdir) + abcprop = self.abcprop(abc, '/Cube/.xform/.userProperties') + + # Simple, single values. + self.assertEqual(abcprop['static_int'], [327]) + self.assertEqual(abcprop['static_float'], [47.01]) + self.assertEqual(abcprop['static_string'], ['Agents']) + self.assertEqual(abcprop['keyed_float'], [-1]) + self.assertEqual(abcprop['keyed_int'], [-47]) + + # Arrays. + self.assertEqual(abcprop['keyed_array_float'], [-1.000, 0.000, 1.000]) + self.assertEqual(abcprop['keyed_array_int'], [42, 47, 327]) + + # Multi-dimensional arrays. + self.assertEqual(abcprop['array_of_strings'], ['ผัดไทย', 'Pad Thai']) + self.assertEqual( + abcprop['matrix_tuple'], + [1.0, 0.0, 0.0, 3.33333, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0]) + self.assertEqual( + abcprop['static_matrix'], + [1.0, 0.0, 0.0, 3.33333, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0]) + self.assertEqual( + abcprop['nonuniform_array'], + [10, 20, 30, 1, 2, 47]) + + @with_tempdir + def test_mesh_props(self, tempdir: pathlib.Path) -> None: + abc = self._run_export(tempdir) + abcprop = self.abcprop(abc, '/Cube/Cube/.geom/.userProperties') + self.assertEqual(abcprop['mesh_tags'], ['cube', 'box', 'low-poly-sphere']) + + @with_tempdir + def test_camera_props(self, tempdir: pathlib.Path) -> None: + abc = self._run_export(tempdir) + abcprop = self.abcprop(abc, '/Camera/Hasselblad/.geom/.userProperties') + self.assertEqual(abcprop['type'], ['500c/m']) + + @with_tempdir + def test_disabled_export_option(self, tempdir: pathlib.Path) -> None: + abc = tempdir / 'custom-properties.abc' + script = ( + "import bpy; bpy.context.scene.frame_set(1); " + "bpy.ops.wm.alembic_export(filepath='%s', start=1, end=1, export_custom_properties=False)" % abc.as_posix() + ) + self.run_blender('custom-properties.blend', script) + + abcprop = self.abcprop(abc, '/Camera/Hasselblad/.geom/.userProperties') + self.assertIn('eyeSeparation', abcprop, 'Regular non-standard properties should still be written') + self.assertNotIn('type', abcprop, 'Custom properties should not be written') + + if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('--blender', required=True) |