Merge branch 'master' into blender2.8
[blender-addons.git] / development_api_navigator.py
blob15622ec7e972d06fb9b9f134b71ca61a278d118b
1 # ##### BEGIN GPL LICENSE BLOCK #####
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.
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.
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.
17 # ##### END GPL LICENCE BLOCK #####
19 bl_info = {
20 "name": "API Navigator",
21 "author": "Dany Lebel (Axon_D)",
22 "version": (1, 0, 4),
23 "blender": (2, 57, 0),
24 "location": "Text Editor > Properties > API Navigator Panel",
25 "description": "Allows exploration of the python api via the user interface",
26 "warning": "",
27 "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/"
28 "Scripts/Text_Editor/API_Navigator",
29 "category": "Development",
32 """
33 You can browse through the tree structure of the api. Each child object appears in a list
34 that tries to be representative of its type. These lists are :
36 * Items (for an iterable object)
37 * Item Values (for an iterable object wich only supports index)
38 * Modules
39 * Types
40 * Properties
41 * Structs and Functions
42 * Methods and Functions
43 * Attributes
44 * Inaccessible (some objects may be listed but inaccessible)
46 The lists can be filtered to help searching in the tree. Just enter the text in the
47 filter section. It is also possible to explore other modules. Go the the root and select
48 it in the list of available modules. It will be imported dynamically.
50 In the text section, some informations are displayed. The type of the object,
51 what it returns, and its docstring. We could hope that these docstrings will be as
52 descriptive as possible. This text data block named api_doc_ can be toggled on and off
53 with the Escape key. (but a bug prevent the keymap to register correctly at start)
55 """
57 import bpy
58 from bpy.types import (
59 Operator,
60 Panel,
61 PropertyGroup,
63 from bpy.props import (
64 BoolVectorProperty,
65 StringProperty,
66 IntProperty,
67 PointerProperty,
69 from console.complete_import import get_root_modules
72 # ########## Global Variables ##########
74 last_text = None # last text data block
75 root_module = None # root module of the tree
76 root_m_path = '' # root_module + path as a string
77 current_module = None # the object itself in the tree structure
78 tree_level = None # the list of objects from the current_module
81 def init_tree_level():
82 global tree_level
83 tree_level = [[], [], [], [], [], [], [], [], []]
86 init_tree_level()
88 api_doc_ = '' # the documentation formated for the API Navigator
89 module_type = None # the type of current_module
90 return_report = '' # what current_module returns
91 filter_mem = {} # remember last filters entered for each path
92 too_long = False # is tree_level list too long to display in a panel?
95 # ########## Functions ############
96 def get_root_module(path):
97 global root_module
98 if '.' in path:
99 root = path[:path.find('.')]
100 else:
101 root = path
102 try:
103 root_module = __import__(root)
104 except:
105 root_module = None
108 def evaluate(module):
109 global root_module, tree_level, root_m_path
111 try:
112 len_name = root_module.__name__.__len__()
113 root_m_path = 'root_module' + module[len_name:]
114 current_module = eval(root_m_path)
115 return current_module
116 except:
117 init_tree_level
118 return None
121 def get_tree_level():
123 path = bpy.context.window_manager.api_nav_props.path
125 def object_list():
126 global current_module, root_m_path
128 itm, val, mod, typ, props, struct, met, att, bug = [], [], [], [], [], [], [], [], []
129 iterable = isiterable(current_module)
130 if iterable:
131 iter(current_module)
132 current_type = str(module_type)
133 if current_type != "<class 'str'>":
134 if iterable == 'a':
135 itm = list(current_module.keys())
136 if not itm:
137 val = list(current_module)
138 else:
139 val = list(current_module)
141 for i in dir(current_module):
142 try:
143 t = str(type(eval(root_m_path + '.' + i)))
144 except (AttributeError, SyntaxError):
145 bug += [i]
146 continue
148 if t == "<class 'module'>":
149 mod += [i]
150 elif t[0:16] == "<class 'bpy_prop":
151 props += [i]
152 elif t[8:11] == 'bpy':
153 struct += [i]
154 elif t == "<class 'builtin_function_or_method'>":
155 met += [i]
156 elif t == "<class 'type'>":
157 typ += [i]
158 else:
159 att += [i]
161 return [itm, val, mod, typ, props, struct, met, att, bug]
163 if not path:
164 return [[], [], [i for i in get_root_modules()], [], [], [], [], [], []]
165 return object_list()
168 def parent(path):
169 """Returns the parent path"""
170 parent = path
171 if parent[-1] == ']' and '[' in parent:
172 while parent[-1] != '[':
173 parent = parent[:-1]
174 elif '.' in parent:
175 while parent[-1] != '.':
176 parent = parent[:-1]
177 else:
178 return ''
179 parent = parent[:-1]
180 return parent
183 def update_filter():
184 """Update the filter according to the current path"""
185 global filter_mem
187 try:
188 bpy.context.window_manager.api_nav_props.filters = filter_mem[
189 bpy.context.window_manager.api_nav_props.path
191 except:
192 bpy.context.window_manager.api_nav_props.filters = ''
195 def isiterable(mod):
196 try:
197 iter(mod)
198 except:
199 return False
200 try:
201 mod['']
202 return 'a'
203 except KeyError:
204 return 'a'
205 except (AttributeError, TypeError):
206 return 'b'
209 def fill_filter_mem():
210 global filter_mem
212 filters = bpy.context.window_manager.api_nav_props.filters
213 if filters:
214 filter_mem[bpy.context.window_manager.api_nav_props.old_path] = \
215 bpy.context.window_manager.api_nav_props.filters
216 else:
217 filter_mem.pop(bpy.context.window_manager.api_nav_props.old_path, None)
220 # #### API Navigator parent class ######
221 class ApiNavigator():
222 """Parent class for API Navigator"""
224 @staticmethod
225 def generate_global_values():
226 """Populate the level attributes to display the panel buttons and the documentation"""
227 global tree_level, current_module, module_type, return_report, last_text
229 text = bpy.context.space_data.text
230 if text:
231 if text.name != 'api_doc_':
232 last_text = bpy.context.space_data.text.name
233 elif bpy.data.texts.__len__() < 2:
234 last_text = None
235 else:
236 last_text = None
237 bpy.context.window_manager.api_nav_props.pages = 0
238 get_root_module(bpy.context.window_manager.api_nav_props.path)
239 current_module = evaluate(bpy.context.window_manager.api_nav_props.path)
240 module_type = str(type(current_module))
241 return_report = str(current_module)
242 tree_level = get_tree_level()
244 if tree_level.__len__() > 30:
245 global too_long
246 too_long = True
247 else:
248 too_long = False
250 ApiNavigator.generate_api_doc()
251 return {'FINISHED'}
253 @staticmethod
254 def generate_api_doc():
255 """Format the doc string for API Navigator"""
256 global current_module, api_doc_, return_report, module_type
258 path = bpy.context.window_manager.api_nav_props.path
259 line = "-" * (path.__len__() + 2)
260 header = """\n\n\n\t\t%s\n\t %s\n\
261 _____________________________________________\n\
263 Type : %s\n\
266 Return : %s\n\
267 _____________________________________________\n\
269 Doc:
271 """ % (path, line, module_type, return_report)
272 footer = "\n\
273 _____________________________________________\n\
277 #############################################\n\
278 # api_doc_ #\n\
279 # Escape to toggle text #\n\
280 # (F8 to reload modules if doesn't work) #\n\
281 #############################################"
282 doc = current_module.__doc__
283 api_doc_ = header + str(doc) + footer
284 return {'FINISHED'}
286 @staticmethod
287 def doc_text_datablock():
288 """Create the text databloc or overwrite it if it already exist"""
289 global api_doc_
291 space_data = bpy.context.space_data
293 try:
294 doc_text = bpy.data.texts['api_doc_']
295 space_data.text = doc_text
296 doc_text.clear()
297 except:
298 bpy.data.texts.new(name='api_doc_')
299 doc_text = bpy.data.texts['api_doc_']
300 space_data.text = doc_text
302 doc_text.write(text=api_doc_)
303 return {'FINISHED'}
306 # ######### Operators ###########
307 def api_update(context):
308 if bpy.context.window_manager.api_nav_props.path != bpy.context.window_manager.api_nav_props.old_path:
309 fill_filter_mem()
310 bpy.context.window_manager.api_nav_props.old_path = bpy.context.window_manager.api_nav_props.path
311 update_filter()
312 ApiNavigator.generate_global_values()
313 ApiNavigator.doc_text_datablock()
316 class Update(ApiNavigator, Operator):
317 """Update the tree structure"""
318 bl_idname = "api_navigator.update"
319 bl_label = "API Navigator Update"
321 def execute(self, context):
322 api_update()
323 return {'FINISHED'}
326 class BackToBpy(ApiNavigator, Operator):
327 """Go back to module bpy"""
328 bl_idname = "api_navigator.back_to_bpy"
329 bl_label = "Back to bpy"
331 def execute(self, context):
332 fill_filter_mem()
333 if not bpy.context.window_manager.api_nav_props.path:
334 bpy.context.window_manager.api_nav_props.old_path = \
335 bpy.context.window_manager.api_nav_props.path = 'bpy'
336 else:
337 bpy.context.window_manager.api_nav_props.old_path = \
338 bpy.context.window_manager.api_nav_props.path = 'bpy'
339 update_filter()
340 self.generate_global_values()
341 self.doc_text_datablock()
342 return {'FINISHED'}
345 class Down(ApiNavigator, Operator):
346 """Go to this Module"""
347 bl_idname = "api_navigator.down"
348 bl_label = "API Navigator Down"
350 pointed_module = StringProperty(
351 name="Current Module",
352 default=""
355 def execute(self, context):
356 fill_filter_mem()
358 if not bpy.context.window_manager.api_nav_props.path:
359 bpy.context.window_manager.api_nav_props.old_path = \
360 bpy.context.window_manager.api_nav_props.path = \
361 bpy.context.window_manager.api_nav_props.path + self.pointed_module
362 else:
363 bpy.context.window_manager.api_nav_props.old_path = \
364 bpy.context.window_manager.api_nav_props.path = \
365 bpy.context.window_manager.api_nav_props.path + '.' + self.pointed_module
367 update_filter()
368 self.generate_global_values()
369 self.doc_text_datablock()
370 return {'FINISHED'}
373 class Parent(ApiNavigator, Operator):
374 """Go to Parent Module"""
375 bl_idname = "api_navigator.parent"
376 bl_label = "API Navigator Parent"
378 def execute(self, context):
379 path = bpy.context.window_manager.api_nav_props.path
381 if path:
382 fill_filter_mem()
383 bpy.context.window_manager.api_nav_props.old_path = \
384 bpy.context.window_manager.api_nav_props.path = \
385 parent(bpy.context.window_manager.api_nav_props.path)
386 update_filter()
387 self.generate_global_values()
388 self.doc_text_datablock()
390 return {'FINISHED'}
393 class ClearFilter(ApiNavigator, Operator):
394 """Clear the filter"""
395 bl_idname = "api_navigator.clear_filter"
396 bl_label = "API Nav clear filter"
398 def execute(self, context):
399 bpy.context.window_manager.api_nav_props.filters = ''
400 return {'FINISHED'}
403 class Subscript(ApiNavigator, Operator):
404 """Subscript to this Item"""
405 bl_idname = "api_navigator.subscript"
406 bl_label = "API Navigator Subscript"
408 subscription = StringProperty(
409 name="",
410 default=""
413 def execute(self, context):
414 fill_filter_mem()
415 bpy.context.window_manager.api_nav_props.old_path = \
416 bpy.context.window_manager.api_nav_props.path = \
417 bpy.context.window_manager.api_nav_props.path + '[' + self.subscription + ']'
418 update_filter()
419 self.generate_global_values()
420 self.doc_text_datablock()
421 return {'FINISHED'}
424 class Toggle_doc(ApiNavigator, Operator):
425 """Toggle on or off api_doc_ Text"""
426 bl_idname = "api_navigator.toggle_doc"
427 bl_label = "Toggle api_doc_"
429 def execute(self, context):
430 global last_text
432 try:
433 if bpy.context.space_data.text.name != "api_doc_":
434 last_text = bpy.context.space_data.text.name
435 except:
436 pass
438 try:
439 text = bpy.data.texts["api_doc_"]
440 bpy.data.texts["api_doc_"].clear()
441 bpy.data.texts.remove(text)
442 except KeyError:
443 self.doc_text_datablock()
444 return {'FINISHED'}
446 try:
447 text = bpy.data.texts[last_text]
448 bpy.context.space_data.text = text
449 return {'FINISHED'}
450 except:
451 pass
453 bpy.context.space_data.text = None
455 return {'FINISHED'}
458 # ######### UI Panels ############
459 class OBJECT_PT_api_navigator(ApiNavigator, Panel):
460 bl_idname = 'OBJECT_PT_api_navigator'
461 bl_space_type = "TEXT_EDITOR"
462 bl_region_type = "UI"
463 bl_label = "API Navigator"
464 bl_options = {'DEFAULT_CLOSED'}
466 columns = 3
468 def iterable_draw(self):
469 # Note: Currently unused method
470 global tree_level, current_module
472 iterable = isiterable(current_module)
474 if iterable:
475 iter(current_module)
476 current_type = str(module_type)
478 if current_type == "<class 'str'>":
479 return {'FINISHED'}
481 col = self.layout
482 reduce_to = bpy.context.window_manager.api_nav_props.reduce_to * self.columns
483 pages = bpy.context.window_manager.api_nav_props.pages
484 page_index = reduce_to * pages
485 count = 0
486 i = 0
487 filtered = 0
489 if iterable == 'a':
490 current_type.__iter__()
491 collection = list(current_module.keys())
492 end = collection.__len__()
493 box = self.layout.box()
494 row = box.row()
495 row.label(text="Items", icon="DOTSDOWN")
496 box = box.box()
497 col = box.column(align=True)
499 while count < reduce_to and i < end:
500 mod = collection[i]
501 if filtered < page_index:
502 filtered += 1
503 i += 1
504 continue
506 if not (i % self.columns):
507 row = col.row()
508 row.operator("api_navigator.subscript",
509 text=mod, emboss=True).subscription = '"' + mod + '"'
510 filtered += 1
511 i += 1
512 count += 1
514 elif iterable == 'b':
515 box = self.layout.box()
516 row = box.row()
517 row.label(text="Item Values", icon="OOPS")
518 box = box.box()
519 col = box.column(align=True)
520 collection = list(current_module)
521 end = collection.__len__()
523 while count < reduce_to and i < end:
524 mod = str(collection[i])
525 if filtered < page_index:
526 filtered += 1
527 i += 1
528 continue
530 if not (i % self.columns):
531 row = col.row()
532 row.operator("api_navigator.subscript",
533 text=mod, emboss=True).subscription = str(i)
534 filtered += 1
535 i += 1
536 count += 1
538 too_long = end > 30
540 if too_long:
541 row = col.row()
542 row.prop(bpy.context.window_manager.api_nav_props, "reduce_to")
543 row.label(text="", icon="DOTSDOWN")
544 row.prop(bpy.context.window_manager.api_nav_props, "pages", text="Pages")
546 return {'FINISHED'}
548 def list_draw(self, t, pages, icon, label=None, emboss=True):
549 global tree_level, current_module
551 def reduced(row, too_long):
552 if row and too_long:
553 sub_row = row.row(align=True)
554 sub_row.prop(bpy.context.window_manager.api_nav_props, "reduce_to")
555 sub_row.prop(bpy.context.window_manager.api_nav_props, "pages", text="Pages")
557 layout = self.layout
558 filters = bpy.context.window_manager.api_nav_props.filters
559 reduce_to = bpy.context.window_manager.api_nav_props.reduce_to * self.columns
560 page_index = reduce_to * pages
561 show_panel_elements = bpy.context.window_manager.api_nav_props.panel_toggle[t] if \
562 0 <= t < 10 else True
564 lenght = tree_level[t].__len__()
565 too_long = lenght > reduce_to
567 if lenght:
568 col = layout.column()
569 box = col.box()
571 title_box = box.row(align=True)
572 title_box.prop(bpy.context.window_manager.api_nav_props,
573 "panel_toggle", text="", index=t if 0 <= t < 10 else 0)
574 title_box.label(text=label, icon=icon)
575 reduced(box, too_long)
577 if show_panel_elements:
578 if t < 2:
579 box = box.box()
580 row = box.row()
581 col = row.column(align=True)
582 i = 0
583 objects, count, filtered = 0, 0, 0
585 while count < reduce_to and i < lenght:
586 obj = tree_level[t][i]
588 if filters and filters not in obj:
589 i += 1
590 continue
591 elif filtered < page_index:
592 filtered += 1
593 i += 1
594 continue
596 if not (objects % self.columns):
597 row = col.row(align=True)
598 if t > 1:
599 row.operator("api_navigator.down",
600 text=obj, emboss=emboss).pointed_module = obj
601 elif t == 0:
602 row.operator("api_navigator.subscript",
603 text=str(obj), emboss=emboss).subscription = '"' + obj + '"'
604 else:
605 row.operator("api_navigator.subscript",
606 text=str(obj), emboss=emboss).subscription = str(i)
607 filtered += 1
608 i += 1
609 objects += 1
610 count += 1
612 return {'FINISHED'}
614 def draw(self, context):
615 global tree_level, current_module, module_type, return_report
617 api_update(context)
619 layout = self.layout
620 layout.label(text="Tree Structure:")
622 col = layout.column(align=True)
623 col.prop(bpy.context.window_manager.api_nav_props, 'path', text='')
625 row = col.row(align=True)
626 row.operator("api_navigator.parent", text="Parent", icon="BACK")
627 row.operator("api_navigator.back_to_bpy", text='', emboss=True, icon="FILE_PARENT")
629 col = layout.column()
630 row = col.row(align=True)
631 row.prop(bpy.context.window_manager.api_nav_props, "filters", text="Filter")
632 row.operator("api_navigator.clear_filter", text="", icon="PANEL_CLOSE")
634 col = layout.column()
636 pages = bpy.context.window_manager.api_nav_props.pages
637 self.list_draw(0, pages, "DOTSDOWN", label="Items")
638 self.list_draw(1, pages, "DOTSDOWN", label="Item Values")
639 self.list_draw(2, pages, "PACKAGE", label="Modules", emboss=True)
640 self.list_draw(3, pages, "WORDWRAP_ON", label="Types", emboss=True)
641 self.list_draw(4, pages, "BUTS", label="Properties", emboss=True)
642 self.list_draw(5, pages, "OOPS", label="Structs and Functions")
643 self.list_draw(6, pages, "SCRIPTWIN", label="Methods and Functions")
644 self.list_draw(7, pages, "INFO", label="Attributes")
645 self.list_draw(8, pages, "ERROR", label="Inaccessible")
648 # ###### Properties #######
649 class ApiNavProps(PropertyGroup):
651 Fake module like class.
653 bpy.context.window_manager.api_nav_props
655 path = StringProperty(
656 name="Path",
657 description="Enter bpy.ops.api_navigator to see the documentation",
658 default="bpy"
660 old_path = StringProperty(
661 name="Old Path",
662 default=""
664 filters = StringProperty(
665 name="Filters",
666 description="Filter the resulting modules",
667 default=""
669 reduce_to = IntProperty(
670 name="Reduce to",
671 description="Display a maximum number of x entries by pages",
672 default=10,
673 min=1
675 pages = IntProperty(
676 name="Pages",
677 description="Display a Page",
678 default=0,
679 min=0
681 panel_toggle = BoolVectorProperty(
682 name="Tab",
683 description="Expand/Collapse UI elements",
684 default=(True,) * 9,
685 size=9,
689 # ######## Register #########
690 def register_keymaps():
691 kc = bpy.context.window_manager.keyconfigs.addon
692 if kc:
693 km = kc.keymaps.new(name="Text", space_type='TEXT_EDITOR')
694 km.keymap_items.new('api_navigator.toggle_doc', 'ESC', 'PRESS')
697 def unregister_keymaps():
698 kc = bpy.context.window_manager.keyconfigs.addon
699 if kc:
700 km = kc.keymaps["Text"]
701 kmi = km.keymap_items["api_navigator.toggle_doc"]
702 km.keymap_items.remove(kmi)
705 def register():
706 bpy.utils.register_module(__name__)
707 bpy.types.WindowManager.api_nav_props = PointerProperty(
708 type=ApiNavProps,
709 name="API Nav Props",
710 description=""
712 register_keymaps()
715 def unregister():
716 unregister_keymaps()
717 del bpy.types.WindowManager.api_nav_props
719 bpy.utils.unregister_module(__name__)
722 if __name__ == '__main__':
723 register()