Cleanup: pep8
[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         obj.show_all_edges = True
201
202         mesh = obj.data
203
204         mesh.show_normal_vertex = True
205
206         # lame!
207         if IS_BMESH:
208             for poly in mesh.polygons:
209                 poly.use_smooth = True
210         else:
211             for face in mesh.faces:
212                 face.use_smooth = True
213
214
215 def defaults_modifier(mod):
216     mod.show_in_editmode = True
217     mod.show_on_cage = True
218
219
220 # -----------------------------------------------------------------------------
221 # models (utils)
222
223
224 if IS_BMESH:
225     def mesh_bmesh_poly_elems(poly, elems):
226         vert_start = poly.loop_start
227         vert_total = poly.loop_total
228         return elems[vert_start:vert_start + vert_total]
229
230     def mesh_bmesh_poly_vertices(poly):
231         return [loop.vertex_index
232                 for loop in mesh_bmesh_poly_elems(poly, poly.id_data.loops)]
233
234
235 def mesh_bounds(mesh):
236     xmin = ymin = zmin = +100000000.0
237     xmax = ymax = zmax = -100000000.0
238
239     for v in mesh.vertices:
240         x, y, z = v.co
241         xmax = max(x, xmax)
242         ymax = max(y, ymax)
243         zmax = max(z, zmax)
244
245         xmin = min(x, xmin)
246         ymin = min(y, ymin)
247         zmin = min(z, zmin)
248
249     return (xmin, ymin, zmin), (xmax, ymax, zmax)
250
251
252 def mesh_uv_add(obj):
253
254     uvs = ((0.0, 0.0),
255            (0.0, 1.0),
256            (1.0, 1.0),
257            (1.0, 0.0))
258
259     uv_lay = obj.data.uv_textures.new()
260
261     if IS_BMESH:
262         # XXX, odd that we need to do this. until UV's and texface
263         # are separated we will need to keep it
264         uv_loops = obj.data.uv_layers[-1]
265         uv_list = uv_loops.data[:]
266         for poly in obj.data.polygons:
267             poly_uvs = mesh_bmesh_poly_elems(poly, uv_list)
268             for i, c in enumerate(poly_uvs):
269                 c.uv = uvs[i % 4]
270     else:
271         for uv in uv_lay.data:
272             uv.uv1 = uvs[0]
273             uv.uv2 = uvs[1]
274             uv.uv3 = uvs[2]
275             uv.uv4 = uvs[3]
276
277     return uv_lay
278
279
280 def mesh_vcol_add(obj, mode=0):
281
282     colors = ((0.0, 0.0, 0.0),  # black
283               (1.0, 0.0, 0.0),  # red
284               (0.0, 1.0, 0.0),  # green
285               (0.0, 0.0, 1.0),  # blue
286               (1.0, 1.0, 0.0),  # yellow
287               (0.0, 1.0, 1.0),  # cyan
288               (1.0, 0.0, 1.0),  # magenta
289               (1.0, 1.0, 1.0),  # white
290               )
291
292     def colors_get(i):
293         return colors[i % len(colors)]
294
295     vcol_lay = obj.data.vertex_colors.new()
296
297     mesh = obj.data
298
299     if IS_BMESH:
300         col_list = vcol_lay.data[:]
301         for poly in mesh.polygons:
302             face_verts = mesh_bmesh_poly_vertices(poly)
303             poly_cols = mesh_bmesh_poly_elems(poly, col_list)
304             for i, c in enumerate(poly_cols):
305                 c.color = colors_get(face_verts[i])
306     else:
307         for i, col in enumerate(vcol_lay.data):
308             face_verts = mesh.faces[i].vertices
309             col.color1 = colors_get(face_verts[0])
310             col.color2 = colors_get(face_verts[1])
311             col.color3 = colors_get(face_verts[2])
312             if len(face_verts) == 4:
313                 col.color4 = colors_get(face_verts[3])
314
315     return vcol_lay
316
317
318 def mesh_vgroup_add(obj, name="Group", axis=0, invert=False, mode=0):
319     mesh = obj.data
320     vgroup = obj.vertex_groups.new(name=name)
321     vgroup.add(list(range(len(mesh.vertices))), 1.0, 'REPLACE')
322     group_index = len(obj.vertex_groups) - 1
323
324     min_bb, max_bb = mesh_bounds(mesh)
325
326     range_axis = max_bb[axis] - min_bb[axis]
327
328     # gradient
329     for v in mesh.vertices:
330         for vg in v.groups:
331             if vg.group == group_index:
332                 f = (v.co[axis] - min_bb[axis]) / range_axis
333                 vg.weight = 1.0 - f if invert else f
334
335     return vgroup
336
337
338 def mesh_shape_add(obj, mode=0):
339     pass
340
341
342 def mesh_armature_add(obj, mode=0):
343     pass
344
345
346 # -----------------------------------------------------------------------------
347 # modifiers
348
349 def modifier_subsurf_add(scene, obj, levels=2):
350     mod = obj.modifiers.new(name=whoami(), type='SUBSURF')
351     defaults_modifier(mod)
352
353     mod.levels = levels
354     mod.render_levels = levels
355     return mod
356
357
358 def modifier_armature_add(scene, obj):
359     mod = obj.modifiers.new(name=whoami(), type='ARMATURE')
360     defaults_modifier(mod)
361
362     arm_data = bpy.data.armatures.new(whoami())
363     obj_arm = bpy.data.objects.new(whoami(), arm_data)
364
365     scene.objects.link(obj_arm)
366
367     obj_arm.select = True
368     scene.objects.active = obj_arm
369
370     bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
371     bpy.ops.object.mode_set(mode='EDIT', toggle=False)
372
373     # XXX, annoying, remove bone.
374     while arm_data.edit_bones:
375         obj_arm.edit_bones.remove(arm_data.edit_bones[-1])
376
377     bone_a = arm_data.edit_bones.new("Bone.A")
378     bone_b = arm_data.edit_bones.new("Bone.B")
379     bone_b.parent = bone_a
380
381     bone_a.head = -1, 0, 0
382     bone_a.tail = 0, 0, 0
383     bone_b.head = 0, 0, 0
384     bone_b.tail = 1, 0, 0
385
386     # Get armature animation data
387     bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
388
389     # 45d armature
390     obj_arm.pose.bones["Bone.B"].rotation_quaternion = 1, -0.5, 0, 0
391
392     # set back to the original
393     scene.objects.active = obj
394
395     # display options
396     obj_arm.show_x_ray = True
397     arm_data.draw_type = 'STICK'
398
399     # apply to modifier
400     mod.object = obj_arm
401
402     mesh_vgroup_add(obj, name="Bone.A", axis=0, invert=True)
403     mesh_vgroup_add(obj, name="Bone.B", axis=0, invert=False)
404
405     return mod
406
407
408 def modifier_mirror_add(scene, obj):
409     mod = obj.modifiers.new(name=whoami(), type='MIRROR')
410     defaults_modifier(mod)
411
412     return mod
413
414
415 def modifier_solidify_add(scene, obj, thickness=0.25):
416     mod = obj.modifiers.new(name=whoami(), type='SOLIDIFY')
417     defaults_modifier(mod)
418
419     mod.thickness = thickness
420
421     return mod
422
423
424 def modifier_hook_add(scene, obj, use_vgroup=True):
425     scene.objects.active = obj
426
427     # no nice way to add hooks from py api yet
428     # assume object mode, hook first face!
429     mesh = obj.data
430
431     if use_vgroup:
432         for v in mesh.vertices:
433             v.select = True
434     else:
435         for v in mesh.vertices:
436             v.select = False
437
438         for i in mesh.faces[0].vertices:
439             mesh.vertices[i].select = True
440
441     bpy.ops.object.mode_set(mode='EDIT', toggle=False)
442     bpy.ops.object.hook_add_newob()
443     bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
444
445     # mod = obj.modifiers.new(name=whoami(), type='HOOK')
446     mod = obj.modifiers[-1]
447     defaults_modifier(mod)
448
449     obj_hook = mod.object
450     obj_hook.rotation_euler = 0, math.radians(45), 0
451     obj_hook.show_x_ray = True
452
453     if use_vgroup:
454         mod.vertex_group = obj.vertex_groups[0].name
455
456     return mod
457
458
459 def modifier_decimate_add(scene, obj):
460     mod = obj.modifiers.new(name=whoami(), type='DECIMATE')
461     defaults_modifier(mod)
462
463     mod.ratio = 1 / 3
464
465     return mod
466
467
468 def modifier_build_add(scene, obj):
469     mod = obj.modifiers.new(name=whoami(), type='BUILD')
470     defaults_modifier(mod)
471
472     # ensure we display some faces
473     if IS_BMESH:
474         totface = len(obj.data.polygons)
475     else:
476         totface = len(obj.data.faces)
477
478     mod.frame_start = totface // 2
479     mod.frame_duration = totface
480
481     return mod
482
483
484 def modifier_mask_add(scene, obj):
485     mod = obj.modifiers.new(name=whoami(), type='MASK')
486     defaults_modifier(mod)
487
488     mod.vertex_group = obj.vertex_groups[0].name
489
490     return mod
491
492
493 # -----------------------------------------------------------------------------
494 # models
495
496 # useful since its solid boxy shape but simple enough to debug errors
497 cube_like_vertices = (
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     (-1, 1, 1),
506     (0, -1, -1),
507     (1, 0, -1),
508     (0, 1, -1),
509     (-1, 0, -1),
510     (1, 0, 1),
511     (0, -1, 1),
512     (-1, 0, 1),
513     (0, 1, 1),
514     (1, -1, 0),
515     (1, 1, 0),
516     (-1, -1, 0),
517     (-1, 1, 0),
518     (0, 0, -1),
519     (0, 0, 1),
520     (1, 0, 0),
521     (0, -1, 0),
522     (-1, 0, 0),
523     (2, 0, 0),
524     (2, 0, -1),
525     (2, 1, 0),
526     (2, 1, -1),
527     (0, 1, 2),
528     (0, 0, 2),
529     (-1, 0, 2),
530     (-1, 1, 2),
531     (-1, 0, 3),
532     (-1, 1, 3),
533     (0, 1, 3),
534     (0, 0, 3),
535     )
536
537
538 cube_like_faces = (
539     (0, 9, 20, 10),
540     (0, 10, 17),
541     (0, 17, 27, 28),
542     (1, 16, 23, 8),
543     (2, 18, 24, 11),
544     (3, 19, 10),
545     (4, 15, 21, 12),
546     (4, 17, 15),
547     (7, 14, 31, 32),
548     (7, 15, 19),
549     (8, 23, 18, 2),
550     (9, 0, 28, 26),
551     (9, 1, 8, 20),
552     (9, 22, 16, 1),
553     (10, 20, 11, 3),
554     (11, 24, 19, 3),
555     (12, 21, 13, 5),
556     (13, 6, 18),
557     (14, 21, 30, 31),
558     (15, 7, 32, 29),
559     (15, 17, 10, 19),
560     (16, 5, 13, 23),
561     (17, 4, 12, 22),
562     (17, 22, 25, 27),
563     (18, 6, 14, 24),
564     (20, 8, 2, 11),
565     (21, 14, 6, 13),
566     (21, 15, 29, 30),
567     (22, 9, 26, 25),
568     (22, 12, 5, 16),
569     (23, 13, 18),
570     (24, 14, 7, 19),
571     (28, 27, 25, 26),
572     (29, 32, 34, 35),
573     (30, 29, 35, 36),
574     (31, 30, 36, 33),
575     (32, 31, 33, 34),
576     (35, 34, 33, 36),
577     )
578
579
580 # useful since its a shell for solidify and it can be mirrored
581 cube_shell_vertices = (
582     (0, 0, 1),
583     (0, 1, 1),
584     (-1, 1, 1),
585     (-1, 0, 1),
586     (0, 0, 0),
587     (0, 1, 0),
588     (-1, 1, 0),
589     (-1, 0, 0),
590     (-1, -1, 0),
591     (0, -1, 0),
592     (0, 0, -1),
593     (0, 1, -1),
594     )
595
596
597 cube_shell_face = (
598     (0, 1, 2, 3),
599     (0, 3, 8, 9),
600     (1, 5, 6, 2),
601     (2, 6, 7, 3),
602     (3, 7, 8),
603     (4, 7, 10),
604     (6, 5, 11),
605     (7, 4, 9, 8),
606     (10, 7, 6, 11),
607     )
608
609
610 def make_cube(scene):
611     bpy.ops.mesh.primitive_cube_add(view_align=False,
612                                     enter_editmode=False,
613                                     location=(0, 0, 0),
614                                     rotation=(0, 0, 0),
615                                     )
616
617     obj = scene.objects.active
618
619     defaults_object(obj)
620     return obj
621
622
623 def make_cube_extra(scene):
624     obj = make_cube(scene)
625
626     # extra data layers
627     mesh_uv_add(obj)
628     mesh_vcol_add(obj)
629     mesh_vgroup_add(obj)
630
631     return obj
632
633
634 def make_cube_like(scene):
635     mesh = bpy.data.meshes.new(whoami())
636
637     mesh.from_pydata(cube_like_vertices, (), cube_like_faces)
638     mesh.update()  # add edges
639     obj = bpy.data.objects.new(whoami(), mesh)
640     scene.objects.link(obj)
641
642     defaults_object(obj)
643     return obj
644
645
646 def make_cube_like_extra(scene):
647     obj = make_cube_like(scene)
648
649     # extra data layers
650     mesh_uv_add(obj)
651     mesh_vcol_add(obj)
652     mesh_vgroup_add(obj)
653
654     return obj
655
656
657 def make_cube_shell(scene):
658     mesh = bpy.data.meshes.new(whoami())
659
660     mesh.from_pydata(cube_shell_vertices, (), cube_shell_face)
661     mesh.update()  # add edges
662     obj = bpy.data.objects.new(whoami(), mesh)
663     scene.objects.link(obj)
664
665     defaults_object(obj)
666     return obj
667
668
669 def make_cube_shell_extra(scene):
670     obj = make_cube_shell(scene)
671
672     # extra data layers
673     mesh_uv_add(obj)
674     mesh_vcol_add(obj)
675     mesh_vgroup_add(obj)
676
677     return obj
678
679
680 def make_monkey(scene):
681     bpy.ops.mesh.primitive_monkey_add(view_align=False,
682                                       enter_editmode=False,
683                                       location=(0, 0, 0),
684                                       rotation=(0, 0, 0),
685                                       )
686     obj = scene.objects.active
687
688     defaults_object(obj)
689     return obj
690
691
692 def make_monkey_extra(scene):
693     obj = make_monkey(scene)
694
695     # extra data layers
696     mesh_uv_add(obj)
697     mesh_vcol_add(obj)
698     mesh_vgroup_add(obj)
699
700     return obj
701
702
703 # -----------------------------------------------------------------------------
704 # tests (utils)
705
706 global_tests = []
707
708 global_tests.append(("none",
709     (),
710     ))
711
712 # single
713 global_tests.append(("subsurf_single",
714     ((modifier_subsurf_add, dict(levels=2)), ),
715     ))
716
717
718 global_tests.append(("armature_single",
719     ((modifier_armature_add, dict()), ),
720     ))
721
722
723 global_tests.append(("mirror_single",
724     ((modifier_mirror_add, dict()), ),
725     ))
726
727 global_tests.append(("hook_single",
728     ((modifier_hook_add, dict()), ),
729     ))
730
731 global_tests.append(("decimate_single",
732     ((modifier_decimate_add, dict()), ),
733     ))
734
735 global_tests.append(("build_single",
736     ((modifier_build_add, dict()), ),
737     ))
738
739 global_tests.append(("mask_single",
740     ((modifier_mask_add, dict()), ),
741     ))
742
743
744 # combinations
745 global_tests.append(("mirror_subsurf",
746     ((modifier_mirror_add, dict()),
747      (modifier_subsurf_add, dict(levels=2))),
748     ))
749
750 global_tests.append(("solidify_subsurf",
751     ((modifier_solidify_add, dict()),
752      (modifier_subsurf_add, dict(levels=2))),
753     ))
754
755
756 def apply_test(test, scene, obj,
757                render_func=None,
758                render_args=None,
759                render_kwargs=None,
760                ):
761
762     test_name, test_funcs = test
763
764     for cb, kwargs in test_funcs:
765         cb(scene, obj, **kwargs)
766
767     render_kwargs_copy = render_kwargs.copy()
768
769     # add test name in filepath
770     render_kwargs_copy["filepath"] += "_%s" % test_name
771
772     render_func(*render_args, **render_kwargs_copy)
773
774
775 # -----------------------------------------------------------------------------
776 # tests themselves!
777 # having the 'test_' prefix automatically means these functions are called
778 # for testing
779
780
781 def test_cube(context, test):
782     scene = context.scene
783     obj = make_cube_extra(scene)
784     ctx_camera_setup(context, location=(3, 3, 3))
785
786     apply_test(test, scene, obj,
787                render_func=render_gl_all_modes,
788                render_args=(context, obj),
789                render_kwargs=dict(filepath=whoami()))
790
791
792 def test_cube_like(context, test):
793     scene = context.scene
794     obj = make_cube_like_extra(scene)
795     ctx_camera_setup(context, location=(5, 5, 5))
796
797     apply_test(test, scene, obj,
798                render_func=render_gl_all_modes,
799                render_args=(context, obj),
800                render_kwargs=dict(filepath=whoami()))
801
802
803 def test_cube_shell(context, test):
804     scene = context.scene
805     obj = make_cube_shell_extra(scene)
806     ctx_camera_setup(context, location=(4, 4, 4))
807
808     apply_test(test, scene, obj,
809                render_func=render_gl_all_modes,
810                render_args=(context, obj),
811                render_kwargs=dict(filepath=whoami()))
812
813
814 # -----------------------------------------------------------------------------
815 # call all tests
816
817 def main():
818     print("Calling main!")
819     # render_gl(bpy.context, "/testme")
820     # ctx_clear_scene()
821
822     context = bpy.context
823
824     ctx_clear_scene()
825
826     # run all tests
827     for key, val in sorted(globals().items()):
828         if key.startswith("test_") and hasattr(val, "__call__"):
829             print("calling:", key)
830             for t in global_tests:
831                 val(context, test=t)
832                 ctx_clear_scene()
833
834
835 # -----------------------------------------------------------------------------
836 # annoying workaround for theme initialization
837
838 if __name__ == "__main__":
839     import bpy
840     from bpy.app.handlers import persistent
841
842     @persistent
843     def load_handler(dummy):
844         print("Load Handler:", bpy.data.filepath)
845         if load_handler.first is False:
846             bpy.app.handlers.scene_update_post.remove(load_handler)
847             try:
848                 main()
849                 import sys
850                 sys.exit(0)
851             except:
852                 import traceback
853                 traceback.print_exc()
854
855                 # import sys
856                 # sys.exit(1)  # comment to debug
857
858         else:
859             load_handler.first = False
860
861     load_handler.first = True
862     bpy.app.handlers.scene_update_post.append(load_handler)