244 lines
8.9 KiB
Python
Executable File
244 lines
8.9 KiB
Python
Executable File
# This script pops up an interactive IPython shell configured for use in
|
|
# FaceFX Studio and configures Studio's Python environment.
|
|
|
|
import wx
|
|
import sys
|
|
import string
|
|
from IPython.gui.wx.ipython_view import IPShellWidget
|
|
import IPython.ipapi
|
|
import FxStudio
|
|
|
|
|
|
class CodeGenerator:
|
|
|
|
def __init__(self):
|
|
self.begin()
|
|
|
|
|
|
def begin(self):
|
|
self.code = []
|
|
self.tab = " "
|
|
self.level = 0
|
|
|
|
|
|
def getCode(self):
|
|
return string.join(self.code, "")
|
|
|
|
|
|
def write(self, string):
|
|
self.code.append(self.tab * self.level + string)
|
|
|
|
|
|
def indent(self):
|
|
self.level = self.level + 1
|
|
|
|
|
|
def dedent(self):
|
|
if self.level == 0:
|
|
raise SyntaxError, "tab level is already 0!"
|
|
self.level = self.level - 1
|
|
|
|
|
|
def compile(self):
|
|
bytecode = compile(self.getCode(), "<string>", "exec")
|
|
ip = IPython.ipapi.get()
|
|
ip.ev(bytecode)
|
|
|
|
|
|
def wrap_studio_command_with_magic(cmd, docstr):
|
|
cg = CodeGenerator()
|
|
cg.begin()
|
|
cg.write("import FxStudio\n")
|
|
cg.write("import IPython.ipapi\n")
|
|
cg.write("ip = IPython.ipapi.get()\n")
|
|
cg.write("\n")
|
|
cg.write("def facefx_magic_wrap_")
|
|
cg.write(cmd)
|
|
cg.write("(self, arg):\n")
|
|
cg.indent()
|
|
cg.write("\"\"\"\n");
|
|
cg.write(docstr);
|
|
cg.write("\n\"\"\"\n");
|
|
cg.write("FxStudio.issueCommand(\"")
|
|
cg.write(cmd)
|
|
cg.write(" %s\" % arg.replace(\'\\\'\', \'\\\"\'))\n")
|
|
cg.dedent()
|
|
cg.write("\n")
|
|
cg.write("ip.expose_magic('")
|
|
cg.write(cmd)
|
|
cg.write("', facefx_magic_wrap_")
|
|
cg.write(cmd)
|
|
cg.write(")\n")
|
|
cg.compile()
|
|
|
|
|
|
def magicfy_all_studio_commands():
|
|
cmd_list = FxStudio.getConsoleCommandList()
|
|
for cmd in cmd_list:
|
|
if cmd != 'quit' and cmd != 'exit':
|
|
wrap_studio_command_with_magic(cmd, FxStudio.getConsoleCommandHelp(cmd))
|
|
|
|
|
|
class FaceFXOnDemandOutputWindow:
|
|
|
|
def __init__(self, title = "wxPython: stdout/stderr"):
|
|
self.frame = None
|
|
self.title = title
|
|
self.pos = wx.DefaultPosition
|
|
self.size = (450, 300)
|
|
self.color_palette = FxStudio.getColorPalette()
|
|
self.triggers = []
|
|
self.logfile = open(FxStudio.getDirectory('log') + FxStudio.getConsoleVariable('g_applaunchtime') + "-python-log.txt", "w")
|
|
self.continue_line = False
|
|
|
|
|
|
def __del__(self):
|
|
self.logfile.close()
|
|
|
|
|
|
def raiseWhenSeen(self, trigger):
|
|
import types
|
|
if type(trigger) in types.StringTypes:
|
|
trigger = [trigger]
|
|
self.triggers = trigger
|
|
|
|
|
|
def createOutputWindow(self, st):
|
|
if FxStudio.getConsoleVariableAsSwitch('py_enableoutputwindow') == True:
|
|
self.frame = wx.Frame(FxStudio.getMainWindow(), -1, self.title, self.pos, self.size,
|
|
style=wx.DEFAULT_FRAME_STYLE)
|
|
self.text = wx.TextCtrl(self.frame, -1, "",
|
|
style=wx.TE_MULTILINE|wx.TE_READONLY)
|
|
self.text.SetFont(wx.Font(8, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, FxStudio.getUnicodeFontName(), wx.FONTENCODING_SYSTEM))
|
|
self.text.SetBackgroundColour(self.color_palette['BaseColour1'])
|
|
self.text.SetForegroundColour(self.color_palette['BaseColour8'])
|
|
ib = wx.IconBundle()
|
|
ib.AddIconFromFile(getAppIconPath(), wx.BITMAP_TYPE_ANY)
|
|
self.frame.SetIcons(ib)
|
|
self.text.AppendText(st)
|
|
self.log_to_file(st)
|
|
self.frame.Show(True)
|
|
self.frame.Bind(wx.EVT_CLOSE, self.onCloseWindow)
|
|
else:
|
|
self.log_to_file(st)
|
|
|
|
|
|
def onCloseWindow(self, event):
|
|
if self.frame is not None:
|
|
self.frame.Destroy()
|
|
self.frame = None
|
|
self.text = None
|
|
|
|
|
|
def log_to_file(self, text):
|
|
import time as __facefx_time
|
|
if not text.isspace() and not self.continue_line:
|
|
logtext = __facefx_time.strftime("%H:%M:%S", __facefx_time.localtime()) + ": " + text
|
|
self.logfile.write(logtext)
|
|
elif text.isspace() and not self.continue_line:
|
|
logtext = __facefx_time.strftime("%H:%M:%S", __facefx_time.localtime()) + ": " + text
|
|
self.logfile.write(logtext)
|
|
else:
|
|
self.logfile.write(text)
|
|
self.logfile.flush()
|
|
if text.endswith(('\n','\r')):
|
|
self.continue_line = False
|
|
else:
|
|
self.continue_line = True
|
|
|
|
|
|
def write(self, text):
|
|
if self.frame is None:
|
|
if not wx.Thread_IsMain():
|
|
wx.CallAfter(self.CreateOutputWindow, text)
|
|
else:
|
|
self.createOutputWindow(text)
|
|
else:
|
|
if not wx.Thread_IsMain():
|
|
wx.CallAfter(self.__write, text)
|
|
else:
|
|
self.__write(text)
|
|
|
|
|
|
def __write(self, text):
|
|
self.text.AppendText(text)
|
|
self.log_to_file(text)
|
|
for item in self.triggers:
|
|
if item in text:
|
|
self.frame.Raise()
|
|
break
|
|
|
|
|
|
def close(self):
|
|
if self.frame is not None:
|
|
wx.CallAfter(self.frame.Close)
|
|
|
|
|
|
def flush(self):
|
|
pass
|
|
|
|
|
|
class FaceFXIPythonShell(wx.Window):
|
|
|
|
def __init__(self, parent, id, title):
|
|
wx.Window.__init__(self, parent, id, style=wx.NO_BORDER, name=title)
|
|
self._sizer = wx.BoxSizer(wx.VERTICAL)
|
|
self.shell = IPShellWidget(self, intro='Welcome to the FaceFX IPython Shell.\n\n')
|
|
# Turn on STC completion and turn off threading.
|
|
self.shell.options['completion']['value'] = 'STC'
|
|
self.shell.options['threading']['value'] = 'False'
|
|
self.shell.reloadOptions(self.shell.options)
|
|
# Turn off the annoying 80 column vertical line.
|
|
self.shell.text_ctrl.SetEdgeMode(wx.stc.STC_EDGE_NONE)
|
|
self.color_palette = FxStudio.getColorPalette()
|
|
self.shell.text_ctrl.StyleSetBackground(wx.stc.STC_STYLE_DEFAULT, self.color_palette['BaseColour1'])
|
|
for style in self.shell.text_ctrl.ANSI_STYLES.values():
|
|
self.shell.text_ctrl.StyleSetBackground(style[0], self.color_palette['BaseColour1'])
|
|
self.shell.text_ctrl.SetCaretForeground('WHITE')
|
|
self.shell.text_ctrl.SetWindowStyle(self.shell.text_ctrl.GetWindowStyle() | wx.NO_BORDER)
|
|
self.shell.text_ctrl.Refresh()
|
|
ip = IPython.ipapi.get()
|
|
ip.ex('from FxStudio import *')
|
|
self._sizer.Add(self.shell, 1, wx.EXPAND)
|
|
self.SetSizer(self._sizer)
|
|
self.Bind(wx.EVT_SIZE, self.onSize)
|
|
# Hook into the underlying STC and add in the missing mouse capture lost event handler
|
|
# to prevent C++ wxWidgets code from asserting. Note that there's not much we can
|
|
# do about the selection weirdness that happens if this state is triggered.
|
|
self.shell.text_ctrl.Bind(wx.EVT_MOUSE_CAPTURE_LOST, self.onMouseCaptureLost)
|
|
self.SetAutoLayout(1)
|
|
self.output = FaceFXOnDemandOutputWindow(title="output")
|
|
sys.stdout = self.output
|
|
sys.stderr = self.output
|
|
FxStudio.dockInMainWindowNotebook(self, "Python")
|
|
|
|
|
|
def onSize(self, newSize):
|
|
self.Layout()
|
|
|
|
|
|
# Do nothing when mouse capture is lost. This causes selection to be a little
|
|
# weird if this happens but will not lock or crash the application or cause the
|
|
# C++ wxWidgets code to assert.
|
|
def onMouseCaptureLost(self, event):
|
|
pass
|
|
|
|
|
|
old = wx.FindWindowByName('FaceFX IPython Shell')
|
|
if old == None:
|
|
FxStudio.msg('Starting FaceFX IPython Shell...')
|
|
FxStudio.msg('Using IPython version {0}.'.format(IPython.__version__))
|
|
FxStudio.msg('Using wxPython version {0}.'.format(wx.__version__))
|
|
panel = FaceFXIPythonShell(FxStudio.getMainWindowNotebook(), wx.ID_ANY, 'FaceFX IPython Shell')
|
|
ip = IPython.ipapi.get()
|
|
ip.ex('import ipython')
|
|
ip.ex('ipython.magicfy_all_studio_commands()')
|
|
py_enableoutputwindow_old = FxStudio.getConsoleVariable('py_enableoutputwindow')
|
|
FxStudio.setConsoleVariable('py_enableoutputwindow', '0')
|
|
ip.IP.magic_cd(FxStudio.getDirectory('log'))
|
|
ipython_log_filename = FxStudio.getConsoleVariable('g_applaunchtime')
|
|
ipython_log_filename += '-ipython-log.txt'
|
|
ip.IP.magic_logstart('-o -r -t %s over' % ipython_log_filename)
|
|
ip.IP.magic_cd(FxStudio.getDirectory('app'))
|
|
FxStudio.setConsoleVariable('py_enableoutputwindow', py_enableoutputwindow_old)
|