Alembic export: support simple child hairs (Fix T51144)
[blender.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 SimpleImportTest(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 test_import_cube_hierarchy(self):
47         res = bpy.ops.wm.alembic_import(
48             filepath=str(self.testdir / "cubes-hierarchy.abc"),
49             as_background_job=False)
50         self.assertEqual({'FINISHED'}, res)
51
52         # The objects should be linked to scene_collection in Blender 2.8,
53         # and to scene in Blender 2.7x.
54         objects = bpy.context.scene.objects
55         self.assertEqual(13, len(objects))
56
57         # Test the hierarchy.
58         self.assertIsNone(objects['Cube'].parent)
59         self.assertEqual(objects['Cube'], objects['Cube_001'].parent)
60         self.assertEqual(objects['Cube'], objects['Cube_002'].parent)
61         self.assertEqual(objects['Cube'], objects['Cube_003'].parent)
62         self.assertEqual(objects['Cube_003'], objects['Cube_004'].parent)
63         self.assertEqual(objects['Cube_003'], objects['Cube_005'].parent)
64         self.assertEqual(objects['Cube_003'], objects['Cube_006'].parent)
65
66     def test_select_after_import(self):
67         # Add a sphere, so that there is something in the scene, selected, and active,
68         # before we do the Alembic import.
69         bpy.ops.mesh.primitive_uv_sphere_add()
70         sphere = bpy.context.active_object
71         self.assertEqual('Sphere', sphere.name)
72         self.assertEqual([sphere], bpy.context.selected_objects)
73
74         bpy.ops.wm.alembic_import(
75             filepath=str(self.testdir / "cubes-hierarchy.abc"),
76             as_background_job=False)
77
78         # The active object is probably the first one that was imported, but this
79         # behaviour is not defined. At least it should be one of the cubes, and
80         # not the sphere.
81         self.assertNotEqual(sphere, bpy.context.active_object)
82         self.assertTrue('Cube' in bpy.context.active_object.name)
83
84         # All cubes should be selected, but the sphere shouldn't be.
85         for ob in bpy.data.objects:
86             self.assertEqual('Cube' in ob.name, ob.select)
87
88     def test_change_path_constraint(self):
89         import math
90
91         fname = 'cube-rotating1.abc'
92         abc = self.testdir / fname
93         relpath = bpy.path.relpath(str(abc))
94
95         res = bpy.ops.wm.alembic_import(filepath=str(abc), as_background_job=False)
96         self.assertEqual({'FINISHED'}, res)
97         cube = bpy.context.active_object
98
99         # Check that the file loaded ok.
100         bpy.context.scene.frame_set(10)
101         x, y, z = cube.matrix_world.to_euler('XYZ')
102         self.assertAlmostEqual(x, 0)
103         self.assertAlmostEqual(y, 0)
104         self.assertAlmostEqual(z, math.pi / 2, places=5)
105
106         # Change path from absolute to relative. This should not break the animation.
107         bpy.context.scene.frame_set(1)
108         bpy.data.cache_files[fname].filepath = relpath
109         bpy.context.scene.frame_set(10)
110
111         x, y, z = cube.matrix_world.to_euler('XYZ')
112         self.assertAlmostEqual(x, 0)
113         self.assertAlmostEqual(y, 0)
114         self.assertAlmostEqual(z, math.pi / 2, places=5)
115
116         # Replace the Alembic file; this should apply new animation.
117         bpy.data.cache_files[fname].filepath = relpath.replace('1.abc', '2.abc')
118         bpy.context.scene.update()
119
120         x, y, z = cube.matrix_world.to_euler('XYZ')
121         self.assertAlmostEqual(x, math.pi / 2, places=5)
122         self.assertAlmostEqual(y, 0)
123         self.assertAlmostEqual(z, 0)
124
125     def test_change_path_modifier(self):
126         import math
127
128         fname = 'animated-mesh.abc'
129         abc = self.testdir / fname
130         relpath = bpy.path.relpath(str(abc))
131
132         res = bpy.ops.wm.alembic_import(filepath=str(abc), as_background_job=False)
133         self.assertEqual({'FINISHED'}, res)
134         cube = bpy.context.active_object
135
136         # Check that the file loaded ok.
137         bpy.context.scene.frame_set(6)
138         self.assertAlmostEqual(-1, cube.data.vertices[0].co.x)
139         self.assertAlmostEqual(-1, cube.data.vertices[0].co.y)
140         self.assertAlmostEqual(0.5905638933181763, cube.data.vertices[0].co.z)
141
142         # Change path from absolute to relative. This should not break the animation.
143         bpy.context.scene.frame_set(1)
144         bpy.data.cache_files[fname].filepath = relpath
145         bpy.context.scene.frame_set(6)
146
147         self.assertAlmostEqual(1, cube.data.vertices[3].co.x)
148         self.assertAlmostEqual(1, cube.data.vertices[3].co.y)
149         self.assertAlmostEqual(0.5905638933181763, cube.data.vertices[3].co.z)
150
151     def test_import_long_names(self):
152         # This file contains very long names. The longest name is 4047 chars.
153         bpy.ops.wm.alembic_import(
154             filepath=str(self.testdir / "long-names.abc"),
155             as_background_job=False)
156
157         self.assertIn('Cube', bpy.data.objects)
158         self.assertEqual('CubeShape', bpy.data.objects['Cube'].data.name)
159
160
161 def main():
162     global args
163     import argparse
164
165     if '--' in sys.argv:
166         argv = [sys.argv[0]] + sys.argv[sys.argv.index('--')+1:]
167     else:
168         argv = sys.argv
169
170     parser = argparse.ArgumentParser()
171     parser.add_argument('--testdir', required=True, type=pathlib.Path)
172     args, remaining = parser.parse_known_args(argv)
173
174     unittest.main(argv=remaining)
175
176
177 if __name__ == "__main__":
178     import traceback
179     # So a python error exits Blender itself too
180     try:
181         main()
182     except SystemExit:
183         raise
184     except:
185         traceback.print_exc()
186         sys.exit(1)