bl_run_operators.py 14 KB

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