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_select_after_import(self):
79 # Add a sphere, so that there is something in the scene, selected, and active,
80 # before we do the Alembic import.
81 bpy.ops.mesh.primitive_uv_sphere_add()
82 sphere = bpy.context.active_object
83 self.assertEqual('Sphere', sphere.name)
84 self.assertEqual([sphere], bpy.context.selected_objects)
86 bpy.ops.wm.alembic_import(
87 filepath=str(self.testdir / "cubes-hierarchy.abc"),
88 as_background_job=False)
90 # The active object is probably the first one that was imported, but this
91 # behaviour is not defined. At least it should be one of the cubes, and
93 self.assertNotEqual(sphere, bpy.context.active_object)
94 self.assertTrue('Cube' in bpy.context.active_object.name)
96 # All cubes should be selected, but the sphere shouldn't be.
97 for ob in bpy.data.objects:
98 self.assertEqual('Cube' in ob.name, ob.select)
100 def test_change_path_constraint(self):
103 fname = 'cube-rotating1.abc'
104 abc = self.testdir / fname
105 relpath = bpy.path.relpath(str(abc))
107 res = bpy.ops.wm.alembic_import(filepath=str(abc), as_background_job=False)
108 self.assertEqual({'FINISHED'}, res)
109 cube = bpy.context.active_object
111 # Check that the file loaded ok.
112 bpy.context.scene.frame_set(10)
113 x, y, z = cube.matrix_world.to_euler('XYZ')
114 self.assertAlmostEqual(x, 0)
115 self.assertAlmostEqual(y, 0)
116 self.assertAlmostEqual(z, math.pi / 2, places=5)
118 # Change path from absolute to relative. This should not break the animation.
119 bpy.context.scene.frame_set(1)
120 bpy.data.cache_files[fname].filepath = relpath
121 bpy.context.scene.frame_set(10)
123 x, y, z = cube.matrix_world.to_euler('XYZ')
124 self.assertAlmostEqual(x, 0)
125 self.assertAlmostEqual(y, 0)
126 self.assertAlmostEqual(z, math.pi / 2, places=5)
128 # Replace the Alembic file; this should apply new animation.
129 bpy.data.cache_files[fname].filepath = relpath.replace('1.abc', '2.abc')
130 bpy.context.scene.update()
132 x, y, z = cube.matrix_world.to_euler('XYZ')
133 self.assertAlmostEqual(x, math.pi / 2, places=5)
134 self.assertAlmostEqual(y, 0)
135 self.assertAlmostEqual(z, 0)
137 def test_change_path_modifier(self):
140 fname = 'animated-mesh.abc'
141 abc = self.testdir / fname
142 relpath = bpy.path.relpath(str(abc))
144 res = bpy.ops.wm.alembic_import(filepath=str(abc), as_background_job=False)
145 self.assertEqual({'FINISHED'}, res)
146 cube = bpy.context.active_object
148 # Check that the file loaded ok.
149 bpy.context.scene.frame_set(6)
150 self.assertAlmostEqual(-1, cube.data.vertices[0].co.x)
151 self.assertAlmostEqual(-1, cube.data.vertices[0].co.y)
152 self.assertAlmostEqual(0.5905638933181763, cube.data.vertices[0].co.z)
154 # Change path from absolute to relative. This should not break the animation.
155 bpy.context.scene.frame_set(1)
156 bpy.data.cache_files[fname].filepath = relpath
157 bpy.context.scene.frame_set(6)
159 self.assertAlmostEqual(1, cube.data.vertices[3].co.x)
160 self.assertAlmostEqual(1, cube.data.vertices[3].co.y)
161 self.assertAlmostEqual(0.5905638933181763, cube.data.vertices[3].co.z)
163 def test_import_long_names(self):
164 # This file contains very long names. The longest name is 4047 chars.
165 bpy.ops.wm.alembic_import(
166 filepath=str(self.testdir / "long-names.abc"),
167 as_background_job=False)
169 self.assertIn('Cube', bpy.data.objects)
170 self.assertEqual('CubeShape', bpy.data.objects['Cube'].data.name)
173 class VertexColourImportTest(AbstractAlembicTest):
174 def test_import_from_houdini(self):
175 # Houdini saved "face-varying", and as RGB.
176 res = bpy.ops.wm.alembic_import(
177 filepath=str(self.testdir / "vertex-colours-houdini.abc"),
178 as_background_job=False)
179 self.assertEqual({'FINISHED'}, res)
181 ob = bpy.context.active_object
182 layer = ob.data.vertex_colors['Cf'] # MeshLoopColorLayer
184 # Test some known-good values.
185 self.assertAlmostEqualFloatArray(layer.data[0].color, (0, 0, 0))
186 self.assertAlmostEqualFloatArray(layer.data[98].color, (0.9019607, 0.4745098, 0.2666666))
187 self.assertAlmostEqualFloatArray(layer.data[99].color, (0.8941176, 0.4705882, 0.2627451))
189 def test_import_from_blender(self):
190 # Blender saved per-vertex, and as RGBA.
191 res = bpy.ops.wm.alembic_import(
192 filepath=str(self.testdir / "vertex-colours-blender.abc"),
193 as_background_job=False)
194 self.assertEqual({'FINISHED'}, res)
196 ob = bpy.context.active_object
197 layer = ob.data.vertex_colors['Cf'] # MeshLoopColorLayer
199 # Test some known-good values.
200 self.assertAlmostEqualFloatArray(layer.data[0].color, (1.0, 0.0156862, 0.3607843))
201 self.assertAlmostEqualFloatArray(layer.data[98].color, (0.0941176, 0.1215686, 0.9137254))
202 self.assertAlmostEqualFloatArray(layer.data[99].color, (0.1294117, 0.3529411, 0.7529411))
210 argv = [sys.argv[0]] + sys.argv[sys.argv.index('--')+1:]
214 parser = argparse.ArgumentParser()
215 parser.add_argument('--testdir', required=True, type=pathlib.Path)
216 args, remaining = parser.parse_known_args(argv)
218 unittest.main(argv=remaining)
221 if __name__ == "__main__":
223 # So a python error exits Blender itself too
229 traceback.print_exc()