Cleanup: delete .arcconfig
[blender-dev-tools.git] / utils_api / bpy_introspect_ui.py
blobd893bd51489cd83314e70b5d2528e22915babb1e
1 #!/usr/bin/env python3
2 # SPDX-License-Identifier: GPL-2.0-or-later
4 # This script dumps ui definitions as XML.
5 # useful for finding bad api usage.
7 """
8 Example usage:
10 python3 source/tools/utils_api/bpy_introspect_ui.py
11 """
13 import sys
14 ModuleType = type(sys)
17 def module_add(name):
18 mod = sys.modules[name] = ModuleType(name)
19 return mod
22 class AttributeBuilder:
23 """__slots__ = (
24 "_attr", "_attr_list", "_item_set", "_args",
25 "active", "operator_context", "enabled", "index", "data"
26 )"""
28 def _as_py(self):
29 data = [self._attr_single, self._args, [child._as_py() for child in self._attr_list]]
30 return data
32 def _as_xml(self, indent=" "):
34 def to_xml_str(value):
35 if type(value) == str:
36 # quick shoddy clean
37 value = value.replace("&", " ")
38 value = value.replace("<", " ")
39 value = value.replace(">", " ")
41 return '"' + value + '"'
42 else:
43 return '"' + str(value) + '"'
45 def dict_to_kw(args, dict_args):
46 args_str = ""
47 if args:
48 # args_str += " ".join([to_xml_str(a) for a in args])
49 for i, a in enumerate(args):
50 args_str += "arg" + str(i + 1) + "=" + to_xml_str(a) + " "
52 if dict_args:
53 args_str += " ".join(["%s=%s" % (key, to_xml_str(value)) for key, value in sorted(dict_args.items())])
55 if args_str:
56 return " " + args_str
58 return ""
60 lines = []
62 def py_to_xml(item, indent_ctx):
63 if item._attr_list:
64 lines.append("%s<%s%s>" % (indent_ctx, item._attr_single, dict_to_kw(item._args_tuple, item._args)))
65 for child in item._attr_list:
66 # print(child._attr)
67 py_to_xml(child, indent_ctx + indent)
68 lines.append("%s</%s>" % (indent_ctx, item._attr_single))
69 else:
70 lines.append("%s<%s%s/>" % (indent_ctx, item._attr_single, dict_to_kw(item._args_tuple, item._args)))
72 py_to_xml(self, indent)
74 return "\n".join(lines)
76 def __init__(self, attr, attr_single):
77 self._attr = attr
78 self._attr_single = attr_single
79 self._attr_list = []
80 self._item_set = []
81 self._args = {}
82 self._args_tuple = ()
84 def __call__(self, *args, **kwargs):
85 # print(self._attr, args, kwargs)
86 self._args_tuple = args
87 self._args = kwargs
88 return self
90 def __getattr__(self, attr):
91 attr_next = self._attr + "." + attr
92 # Useful for debugging.
93 # print(attr_next)
94 attr_obj = _attribute_builder_overrides.get(attr_next, ...)
95 if attr_obj is ...:
96 attr_obj = NewAttr(attr_next, attr)
97 self._attr_list.append(attr_obj)
98 return attr_obj
100 # def __setattr__(self, attr, value):
101 # setatte
103 def __getitem__(self, item):
104 item_obj = NewAttr(self._attr + "[" + repr(item) + "]", item)
105 self._item_set.append(item_obj)
106 return item_obj
108 def __setitem__(self, item, value):
109 pass # TODO?
111 def __repr__(self):
112 return self._attr
114 def __iter__(self):
115 return iter([])
117 # def __len__(self):
118 # return 0
120 def __int__(self):
121 return 0
123 def __cmp__(self, other):
124 return -1
126 def __lt__(self, other):
127 return -1
129 def __gt__(self, other):
130 return -1
132 def __le__(self, other):
133 return -1
135 def __add__(self, other):
136 return self
138 def __sub__(self, other):
139 return self
141 def __truediv__(self, other):
142 return self
144 def __floordiv__(self, other):
145 return self
147 def __round__(self, other):
148 return self
150 def __float__(self):
151 return 0.0
153 # Custom functions
154 def lower(self):
155 return ""
157 def upper(self):
158 return ""
160 def keys(self):
161 return []
164 class AttributeBuilder_Seq(AttributeBuilder):
165 def __len__(self):
166 return 0
169 _attribute_builder_overrides = {
170 "context.gpencil.layers": AttributeBuilder_Seq("context.gpencil.layers", "layers"),
171 "context.gpencil_data.layers": AttributeBuilder_Seq("context.gpencil_data.layers", "layers"),
172 "context.object.material_slots": (),
173 "context.selected_nodes": (),
174 "context.selected_sequences": (),
175 "context.space_data.bookmarks": (),
176 "context.space_data.text.filepath": "",
177 "context.preferences.filepaths.script_directory": "",
178 "context.tool_settings.snap_elements": (True, ) * 3,
179 "context.selected_objects": (),
180 "context.tool_settings.mesh_select_mode": (True, ) * 3,
181 "context.mode": 'PAINT_TEXTURE',
185 def NewAttr(attr, attr_single):
186 obj = AttributeBuilder(attr, attr_single)
187 return obj
190 def NewAttr_Seq(attr, attr_single):
191 obj = AttributeBuilder_Seq(attr, attr_single)
192 return obj
195 class BaseFakeUI:
197 def __init__(self):
198 self.layout = NewAttr("self.layout", "layout")
201 class Panel(BaseFakeUI):
203 @property
204 def is_popover(self):
205 return False
208 class UIList:
209 pass
212 class Header(BaseFakeUI):
213 pass
216 class Menu(BaseFakeUI):
218 def draw_preset(self, context):
219 pass
221 def path_menu(
222 self, searchpaths, operator, *,
223 props_default=None, prop_filepath="filepath",
224 filter_ext=None, filter_path=None, display_name=None,
225 add_operator=None,
227 pass
229 @classmethod
230 def draw_collapsible(cls, context, layout):
231 cls.draw(layout, context)
233 @classmethod
234 def is_extended(cls):
235 return False
238 class Operator(BaseFakeUI):
239 pass
242 class PropertyGroup:
243 pass
246 # setup fake module
247 def fake_main():
248 bpy = module_add("bpy")
250 # Registerable Subclasses
251 bpy.types = module_add("bpy.types")
252 bpy.types.Panel = Panel
253 bpy.types.Header = Header
254 bpy.types.Menu = Menu
255 bpy.types.UIList = UIList
256 bpy.types.PropertyGroup = PropertyGroup
257 bpy.types.Operator = Operator
259 # ID Subclasses
260 bpy.types.Armature = type("Armature", (), {})
261 bpy.types.Brush = type("Brush", (), {})
262 bpy.types.Brush.bl_rna = NewAttr("bpy.types.Brush.bl_rna", "bl_rna")
263 bpy.types.Camera = type("Camera", (), {})
264 bpy.types.Curve = type("Curve", (), {})
265 bpy.types.GreasePencil = type("GreasePencil", (), {})
266 bpy.types.Lattice = type("Lattice", (), {})
267 bpy.types.Light = type("Light", (), {})
268 bpy.types.Material = type("Material", (), {})
269 bpy.types.Mesh = type("Mesh", (), {})
270 bpy.types.MetaBall = type("MetaBall", (), {})
271 bpy.types.Object = type("Object", (), {})
272 bpy.types.Object.bl_rna = NewAttr("bpy.types.Object.bl_rna", "bl_rna")
273 bpy.types.ParticleSettings = type("ParticleSettings", (), {})
274 bpy.types.Scene = type("Scene", (), {})
275 bpy.types.Sequence = type("Sequence", (), {})
276 bpy.types.Speaker = type("Speaker", (), {})
277 bpy.types.SurfaceCurve = type("SurfaceCurve", (), {})
278 bpy.types.TextCurve = type("SurfaceCurve", (), {})
279 bpy.types.Texture = type("Texture", (), {})
280 bpy.types.WindowManager = type("WindowManager", (), {})
281 bpy.types.WorkSpace = type("WorkSpace", (), {})
282 bpy.types.World = type("World", (), {})
284 # Other types
285 bpy.types.Bone = type("Bone", (), {})
286 bpy.types.EditBone = type("EditBone", (), {})
287 bpy.types.Event = type("Event", (), {})
288 bpy.types.Event.bl_rna = NewAttr("bpy.types.Event.bl_rna", "bl_rna")
289 bpy.types.FreestyleLineStyle = type("FreestyleLineStyle", (), {})
290 bpy.types.PoseBone = type("PoseBone", (), {})
291 bpy.types.Theme = type("Theme", (), {})
292 bpy.types.Theme.bl_rna = NewAttr("bpy.types.Theme.bl_rna", "bl_rna")
293 bpy.types.ToolSettings = type("bpy.types.ToolSettings", (), {})
294 bpy.types.ToolSettings.bl_rna = NewAttr("bpy.types.ToolSettings.bl_rna", "bl_rna")
296 bpy.props = module_add("bpy.props")
297 bpy.props.StringProperty = dict
298 bpy.props.BoolProperty = dict
299 bpy.props.BoolVectorProperty = dict
300 bpy.props.IntProperty = dict
301 bpy.props.EnumProperty = dict
302 bpy.props.FloatProperty = dict
303 bpy.props.FloatVectorProperty = dict
304 bpy.props.CollectionProperty = dict
306 bpy.app = module_add("bpy.app")
307 bpy.app.use_userpref_skip_save_on_exit = False
309 bpy.app.build_options = module_add("bpy.app.build_options")
310 bpy.app.build_options.fluid = True
311 bpy.app.build_options.freestyle = True
312 bpy.app.build_options.mod_fluid = True
313 bpy.app.build_options.collada = True
314 bpy.app.build_options.international = True
315 bpy.app.build_options.mod_smoke = True
316 bpy.app.build_options.alembic = True
317 bpy.app.build_options.bullet = True
318 bpy.app.build_options.usd = True
320 bpy.app.translations = module_add("bpy.app.translations")
321 bpy.app.translations.pgettext_iface = lambda s, context="": s
322 bpy.app.translations.pgettext_data = lambda s: s
323 bpy.app.translations.pgettext_tip = lambda s: s
324 # id's are chosen at random here...
325 bpy.app.translations.contexts = module_add("bpy.app.translations.contexts")
326 bpy.app.translations.contexts.default = "CONTEXT_DEFAULT"
327 bpy.app.translations.contexts.operator_default = "CONTEXT_DEFAULT"
328 bpy.app.translations.contexts.id_particlesettings = "CONTEXT_DEFAULT"
329 bpy.app.translations.contexts.id_movieclip = "CONTEXT_ID_MOVIECLIP"
330 bpy.app.translations.contexts.id_windowmanager = "CONTEXT_ID_WM"
331 bpy.app.translations.contexts.plural = "CONTEXT_PLURAL"
333 bpy.utils = module_add("bpy.utils")
334 bpy.utils.register_class = lambda cls: ()
335 bpy.utils.app_template_paths = lambda: ()
338 def fake_helper():
340 class PropertyPanel:
341 pass
343 rna_prop_ui = module_add("rna_prop_ui")
344 rna_prop_ui.PropertyPanel = PropertyPanel
345 rna_prop_ui.draw = NewAttr("rna_prop_ui.draw", "draw")
347 rigify = module_add("rigify")
348 rigify.get_submodule_types = lambda: []
351 def fake_runtime():
352 """Only call this before `draw()` functions."""
354 # Misc Sub-classes
355 bpy.types.EffectSequence = type("EffectSequence", (), {})
357 # Operator Sub-classes.
358 bpy.types.WM_OT_doc_view = type("WM_OT_doc_view", (), {"_prefix": ""})
360 bpy.data = module_add("bpy.data")
361 bpy.data.scenes = ()
362 bpy.data.speakers = ()
363 bpy.data.collections = ()
364 bpy.data.meshes = ()
365 bpy.data.shape_keys = ()
366 bpy.data.materials = ()
367 bpy.data.lattices = ()
368 bpy.data.lights = ()
369 bpy.data.lightprobes = ()
370 bpy.data.fonts = ()
371 bpy.data.textures = ()
372 bpy.data.cameras = ()
373 bpy.data.curves = ()
374 bpy.data.linestyles = ()
375 bpy.data.masks = ()
376 bpy.data.metaballs = ()
377 bpy.data.movieclips = ()
378 bpy.data.armatures = ()
379 bpy.data.particles = ()
380 bpy.data.grease_pencils = ()
381 bpy.data.cache_files = ()
382 bpy.data.workspaces = ()
384 bpy.data.is_dirty = True
385 bpy.data.is_saved = True
386 bpy.data.use_autopack = True
388 # defined in fake_main()
389 bpy.utils.smpte_from_frame = lambda f: ""
390 bpy.utils.script_paths = lambda f: ()
391 bpy.utils.user_resource = lambda a, b: ()
393 bpy.app.debug = False
394 bpy.app.version = 2, 55, 1
395 bpy.app.autoexec_fail = False
397 bpy.path = module_add("bpy.path")
398 bpy.path.display_name = lambda f, has_ext=False: ""
400 bpy_extras = module_add("bpy_extras")
401 bpy_extras.keyconfig_utils = module_add("bpy_extras.keyconfig_utils")
402 bpy_extras.keyconfig_utils.KM_HIERARCHY = ()
403 bpy_extras.keyconfig_utils.keyconfig_merge = lambda a, b: ()
405 addon_utils = module_add("addon_utils")
406 # addon_utils.modules = lambda f: []
408 def _(refresh=False):
409 return ()
410 addon_utils.modules = _
411 del _
412 addon_utils.modules_refresh = lambda f: None
413 addon_utils.module_bl_info = lambda f: None
414 addon_utils.addons_fake_modules = {}
415 addon_utils.error_duplicates = ()
416 addon_utils.error_encoding = ()
419 fake_main()
420 fake_helper()
421 # fake_runtime() # call after initial import so we can catch
422 # # bad use of modules outside of draw() functions.
424 import bpy
427 def module_classes(mod):
428 classes = []
429 for value in mod.__dict__.values():
430 try:
431 is_subclass = issubclass(value, BaseFakeUI)
432 except:
433 is_subclass = False
435 if is_subclass:
436 classes.append(value)
438 return classes
441 def main():
443 import os
444 BASE_DIR = os.path.join(os.path.dirname(__file__), "..", "..", "..")
445 BASE_DIR = os.path.normpath(os.path.abspath(BASE_DIR))
446 MODULE_DIR_UI = os.path.join(BASE_DIR, "release", "scripts", "startup")
447 MODULE_DIR_MOD = os.path.join(BASE_DIR, "release", "scripts", "modules")
449 print("Using base dir: %r" % BASE_DIR)
450 print("Using module dir: %r" % MODULE_DIR_UI)
452 sys.path.insert(0, MODULE_DIR_UI)
453 sys.path.insert(0, MODULE_DIR_MOD)
455 scripts_dir = os.path.join(MODULE_DIR_UI, "bl_ui")
456 for f in sorted(os.listdir(scripts_dir)):
457 if f.endswith(".py") and not f.startswith("__init__"):
458 # print(f)
459 mod = __import__("bl_ui." + f[:-3]).__dict__[f[:-3]]
461 classes = module_classes(mod)
463 for cls in classes:
464 setattr(bpy.types, cls.__name__, cls)
466 fake_runtime()
468 # print("running...")
469 print("<ui>")
470 for f in sorted(os.listdir(scripts_dir)):
471 if f.endswith(".py") and not f.startswith("__init__"):
472 # print(f)
473 mod = __import__("bl_ui." + f[:-3]).__dict__[f[:-3]]
475 classes = module_classes(mod)
477 for cls in classes:
478 # want to check if the draw function is directly in the class
479 # print("draw")
480 if "draw" in cls.__dict__:
481 self = cls()
482 self.draw(NewAttr("context", "context"))
483 # print(self.layout._as_py())
484 self.layout._args['id'] = mod.__name__ + "." + cls.__name__
485 print(self.layout._as_xml())
486 print("</ui>")
489 if __name__ == "__main__":
490 main()