fix for various corner cases from testing more sample files.
[blender-addons.git] / development_api_navigator.py
blob2f1217242b81fa6fae48b1745115b9bb77c2435f
1 # development_api_navigator.py
3 # ***** BEGIN GPL LICENSE BLOCK *****
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software Foundation,
18 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 # ***** END GPL LICENCE BLOCK *****
22 bl_info = {
23 "name": "API Navigator",
24 "author": "Dany Lebel (Axon_D)",
25 "version": (1, 0, 2),
26 "blender": (2, 57, 0),
27 "location": "Text Editor > Properties > API Navigator Panel",
28 "description": "Allows exploration of the python api via the user interface",
29 "warning": "",
30 "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
31 "Scripts/Text_Editor/API_Navigator",
32 "tracker_url": "http://projects.blender.org/tracker/index.php?"\
33 "func=detail&aid=24982",
34 "category": "Development"}
36 """
37 You can browse through the tree structure of the api. Each child object appears in a list
38 that tries to be representative of its type. These lists are :
40 * Items (for an iterable object)
41 * Item Values (for an iterable object wich only supports index)
42 * Modules
43 * Types
44 * Properties
45 * Structs and Functions
46 * Methods and Functions
47 * Attributes
48 * Inaccessible (some objects may be listed but inaccessible)
50 The lists can be filtered to help searching in the tree. Just enter the text in the
51 filter section. It is also possible to explore other modules. Go the the root and select
52 it in the list of available modules. It will be imported dynamically.
54 In the text section, some informations are displayed. The type of the object,
55 what it returns, and its docstring. We could hope that these docstrings will be as
56 descriptive as possible. This text data block named api_doc_ can be toggled on and off
57 with the Escape key. (but a bug prevent the keymap to register correctly at start)
59 """
61 import bpy
62 from console.complete_import import get_root_modules
65 ############ Global Variables ############
67 last_text = None # last text data block
69 root_module = None # root module of the tree
71 root_m_path = '' # root_module + path as a string
73 current_module = None # the object itself in the tree structure
76 tree_level = None # the list of objects from the current_module
78 def init_tree_level():
79 global tree_level
80 tree_level = [[],[],[],[],[],[],[], [], []]
82 init_tree_level()
85 api_doc_ = '' # the documentation formated for the API Navigator
87 module_type = None # the type of current_module
89 return_report = '' # what current_module returns
91 filter_mem = {} # remember last filters entered for each path
93 too_long = False # is tree_level list too long to display in a panel?
96 ############ Functions ############
98 def get_root_module(path):
99 #print('get_root_module')
100 global root_module
101 if '.' in path:
102 root = path[:path.find('.')]
103 else :
104 root = path
105 try :
106 root_module = __import__(root)
107 except :
108 root_module = None
111 def evaluate(module):
112 #print('evaluate')
113 global root_module, tree_level, root_m_path
115 # path = bpy.context.window_manager.api_nav_props.path
116 try :
117 len_name = root_module.__name__.__len__()
118 root_m_path = 'root_module' + module[len_name:]
119 current_module = eval(root_m_path)
120 return current_module
121 except :
122 init_tree_level
123 return None
126 def get_tree_level():
127 #print('get_tree_level')
129 path = bpy.context.window_manager.api_nav_props.path
131 def object_list():
132 #print('object_list')
133 global current_module, root_m_path
135 itm, val, mod, typ, props, struct, met, att, bug = [], [], [], [], [], [], [], [], []
136 iterable = isiterable(current_module)
137 if iterable:
138 iter(current_module)
139 current_type = str(module_type)
140 if current_type != "<class 'str'>":
141 if iterable == 'a':
142 #if iterable == 'a':
143 #current_type.__iter__()
144 itm = list(current_module.keys())
145 if not itm:
146 val = list(current_module)
147 else :
148 val = list(current_module)
150 for i in dir(current_module):
151 try :
152 t = str(type(eval(root_m_path + '.' + i)))
153 except (AttributeError, SyntaxError):
154 bug += [i]
155 continue
158 if t == "<class 'module'>":
159 mod += [i]
160 elif t[0:16] == "<class 'bpy_prop":
161 props += [i]
162 elif t[8:11] == 'bpy':
163 struct += [i]
164 elif t == "<class 'builtin_function_or_method'>":
165 met += [i]
166 elif t == "<class 'type'>":
167 typ += [i]
168 else :
169 att += [i]
171 return [itm, val, mod, typ, props, struct, met, att, bug]
173 if not path:
174 return [[], [], [i for i in get_root_modules()], [], [], [], [], [], []]
175 return object_list()
178 def parent(path):
179 """Returns the parent path"""
180 #print('parent')
182 parent = path
183 if parent[-1] == ']' and '[' in parent:
184 while parent[-1] != '[':
185 parent = parent[:-1]
186 elif '.' in parent:
187 while parent[-1] != '.':
188 parent = parent[:-1]
189 else :
190 return ''
191 parent = parent[:-1]
192 return parent
195 def update_filter():
196 """Update the filter according to the current path"""
197 global filter_mem
199 try :
200 bpy.context.window_manager.api_nav_props.filter = filter_mem[bpy.context.window_manager.api_nav_props.path]
201 except :
202 bpy.context.window_manager.api_nav_props.filter = ''
205 def isiterable(mod):
207 try :
208 iter(mod)
209 except :
210 return False
211 try :
212 mod['']
213 return 'a'
214 except KeyError:
215 return 'a'
216 except (AttributeError, TypeError):
217 return 'b'
220 def fill_filter_mem():
221 global filter_mem
223 filter = bpy.context.window_manager.api_nav_props.filter
224 if filter:
225 filter_mem[bpy.context.window_manager.api_nav_props.old_path] = bpy.context.window_manager.api_nav_props.filter
226 else :
227 filter_mem.pop(bpy.context.window_manager.api_nav_props.old_path, None)
230 ###### API Navigator parent class #######
232 class ApiNavigator():
233 """Parent class for API Navigator"""
235 @staticmethod
236 def generate_global_values():
237 """Populate the level attributes to display the panel buttons and the documentation"""
238 global tree_level, current_module, module_type, return_report, last_text
240 text = bpy.context.space_data.text
241 if text:
242 if text.name != 'api_doc_':
243 last_text = bpy.context.space_data.text.name
244 elif bpy.data.texts.__len__() < 2:
245 last_text = None
246 else :
247 last_text = None
248 bpy.context.window_manager.api_nav_props.pages = 0
249 get_root_module(bpy.context.window_manager.api_nav_props.path)
250 current_module = evaluate(bpy.context.window_manager.api_nav_props.path)
251 module_type = str(type(current_module))
252 return_report = str(current_module)
253 tree_level = get_tree_level()
255 if tree_level.__len__() > 30:
256 global too_long
257 too_long = True
258 else :
259 too_long = False
261 ApiNavigator.generate_api_doc()
262 return {'FINISHED'}
264 @staticmethod
265 def generate_api_doc():
266 """Format the doc string for API Navigator"""
267 global current_module, api_doc_, return_report, module_type
269 path = bpy.context.window_manager.api_nav_props.path
270 line = "-" * (path.__len__()+2)
271 header = """\n\n\n\t\t%s\n\t %s\n\
272 _____________________________________________\n\
274 Type : %s\n\
277 Return : %s\n\
278 _____________________________________________\n\
280 Doc:
282 """ % (path, line, module_type, return_report)
283 footer = "\n\
284 _____________________________________________\n\
288 #############################################\n\
289 # api_doc_ #\n\
290 # Escape to toggle text #\n\
291 # (F8 to reload modules if doesn't work) #\n\
292 #############################################"
293 doc = current_module.__doc__
294 api_doc_ = header + str(doc) + footer
295 return {'FINISHED'}
297 @staticmethod
298 def doc_text_datablock():
299 """Create the text databloc or overwrite it if it already exist"""
300 global api_doc_
302 space_data = bpy.context.space_data
304 try :
305 doc_text = bpy.data.texts['api_doc_']
306 space_data.text = doc_text
307 doc_text.clear()
308 except :
309 bpy.data.texts.new(name='api_doc_')
310 doc_text = bpy.data.texts['api_doc_']
311 space_data.text = doc_text
313 doc_text.write(text=api_doc_)
314 return {'FINISHED'}
318 ############ Operators ############
319 def api_update(context):
320 if bpy.context.window_manager.api_nav_props.path != bpy.context.window_manager.api_nav_props.old_path:
321 fill_filter_mem()
322 bpy.context.window_manager.api_nav_props.old_path = bpy.context.window_manager.api_nav_props.path
323 update_filter()
324 ApiNavigator.generate_global_values()
325 ApiNavigator.doc_text_datablock()
328 class Update(ApiNavigator, bpy.types.Operator):
329 """Update the tree structure"""
330 bl_idname = "api_navigator.update"
331 bl_label = "API Navigator Update"
333 def execute(self, context):
334 api_update()
335 return {'FINISHED'}
338 class BackToBpy(ApiNavigator, bpy.types.Operator):
339 """go back to module bpy"""
340 bl_idname = "api_navigator.back_to_bpy"
341 bl_label = "Back to bpy"
343 def execute(self, context):
344 fill_filter_mem()
345 if not bpy.context.window_manager.api_nav_props.path:
346 bpy.context.window_manager.api_nav_props.old_path = bpy.context.window_manager.api_nav_props.path = 'bpy'
347 else :
348 bpy.context.window_manager.api_nav_props.old_path = bpy.context.window_manager.api_nav_props.path = 'bpy'
349 update_filter()
350 self.generate_global_values()
351 self.doc_text_datablock()
352 return {'FINISHED'}
355 class Down(ApiNavigator, bpy.types.Operator):
356 """go to this Module"""
357 bl_idname = "api_navigator.down"
358 bl_label = "API Navigator Down"
359 pointed_module = bpy.props.StringProperty(name='Current Module', default='')
362 def execute(self, context):
363 fill_filter_mem()
365 if not bpy.context.window_manager.api_nav_props.path:
366 bpy.context.window_manager.api_nav_props.old_path = bpy.context.window_manager.api_nav_props.path = bpy.context.window_manager.api_nav_props.path + self.pointed_module
367 else :
368 bpy.context.window_manager.api_nav_props.old_path = bpy.context.window_manager.api_nav_props.path = bpy.context.window_manager.api_nav_props.path + '.' + self.pointed_module
370 update_filter()
371 self.generate_global_values()
372 self.doc_text_datablock()
373 return {'FINISHED'}
376 class Parent(ApiNavigator, bpy.types.Operator):
377 """go to Parent Module"""
378 bl_idname = "api_navigator.parent"
379 bl_label = "API Navigator Parent"
382 def execute(self, context):
383 path = bpy.context.window_manager.api_nav_props.path
385 if path:
386 fill_filter_mem()
387 bpy.context.window_manager.api_nav_props.old_path = bpy.context.window_manager.api_nav_props.path = parent(bpy.context.window_manager.api_nav_props.path)
388 update_filter()
389 self.generate_global_values()
390 self.doc_text_datablock()
392 return {'FINISHED'}
395 class ClearFilter(ApiNavigator, bpy.types.Operator):
396 """Clear the filter"""
397 bl_idname = 'api_navigator.clear_filter'
398 bl_label = 'API Nav clear filter'
400 def execute(self, context):
401 bpy.context.window_manager.api_nav_props.filter = ''
402 return {'FINISHED'}
405 class FakeButton(ApiNavigator, bpy.types.Operator):
406 """The list is not displayed completely""" # only serve as an indicator
407 bl_idname = 'api_navigator.fake_button'
408 bl_label = ''
411 class Subscript(ApiNavigator, bpy.types.Operator):
412 """Subscript to this Item"""
413 bl_idname = "api_navigator.subscript"
414 bl_label = "API Navigator Subscript"
415 subscription = bpy.props.StringProperty(name='', default='')
417 def execute(self, context):
418 fill_filter_mem()
419 bpy.context.window_manager.api_nav_props.old_path = bpy.context.window_manager.api_nav_props.path = bpy.context.window_manager.api_nav_props.path + '[' + self.subscription + ']'
420 update_filter()
421 self.generate_global_values()
422 self.doc_text_datablock()
423 return {'FINISHED'}
426 class Toggle_doc(ApiNavigator, bpy.types.Operator):
427 """Toggle on or off api_doc_ Text"""
428 bl_idname = 'api_navigator.toggle_doc'
429 bl_label = 'Toggle api_doc_'
432 def execute(self, context):
433 global last_text
435 try :
436 if bpy.context.space_data.text.name != "api_doc_":
437 last_text = bpy.context.space_data.text.name
438 except : pass
440 try :
441 text = bpy.data.texts["api_doc_"]
442 bpy.data.texts["api_doc_"].clear()
443 bpy.data.texts.remove(text)
444 except KeyError:
445 self.doc_text_datablock()
446 return {'FINISHED'}
448 try :
449 text = bpy.data.texts[last_text]
450 bpy.context.space_data.text = text
451 #line = bpy.ops.text.line_number() # operator doesn't seems to work ???
452 #bpy.ops.text.jump(line=line)
453 return {'FINISHED'}
454 except : pass
456 bpy.context.space_data.text = None
457 return {'FINISHED'}
459 ############ UI Panels ############
461 class OBJECT_PT_api_navigator(ApiNavigator, bpy.types.Panel):
462 bl_idname = 'api_navigator'
463 bl_space_type = "TEXT_EDITOR"
464 bl_region_type = "UI"
465 bl_label = "API Navigator"
466 bl_options = {'DEFAULT_CLOSED'}
469 columns = 3
472 def iterable_draw(self):
473 global tree_level, current_module
475 iterable = isiterable(current_module)
477 if iterable:
478 iter(current_module)
479 current_type = str(module_type)
481 if current_type == "<class 'str'>":
482 return {'FINISHED'}
484 col = self.layout
485 # filter = bpy.context.window_manager.api_nav_props.filter # UNUSED
486 reduce_to = bpy.context.window_manager.api_nav_props.reduce_to * self.columns
487 pages = bpy.context.window_manager.api_nav_props.pages
488 page_index = reduce_to*pages
489 # rank = 0 # UNUSED
490 count = 0
491 i = 0
492 filtered = 0
494 if iterable == 'a':
495 current_type.__iter__()
496 collection = list(current_module.keys())
497 end = collection.__len__()
498 box = self.layout.box()
499 row = box.row()
500 row.label(text="Items", icon="DOTSDOWN")
501 box = box.box()
502 col = box.column(align=True)
504 while count < reduce_to and i < end:
505 mod = collection[i]
506 if filtered < page_index:
507 filtered += 1
508 i += 1
509 continue
511 if not (i % self.columns):
512 row = col.row()
513 row.operator('api_navigator.subscript', text=mod, emboss=False).subscription = '"' + mod + '"'
514 filtered += 1
515 i += 1
516 count += 1
518 elif iterable == 'b':
519 box = self.layout.box()
520 row = box.row()
521 row.label(text="Item Values", icon="OOPS")
522 box = box.box()
523 col = box.column(align=True)
524 collection = list(current_module)
525 end = collection.__len__()
527 while count < reduce_to and i < end:
528 mod = str(collection[i])
529 if filtered < page_index:
530 filtered += 1
531 i += 1
532 continue
534 if not (i % self.columns):
535 row = col.row()
536 row.operator('api_navigator.subscript', text=mod, emboss=False).subscription = str(i)
537 filtered += 1
538 i += 1
539 count += 1
541 too_long = end > 30
543 if too_long:
544 row = col.row()
545 row.prop(bpy.context.window_manager.api_nav_props, 'reduce_to')
546 row.operator('api_navigator.fake_button', text='', emboss=False, icon="DOTSDOWN")
547 row.prop(bpy.context.window_manager.api_nav_props, 'pages', text='Pages')
549 return {'FINISHED'}
554 def list_draw(self, t, pages, icon, label=None, emboss=False):
555 global tree_level, current_module
557 def reduced(too_long):
559 if too_long:
560 row = col.row()
561 row.prop(bpy.context.window_manager.api_nav_props, 'reduce_to')
562 row.operator('api_navigator.fake_button', text='', emboss=False, icon="DOTSDOWN")
563 row.prop(bpy.context.window_manager.api_nav_props, 'pages', text='Pages')
565 layout = self.layout
567 filter = bpy.context.window_manager.api_nav_props.filter
569 reduce_to = bpy.context.window_manager.api_nav_props.reduce_to * self.columns
571 page_index = reduce_to*pages
574 len = tree_level[t].__len__()
575 too_long = len > reduce_to
577 if len:
578 col = layout.column()
579 box = col.box()
581 row = box.row()
582 row.label(text=label, icon=icon)
584 if t < 2:
585 box = box.box()
586 row = box.row()
587 col = row.column(align=True)
588 i = 0
589 objects = 0
590 count = 0
591 filtered = 0
593 while count < reduce_to and i < len:
594 obj = tree_level[t][i]
596 if filter and filter not in obj:
597 i += 1
598 continue
599 elif filtered < page_index:
600 filtered += 1
601 i += 1
602 continue
604 if not (objects % self.columns):
605 row = col.row()
606 if t > 1:
607 row.operator("api_navigator.down", text=obj, emboss=emboss).pointed_module = obj
608 elif t == 0:
609 row.operator('api_navigator.subscript', text=str(obj), emboss=False).subscription = '"' + obj + '"'
610 else :
611 row.operator('api_navigator.subscript', text=str(obj), emboss=False).subscription = str(i)
612 filtered += 1
613 i += 1
614 objects += 1
615 count += 1
617 reduced(too_long)
619 return {'FINISHED'}
622 def draw(self, context):
623 global tree_level, current_module, module_type, return_report
625 api_update(context)
627 ###### layout ######
628 layout = self.layout
630 layout.label(text="Tree Structure:")
631 col = layout.column(align=True)
633 col.prop(bpy.context.window_manager.api_nav_props, 'path', text='')
634 row = col.row()
635 row.operator("api_navigator.parent", text="Parent", icon="BACK")
636 row.operator("api_navigator.back_to_bpy", text='', emboss=True, icon="FILE_PARENT")
638 col = layout.column()
639 row = col.row(align=True)
640 row.prop(bpy.context.window_manager.api_nav_props, 'filter')
641 row.operator('api_navigator.clear_filter', text='', icon='PANEL_CLOSE')
643 col = layout.column()
645 pages = bpy.context.window_manager.api_nav_props.pages
646 self.list_draw(0, pages, "DOTSDOWN", label="Items")
647 self.list_draw(1, pages, "DOTSDOWN", label="Item Values")
648 self.list_draw(2, pages, "PACKAGE", label="Modules", emboss=True)
649 self.list_draw(3, pages, "WORDWRAP_ON", label="Types", emboss=False)
650 self.list_draw(4, pages, "BUTS", label="Properties", emboss=False)
651 self.list_draw(5, pages, "OOPS", label="Structs and Functions")
652 self.list_draw(6, pages, "SCRIPTWIN", label="Methods and Functions")
653 self.list_draw(7, pages, "INFO", label="Attributes")
654 self.list_draw(8, pages, "ERROR", label="Inaccessible")
657 ########### Menu functions ###############
660 def register_keymaps():
661 kc = bpy.context.window_manager.keyconfigs.addon
662 if kc:
663 km = kc.keymaps.new(name="Text", space_type='TEXT_EDITOR')
664 km.keymap_items.new('api_navigator.toggle_doc', 'ESC', 'PRESS')
667 def unregister_keymaps():
668 kc = bpy.context.window_manager.keyconfigs.addon
669 if kc:
670 km = kc.keymaps["Text"]
671 kmi = km.keymap_items["api_navigator.toggle_doc"]
672 km.keymap_items.remove(kmi)
675 def register():
676 from bpy.props import StringProperty, IntProperty, PointerProperty
678 class ApiNavProps(bpy.types.PropertyGroup):
680 Fake module like class.
682 bpy.context.window_manager.api_nav_props
684 """
685 path = StringProperty(name='path',
686 description='Enter bpy.ops.api_navigator to see the documentation',
687 default='bpy')
688 old_path = StringProperty(name='old_path', default='')
689 filter = StringProperty(name='filter',
690 description='Filter the resulting modules', default='')
691 reduce_to = IntProperty(name='Reduce to ',
692 description='Display a maximum number of x entries by pages',
693 default=10, min=1)
694 pages = IntProperty(name='Pages',
695 description='Display a Page', default=0, min=0)
697 bpy.utils.register_module(__name__)
699 bpy.types.WindowManager.api_nav_props = PointerProperty(
700 type=ApiNavProps, name='API Nav Props', description='')
702 register_keymaps()
703 #print(get_tree_level())
706 def unregister():
707 unregister_keymaps()
708 del bpy.types.WindowManager.api_nav_props
710 bpy.utils.unregister_module(__name__)
713 if __name__ == '__main__':
714 register()