Pose Library: update for rename of asset_library to asset_library_ref
[blender-addons.git] / development_edit_operator.py
blobf9e0b9110b77408d060eab1a15e4aad8d41c6822
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 LICENSE BLOCK #####
20 bl_info = {
21 "name": "Edit Operator Source",
22 "author": "scorpion81",
23 "version": (1, 2, 2),
24 "blender": (2, 80, 0),
25 "location": "Text Editor > Sidebar > Edit Operator",
26 "description": "Opens source file of chosen operator or call locations, if source not available",
27 "warning": "",
28 "doc_url": "{BLENDER_MANUAL_URL}/addons/development/edit_operator.html",
29 "category": "Development",
32 import bpy
33 import sys
34 import os
35 import inspect
36 from bpy.types import (
37 Operator,
38 Panel,
39 Header,
40 Menu,
41 PropertyGroup
43 from bpy.props import (
44 EnumProperty,
45 StringProperty,
46 IntProperty
49 def stdlib_excludes():
50 #need a handy list of modules to avoid walking into
51 import distutils.sysconfig as sysconfig
52 excludes = []
53 std_lib = sysconfig.get_python_lib(standard_lib=True)
54 for top, dirs, files in os.walk(std_lib):
55 for nm in files:
56 if nm != '__init__.py' and nm[-3:] == '.py':
57 excludes.append(os.path.join(top, nm)[len(std_lib)+1:-3].replace('\\','.'))
59 return excludes
61 def make_loc(prefix, c):
62 #too long and not helpful... omitting for now
63 space = ""
64 #if hasattr(c, "bl_space_type"):
65 # space = c.bl_space_type
67 region = ""
68 #if hasattr(c, "bl_region_type"):
69 # region = c.bl_region_type
71 label = ""
72 if hasattr(c, "bl_label"):
73 label = c.bl_label
75 return prefix+": " + space + " " + region + " " + label
77 def walk_module(opname, mod, calls=[], exclude=[]):
79 for name, m in inspect.getmembers(mod):
80 if inspect.ismodule(m):
81 if m.__name__ not in exclude:
82 #print(name, m.__name__)
83 walk_module(opname, m, calls, exclude)
84 elif inspect.isclass(m):
85 if (issubclass(m, Panel) or \
86 issubclass(m, Header) or \
87 issubclass(m, Menu)) and mod.__name__ != "bl_ui":
88 if hasattr(m, "draw"):
89 loc = ""
90 file = ""
91 line = -1
92 src, n = inspect.getsourcelines(m.draw)
93 for i, s in enumerate(src):
94 if opname in s:
95 file = mod.__file__
96 line = n + i
98 if issubclass(m, Panel) and name != "Panel":
99 loc = make_loc("Panel", m)
100 calls.append([opname, loc, file, line])
101 if issubclass(m, Header) and name != "Header":
102 loc = make_loc("Header", m)
103 calls.append([opname, loc, file, line])
104 if issubclass(m, Menu) and name != "Menu":
105 loc = make_loc("Menu", m)
106 calls.append([opname, loc, file, line])
109 def getclazz(opname):
110 opid = opname.split(".")
111 opmod = getattr(bpy.ops, opid[0])
112 op = getattr(opmod, opid[1])
113 id = op.get_rna_type().bl_rna.identifier
114 try:
115 clazz = getattr(bpy.types, id)
116 return clazz
117 except AttributeError:
118 return None
121 def getmodule(opname):
122 addon = True
123 clazz = getclazz(opname)
125 if clazz is None:
126 return "", -1, False
128 modn = clazz.__module__
130 try:
131 line = inspect.getsourcelines(clazz)[1]
132 except IOError:
133 line = -1
134 except TypeError:
135 line = -1
137 if modn == 'bpy.types':
138 mod = 'C operator'
139 addon = False
140 elif modn != '__main__':
141 mod = sys.modules[modn].__file__
142 else:
143 addon = False
144 mod = modn
146 return mod, line, addon
149 def get_ops():
150 allops = []
151 opsdir = dir(bpy.ops)
152 for opmodname in opsdir:
153 opmod = getattr(bpy.ops, opmodname)
154 opmoddir = dir(opmod)
155 for o in opmoddir:
156 name = opmodname + "." + o
157 clazz = getclazz(name)
158 #if (clazz is not None) :# and clazz.__module__ != 'bpy.types'):
159 allops.append(name)
160 del opmoddir
162 # add own operator name too, since its not loaded yet when this is called
163 allops.append("text.edit_operator")
164 l = sorted(allops)
165 del allops
166 del opsdir
168 return [(y, y, "", x) for x, y in enumerate(l)]
170 class OperatorEntry(PropertyGroup):
172 label : StringProperty(
173 name="Label",
174 description="",
175 default=""
178 path : StringProperty(
179 name="Path",
180 description="",
181 default=""
184 line : IntProperty(
185 name="Line",
186 description="",
187 default=-1
190 class TEXT_OT_EditOperator(Operator):
191 bl_idname = "text.edit_operator"
192 bl_label = "Edit Operator"
193 bl_description = "Opens the source file of operators chosen from Menu"
194 bl_property = "op"
196 items = get_ops()
198 op : EnumProperty(
199 name="Op",
200 description="",
201 items=items
204 path : StringProperty(
205 name="Path",
206 description="",
207 default=""
210 line : IntProperty(
211 name="Line",
212 description="",
213 default=-1
216 def show_text(self, context, path, line):
217 found = False
219 for t in bpy.data.texts:
220 if t.filepath == path:
221 #switch to the wanted text first
222 context.space_data.text = t
223 ctx = context.copy()
224 ctx['edit_text'] = t
225 bpy.ops.text.jump(ctx, line=line)
226 found = True
227 break
229 if (found is False):
230 self.report({'INFO'},
231 "Opened file: " + path)
232 bpy.ops.text.open(filepath=path)
233 bpy.ops.text.jump(line=line)
235 def show_calls(self, context):
236 import bl_ui
237 import addon_utils
239 exclude = stdlib_excludes()
240 exclude.append("bpy")
241 exclude.append("sys")
243 calls = []
244 walk_module(self.op, bl_ui, calls, exclude)
246 for m in addon_utils.modules():
247 try:
248 mod = sys.modules[m.__name__]
249 walk_module(self.op, mod, calls, exclude)
250 except KeyError:
251 continue
253 for c in calls:
254 cl = context.scene.calls.add()
255 cl.name = c[0]
256 cl.label = c[1]
257 cl.path = c[2]
258 cl.line = c[3]
260 def invoke(self, context, event):
261 context.window_manager.invoke_search_popup(self)
262 return {'PASS_THROUGH'}
264 def execute(self, context):
265 if self.path != "" and self.line != -1:
266 #invocation of one of the "found" locations
267 self.show_text(context, self.path, self.line)
268 return {'FINISHED'}
269 else:
270 context.scene.calls.clear()
271 path, line, addon = getmodule(self.op)
273 if addon:
274 self.show_text(context, path, line)
276 #add convenient "source" button, to toggle back from calls to source
277 c = context.scene.calls.add()
278 c.name = self.op
279 c.label = "Source"
280 c.path = path
281 c.line = line
283 self.show_calls(context)
284 context.area.tag_redraw()
286 return {'FINISHED'}
287 else:
289 self.report({'WARNING'},
290 "Found no source file for " + self.op)
292 self.show_calls(context)
293 context.area.tag_redraw()
295 return {'FINISHED'}
298 class TEXT_PT_EditOperatorPanel(Panel):
299 bl_space_type = 'TEXT_EDITOR'
300 bl_region_type = 'UI'
301 bl_label = "Edit Operator"
302 bl_category = "Text"
303 bl_options = {'DEFAULT_CLOSED'}
305 def draw(self, context):
306 layout = self.layout
307 op = layout.operator("text.edit_operator")
308 op.path = ""
309 op.line = -1
311 if len(context.scene.calls) > 0:
312 box = layout.box()
313 box.label(text="Calls of: " + context.scene.calls[0].name)
314 box.operator_context = 'EXEC_DEFAULT'
315 for c in context.scene.calls:
316 op = box.operator("text.edit_operator", text=c.label)
317 op.path = c.path
318 op.line = c.line
319 op.op = c.name
322 def register():
323 bpy.utils.register_class(OperatorEntry)
324 bpy.types.Scene.calls = bpy.props.CollectionProperty(name="Calls",
325 type=OperatorEntry)
326 bpy.utils.register_class(TEXT_OT_EditOperator)
327 bpy.utils.register_class(TEXT_PT_EditOperatorPanel)
330 def unregister():
331 bpy.utils.unregister_class(TEXT_PT_EditOperatorPanel)
332 bpy.utils.unregister_class(TEXT_OT_EditOperator)
333 del bpy.types.Scene.calls
334 bpy.utils.unregister_class(OperatorEntry)
337 if __name__ == "__main__":
338 register()