Merge branch 'blender2.7'
[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-80 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(*args):
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         pydoc.help(*args)
46
47     namespace["help"] = _help
48
49
50 def get_console(console_id):
51     """
52     helper function for console operators
53     currently each text data block 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 clearing 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 can't 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, 3.2 ? (worked in 3.1.1)
84         # seems there is no way to clear StringIO objects for writing, have to
85         # make new ones each time.
86         import io
87         stdout = io.StringIO()
88         stderr = io.StringIO()
89     else:
90         if _BPY_MAIN_OWN:
91             import types
92             bpy_main_mod = types.ModuleType("__main__")
93             namespace = bpy_main_mod.__dict__
94         else:
95             namespace = {}
96
97         namespace["__builtins__"] = sys.modules["builtins"]
98         namespace["bpy"] = bpy
99
100         # weak! - but highly convenient
101         namespace["C"] = bpy.context
102         namespace["D"] = bpy.data
103
104         replace_help(namespace)
105
106         console = InteractiveConsole(locals=namespace,
107                                      filename="<blender_console>")
108
109         console.push("from mathutils import *")
110         console.push("from math import *")
111
112         if _BPY_MAIN_OWN:
113             console._bpy_main_mod = bpy_main_mod
114
115         import io
116         stdout = io.StringIO()
117         stderr = io.StringIO()
118
119         consoles[console_id] = console, stdout, stderr
120
121     return console, stdout, stderr
122
123
124 # Both prompts must be the same length
125 PROMPT = '>>> '
126 PROMPT_MULTI = '... '
127
128
129 def execute(context, is_interactive):
130     sc = context.space_data
131
132     try:
133         line_object = sc.history[-1]
134     except:
135         return {'CANCELLED'}
136
137     console, stdout, stderr = get_console(hash(context.region))
138
139     if _BPY_MAIN_OWN:
140         main_mod_back = sys.modules["__main__"]
141         sys.modules["__main__"] = console._bpy_main_mod
142
143     # redirect output
144     from contextlib import (
145         redirect_stdout,
146         redirect_stderr,
147     )
148
149     # not included with Python
150     class redirect_stdin(redirect_stdout.__base__):
151         _stream = "stdin"
152
153     # don't allow the stdin to be used, can lock blender.
154     with redirect_stdout(stdout), \
155             redirect_stderr(stderr), \
156             redirect_stdin(None):
157
158         # in case exception happens
159         line = ""  # in case of encoding error
160         is_multiline = False
161
162         try:
163             line = line_object.body
164
165             # run the console, "\n" executes a multi line statement
166             line_exec = line if line.strip() else "\n"
167
168             is_multiline = console.push(line_exec)
169         except:
170             # unlikely, but this can happen with unicode errors for example.
171             import traceback
172             stderr.write(traceback.format_exc())
173
174     if _BPY_MAIN_OWN:
175         sys.modules["__main__"] = main_mod_back
176
177     stdout.seek(0)
178     stderr.seek(0)
179
180     output = stdout.read()
181     output_err = stderr.read()
182
183     # cleanup
184     sys.last_traceback = None
185
186     # So we can reuse, clear all data
187     stdout.truncate(0)
188     stderr.truncate(0)
189
190     # special exception. its possible the command loaded a new user interface
191     if hash(sc) != hash(context.space_data):
192         return {'FINISHED'}
193
194     bpy.ops.console.scrollback_append(text=sc.prompt + line, type='INPUT')
195
196     if is_multiline:
197         sc.prompt = PROMPT_MULTI
198         if is_interactive:
199             indent = line[:len(line) - len(line.lstrip())]
200             if line.rstrip().endswith(":"):
201                 indent += "    "
202         else:
203             indent = ""
204     else:
205         sc.prompt = PROMPT
206         indent = ""
207
208     # insert a new blank line
209     bpy.ops.console.history_append(text=indent, current_character=0,
210                                    remove_duplicates=True)
211     sc.history[-1].current_character = len(indent)
212
213     # Insert the output into the editor
214     # not quite correct because the order might have changed,
215     # but ok 99% of the time.
216     if output:
217         add_scrollback(output, 'OUTPUT')
218     if output_err:
219         add_scrollback(output_err, 'ERROR')
220
221     # execute any hooks
222     for func, args in execute.hooks:
223         func(*args)
224
225     return {'FINISHED'}
226
227
228 execute.hooks = []
229
230
231 def autocomplete(context):
232     _readline_bypass()
233
234     from console import intellisense
235
236     sc = context.space_data
237
238     console = get_console(hash(context.region))[0]
239
240     if not console:
241         return {'CANCELLED'}
242
243     # don't allow the stdin to be used, can lock blender.
244     # note: unlikely stdin would be used for autocomplete. but its possible.
245     stdin_backup = sys.stdin
246     sys.stdin = None
247
248     scrollback = ""
249     scrollback_error = ""
250
251     if _BPY_MAIN_OWN:
252         main_mod_back = sys.modules["__main__"]
253         sys.modules["__main__"] = console._bpy_main_mod
254
255     try:
256         current_line = sc.history[-1]
257         line = current_line.body
258
259         # This function isn't aware of the text editor or being an operator
260         # just does the autocomplete then copy its results back
261         result = intellisense.expand(
262             line=line,
263             cursor=current_line.current_character,
264             namespace=console.locals,
265             private=bpy.app.debug_python)
266
267         line_new = result[0]
268         current_line.body, current_line.current_character, scrollback = result
269         del result
270
271         # update selection. setting body should really do this!
272         ofs = len(line_new) - len(line)
273         sc.select_start += ofs
274         sc.select_end += ofs
275     except:
276         # unlikely, but this can happen with unicode errors for example.
277         # or if the api attribute access its self causes an error.
278         import traceback
279         scrollback_error = traceback.format_exc()
280
281     if _BPY_MAIN_OWN:
282         sys.modules["__main__"] = main_mod_back
283
284     # Separate autocomplete output by command prompts
285     if scrollback != '':
286         bpy.ops.console.scrollback_append(text=sc.prompt + current_line.body,
287                                           type='INPUT')
288
289     # Now we need to copy back the line from blender back into the
290     # text editor. This will change when we don't use the text editor
291     # anymore
292     if scrollback:
293         add_scrollback(scrollback, 'INFO')
294
295     if scrollback_error:
296         add_scrollback(scrollback_error, 'ERROR')
297
298     # restore the stdin
299     sys.stdin = stdin_backup
300
301     context.area.tag_redraw()
302
303     return {'FINISHED'}
304
305
306 def copy_as_script(context):
307     sc = context.space_data
308     lines = [
309         "import bpy",
310         "from bpy import data as D",
311         "from bpy import context as C",
312         "from mathutils import *",
313         "from math import *",
314         "",
315     ]
316
317     for line in sc.scrollback:
318         text = line.body
319         type = line.type
320
321         if type == 'INFO':  # ignore autocomp.
322             continue
323         if type == 'INPUT':
324             if text.startswith(PROMPT):
325                 text = text[len(PROMPT):]
326             elif text.startswith(PROMPT_MULTI):
327                 text = text[len(PROMPT_MULTI):]
328         elif type == 'OUTPUT':
329             text = "#~ " + text
330         elif type == 'ERROR':
331             text = "#! " + text
332
333         lines.append(text)
334
335     context.window_manager.clipboard = "\n".join(lines)
336
337     return {'FINISHED'}
338
339
340 def banner(context):
341     sc = context.space_data
342     version_string = sys.version.strip().replace('\n', ' ')
343
344     add_scrollback("PYTHON INTERACTIVE CONSOLE %s" % version_string, 'OUTPUT')
345     add_scrollback("", 'OUTPUT')
346     add_scrollback("Command History:     Up/Down Arrow", 'OUTPUT')
347     add_scrollback("Cursor:              Left/Right Home/End", 'OUTPUT')
348     add_scrollback("Remove:              Backspace/Delete", 'OUTPUT')
349     add_scrollback("Execute:             Enter", 'OUTPUT')
350     add_scrollback("Autocomplete:        Ctrl-Space", 'OUTPUT')
351     add_scrollback("Zoom:                Ctrl +/-, Ctrl-Wheel", 'OUTPUT')
352     add_scrollback("Builtin Modules:     bpy, bpy.data, bpy.ops, "
353                    "bpy.props, bpy.types, bpy.context, bpy.utils, "
354                    "bgl, blf, mathutils",
355                    'OUTPUT')
356     add_scrollback("Convenience Imports: from mathutils import *; "
357                    "from math import *", 'OUTPUT')
358     add_scrollback("Convenience Variables: C = bpy.context, D = bpy.data",
359                    'OUTPUT')
360     add_scrollback("", 'OUTPUT')
361     sc.prompt = PROMPT
362
363     return {'FINISHED'}
364
365
366 # workaround for readline crashing, see: T43491
367 def _readline_bypass():
368     if "rlcompleter" in sys.modules or "readline" in sys.modules:
369         return
370
371     # prevent 'rlcompleter' from loading the real 'readline' module.
372     sys.modules["readline"] = None
373     import rlcompleter
374     del sys.modules["readline"]