import os import wx import wx.lib.scrolledpanel as scrolled import re import traceback import win32gui, win32process, pythoncom from win32com.client import constants as c from win32com.client import constants False = 0 True = 1 xsi = Application log = xsi.LogMessage def XSILoadPlugin(in_reg): in_reg.Author = "James Parks" in_reg.Name = "jpSetSceneRanges" in_reg.Email = "arcsecond@gmail.com" in_reg.URL = "www.arcsecond.com" in_reg.Major = 1 in_reg.Minor = 0 in_reg.Categories = 'SetSceneRanges, scripts' in_reg.RegisterCommand("jpSetSceneRanges", "jpSetSceneRanges") in_reg.RegisterMenu(c.siMenuTbGetPropertyID, "jpSetSceneRanges_Menu", False, False) return True def jpSetSceneRanges_Init(in_ctxt): oCmd = in_ctxt.Source oCmd.Description = "" oCmd.ReturnValue = True return True def jpSetSceneRanges_Execute(): jpSSRFrame.create() return True def jpSetSceneRanges_Menu_Init(in_ctxt): oMenu = in_ctxt.Source oMenu.AddCommandItem("jpSetSceneRanges", "jpSetSceneRanges") return True #---------------------------- # That's the interesting bit: # This class can be put in a separate module and imported in the plugin for exemple. class XSISubFrame(wx.Frame): """ XSI wx SubFrame with special event bound to the close. Please inherit from this one to create a custom frame in XSI. In order to ensure a clean exit, please call the XSISubFrame.Close method to generate a EVT_CLOSE event. Don't forget to call the XSISubFrame.__init__ in your __init__! And don't forget to define the self.panel at the end of your .__init__! @author: Aloys Baillet """ _topLevelXSIWindowHandle = None @classmethod def create(cls, *args, **kw): """ Call this static class method to create a new instance of your subframe. This class method will use the XSI Top Level window as a parent for the current frame. """ app = wx.GetApp() if app is None: app = wx.App(redirect=False) topHandle = XSISubFrame._getXSITopLevelWindow() top = wx.PreFrame() top.AssociateHandle(topHandle) top.PostCreate(top) app.SetTopWindow(top) try: frame = cls(top, app, *args, **kw) frame.Show(True) except: Application.LogMessage('An error occured during the instantiation of the %s frame class: %s'%(cls.__name__, traceback.format_exc()), c.siError) frame = None top.DissociateHandle() return frame @staticmethod def _getXSITopLevelWindow(): """ Returns the handle to the XSI top-level window """ if XSISubFrame._topLevelXSIWindowHandle is not None: return XSISubFrame._topLevelXSIWindowHandle def callback(handle, winList): winList.append(handle) return True wins = [] win32gui.EnumWindows(callback, wins) currentId = os.getpid() for handle in wins: tid, pid = win32process.GetWindowThreadProcessId(handle) if pid == currentId: title = win32gui.GetWindowText(handle) if title.startswith('SOFTIMAGE'): XSISubFrame._topLevelXSIWindowHandle = handle return handle return None def __init__(self, parent, app, id, title, pos=(150, 150), size=(350, 200), style=wx.DEFAULT_FRAME_STYLE|wx.FRAME_FLOAT_ON_PARENT|wx.FRAME_NO_TASKBAR|wx.FULL_REPAINT_ON_RESIZE, name='frame'): wx.Frame.__init__(self, parent, id, title, pos, size, style, name) self.app = app self.panel = None self._runningModal = False self.SetBackgroundStyle(wx.BG_STYLE_COLOUR) self.SetBackgroundColour(wx.Color(171, 168, 166)) self.Bind(wx.EVT_CLOSE, self.OnClose) self.Bind(wx.EVT_ACTIVATE, self.OnActivate) icon = wx.Icon('xsiIcon', wx.BITMAP_TYPE_ICO, 16, 16) icon.LoadFile(xsi.InstallationPath(c.siFactoryPath)+'\\xsi.ico', wx.BITMAP_TYPE_ICO) self.SetIcon(icon) def OnActivate(self, evt): """Event handler for activation. Used to detect modality in XSI""" if self._runningModal: return self.panel.Enable(win32gui.IsWindowEnabled(XSISubFrame._topLevelXSIWindowHandle)) evt.Skip() def OnClose(self, evt): """Event handler for EVT_CLOSE event.""" self.Show(False) self.Destroy() # This is because the regular wx Destroy waits for the application to destroy the window # But there is no wxApp running, so we do it ourselves win32gui.DestroyWindow(self.GetHandle()) win32gui.SetFocus(XSISubFrame._topLevelXSIWindowHandle) def showModalDialog(self, dlg): """ Method to display a modal dialog that disactivate XSI window. """ self._runningModal = True top = XSISubFrame._topLevelXSIWindowHandle win32gui.EnableWindow(top, False) dlgReturn = dlg.ShowModal() win32gui.EnableWindow(top, True) self._runningModal = False return dlgReturn def __del__(self): pass # End of the interesting bit #----------------- class XSITextCtrl(wx.TextCtrl): """ Custom Text control for XSI, the regular wx.TextCtrl has been "hacked" by XSI So we need to use the wx.TE_RICH2 control to have a correct behavior """ def __init__(self, parent, id, value='', pos=(-1, -1), size=(-1, -1), style=0, validator=wx.DefaultValidator, name=''): style += wx.TE_RICH2 wx.TextCtrl.__init__(self, parent, id, value, pos, size, style, validator, name) self.SetBackgroundStyle(wx.BG_STYLE_COLOUR) self.SetBackgroundColour(wx.Color(54, 51, 51)) self.SetForegroundColour(wx.Color(255, 255, 255)) class jpSSR_wxSceneRanger(wx.Panel): def __init__(self, parent, name): wx.Panel.__init__(self, parent, -1) rangeButton = wx.Button(self, -1, name, size=(80,-1)) self.Bind(wx.EVT_BUTTON, self.setRange, rangeButton) self.rangeMin_ENTRY = XSITextCtrl(self, -1, "0", size=(30,-1)) self.rangeMax_ENTRY = XSITextCtrl(self, -1, "0", size=(30,-1)) sizer = wx.BoxSizer(wx.HORIZONTAL) sizer.Add(rangeButton, 0, wx.ALL) sizer.Add(self.rangeMin_ENTRY, 0, wx.ALL) sizer.Add(self.rangeMax_ENTRY, 0, wx.ALL) #sizer.Add(deformButton, 0, wx.ALL) self.SetSizer(sizer) self.Layout() self.SetSize(sizer.GetSize()) self.SetAutoLayout(True) self.frozen = False def setRange(self, event): Application.LogMessage("This is a test: setRange") rangeMin = self.rangeMin_ENTRY.GetValue() rangeMax = self.rangeMax_ENTRY.GetValue() if int(rangeMin) != 0 or int(rangeMax) != 0: Application.SetValue("PlayControl.In", rangeMin, "") Application.SetValue("PlayControl.GlobalIn", rangeMin, "") Application.SetValue("PlayControl.Out", rangeMax, "") Application.SetValue("PlayControl.GlobalOut", rangeMax, "") else: Application.LogMessage("Invalid Range: no changes made") def setMinMax(self, rangeMin, rangeMax): self.rangeMin_ENTRY.SetValue(rangeMin) self.rangeMax_ENTRY.SetValue(rangeMax) class jpSSRFrame(XSISubFrame): def __init__(self, parent, app): XSISubFrame.__init__(self, parent, app, -1, 'jpSetSceneRanges', size=(150, 200)) self.panel = jpSSRPanel(self) def OnClose(self, event): """ This method is bound by XSISubFrame to the EVT_CLOSE event. It shows a modal dialog before closing. Notice the win32gui hack to disable the XSI top-level window during the modal time. """ #dlg = wx.MessageDialog(None, 'Are you sure?', 'Sure?') #if self.showModalDialog(dlg) == wx.ID_OK: self.panel.onWindowClose() XSISubFrame.OnClose(self, event) class jpSSRPanel(wx.Panel): _runningInstances = [] def __init__(self, parent): Application.LogMessage("This is a test: __init__") wx.Panel.__init__(self, parent, -1) self.createWidgets(parent) def createWidgets(self, parent): self.frame = parent newPrefsFlag = 0 ################### #make preferences ################### jpSSRPrefs = Application.Preferences.Categories("jpSetSceneRanges") if not jpSSRPrefs: oCus = Application.ActiveSceneRoot.AddCustomProperty( "tmp" ) oCus.AddParameter2("jpSSRpath", constants.siString, "\\\\Nitro\\vol1\\cgstor", 0,0,0,0, 0, 0, "jpSSRPath" ) Application.InstallCustomPreferences( oCus, "jpSetSceneRanges" ) Application.Preferences.Export(r"c:\jpSetSceneRanges.xsiprefs", "jpSetSceneRanges") newPrefsFlag = 1 self.jpSSRPath = Application.Preferences.GetPreferenceValue("jpSetSceneRanges.jpSSRpath") if not newPrefsFlag: lines = [] lines = self.jpReadLines(self.jpSSRPath) self.numOfScenes = lines.pop(0) self.project = lines.pop(0) else: self.numOfScenes = "4" self.project = "Project" lines = ["0:0", "0:0", "0:0", "0:0"] ################ #Menu ################ menuBar = wx.MenuBar() fileMenu = wx.Menu() getPOpath_MENUITEM = fileMenu.Append(-1, "Read...") printPOpath_MENUITEM = fileMenu.Append(-1, "Print Path...") close_MENUITEM = fileMenu.Append(-1, "Close") menuBar.Append(fileMenu, "File") self.frame.SetMenuBar(menuBar) self.frame.Bind(wx.EVT_MENU, self.readFile, getPOpath_MENUITEM) self.frame.Bind(wx.EVT_MENU, self.printSSRpath, printPOpath_MENUITEM) self.frame.Bind(wx.EVT_MENU, self.OnClose, close_MENUITEM) ################# #Buttons 'n' Such ################# numOfScenes_STATIC = wx.StaticText(self, -1, "Num of Scenes", size=(150,-1)) self.numOfScenes_ENTRY = XSITextCtrl(self, -1, self.numOfScenes, size=(140,-1)) project_STATIC = wx.StaticText(self, -1, "Project", size=(140,-1)) self.project_ENTRY = XSITextCtrl(self, -1, self.project, size=(140,-1)) self.rangeAll_BTN = wx.Button(self, -1, "Range All", size=(140,-1)) self.Bind(wx.EVT_BUTTON, self.rangeAll, self.rangeAll_BTN) ################## #Sizer-ific ################## self.sizer = wx.BoxSizer(wx.VERTICAL) self.sizer.Add(numOfScenes_STATIC, 0, wx.ALL) self.sizer.Add(self.numOfScenes_ENTRY, 0, wx.ALL) self.sizer.Add(project_STATIC, 0, wx.ALL) self.sizer.Add(self.project_ENTRY, 0, wx.ALL) self.sizer.Add(self.rangeAll_BTN, 0, wx.ALL) #wxSceneRangers padding = 2 self.made_BTNs = [] self.numOfScenes = self.numOfScenes_ENTRY.GetValue() for i in range(1, int(self.numOfScenes)+1): self.made_BTNs.append(jpSSR_wxSceneRanger(self, "sc" + str(i).zfill(padding))) self.sizer.Add(self.made_BTNs[i-1], 0, wx.ALL) thisRange = lines.pop(0) rangeMin, rangeMax = re.split(":", thisRange) self.made_BTNs[i-1].setMinMax(rangeMin, rangeMax) self.SetSizer(self.sizer) self.Layout() self.SetSize(self.sizer.GetSize()) self.SetAutoLayout(True) self.frozen = False self.Show(True) jpSSRPanel._runningInstances.append(self) def rangeAll(self, event): Application.LogMessage("This is a test: rangeAll method") rangeMin = self.made_BTNs[0].rangeMin_ENTRY.GetValue() rangeMax = self.made_BTNs[-1].rangeMax_ENTRY.GetValue() Application.LogMessage(str(rangeMin) + ", " + str(rangeMax)) if int(rangeMin) != 0 or int(rangeMax) != 0: Application.SetValue("PlayControl.In", rangeMin, "") Application.SetValue("PlayControl.GlobalIn", rangeMin, "") Application.SetValue("PlayControl.Out", rangeMax, "") Application.SetValue("PlayControl.GlobalOut", rangeMax, "") else: Application.LogMessage("Invalid Range: no changes made") def jpReadLines(self, fileToRead): lines = [] badLines = [] if os.path.exists(fileToRead) == False: print "File Not Found" else: thisFile = file(fileToRead, "r+") badLines = thisFile.readlines() thisFile.close() for i in range(0, len(badLines)): lines.append(badLines[i].strip()) return lines def readFile(self, event): Application.LogMessage("This is a test: readFile method") dialog = wx.FileDialog(None, "Choose a Path...", defaultFile="ranges.txt", wildcard="*.txt", style=wx.DD_DEFAULT_STYLE | wx.DD_NEW_DIR_BUTTON) if dialog.ShowModal() == wx.ID_OK: self.jpSSRPath = dialog.GetPath() rangesFile = self.jpSSRPath newScenes = self.jpReadLines(rangesFile) dialog.Destroy() #set preferences Application.Preferences.SetPreferenceValue("jpSetSceneRanges.jpSSRpath", self.jpSSRPath) # self.OnClose() # jpSSRFrame.create() def printSSRpath(self,event): Application.LogMessage(str(self.jpSSRPath)) def saveFile(self): Application.LogMessage("This is the Save File Fucntion") dialog = wx.FileDialog(None, "Save Ranges...", deafultDir=self.jpSSRPath, defaultFile="ranges.txt", wildcard="*.txt", style=wx.SAVE | wx.OVERWRITE_PROMPT) if dialog.ShowModal() == wx.ID_OK: filename = dialog.GetFilename() if str(filename) != "": saveFile = file(filename, "w+") numOfScenes = self.numOfScenes_ENTRY.GetValue() saveFile.write(numOfScenes + "\n") project = self.project_ENTRY.GetValue() saveFile.write(project + "\n") for i in range(0, int(self.numOfScenes)): rangeMin = self.madeButtons[i][1].get() rangeMax = self.madeButtons[i][2].get() saveFile.write(rangeMin + ":" + rangeMax + "\n") saveFile.close() def onWindowClose(self): jpSSRPanel._runningInstances.remove(self) def OnClose(self, evt): """Event handler for the button click.""" self.frame.Close()