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