Don't use context.active_object
[blender-addons.git] / development_api_navigator.py
blob14d93d6aae7b7d64c72764dcc310a63084716468
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 "category": "Development",
35 """
36 You can browse through the tree structure of the api. Each child object appears in a list
37 that tries to be representative of its type. These lists are :
39 * Items (for an iterable object)
40 * Item Values (for an iterable object wich only supports index)
41 * Modules
42 * Types
43 * Properties
44 * Structs and Functions
45 * Methods and Functions
46 * Attributes
47 * Inaccessible (some objects may be listed but inaccessible)
49 The lists can be filtered to help searching in the tree. Just enter the text in the
50 filter section. It is also possible to explore other modules. Go the the root and select
51 it in the list of available modules. It will be imported dynamically.
53 In the text section, some informations are displayed. The type of the object,
54 what it returns, and its docstring. We could hope that these docstrings will be as
55 descriptive as possible. This text data block named api_doc_ can be toggled on and off
56 with the Escape key. (but a bug prevent the keymap to register correctly at start)
58 """
60 import bpy
61 from console.complete_import import get_root_modules
64 ############ Global Variables ############
66 last_text = None # last text data block
68 root_module = None # root module of the tree
70 root_m_path = '' # root_module + path as a string
72 current_module = None # the object itself in the tree structure
75 tree_level = None # the list of objects from the current_module
77 def init_tree_level():
78 global tree_level
79 tree_level = [[],[],[],[],[],[],[], [], []]
81 init_tree_level()
84 api_doc_ = '' # the documentation formated for the API Navigator
86 module_type = None # the type of current_module
88 return_report = '' # what current_module returns
90 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 ############
97 def get_root_module(path):
98 #print('get_root_module')
99 global root_module
100 if '.' in path:
101 root = path[:path.find('.')]
102 else :
103 root = path
104 try :
105 root_module = __import__(root)
106 except :
107 root_module = None
110 def evaluate(module):
111 #print('evaluate')
112 global root_module, tree_level, root_m_path
114 # path = bpy.context.window_manager.api_nav_props.path
115 try :
116 len_name = root_module.__name__.__len__()
117 root_m_path = 'root_module' + module[len_name:]
118 current_module = eval(root_m_path)
119 return current_module
120 except :
121 init_tree_level
122 return None
125 def get_tree_level():
126 #print('get_tree_level')
128 path = bpy.context.window_manager.api_nav_props.path
130 def object_list():
131 #print('object_list')
132 global current_module, root_m_path
134 itm, val, mod, typ, props, struct, met, att, bug = [], [], [], [], [], [], [], [], []
135 iterable = isiterable(current_module)
136 if iterable:
137 iter(current_module)
138 current_type = str(module_type)
139 if current_type != "<class 'str'>":
140 if iterable == 'a':
141 #if iterable == 'a':
142 #current_type.__iter__()
143 itm = list(current_module.keys())
144 if not itm:
145 val = list(current_module)
146 else :
147 val = list(current_module)
149 for i in dir(current_module):
150 try :
151 t = str(type(eval(root_m_path + '.' + i)))
152 except (AttributeError, SyntaxError):
153 bug += [i]
154 continue
157 if t == "<class 'module'>":
158 mod += [i]
159 elif t[0:16] == "<class 'bpy_prop":
160 props += [i]
161 elif t[8:11] == 'bpy':
162 struct += [i]
163 elif t == "<class 'builtin_function_or_method'>":
164 met += [i]
165 elif t == "<class 'type'>":
166 typ += [i]
167 else :
168 att += [i]
170 return [itm, val, mod, typ, props, struct, met, att, bug]
172 if not path:
173 return [[], [], [i for i in get_root_modules()], [], [], [], [], [], []]
174 return object_list()
177 def parent(path):
178 """Returns the parent path"""
179 #print('parent')
181 parent = path
182 if parent[-1] == ']' and '[' in parent:
183 while parent[-1] != '[':
184 parent = parent[:-1]
185 elif '.' in parent:
186 while parent[-1] != '.':
187 parent = parent[:-1]
188 else :
189 return ''
190 parent = parent[:-1]
191 return parent
194 def update_filter():
195 """Update the filter according to the current path"""
196 global filter_mem
198 try :
199 bpy.context.window_manager.api_nav_props.filter = filter_mem[bpy.context.window_manager.api_nav_props.path]
200 except :
201 bpy.context.window_manager.api_nav_props.filter = ''
204 def isiterable(mod):
206 try :
207 iter(mod)
208 except :
209 return False
210 try :
211 mod['']
212 return 'a'
213 except KeyError:
214 return 'a'
215 except (AttributeError, TypeError):
216 return 'b'
219 def fill_filter_mem():
220 global filter_mem
222 filter = bpy.context.window_manager.api_nav_props.filter
223 if filter:
224 filter_mem[bpy.context.window_manager.api_nav_props.old_path] = bpy.context.window_manager.api_nav_props.filter
225 else :
226 filter_mem.pop(bpy.context.window_manager.api_nav_props.old_path, None)
229 ###### API Navigator parent class #######
231 class ApiNavigator():
232 """Parent class for API Navigator"""
234 @staticmethod
235 def generate_global_values():
236 """Populate the level attributes to display the panel buttons and the documentation"""
237 global tree_level, current_module, module_type, return_report, last_text
239 text = bpy.context.space_data.text
240 if text:
241 if text.name != 'api_doc_':
242 last_text = bpy.context.space_data.text.name
243 elif bpy.data.texts.__len__() < 2:
244 last_text = None
245 else :
246 last_text = None
247 bpy.context.window_manager.api_nav_props.pages = 0
248 get_root_module(bpy.context.window_manager.api_nav_props.path)
249 current_module = evaluate(bpy.context.window_manager.api_nav_props.path)
250 module_type = str(type(current_module))
251 return_report = str(current_module)
252 tree_level = get_tree_level()
254 if tree_level.__len__() > 30:
255 global too_long
256 too_long = True
257 else :
258 too_long = False
260 ApiNavigator.generate_api_doc()
261 return {'FINISHED'}
263 @staticmethod
264 def generate_api_doc():
265 """Format the doc string for API Navigator"""
266 global current_module, api_doc_, return_report, module_type
268 path = bpy.context.window_manager.api_nav_props.path
269 line = "-" * (path.__len__()+2)
270 header = """\n\n\n\t\t%s\n\t %s\n\
271 _____________________________________________\n\
273 Type : %s\n\
276 Return : %s\n\
277 _____________________________________________\n\
279 Doc:
281 """ % (path, line, module_type, return_report)
282 footer = "\n\
283 _____________________________________________\n\
287 #############################################\n\
288 # api_doc_ #\n\
289 # Escape to toggle text #\n\
290 # (F8 to reload modules if doesn't work) #\n\
291 #############################################"
292 doc = current_module.__doc__
293 api_doc_ = header + str(doc) + footer
294 return {'FINISHED'}
296 @staticmethod
297 def doc_text_datablock():
298 """Create the text databloc or overwrite it if it already exist"""
299 global api_doc_
301 space_data = bpy.context.space_data
303 try :
304 doc_text = bpy.data.texts['api_doc_']
305 space_data.text = doc_text
306 doc_text.clear()
307 except :
308 bpy.data.texts.new(name='api_doc_')
309 doc_text = bpy.data.texts['api_doc_']
310 space_data.text = doc_text
312 doc_text.write(text=api_doc_)
313 return {'FINISHED'}
317 ############ Operators ############
318 def api_update(context):
319 if bpy.context.window_manager.api_nav_props.path != bpy.context.window_manager.api_nav_props.old_path:
320 fill_filter_mem()
321 bpy.context.window_manager.api_nav_props.old_path = bpy.context.window_manager.api_nav_props.path
322 update_filter()
323 ApiNavigator.generate_global_values()
324 ApiNavigator.doc_text_datablock()
327 class Update(ApiNavigator, bpy.types.Operator):
328 """Update the tree structure"""
329 bl_idname = "api_navigator.update"
330 bl_label = "API Navigator Update"
332 def execute(self, context):
333 api_update()
334 return {'FINISHED'}
337 class BackToBpy(ApiNavigator, bpy.types.Operator):
338 """go back to module bpy"""
339 bl_idname = "api_navigator.back_to_bpy"
340 bl_label = "Back to bpy"
342 def execute(self, context):
343 fill_filter_mem()
344 if not bpy.context.window_manager.api_nav_props.path:
345 bpy.context.window_manager.api_nav_props.old_path = bpy.context.window_manager.api_nav_props.path = 'bpy'
346 else :
347 bpy.context.window_manager.api_nav_props.old_path = bpy.context.window_manager.api_nav_props.path = 'bpy'
348 update_filter()
349 self.generate_global_values()
350 self.doc_text_datablock()
351 return {'FINISHED'}
354 class Down(ApiNavigator, bpy.types.Operator):
355 """go to this Module"""
356 bl_idname = "api_navigator.down"
357 bl_label = "API Navigator Down"
358 pointed_module = bpy.props.StringProperty(name='Current Module', default='')
361 def execute(self, context):
362 fill_filter_mem()
364 if not bpy.context.window_manager.api_nav_props.path:
365 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
366 else :
367 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
369 update_filter()
370 self.generate_global_values()
371 self.doc_text_datablock()
372 return {'FINISHED'}
375 class Parent(ApiNavigator, bpy.types.Operator):
376 """go to Parent Module"""
377 bl_idname = "api_navigator.parent"
378 bl_label = "API Navigator Parent"
381 def execute(self, context):
382 path = bpy.context.window_manager.api_nav_props.path
384 if path:
385 fill_filter_mem()
386 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)
387 update_filter()
388 self.generate_global_values()
389 self.doc_text_datablock()
391 return {'FINISHED'}
394 class ClearFilter(ApiNavigator, bpy.types.Operator):
395 """Clear the filter"""
396 bl_idname = 'api_navigator.clear_filter'
397 bl_label = 'API Nav clear filter'
399 def execute(self, context):
400 bpy.context.window_manager.api_nav_props.filter = ''
401 return {'FINISHED'}
404 class FakeButton(ApiNavigator, bpy.types.Operator):
405 """The list is not displayed completely""" # only serve as an indicator
406 bl_idname = 'api_navigator.fake_button'
407 bl_label = ''
410 class Subscript(ApiNavigator, bpy.types.Operator):
411 """Subscript to this Item"""
412 bl_idname = "api_navigator.subscript"
413 bl_label = "API Navigator Subscript"
414 subscription = bpy.props.StringProperty(name='', default='')
416 def execute(self, context):
417 fill_filter_mem()
418 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 + ']'
419 update_filter()
420 self.generate_global_values()
421 self.doc_text_datablock()
422 return {'FINISHED'}
425 class Toggle_doc(ApiNavigator, bpy.types.Operator):
426 """Toggle on or off api_doc_ Text"""
427 bl_idname = 'api_navigator.toggle_doc'
428 bl_label = 'Toggle api_doc_'
431 def execute(self, context):
432 global last_text
434 try :
435 if bpy.context.space_data.text.name != "api_doc_":
436 last_text = bpy.context.space_data.text.name
437 except : pass
439 try :
440 text = bpy.data.texts["api_doc_"]
441 bpy.data.texts["api_doc_"].clear()
442 bpy.data.texts.remove(text)
443 except KeyError:
444 self.doc_text_datablock()
445 return {'FINISHED'}
447 try :
448 text = bpy.data.texts[last_text]
449 bpy.context.space_data.text = text
450 #line = bpy.ops.text.line_number() # operator doesn't seems to work ???
451 #bpy.ops.text.jump(line=line)
452 return {'FINISHED'}
453 except : pass
455 bpy.context.space_data.text = None
456 return {'FINISHED'}
458 ############ UI Panels ############
460 class OBJECT_PT_api_navigator(ApiNavigator, bpy.types.Panel):
461 bl_idname = 'api_navigator'
462 bl_space_type = "TEXT_EDITOR"
463 bl_region_type = "UI"
464 bl_label = "API Navigator"
465 bl_options = {'DEFAULT_CLOSED'}
468 columns = 3
471 def iterable_draw(self):
472 global tree_level, current_module
474 iterable = isiterable(current_module)
476 if iterable:
477 iter(current_module)
478 current_type = str(module_type)
480 if current_type == "<class 'str'>":
481 return {'FINISHED'}
483 col = self.layout
484 # filter = bpy.context.window_manager.api_nav_props.filter # UNUSED
485 reduce_to = bpy.context.window_manager.api_nav_props.reduce_to * self.columns
486 pages = bpy.context.window_manager.api_nav_props.pages
487 page_index = reduce_to*pages
488 # rank = 0 # UNUSED
489 count = 0
490 i = 0
491 filtered = 0
493 if iterable == 'a':
494 current_type.__iter__()
495 collection = list(current_module.keys())
496 end = collection.__len__()
497 box = self.layout.box()
498 row = box.row()
499 row.label(text="Items", icon="DOTSDOWN")
500 box = box.box()
501 col = box.column(align=True)
503 while count < reduce_to and i < end:
504 mod = collection[i]
505 if filtered < page_index:
506 filtered += 1
507 i += 1
508 continue
510 if not (i % self.columns):
511 row = col.row()
512 row.operator('api_navigator.subscript', text=mod, emboss=False).subscription = '"' + mod + '"'
513 filtered += 1
514 i += 1
515 count += 1
517 elif iterable == 'b':
518 box = self.layout.box()
519 row = box.row()
520 row.label(text="Item Values", icon="OOPS")
521 box = box.box()
522 col = box.column(align=True)
523 collection = list(current_module)
524 end = collection.__len__()
526 while count < reduce_to and i < end:
527 mod = str(collection[i])
528 if filtered < page_index:
529 filtered += 1
530 i += 1
531 continue
533 if not (i % self.columns):
534 row = col.row()
535 row.operator('api_navigator.subscript', text=mod, emboss=False).subscription = str(i)
536 filtered += 1
537 i += 1
538 count += 1
540 too_long = end > 30
542 if too_long:
543 row = col.row()
544 row.prop(bpy.context.window_manager.api_nav_props, 'reduce_to')
545 row.operator('api_navigator.fake_button', text='', emboss=False, icon="DOTSDOWN")
546 row.prop(bpy.context.window_manager.api_nav_props, 'pages', text='Pages')
548 return {'FINISHED'}
553 def list_draw(self, t, pages, icon, label=None, emboss=False):
554 global tree_level, current_module
556 def reduced(too_long):
558 if too_long:
559 row = col.row()
560 row.prop(bpy.context.window_manager.api_nav_props, 'reduce_to')
561 row.operator('api_navigator.fake_button', text='', emboss=False, icon="DOTSDOWN")
562 row.prop(bpy.context.window_manager.api_nav_props, 'pages', text='Pages')
564 layout = self.layout
566 filter = bpy.context.window_manager.api_nav_props.filter
568 reduce_to = bpy.context.window_manager.api_nav_props.reduce_to * self.columns
570 page_index = reduce_to*pages
573 len = tree_level[t].__len__()
574 too_long = len > reduce_to
576 if len:
577 col = layout.column()
578 box = col.box()
580 row = box.row()
581 row.label(text=label, icon=icon)
583 if t < 2:
584 box = box.box()
585 row = box.row()
586 col = row.column(align=True)
587 i = 0
588 objects = 0
589 count = 0
590 filtered = 0
592 while count < reduce_to and i < len:
593 obj = tree_level[t][i]
595 if filter and filter not in obj:
596 i += 1
597 continue
598 elif filtered < page_index:
599 filtered += 1
600 i += 1
601 continue
603 if not (objects % self.columns):
604 row = col.row()
605 if t > 1:
606 row.operator("api_navigator.down", text=obj, emboss=emboss).pointed_module = obj
607 elif t == 0:
608 row.operator('api_navigator.subscript', text=str(obj), emboss=False).subscription = '"' + obj + '"'
609 else :
610 row.operator('api_navigator.subscript', text=str(obj), emboss=False).subscription = str(i)
611 filtered += 1
612 i += 1
613 objects += 1
614 count += 1
616 reduced(too_long)
618 return {'FINISHED'}
621 def draw(self, context):
622 global tree_level, current_module, module_type, return_report
624 api_update(context)
626 ###### layout ######
627 layout = self.layout
629 layout.label(text="Tree Structure:")
630 col = layout.column(align=True)
632 col.prop(bpy.context.window_manager.api_nav_props, 'path', text='')
633 row = col.row()
634 row.operator("api_navigator.parent", text="Parent", icon="BACK")
635 row.operator("api_navigator.back_to_bpy", text='', emboss=True, icon="FILE_PARENT")
637 col = layout.column()
638 row = col.row(align=True)
639 row.prop(bpy.context.window_manager.api_nav_props, 'filter')
640 row.operator('api_navigator.clear_filter', text='', icon='PANEL_CLOSE')
642 col = layout.column()
644 pages = bpy.context.window_manager.api_nav_props.pages
645 self.list_draw(0, pages, "DOTSDOWN", label="Items")
646 self.list_draw(1, pages, "DOTSDOWN", label="Item Values")
647 self.list_draw(2, pages, "PACKAGE", label="Modules", emboss=True)
648 self.list_draw(3, pages, "WORDWRAP_ON", label="Types", emboss=False)
649 self.list_draw(4, pages, "BUTS", label="Properties", emboss=False)
650 self.list_draw(5, pages, "OOPS", label="Structs and Functions")
651 self.list_draw(6, pages, "SCRIPTWIN", label="Methods and Functions")
652 self.list_draw(7, pages, "INFO", label="Attributes")
653 self.list_draw(8, pages, "ERROR", label="Inaccessible")
656 ########### Menu functions ###############
659 def register_keymaps():
660 kc = bpy.context.window_manager.keyconfigs.addon
661 if kc:
662 km = kc.keymaps.new(name="Text", space_type='TEXT_EDITOR')
663 km.keymap_items.new('api_navigator.toggle_doc', 'ESC', 'PRESS')
666 def unregister_keymaps():
667 kc = bpy.context.window_manager.keyconfigs.addon
668 if kc:
669 km = kc.keymaps["Text"]
670 kmi = km.keymap_items["api_navigator.toggle_doc"]
671 km.keymap_items.remove(kmi)
674 def register():
675 from bpy.props import StringProperty, IntProperty, PointerProperty
677 class ApiNavProps(bpy.types.PropertyGroup):
679 Fake module like class.
681 bpy.context.window_manager.api_nav_props
683 """
684 path = StringProperty(name='path',
685 description='Enter bpy.ops.api_navigator to see the documentation',
686 default='bpy')
687 old_path = StringProperty(name='old_path', default='')
688 filter = StringProperty(name='filter',
689 description='Filter the resulting modules', default='')
690 reduce_to = IntProperty(name='Reduce to ',
691 description='Display a maximum number of x entries by pages',
692 default=10, min=1)
693 pages = IntProperty(name='Pages',
694 description='Display a Page', default=0, min=0)
696 bpy.utils.register_module(__name__)
698 bpy.types.WindowManager.api_nav_props = PointerProperty(
699 type=ApiNavProps, name='API Nav Props', description='')
701 register_keymaps()
702 #print(get_tree_level())
705 def unregister():
706 unregister_keymaps()
707 del bpy.types.WindowManager.api_nav_props
709 bpy.utils.unregister_module(__name__)
712 if __name__ == '__main__':
713 register()