2db38895f9b504f51b2026ac6daf924f78b77da6
[blender.git] / tests / python / bl_mesh_modifiers.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 # Currently this script only generates images from different modifier
22 # combinations and does not validate they work correctly,
23 # this is because we don't get 1:1 match with bmesh.
24 #
25 # Later, we may have a way to check the results are valid.
26
27
28 # ./blender.bin --factory-startup --python tests/python/bl_mesh_modifiers.py
29 #
30
31 import math
32
33 USE_QUICK_RENDER = False
34 IS_BMESH = hasattr(__import__("bpy").types, "LoopColors")
35
36 # -----------------------------------------------------------------------------
37 # utility functions
38
39
40 def render_gl(context, filepath, shade):
41
42     def ctx_viewport_shade(context, shade):
43         for area in context.window.screen.areas:
44             if area.type == 'VIEW_3D':
45                 space = area.spaces.active
46                 # rv3d = space.region_3d
47                 space.viewport_shade = shade
48
49     import bpy
50     scene = context.scene
51     render = scene.render
52     render.filepath = filepath
53     render.image_settings.file_format = 'PNG'
54     render.image_settings.color_mode = 'RGB'
55     render.use_file_extension = True
56     render.use_antialiasing = False
57
58     # render size
59     render.resolution_percentage = 100
60     render.resolution_x = 512
61     render.resolution_y = 512
62
63     ctx_viewport_shade(context, shade)
64
65     #~ # stop to inspect!
66     #~ if filepath == "test_cube_shell_solidify_subsurf_wp_wire":
67         #~ assert(0)
68     #~ else:
69         #~ return
70
71     bpy.ops.render.opengl(write_still=True,
72                           view_context=True)
73
74
75 def render_gl_all_modes(context, obj, filepath=""):
76
77     assert(obj is not None)
78     assert(filepath != "")
79
80     scene = context.scene
81
82     # avoid drawing outline/center dot
83     bpy.ops.object.select_all(action='DESELECT')
84     scene.objects.active = None
85
86     # editmode
87     scene.tool_settings.mesh_select_mode = False, True, False
88
89     # render
90     render_gl(context, filepath + "_ob_solid", shade='SOLID')
91
92     if USE_QUICK_RENDER:
93         return
94
95     render_gl(context, filepath + "_ob_wire", shade='WIREFRAME')
96     render_gl(context, filepath + "_ob_textured", shade='TEXTURED')
97
98     # -------------------------------------------------------------------------
99     # not just draw modes, but object modes!
100     scene.objects.active = obj
101
102     bpy.ops.object.mode_set(mode='EDIT', toggle=False)
103     bpy.ops.mesh.select_all(action='DESELECT')
104     render_gl(context, filepath + "_edit_wire", shade='WIREFRAME')
105     render_gl(context, filepath + "_edit_solid", shade='SOLID')
106     render_gl(context, filepath + "_edit_textured", shade='TEXTURED')
107     bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
108
109     bpy.ops.object.mode_set(mode='WEIGHT_PAINT', toggle=False)
110
111     render_gl(context, filepath + "_wp_wire", shade='WIREFRAME')
112
113     assert(1)
114
115     bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
116
117     scene.objects.active = None
118
119
120 def ctx_clear_scene():  # copied from batch_import.py
121     import bpy
122     unique_obs = set()
123     for scene in bpy.data.scenes:
124         for obj in scene.objects[:]:
125             scene.objects.unlink(obj)
126             unique_obs.add(obj)
127
128     # remove obdata, for now only worry about the startup scene
129     for bpy_data_iter in (bpy.data.objects,
130                           bpy.data.meshes,
131                           bpy.data.lamps,
132                           bpy.data.cameras,
133                           ):
134
135         for id_data in bpy_data_iter:
136             bpy_data_iter.remove(id_data)
137
138
139 def ctx_viewport_camera(context):
140     # because gl render without view_context has no shading option.
141     for area in context.window.screen.areas:
142         if area.type == 'VIEW_3D':
143             space = area.spaces.active
144             space.region_3d.view_perspective = 'CAMERA'
145
146
147 def ctx_camera_setup(context,
148                      location=(0.0, 0.0, 0.0),
149                      lookat=(0.0, 0.0, 0.0),
150                      # most likely the following vars can be left as defaults
151                      up=(0.0, 0.0, 1.0),
152                      lookat_axis='-Z',
153                      up_axis='Y',
154                      ):
155
156     camera = bpy.data.cameras.new(whoami())
157     obj = bpy.data.objects.new(whoami(), camera)
158
159     scene = context.scene
160     scene.objects.link(obj)
161     scene.camera = obj
162
163     from mathutils import Vector, Matrix
164
165     # setup transform
166     view_vec = Vector(lookat) - Vector(location)
167     rot_mat = view_vec.to_track_quat(lookat_axis, up_axis).to_matrix().to_4x4()
168     tra_mat = Matrix.Translation(location)
169
170     obj.matrix_world = tra_mat * rot_mat
171
172     ctx_viewport_camera(context)
173
174     return obj
175
176
177 # -----------------------------------------------------------------------------
178 # inspect functions
179
180 import inspect
181
182
183 # functions
184
185 def whoami():
186     return inspect.stack()[1][3]
187
188
189 def whosdaddy():
190     return inspect.stack()[2][3]
191
192
193 # -----------------------------------------------------------------------------
194 # models (defaults)
195
196 def defaults_object(obj):
197     obj.show_wire = True
198
199     if obj.type == 'MESH':
200         mesh = obj.data
201         mesh.show_all_edges = True
202
203         mesh.show_normal_vertex = True
204
205         # lame!
206         if IS_BMESH:
207             for poly in mesh.polygons:
208                 poly.use_smooth = True
209         else:
210             for face in mesh.faces:
211                 face.use_smooth = True
212
213
214 def defaults_modifier(mod):
215     mod.show_in_editmode = True
216     mod.show_on_cage = True
217
218
219 # -----------------------------------------------------------------------------
220 # models (utils)
221
222
223 if IS_BMESH:
224     def mesh_bmesh_poly_elems(poly, elems):
225         vert_start = poly.loop_start
226         vert_total = poly.loop_total
227         return elems[vert_start:vert_start + vert_total]
228
229     def mesh_bmesh_poly_vertices(poly):
230         return [loop.vertex_index
231                 for loop in mesh_bmesh_poly_elems(poly, poly.id_data.loops)]
232
233
234 def mesh_bounds(mesh):
235     xmin = ymin = zmin = +100000000.0
236     xmax = ymax = zmax = -100000000.0
237
238     for v in mesh.vertices:
239         x, y, z = v.co
240         xmax = max(x, xmax)
241         ymax = max(y, ymax)
242         zmax = max(z, zmax)
243
244         xmin = min(x, xmin)
245         ymin = min(y, ymin)
246         zmin = min(z, zmin)
247
248     return (xmin, ymin, zmin), (xmax, ymax, zmax)
249
250
251 def mesh_uv_add(obj):
252
253     uvs = ((0.0, 0.0),
254            (0.0, 1.0),
255            (1.0, 1.0),
256            (1.0, 0.0))
257
258     uv_lay = obj.data.uv_textures.new()
259
260     if IS_BMESH:
261         # XXX, odd that we need to do this. until UV's and texface
262         # are separated we will need to keep it
263         uv_loops = obj.data.uv_layers[-1]
264         uv_list = uv_loops.data[:]
265         for poly in obj.data.polygons:
266             poly_uvs = mesh_bmesh_poly_elems(poly, uv_list)
267             for i, c in enumerate(poly_uvs):
268                 c.uv = uvs[i % 4]
269     else:
270         for uv in uv_lay.data:
271             uv.uv1 = uvs[0]
272             uv.uv2 = uvs[1]
273             uv.uv3 = uvs[2]
274             uv.uv4 = uvs[3]
275
276     return uv_lay
277
278
279 def mesh_vcol_add(obj, mode=0):
280
281     colors = ((0.0, 0.0, 0.0),  # black
282               (1.0, 0.0, 0.0),  # red
283               (0.0, 1.0, 0.0),  # green
284               (0.0, 0.0, 1.0),  # blue
285               (1.0, 1.0, 0.0),  # yellow
286               (0.0, 1.0, 1.0),  # cyan
287               (1.0, 0.0, 1.0),  # magenta
288               (1.0, 1.0, 1.0),  # white
289               )
290
291     def colors_get(i):
292         return colors[i % len(colors)]
293
294     vcol_lay = obj.data.vertex_colors.new()
295
296     mesh = obj.data
297
298     if IS_BMESH:
299         col_list = vcol_lay.data[:]
300         for poly in mesh.polygons:
301             face_verts = mesh_bmesh_poly_vertices(poly)
302             poly_cols = mesh_bmesh_poly_elems(poly, col_list)
303             for i, c in enumerate(poly_cols):
304                 c.color = colors_get(face_verts[i])
305     else:
306         for i, col in enumerate(vcol_lay.data):
307             face_verts = mesh.faces[i].vertices
308             col.color1 = colors_get(face_verts[0])
309             col.color2 = colors_get(face_verts[1])
310             col.color3 = colors_get(face_verts[2])
311             if len(face_verts) == 4:
312                 col.color4 = colors_get(face_verts[3])
313
314     return vcol_lay
315
316
317 def mesh_vgroup_add(obj, name="Group", axis=0, invert=False, mode=0):
318     mesh = obj.data
319     vgroup = obj.vertex_groups.new(name=name)
320     vgroup.add(list(range(len(mesh.vertices))), 1.0, 'REPLACE')
321     group_index = len(obj.vertex_groups) - 1
322
323     min_bb, max_bb = mesh_bounds(mesh)
324
325     range_axis = max_bb[axis] - min_bb[axis]
326
327     # gradient
328     for v in mesh.vertices:
329         for vg in v.groups:
330             if vg.group == group_index:
331                 f = (v.co[axis] - min_bb[axis]) / range_axis
332                 vg.weight = 1.0 - f if invert else f
333
334     return vgroup
335
336
337 def mesh_shape_add(obj, mode=0):
338     pass
339
340
341 def mesh_armature_add(obj, mode=0):
342     pass
343
344
345 # -----------------------------------------------------------------------------
346 # modifiers
347
348 def modifier_subsurf_add(scene, obj, levels=2):
349     mod = obj.modifiers.new(name=whoami(), type='SUBSURF')
350     defaults_modifier(mod)
351
352     mod.levels = levels
353     mod.render_levels = levels
354     return mod
355
356
357 def modifier_armature_add(scene, obj):
358     mod = obj.modifiers.new(name=whoami(), type='ARMATURE')
359     defaults_modifier(mod)
360
361     arm_data = bpy.data.armatures.new(whoami())
362     obj_arm = bpy.data.objects.new(whoami(), arm_data)
363
364     scene.objects.link(obj_arm)
365
366     obj_arm.select = True
367     scene.objects.active = obj_arm
368
369     bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
370     bpy.ops.object.mode_set(mode='EDIT', toggle=False)
371
372     # XXX, annoying, remove bone.
373     while arm_data.edit_bones:
374         obj_arm.edit_bones.remove(arm_data.edit_bones[-1])
375
376     bone_a = arm_data.edit_bones.new("Bone.A")
377     bone_b = arm_data.edit_bones.new("Bone.B")
378     bone_b.parent = bone_a
379
380     bone_a.head = -1, 0, 0
381     bone_a.tail = 0, 0, 0
382     bone_b.head = 0, 0, 0
383     bone_b.tail = 1, 0, 0
384
385     # Get armature animation data
386     bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
387
388     # 45d armature
389     obj_arm.pose.bones["Bone.B"].rotation_quaternion = 1, -0.5, 0, 0
390
391     # set back to the original
392     scene.objects.active = obj
393
394     # display options
395     obj_arm.show_x_ray = True
396     arm_data.draw_type = 'STICK'
397
398     # apply to modifier
399     mod.object = obj_arm
400
401     mesh_vgroup_add(obj, name="Bone.A", axis=0, invert=True)
402     mesh_vgroup_add(obj, name="Bone.B", axis=0, invert=False)
403
404     return mod
405
406
407 def modifier_mirror_add(scene, obj):
408     mod = obj.modifiers.new(name=whoami(), type='MIRROR')
409     defaults_modifier(mod)
410
411     return mod
412
413
414 def modifier_solidify_add(scene, obj, thickness=0.25):
415     mod = obj.modifiers.new(name=whoami(), type='SOLIDIFY')
416     defaults_modifier(mod)
417
418     mod.thickness = thickness
419
420     return mod
421
422
423 def modifier_hook_add(scene, obj, use_vgroup=True):
424     scene.objects.active = obj
425
426     # no nice way to add hooks from py api yet
427     # assume object mode, hook first face!
428     mesh = obj.data
429
430     if use_vgroup:
431         for v in mesh.vertices:
432             v.select = True
433     else:
434         for v in mesh.vertices:
435             v.select = False
436
437         for i in mesh.faces[0].vertices:
438             mesh.vertices[i].select = True
439
440     bpy.ops.object.mode_set(mode='EDIT', toggle=False)
441     bpy.ops.object.hook_add_newob()
442     bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
443
444     # mod = obj.modifiers.new(name=whoami(), type='HOOK')
445     mod = obj.modifiers[-1]
446     defaults_modifier(mod)
447
448     obj_hook = mod.object
449     obj_hook.rotation_euler = 0, math.radians(45), 0
450     obj_hook.show_x_ray = True
451
452     if use_vgroup:
453         mod.vertex_group = obj.vertex_groups[0].name
454
455     return mod
456
457
458 def modifier_decimate_add(scene, obj):
459     mod = obj.modifiers.new(name=whoami(), type='DECIMATE')
460     defaults_modifier(mod)
461
462     mod.ratio = 1 / 3
463
464     return mod
465
466
467 def modifier_build_add(scene, obj):
468     mod = obj.modifiers.new(name=whoami(), type='BUILD')
469     defaults_modifier(mod)
470
471     # ensure we display some faces
472     if IS_BMESH:
473         totface = len(obj.data.polygons)
474     else:
475         totface = len(obj.data.faces)
476
477     mod.frame_start = totface // 2
478     mod.frame_duration = totface
479
480     return mod
481
482
483 def modifier_mask_add(scene, obj):
484     mod = obj.modifiers.new(name=whoami(), type='MASK')
485     defaults_modifier(mod)
486
487     mod.vertex_group = obj.vertex_groups[0].name
488
489     return mod
490
491
492 # -----------------------------------------------------------------------------
493 # models
494
495 # useful since its solid boxy shape but simple enough to debug errors
496 cube_like_vertices = (
497     (1, 1, -1),
498     (1, -1, -1),
499     (-1, -1, -1),
500     (-1, 1, -1),
501     (1, 1, 1),
502     (1, -1, 1),
503     (-1, -1, 1),
504     (-1, 1, 1),
505     (0, -1, -1),
506     (1, 0, -1),
507     (0, 1, -1),
508     (-1, 0, -1),
509     (1, 0, 1),
510     (0, -1, 1),
511     (-1, 0, 1),
512     (0, 1, 1),
513     (1, -1, 0),
514     (1, 1, 0),
515     (-1, -1, 0),
516     (-1, 1, 0),
517     (0, 0, -1),
518     (0, 0, 1),
519     (1, 0, 0),
520     (0, -1, 0),
521     (-1, 0, 0),
522     (2, 0, 0),
523     (2, 0, -1),
524     (2, 1, 0),
525     (2, 1, -1),
526     (0, 1, 2),
527     (0, 0, 2),
528     (-1, 0, 2),
529     (-1, 1, 2),
530     (-1, 0, 3),
531     (-1, 1, 3),
532     (0, 1, 3),
533     (0, 0, 3),
534     )
535
536
537 cube_like_faces = (
538     (0, 9, 20, 10),
539     (0, 10, 17),
540     (0, 17, 27, 28),
541     (1, 16, 23, 8),
542     (2, 18, 24, 11),
543     (3, 19, 10),
544     (4, 15, 21, 12),
545     (4, 17, 15),
546     (7, 14, 31, 32),
547     (7, 15, 19),
548     (8, 23, 18, 2),
549     (9, 0, 28, 26),
550     (9, 1, 8, 20),
551     (9, 22, 16, 1),
552     (10, 20, 11, 3),
553     (11, 24, 19, 3),
554     (12, 21, 13, 5),
555     (13, 6, 18),
556     (14, 21, 30, 31),
557     (15, 7, 32, 29),
558     (15, 17, 10, 19),
559     (16, 5, 13, 23),
560     (17, 4, 12, 22),
561     (17, 22, 25, 27),
562     (18, 6, 14, 24),
563     (20, 8, 2, 11),
564     (21, 14, 6, 13),
565     (21, 15, 29, 30),
566     (22, 9, 26, 25),
567     (22, 12, 5, 16),
568     (23, 13, 18),
569     (24, 14, 7, 19),
570     (28, 27, 25, 26),
571     (29, 32, 34, 35),
572     (30, 29, 35, 36),
573     (31, 30, 36, 33),
574     (32, 31, 33, 34),
575     (35, 34, 33, 36),
576     )
577
578
579 # useful since its a shell for solidify and it can be mirrored
580 cube_shell_vertices = (
581     (0, 0, 1),
582     (0, 1, 1),
583     (-1, 1, 1),
584     (-1, 0, 1),
585     (0, 0, 0),
586     (0, 1, 0),
587     (-1, 1, 0),
588     (-1, 0, 0),
589     (-1, -1, 0),
590     (0, -1, 0),
591     (0, 0, -1),
592     (0, 1, -1),
593     )
594
595
596 cube_shell_face = (
597     (0, 1, 2, 3),
598     (0, 3, 8, 9),
599     (1, 5, 6, 2),
600     (2, 6, 7, 3),
601     (3, 7, 8),
602     (4, 7, 10),
603     (6, 5, 11),
604     (7, 4, 9, 8),
605     (10, 7, 6, 11),
606     )
607
608
609 def make_cube(scene):
610     bpy.ops.mesh.primitive_cube_add(view_align=False,
611                                     enter_editmode=False,
612                                     location=(0, 0, 0),
613                                     rotation=(0, 0, 0),
614                                     )
615
616     obj = scene.objects.active
617
618     defaults_object(obj)
619     return obj
620
621
622 def make_cube_extra(scene):
623     obj = make_cube(scene)
624
625     # extra data layers
626     mesh_uv_add(obj)
627     mesh_vcol_add(obj)
628     mesh_vgroup_add(obj)
629
630     return obj
631
632
633 def make_cube_like(scene):
634     mesh = bpy.data.meshes.new(whoami())
635
636     mesh.from_pydata(cube_like_vertices, (), cube_like_faces)
637     mesh.update()  # add edges
638     obj = bpy.data.objects.new(whoami(), mesh)
639     scene.objects.link(obj)
640
641     defaults_object(obj)
642     return obj
643
644
645 def make_cube_like_extra(scene):
646     obj = make_cube_like(scene)
647
648     # extra data layers
649     mesh_uv_add(obj)
650     mesh_vcol_add(obj)
651     mesh_vgroup_add(obj)
652
653     return obj
654
655
656 def make_cube_shell(scene):
657     mesh = bpy.data.meshes.new(whoami())
658
659     mesh.from_pydata(cube_shell_vertices, (), cube_shell_face)
660     mesh.update()  # add edges
661     obj = bpy.data.objects.new(whoami(), mesh)
662     scene.objects.link(obj)
663
664     defaults_object(obj)
665     return obj
666
667
668 def make_cube_shell_extra(scene):
669     obj = make_cube_shell(scene)
670
671     # extra data layers
672     mesh_uv_add(obj)
673     mesh_vcol_add(obj)
674     mesh_vgroup_add(obj)
675
676     return obj
677
678
679 def make_monkey(scene):
680     bpy.ops.mesh.primitive_monkey_add(view_align=False,
681                                       enter_editmode=False,
682                                       location=(0, 0, 0),
683                                       rotation=(0, 0, 0),
684                                       )
685     obj = scene.objects.active
686
687     defaults_object(obj)
688     return obj
689
690
691 def make_monkey_extra(scene):
692     obj = make_monkey(scene)
693
694     # extra data layers
695     mesh_uv_add(obj)
696     mesh_vcol_add(obj)
697     mesh_vgroup_add(obj)
698
699     return obj
700
701
702 # -----------------------------------------------------------------------------
703 # tests (utils)
704
705 global_tests = []
706
707 global_tests.append(("none",
708     (),
709     ))
710
711 # single
712 global_tests.append(("subsurf_single",
713     ((modifier_subsurf_add, dict(levels=2)), ),
714     ))
715
716
717 global_tests.append(("armature_single",
718     ((modifier_armature_add, dict()), ),
719     ))
720
721
722 global_tests.append(("mirror_single",
723     ((modifier_mirror_add, dict()), ),
724     ))
725
726 global_tests.append(("hook_single",
727     ((modifier_hook_add, dict()), ),
728     ))
729
730 global_tests.append(("decimate_single",
731     ((modifier_decimate_add, dict()), ),
732     ))
733
734 global_tests.append(("build_single",
735     ((modifier_build_add, dict()), ),
736     ))
737
738 global_tests.append(("mask_single",
739     ((modifier_mask_add, dict()), ),
740     ))
741
742
743 # combinations
744 global_tests.append(("mirror_subsurf",
745     ((modifier_mirror_add, dict()),
746      (modifier_subsurf_add, dict(levels=2))),
747     ))
748
749 global_tests.append(("solidify_subsurf",
750     ((modifier_solidify_add, dict()),
751      (modifier_subsurf_add, dict(levels=2))),
752     ))
753
754
755 def apply_test(test, scene, obj,
756                render_func=None,
757                render_args=None,
758                render_kwargs=None,
759                ):
760
761     test_name, test_funcs = test
762
763     for cb, kwargs in test_funcs:
764         cb(scene, obj, **kwargs)
765
766     render_kwargs_copy = render_kwargs.copy()
767
768     # add test name in filepath
769     render_kwargs_copy["filepath"] += "_%s" % test_name
770
771     render_func(*render_args, **render_kwargs_copy)
772
773
774 # -----------------------------------------------------------------------------
775 # tests themselves!
776 # having the 'test_' prefix automatically means these functions are called
777 # for testing
778
779
780 def test_cube(context, test):
781     scene = context.scene
782     obj = make_cube_extra(scene)
783     ctx_camera_setup(context, location=(3, 3, 3))
784
785     apply_test(test, scene, obj,
786                render_func=render_gl_all_modes,
787                render_args=(context, obj),
788                render_kwargs=dict(filepath=whoami()))
789
790
791 def test_cube_like(context, test):
792     scene = context.scene
793     obj = make_cube_like_extra(scene)
794     ctx_camera_setup(context, location=(5, 5, 5))
795
796     apply_test(test, scene, obj,
797                render_func=render_gl_all_modes,
798                render_args=(context, obj),
799                render_kwargs=dict(filepath=whoami()))
800
801
802 def test_cube_shell(context, test):
803     scene = context.scene
804     obj = make_cube_shell_extra(scene)
805     ctx_camera_setup(context, location=(4, 4, 4))
806
807     apply_test(test, scene, obj,
808                render_func=render_gl_all_modes,
809                render_args=(context, obj),
810                render_kwargs=dict(filepath=whoami()))
811
812
813 # -----------------------------------------------------------------------------
814 # call all tests
815
816 def main():
817     print("Calling main!")
818     #render_gl(bpy.context, "/testme")
819     #ctx_clear_scene()
820
821     context = bpy.context
822
823     ctx_clear_scene()
824
825     # run all tests
826     for key, val in sorted(globals().items()):
827         if key.startswith("test_") and hasattr(val, "__call__"):
828             print("calling:", key)
829             for t in global_tests:
830                 val(context, test=t)
831                 ctx_clear_scene()
832
833
834 # -----------------------------------------------------------------------------
835 # annoying workaround for theme initialization
836
837 if __name__ == "__main__":
838     import bpy
839     from bpy.app.handlers import persistent
840
841     @persistent
842     def load_handler(dummy):
843         print("Load Handler:", bpy.data.filepath)
844         if load_handler.first is False:
845             bpy.app.handlers.scene_update_post.remove(load_handler)
846             try:
847                 main()
848                 import sys
849                 sys.exit(0)
850             except:
851                 import traceback
852                 traceback.print_exc()
853
854                 # import sys
855                 # sys.exit(1)  # comment to debug
856
857         else:
858             load_handler.first = False
859
860     load_handler.first = True
861     bpy.app.handlers.scene_update_post.append(load_handler)