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 #####
21 "name": "Edit Operator Source",
22 "author": "scorpion81",
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",
28 "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/"
29 "Py/Scripts/Development/Edit_Operator_Source",
30 "category": "Development"}
36 from bpy
.types
import (
43 from bpy
.props
import (
49 def stdlib_excludes():
50 #need a handy list of modules to avoid walking into
51 import distutils
.sysconfig
as sysconfig
53 std_lib
= sysconfig
.get_python_lib(standard_lib
=True)
54 for top
, dirs
, files
in os
.walk(std_lib
):
56 if nm
!= '__init__.py' and nm
[-3:] == '.py':
57 excludes
.append(os
.path
.join(top
, nm
)[len(std_lib
)+1:-3].replace('\\','.'))
61 def make_loc(prefix
, c
):
62 #too long and not helpful... omitting for now
64 #if hasattr(c, "bl_space_type"):
65 # space = c.bl_space_type
68 #if hasattr(c, "bl_region_type"):
69 # region = c.bl_region_type
72 if hasattr(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"):
92 src
, n
= inspect
.getsourcelines(m
.draw
)
93 for i
, s
in enumerate(src
):
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
115 clazz
= getattr(bpy
.types
, id)
117 except AttributeError:
121 def getmodule(opname
):
123 clazz
= getclazz(opname
)
128 modn
= clazz
.__module
__
131 line
= inspect
.getsourcelines(clazz
)[1]
137 if modn
== 'bpy.types':
140 elif modn
!= '__main__':
141 mod
= sys
.modules
[modn
].__file
__
146 return mod
, line
, addon
151 opsdir
= dir(bpy
.ops
)
152 for opmodname
in opsdir
:
153 opmod
= getattr(bpy
.ops
, opmodname
)
154 opmoddir
= dir(opmod
)
156 name
= opmodname
+ "." + o
157 clazz
= getclazz(name
)
158 #if (clazz is not None) :# and clazz.__module__ != 'bpy.types'):
162 # add own operator name too, since its not loaded yet when this is called
163 allops
.append("text.edit_operator")
168 return [(y
, y
, "", x
) for x
, y
in enumerate(l
)]
170 class OperatorEntry(PropertyGroup
):
172 label
: StringProperty(
178 path
: StringProperty(
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"
204 path
: StringProperty(
216 def show_text(self
, context
, path
, line
):
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
225 bpy
.ops
.text
.jump(ctx
, line
=line
)
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
):
239 exclude
= stdlib_excludes()
240 exclude
.append("bpy")
241 exclude
.append("sys")
244 walk_module(self
.op
, bl_ui
, calls
, exclude
)
246 for m
in addon_utils
.modules():
248 mod
= sys
.modules
[m
.__name
__]
249 walk_module(self
.op
, mod
, calls
, exclude
)
254 cl
= context
.scene
.calls
.add()
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
)
270 context
.scene
.calls
.clear()
271 path
, line
, addon
= getmodule(self
.op
)
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()
283 self
.show_calls(context
)
284 context
.area
.tag_redraw()
289 self
.report({'WARNING'},
290 "Found no source file for " + self
.op
)
292 self
.show_calls(context
)
293 context
.area
.tag_redraw()
298 class TEXT_PT_EditOperatorPanel(Panel
):
299 bl_space_type
= 'TEXT_EDITOR'
300 bl_region_type
= 'UI'
301 bl_label
= "Edit Operator"
303 bl_options
= {'DEFAULT_CLOSED'}
305 def draw(self
, context
):
307 op
= layout
.operator("text.edit_operator")
311 if len(context
.scene
.calls
) > 0:
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
)
323 bpy
.utils
.register_class(OperatorEntry
)
324 bpy
.types
.Scene
.calls
= bpy
.props
.CollectionProperty(name
="Calls",
326 bpy
.utils
.register_class(TEXT_OT_EditOperator
)
327 bpy
.utils
.register_class(TEXT_PT_EditOperatorPanel
)
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__":