== console ==
[blender.git] / release / scripts / op / console_python.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
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.
7 #
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.
12 #
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.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18
19 # <pep8 compliant>
20 import sys
21 import bpy
22
23 language_id = 'python'
24
25 # store our own __main__ module, not 100% needed
26 # but python expects this in some places
27 _BPY_MAIN_OWN = True
28
29
30 def add_scrollback(text, text_type):
31     for l in text.split('\n'):
32         bpy.ops.console.scrollback_append(text=l.replace('\t', '    '),
33             type=text_type)
34
35
36 def get_console(console_id):
37     '''
38     helper function for console operators
39     currently each text datablock gets its own
40     console - code.InteractiveConsole()
41     ...which is stored in this function.
42
43     console_id can be any hashable type
44     '''
45     from code import InteractiveConsole
46
47     consoles = getattr(get_console, "consoles", None)
48     hash_next = hash(bpy.context.window_manager)
49
50     if consoles is None:
51         consoles = get_console.consoles = {}
52         get_console.consoles_namespace_hash = hash_next
53     else:
54         # check if clearning the namespace is needed to avoid a memory leak.
55         # the window manager is normally loaded with new blend files
56         # so this is a reasonable way to deal with namespace clearing.
57         # bpy.data hashing is reset by undo so cant be used.
58         hash_prev = getattr(get_console, "consoles_namespace_hash", 0)
59
60         if hash_prev != hash_next:
61             get_console.consoles_namespace_hash = hash_next
62             consoles.clear()
63
64     console_data = consoles.get(console_id)
65
66     if console_data:
67         console, stdout, stderr = console_data
68
69         # XXX, bug in python 3.1.2 ? (worked in 3.1.1)
70         # seems there is no way to clear StringIO objects for writing, have to make new ones each time.
71         import io
72         stdout = io.StringIO()
73         stderr = io.StringIO()
74     else:
75         if _BPY_MAIN_OWN:
76             import types
77             bpy_main_mod = types.ModuleType("__main__")
78             namespace = bpy_main_mod.__dict__
79         else:
80             namespace = {}
81
82         namespace["__builtins__"] = sys.modules["builtins"]
83         namespace["bpy"] = bpy
84         namespace["C"] = bpy.context
85         
86         namespace.update(__import__("mathutils").__dict__) # from mathutils import *
87         namespace.update(__import__("math").__dict__) # from math import *
88
89         console = InteractiveConsole(locals=namespace, filename="<blender_console>")
90
91         if _BPY_MAIN_OWN:
92             console._bpy_main_mod = bpy_main_mod
93
94         import io
95         stdout = io.StringIO()
96         stderr = io.StringIO()
97
98         consoles[console_id] = console, stdout, stderr
99
100     return console, stdout, stderr
101
102
103 # Both prompts must be the same length
104 PROMPT = '>>> '
105 PROMPT_MULTI = '... '
106
107
108 def execute(context):
109     sc = context.space_data
110
111     try:
112         line_object = sc.history[-1]
113     except:
114         return {'CANCELLED'}
115
116     console, stdout, stderr = get_console(hash(context.region))
117
118     # redirect output
119     sys.stdout = stdout
120     sys.stderr = stderr
121
122     # dont allow the stdin to be used, can lock blender.
123     stdin_backup = sys.stdin
124     sys.stdin = None
125
126     if _BPY_MAIN_OWN:
127         main_mod_back = sys.modules["__main__"]
128         sys.modules["__main__"] = console._bpy_main_mod
129
130     # incase exception happens
131     line = ""  # incase of encodingf error
132     is_multiline = False
133
134     try:
135         line = line_object.body
136
137         # run the console, "\n" executes a multiline statement
138         line_exec = line if line.strip() else "\n"
139
140         is_multiline = console.push(line_exec)
141     except:
142         # unlikely, but this can happen with unicode errors for example.
143         import traceback
144         stderr.write(traceback.format_exc())
145
146     if _BPY_MAIN_OWN:
147         sys.modules["__main__"] = main_mod_back
148
149     stdout.seek(0)
150     stderr.seek(0)
151
152     output = stdout.read()
153     output_err = stderr.read()
154
155     # cleanup
156     sys.stdout = sys.__stdout__
157     sys.stderr = sys.__stderr__
158     sys.last_traceback = None
159
160     # So we can reuse, clear all data
161     stdout.truncate(0)
162     stderr.truncate(0)
163
164     # special exception. its possible the command loaded a new user interface
165     if hash(sc) != hash(context.space_data):
166         return
167
168     bpy.ops.console.scrollback_append(text=sc.prompt + line, type='INPUT')
169
170     if is_multiline:
171         sc.prompt = PROMPT_MULTI
172     else:
173         sc.prompt = PROMPT
174
175     # insert a new blank line
176     bpy.ops.console.history_append(text="", current_character=0,
177         remove_duplicates=True)
178
179     # Insert the output into the editor
180     # not quite correct because the order might have changed,
181     # but ok 99% of the time.
182     if output:
183         add_scrollback(output, 'OUTPUT')
184     if output_err:
185         add_scrollback(output_err, 'ERROR')
186
187     # restore the stdin
188     sys.stdin = stdin_backup
189     
190     # execute any hooks
191     for func, args in execute.hooks:
192         func(*args)
193
194     return {'FINISHED'}
195
196 execute.hooks = []
197
198
199 def autocomplete(context):
200     from console import intellisense
201
202     sc = context.space_data
203
204     console = get_console(hash(context.region))[0]
205
206     if not console:
207         return {'CANCELLED'}
208
209     # dont allow the stdin to be used, can lock blender.
210     # note: unlikely stdin would be used for autocomp. but its possible.
211     stdin_backup = sys.stdin
212     sys.stdin = None
213
214     scrollback = ""
215     scrollback_error = ""
216
217     if _BPY_MAIN_OWN:
218         main_mod_back = sys.modules["__main__"]
219         sys.modules["__main__"] = console._bpy_main_mod
220
221     try:
222         current_line = sc.history[-1]
223         line = current_line.body
224
225         # This function isnt aware of the text editor or being an operator
226         # just does the autocomp then copy its results back
227         current_line.body, current_line.current_character, scrollback = \
228             intellisense.expand(
229                 line=current_line.body,
230                 cursor=current_line.current_character,
231                 namespace=console.locals,
232                 private=bpy.app.debug)
233     except:
234         # unlikely, but this can happen with unicode errors for example.
235         # or if the api attribute access its self causes an error.
236         import traceback
237         scrollback_error = traceback.format_exc()
238
239     if _BPY_MAIN_OWN:
240         sys.modules["__main__"] = main_mod_back
241
242     # Separate automplete output by command prompts
243     if scrollback != '':
244         bpy.ops.console.scrollback_append(text=sc.prompt + current_line.body, type='INPUT')
245
246     # Now we need to copy back the line from blender back into the
247     # text editor. This will change when we dont use the text editor
248     # anymore
249     if scrollback:
250         add_scrollback(scrollback, 'INFO')
251
252     if scrollback_error:
253         add_scrollback(scrollback_error, 'ERROR')
254
255     # restore the stdin
256     sys.stdin = stdin_backup
257
258     context.area.tag_redraw()
259
260     return {'FINISHED'}
261
262
263 def banner(context):
264     sc = context.space_data
265     version_string = sys.version.strip().replace('\n', ' ')
266
267     add_scrollback(" * Python Interactive Console %s *" % version_string, 'OUTPUT')
268     add_scrollback("Command History:     Up/Down Arrow", 'OUTPUT')
269     add_scrollback("Cursor:              Left/Right Home/End", 'OUTPUT')
270     add_scrollback("Remove:              Backspace/Delete", 'OUTPUT')
271     add_scrollback("Execute:             Enter", 'OUTPUT')
272     add_scrollback("Autocomplete:        Ctrl+Space", 'OUTPUT')
273     add_scrollback("Ctrl +/-  Wheel:     Zoom", 'OUTPUT')
274     add_scrollback("Builtin Modules:     bpy, bpy.data, bpy.ops, bpy.props, bpy.types, bpy.context, bgl, blf, mathutils", 'OUTPUT')
275     add_scrollback("Convenience Imports: from mathutils import *; from math import *", 'OUTPUT')
276     add_scrollback("", 'OUTPUT')
277     add_scrollback("  WARNING!!! Blender 2.5 API is subject to change, see API reference for more info.", 'ERROR')
278     add_scrollback("", 'OUTPUT')
279     sc.prompt = PROMPT
280
281     return {'FINISHED'}
282
283
284 def register():
285     pass
286
287
288 def unregister():
289     pass
290
291
292 if __name__ == "__main__":
293     register()