1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
22 ./blender.bin --background -noaudio --factory-startup --python tests/python/bl_alembic_import_test.py -- --testdir /path/to/lib/tests/alembic
34 class AbstractAlembicTest(unittest.TestCase):
37 cls.testdir = args.testdir
40 self.assertTrue(self.testdir.exists(),
41 'Test dir %s should exist' % self.testdir)
43 # Make sure we always start with a known-empty file.
44 bpy.ops.wm.open_mainfile(filepath=str(self.testdir / "empty.blend"))
46 def assertAlmostEqualFloatArray(self, actual, expect, places=6, delta=None):
47 """Asserts that the arrays of floats are almost equal."""
49 self.assertEqual(len(actual), len(expect),
50 'Actual array has %d items, expected %d' % (len(actual), len(expect)))
52 for idx, (act, exp) in enumerate(zip(actual, expect)):
53 self.assertAlmostEqual(act, exp, places=places, delta=delta,
54 msg='%f != %f at index %d' % (act, exp, idx))
57 class SimpleImportTest(AbstractAlembicTest):
58 def test_import_cube_hierarchy(self):
59 res = bpy.ops.wm.alembic_import(
60 filepath=str(self.testdir / "cubes-hierarchy.abc"),
61 as_background_job=False)
62 self.assertEqual({'FINISHED'}, res)
64 # The objects should be linked to scene_collection in Blender 2.8,
65 # and to scene in Blender 2.7x.
66 objects = bpy.context.scene.objects
67 self.assertEqual(13, len(objects))
70 self.assertIsNone(objects['Cube'].parent)
71 self.assertEqual(objects['Cube'], objects['Cube_001'].parent)
72 self.assertEqual(objects['Cube'], objects['Cube_002'].parent)
73 self.assertEqual(objects['Cube'], objects['Cube_003'].parent)
74 self.assertEqual(objects['Cube_003'], objects['Cube_004'].parent)
75 self.assertEqual(objects['Cube_003'], objects['Cube_005'].parent)
76 self.assertEqual(objects['Cube_003'], objects['Cube_006'].parent)
78 def test_inherit_or_not(self):
79 res = bpy.ops.wm.alembic_import(
80 filepath=str(self.testdir / "T52022-inheritance.abc"),
81 as_background_job=False)
82 self.assertEqual({'FINISHED'}, res)
84 # The objects should be linked to scene_collection in Blender 2.8,
85 # and to scene in Blender 2.7x.
86 objects = bpy.context.scene.objects
88 # ABC parent is top-level object, which translates to nothing in Blender
89 self.assertIsNone(objects['locator1'].parent)
91 # ABC parent is locator1, but locator2 has "inherits Xforms" = false, which
92 # translates to "no parent" in Blender.
93 self.assertIsNone(objects['locator2'].parent)
95 # Shouldn't have inherited the ABC parent's transform.
96 x, y, z = objects['locator2'].matrix_world.to_translation()
97 self.assertAlmostEqual(0, x)
98 self.assertAlmostEqual(0, y)
99 self.assertAlmostEqual(2, z)
101 # ABC parent is inherited and translates to normal parent in Blender.
102 self.assertEqual(objects['locator2'], objects['locatorShape2'].parent)
104 # Should have inherited its ABC parent's transform.
105 x, y, z = objects['locatorShape2'].matrix_world.to_translation()
106 self.assertAlmostEqual(0, x)
107 self.assertAlmostEqual(0, y)
108 self.assertAlmostEqual(2, z)
111 def test_select_after_import(self):
112 # Add a sphere, so that there is something in the scene, selected, and active,
113 # before we do the Alembic import.
114 bpy.ops.mesh.primitive_uv_sphere_add()
115 sphere = bpy.context.active_object
116 self.assertEqual('Sphere', sphere.name)
117 self.assertEqual([sphere], bpy.context.selected_objects)
119 bpy.ops.wm.alembic_import(
120 filepath=str(self.testdir / "cubes-hierarchy.abc"),
121 as_background_job=False)
123 # The active object is probably the first one that was imported, but this
124 # behaviour is not defined. At least it should be one of the cubes, and
126 self.assertNotEqual(sphere, bpy.context.active_object)
127 self.assertTrue('Cube' in bpy.context.active_object.name)
129 # All cubes should be selected, but the sphere shouldn't be.
130 for ob in bpy.data.objects:
131 self.assertEqual('Cube' in ob.name, ob.select)
133 def test_change_path_constraint(self):
136 fname = 'cube-rotating1.abc'
137 abc = self.testdir / fname
138 relpath = bpy.path.relpath(str(abc))
140 res = bpy.ops.wm.alembic_import(filepath=str(abc), as_background_job=False)
141 self.assertEqual({'FINISHED'}, res)
142 cube = bpy.context.active_object
144 # Check that the file loaded ok.
145 bpy.context.scene.frame_set(10)
146 x, y, z = cube.matrix_world.to_euler('XYZ')
147 self.assertAlmostEqual(x, 0)
148 self.assertAlmostEqual(y, 0)
149 self.assertAlmostEqual(z, math.pi / 2, places=5)
151 # Change path from absolute to relative. This should not break the animation.
152 bpy.context.scene.frame_set(1)
153 bpy.data.cache_files[fname].filepath = relpath
154 bpy.context.scene.frame_set(10)
156 x, y, z = cube.matrix_world.to_euler('XYZ')
157 self.assertAlmostEqual(x, 0)
158 self.assertAlmostEqual(y, 0)
159 self.assertAlmostEqual(z, math.pi / 2, places=5)
161 # Replace the Alembic file; this should apply new animation.
162 bpy.data.cache_files[fname].filepath = relpath.replace('1.abc', '2.abc')
163 bpy.context.scene.update()
165 if args.with_legacy_depsgraph:
166 bpy.context.scene.frame_set(10)
168 x, y, z = cube.matrix_world.to_euler('XYZ')
169 self.assertAlmostEqual(x, math.pi / 2, places=5)
170 self.assertAlmostEqual(y, 0)
171 self.assertAlmostEqual(z, 0)
173 def test_change_path_modifier(self):
176 fname = 'animated-mesh.abc'
177 abc = self.testdir / fname
178 relpath = bpy.path.relpath(str(abc))
180 res = bpy.ops.wm.alembic_import(filepath=str(abc), as_background_job=False)
181 self.assertEqual({'FINISHED'}, res)
182 plane = bpy.context.active_object
184 # Check that the file loaded ok.
185 bpy.context.scene.frame_set(6)
186 mesh = plane.to_mesh(bpy.context.scene, True, 'RENDER')
187 self.assertAlmostEqual(-1, mesh.vertices[0].co.x)
188 self.assertAlmostEqual(-1, mesh.vertices[0].co.y)
189 self.assertAlmostEqual(0.5905638933181763, mesh.vertices[0].co.z)
191 # Change path from absolute to relative. This should not break the animation.
192 bpy.context.scene.frame_set(1)
193 bpy.data.cache_files[fname].filepath = relpath
194 bpy.context.scene.frame_set(6)
196 mesh = plane.to_mesh(bpy.context.scene, True, 'RENDER')
197 self.assertAlmostEqual(1, mesh.vertices[3].co.x)
198 self.assertAlmostEqual(1, mesh.vertices[3].co.y)
199 self.assertAlmostEqual(0.5905638933181763, mesh.vertices[3].co.z)
201 def test_import_long_names(self):
202 # This file contains very long names. The longest name is 4047 chars.
203 bpy.ops.wm.alembic_import(
204 filepath=str(self.testdir / "long-names.abc"),
205 as_background_job=False)
207 self.assertIn('Cube', bpy.data.objects)
208 self.assertEqual('CubeShape', bpy.data.objects['Cube'].data.name)
211 class VertexColourImportTest(AbstractAlembicTest):
212 def test_import_from_houdini(self):
213 # Houdini saved "face-varying", and as RGB.
214 res = bpy.ops.wm.alembic_import(
215 filepath=str(self.testdir / "vertex-colours-houdini.abc"),
216 as_background_job=False)
217 self.assertEqual({'FINISHED'}, res)
219 ob = bpy.context.active_object
220 layer = ob.data.vertex_colors['Cf'] # MeshLoopColorLayer
222 # Test some known-good values.
223 self.assertAlmostEqualFloatArray(layer.data[0].color, (0, 0, 0, 1.0))
224 self.assertAlmostEqualFloatArray(layer.data[98].color, (0.9019607, 0.4745098, 0.2666666, 1.0))
225 self.assertAlmostEqualFloatArray(layer.data[99].color, (0.8941176, 0.4705882, 0.2627451, 1.0))
227 def test_import_from_blender(self):
228 # Blender saved per-vertex, and as RGBA.
229 res = bpy.ops.wm.alembic_import(
230 filepath=str(self.testdir / "vertex-colours-blender.abc"),
231 as_background_job=False)
232 self.assertEqual({'FINISHED'}, res)
234 ob = bpy.context.active_object
235 layer = ob.data.vertex_colors['Cf'] # MeshLoopColorLayer
237 # Test some known-good values.
238 self.assertAlmostEqualFloatArray(layer.data[0].color, (1.0, 0.0156862, 0.3607843, 1.0))
239 self.assertAlmostEqualFloatArray(layer.data[98].color, (0.0941176, 0.1215686, 0.9137254, 1.0))
240 self.assertAlmostEqualFloatArray(layer.data[99].color, (0.1294117, 0.3529411, 0.7529411, 1.0))
248 argv = [sys.argv[0]] + sys.argv[sys.argv.index('--') + 1:]
252 parser = argparse.ArgumentParser()
253 parser.add_argument('--testdir', required=True, type=pathlib.Path)
254 parser.add_argument('--with-legacy-depsgraph', default=False,
255 type=lambda v: v in {'ON', 'YES', 'TRUE'})
256 args, remaining = parser.parse_known_args(argv)
258 unittest.main(argv=remaining)
261 if __name__ == "__main__":
263 # So a python error exits Blender itself too
269 traceback.print_exc()