Alembic export: fixed curve type and order.
authorSybren A. Stüvel <sybren@stuvel.eu>
Tue, 18 Apr 2017 10:17:07 +0000 (12:17 +0200)
committerSybren A. Stüvel <sybren@stuvel.eu>
Tue, 18 Apr 2017 11:57:04 +0000 (13:57 +0200)
The order number written to Alembic is the same as we use in memory, so
the +1 wasn't needed, at least according to the reference Maya exporter
maya/AbcExport/MayaNurbsCurveWriter.cpp, function
MayaNurbsCurveWriter::write(), in the Alembic source code.

Furthermore, when writing an array of nurb orders, the curve type should
be set to kVariableOrder, otherwise the importer will ignore it.

source/blender/alembic/intern/abc_curves.cc
tests/python/alembic_tests.py

index 28e75db2862538006cdd4018993dc1af3f1035c2..58b8d7e05cd03838b8095c07f13954a282d0a5d9 100644 (file)
@@ -95,7 +95,7 @@ void AbcCurveWriter::do_write()
        for (; nurbs; nurbs = nurbs->next) {
                if (nurbs->bp) {
                        curve_basis = Alembic::AbcGeom::kNoBasis;
        for (; nurbs; nurbs = nurbs->next) {
                if (nurbs->bp) {
                        curve_basis = Alembic::AbcGeom::kNoBasis;
-                       curve_type = Alembic::AbcGeom::kLinear;
+                       curve_type = Alembic::AbcGeom::kVariableOrder;
 
                        const int totpoint = nurbs->pntsu * nurbs->pntsv;
 
 
                        const int totpoint = nurbs->pntsu * nurbs->pntsv;
 
@@ -160,7 +160,7 @@ void AbcCurveWriter::do_write()
                        }
                }
 
                        }
                }
 
-               orders.push_back(nurbs->orderu + 1);
+               orders.push_back(nurbs->orderu);
                vert_counts.push_back(verts.size());
        }
 
                vert_counts.push_back(verts.size());
        }
 
index 209b34a8634cc1b0bc12f0fd3f13cefcd81237cd..14c817549a9ae27b0e2fc7dd5ac54e5287da71ca 100755 (executable)
@@ -116,6 +116,7 @@ class AbstractAlembicTest(unittest.TestCase):
         The Python bindings for Alembic are old, and only compatible with Python 2.x,
         so that's why we can't use them here, and have to rely on other tooling.
         """
         The Python bindings for Alembic are old, and only compatible with Python 2.x,
         so that's why we can't use them here, and have to rely on other tooling.
         """
+        import collections
 
         abcls = self.alembic_root / 'bin' / 'abcls'
 
 
         abcls = self.alembic_root / 'bin' / 'abcls'
 
@@ -133,14 +134,19 @@ class AbstractAlembicTest(unittest.TestCase):
         converters = {
             'bool_t': int,
             'uint8_t': int,
         converters = {
             'bool_t': int,
             'uint8_t': int,
+            'int32_t': int,
             'float64_t': float,
             'float64_t': float,
+            'float32_t': float,
         }
 
         result = {}
 
         # Ideally we'd get abcls to output JSON, see https://github.com/alembic/alembic/issues/121
         }
 
         result = {}
 
         # Ideally we'd get abcls to output JSON, see https://github.com/alembic/alembic/issues/121
-        lines = output.split('\n')
-        for info, value in zip(lines[0::2], lines[1::2]):
+        lines = collections.deque(output.split('\n'))
+        while lines:
+            info = lines.popleft()
+            if not info:
+                continue
             proptype, valtype_and_arrsize, name_and_extent = info.split()
 
             # Parse name and extent
             proptype, valtype_and_arrsize, name_and_extent = info.split()
 
             # Parse name and extent
@@ -152,22 +158,41 @@ class AbstractAlembicTest(unittest.TestCase):
             if extent != '1':
                 self.fail('Unsupported extent %s for property %s/%s' % (extent, proppath, name))
 
             if extent != '1':
                 self.fail('Unsupported extent %s for property %s/%s' % (extent, proppath, name))
 
-            # Parse type and convert values
+            # Parse type
             m = self.abcls_array.match(valtype_and_arrsize)
             if not m:
                 self.fail('Unparsable value type from abcls: %s' % valtype_and_arrsize)
             m = self.abcls_array.match(valtype_and_arrsize)
             if not m:
                 self.fail('Unparsable value type from abcls: %s' % valtype_and_arrsize)
-            valtype, arraysize = m.group('name'), m.group('arraysize')
+            valtype, scalarsize = m.group('name'), m.group('arraysize')
 
 
+            # Convert values
             try:
                 conv = converters[valtype]
             except KeyError:
                 self.fail('Unsupported type %s for property %s/%s' % (valtype, proppath, name))
 
             try:
                 conv = converters[valtype]
             except KeyError:
                 self.fail('Unsupported type %s for property %s/%s' % (valtype, proppath, name))
 
-            if arraysize is None:
-                result[name] = conv(value)
+            def convert_single_line(linevalue):
+                try:
+                    if scalarsize is None:
+                        return conv(linevalue)
+                    else:
+                        return [conv(v.strip()) for v in linevalue.split(',')]
+                except ValueError as ex:
+                    return str(ex)
+
+            if proptype == 'ScalarProperty':
+                value = lines.popleft()
+                result[name] = convert_single_line(value)
+            elif proptype == 'ArrayProperty':
+                arrayvalue = []
+                # Arrays consist of a variable number of items, and end in a blank line.
+                while True:
+                    linevalue = lines.popleft()
+                    if not linevalue:
+                        break
+                    arrayvalue.append(convert_single_line(linevalue))
+                result[name] = arrayvalue
             else:
             else:
-                values = [conv(v.strip()) for v in value.split(',')]
-                result[name] = values
+                self.fail('Unsupported type %s for property %s/%s' % (proptype, proppath, name))
 
         return result
 
 
         return result
 
@@ -260,6 +285,17 @@ class DupliGroupExportTest(AbstractAlembicTest):
         )
 
 
         )
 
 
+class CurveExportTest(AbstractAlembicTest):
+    @with_tempdir
+    def test_export_single_curve(self, tempdir: pathlib.Path):
+        abc = tempdir / 'single-curve.abc'
+        script = "import bpy; bpy.ops.wm.alembic_export(filepath='%s', start=1, end=1, " \
+                 "renderable_only=True, visible_layers_only=True, flatten=False)" % abc
+        self.run_blender('single-curve.blend', script)
+
+        # Now check the resulting Alembic file.
+        abcprop = self.abcprop(abc, '/NurbsCurve/NurbsCurveShape/.geom')
+        self.assertEqual(abcprop['.orders'], [4])
 
 
 if __name__ == '__main__':
 
 
 if __name__ == '__main__':