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