2 # SPDX-License-Identifier: GPL-2.0-or-later
4 # This script dumps ui definitions as XML.
5 # useful for finding bad api usage.
10 python3 source/tools/utils_api/bpy_introspect_ui.py
14 ModuleType
= type(sys
)
18 mod
= sys
.modules
[name
] = ModuleType(name
)
22 class AttributeBuilder
:
24 "_attr", "_attr_list", "_item_set", "_args",
25 "active", "operator_context", "enabled", "index", "data"
29 data
= [self
._attr
_single
, self
._args
, [child
._as
_py
() for child
in self
._attr
_list
]]
32 def _as_xml(self
, indent
=" "):
34 def to_xml_str(value
):
35 if type(value
) == str:
37 value
= value
.replace("&", " ")
38 value
= value
.replace("<", " ")
39 value
= value
.replace(">", " ")
41 return '"' + value
+ '"'
43 return '"' + str(value
) + '"'
45 def dict_to_kw(args
, dict_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
) + " "
53 args_str
+= " ".join(["%s=%s" % (key
, to_xml_str(value
)) for key
, value
in sorted(dict_args
.items())])
62 def py_to_xml(item
, indent_ctx
):
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
:
67 py_to_xml(child
, indent_ctx
+ indent
)
68 lines
.append("%s</%s>" % (indent_ctx
, item
._attr
_single
))
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
):
78 self
._attr
_single
= attr_single
84 def __call__(self
, *args
, **kwargs
):
85 # print(self._attr, args, kwargs)
86 self
._args
_tuple
= args
90 def __getattr__(self
, attr
):
91 attr_next
= self
._attr
+ "." + attr
92 # Useful for debugging.
94 attr_obj
= _attribute_builder_overrides
.get(attr_next
, ...)
96 attr_obj
= NewAttr(attr_next
, attr
)
97 self
._attr
_list
.append(attr_obj
)
100 # def __setattr__(self, attr, value):
103 def __getitem__(self
, item
):
104 item_obj
= NewAttr(self
._attr
+ "[" + repr(item
) + "]", item
)
105 self
._item
_set
.append(item_obj
)
108 def __setitem__(self
, item
, value
):
123 def __cmp__(self
, other
):
126 def __lt__(self
, other
):
129 def __gt__(self
, other
):
132 def __le__(self
, other
):
135 def __add__(self
, other
):
138 def __sub__(self
, other
):
141 def __truediv__(self
, other
):
144 def __floordiv__(self
, other
):
147 def __round__(self
, other
):
164 class AttributeBuilder_Seq(AttributeBuilder
):
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
)
190 def NewAttr_Seq(attr
, attr_single
):
191 obj
= AttributeBuilder_Seq(attr
, attr_single
)
198 self
.layout
= NewAttr("self.layout", "layout")
201 class Panel(BaseFakeUI
):
204 def is_popover(self
):
212 class Header(BaseFakeUI
):
216 class Menu(BaseFakeUI
):
218 def draw_preset(self
, context
):
222 self
, searchpaths
, operator
, *,
223 props_default
=None, prop_filepath
="filepath",
224 filter_ext
=None, filter_path
=None, display_name
=None,
230 def draw_collapsible(cls
, context
, layout
):
231 cls
.draw(layout
, context
)
234 def is_extended(cls
):
238 class Operator(BaseFakeUI
):
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
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", (), {})
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: ()
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: []
352 """Only call this before `draw()` functions."""
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")
362 bpy
.data
.speakers
= ()
363 bpy
.data
.collections
= ()
365 bpy
.data
.shape_keys
= ()
366 bpy
.data
.materials
= ()
367 bpy
.data
.lattices
= ()
369 bpy
.data
.lightprobes
= ()
371 bpy
.data
.textures
= ()
372 bpy
.data
.cameras
= ()
374 bpy
.data
.linestyles
= ()
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):
410 addon_utils
.modules
= _
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
= ()
421 # fake_runtime() # call after initial import so we can catch
422 # # bad use of modules outside of draw() functions.
427 def module_classes(mod
):
429 for value
in mod
.__dict
__.values():
431 is_subclass
= issubclass(value
, BaseFakeUI
)
436 classes
.append(value
)
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__"):
459 mod
= __import__("bl_ui." + f
[:-3]).__dict
__[f
[:-3]]
461 classes
= module_classes(mod
)
464 setattr(bpy
.types
, cls
.__name
__, cls
)
468 # print("running...")
470 for f
in sorted(os
.listdir(scripts_dir
)):
471 if f
.endswith(".py") and not f
.startswith("__init__"):
473 mod
= __import__("bl_ui." + f
[:-3]).__dict
__[f
[:-3]]
475 classes
= module_classes(mod
)
478 # want to check if the draw function is directly in the class
480 if "draw" in cls
.__dict
__:
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
())
489 if __name__
== "__main__":