FBX exporter conversion almost done.
[blender.git] / release / ui / space_console.py
1
2 import bpy
3
4 class CONSOLE_HT_header(bpy.types.Header):
5         __space_type__ = "CONSOLE"
6         __idname__ = "CONSOLE_HT_header"
7
8         def draw(self, context):
9                 sc = context.space_data
10                 # text = sc.text
11                 layout = self.layout
12
13                 layout.template_header()
14
15                 if context.area.show_menus:
16                         row = layout.row()
17                         row.itemM("CONSOLE_MT_console")
18                 
19                 row = layout.row()
20                 row.scale_x = 0.9
21                 row.itemR(sc, "type", expand=True)
22                 if sc.type == 'REPORT':
23                         row.itemR(sc, "show_report_debug")
24                         row.itemR(sc, "show_report_info")
25                         row.itemR(sc, "show_report_operator")
26                         row.itemR(sc, "show_report_warn")
27                         row.itemR(sc, "show_report_error")
28
29
30 class CONSOLE_MT_console(bpy.types.Menu):
31         __space_type__ = "CONSOLE"
32         __label__ = "Console"
33
34         def draw(self, context):
35                 layout = self.layout
36                 sc = context.space_data
37
38                 layout.column()
39                 layout.itemO("CONSOLE_OT_clear")
40
41 def add_scrollback(text, text_type):
42         for l in text.split('\n'):
43                 bpy.ops.CONSOLE_OT_scrollback_append(text=l.replace('\t', '    '), type=text_type)
44
45 def get_console(console_id):
46         '''
47         helper function for console operators
48         currently each text datablock gets its own console - code.InteractiveConsole()
49         ...which is stored in this function.
50         
51         console_id can be any hashable type
52         '''
53         import sys, code, io
54         
55         try:    consoles = get_console.consoles
56         except:consoles = get_console.consoles = {}
57         
58         # clear all dead consoles, use text names as IDs
59         # TODO, find a way to clear IDs
60         '''
61         for console_id in list(consoles.keys()):
62                 if console_id not in bpy.data.texts:
63                         del consoles[id]
64         '''
65         
66         try:
67                 namespace, console, stdout, stderr = consoles[console_id]
68         except:
69                 namespace = {'__builtins__':__builtins__} # locals()
70                 namespace['bpy'] = bpy
71                 
72                 console = code.InteractiveConsole(namespace)
73                 
74                 if sys.version.startswith('2'):
75                         stdout = io.BytesIO()  # Py2x support
76                         stderr = io.BytesIO()
77                 else:
78                         stdout = io.StringIO()
79                         stderr = io.StringIO()
80         
81                 consoles[console_id]= namespace, console, stdout, stderr
82                 
83         return namespace, console, stdout, stderr
84
85 class CONSOLE_OT_exec(bpy.types.Operator):
86         '''
87         Operator documentatuon text, will be used for the operator tooltip and python docs.
88         '''
89         __label__ = "Console Execute"
90         __register__ = True
91         
92         # Both prompts must be the same length
93         PROMPT = '>>> ' 
94         PROMPT_MULTI = '... '
95         
96         # is this working???
97         '''
98         def poll(self, context):
99                 return (context.space_data.type == 'PYTHON')
100         ''' # its not :|
101         
102         def execute(self, context):
103                 import sys
104                 
105                 sc = context.space_data
106                 
107                 try:
108                         line = sc.history[-1].line
109                 except:
110                         return ('CANCELLED',)
111                 
112                 if sc.type != 'PYTHON':
113                         return ('CANCELLED',)
114                 
115                 namespace, console, stdout, stderr = get_console(hash(context.region))
116                 
117                 # redirect output
118                 sys.stdout = stdout
119                 sys.stderr = stderr
120                 
121                 # run the console
122                 if not line.strip():
123                         line_exec = '\n' # executes a multiline statement
124                 else:
125                         line_exec = line
126                 
127                 is_multiline = console.push(line_exec)
128                 
129                 stdout.seek(0)
130                 stderr.seek(0)
131                 
132                 output = stdout.read()
133                 output_err = stderr.read()
134         
135                 # cleanup
136                 sys.stdout = sys.__stdout__
137                 sys.stderr = sys.__stderr__
138                 sys.last_traceback = None
139                 
140                 # So we can reuse, clear all data
141                 stdout.truncate(0)
142                 stderr.truncate(0)
143                 
144                 bpy.ops.CONSOLE_OT_scrollback_append(text = sc.prompt+line, type='INPUT')
145                 
146                 if is_multiline:        sc.prompt = self.PROMPT_MULTI
147                 else:                           sc.prompt = self.PROMPT
148                 
149                 # insert a new blank line
150                 bpy.ops.CONSOLE_OT_history_append(text="", current_character=0)
151                 
152                 # Insert the output into the editor
153                 # not quite correct because the order might have changed, but ok 99% of the time.
154                 if output:                      add_scrollback(output, 'OUTPUT')
155                 if output_err:          add_scrollback(output_err, 'ERROR')
156                 
157                 
158                 return ('FINISHED',)
159
160
161 def autocomp(bcon):
162         '''
163         This function has been taken from a BGE console autocomp I wrote a while ago
164         the dictionaty bcon is not needed but it means I can copy and paste from the old func
165         which works ok for now.
166         
167         could be moved into its own module.
168         '''
169         
170         
171         def is_delimiter(ch):
172                 '''
173                 For skipping words
174                 '''
175                 if ch == '_':
176                         return False
177                 if ch.isalnum():
178                         return False
179                 
180                 return True
181         
182         def is_delimiter_autocomp(ch):
183                 '''
184                 When autocompleteing will earch back and 
185                 '''
186                 if ch in '._[] "\'':
187                         return False
188                 if ch.isalnum():
189                         return False
190                 
191                 return True
192
193         
194         def do_autocomp(autocomp_prefix, autocomp_members):
195                 '''
196                 return text to insert and a list of options
197                 '''
198                 autocomp_members = [v for v in autocomp_members if v.startswith(autocomp_prefix)]
199                 
200                 print("AUTO: '%s'" % autocomp_prefix)
201                 print("MEMBERS: '%s'" % str(autocomp_members))
202                 
203                 if not autocomp_prefix:
204                         return '', autocomp_members
205                 elif len(autocomp_members) > 1:
206                         # find a common string between all members after the prefix 
207                         # 'ge' [getA, getB, getC] --> 'get'
208                         
209                         # get the shortest member
210                         min_len = min([len(v) for v in autocomp_members])
211                         
212                         autocomp_prefix_ret = ''
213                         
214                         for i in range(len(autocomp_prefix), min_len):
215                                 char_soup = set()
216                                 for v in autocomp_members:
217                                         char_soup.add(v[i])
218                                 
219                                 if len(char_soup) > 1:
220                                         break
221                                 else:
222                                         autocomp_prefix_ret += char_soup.pop()
223                                 
224                         print(autocomp_prefix_ret)
225                         return autocomp_prefix_ret, autocomp_members
226                 elif len(autocomp_members) == 1:
227                         return autocomp_members[0][len(autocomp_prefix):], []
228                 else:
229                         return '', []
230         
231
232         def BCon_PrevChar(bcon):
233                 cursor = bcon['cursor']-1
234                 if cursor<0:
235                         return None
236                         
237                 try:
238                         return bcon['edit_text'][cursor]
239                 except:
240                         return None
241                 
242                 
243         def BCon_NextChar(bcon):
244                 try:
245                         return bcon['edit_text'][bcon['cursor']]
246                 except:
247                         return None
248         
249         def BCon_cursorLeft(bcon):
250                 bcon['cursor'] -= 1
251                 if bcon['cursor'] < 0:
252                         bcon['cursor'] = 0
253
254         def BCon_cursorRight(bcon):
255                         bcon['cursor'] += 1
256                         if bcon['cursor'] > len(bcon['edit_text']):
257                                 bcon['cursor'] = len(bcon['edit_text'])
258         
259         def BCon_AddScrollback(bcon, text):
260                 
261                 bcon['scrollback'] = bcon['scrollback'] + text
262                 
263         
264         def BCon_cursorInsertChar(bcon, ch):
265                 if bcon['cursor']==0:
266                         bcon['edit_text'] = ch + bcon['edit_text']
267                 elif bcon['cursor']==len(bcon['edit_text']):
268                         bcon['edit_text'] = bcon['edit_text'] + ch
269                 else:
270                         bcon['edit_text'] = bcon['edit_text'][:bcon['cursor']] + ch + bcon['edit_text'][bcon['cursor']:]
271                         
272                 bcon['cursor'] 
273                 if bcon['cursor'] > len(bcon['edit_text']):
274                         bcon['cursor'] = len(bcon['edit_text'])
275                 BCon_cursorRight(bcon)
276         
277         
278         TEMP_NAME = '___tempname___'
279         
280         cursor_orig = bcon['cursor']
281         
282         ch = BCon_PrevChar(bcon)
283         while ch != None and (not is_delimiter(ch)):
284                 ch = BCon_PrevChar(bcon)
285                 BCon_cursorLeft(bcon)
286         
287         if ch != None:
288                 BCon_cursorRight(bcon)
289         
290         #print (cursor_orig, bcon['cursor'])
291         
292         cursor_base = bcon['cursor']
293         
294         autocomp_prefix = bcon['edit_text'][cursor_base:cursor_orig]
295         
296         print("PREFIX:'%s'" % autocomp_prefix)
297         
298         # Get the previous word
299         if BCon_PrevChar(bcon)=='.':
300                 BCon_cursorLeft(bcon)
301                 ch = BCon_PrevChar(bcon)
302                 while ch != None and is_delimiter_autocomp(ch)==False:
303                         ch = BCon_PrevChar(bcon)
304                         BCon_cursorLeft(bcon)
305                 
306                 cursor_new = bcon['cursor']
307                 
308                 if ch != None:
309                         cursor_new+=1
310                 
311                 pytxt = bcon['edit_text'][cursor_new:cursor_base-1].strip()
312                 print("AUTOCOMP EVAL: '%s'" % pytxt)
313                 #try:
314                 if pytxt:
315                         bcon['console'].runsource(TEMP_NAME + '=' + pytxt, '<input>', 'single')
316                         # print val
317                 else: ##except:
318                         val = None
319                 
320                 try:
321                         val = bcon['namespace'][TEMP_NAME]
322                         del bcon['namespace'][TEMP_NAME]
323                 except:
324                         val = None
325                 
326                 if val:
327                         autocomp_members = dir(val)
328                         
329                         autocomp_prefix_ret, autocomp_members = do_autocomp(autocomp_prefix, autocomp_members)
330                         
331                         bcon['cursor'] = cursor_orig
332                         for v in autocomp_prefix_ret:
333                                 BCon_cursorInsertChar(bcon, v)
334                         cursor_orig = bcon['cursor']
335                         
336                         if autocomp_members:
337                                 BCon_AddScrollback(bcon, ', '.join(autocomp_members))
338                 
339                 del val
340                 
341         else:
342                 # Autocomp global namespace
343                 autocomp_members = bcon['namespace'].keys()
344                 
345                 if autocomp_prefix:
346                         autocomp_members = [v for v in autocomp_members if v.startswith(autocomp_prefix)]
347                 
348                 autocomp_prefix_ret, autocomp_members = do_autocomp(autocomp_prefix, autocomp_members)
349                 
350                 bcon['cursor'] = cursor_orig
351                 for v in autocomp_prefix_ret:
352                         BCon_cursorInsertChar(bcon, v)
353                 cursor_orig = bcon['cursor']
354                 
355                 if autocomp_members:
356                         BCon_AddScrollback(bcon, ', '.join(autocomp_members))
357         
358         bcon['cursor'] = cursor_orig
359
360
361 class CONSOLE_OT_autocomplete(bpy.types.Operator):
362         '''
363         Operator documentatuon text, will be used for the operator tooltip and python docs.
364         '''
365         __label__ = "Console Autocomplete"
366         __register__ = True
367         
368         def poll(self, context):
369                 return context.space_data.type == 'PYTHON'
370         
371         def execute(self, context):
372                 
373                 sc = context.space_data
374                 
375                 namespace, console, stdout, stderr = get_console(hash(context.region))
376                 
377                 current_line = sc.history[-1]
378                 line = current_line.line
379                 
380                 if not console:
381                         return ('CANCELLED',)
382                 
383                 if sc.type != 'PYTHON':
384                         return ('CANCELLED',)
385                 
386                 # fake cursor, use for autocomp func.
387                 bcon = {}
388                 bcon['cursor'] = current_line.current_character
389                 bcon['console'] = console
390                 bcon['edit_text'] = line
391                 bcon['namespace'] = namespace
392                 bcon['scrollback'] = '' # nor from the BGE console
393                 
394                 
395                 # This function isnt aware of the text editor or being an operator
396                 # just does the autocomp then copy its results back
397                 autocomp(bcon)
398                 
399                 # Now we need to copy back the line from blender back into the text editor.
400                 # This will change when we dont use the text editor anymore
401                 if bcon['scrollback']:
402                         add_scrollback(bcon['scrollback'], 'INFO')
403                 
404                 # copy back
405                 current_line.line = bcon['edit_text']
406                 current_line.current_character = bcon['cursor']
407                 
408                 context.area.tag_redraw()
409                 
410                 return ('FINISHED',)
411
412
413
414 bpy.types.register(CONSOLE_HT_header)
415 bpy.types.register(CONSOLE_MT_console)
416
417 bpy.ops.add(CONSOLE_OT_exec)
418 bpy.ops.add(CONSOLE_OT_autocomplete)
419