Integrated Freestyle to rendering pipeline
[blender.git] / release / scripts / console.py
1 #!BPY
2
3 """
4 Name: 'Interactive Python Console'
5 Blender: 245
6 Group: 'System'
7 Tooltip: 'Interactive Python Console'
8 """
9
10 __author__ = "Campbell Barton aka ideasman42"
11 __url__ = ["www.blender.org", "blenderartists.org", "www.python.org"]
12 __bpydoc__ = """\
13 This is an interactive console, similar to Python's own command line interpreter.  Since it is embedded in Blender, it has access to all Blender Python modules.
14
15 Those completely new to Python are recommended to check the link button above
16 that points to its official homepage, with news, downloads and documentation.
17
18 Usage:<br>
19   Type your code and hit "Enter" to get it executed.<br>
20   - Right mouse click: Console Menu (Save output, etc);<br>
21   - Mousewheel: Scroll text
22   - Arrow keys: command history and cursor;<br>
23   - Shift + Backspace: Backspace whole word;<br>
24   - Shift + Arrow keys: jump words;<br>
25   - Ctrl + (+/- or mousewheel): Zoom text size;<br>
26   - Ctrl + Enter: auto compleate based on variable names and modules loaded -- multiple choices popup a menu;<br>
27   - Shift + Enter: multiline functions -- delays executing code until only Enter is pressed.
28 """
29
30 # -------------------------------------------------------------------------- 
31 # ***** BEGIN GPL LICENSE BLOCK ***** 
32
33 # This program is free software; you can redistribute it and/or 
34 # modify it under the terms of the GNU General Public License 
35 # as published by the Free Software Foundation; either version 2 
36 # of the License, or (at your option) any later version. 
37
38 # This program is distributed in the hope that it will be useful, 
39 # but WITHOUT ANY WARRANTY; without even the implied warranty of 
40 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
41 # GNU General Public License for more details. 
42
43 # You should have received a copy of the GNU General Public License 
44 # along with this program; if not, write to the Free Software Foundation, 
45 # Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 
46
47 # ***** END GPL LICENCE BLOCK ***** 
48 # -------------------------------------------------------------------------- 
49
50 import Blender
51 import bpy
52 from Blender import *
53 import sys as python_sys
54 import StringIO
55
56 # Constants
57 __DELIMETERS__ = '. ,=+-*/%<>&~][{}():\t'
58 __VARIABLE_DELIMETERS__ = ' ,=+-*/%<>&~{}():\t'
59
60 __LINE_HISTORY__ = 500
61
62 global __FONT_SIZE__
63
64 __FONT_SIZES__ = ( ('tiny', 10), ('small', 12), ('normalfix', 14), ('large', 16) )
65 __FONT_SIZE__ = 2 # index for the list above, normal default.
66
67 global __CONSOLE_LINE_OFFSET__
68 __CONSOLE_LINE_OFFSET__ = 0
69
70 cmdBuffer = [] # dosnt need to be global
71
72 '''
73 # Generic Blender functions
74 def getActScriptWinRect():
75         area = Window.GetAreaSize()
76         area = (area[0]-1, area[1]-1)
77         for scrInfo in Window.GetScreenInfo(Window.Types['SCRIPT'], 'win', ''):
78                 if scrInfo['vertices'][2]-scrInfo['vertices'][0] == area[0]:
79                         if scrInfo['vertices'][3]-scrInfo['vertices'][1] == area[1]:
80                                 return scrInfo['vertices']
81         return None
82 '''
83
84
85 # menuText, # per group
86 def PupMenuLess(menu, groupSize=35):
87         more = ['   more...']
88         less = ['   less...']
89         
90         menuList= menu.split('|')
91         
92         # No Less Needed, just call.
93         if len(menuList) < groupSize:
94                 return Draw.PupMenu(menu)
95         
96         title = menuList[0].split('%t')[0]
97         
98         # Split the list into groups
99         menuGroups = [[]]
100         for li in menuList[1:]:
101                 if len(menuGroups[-1]) < groupSize:
102                         menuGroups[-1].append(li)
103                 else:
104                         menuGroups.append([li])
105         
106         # Stores teh current menu group we are looking at
107         groupIdx = 0
108         while 1:
109                 # Give us a title with the menu number
110                 numTitle = [ ' '.join([title, str(groupIdx + 1), 'of', str(len(menuGroups)), '%t'])]
111                 if groupIdx == 0:
112                         menuString = '|'.join(numTitle + menuGroups[groupIdx] + more)
113                 elif groupIdx == len(menuGroups)-1:
114                         menuString = '|'.join(numTitle + less + menuGroups[groupIdx])
115                 else: # In the middle somewhere so Show a more and less
116                         menuString = '|'.join(numTitle + less + menuGroups[groupIdx] + more)
117                 result = Draw.PupMenu(menuString)
118                 # User Exit
119                 if result == -1:
120                         return -1
121                 
122                 if groupIdx == 0: # First menu
123                         if result-1 < groupSize:
124                                 return result
125                         else: # must be more
126                                 groupIdx +=1
127                 elif groupIdx == len(menuGroups): # Last Menu
128                         if result == 1: # Must be less
129                                 groupIdx -= 1
130                         else: # Must be a choice
131                                 return result + (groupIdx*groupSize)
132                         
133                 else:   
134                         if result == 1: # Must be less
135                                 groupIdx -= 1
136                         elif result-2 == groupSize:
137                                 groupIdx +=1
138                         else:
139                                 return result - 1 + (groupIdx*groupSize)
140                                 
141
142
143 # Use newstyle classes, Im not bothering with inheretence
144 # but slots are faster.
145 class cmdLine(object):
146         __slots__ = [\
147         'cmd', # is the command string, or any other message
148         'type',# type: 0:user input  1:program feedback  2:error message.  3:option feedback
149         'exe' #  0- not yet executed   1:executed
150         ]
151         def __init__(self, cmd, type, exe):
152                 self.cmd = cmd
153                 self.type = type
154                 self.exe = exe
155
156 # Include external file with internal namespace
157 def include(filename):
158         file = open(filename, 'r')
159         filedata = file.read()
160         file.close()
161         return compile(filedata, filename, 'exec')
162
163 # Writes command line data to a blender text file.
164 def writeCmdData(type):
165         newText = Text.New('command_output.py', 1)
166         if type == 3:   newText.write('\n'.join( [ myCmd.cmd for myCmd in cmdBuffer ] ))
167         else:                   newText.write('\n'.join( [ myCmd.cmd for myCmd in cmdBuffer if myCmd.type is type] ))
168         Draw.PupMenu('%s written' % newText.name)
169
170 def insertCmdData():
171         texts = list(bpy.data.texts)
172         textNames = [tex.name for tex in texts]
173         if textNames:
174                 choice = Draw.PupMenu('|'.join(textNames))
175                 if choice != -1:
176                         text = texts[choice-1]
177                         
178                         # Add the text!
179                         for l in text.asLines():
180                                 cmdBuffer.append(cmdLine('%s ' % l, 0, 0))
181                         Draw.Redraw()
182         
183
184 COLLECTED_VAR_NAMES = {} # a list of keys, each key has a list of absolute paths
185
186 # Pain and simple recursice dir(), accepts a string
187 unused_types = str, dict, list, float, int, str, type, tuple, type(dir), type(None)
188 def rdir(dirString, depth=0):
189         #print ' ' * depth, dirString
190         # MAX DEPTH SET HERE
191         if depth > 5:
192                 # print 'maxdepoth reached.'
193                 return
194                 
195         global COLLECTED_VAR_NAMES
196         dirStringSplit = dirString.split('.')
197         
198         exec('value=' + dirString)      
199         
200         if type(value) in unused_types:
201                 # print 'bad type'
202                 return
203         dirList = dir(value)
204         
205         for dirItem in dirList:
206                 if dirItem.startswith('_'):
207                         continue
208                         
209                 dirData = None
210                 try:
211                         # Rare cases this can mess up, material.shader was a problem.
212                         exec('dirData = %s.%s' % (dirString, dirItem))
213                         #print dirData
214                 except:
215                         # Dont bother with this data.
216                         continue
217                 #print  'HEY', dirData, dirItem
218                 #if type(dirItem) != str:
219                 #       print dirItem, type(dirItem)
220                 
221                 if dirItem not in COLLECTED_VAR_NAMES: # .keys()
222                         COLLECTED_VAR_NAMES[dirItem] = []
223                 
224                 # Add the string
225                 # splitD = dirString.split('"')[-2]
226                 
227                 # Example of dirString
228                 # __CONSOLE_VAR_DICT__["Main"].scenes.active.render
229                 
230                 # Works but can be faster
231                 # splitD = dirString.replace('__CONSOLE_VAR_DICT__["', '').replace('"]', '')
232                 
233                 splitD = dirString[22:].replace('"]', '')
234                 
235                 if splitD not in COLLECTED_VAR_NAMES[dirItem]:
236                         # print dirItem, dirString, splitD,
237                         COLLECTED_VAR_NAMES[dirItem].append(splitD)
238                 
239                 
240                 # Stops recursice stuff, overlooping
241                 #print type(dirItem)
242                 #if type(dirData) == types.ClassType or \
243                 #        type(dirData) == types.ModuleType:
244                 type_dirData = type(dirData)
245                 if type_dirData not in unused_types:
246                         # print type(dirData), dirItem
247                         # Dont loop up dirs for strings ints etc.
248                         if dirItem not in dirStringSplit:
249                                 rdir( '%s.%s' % (dirString, dirItem), depth+1)
250                 '''
251                 elif depth == 0: # Add local variables
252                         # print type(dirData), dirItem
253                         # Dont loop up dirs for strings ints etc.
254                         if dirItem not in dirStringSplit:
255                                 rdir( '%s.%s' % (dirString, dirItem), depth+1)
256                 '''
257
258 def recursive_dir():
259         global COLLECTED_VAR_NAMES
260         
261         for name in __CONSOLE_VAR_DICT__: # .keys()
262                 if not name.startswith('_'): # Dont pick names like __name__
263                         rdir('__CONSOLE_VAR_DICT__["%s"]' % name)
264                         #print COLLECTED_VAR_NAMES
265                         COLLECTED_VAR_NAMES[name] = [''] 
266         return COLLECTED_VAR_NAMES
267
268 # Runs the code line(s) the user has entered and handle errors
269 # As well as feeding back the output into the blender window.
270 def runUserCode(__USER_CODE_STRING__):
271         global __CONSOLE_VAR_DICT__ # We manipulate the variables here. loading and saving from localspace to this global var.
272         
273         # Open A File like object to write all output to, that would useually be printed. 
274         python_sys.stdout.flush() # Get rid of whatever came before
275         __FILE_LIKE_STRING__ = StringIO.StringIO() # make a new file like string, this saves up from making a file.
276         __STD_OUTPUT__ = python_sys.stdout # we need to store the normal output.
277         python_sys.stdout=__FILE_LIKE_STRING__ # Now anything printed will be written to the file like string.
278         
279         # Try and run the user entered line(s)
280         try:
281                 # Load all variabls from global dict to local space.
282                 __TMP_VAR_NAME__ = __TMP_VAR__ = '' # so as not to raise an error when del'ing
283
284                 for __TMP_VAR_NAME__, __TMP_VAR__ in __CONSOLE_VAR_DICT__.items():
285                         exec('%s%s' % (__TMP_VAR_NAME__,'=__TMP_VAR__'))
286                 del __TMP_VAR_NAME__
287                 del __TMP_VAR__
288                 
289                 # Now all the vars are loaded, execute the code. # Newline thanks to phillip,
290                 exec(compile(__USER_CODE_STRING__, 'blender_cmd.py', 'single')) #exec(compile(__USER_CODE_STRING__, 'blender_cmd.py', 'exec'))
291                 
292                 # Flush global dict, allow the user to remove items.
293                 __CONSOLE_VAR_DICT__ = {}
294
295                 __TMP_VAR_NAME__ = '' # so as not to raise an error when del'ing        
296                 # Write local veriables to global __CONSOLE_VAR_DICT__
297                 for __TMP_VAR_NAME__ in dir():
298                         if      __TMP_VAR_NAME__ != '__FILE_LIKE_STRING__' and\
299                                         __TMP_VAR_NAME__ != '__STD_OUTPUT__' and\
300                                         __TMP_VAR_NAME__ != '__TMP_VAR_NAME__' and\
301                                         __TMP_VAR_NAME__ != '__USER_CODE_STRING__':
302                                 
303                                 # Execute the local > global coversion.
304                                 exec('%s%s' % ('__CONSOLE_VAR_DICT__[__TMP_VAR_NAME__]=', __TMP_VAR_NAME__))
305                 del __TMP_VAR_NAME__
306         
307         except: # Prints the REAL exception.
308                 error = '%s:  %s' % (python_sys.exc_type, python_sys.exc_value)         
309                 for errorLine in error.split('\n'):
310                         cmdBuffer.append(cmdLine(errorLine, 2, None)) # new line to type into
311         
312         python_sys.stdout = __STD_OUTPUT__ # Go back to output to the normal blender console
313         
314         # Copy all new output to cmdBuffer
315         
316         __FILE_LIKE_STRING__.seek(0) # the readline function requires that we go back to the start of the file.
317         
318         for line in __FILE_LIKE_STRING__.readlines():
319                 cmdBuffer.append(cmdLine(line, 1, None))
320                 
321         cmdBuffer.append(cmdLine(' ', 0, 0)) # new line to type into
322         python_sys.stdout=__STD_OUTPUT__
323         __FILE_LIKE_STRING__.close()
324
325
326
327
328
329 #------------------------------------------------------------------------------#
330 #                             event handling code                              #
331 #------------------------------------------------------------------------------#
332 def handle_event(evt, val):
333         
334         # Insert Char into the cammand line
335         def insCh(ch): # Instert a char
336                 global cursor
337                 # Later account for a cursor variable
338                 cmdBuffer[-1].cmd = ('%s%s%s' % ( cmdBuffer[-1].cmd[:cursor], ch, cmdBuffer[-1].cmd[cursor:]))
339         
340         #------------------------------------------------------------------------------#
341         #                        Define Complex Key Actions                            #
342         #------------------------------------------------------------------------------#
343         def actionEnterKey():
344                 global histIndex, cursor
345                 
346                 def getIndent(string):
347                         # Gather white space to add in the previous line
348                         # Ignore the last char since its padding.
349                         whiteSpace = ''
350                         #for i in range(len(cmdBuffer[-1].cmd)):
351                         for i in xrange(len(string)-1):
352                                 if cmdBuffer[-1].cmd[i] == ' ' or cmdBuffer[-1].cmd[i] == '\t':
353                                         whiteSpace += string[i]
354                                 else:
355                                         break
356                         return whiteSpace
357                 
358                 # Autocomplete
359                 if Window.GetKeyQualifiers() & Window.Qual.CTRL:
360                         actionAutoCompleate()
361                         return
362                 
363                 # Are we in the middle of a multiline part or not?
364                 # try be smart about it
365                 if cmdBuffer[-1].cmd.split('#')[0].rstrip().endswith(':'):
366                         # : indicates an indent is needed
367                         cmdBuffer.append(cmdLine('\t%s ' % getIndent(cmdBuffer[-1].cmd), 0, 0))
368                         print ': indicates an indent is needed'         
369                 
370                 elif cmdBuffer[-1].cmd[0] in [' ', '\t'] and len(cmdBuffer[-1].cmd) > 1 and cmdBuffer[-1].cmd.split():
371                         # white space at the start means he havnt finished the multiline.
372                         cmdBuffer.append(cmdLine('%s ' % getIndent(cmdBuffer[-1].cmd), 0, 0))
373                         print 'white space at the start means he havnt finished the multiline.'
374                 
375                 elif Window.GetKeyQualifiers() & Window.Qual.SHIFT:
376                         # Crtl forces multiline
377                         cmdBuffer.append(cmdLine('%s ' % getIndent(cmdBuffer[-1].cmd), 0, 0))
378                         print 'Crtl forces multiline'                   
379                 
380                 else: # Execute multiline code block
381                         
382                         # Multiline code will still run with 1 line,
383                         multiLineCode = ['if 1:'] # End of the multiline first.
384                         
385                         # Seek the start of the file multiline
386                         i = 1
387                         while cmdBuffer[-i].exe == 0:
388                                 i+=1
389                         
390                         while i > 1:
391                                 i-=1
392                                 
393                                 if cmdBuffer[-i].cmd == ' ':# Tag as an output type so its not used in the key history
394                                         cmdBuffer[-i].type = 1
395                                 else: # Tab added at the start for added if 1: statement
396                                         multiLineCode.append('\t%s' % cmdBuffer[-i].cmd )
397                                 
398                                 # Mark as executed
399                                 cmdBuffer[-i].exe = 1                           
400                                 
401                         multiLineCode.append('\tpass') # reverse will make this the start.                      
402                         
403                         # Dubug, print the code that is executed.
404                         #for m in multiLineCode: print m
405                         
406                         runUserCode('\n'.join(multiLineCode))
407                         
408                         # Clear the output based on __LINE_HISTORY__
409                         if len(cmdBuffer) > __LINE_HISTORY__:
410                                 cmdBuffer[:__LINE_HISTORY__] = []
411                 
412                 histIndex = cursor = -1 # Reset cursor and history
413         
414         def actionUpKey():
415                 global histIndex
416                 if abs(histIndex)+1 >= len(cmdBuffer):
417                         histIndex = -1
418                         
419                         # When wrapping allow 1 plank lines
420                         if cmdBuffer[-1].cmd != ' ':
421                                 cmdBuffer[-1].cmd = ' '
422                                 return
423                 
424                 histIndex_orig = histIndex
425                 histIndex -= 1
426                 
427                 while   (cmdBuffer[histIndex].type != 0 and abs(histIndex) < len(cmdBuffer)) or \
428                                 ( cmdBuffer[histIndex].cmd == cmdBuffer[histIndex_orig].cmd):
429                         histIndex -= 1
430                         
431                 if cmdBuffer[histIndex].type == 0: # we found one
432                         cmdBuffer[-1].cmd = cmdBuffer[histIndex].cmd                    
433         
434         def actionDownKey():
435                 global histIndex
436                 if histIndex >= -2:
437                         histIndex = -len(cmdBuffer)
438                         
439                         # When wrapping allow 1 plank lines
440                         if cmdBuffer[-1].cmd != ' ':
441                                 cmdBuffer[-1].cmd = ' '
442                                 return
443                         
444                 histIndex_orig = histIndex
445                 histIndex += 1
446                 while   (cmdBuffer[histIndex].type != 0 and histIndex != -2) or \
447                                 ( cmdBuffer[histIndex].cmd == cmdBuffer[histIndex_orig].cmd):
448                         
449                         histIndex += 1
450                         
451                 if cmdBuffer[histIndex].type == 0: # we found one
452                         cmdBuffer[-1].cmd = cmdBuffer[histIndex].cmd
453         
454         def actionRightMouse():
455                 global __FONT_SIZE__
456                 choice = Draw.PupMenu('Console Menu%t|Write Input Data (white)|Write Output Data (blue)|Write Error Data (red)|Write All Text|%l|Insert Blender text|%l|Font Size|%l|Clear Output|Quit')
457                 
458                 if choice == 1:
459                         writeCmdData(0) # type 0 user
460                 elif choice == 2:
461                         writeCmdData(1) # type 1 user output
462                 elif choice == 3:
463                         writeCmdData(2) # type 2 errors
464                 elif choice == 4:
465                         writeCmdData(3) # All
466                 elif choice == 6:
467                         insertCmdData() # Insert text from Blender and run it.
468                 elif choice == 8:
469                         # Fontsize.
470                         font_choice = Draw.PupMenu('Font Size%t|Large|Normal|Small|Tiny')
471                         if font_choice != -1:
472                                 if font_choice == 1:
473                                         __FONT_SIZE__ = 3
474                                 elif font_choice == 2:
475                                         __FONT_SIZE__ = 2
476                                 elif font_choice == 3:
477                                         __FONT_SIZE__ = 1
478                                 elif font_choice == 4:
479                                         __FONT_SIZE__ = 0
480                                 Draw.Redraw()
481                 elif choice == 10: # Clear all output
482                         cmdBuffer[:] = [cmd for cmd in cmdBuffer if cmd.type == 0] # keep user input
483                         Draw.Redraw()
484                 elif choice == 11: # Exit
485                         Draw.Exit()
486         
487         
488         # Auto compleating, quite complex- use recutsice dir for the moment.
489         def actionAutoCompleate(): # Ctrl + Tab
490                 if not cmdBuffer[-1].cmd[:cursor].split():
491                         return
492                 
493                 
494                 RECURSIVE_DIR = recursive_dir()
495                 
496                 # get last name of user input
497                 editVar = cmdBuffer[-1].cmd[:cursor]
498                 # Split off spaces operators etc from the staryt of the command so we can use the startswith function.
499                 for splitChar in __VARIABLE_DELIMETERS__:
500                         editVar = editVar[:-1].split(splitChar)[-1] + editVar[-1]
501                 
502                 
503                 # Now we should have the var by its self
504                 if editVar:
505                         possibilities = []
506                         
507                         for __TMP_VAR_NAME__ in RECURSIVE_DIR: #.keys():
508                                 #print '\t', __TMP_VAR_NAME__
509                                 if __TMP_VAR_NAME__ == editVar:
510                                         # print 'ADITVAR IS A VAR'
511                                         pass
512                                 '''
513                                 elif __TMP_VAR_NAME__.startswith( editVar ):
514                                         print __TMP_VAR_NAME__, 'aaa'
515                                         possibilities.append( __TMP_VAR_NAME__ )
516                                 '''
517                                 possibilities.append( __TMP_VAR_NAME__ )
518                         
519                         if len(possibilities) == 1:
520                                 cmdBuffer[-1].cmd = ('%s%s%s' % (cmdBuffer[-1].cmd[:cursor - len(editVar)], possibilities[0], cmdBuffer[-1].cmd[cursor:]))    
521                         
522                         elif possibilities: # If its not just []
523                                 # -1 with insert is the second last.
524                                 
525                                 # Text choice
526                                 #cmdBuffer.insert(-1, cmdLine('options: %s' % ' '.join(possibilities), 3, None))
527                                 
528                                 menuList = [] # A lits of tuples- ABSOLUTE, RELATIVE
529                                 
530                                 for __TMP_VAR_NAME__ in possibilities:
531                                         for usage in RECURSIVE_DIR[__TMP_VAR_NAME__]:
532                                                 # Account for non absolute (variables for eg.)
533                                                 if usage: # not ''
534                                                         absName = '%s.%s' % (usage, __TMP_VAR_NAME__)
535                                                         
536                                                         if __TMP_VAR_NAME__.startswith(editVar):
537                                                                 menuList.append( # Used for names and can be entered when pressing shift.
538                                                                   (absName, # Absolute name
539                                                                   __TMP_VAR_NAME__) # Relative name, non shift
540                                                                   )
541                                                 
542                                                 #else:
543                                                 #       if absName.find(editVar) != -1:
544                                                 #               menuList.append((__TMP_VAR_NAME__, __TMP_VAR_NAME__)) # Used for names and can be entered when pressing shift.
545                                 
546                                 # No items to display? no menu
547                                 if not menuList:
548                                         return
549                                         
550                                 menuList.sort()
551                                 
552                                 choice = PupMenuLess( # Menu for the user to choose the autocompleate
553                                 'Choices (Shift for local name, Ctrl for Docs)%t|' + # Title Text
554                                 '|'.join(['%s,  %s' % m for m in menuList])) # Use Absolute names m[0]
555                                 
556                                 if choice != -1:
557                                         if Window.GetKeyQualifiers() & Window.Qual.CTRL:  # Help
558                                                 cmdBuffer[-1].cmd = ('help(%s%s) ' % (cmdBuffer[-1].cmd[:cursor - len(editVar)], menuList[choice-1][0]))    
559                                         elif Window.GetKeyQualifiers() & Window.Qual.SHIFT:  # Put in the long name
560                                                 cmdBuffer[-1].cmd = ('%s%s%s' % (cmdBuffer[-1].cmd[:cursor - len(editVar)], menuList[choice-1][1], cmdBuffer[-1].cmd[cursor:]))    
561                                         else: # Only paste in the Short name
562                                                 cmdBuffer[-1].cmd = ('%s%s%s' % (cmdBuffer[-1].cmd[:cursor - len(editVar)], menuList[choice-1][0], cmdBuffer[-1].cmd[cursor:]))    
563                                                 
564                                                 
565                 else:
566                         # print 'NO EDITVAR'
567                         return
568                 
569         # ------------------end------------------ #
570         
571         # Quit from menu only
572         #if (evt == Draw.ESCKEY and not val):
573         #       Draw.Exit()
574         if evt == Draw.MOUSEX or evt == Draw.MOUSEY: # AVOID TOO MANY REDRAWS.
575                 return  
576         
577         
578         global cursor
579         global histIndex        
580         global __FONT_SIZE__
581         global __CONSOLE_LINE_OFFSET__
582         
583         ascii = Blender.event
584         
585         resetScroll = True
586         
587         #------------------------------------------------------------------------------#
588         #                             key codes and key handling                       #
589         #------------------------------------------------------------------------------#
590         
591         # UP DOWN ARROW KEYS, TO TRAVERSE HISTORY
592         if (evt == Draw.UPARROWKEY and val): actionUpKey()
593         elif (evt == Draw.DOWNARROWKEY and val): actionDownKey()
594         
595         elif (evt == Draw.RIGHTARROWKEY and val):
596                 if Window.GetKeyQualifiers() & Window.Qual.SHIFT:
597                         wordJump = False
598                         newCursor = cursor+1
599                         while newCursor<0:
600                                 
601                                 if cmdBuffer[-1].cmd[newCursor] not in __DELIMETERS__:
602                                         newCursor+=1
603                                 else:
604                                         wordJump = True
605                                         break
606                         if wordJump: # Did we find a new cursor pos?
607                                 cursor = newCursor
608                         else:
609                                 cursor = -1 # end of line
610                 else:
611                         cursor +=1
612                         if cursor > -1:
613                                 cursor = -1
614         
615         elif (evt == Draw.LEFTARROWKEY and val):
616                 if Window.GetKeyQualifiers() & Window.Qual.SHIFT:
617                         wordJump = False
618                         newCursor = cursor-1
619                         while abs(newCursor) < len(cmdBuffer[-1].cmd):
620                                 
621                                 if cmdBuffer[-1].cmd[newCursor] not in __DELIMETERS__ or\
622                                 newCursor == cursor:
623                                         newCursor-=1
624                                 else:
625                                         wordJump = True
626                                         break
627                         if wordJump: # Did we find a new cursor pos?
628                                 cursor = newCursor
629                         else: 
630                                 cursor = -len(cmdBuffer[-1].cmd) # Start of line
631                         
632                 else:
633                         if len(cmdBuffer[-1].cmd) > abs(cursor):
634                                 cursor -=1
635         
636         elif (evt == Draw.HOMEKEY and val):
637                 cursor  = -len(cmdBuffer[-1].cmd)
638         
639         elif (evt == Draw.ENDKEY and val):
640                 cursor = -1
641         
642         elif (evt == Draw.TABKEY and val):
643                 insCh('\t')     
644         
645         elif (evt == Draw.BACKSPACEKEY and val):
646                 if Window.GetKeyQualifiers() & Window.Qual.SHIFT:
647                         i = -1
648                         for d in __DELIMETERS__:
649                                 i = max(i, cmdBuffer[-1].cmd[:cursor-1].rfind(d))
650                         if i == -1:
651                                 i=0
652                         cmdBuffer[-1].cmd = ('%s%s' % (cmdBuffer[-1].cmd[:i] , cmdBuffer[-1].cmd[cursor:]))
653                         
654                 else:
655                         # Normal backspace.
656                         cmdBuffer[-1].cmd = ('%s%s' % (cmdBuffer[-1].cmd[:cursor-1] , cmdBuffer[-1].cmd[cursor:]))
657                         
658         elif (evt == Draw.DELKEY and val) and cursor < -1:
659                 cmdBuffer[-1].cmd = cmdBuffer[-1].cmd[:cursor] + cmdBuffer[-1].cmd[cursor+1:]
660                 cursor +=1
661         
662         elif ((evt == Draw.RETKEY or evt == Draw.PADENTER) and val):
663                 actionEnterKey()
664                         
665         elif (evt == Draw.RIGHTMOUSE and not val): actionRightMouse(); return
666         
667         elif (evt == Draw.PADPLUSKEY or evt == Draw.EQUALKEY or evt == Draw.WHEELUPMOUSE) and val and Window.GetKeyQualifiers() & Window.Qual.CTRL:
668                 __FONT_SIZE__ += 1
669                 __FONT_SIZE__ = min(len(__FONT_SIZES__)-1, __FONT_SIZE__)
670         elif (evt == Draw.PADMINUS or evt == Draw.MINUSKEY or evt == Draw.WHEELDOWNMOUSE) and val and Window.GetKeyQualifiers() & Window.Qual.CTRL:
671                 __FONT_SIZE__ -=1
672                 __FONT_SIZE__ = max(0, __FONT_SIZE__)
673         
674         
675         elif evt == Draw.WHEELUPMOUSE and val:
676                 __CONSOLE_LINE_OFFSET__ += 1
677                 __CONSOLE_LINE_OFFSET__ = min(len(cmdBuffer)-2, __CONSOLE_LINE_OFFSET__)
678                 resetScroll = False
679                 
680         elif evt == Draw.WHEELDOWNMOUSE and val:
681                 __CONSOLE_LINE_OFFSET__ -= 1
682                 __CONSOLE_LINE_OFFSET__ = max(0, __CONSOLE_LINE_OFFSET__)
683                 resetScroll = False
684         
685
686         elif ascii:
687                 insCh(chr(ascii))
688         else:
689                 return # dont redraw.
690         
691         # If the user types in anything then scroll to bottom.
692         if resetScroll:
693                 __CONSOLE_LINE_OFFSET__ = 0
694         Draw.Redraw()
695
696
697 def draw_gui():
698         # Get the bounds from ObleGL directly
699         __CONSOLE_RECT__ = BGL.Buffer(BGL.GL_FLOAT, 4)
700         BGL.glGetFloatv(BGL.GL_SCISSOR_BOX, __CONSOLE_RECT__) 
701         __CONSOLE_RECT__= __CONSOLE_RECT__.list
702         
703         # Clear the screen
704         BGL.glClearColor(0.0, 0.0, 0.0, 1.0)
705         BGL.glClear(BGL.GL_COLOR_BUFFER_BIT)         # use it to clear the color buffer
706         
707         
708         # Fixed margin. use a margin since 0 margin can be hard to seewhen close to a crt's edge.
709         margin = 4
710         
711         # Convenience
712         FNT_NAME, FNT_HEIGHT = __FONT_SIZES__[__FONT_SIZE__]
713         
714         # Draw cursor location colour
715         if __CONSOLE_LINE_OFFSET__ == 0:
716                 cmd2curWidth = Draw.GetStringWidth(cmdBuffer[-1].cmd[:cursor], FNT_NAME)
717                 BGL.glColor3f(0.8, 0.2, 0.2)
718                 if cmd2curWidth == 0:
719                         BGL.glRecti(margin,2,margin+2, FNT_HEIGHT+2)
720                 else:
721                         BGL.glRecti(margin + cmd2curWidth-2,2, margin+cmd2curWidth, FNT_HEIGHT+2)
722         
723         BGL.glColor3f(1,1,1)
724         # Draw the set of cammands to the buffer
725         consoleLineIdx = __CONSOLE_LINE_OFFSET__ + 1
726         wrapLineIndex = 0
727         while consoleLineIdx < len(cmdBuffer) and  __CONSOLE_RECT__[3] > (consoleLineIdx - __CONSOLE_LINE_OFFSET__) * FNT_HEIGHT:
728                 if cmdBuffer[-consoleLineIdx].type == 0:
729                         BGL.glColor3f(1, 1, 1)
730                 elif cmdBuffer[-consoleLineIdx].type == 1:
731                         BGL.glColor3f(.3, .3, 1)
732                 elif cmdBuffer[-consoleLineIdx].type == 2:
733                         BGL.glColor3f(1.0, 0, 0)
734                 elif cmdBuffer[-consoleLineIdx].type == 3:
735                         BGL.glColor3f(0, 0.8, 0)
736                 else:  
737                         BGL.glColor3f(1, 1, 0)
738                 
739                 if consoleLineIdx == 1: # user input
740                         BGL.glRasterPos2i(margin, (FNT_HEIGHT * (consoleLineIdx-__CONSOLE_LINE_OFFSET__)) - 8)
741                         Draw.Text(cmdBuffer[-consoleLineIdx].cmd, FNT_NAME)             
742                 else: # WRAP
743                         lwid = Draw.GetStringWidth(cmdBuffer[-consoleLineIdx].cmd, FNT_NAME)
744                         if margin + lwid >  __CONSOLE_RECT__[2]:
745                                 wrapLineList = []
746                                 wtext = cmdBuffer[-consoleLineIdx].cmd
747                                 wlimit = len(wtext)
748                                 chunksz = int(( __CONSOLE_RECT__[2] - margin ) / (lwid / len(wtext)))
749                                 lstart = 0
750                                 fsize = FNT_NAME
751                                 while lstart < wlimit:
752                                         lend = min(lstart+chunksz,wlimit)
753                                         ttext = wtext[lstart:lend]
754                                         while lend < wlimit and Draw.GetStringWidth(ttext, fsize) + margin < __CONSOLE_RECT__[2]:
755                                                 lend += 1
756                                                 ttext = wtext[lstart:lend]
757                                         while lend > lstart+1 and Draw.GetStringWidth(ttext, fsize) + margin > __CONSOLE_RECT__[2]:
758                                                 lend -= 1
759                                                 ttext = wtext[lstart:lend]
760                                         wrapLineList.append(ttext)
761                                         lstart = lend 
762                                 # Now we have a list of lines, draw them (OpenGLs reverse ordering requires this odd change)
763                                 wrapLineList.reverse()
764                                 for wline in wrapLineList:
765                                         BGL.glRasterPos2i(margin, (FNT_HEIGHT*((consoleLineIdx-__CONSOLE_LINE_OFFSET__) + wrapLineIndex)) - 8)
766                                         Draw.Text(wline, FNT_NAME)
767                                         wrapLineIndex += 1
768                                 wrapLineIndex-=1 # otherwise we get a silly extra line. 
769                                 
770                         else: # no wrapping.
771                                 
772                                 BGL.glRasterPos2i(margin, (FNT_HEIGHT * ((consoleLineIdx-__CONSOLE_LINE_OFFSET__)+wrapLineIndex)) - 8)
773                                 Draw.Text(cmdBuffer[-consoleLineIdx].cmd, FNT_NAME)
774                 consoleLineIdx += 1
775
776 # This recieves the event index, call a function from here depending on the event.
777 def handle_button_event(evt):
778         pass
779
780
781 # Run the console
782 __CONSOLE_VAR_DICT__ = {} # Initialize var dict
783
784
785 # Print Startup lines, add __bpydoc__ to the console startup.
786 for l in __bpydoc__.split('<br>'):
787         cmdBuffer.append( cmdLine(l, 1, None) )
788         
789
790 histIndex = cursor = -1 # How far back from the first letter are we? - in current CMD line, history if for moving up and down lines.
791
792 # Autoexec, startup code.
793 scriptDir = Get('scriptsdir')
794 console_autoexec = None
795 if scriptDir:
796         if not scriptDir.endswith(Blender.sys.sep):
797                 scriptDir += Blender.sys.sep
798         
799         console_autoexec  = '%s%s' % (scriptDir, 'console_autoexec.py')
800
801         if not sys.exists(console_autoexec):
802                 # touch the file
803                 try:
804                         open(console_autoexec, 'w').close()
805                         cmdBuffer.append(cmdLine('...console_autoexec.py not found, making new in scripts dir', 1, None))
806                 except:
807                         cmdBuffer.append(cmdLine('...console_autoexec.py could not write, this is ok', 1, None))
808                         scriptDir = None # make sure we only use this for console_autoexec.py
809         
810         if not sys.exists(console_autoexec):
811                 console_autoexec = None
812         
813         else:
814                 cmdBuffer.append(cmdLine('...Using existing console_autoexec.py in scripts dir', 1, None))
815
816
817
818 #-Autoexec---------------------------------------------------------------------#
819 # Just use the function to jump into local naming mode.
820 # This is so we can loop through all of the autoexec functions / vars and add them to the __CONSOLE_VAR_DICT__
821 def include_console(includeFile):
822         global __CONSOLE_VAR_DICT__ # write autoexec vars to this.
823         
824         # Execute an external py file as if local
825         exec(include(includeFile))
826
827 def standard_imports():
828         # Write local to global __CONSOLE_VAR_DICT__ for reuse,
829         
830         exec('%s%s' % ('__CONSOLE_VAR_DICT__["bpy"]=', 'bpy'))
831         exec('%s%s' % ('__CONSOLE_VAR_DICT__["Blender"]=', 'Blender'))
832         
833         for ls in (dir(), dir(Blender)):
834                 for __TMP_VAR_NAME__ in ls:
835                         # Execute the local > global coversion.
836                         exec('%s%s' % ('__CONSOLE_VAR_DICT__[__TMP_VAR_NAME__]=', __TMP_VAR_NAME__))
837         
838         # Add dummy imports to input so output scripts to a text file work as expected
839         cmdBuffer.append(cmdLine('import bpy', 0, 1))
840         cmdBuffer.append(cmdLine('import Blender', 0, 1)) # pretend we have been executed, as we kindof have.
841         cmdBuffer.append(cmdLine('from Blender import *', 0, 1))
842
843 if scriptDir and console_autoexec:
844         include_console(console_autoexec) # pass the blender module
845
846 standard_imports() # import Blender and bpy
847
848 #-end autoexec-----------------------------------------------------------------#
849
850
851 # Append new line to write to
852 cmdBuffer.append(cmdLine(' ', 0, 0))
853
854 #------------------------------------------------------------------------------#
855 #                    register the event handling code, GUI                     #
856 #------------------------------------------------------------------------------#
857 def main():
858         Draw.Register(draw_gui, handle_event, handle_button_event)
859
860 if __name__ == '__main__':
861         main()