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