Fix T51762: Unit test script_alembic_import is failing.
[blender-staging.git] / tests / python / bl_alembic_import_test.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
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.
7 #
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.
12 #
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.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18
19 # <pep8 compliant>
20
21 """
22 ./blender.bin --background -noaudio --factory-startup --python tests/python/bl_alembic_import_test.py -- --testdir /path/to/lib/tests/alembic
23 """
24
25 import pathlib
26 import sys
27 import unittest
28
29 import bpy
30
31 args = None
32
33
34 class AbstractAlembicTest(unittest.TestCase):
35     @classmethod
36     def setUpClass(cls):
37         cls.testdir = args.testdir
38
39     def setUp(self):
40         self.assertTrue(self.testdir.exists(),
41                         'Test dir %s should exist' % self.testdir)
42
43         # Make sure we always start with a known-empty file.
44         bpy.ops.wm.open_mainfile(filepath=str(self.testdir / "empty.blend"))
45
46     def assertAlmostEqualFloatArray(self, actual, expect, places=6, delta=None):
47         """Asserts that the arrays of floats are almost equal."""
48
49         self.assertEqual(len(actual), len(expect),
50                          'Actual array has %d items, expected %d' % (len(actual), len(expect)))
51
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))
55
56
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)
63
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))
68
69         # Test the hierarchy.
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)
77
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)
85
86         bpy.ops.wm.alembic_import(
87             filepath=str(self.testdir / "cubes-hierarchy.abc"),
88             as_background_job=False)
89
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
92         # not the sphere.
93         self.assertNotEqual(sphere, bpy.context.active_object)
94         self.assertTrue('Cube' in bpy.context.active_object.name)
95
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)
99
100     def test_change_path_constraint(self):
101         import math
102
103         fname = 'cube-rotating1.abc'
104         abc = self.testdir / fname
105         relpath = bpy.path.relpath(str(abc))
106
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
110
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)
117
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)
122
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)
127
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()
131
132         if args.with_legacy_depsgraph:
133             bpy.context.scene.frame_set(10)
134
135         x, y, z = cube.matrix_world.to_euler('XYZ')
136         self.assertAlmostEqual(x, math.pi / 2, places=5)
137         self.assertAlmostEqual(y, 0)
138         self.assertAlmostEqual(z, 0)
139
140     def test_change_path_modifier(self):
141         import math
142
143         fname = 'animated-mesh.abc'
144         abc = self.testdir / fname
145         relpath = bpy.path.relpath(str(abc))
146
147         res = bpy.ops.wm.alembic_import(filepath=str(abc), as_background_job=False)
148         self.assertEqual({'FINISHED'}, res)
149         cube = bpy.context.active_object
150
151         # Check that the file loaded ok.
152         bpy.context.scene.frame_set(6)
153         self.assertAlmostEqual(-1, cube.data.vertices[0].co.x)
154         self.assertAlmostEqual(-1, cube.data.vertices[0].co.y)
155         self.assertAlmostEqual(0.5905638933181763, cube.data.vertices[0].co.z)
156
157         # Change path from absolute to relative. This should not break the animation.
158         bpy.context.scene.frame_set(1)
159         bpy.data.cache_files[fname].filepath = relpath
160         bpy.context.scene.frame_set(6)
161
162         self.assertAlmostEqual(1, cube.data.vertices[3].co.x)
163         self.assertAlmostEqual(1, cube.data.vertices[3].co.y)
164         self.assertAlmostEqual(0.5905638933181763, cube.data.vertices[3].co.z)
165
166     def test_import_long_names(self):
167         # This file contains very long names. The longest name is 4047 chars.
168         bpy.ops.wm.alembic_import(
169             filepath=str(self.testdir / "long-names.abc"),
170             as_background_job=False)
171
172         self.assertIn('Cube', bpy.data.objects)
173         self.assertEqual('CubeShape', bpy.data.objects['Cube'].data.name)
174
175
176 class VertexColourImportTest(AbstractAlembicTest):
177     def test_import_from_houdini(self):
178         # Houdini saved "face-varying", and as RGB.
179         res = bpy.ops.wm.alembic_import(
180             filepath=str(self.testdir / "vertex-colours-houdini.abc"),
181             as_background_job=False)
182         self.assertEqual({'FINISHED'}, res)
183
184         ob = bpy.context.active_object
185         layer = ob.data.vertex_colors['Cf']  # MeshLoopColorLayer
186
187         # Test some known-good values.
188         self.assertAlmostEqualFloatArray(layer.data[0].color, (0, 0, 0))
189         self.assertAlmostEqualFloatArray(layer.data[98].color, (0.9019607, 0.4745098, 0.2666666))
190         self.assertAlmostEqualFloatArray(layer.data[99].color, (0.8941176, 0.4705882, 0.2627451))
191
192     def test_import_from_blender(self):
193         # Blender saved per-vertex, and as RGBA.
194         res = bpy.ops.wm.alembic_import(
195             filepath=str(self.testdir / "vertex-colours-blender.abc"),
196             as_background_job=False)
197         self.assertEqual({'FINISHED'}, res)
198
199         ob = bpy.context.active_object
200         layer = ob.data.vertex_colors['Cf']  # MeshLoopColorLayer
201
202         # Test some known-good values.
203         self.assertAlmostEqualFloatArray(layer.data[0].color, (1.0, 0.0156862, 0.3607843))
204         self.assertAlmostEqualFloatArray(layer.data[98].color, (0.0941176, 0.1215686, 0.9137254))
205         self.assertAlmostEqualFloatArray(layer.data[99].color, (0.1294117, 0.3529411, 0.7529411))
206
207
208 def main():
209     global args
210     import argparse
211
212     if '--' in sys.argv:
213         argv = [sys.argv[0]] + sys.argv[sys.argv.index('--') + 1:]
214     else:
215         argv = sys.argv
216
217     parser = argparse.ArgumentParser()
218     parser.add_argument('--testdir', required=True, type=pathlib.Path)
219     parser.add_argument('--with-legacy-depsgraph', default=False,
220                         type=lambda v: v in {'ON', 'YES', 'TRUE'})
221     args, remaining = parser.parse_known_args(argv)
222
223     unittest.main(argv=remaining)
224
225
226 if __name__ == "__main__":
227     import traceback
228     # So a python error exits Blender itself too
229     try:
230         main()
231     except SystemExit:
232         raise
233     except:
234         traceback.print_exc()
235         sys.exit(1)