Cleanup: pep8
[blender.git] / tests / python / bl_run_operators.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 # semi-useful script, runs all operators in a number of different
22 # contexts, cheap way to find misc small bugs but is in no way a complete test.
23 #
24 # only error checked for here is a segfault.
25
26 import bpy
27 import sys
28
29 USE_ATTRSET = False
30 USE_FILES = ""  # "/mango/"
31 USE_RANDOM = False
32 USE_RANDOM_SCREEN = False
33 RANDOM_SEED = [1]  # so we can redo crashes
34 RANDOM_RESET = 0.1  # 10% chance of resetting on each new operator
35 RANDOM_MULTIPLY = 10
36
37 STATE = {
38     "counter": 0,
39     }
40
41
42 op_blacklist = (
43     "script.reload",
44     "export*.*",
45     "import*.*",
46     "*.save_*",
47     "*.read_*",
48     "*.open_*",
49     "*.link_append",
50     "render.render",
51     "render.play_rendered_anim",
52     "sound.bake_animation",    # OK but slow
53     "sound.mixdown",           # OK but slow
54     "object.bake_image",       # OK but slow
55     "object.paths_calculate",  # OK but slow
56     "object.paths_update",     # OK but slow
57     "ptcache.bake_all",        # OK but slow
58     "nla.bake",                # OK but slow
59     "*.*_export",
60     "*.*_import",
61     "ed.undo",
62     "ed.undo_push",
63     "script.autoexec_warn_clear",
64     "screen.delete",           # already used for random screens
65     "wm.blenderplayer_start",
66     "wm.recover_auto_save",
67     "wm.quit_blender",
68     "wm.url_open",
69     "wm.doc_view",
70     "wm.doc_edit",
71     "wm.doc_view_manual",
72     "wm.path_open",
73     "wm.copy_prev_settings",
74     "wm.theme_install",
75     "wm.context_*",
76     "wm.properties_add",
77     "wm.properties_remove",
78     "wm.properties_edit",
79     "wm.properties_context_change",
80     "wm.operator_cheat_sheet",
81     "wm.interface_theme_*",
82     "wm.previews_ensure",       # slow - but harmless
83     "wm.appconfig_*",           # just annoying - but harmless
84     "wm.keyitem_add",           # just annoying - but harmless
85     "wm.keyconfig_activate",    # just annoying - but harmless
86     "wm.keyconfig_preset_add",  # just annoying - but harmless
87     "wm.keyconfig_test",        # just annoying - but harmless
88     "wm.memory_statistics",     # another annoying one
89     "wm.dependency_relations",  # another annoying one
90     "wm.keymap_restore",        # another annoying one
91     "wm.addon_*",               # harmless, but dont change state
92     "console.*",                # just annoying - but harmless
93     )
94
95
96 def blend_list(mainpath):
97     import os
98     from os.path import join, splitext
99
100     def file_list(path, filename_check=None):
101         for dirpath, dirnames, filenames in os.walk(path):
102
103             # skip '.svn'
104             if dirpath.startswith("."):
105                 continue
106
107             for filename in filenames:
108                 filepath = join(dirpath, filename)
109                 if filename_check is None or filename_check(filepath):
110                     yield filepath
111
112     def is_blend(filename):
113         ext = splitext(filename)[1]
114         return (ext in {".blend", })
115
116     return list(sorted(file_list(mainpath, is_blend)))
117
118 if USE_FILES:
119     USE_FILES_LS = blend_list(USE_FILES)
120     # print(USE_FILES_LS)
121
122
123 def filter_op_list(operators):
124     from fnmatch import fnmatchcase
125
126     def is_op_ok(op):
127         for op_match in op_blacklist:
128             if fnmatchcase(op, op_match):
129                 print("    skipping: %s (%s)" % (op, op_match))
130                 return False
131         return True
132
133     operators[:] = [op for op in operators if is_op_ok(op[0])]
134
135
136 def reset_blend():
137     bpy.ops.wm.read_factory_settings()
138     for scene in bpy.data.scenes:
139         # reduce range so any bake action doesnt take too long
140         scene.frame_start = 1
141         scene.frame_end = 5
142
143     if USE_RANDOM_SCREEN:
144         import random
145         for i in range(random.randint(0, len(bpy.data.screens))):
146             bpy.ops.screen.delete()
147         print("Scree IS", bpy.context.screen)
148
149
150 def reset_file():
151     import random
152     f = USE_FILES_LS[random.randint(0, len(USE_FILES_LS) - 1)]
153     bpy.ops.wm.open_mainfile(filepath=f)
154
155
156 if USE_ATTRSET:
157     def build_property_typemap(skip_classes):
158
159         property_typemap = {}
160
161         for attr in dir(bpy.types):
162             cls = getattr(bpy.types, attr)
163             if issubclass(cls, skip_classes):
164                 continue
165
166             # # to support skip-save we cant get all props
167             # properties = cls.bl_rna.properties.keys()
168             properties = []
169             for prop_id, prop in cls.bl_rna.properties.items():
170                 if not prop.is_skip_save:
171                     properties.append(prop_id)
172
173             properties.remove("rna_type")
174             property_typemap[attr] = properties
175
176         return property_typemap
177     CLS_BLACKLIST = (
178         bpy.types.BrushTextureSlot,
179         bpy.types.Brush,
180         )
181     property_typemap = build_property_typemap(CLS_BLACKLIST)
182     bpy_struct_type = bpy.types.Struct.__base__
183
184     def id_walk(value, parent):
185         value_type = type(value)
186         value_type_name = value_type.__name__
187
188         value_id = getattr(value, "id_data", Ellipsis)
189         value_props = property_typemap.get(value_type_name, ())
190
191         for prop in value_props:
192             subvalue = getattr(value, prop)
193
194             if subvalue == parent:
195                 continue
196             # grr, recursive!
197             if prop == "point_caches":
198                 continue
199             subvalue_type = type(subvalue)
200             yield value, prop, subvalue_type
201             subvalue_id = getattr(subvalue, "id_data", Ellipsis)
202
203             if value_id == subvalue_id:
204                 if subvalue_type == float:
205                     pass
206                 elif subvalue_type == int:
207                     pass
208                 elif subvalue_type == bool:
209                     pass
210                 elif subvalue_type == str:
211                     pass
212                 elif hasattr(subvalue, "__len__"):
213                     for sub_item in subvalue[:]:
214                         if isinstance(sub_item, bpy_struct_type):
215                             subitem_id = getattr(sub_item, "id_data", Ellipsis)
216                             if subitem_id == subvalue_id:
217                                 yield from id_walk(sub_item, value)
218
219                 if subvalue_type.__name__ in property_typemap:
220                     yield from id_walk(subvalue, value)
221
222     # main function
223     _random_values = (
224         None, object, type,
225         1, 0.1, -1,  # float("nan"),
226         "", "test", b"", b"test",
227         (), [], {},
228         (10,), (10, 20), (0, 0, 0),
229         {0: "", 1: "hello", 2: "test"}, {"": 0, "hello": 1, "test": 2},
230         set(), {"", "test", "."}, {None, ..., type},
231         range(10), (" " * i for i in range(10)),
232         )
233
234     def attrset_data():
235         for attr in dir(bpy.data):
236             if attr == "window_managers":
237                 continue
238             seq = getattr(bpy.data, attr)
239             if seq.__class__.__name__ == 'bpy_prop_collection':
240                 for id_data in seq:
241                     for val, prop, tp in id_walk(id_data, bpy.data):
242                         # print(id_data)
243                         for val_rnd in _random_values:
244                             try:
245                                 setattr(val, prop, val_rnd)
246                             except:
247                                 pass
248
249
250 def run_ops(operators, setup_func=None, reset=True):
251     print("\ncontext:", setup_func.__name__)
252
253     # first invoke
254     for op_id, op in operators:
255         if op.poll():
256             print("    operator: %4d, %s" % (STATE["counter"], op_id))
257             STATE["counter"] += 1
258             sys.stdout.flush()  # in case of crash
259
260             # disable will get blender in a bad state and crash easy!
261             if reset:
262                 reset_test = True
263                 if USE_RANDOM:
264                     import random
265                     if random.random() < (1.0 - RANDOM_RESET):
266                         reset_test = False
267
268                 if reset_test:
269                     if USE_FILES:
270                         reset_file()
271                     else:
272                         reset_blend()
273                 del reset_test
274
275             if USE_RANDOM:
276                 # we can't be sure it will work
277                 try:
278                     setup_func()
279                 except:
280                     pass
281             else:
282                 setup_func()
283
284             for mode in {'EXEC_DEFAULT', 'INVOKE_DEFAULT'}:
285                 try:
286                     op(mode)
287                 except:
288                     #import traceback
289                     #traceback.print_exc()
290                     pass
291
292                 if USE_ATTRSET:
293                     attrset_data()
294
295     if not operators:
296         # run test
297         if reset:
298             reset_blend()
299         if USE_RANDOM:
300             # we can't be sure it will work
301             try:
302                 setup_func()
303             except:
304                 pass
305         else:
306             setup_func()
307
308
309 # contexts
310 def ctx_clear_scene():  # copied from batch_import.py
311     unique_obs = set()
312     for scene in bpy.data.scenes:
313         for obj in scene.objects[:]:
314             scene.objects.unlink(obj)
315             unique_obs.add(obj)
316
317     # remove obdata, for now only worry about the startup scene
318     for bpy_data_iter in (bpy.data.objects, bpy.data.meshes, bpy.data.lamps, bpy.data.cameras):
319         for id_data in bpy_data_iter:
320             bpy_data_iter.remove(id_data)
321
322
323 def ctx_editmode_mesh():
324     bpy.ops.object.mode_set(mode='EDIT')
325
326
327 def ctx_editmode_mesh_extra():
328     bpy.ops.object.vertex_group_add()
329     bpy.ops.object.shape_key_add(from_mix=False)
330     bpy.ops.object.shape_key_add(from_mix=True)
331     bpy.ops.mesh.uv_texture_add()
332     bpy.ops.mesh.vertex_color_add()
333     bpy.ops.object.material_slot_add()
334     # editmode last!
335     bpy.ops.object.mode_set(mode='EDIT')
336
337
338 def ctx_editmode_mesh_empty():
339     bpy.ops.object.mode_set(mode='EDIT')
340     bpy.ops.mesh.select_all(action='SELECT')
341     bpy.ops.mesh.delete()
342
343
344 def ctx_editmode_curves():
345     bpy.ops.curve.primitive_nurbs_circle_add()
346     bpy.ops.object.mode_set(mode='EDIT')
347
348
349 def ctx_editmode_curves_empty():
350     bpy.ops.curve.primitive_nurbs_circle_add()
351     bpy.ops.object.mode_set(mode='EDIT')
352     bpy.ops.curve.select_all(action='SELECT')
353     bpy.ops.curve.delete(type='VERT')
354
355
356 def ctx_editmode_surface():
357     bpy.ops.surface.primitive_nurbs_surface_torus_add()
358     bpy.ops.object.mode_set(mode='EDIT')
359
360
361 def ctx_editmode_mball():
362     bpy.ops.object.metaball_add()
363     bpy.ops.object.mode_set(mode='EDIT')
364
365
366 def ctx_editmode_text():
367     bpy.ops.object.text_add()
368     bpy.ops.object.mode_set(mode='EDIT')
369
370
371 def ctx_editmode_armature():
372     bpy.ops.object.armature_add()
373     bpy.ops.object.mode_set(mode='EDIT')
374
375
376 def ctx_editmode_armature_empty():
377     bpy.ops.object.armature_add()
378     bpy.ops.object.mode_set(mode='EDIT')
379     bpy.ops.armature.select_all(action='SELECT')
380     bpy.ops.armature.delete()
381
382
383 def ctx_editmode_lattice():
384     bpy.ops.object.add(type='LATTICE')
385     bpy.ops.object.mode_set(mode='EDIT')
386     # bpy.ops.object.vertex_group_add()
387
388
389 def ctx_object_empty():
390     bpy.ops.object.add(type='EMPTY')
391
392
393 def ctx_object_pose():
394     bpy.ops.object.armature_add()
395     bpy.ops.object.mode_set(mode='POSE')
396     bpy.ops.pose.select_all(action='SELECT')
397
398
399 def ctx_object_paint_weight():
400     bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
401
402
403 def ctx_object_paint_vertex():
404     bpy.ops.object.mode_set(mode='VERTEX_PAINT')
405
406
407 def ctx_object_paint_sculpt():
408     bpy.ops.object.mode_set(mode='SCULPT')
409
410
411 def ctx_object_paint_texture():
412     bpy.ops.object.mode_set(mode='TEXTURE_PAINT')
413
414
415 def bpy_check_type_duplicates():
416     # non essential sanity check
417     bl_types = dir(bpy.types)
418     bl_types_unique = set(bl_types)
419
420     if len(bl_types) != len(bl_types_unique):
421         print("Error, found duplicates in 'bpy.types'")
422         for t in sorted(bl_types_unique):
423             tot = bl_types.count(t)
424             if tot > 1:
425                 print("    '%s', %d" % (t, tot))
426         import sys
427         sys.exit(1)
428
429
430 def main():
431
432     bpy_check_type_duplicates()
433
434     # reset_blend()
435     import bpy
436     operators = []
437     for mod_name in dir(bpy.ops):
438         mod = getattr(bpy.ops, mod_name)
439         for submod_name in dir(mod):
440             op = getattr(mod, submod_name)
441             operators.append(("%s.%s" % (mod_name, submod_name), op))
442
443     operators.sort(key=lambda op: op[0])
444
445     filter_op_list(operators)
446
447     # for testing, mix the list up.
448     #operators.reverse()
449
450     if USE_RANDOM:
451         import random
452         random.seed(RANDOM_SEED[0])
453         operators = operators * RANDOM_MULTIPLY
454         random.shuffle(operators)
455
456     # 2 passes, first just run setup_func to make sure they are ok
457     for operators_test in ((), operators):
458         # Run the operator tests in different contexts
459         run_ops(operators_test, setup_func=lambda: None)
460
461         if USE_FILES:
462             continue
463
464         run_ops(operators_test, setup_func=ctx_clear_scene)
465         # object modes
466         run_ops(operators_test, setup_func=ctx_object_empty)
467         run_ops(operators_test, setup_func=ctx_object_pose)
468         run_ops(operators_test, setup_func=ctx_object_paint_weight)
469         run_ops(operators_test, setup_func=ctx_object_paint_vertex)
470         run_ops(operators_test, setup_func=ctx_object_paint_sculpt)
471         run_ops(operators_test, setup_func=ctx_object_paint_texture)
472         # mesh
473         run_ops(operators_test, setup_func=ctx_editmode_mesh)
474         run_ops(operators_test, setup_func=ctx_editmode_mesh_extra)
475         run_ops(operators_test, setup_func=ctx_editmode_mesh_empty)
476         # armature
477         run_ops(operators_test, setup_func=ctx_editmode_armature)
478         run_ops(operators_test, setup_func=ctx_editmode_armature_empty)
479         # curves
480         run_ops(operators_test, setup_func=ctx_editmode_curves)
481         run_ops(operators_test, setup_func=ctx_editmode_curves_empty)
482         run_ops(operators_test, setup_func=ctx_editmode_surface)
483         # other
484         run_ops(operators_test, setup_func=ctx_editmode_mball)
485         run_ops(operators_test, setup_func=ctx_editmode_text)
486         run_ops(operators_test, setup_func=ctx_editmode_lattice)
487
488         if not operators_test:
489             print("All setup functions run fine!")
490
491     print("Finished %r" % __file__)
492
493 if __name__ == "__main__":
494     #~ for i in range(200):
495         #~ RANDOM_SEED[0] += 1
496         #~ main()
497     main()