Fix T52022 Alembic Inherits transform not taken into account
authorSybren A. Stüvel <sybren@stuvel.eu>
Tue, 11 Jul 2017 14:16:56 +0000 (16:16 +0200)
committerSybren A. Stüvel <sybren@stuvel.eu>
Tue, 11 Jul 2017 14:17:27 +0000 (16:17 +0200)
Alembic's "inherits transform" flag wasn't taken into account when
constructing the parent object relations.

source/blender/alembic/intern/abc_object.cc
source/blender/alembic/intern/abc_object.h
source/blender/alembic/intern/alembic_capi.cc
tests/python/bl_alembic_import_test.py

index 04f6da4871160c82c5e71c48e105850765fcd725..6c4cb60d63c8de017079523ab07225bc2b051a92 100644 (file)
@@ -140,6 +140,38 @@ AbcObjectReader::AbcObjectReader(const IObject &object, ImportSettings &settings
        else {
                m_object_name = m_data_name = parts[parts.size() - 1];
        }
+
+       determine_inherits_xform();
+}
+
+/* Determine whether we can inherit our parent's XForm */
+void AbcObjectReader::determine_inherits_xform()
+{
+       m_inherits_xform = false;
+
+       IXform ixform = xform();
+       if (!ixform) {
+               return;
+       }
+
+       const IXformSchema & schema(ixform.getSchema());
+       if (!schema.valid()) {
+               std::cerr << "Alembic object " << ixform.getFullName()
+                         << " has an invalid schema." << std::endl;
+               return;
+       }
+
+       m_inherits_xform = schema.getInheritsXforms();
+
+       IObject ixform_parent = ixform.getParent();
+       if (!ixform_parent.getParent()) {
+               /* The archive top object certainly is not a transform itself, so handle
+                * it as "no parent". */
+               m_inherits_xform = false;
+       }
+       else {
+               m_inherits_xform = ixform_parent && m_inherits_xform;
+       }
 }
 
 AbcObjectReader::~AbcObjectReader()
@@ -286,32 +318,10 @@ void AbcObjectReader::read_matrix(float r_mat[4][4], const float time,
                return;
        }
 
-       bool has_alembic_parent;
-       IObject ixform_parent = ixform.getParent();
-       if (!ixform_parent.getParent()) {
-               /* The archive top object certainly is not a transform itself, so handle
-                * it as "no parent". */
-               has_alembic_parent = false;
-       }
-       else {
-               has_alembic_parent = ixform_parent && schema.getInheritsXforms();
-
-               if (has_alembic_parent && m_object->parent == NULL) {
-                       /* TODO Sybren: This happened in some files. I think I solved it,
-                        * but I'll leave this check in here anyway until we've tested it
-                        * more thoroughly. Better than crashing on a null parent anyway. */
-                       std::cerr << "Alembic object " << m_iobject.getFullName()
-                                 << " with transform " << ixform.getFullName()
-                                 << " has an Alembic parent but no parent Blender object."
-                                 << std::endl;
-                       has_alembic_parent = false;
-               }
-       }
-
        const Imath::M44d matrix = get_matrix(schema, time);
        convert_matrix(matrix, m_object, r_mat);
 
-       if (has_alembic_parent) {
+       if (m_inherits_xform) {
                /* In this case, the matrix in Alembic is in local coordinates, so
                 * convert to world matrix. To prevent us from reading and accumulating
                 * all parent matrices in the Alembic file, we assume that the Blender
index cba79ffede5354e733568215956dfc7192a97c66..852ef451f236f50aac42e5c7d031b43940f58d22 100644 (file)
@@ -143,6 +143,8 @@ protected:
         * modifiers and/or constraints. */
        int m_refcount;
 
+       bool m_inherits_xform;
+
 public:
        AbcObjectReader *parent_reader;
 
@@ -167,6 +169,7 @@ public:
        const std::string & name() const { return m_name; }
        const std::string & object_name() const { return m_object_name; }
        const std::string & data_name() const { return m_data_name; }
+       bool inherits_xform() const { return m_inherits_xform; }
 
        virtual bool valid() const = 0;
        virtual bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header,
@@ -194,6 +197,9 @@ public:
 
        void read_matrix(float r_mat[4][4], const float time,
                         const float scale, bool &is_constant);
+
+protected:
+       void determine_inherits_xform();
 };
 
 Imath::M44d get_matrix(const Alembic::AbcGeom::IXformSchema &schema, const float time);
index 83d971d00d985c97ce7d4c2f1dba21df49ff5747..bee8e92e96977aec34aec357993df8ada0969874 100644 (file)
@@ -760,7 +760,7 @@ static void import_startjob(void *user_data, short *stop, short *do_update, floa
                const AbcObjectReader *parent_reader = reader->parent_reader;
                Object *ob = reader->object();
 
-               if (parent_reader == NULL) {
+               if (parent_reader == NULL || !reader->inherits_xform()) {
                        ob->parent = NULL;
                }
                else {
index d8cd64a1d569a688f2aed6619115d66eae189e76..f45748f86e78dcbecaa2560e754926ffbf72466a 100644 (file)
@@ -75,6 +75,39 @@ class SimpleImportTest(AbstractAlembicTest):
         self.assertEqual(objects['Cube_003'], objects['Cube_005'].parent)
         self.assertEqual(objects['Cube_003'], objects['Cube_006'].parent)
 
+    def test_inherit_or_not(self):
+        res = bpy.ops.wm.alembic_import(
+            filepath=str(self.testdir / "T52022-inheritance.abc"),
+            as_background_job=False)
+        self.assertEqual({'FINISHED'}, res)
+
+        # The objects should be linked to scene_collection in Blender 2.8,
+        # and to scene in Blender 2.7x.
+        objects = bpy.context.scene.objects
+
+        # ABC parent is top-level object, which translates to nothing in Blender
+        self.assertIsNone(objects['locator1'].parent)
+
+        # ABC parent is locator1, but locator2 has "inherits Xforms" = false, which
+        # translates to "no parent" in Blender.
+        self.assertIsNone(objects['locator2'].parent)
+
+        # Shouldn't have inherited the ABC parent's transform.
+        x, y, z = objects['locator2'].matrix_world.to_translation()
+        self.assertAlmostEqual(0, x)
+        self.assertAlmostEqual(0, y)
+        self.assertAlmostEqual(2, z)
+
+        # ABC parent is inherited and translates to normal parent in Blender.
+        self.assertEqual(objects['locator2'], objects['locatorShape2'].parent)
+
+        # Should have inherited its ABC parent's transform.
+        x, y, z = objects['locatorShape2'].matrix_world.to_translation()
+        self.assertAlmostEqual(0, x)
+        self.assertAlmostEqual(0, y)
+        self.assertAlmostEqual(2, z)
+
+
     def test_select_after_import(self):
         # Add a sphere, so that there is something in the scene, selected, and active,
         # before we do the Alembic import.