diff --git a/src/pyqt_example.py b/src/pyqt_example.py index 0390284..d295fce 100644 --- a/src/pyqt_example.py +++ b/src/pyqt_example.py @@ -1,165 +1,160 @@ -import os -import sip - -from PyQt4 import uic - -from PyQt4.QtCore import Qt -from PyQt4.QtCore import pyqtSignal -from PyQt4.QtCore import pyqtSlot - -from PyQt4.QtGui import QDialog -from PyQt4.QtGui import QWidget -from PyQt4.QtGui import QPushButton -from PyQt4.QtGui import QLineEdit -from PyQt4.QtGui import QVBoxLayout -from PyQt4.QtGui import QMenu -from PyQt4.QtGui import QCursor - - -class ExampleDialog( QDialog ): - def __init__( self, parent ): - QDialog.__init__( self, parent ) - - self.setGeometry( 100, 100, 200, 100 ) - self.setWindowTitle( "Hello World" ) - self.setToolTip( "This is a QWidget widget" ) - - self.btn = QPushButton( "Log Text", self ) - self.btn.setToolTip( "This is a QPushButton widget" ) - self.btn.resize( self.btn.sizeHint() ) - self.btn.clicked.connect( self.logText ) - - self.lineedit = QLineEdit( "Hello World", self ) - self.lineedit.setToolTip( "Type Something" ) - - layout = QVBoxLayout( self ) - layout.addWidget( self.lineedit ) - layout.addWidget( self.btn ) - - def logText( self ): - Application.LogMessage( self.lineedit.text() ) - -class ExampleSignalSlot( ExampleDialog ): - def __init__( self, parent ): - ExampleDialog.__init__( self,parent ) - self.setWindowTitle( "Signal/Slot Example" ) - self.lineedit.setText( "" ) - - # module containing sievents mapped to pyqtsignals - from sisignals import signals, muteSIEvent - - # connect the siActivate signal to the activate slot - signals.siActivate.connect( self.activate ) - muteSIEvent( "siActivate", False ) - - # connect the siPassChange signal to the passChanged slot - signals.siPassChange.connect( self.passChanged ) - muteSIEvent( "siPassChange", False ) - - def activate( self, state = None ): - if state is not None: - if state: - self.lineedit.setText( "Welcome Back!" ) - else: - self.lineedit.setText( "Good Bye!") - - def passChanged( self, targetPass = "" ): - self.lineedit.setText( targetPass ) - - def closeEvent( self, event ): - # disconnect signals from slots when you close the widget - # muteSIEvent() can be used to mute the signals softimage events send - # but be careful if another widget exists and is using them - from sisignals import signals, muteSIEvent - signals.siActivate.disconnect( self.activate ) - signals.siPassChange.disconnect( self.passChanged ) - #muteSIEvent( "siActivate", True ) - #muteSIEvent( "siPassChange", True ) - -class ExampleMenu( QMenu ): - def __init__( self, parent ): - QMenu.__init__( self, parent ) - - # add actions and a separator - hello = self.addAction("Print 'Hello!'") - self.addSeparator() - world = self.addAction("Print 'World!'") - - # connect to the individual action's signal - hello.triggered.connect( self.hello ) - world.triggered.connect( self.world ) - - # connect to the menu level signal - self.triggered.connect( self.menuTrigger ) - - def hello( self ): - print( "Hello!" ) - - def world( self ): - print( "World!" ) - - def menuTrigger( self, action ): - if action.text() == "Print 'Hello!'": - print( "You clicked, Print 'Hello!'" ) - elif action.text() == "Print 'World!'": - print( "You clicked, Print 'World!'" ) - -class ExampleUIFile( QDialog ): - def __init__( self, parent, uifilepath ): - QDialog.__init__( self, parent ) - - # load ui file - self.ui = uic.loadUi( uifilepath, self ) - - # connect to the createCube function - self.ui.uiCreateCube.clicked.connect( self.createCube ) - - def createCube( self ): - cube = Application.CreatePrim("Cube", "MeshSurface", str(self.uiCubeName.text()), "") - cube.Length.Value = self.uiCubeLength.value() - -def XSILoadPlugin( in_reg ): - in_reg.Name = "PyQt_Example" - in_reg.Author = "Steven Caron" - in_reg.RegisterCommand( "ExampleDialog" ) - in_reg.RegisterCommand( "ExampleSignalSlot" ) - in_reg.RegisterCommand( "ExampleMenu" ) - in_reg.RegisterCommand( "ExampleUIFile" ) - -def ExampleDialog_Execute(): - """a simple example dialog showing basic functionality of the pyqt for softimage plugin""" - sianchor = Application.getQtSoftimageAnchor() - sianchor = sip.wrapinstance( long(sianchor), QWidget ) - dialog = ExampleDialog( sianchor ) - dialog.show() - -def ExampleSignalSlot_Execute(): - """a simple example showing softimage events triggering pyqt signals""" - sianchor = Application.getQtSoftimageAnchor() - sianchor = sip.wrapinstance( long(sianchor), QWidget ) - dialog = ExampleSignalSlot( sianchor ) - dialog.show() - -def ExampleMenu_Execute(): - """a simple example showing the use of a qmenu""" - sianchor = Application.getQtSoftimageAnchor() - sianchor = sip.wrapinstance( long(sianchor), QWidget ) - menu = ExampleMenu( sianchor ) - - # notice the use of QCursor and exec_ call - menu.exec_(QCursor.pos()) - -def ExampleUIFile_Execute(): - """a simple example showing the use of a .ui file created using QtDesigner""" - - # find plugin to get the path to the example ui file - plugin = Application.Plugins("PyQt_Example") - if plugin is None: - return False - - sianchor = Application.getQtSoftimageAnchor() - sianchor = sip.wrapinstance( long(sianchor), QWidget ) - uifilepath = os.path.join(plugin.OriginPath, "exampleui.ui") - dialog = QDialog(sianchor) - dialog = ExampleUIFile( dialog, uifilepath ) +import os +import sip + +from PyQt4 import uic + +from PyQt4.QtCore import Qt +from PyQt4.QtCore import pyqtSignal +from PyQt4.QtCore import pyqtSlot + +from PyQt4.QtGui import QDialog +from PyQt4.QtGui import QWidget +from PyQt4.QtGui import QPushButton +from PyQt4.QtGui import QLineEdit +from PyQt4.QtGui import QVBoxLayout +from PyQt4.QtGui import QMenu +from PyQt4.QtGui import QCursor + + +class ExampleDialog( QDialog ): + def __init__( self, parent ): + QDialog.__init__( self, parent ) + + self.setGeometry( 100, 100, 200, 100 ) + self.setWindowTitle( "Hello World" ) + self.setToolTip( "This is a QWidget widget" ) + + self.btn = QPushButton( "Log Text", self ) + self.btn.setToolTip( "This is a QPushButton widget" ) + self.btn.resize( self.btn.sizeHint() ) + self.btn.clicked.connect( self.logText ) + + self.lineedit = QLineEdit( "Hello World", self ) + self.lineedit.setToolTip( "Type Something" ) + + layout = QVBoxLayout( self ) + layout.addWidget( self.lineedit ) + layout.addWidget( self.btn ) + + def logText( self ): + Application.LogMessage( self.lineedit.text() ) + +class ExampleSignalSlot( ExampleDialog ): + def __init__( self, parent ): + ExampleDialog.__init__( self,parent ) + self.setWindowTitle( "Signal/Slot Example" ) + self.lineedit.setText( "" ) + + # module containing sievents mapped to pyqtsignals + from sisignals import signals + + # connect the siActivate signal to the activate slot and unmute the event if necessary. + signals.connect('siActivate', self.activate ) + + # connect the siPassChange signal to the passChanged slot and unmute the event if necessary. + signals.connect('siPassChange', self.passChanged ) + + def activate( self, state = None ): + if state is not None: + if state: + self.lineedit.setText( "Welcome Back!" ) + else: + self.lineedit.setText( "Good Bye!") + + def passChanged( self, targetPass = "" ): + self.lineedit.setText( targetPass ) + + def closeEvent( self, event ): + # Disconnect signals from slots when you close the widget. + # Softimage events are muted if no other widgets are using them. + from sisignals import signals + signals.disconnect('siActivate', self.activate ) + signals.disconnect('siPassChange', self.passChanged ) + +class ExampleMenu( QMenu ): + def __init__( self, parent ): + QMenu.__init__( self, parent ) + + # add actions and a separator + hello = self.addAction("Print 'Hello!'") + self.addSeparator() + world = self.addAction("Print 'World!'") + + # connect to the individual action's signal + hello.triggered.connect( self.hello ) + world.triggered.connect( self.world ) + + # connect to the menu level signal + self.triggered.connect( self.menuTrigger ) + + def hello( self ): + print( "Hello!" ) + + def world( self ): + print( "World!" ) + + def menuTrigger( self, action ): + if action.text() == "Print 'Hello!'": + print( "You clicked, Print 'Hello!'" ) + elif action.text() == "Print 'World!'": + print( "You clicked, Print 'World!'" ) + +class ExampleUIFile( QDialog ): + def __init__( self, parent, uifilepath ): + QDialog.__init__( self, parent ) + + # load ui file + self.ui = uic.loadUi( uifilepath, self ) + + # connect to the createCube function + self.ui.uiCreateCube.clicked.connect( self.createCube ) + + def createCube( self ): + cube = Application.CreatePrim("Cube", "MeshSurface", str(self.uiCubeName.text()), "") + cube.Length.Value = self.uiCubeLength.value() + +def XSILoadPlugin( in_reg ): + in_reg.Name = "PyQt_Example" + in_reg.Author = "Steven Caron" + in_reg.RegisterCommand( "ExampleDialog" ) + in_reg.RegisterCommand( "ExampleSignalSlot" ) + in_reg.RegisterCommand( "ExampleMenu" ) + in_reg.RegisterCommand( "ExampleUIFile" ) + +def ExampleDialog_Execute(): + """a simple example dialog showing basic functionality of the pyqt for softimage plugin""" + sianchor = Application.getQtSoftimageAnchor() + sianchor = sip.wrapinstance( long(sianchor), QWidget ) + dialog = ExampleDialog( sianchor ) + dialog.show() + +def ExampleSignalSlot_Execute(): + """a simple example showing softimage events triggering pyqt signals""" + sianchor = Application.getQtSoftimageAnchor() + sianchor = sip.wrapinstance( long(sianchor), QWidget ) + dialog = ExampleSignalSlot( sianchor ) + dialog.show() + +def ExampleMenu_Execute(): + """a simple example showing the use of a qmenu""" + sianchor = Application.getQtSoftimageAnchor() + sianchor = sip.wrapinstance( long(sianchor), QWidget ) + menu = ExampleMenu( sianchor ) + + # notice the use of QCursor and exec_ call + menu.exec_(QCursor.pos()) + +def ExampleUIFile_Execute(): + """a simple example showing the use of a .ui file created using QtDesigner""" + + # find plugin to get the path to the example ui file + plugin = Application.Plugins("PyQt_Example") + if plugin is None: + return False + + sianchor = Application.getQtSoftimageAnchor() + sianchor = sip.wrapinstance( long(sianchor), QWidget ) + uifilepath = os.path.join(plugin.OriginPath, "exampleui.ui") + dialog = QDialog(sianchor) + dialog = ExampleUIFile( dialog, uifilepath ) dialog.show() \ No newline at end of file diff --git a/src/qtevents.py b/src/qtevents.py index aefc633..d1d1213 100644 --- a/src/qtevents.py +++ b/src/qtevents.py @@ -2,7 +2,7 @@ from win32com.client import Dispatch as disp from win32com.client import constants as C -si = disp('XSI.Application') +si = disp('XSI.Application').Application # Create a mapping of virtual keys import win32con @@ -276,13 +276,14 @@ def XSILoadPlugin( in_reg ): in_reg.RegisterEvent( "QtEvents_SelectionChange", C.siOnSelectionChange ) in_reg.RegisterEvent( "QtEvents_ValueChange", C.siOnValueChange ) - # mute immediately. the dialog is responsble for turning the events it needs on - events = si.EventInfos - from sisignals import EVENT_MAPPING - for key,value in EVENT_MAPPING.iteritems(): - event = events( value ) - if si.ClassName( event ) == "EventInfo": - event.Mute = True + # events added in 2012, err v10.0 + if xsi_version() >= 10.0: + in_reg.RegisterEvent( "QtEvents_Undo", C.siOnEndCommand) + in_reg.RegisterEvent( "QtEvents_Redo", C.siOnEndCommand) + + # Clean the registry and mute the events immediately. + from sisignals import signals + signals.reset() return True @@ -312,15 +313,15 @@ def QtEvents_KeyUp_OnEvent( in_ctxt ): def QtEvents_Activate_OnEvent( in_ctxt ): from sisignals import signals - signals.siActivate.emit( in_ctxt.GetAttribute( "State" ) ) + signals.emit( "siActivate", in_ctxt.GetAttribute( "State" ) ) def QtEvents_FileExport_OnEvent( in_ctxt ): from sisignals import signals - signals.siFileExport.emit( in_ctxt.GetAttribute( "FileName" ) ) + signals.emit( "siFileExport", in_ctxt.GetAttribute( "FileName" ) ) def QtEvents_FileImport_OnEvent( in_ctxt ): from sisignals import signals - signals.siFileImport.emit( in_ctxt.GetAttribute( "FileName" ) ) + signals.emit( "siFileImport", in_ctxt.GetAttribute( "FileName" ) ) #def QtEvents_CustomFileExport_OnEvent( in_ctxt ): @@ -328,69 +329,80 @@ def QtEvents_FileImport_OnEvent( in_ctxt ): def QtEvents_RenderFrame_OnEvent( in_ctxt ): from sisignals import signals - signals.siRenderFrame.emit( in_ctxt.GetAttribute( "FileName" ), in_ctxt.GetAttribute( "Frame" ) ) + signals.emit( "siRenderFrame", in_ctxt.GetAttribute( "FileName" ), in_ctxt.GetAttribute( "Frame" ) ) def QtEvents_RenderSequence_OnEvent( in_ctxt ): from sisignals import signals - signals.siRenderSequence.emit( in_ctxt.GetAttribute( "FileName" ), in_ctxt.GetAttribute( "Frame" ) ) + signals.emit( "siRenderSequence", in_ctxt.GetAttribute( "FileName" ), in_ctxt.GetAttribute( "Frame" ) ) def QtEvents_RenderAbort_OnEvent( in_ctxt ): from sisignals import signals - signals.siRenderAbort.emit( in_ctxt.GetAttribute( "FileName" ), in_ctxt.GetAttribute( "Frame" ) ) + signals.emit( "siRenderAbort", in_ctxt.GetAttribute( "FileName" ), in_ctxt.GetAttribute( "Frame" ) ) def QtEvents_PassChange_OnEvent( in_ctxt ): from sisignals import signals - signals.siPassChange.emit( in_ctxt.GetAttribute( "TargetPass" ) ) + signals.emit( "siPassChange", in_ctxt.GetAttribute( "TargetPass" ) ) def QtEvents_SceneOpen_OnEvent( in_ctxt ): from sisignals import signals - signals.siSceneOpen.emit( in_ctxt.GetAttribute( "FileName" ) ) + signals.emit( "siSceneOpen", in_ctxt.GetAttribute( "FileName" ) ) def QtEvents_SceneSaveAs_OnEvent( in_ctxt ): from sisignals import signals - signals.siSceneSaveAs.emit( in_ctxt.GetAttribute( "FileName" ) ) + signals.emit( "siSceneSaveAs", in_ctxt.GetAttribute( "FileName" ) ) def QtEvents_SceneSave_OnEvent( in_ctxt ): from sisignals import signals - signals.siSceneSave.emit( in_ctxt.GetAttribute( "FileName" ) ) + signals.emit( "siSceneSave", in_ctxt.GetAttribute( "FileName" ) ) def QtEvents_ChangeProject_OnEvent( in_ctxt ): from sisignals import signals - signals.siChangeProject.emit( in_ctxt.GetAttribute( "NewProjectPath" ) ) + signals.emit( "siChangeProject", in_ctxt.GetAttribute( "NewProjectPath" ) ) def QtEvents_ConnectShader_OnEvent( in_ctxt ): from sisignals import signals - signals.siConnectShader.emit( in_ctxt.GetAttribute( "Source" ), in_ctxt.GetAttribute( "Target" ) ) + signals.emit( "siConnectShader", in_ctxt.GetAttribute( "Source" ), in_ctxt.GetAttribute( "Target" ) ) def QtEvents_DisconnectShader_OnEvent( in_ctxt ): from sisignals import signals - signals.siDisconnectShader.emit( in_ctxt.GetAttribute( "Source" ), in_ctxt.GetAttribute( "Target" ) ) + signals.emit( "siDisconnectShader", in_ctxt.GetAttribute( "Source" ), in_ctxt.GetAttribute( "Target" ) ) def QtEvents_CreateShader_OnEvent( in_ctxt ): from sisignals import signals - signals.siCreateShader.emit( in_ctxt.GetAttribute( "Shader" ), in_ctxt.GetAttribute( "ProgID" ) ) + signals.emit( "siCreateShader", in_ctxt.GetAttribute( "Shader" ), in_ctxt.GetAttribute( "ProgID" ) ) def QtEvents_SourcePathChange_OnEvent( in_ctxt ): from sisignals import signals - signals.siSourcePathChange.emit( in_ctxt.GetAttribute( "FileName" ) ) + signals.emit( "siSourcePathChange", in_ctxt.GetAttribute( "FileName" ) ) def QtEvents_DragAndDrop_OnEvent( in_ctxt ): from sisignals import signals - signals.siDragAndDrop.emit( in_ctxt.GetAttribute( "DragSource" ) ) + signals.emit( "siDragAndDrop", in_ctxt.GetAttribute( "DragSource" ) ) def QtEvents_ObjectAdded_OnEvent( in_ctxt ): from sisignals import signals - signals.siObjectAdded.emit( in_ctxt.GetAttribute( "Objects" ) ) + signals.emit( "siObjectAdded", in_ctxt.GetAttribute( "Objects" ) ) def QtEvents_ObjectRemoved_OnEvent( in_ctxt ): from sisignals import signals - signals.siObjectRemoved.emit( in_ctxt.GetAttribute( "Objects" ) ) + signals.emit( "siObjectRemoved", in_ctxt.GetAttribute( "Objects" ) ) def QtEvents_SelectionChange_OnEvent( in_ctxt ): from sisignals import signals - signals.siSelectionChange.emit( in_ctxt.GetAttribute( "ChangeType" ) ) + signals.emit( "siSelectionChange", in_ctxt.GetAttribute( "ChangeType" ) ) def QtEvents_ValueChange_OnEvent( in_ctxt ): from sisignals import signals - signals.siValueChange.emit( in_ctxt.GetAttribute( "FullName" ) ) - + signals.emit( "siValueChange", in_ctxt.GetAttribute( "FullName" ), in_ctxt.GetAttribute( "Object" ), in_ctxt.GetAttribute( "PreviousValue" )) + +def QtEvents_Undo_OnEvent( in_ctxt ): + o_command = in_ctxt.GetAttribute( "Command" ) + if o_command.ScriptingName == "Undo": + from sisignals import signals + signals.emit( "siUndo" ) + +def QtEvents_Redo_OnEvent( in_ctxt ): + o_command = in_ctxt.GetAttribute( "Command" ) + if o_command.ScriptingName == "Redo": + from sisignals import signals + signals.emit( "siRedo" ) \ No newline at end of file diff --git a/src/sisignals.py b/src/sisignals.py index 502cb35..ad41868 100644 --- a/src/sisignals.py +++ b/src/sisignals.py @@ -3,7 +3,7 @@ from win32com.client import Dispatch as disp from win32com.client import constants as C -si = disp('XSI.Application') +si = disp('XSI.Application').Application EVENT_MAPPING = { #pyqtsignal : softimage event @@ -37,13 +37,17 @@ "siSourcePathChange" : "QtEvents_SourcePathChange", "siValueChange" : "QtEvents_ValueChange", + + "siUndo": "QtEvents_Undo", + "siRedo": "QtEvents_Redo", } class SISignals( QObject ): """ - class for mapping softimage events to pyqt signals + Class for mapping softimage events to pyqt signals not all context attributes are passed as signal arguments, add more as needed - currently all signals are expected to be 'siOnEnd' versions of softimage events + currently all signals are expected to be 'siOnEnd' versions of softimage events. + It is implemented as a singleton and registers which signals are in used with which slot. """ # add more pyqtsignals that map to softimage events here @@ -77,16 +81,62 @@ class for mapping softimage events to pyqt signals siSourcePathChange = pyqtSignal(str) # siOnSourcePathChange - siValueChange = pyqtSignal(str) # siOnValueChange + siValueChange = pyqtSignal(str, object, object) # siOnValueChange + + siUndo = pyqtSignal() # siOnEndCommand (undo) + siRedo = pyqtSignal() # siOnEndCommand (redo) + + _instance = None + + _connections = {} def __init__(self): QObject.__init__(self) self.setObjectName( "siSignals" ) + def __new__(cls, *args, **kwargs): + if not cls._instance: + cls._instance = super(SISignals, cls).__new__(cls, *args, **kwargs) + return cls._instance + + def connect(self, signal, function): + if hasattr(self, signal): + if signal in self._connections: + slots = self._connections[signal] + if function not in slots: + getattr(self, signal).connect(function) + slots.append(function) + else: + getattr(self, signal).connect(function) + self._connections[signal] = [function] + muteSIEvent(signal, False) + + def disconnect(self, signal, function): + if hasattr(self, signal): + getattr(self, signal).disconnect(function) + + if signal in self._connections: + slots = self._connections[signal] + if function in slots: + slots.remove(function) + if not len(slots): + self._connections.pop(signal) + muteSIEvent(signal, True) + + def emit(self, signal, *args): + if hasattr(self, signal): + getattr(self,signal).emit(*args) + + def reset(self): + self._connections = {} + for signal in EVENT_MAPPING: + muteSIEvent(signal, True) + signals = SISignals() -def muteSIEvent( event, state = True ): +def muteSIEvent(signal, state=True): events = si.EventInfos - event = events( EVENT_MAPPING[event] ) - if si.ClassName( event ) == "EventInfo": - event.Mute = state \ No newline at end of file + event = events(EVENT_MAPPING[signal]) + if si.ClassName(event) == "EventInfo": + event.Mute = state + \ No newline at end of file