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