diff options
Diffstat (limited to 'release/scripts/console.py')
-rw-r--r-- | release/scripts/console.py | 517 |
1 files changed, 517 insertions, 0 deletions
diff --git a/release/scripts/console.py b/release/scripts/console.py new file mode 100644 index 00000000000..1fbf7908c7a --- /dev/null +++ b/release/scripts/console.py @@ -0,0 +1,517 @@ +#!BPY + +""" +Name: 'Interactive Console' +Blender: 236 +Group: 'System' +Tooltip: 'Interactive Python Console' +""" +__author__ = "Campbell Barton AKA Ideasman" +__url__ = ["http://members.iinet.net.au/~cpbarton/ideasman/", "blender", "elysiun", "Official Python site, http://www.python.org"] +__bpydoc__ = """\ +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. + +Those completely new to Python can check the link button above that points to +its official homepage, with news, downloads and documentation. + +Usage:<br> + Type your code and hit "Enter" to get it executed.<br> + - Right mouse click: Save output;<br> + - Arrow keys: command history and cursor;<br> + - Ctrl + Tab: auto compleate based on variable names and modules loaded -- multiple choices popup a menu;<br> + - Ctrl + Enter: multiline functions -- delays executing code until only Enter is pressed. +""" + +import Blender +from Blender import * +import sys as python_sys +import StringIO +import types + +# Constants +__DELIMETERS__ = '. ,=+-*/%<>&~][{}():' +__LINE_HISTORY__ = 200 +__LINE_HEIGHT__ = 14 +__FONT_SIZE__ = "normal" + +''' +# Generic Blender functions +def getActScriptWinRect(): + area = Window.GetAreaSize() + area = (area[0]-1, area[1]-1) + for scrInfo in Window.GetScreenInfo(Window.Types['SCRIPT'], 'win', ''): + if scrInfo['vertices'][2]-scrInfo['vertices'][0] == area[0]: + if scrInfo['vertices'][3]-scrInfo['vertices'][1] == area[1]: + return scrInfo['vertices'] + return None +''' + +class cmdLine: + # cmd: is the command string, or any other message + # type: 0:user input 1:program feedback 2:error message. 3:option feedback + # exe; 0- not yet executed 1:executed + def __init__(self, cmd, type, exe): + self.cmd = cmd + self.type = type + self.exe = exe + +# Include external file with internal namespace +def include(filename): + file = open(filename, 'r') + filedata = file.read() + file.close() + return compile(filedata, filename, 'exec') + +# Writes command line data to a blender text file. +def writeCmdData(cmdLineList, type): + if type == 3: + typeList = [0,1,2, 3, None] # all + else: + typeList = [type] # so we can use athe lists 'in' methiod + + newText = Text.New('command_output.py', 1) + for myCmd in cmdLineList: + if myCmd.type in typeList: # user input + newText.write('%s\n' % myCmd.cmd) + Draw.PupMenu('%s written' % newText.name) + +def insertCmdData(cmdBuffer): + textNames = [tex.name for tex in Text.Get()] + if textNames: + choice = Draw.PupMenu('|'.join(textNames)) + if choice != -1: + text = Text.Get()[choice-1] + + # Add the text! + for l in text.asLines(): + cmdBuffer.append(cmdLine('%s ' % l, 0, 0)) + Draw.Redraw() + + +COLLECTED_VAR_NAMES = {} # a list of keys, each key has a list of absolute paths + +# Pain and simple recursice dir(), accepts a string +def rdir(dirString): + global COLLECTED_VAR_NAMES + dirStringSplit = dirString.split('.') + + exec('dirList = dir(%s)' % dirString) + for dirItem in dirList: + if not dirItem.startswith('_'): + if dirItem not in COLLECTED_VAR_NAMES.keys(): + COLLECTED_VAR_NAMES[dirItem] = [] + + # Add the string + splitD = dirString.split('"')[-2] + if splitD not in COLLECTED_VAR_NAMES[dirItem]: + COLLECTED_VAR_NAMES[dirItem].append(splitD) + + + + # Stops recursice stuff, overlooping + if type(dirItem) == types.ClassType or \ + type(dirItem) == types.ModuleType: + + print dirString, splitD, dirItem + + # Dont loop up dirs for strings ints etc. + if d not in dirStringSplit: + rdir( '%s.%s' % (dirString, d)) + +def recursive_dir(): + global COLLECTED_VAR_NAMES + + for name in __CONSOLE_VAR_DICT__.keys(): + if not name.startswith('_'): # Dont pick names like __name__ + rdir('__CONSOLE_VAR_DICT__["%s"]' % name) + #print COLLECTED_VAR_NAMES + COLLECTED_VAR_NAMES[name] = [''] + return COLLECTED_VAR_NAMES + +# Runs the code line(s) the user has entered and handle errors +# As well as feeding back the output into the blender window. +def runUserCode(__USER_CODE_STRING__): + global __CONSOLE_VAR_DICT__ # We manipulate the variables here. loading and saving from localspace to this global var. + + # Open A File like object to write all output to, that would useually be printed. + python_sys.stdout.flush() # Get rid of whatever came before + __FILE_LIKE_STRING__ = StringIO.StringIO() # make a new file like string, this saves up from making a file. + __STD_OUTPUT__ = python_sys.stdout # we need to store the normal output. + python_sys.stdout=__FILE_LIKE_STRING__ # Now anything printed will be written to the file like string. + + # Try and run the user entered line(s) + try: + # Load all variabls from global dict to local space. + for __TMP_VAR_NAME__ in __CONSOLE_VAR_DICT__.keys(): + exec('%s%s%s%s' % (__TMP_VAR_NAME__,'=__CONSOLE_VAR_DICT__["', __TMP_VAR_NAME__, '"]')) + del __TMP_VAR_NAME__ + + # Now all the vars are loaded, execute the code. # Newline thanks to phillip, + exec(compile(__USER_CODE_STRING__, 'blender_cmd.py', 'single')) #exec(compile(__USER_CODE_STRING__, 'blender_cmd.py', 'exec')) + + # Write local veriables to global __CONSOLE_VAR_DICT__ + for __TMP_VAR_NAME__ in dir(): + if __TMP_VAR_NAME__ != '__FILE_LIKE_STRING__' and\ + __TMP_VAR_NAME__ != '__STD_OUTPUT__' and\ + __TMP_VAR_NAME__ != '__TMP_VAR_NAME__' and\ + __TMP_VAR_NAME__ != '__USER_CODE_STRING__': + + # Execute the local > global coversion. + exec('%s%s' % ('__CONSOLE_VAR_DICT__[__TMP_VAR_NAME__]=', __TMP_VAR_NAME__)) + del __TMP_VAR_NAME__ + + except: # Prints the REAL exception. + error = str(python_sys.exc_value) + for errorLine in error.split('\n'): + cmdBuffer.append(cmdLine(errorLine, 2, None)) # new line to type into + + python_sys.stdout = __STD_OUTPUT__ # Go back to output to the normal blender console + + # Copy all new output to cmdBuffer + __FILE_LIKE_STRING__.seek(0) # the readline function requires that we go back to the start of the file. + for line in __FILE_LIKE_STRING__.readlines(): + cmdBuffer.append(cmdLine(line, 1, None)) + + cmdBuffer.append(cmdLine(' ', 0, 0)) # new line to type into + python_sys.stdout=__STD_OUTPUT__ + __FILE_LIKE_STRING__.close() + + + + + +#------------------------------------------------------------------------------# +# event handling code # +#------------------------------------------------------------------------------# +def handle_event(evt, val): + + # Insert Char into the cammand line + def insCh(ch): # Instert a char + global cmdBuffer + global cursor + # Later account for a cursor variable + cmdBuffer[-1].cmd = ('%s%s%s' % ( cmdBuffer[-1].cmd[:cursor], ch, cmdBuffer[-1].cmd[cursor:])) + + #------------------------------------------------------------------------------# + # Define Complex Key Actions # + #------------------------------------------------------------------------------# + def actionEnterKey(): + global histIndex, cursor, cmdBuffer + # Check for the neter kay hit + if Window.GetKeyQualifiers() & Window.Qual.CTRL: # HOLDING DOWN SHIFT, GO TO NEXT LINE. + cmdBuffer.append(cmdLine(' ', 0, 0)) + else: + # Multiline code will still run with 1 line, + multiLineCode = ['if 1:'] + if cmdBuffer[-1].cmd != ' ': + multiLineCode = ['%s%s' % (' ', cmdBuffer[-1].cmd)] # added space for fake if. + else: + cmdBuffer[-1].type = 1 + multiLineCode = [] + cmdBuffer[-1].exe = 1 + i = 2 + while cmdBuffer[-i].exe == 0: + if cmdBuffer[-i].cmd == ' ':# Tag as an output type so its not used in the key history + cmdBuffer[-i].type = 1 + else: # space added at the start for added if 1: statement + multiLineCode.append('%s%s' % (' ', cmdBuffer[-i].cmd) ) + + # Mark as executed + cmdBuffer[-i].exe = 1 + i+=1 + + # add if to the end, reverse will make it the start. + multiLineCode.append('if 1:') + multiLineCode.reverse() + multiLineCode.append(' pass') # Now this is the end + + + runUserCode('\n'.join(multiLineCode)) + + # Clear the output based on __LINE_HISTORY__ + if len(cmdBuffer) > __LINE_HISTORY__: + cmdBuffer = cmdBuffer[-__LINE_HISTORY__:] + + histIndex = cursor = -1 # Reset cursor and history + + def actionUpKey(): + global histIndex, cmdBuffer + if abs(histIndex)+1 >= len(cmdBuffer): + histIndex = -1 + histIndex -= 1 + while cmdBuffer[histIndex].type != 0 and abs(histIndex) < len(cmdBuffer): + histIndex -= 1 + if cmdBuffer[histIndex].type == 0: # we found one + cmdBuffer[-1].cmd = cmdBuffer[histIndex].cmd + + def actionDownKey(): + global histIndex, cmdBuffer + if histIndex >= -2: + histIndex = -len(cmdBuffer) + histIndex += 1 + while cmdBuffer[histIndex].type != 0 and histIndex != -2: + histIndex += 1 + if cmdBuffer[histIndex].type == 0: # we found one + cmdBuffer[-1].cmd = cmdBuffer[histIndex].cmd + + def actionRightMouse(): + 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|Quit') + print choice + if choice == 1: + writeCmdData(cmdBuffer, 0) # type 0 user + elif choice == 2: + writeCmdData(cmdBuffer, 1) # type 1 user output + elif choice == 3: + writeCmdData(cmdBuffer, 2) # type 2 errors + elif choice == 4: + writeCmdData(cmdBuffer, 3) # All + elif choice == 6: + insertCmdData(cmdBuffer) # All + elif choice == 8: # Exit + Draw.Exit() + + + # Auto compleating, quite complex- use recutsice dir for the moment. + def actionAutoCompleate(): # Ctrl + Tab + RECURSIVE_DIR = recursive_dir() + + # get last name of user input + editVar = cmdBuffer[-1].cmd[:cursor] + + # Split off spaces operators etc from the staryt of the command so we can use the startswith function. + for splitChar in __DELIMETERS__: + editVar = editVar.split(splitChar)[-1] + + # Now we should have the var by its self + if editVar: + + possibilities = [] + + print editVar, 'editVar' + for __TMP_VAR_NAME__ in RECURSIVE_DIR.keys(): + if __TMP_VAR_NAME__ == editVar: + # print 'ADITVAR IS A VAR' + continue + elif __TMP_VAR_NAME__.startswith( editVar ): + possibilities.append( __TMP_VAR_NAME__ ) + + + if len(possibilities) == 1: + cmdBuffer[-1].cmd = ('%s%s%s' % (cmdBuffer[-1].cmd[:cursor - len(editVar)], possibilities[0], cmdBuffer[-1].cmd[cursor:])) + + elif possibilities: # If its not just [] + # -1 with insert is the second last. + + # Text choice + #cmdBuffer.insert(-1, cmdLine('options: %s' % ' '.join(possibilities), 3, None)) + + menuText = 'Choices (hold shift for whole name)%t|' + menuList = [] + menuListAbs = [] + possibilities.sort() # make nice :) + for __TMP_VAR_NAME__ in possibilities: + for usage in RECURSIVE_DIR[__TMP_VAR_NAME__]: + # Account for non absolute (variables for eg.) + if usage: # not '' + menuListAbs.append('%s.%s' % (usage, __TMP_VAR_NAME__)) # Used for names and can be entered when pressing shift. + else: + menuListAbs.append(__TMP_VAR_NAME__) # Used for names and can be entered when pressing shift. + + menuList.append(__TMP_VAR_NAME__) # Used for non Shift + + + #choice = Draw.PupMenu('Select Variabe name%t|' + '|'.join(possibilities) ) + choice = Draw.PupMenu(menuText + '|'.join(menuListAbs)) + if choice != -1: + + if not Window.GetKeyQualifiers() & Window.Qual.SHIFT: # Only paste in the Short name + cmdBuffer[-1].cmd = ('%s%s%s' % (cmdBuffer[-1].cmd[:cursor - len(editVar)], menuList[choice-1], cmdBuffer[-1].cmd[cursor:])) + else: # Put in the long name + cmdBuffer[-1].cmd = ('%s%s%s' % (cmdBuffer[-1].cmd[:cursor - len(editVar)], menuListAbs[choice-1], cmdBuffer[-1].cmd[cursor:])) + else: + # print 'NO EDITVAR' + return + + # ------------------end------------------# + + + #if (evt == Draw.ESCKEY and not val): + # Draw.Exit() + if evt == Draw.MOUSEX: # AVOID TOO MANY REDRAWS. + return + elif evt == Draw.MOUSEY: + return + + + global cursor + global histIndex + + ascii = Blender.event + + #------------------------------------------------------------------------------# + # key codes and key handling # + #------------------------------------------------------------------------------# + + # UP DOWN ARROW KEYS, TO TRAVERSE HISTORY + if (evt == Draw.UPARROWKEY and val): actionUpKey() + elif (evt == Draw.DOWNARROWKEY and val): actionDownKey() + + elif (evt == Draw.RIGHTARROWKEY and val): + cursor +=1 + if cursor > -1: + cursor = -1 + elif (evt == Draw.LEFTARROWKEY and val): + cursor -=1 + + elif (evt == Draw.TABKEY and val): + if Window.GetKeyQualifiers() & Window.Qual.CTRL: + actionAutoCompleate() + else: + insCh('\t') + + elif (evt == Draw.BACKSPACEKEY and val): cmdBuffer[-1].cmd = ('%s%s' % (cmdBuffer[-1].cmd[:cursor-1] , cmdBuffer[-1].cmd[cursor:])) + elif (evt == Draw.DELKEY and val) and cursor < -1: + cmdBuffer[-1].cmd = cmdBuffer[-1].cmd[:cursor] + cmdBuffer[-1].cmd[cursor+1:] + cursor +=1 + + elif ((evt == Draw.RETKEY or evt == Draw.PADENTER) and val): actionEnterKey() + elif (evt == Draw.RIGHTMOUSE and not val):actionRightMouse(); return + + elif ascii: + insCh(chr(ascii)) + else: + return # dont redraw. + Draw.Redraw() + + +def draw_gui(): + + # Get the bounds from ObleGL directly + __CONSOLE_RECT__ = BGL.Buffer(BGL.GL_FLOAT, 4) + BGL.glGetFloatv(BGL.GL_SCISSOR_BOX, __CONSOLE_RECT__) + __CONSOLE_RECT__= __CONSOLE_RECT__.list + + + # Clear the screen + BGL.glClearColor(0.0, 0.0, 0.0, 1.0) + BGL.glClear(BGL.GL_COLOR_BUFFER_BIT) # use it to clear the color buffer + + # Draw cursor location colour + cmd2curWidth = Draw.GetStringWidth(cmdBuffer[-1].cmd[:cursor], __FONT_SIZE__) + BGL.glColor3f(0.8, 0.2, 0.2) + BGL.glRecti(cmd2curWidth-1,4,cmd2curWidth+1, 20) + + BGL.glColor3f(1,1,1) + # Draw the set of cammands to the buffer + + consoleLineIdx = 1 + wrapLineIndex = 0 + while consoleLineIdx < len(cmdBuffer) and __CONSOLE_RECT__[3] > consoleLineIdx*__LINE_HEIGHT__ : + if cmdBuffer[-consoleLineIdx].type == 0: + BGL.glColor3f(1, 1, 1) + elif cmdBuffer[-consoleLineIdx].type == 1: + BGL.glColor3f(.3, .3, 1) + elif cmdBuffer[-consoleLineIdx].type == 2: + BGL.glColor3f(1.0, 0, 0) + elif cmdBuffer[-consoleLineIdx].type == 3: + BGL.glColor3f(0, 0.8, 0) + else: + BGL.glColor3f(1, 1, 0) + + if consoleLineIdx == 1: # NEVER WRAP THE USER INPUT + BGL.glRasterPos2i(0, (__LINE_HEIGHT__*consoleLineIdx) - 8) + Draw.Text(cmdBuffer[-consoleLineIdx].cmd, __FONT_SIZE__) + + + else: # WRAP? + # LINE WRAP + if Draw.GetStringWidth(cmdBuffer[-consoleLineIdx].cmd, __FONT_SIZE__) > __CONSOLE_RECT__[2]: + wrapLineList = [] + copyCmd = [cmdBuffer[-consoleLineIdx].cmd, ''] + while copyCmd != ['','']: + while Draw.GetStringWidth(copyCmd[0], __FONT_SIZE__) > __CONSOLE_RECT__[2]: + #print copyCmd + copyCmd[1] = '%s%s'% (copyCmd[0][-1], copyCmd[1]) # Add the char on the end + copyCmd[0] = copyCmd[0][:-1]# remove last chat + + # Now we have copyCmd[0] at a good length we can print it. + if copyCmd[0] != '': + wrapLineList.append(copyCmd[0]) + + copyCmd[0]='' + copyCmd = [copyCmd[1], copyCmd[0]] + + # Now we have a list of lines, draw them (OpenGLs reverse ordering requires this odd change) + wrapLineList.reverse() + for wline in wrapLineList: + BGL.glRasterPos2i(0, (__LINE_HEIGHT__*(consoleLineIdx + wrapLineIndex)) - 8) + Draw.Text(wline, __FONT_SIZE__) + wrapLineIndex += 1 + wrapLineIndex-=1 # otherwise we get a silly extra line. + + else: # no wrapping. + + BGL.glRasterPos2i(0, (__LINE_HEIGHT__*(consoleLineIdx+wrapLineIndex)) - 8) + Draw.Text(cmdBuffer[-consoleLineIdx].cmd, __FONT_SIZE__) + + + consoleLineIdx += 1 + + +# This recieves the event index, call a function from here depending on the event. +def handle_button_event(evt): + pass + + +# Run the console +__CONSOLE_VAR_DICT__ = {} # Initialize var dict + + +# Print Startup lines +cmdBuffer = [cmdLine("Welcome to Ideasman's Blender Console", 1, None),\ + cmdLine(' * Right Click: Save output', 1, None),\ + cmdLine(' * Arrow Keys: Command history and cursor', 1, None),\ + cmdLine(' * Ctrl + Tab: Auto compleate based on variable names and modules loaded, multiple choices popup a menu', 1, None),\ + cmdLine(' * Ctrl + Enter: Multiline functions, delays executing code until only Enter is pressed.', 1, None)] + +histIndex = cursor = -1 # How far back from the first letter are we? - in current CMD line, history if for moving up and down lines. + +# Autoexec, startup code. +console_autoexec = '%s%s' % (Get('datadir'), '/console_autoexec.py') +if not sys.exists(console_autoexec): + # touch the file + open(console_autoexec, 'w').close() + cmdBuffer.append(cmdLine('...console_autoexec.py not found, making new in scripts data dir', 1, None)) +else: + cmdBuffer.append(cmdLine('...Using existing console_autoexec.py in scripts data dir', 1, None)) + + + +#-Autoexec---------------------------------------------------------------------# +# Just use the function to jump into local naming mode. +# This is so we can loop through all of the autoexec functions / vars and add them to the __CONSOLE_VAR_DICT__ +def autoexecToVarList(): + global __CONSOLE_VAR_DICT__ # write autoexec vars to this. + + # Execute an external py file as if local + exec(include(console_autoexec)) + + # Write local to global __CONSOLE_VAR_DICT__ for reuse, + for __TMP_VAR_NAME__ in dir() + dir(Blender): + # Execute the local > global coversion. + exec('%s%s' % ('__CONSOLE_VAR_DICT__[__TMP_VAR_NAME__]=', __TMP_VAR_NAME__)) + +autoexecToVarList() # pass the blender module +#-end autoexec-----------------------------------------------------------------# + + +# Append new line to write to +cmdBuffer.append(cmdLine(' ', 0, 0)) + +#------------------------------------------------------------------------------# +# register the event handling code, GUI # +#------------------------------------------------------------------------------# +def main(): + Draw.Register(draw_gui, handle_event, handle_button_event) + +main() |