try: import math import maya.cmds as cmds import pymel.core as pymel import maya.OpenMaya as om from Ui import uiSpaceSwitcher as uiSpaceSwitcher reload(uiSpaceSwitcher) from maya import OpenMayaUI try: from sstk.libs.libQt import QtCore, QtWidgets from sstk.libs import libSerialization except ImportError: from ..Vendor.Qt import QtCore, QtGui, QtWidgets, QtCompat from ..Modules.Library import libSerialization from functools import partial except Exception as e: print "Error: importing python modules!!!\n", print e # TODO - Fix warning message on scene open when the tool is already (Don't seem to cause any problem) # TODO - Need more testing # global variables to this module: CLASS_NAME = "SpaceSwitcher" TITLE = "m071_SpaceSwitcher" DESCRIPTION = "m072_SpaceSwitcherDesc" ICON = "/Icons/dp_spaceSwitcher.png" class QDoubleEmptyStringValidator(QtGui.QIntValidator): """ Override the validator to be able to have an acceptable finish edit signal fired on empty string """ def __init__(self, *args, **kwargs): super(QDoubleEmptyStringValidator, self).__init__(*args, **kwargs) def validate(self, _textInput, _pos): validState = super(QDoubleEmptyStringValidator, self).validate(_textInput, _pos) if validState[0] == QtGui.QValidator.Invalid or validState[0] == QtGui.QValidator.Intermediate: if _textInput == "": return QtGui.QValidator.Acceptable, validState[1], validState[2] else: return validState else: return validState class SpaceSwitcherLogic(object): """ This class is used to setup a SpaceSwitch system on a node """ WORLD_NODE_NAME = "World_SpaceSwitcher" def __init__(self): self.aDrivers = [] # List of parent in the system self.nDriven = None # Base constrained objet (The constraint will no be set on this object self.nSwConst = None # SpaceSwitch constraint for the system self.nSwConstRecept = None # Constrained node self.aFreeIndex = [] # List of free index (Can only happen when a item is removed) in the parent constraint self.sSysName = "SpaceSwitcher_" # Name of the system tempWorld = pymel.ls(self.WORLD_NODE_NAME) if tempWorld: self.worldNode = tempWorld[0] else: self.worldNode = None def setup_space_switch(self, nDriven=None, aDrivers=None, bCreateWolrdNode=False, bCreateParent=True): """ Setup a new space switch system on the node :param nDriven: :param aDrivers: :param bCreateWolrdNode: :param bCreateParent: """ aCurSel = pymel.selected() if aDrivers is not None: aParent = aCurSel[0:-1] else: aParent = aDrivers bContinue = False # Create the worldNode if not self.worldNode and bCreateWolrdNode: self.worldNode = pymel.createNode("transform", n=self.WORLD_NODE_NAME) self.worldNode.visibility.set(False) for pAttr in self.worldNode.listAttr(keyable=True): pymel.setAttr(pAttr, keyable=False, lock=True) self.worldNode.hiddenInOutliner = True if self.worldNode: self.aDrivers.append(self.worldNode) if not nDriven: if len(aCurSel) == 0: pymel.informBox("Space Switcher", "You need to choose at least the node to constraint") # The user only selected the driven node, so create a space switch between it's parent and the world elif len(aCurSel) == 1: self.nDriven = aCurSel[0] bContinue = True else: self.nDriven = aCurSel[-1] bContinue = True else: self.nDriven = nDriven bContinue = True if bContinue: self.sSysName += nDriven.name() sStripName = str(self.nDriven.stripNamespace()).replace(pymel.other.NameParser.PARENT_SEP, "") # Setup the intermediate node to manage the spaceSwitch if bCreateParent: self.nSwConstRecept = pymel.createNode("transform", ss=True) mDriven = self.nDriven.getMatrix(worldSpace=True) self.nSwConstRecept.setMatrix(mDriven, worldSpace=True) self.nSwConstRecept.rename(sStripName + "_Const_Grp") self.nDriven.setParent(self.nSwConstRecept) else: self.nSwConstRecept = self.nDriven.getParent() # Create the parent constraint for the first node, but add the other target manually if bCreateWolrdNode: self.nSwConst = pymel.parentConstraint(self.worldNode, self.nSwConstRecept, n=sStripName + "_SpaceSwitch_Const", mo=True) else: self.nSwConst = pymel.parentConstraint(aParent[0], self.nSwConstRecept, n=sStripName + "_SpaceSwitch_Const", mo=True) self.aDrivers.append(aParent[0]) # Remove the first parent setuped before aParent = aParent[1:] self.nSwConst.getWeightAliasList()[0].set(0.0) # Setup the first key for the current activate constraint, targets offset and rest position if pymel.referenceQuery(self.nDriven, isNodeReferenced=True): pymel.setKeyframe(self.nSwConst.getWeightAliasList()[0], t=0, ott="step") pymel.setKeyframe(self.nSwConst.target[0].targetOffsetTranslate, t=0, ott="step") pymel.setKeyframe(self.nSwConst.target[0].targetOffsetRotate, t=0, ott="step") pymel.setKeyframe(self.nSwConst.restTranslate, t=0, ott="step") pymel.setKeyframe(self.nSwConst.restRotate, t=0, ott="step") if aParent: self.add_target(aParent, firstSetup=True) # else: #If this is the only parent setuped, automaticly switch to it # Do not switch in a non-reference scene to prevent problem with referenced object # if pymel.referenceQuery(self.nDriven, isNodeReferenced=True): # self.do_switch(0) pymel.select(nDriven) def is_parent_exist(self, aNewParentList): """ Look if a node is already a possible parent in the system :param aNewParentList: """ aExistTgt = self.nSwConst.getTargetList() for nParent in aNewParentList: if nParent in aExistTgt: return True return False def _get_adjusted_index(self, _iCurIndex): """ Return the good index in the parent constraint to prevent any problem if one parent have been removed from it """ if _iCurIndex in self.aFreeIndex: return self._get_adjusted_index(_iCurIndex + 1) else: return _iCurIndex pass def add_target(self, aNewParent, firstSetup=False): """ Add a new target to the space switch system :param aNewParent: :param firstSetup: """ aExistTgt = self.nSwConst.getTargetList() for nParent in aNewParent: sStripParentName = str(nParent.stripNamespace()).replace(pymel.other.NameParser.PARENT_SEP, "") # Check if we need to use an free index that could exist after some target removing if len(self.aFreeIndex) != 0: iNewIdx = self.aFreeIndex[0] self.aFreeIndex.pop(0) else: iNewIdx = len(self.nSwConst.getWeightAliasList()) # Ensure that the parent doesn't already exist in the drivers list if not nParent in aExistTgt: # First, calculate the offset between the parent and the driven node vTrans = self._get_tm_offset(nParent, _type="t") vRot = self._get_tm_offset(nParent, _type="r") # Connect the new target manually in the parent constraint if iNewIdx == 0: self.nSwConst.addAttr(sStripParentName + "W" + str(iNewIdx), at="double", min=0, max=1, dv=1, k=True, h=False) else: self.nSwConst.addAttr(sStripParentName + "W" + str(iNewIdx), at="double", min=0, max=1, dv=0, k=True, h=False) pymel.connectAttr(nParent.parentMatrix, self.nSwConst.target[iNewIdx].targetParentMatrix) pymel.connectAttr(nParent.scale, self.nSwConst.target[iNewIdx].targetScale) pymel.connectAttr(nParent.rotateOrder, self.nSwConst.target[iNewIdx].targetRotateOrder) pymel.connectAttr(nParent.rotate, self.nSwConst.target[iNewIdx].targetRotate) pymel.connectAttr(nParent.rotatePivotTranslate, self.nSwConst.target[iNewIdx].targetRotateTranslate) pymel.connectAttr(nParent.rotatePivot, self.nSwConst.target[iNewIdx].targetRotatePivot) pymel.connectAttr(nParent.translate, self.nSwConst.target[iNewIdx].targetTranslate) # Link the created attributes to the weight value of the target nConstTgtWeight = pymel.Attribute(self.nSwConst.name() + "." + sStripParentName + "W" + str(iNewIdx)) pymel.connectAttr(nConstTgtWeight, self.nSwConst.target[iNewIdx].targetWeight) # Set the offset information self.nSwConst.target[iNewIdx].targetOffsetTranslate.targetOffsetTranslateX.set(vTrans[0]) self.nSwConst.target[iNewIdx].targetOffsetTranslate.targetOffsetTranslateY.set(vTrans[1]) self.nSwConst.target[iNewIdx].targetOffsetTranslate.targetOffsetTranslateZ.set(vTrans[2]) self.nSwConst.target[iNewIdx].targetOffsetRotate.targetOffsetRotateX.set(vRot[0]) self.nSwConst.target[iNewIdx].targetOffsetRotate.targetOffsetRotateY.set(vRot[1]) self.nSwConst.target[iNewIdx].targetOffsetRotate.targetOffsetRotateZ.set(vRot[2]) # Do not key an non-referenced object to prevent problem when referencing the scene if pymel.referenceQuery(self.nSwConst, isNodeReferenced=True): pymel.setKeyframe(nConstTgtWeight, t=0, ott="step") pymel.setKeyframe(self.nSwConst.target[iNewIdx].targetOffsetTranslate, t=0, ott="step") pymel.setKeyframe(self.nSwConst.target[iNewIdx].targetOffsetRotate, t=0, ott="step") self.aDrivers.insert(iNewIdx, nParent) else: print("Warning: " + nParent.name() + " is already a driver for " + self.nDriven) # If this is the only parent and it is not referenced, do the switch right now on the frame the user is # if (len(aNewParent) == 1 and not firstSetup and pymel.referenceQuery(self.nDriven, isNodeReferenced=True)): # self.do_switch(iNbTgt - 1) #Since a new target have been added, iNbTgt equal the index to switch too def remove_target(self, iIdx, _all=False): if _all: # Remove the constraint and reset some variable pymel.delete(self.nSwConst) self.aDrivers = [] self.nDriven = None self.nSwConst = None # SpaceSwitch constraint for the system self.nSwConstRecept = None # Space Switch receiver self.aFreeIndex = [] # List of free index (Can only happen when a item is removed) in the parent constraint else: aExistTgt = self.nSwConst.getTargetList() iNbTgt = len(aExistTgt) # Before removing the target, we need to readjust the weight value and offset if needed aWeight = self.nSwConst.getWeightAliasList() if iNbTgt > iIdx: # Get all the frames where the removed index is active if iIdx == -1: aKeyTime = pymel.keyframe(self.nSwConst.restTranslate.restTranslateX, q=True) else: aKeyTime = pymel.keyframe(aWeight[iIdx], q=True) # Cut the keys of all weight at time where the removed target was active. for t in aKeyTime: if aWeight[iIdx].get(time=t) == 1.0: for w in aWeight: try: pymel.cutKey(w, time=t) except: pass # Remove the target pTgt = aExistTgt[iIdx] pymel.parentConstraint(pTgt, self.nSwConstRecept, e=True, rm=True) self.aFreeIndex.append(iIdx) self.aFreeIndex.sort() self.aDrivers.pop(iIdx) # Update all constraint when removing one self.update_constraint_keys() def _get_tm_offset(self, _nParent, _nDriven=None, _type="t"): """ Get the offset between the driven and a driver node """ if _nDriven is None: _nDriven = self.nSwConstRecept mStart = om.MMatrix() mEnd = om.MMatrix() wmStart = _nParent.worldMatrix.get().__melobject__() wmEnd = _nDriven.worldMatrix.get().__melobject__() om.MScriptUtil().createMatrixFromList(wmStart, mStart) om.MScriptUtil().createMatrixFromList(wmEnd, mEnd) mOut = om.MTransformationMatrix(mEnd * mStart.inverse()) if _type == "t": # Extract Translation vTran = om.MVector(mOut.getTranslation(om.MSpace.kTransform)) vTranPymel = [vTran.x, vTran.y, vTran.z] return vTranPymel if _type == "r": # Extract Rotation ro = _nDriven.rotateOrder.get() vRot = om.MEulerRotation(mOut.eulerRotation().reorder(ro)) vRotDeg = [math.degrees(vRot.x), math.degrees(vRot.y), math.degrees(vRot.z)] return vRotDeg def update_constraint_keys(self, _updateAll=False): """ Update all key in the constraint to refresh the offset when needed and prevent any snap :param _updateAll: """ aWeight = self.nSwConst.getWeightAliasList() fCurTime = pymel.currentTime() # List to stock information we need to update in the good frame order aKeyIndex = [] # Check to collect the rest pos/rot key already created aKeyTime = pymel.keyframe(self.nSwConst.restTranslate.restTranslateX, q=True) for t in aKeyTime: if t > fCurTime or _updateAll: aKeyIndex.append((t, -1)) # Check to collect all constraint keys we would need to update for i, w in enumerate(aWeight): aKeyTime = pymel.keyframe(w, q=True) for t in aKeyTime: if t > fCurTime or _updateAll: if w.get(time=t) == 1.0: # Only update the key if the constraint is active aKeyIndex.append((t, i)) # Sort the key index list of tuple to ensure we update the data in the good frame order aKeyIndex.sort() pymel.refresh(su=True) for t, i in aKeyIndex: pymel.setCurrentTime(t - 1) if i >= 0: iAjustedIdx = self._get_adjusted_index(i) # Compute the offset between the parent and the driver vTrans = self._get_tm_offset(self.aDrivers[i], _type="t") vRot = self._get_tm_offset(self.aDrivers[i], _type="r") pymel.setCurrentTime(t) # Set the offset information self.nSwConst.target[iAjustedIdx].targetOffsetTranslate.targetOffsetTranslateX.set(vTrans[0]) self.nSwConst.target[iAjustedIdx].targetOffsetTranslate.targetOffsetTranslateY.set(vTrans[1]) self.nSwConst.target[iAjustedIdx].targetOffsetTranslate.targetOffsetTranslateZ.set(vTrans[2]) self.nSwConst.target[iAjustedIdx].targetOffsetRotate.targetOffsetRotateX.set(vRot[0]) self.nSwConst.target[iAjustedIdx].targetOffsetRotate.targetOffsetRotateY.set(vRot[1]) self.nSwConst.target[iAjustedIdx].targetOffsetRotate.targetOffsetRotateZ.set(vRot[2]) # Update keys pymel.setKeyframe(self.nSwConst.target[iAjustedIdx].targetOffsetTranslate, t=t, ott="step") pymel.setKeyframe(self.nSwConst.target[iAjustedIdx].targetOffsetRotate, t=t, ott="step") pymel.keyTangent(self.nSwConst.target[iAjustedIdx].targetOffsetTranslate, t=t, ott="step") # Force step pymel.keyTangent(self.nSwConst.target[iAjustedIdx].targetOffsetRotate, t=t, ott="step") # Force step else: # Get the offset information from the constraint trans and rot at the time before the key vTrans = self.nSwConst.constraintTranslate.get() vRot = self.nSwConst.constraintRotate.get() pymel.setCurrentTime(t) # Set the offset information self.nSwConst.restTranslate.set(vTrans) self.nSwConst.restRotate.set(vRot) # Update keys pymel.setKeyframe(self.nSwConst.restTranslate, t=t, ott="step") pymel.setKeyframe(self.nSwConst.restRotate, t=t, ott="step") pymel.keyTangent(self.nSwConst.restTranslate, t=t, ott="step") # Force step pymel.keyTangent(self.nSwConst.restRotate, t=t, ott="step") # Force step pymel.setCurrentTime(fCurTime) pymel.refresh(su=False) def do_switch(self, iIdx): """ Switch the parent in which the driven node is constrained. Ensure that the switch is done without any snap of the driven object :param iIdx: """ fCurTime = pymel.currentTime() iActiveWeight = None aWeight = self.nSwConst.getWeightAliasList() # If none is set to 1.0, the value will be -1 which represent the current parent for i, fValue in enumerate(aWeight): # Get the value at the frame before and do not update the offset if we return to same one if fValue.get(time=fCurTime - 1) == 1.0: iActiveWeight = i break # Safety check to ensure that the rest data will be keyed if iActiveWeight is None: aRestKey = pymel.keyframe(self.nSwConst.restTranslate, q=True) if len(aRestKey) > 0: iActiveWeight = -1 with pymel.UndoChunk(): if iActiveWeight != iIdx: # Check is good, but we need to adjust the index after # Update the constraint information for the offset of the parent on which we will switch if iIdx == -1: pymel.parentConstraint(self.nSwConst, mo=True, e=True) pymel.setKeyframe(self.nSwConst.restTranslate, t=fCurTime, ott="step") pymel.setKeyframe(self.nSwConst.restRotate, t=fCurTime, ott="step") pymel.keyTangent(self.nSwConst.restTranslate, t=fCurTime, ott="step") # Force step pymel.keyTangent(self.nSwConst.restRotate, t=fCurTime, ott="step") # Force step else: iAdjustedIdx = self._get_adjusted_index(iIdx) pymel.parentConstraint(self.aDrivers[iIdx], self.nSwConst, mo=True, e=True) pymel.setKeyframe(self.nSwConst.target[iAdjustedIdx].targetOffsetTranslate, t=fCurTime, ott="step") pymel.setKeyframe(self.nSwConst.target[iAdjustedIdx].targetOffsetRotate, t=fCurTime, ott="step") pymel.keyTangent(self.nSwConst.target[iAdjustedIdx].targetOffsetTranslate, t=fCurTime, ott="step") # Force step pymel.keyTangent(self.nSwConst.target[iAdjustedIdx].targetOffsetRotate, t=fCurTime, ott="step") # Force step if iIdx == -1: for wAlias in aWeight: wAlias.set(0.0) else: for i, wAlias in enumerate(aWeight): if i == iIdx: wAlias.set(1.0) else: wAlias.set(0.0) # Set a keyframe on the weight to keep the animation pymel.setKeyframe(aWeight, t=fCurTime, ott="step") pymel.keyTangent(aWeight, ott="step") # Force step self.update_constraint_keys() def _adjust_firstKey(self, iTime, vRestT, vRestRot): """ Adjust the offset of the first constraint key in the system to prevent a snap when we move keys """ pymel.setCurrentTime(iTime) aWeight = self.nSwConst.getWeightAliasList() for i, w in enumerate(aWeight): if w.get() == 1: iParentIdx = self._get_adjusted_index(i) # Create a node as a fake parent to have an easiest way to extract the matrix nTempDriven = pymel.createNode("transform") nTempDriven.setTranslation(vRestT, space="world") nTempDriven.setRotation(vRestRot, space="world") vTrans = self._get_tm_offset(self.aDrivers[iParentIdx], _nDriven=nTempDriven, _type="t") vRot = self._get_tm_offset(self.aDrivers[iParentIdx], _nDriven=nTempDriven, _type="r") self.nSwConst.target[iParentIdx].targetOffsetTranslate.targetOffsetTranslateX.set(vTrans[0]) self.nSwConst.target[iParentIdx].targetOffsetTranslate.targetOffsetTranslateY.set(vTrans[1]) self.nSwConst.target[iParentIdx].targetOffsetTranslate.targetOffsetTranslateZ.set(vTrans[2]) self.nSwConst.target[iParentIdx].targetOffsetRotate.targetOffsetRotateX.set(vRot[0]) self.nSwConst.target[iParentIdx].targetOffsetRotate.targetOffsetRotateY.set(vRot[1]) self.nSwConst.target[iParentIdx].targetOffsetRotate.targetOffsetRotateZ.set(vRot[2]) pymel.setKeyframe(self.nSwConst.target[iParentIdx].targetOffsetTranslate, t=iTime, ott="step") pymel.setKeyframe(self.nSwConst.target[iParentIdx].targetOffsetRotate, t=iTime, ott="step") pymel.keyTangent(self.nSwConst.target[iParentIdx].targetOffsetTranslate, t=iTime, ott="step") # Force step pymel.keyTangent(self.nSwConst.target[iParentIdx].targetOffsetRotate, t=iTime, ott="step") # Force step pymel.delete(nTempDriven) def moveKey(self, _iNewFrame, _iOldFrame): """ Move a constraint key to another frame and ensure to update all constraint offset at the same time :param _iNewFrame: :param _iOldFrame: """ if _iNewFrame != _iOldFrame: pymel.refresh(su=True) with pymel.UndoChunk(): fCurTime = pymel.currentTime() # Check to collect all constraint keys we would need to update aAllKeysConst = pymel.keyframe(self.nSwConst, q=True) aAllKeysConst.sort() # Ensure to update all the keys that are after the move starting at the lowest frame change if _iNewFrame < _iOldFrame: iAdjustFrame = _iNewFrame else: iAdjustFrame = _iOldFrame # Get the rest data before moving the key in case we need to adjust the first frame pymel.setCurrentTime(iAdjustFrame) vRestT = self.nSwConst.constraintTranslate.get() vRestR = self.nSwConst.constraintRotate.get() pymel.keyframe(self.nSwConst, time=(_iOldFrame, _iOldFrame), o="over", timeChange=_iNewFrame) # Handle case where the move key become the first one in the animation if iAdjustFrame <= aAllKeysConst[0]: self._adjust_firstKey(iAdjustFrame, vRestT, vRestR) pymel.setCurrentTime(-1) self.update_constraint_keys() pymel.setCurrentTime(fCurTime) pymel.refresh(su=False) def deleteKey(self, _iFrame): """ Delete a constraint key and ensure everything is correctly ajusted in the animation """ pymel.refresh(su=True) with pymel.UndoChunk(): fCurTime = pymel.currentTime() # Check to collect all constraint keys we would need to update aAllKeysConst = pymel.keyframe(self.nSwConst, q=True) aAllKeysConst.sort() # Get the rest data before deleting the key in case we need to adjust the first frame pymel.setCurrentTime(_iFrame) vRestT = self.nSwConst.constraintTranslate.get() vRestR = self.nSwConst.constraintRotate.get() pymel.cutKey(self.nSwConst, time=(_iFrame, _iFrame)) # Handle case where the cut key become the first one in the animation if _iFrame == aAllKeysConst[0]: self._adjust_firstKey(_iFrame, vRestT, vRestR) pymel.setCurrentTime(_iFrame) self.update_constraint_keys() pymel.setCurrentTime(fCurTime) pymel.refresh(su=False) # src: https://knowledge.autodesk.com/search-result/caas/CloudHelp/cloudhelp/2015/ENU/Maya-SDK/files/ # GUID-3F96AF53-A47E-4351-A86A-396E7BFD6665-htm.html def getMayaWindow(): """ Return the pointer to maya window """ OpenMayaUI.MQtUtil.mainWindow() ptr = OpenMayaUI.MQtUtil.mainWindow() return QtCompat.wrapInstance(long(ptr), QtWidgets.QWidget) class Mode: def __init__(self): pass Inactive = 0, Create = 1, Add = 2, Switch = 3, SwitchSelect = 4, Remove = 5 class SpaceSwitcherDialog(QtWidgets.QMainWindow): def __init__(self, parent=getMayaWindow(), *args, **kwargs): super(SpaceSwitcherDialog, self).__init__(parent) self.ID_COL_FRAME = 0 self.ID_COL_PARENT = 1 self.ID_COL_ACTION = 2 self.sOriginalParent = "Original Parent" self.aConstList = ["XYZ", "XY", "XZ", "YZ", "X", "Y", "Z", "Not Constrained"] self.ui = uiSpaceSwitcher.Ui_win_main() self.ui.setupUi(self) # Setup the base list of parent self.createModel = QtGui.QStandardItemModel(self.ui.lstParent) self.parentItem = QtGui.QStandardItem(self.sOriginalParent) self.parentItem.setCheckable(False) self.parentItem.setEditable(False) self.createModel.appendRow(self.parentItem) self.ui.lstParent.setModel(self.createModel) self.ui.btnAction.setEnabled(False) self.ui.btnAction.setText("Select a Node") self.ui.cbSysList.addItem("--- Select a system ---") self.ui.lstParent.setEnabled(False) self.ui.cbPosition.addItems(self.aConstList) self.ui.cbPosition.setEnabled(False) self.ui.cbRotation.addItems(self.aConstList) self.ui.cbRotation.setEnabled(False) # Intern variable self.aEventCallbacksID = [] self.pTimeJobCallback = None self.pSceneUpdateID = None self.mode = Mode.Inactive self.nSelDriven = None self.aSelDrivers = [] self.pSelSpSys = None self.toRemove = [] self.aSceneSpaceSwitch = [] self.aConstrainedFrame = [] self.bInSelChanged = False self.bBlockSelJob = False self.colorTemplate = "<font color={0}>{1}</font>" self._setup_callbacks() # Force the tool to check the selection on it's opening self.refresh() def refresh(self): """ Refresh the tool information """ self.nSelDriven = None self._fetch_system_from_scene() self._callback_selection_change() def _setup_callbacks(self): """ Setup the button callback and also a callback in maya to know when a selection is changed """ self.ui.btnAction.pressed.connect(self._event_btnAction_pressed) self.ui.btnUpdateAll.pressed.connect(self._event_btnUpdateAll_pressed) self.ui.lstParent.clicked.connect(self._event_lstParent_selChanged) self.ui.cbSysList.currentIndexChanged.connect(self._event_cbSys_selChanged) self.ui.cbPosition.currentIndexChanged.connect(self._event_cbPosition_selChanged) self.ui.cbRotation.currentIndexChanged.connect(self._event_cbRotation_selChanged) self.ui.btnRefresh.pressed.connect(self._event_btnRefresh_pressed) ''' self.iJobSelChange = pymel.scriptJob(event=('SelectionChanged', self._scriptJob_selection_change), compressUndo=True) self.iJobSceneOpen = pymel.scriptJob(event=('SceneOpened', self._scriptJob_scene_opened), compressUndo=False) # Do not put a job on the undo, since it cause problem with undo's themselves self.iJobUndo = pymel.scriptJob(event=('Undo', self._scriptJob_scene_undo), compressUndo=False) ''' pUndoID = om.MEventMessage.addEventCallback("Undo", self._callback_scene_undoRedo) pRedoID = om.MEventMessage.addEventCallback("Redo", self._callback_scene_undoRedo) pSelectionChangeID = om.MEventMessage.addEventCallback("SelectionChanged", self._callback_selection_change) self.pSceneUpdateID = om.MSceneMessage.addCallback(om.MSceneMessage.kSceneUpdate, self._callback_scene_updated) self.pTimeJobCallback = om.MDGMessage.addTimeChangeCallback(self._scriptJob_timeChanged, "onTimeChange") self.aEventCallbacksID = [pUndoID, pRedoID, pSelectionChangeID] self.ui.tblFrameInfo.paintEvent = self._tblFrame_paintEvent def _fetch_system_from_scene(self): """ Get all SpaceSwitch system in the scene """ self.aSceneSpaceSwitch = [] self.ui.cbSysList.clear() self.ui.cbSysList.addItem("--- Select a system ---") lstNetworkNode = libSerialization.getNetworksByClass(SpaceSwitcherLogic.__name__) for pNet in lstNetworkNode: pData = libSerialization.import_network(pNet) # Check to ensure the data is valid, delete it if not if pData.nDriven is not None and pData.nSwConst is not None and pData.nSwConstRecept is not None: self.ui.cbSysList.addItem(pData.nDriven.name()) self.aSceneSpaceSwitch.append(pData) else: print("System {0} will be deleted because some data is invalid. Driven = {1}, Const = {2}, " "Const Recept = {3}".format(pNet, pData.nDriven, pData.nSwConst, pData.nSwConstRecept)) pymel.delete(pNet) def _set_mode_info(self, _mode, _bButtonEnabled): """ Set the tool mode information and ensure all button are correctly activated if needed """ bIsRef = False if self.pSelSpSys: bIsRef = pymel.referenceQuery(self.pSelSpSys.nSwConst, isNodeReferenced=True) self.ui.lblStatus.setText("Current Mode --> ") self.mode = _mode self.ui.btnAction.setEnabled(_bButtonEnabled) # Set the status label info depending of the current mode if _mode == Mode.Create: self.ui.lblStatus.setText(self.ui.lblStatus.text() + self.colorTemplate.format("yellow", "First Setup")) self.ui.btnAction.setText("Setup") elif _mode == Mode.Add: if not bIsRef: self.ui.lblStatus.setText(self.ui.lblStatus.text() + self.colorTemplate.format("green", "Add Parent")) self.ui.btnAction.setText("Add") else: self.ui.lblStatus.setText(self.ui.lblStatus.text() + self.colorTemplate.format("Gray", "Add Parent (Blocked Reference)")) self.ui.btnAction.setText("Add (Blocked)") self.ui.btnAction.setEnabled(False) elif _mode == Mode.Switch or _mode == Mode.SwitchSelect: self.ui.lblStatus.setText(self.ui.lblStatus.text() + self.colorTemplate.format("green", "Switch Parent")) self.ui.btnAction.setText("Switch") elif _mode == Mode.Remove: if not bIsRef: self.ui.lblStatus.setText(self.ui.lblStatus.text() + self.colorTemplate.format("red", "Remove Parent")) self.ui.btnAction.setText("Remove") else: self.ui.lblStatus.setText(self.ui.lblStatus.text() + self.colorTemplate.format("Gray", "Remove Parent (Blocked Reference)")) self.ui.btnAction.setText("Remove (Blocked)") self.ui.btnAction.setEnabled(False) else: self.ui.lblStatus.setText(self.ui.lblStatus.text() + "Inactive") self.ui.btnAction.setText("Select a Node") def _callback_selection_change(self, *args): """ Manage the selection change to know which action the user want to do. The remove action need to be implemented another way """ if not self.bBlockSelJob: self.bInSelChanged = True aCurSel = pymel.selected() if len(aCurSel) == 0: self.nSelDriven = None self.aSelDrivers = [] elif len(aCurSel) == 1: self.nSelDriven = aCurSel[0] self.aSelDrivers = [] else: self.nSelDriven = aCurSel[-1] self.aSelDrivers = aCurSel[0:-1] self._set_mode_info(Mode.Inactive, False) self.pSelSpSys = None if self.nSelDriven is not None: # Look for existing space switcher system for i, pSp in enumerate(self.aSceneSpaceSwitch): if pSp.nDriven == self.nSelDriven: self.pSelSpSys = pSp break self._update_info(self.pSelSpSys) if self.pSelSpSys is None: #Check to ensure that the callback will not catch a network node when we create a new system if pymel.nodeType(self.nSelDriven) != "network": nDrivenParent = self.nSelDriven.getParent() if nDrivenParent is None and pymel.referenceQuery(self.nSelDriven, isNodeReferenced=True): self._set_mode_info(Mode.Create, False) else: # TODO - Check if the parent can possibly receive a constraint on it self._set_mode_info(Mode.Create, True) else: if self.aSelDrivers: if not self.pSelSpSys.is_parent_exist(self.aSelDrivers): # If no selected parent already exist self._set_mode_info(Mode.Add, True) else: if len(self.aSelDrivers) == 1: self._set_mode_info(Mode.SwitchSelect, True) iParentIdx = self.pSelSpSys.aDrivers.index(self.aSelDrivers[0]) pIdx = self.ui.lstParent.model().createIndex(iParentIdx + 1, 0) self.ui.lstParent.selectionModel().select(pIdx, QtCore.QItemSelectionModel.Select) else: self._set_mode_info(Mode.Add, True) else: # If a parent is selected in the list, active the button to do the switch pSel = self.ui.lstParent.selectedIndexes() if pSel: self._set_mode_info(Mode.Switch, True) else: self._set_mode_info(Mode.SwitchSelect, True) pIdx = self.ui.lstParent.model().createIndex(0, 0) self.ui.lstParent.selectionModel().select(pIdx, QtCore.QItemSelectionModel.Select) else: self._update_info(None) self.bInSelChanged = False def _callback_scene_updated(self, *args): """ Find all SpaceSwitcher system in the scene """ self._fetch_system_from_scene() def _callback_scene_undoRedo(self, *args): """ Ensure to refresh the UI on a undo in the scene """ if self.pSelSpSys and pymel.selected(): self._update_info(self.pSelSpSys) else: self._update_info(None) def _scriptJob_timeChanged(self, *args): """ Callbacks that trigger when the time change """ self.ui.tblFrameInfo.viewport().update() def _tblFrame_paintEvent(self, event): """ Override the table paint event to redraw it when we need too :param event: """ super(QtWidgets.QTableWidget, self.ui.tblFrameInfo).paintEvent(event) iRowCount = self.ui.tblFrameInfo.rowCount() iCurTime = int(pymel.currentTime()) for i in range(0, iRowCount): iFrameAfter = 9999999999 pRow = self.ui.tblFrameInfo.item(i, self.ID_COL_FRAME) if i < iRowCount - 1: pRowAfter = self.ui.tblFrameInfo.item(i + 1, self.ID_COL_FRAME) iFrameAfter = pRowAfter.data(QtCore.Qt.UserRole) iFrame = pRow.data(QtCore.Qt.UserRole) pWidget = self.ui.tblFrameInfo.cellWidget(i, self.ID_COL_FRAME) pPal = pWidget.palette() if iFrame <= iCurTime < iFrameAfter: pPal.setColor(pWidget.backgroundRole(), QtCore.Qt.darkRed) elif iCurTime < iFrame and i == 0: pPal.setColor(pWidget.backgroundRole(), QtCore.Qt.darkRed) else: pPal.setColor(pWidget.backgroundRole(), QtCore.Qt.black) pWidget.setPalette(pPal) def closeEvent(self, *args, **kwargs): """ Try to kill the script job when the window is closed :param args: :param kwargs: """ try: om.MDGMessage.removeCallback(self.pTimeJobCallback) for pId in self.aEventCallbacksID: om.MEventMessage.removeCallback(pId) om.MSceneMessage.removeCallback(self.pSceneUpdateID) except: pass def _update_info(self, pSpData): """ Small wrapper to update all needed info in the UI """ if pSpData: iCurSys = self.aSceneSpaceSwitch.index(pSpData) if iCurSys != None: self.ui.cbSysList.setCurrentIndex(iCurSys + 1) # First item is empty self.ui.btnUpdateAll.setEnabled(True) else: self.ui.cbSysList.setCurrentIndex(0) self.ui.btnUpdateAll.setEnabled(False) else: self.ui.cbSysList.setCurrentIndex(0) # First item is empty self.ui.btnUpdateAll.setEnabled(False) self._update_lstParent(pSpData) self._update_tblFrameInfo(pSpData) self._update_cbAxis(pSpData) def _update_cbAxis(self, pData): """ Update constrained axis info for selected system """ if pData is not None: self.ui.cbPosition.setEnabled(True) self.ui.cbRotation.setEnabled(True) sXPos = "" sYPos = "" sZPos = "" sXRot = "" sYRot = "" sZRot = "" if self.pSelSpSys.nSwConstRecept.translateX.listConnections(): sXPos = "X" if self.pSelSpSys.nSwConstRecept.translateY.listConnections(): sYPos = "Y" if self.pSelSpSys.nSwConstRecept.translateZ.listConnections(): sZPos = "Z" if self.pSelSpSys.nSwConstRecept.rotateX.listConnections(): sXRot = "X" if self.pSelSpSys.nSwConstRecept.rotateY.listConnections(): sYRot = "Y" if self.pSelSpSys.nSwConstRecept.rotateZ.listConnections(): sZRot = "Z" sFinalPos = sXPos + sYPos + sZPos sFinalRot = sXRot + sYRot + sZRot if sFinalPos != "": self.ui.cbPosition.setCurrentIndex(self.aConstList.index(sFinalPos)) else: self.ui.cbPosition.setCurrentIndex(len(self.aConstList) - 1) if sFinalPos != "": self.ui.cbRotation.setCurrentIndex(self.aConstList.index(sFinalRot)) else: self.ui.cbRotation.setCurrentIndex(len(self.aConstList) - 1) else: self.ui.cbPosition.setCurrentIndex(0) self.ui.cbPosition.setCurrentIndex(0) self.ui.cbPosition.setEnabled(False) self.ui.cbRotation.setEnabled(False) def _update_lstParent(self, pSpData): """ Update the parent list for the selected system """ self.createModel.clear() if pSpData: self.ui.lstParent.setEnabled(True) self.createModel.appendRow(self.parentItem) for iIdx, nParentInfo in enumerate(pSpData.aDrivers): newParentItem = QtGui.QStandardItem(nParentInfo.name()) newParentItem.setEditable(False) # Prevent any delete action when the sysem is referenced if pymel.referenceQuery(self.pSelSpSys.nSwConst, isNodeReferenced=True): newParentItem.setCheckable(False) else: newParentItem.setCheckable(True) self.createModel.appendRow(newParentItem) else: self.ui.lstParent.setEnabled(False) self.createModel.appendRow(self.parentItem) def _update_tblFrameInfo(self, pSpData): """ Update the frame/parent info with the selected system """ # Clear the table info and refresh it self.ui.tblFrameInfo.setRowCount(0) self.aConstrainedFrame = [] if pSpData: aWeight = pSpData.nSwConst.getWeightAliasList() aZeroKey = [] # List of frame which have key at 0 on all parent aPreventZeroKey = [] # List of frame we know it's not all parent to 0 aKeyParent = [] # List of tuple representing the frame with the parent index # Check to collect all constraint keys we would need to update for i, w in enumerate(aWeight): aKeyTime = pymel.keyframe(w, q=True) for iTime in aKeyTime: if w.get(time=iTime) == 1.0: # Keep info about frame/parent aKeyParent.append((iTime, i)) if iTime not in aPreventZeroKey: aPreventZeroKey.append(iTime) else: if iTime not in aZeroKey: aZeroKey.append(iTime) # Keep possible frame to be one without any parent active for iTime in aZeroKey: if iTime not in aPreventZeroKey: aKeyParent.append((iTime, -1)) # Sort by Frame order (Need to be reversed to be in the good frame order) aKeyParent.sort() # Create a list of the parent name to use in the combo box that will be created aParentName = [nParent.name() for nParent in pSpData.aDrivers] aParentName.insert(0, self.sOriginalParent) for pTblInfo in aKeyParent: iNbRow = self.ui.tblFrameInfo.rowCount() self.ui.tblFrameInfo.insertRow(iNbRow) # Frame Field pFrameCell = QtWidgets.QTableWidgetItem() self.ui.tblFrameInfo.setItem(iNbRow, self.ID_COL_FRAME, pFrameCell) edtFrame = QtWidgets.QLineEdit() edtFrame.setAutoFillBackground(True) edtFrame.setValidator(QDoubleEmptyStringValidator()) edtFrame.setText(str(int(pTblInfo[0]))) edtFrame.returnPressed.connect(partial(self._event_edtFrame_changed, iNbRow)) edtFrame.editingFinished.connect(partial(self._event_edtFrame_endEdit, iNbRow)) ''' Monkey patch the mouse event function to create a right click menu on it. I could have redefined a class that inherit the QLineEdit class, but.... ''' edtFrame.mousePressEvent = partial(self._event_edtFrame_mousePress, iRow=iNbRow) self.ui.tblFrameInfo.setCellWidget(iNbRow, self.ID_COL_FRAME, edtFrame) pFrameCell.setData(QtCore.Qt.UserRole, int(pTblInfo[0])) # Parent Field pCellParent = QtWidgets.QTableWidgetItem() self.ui.tblFrameInfo.setItem(iNbRow, self.ID_COL_PARENT, pCellParent) cbParent = QtWidgets.QComboBox() cbParent.setMaximumWidth(200) cbParent.addItems(aParentName) cbParent.setCurrentIndex( pTblInfo[1] + 1) # Index is always +1 since original parent it -1 in the system cbParent.currentIndexChanged.connect(partial(self._event_cbParent_indexChanged, iNbRow)) cbParent.wheelEvent = self._event_cbParent_wheel # Override the wheel event to prevent change with it cbParent.setFocusPolicy(QtCore.Qt.ClickFocus) self.ui.tblFrameInfo.setCellWidget(iNbRow, self.ID_COL_PARENT, cbParent) pCellParent.setData(QtCore.Qt.UserRole, pTblInfo[1]) pCellAction = QtWidgets.QTableWidgetItem() self.ui.tblFrameInfo.setItem(iNbRow, self.ID_COL_ACTION, pCellAction) btnRemove = QtWidgets.QPushButton() btnRemove.setText("Remove") btnRemove.pressed.connect(partial(self._event_btnRemove_pressed, iNbRow)) self.ui.tblFrameInfo.setCellWidget(iNbRow, self.ID_COL_ACTION, btnRemove) self.aConstrainedFrame.append(int(pTblInfo[0])) self.ui.tblFrameInfo.resizeColumnToContents(self.ID_COL_PARENT) def _event_edtFrame_mousePress(self, event, iRow=0): """ Generate a right click on the QLineEdit with the frame number to allow the user to delete a key """ if event.button() == QtCore.Qt.RightButton: # Get data pFrameCell = self.ui.tblFrameInfo.item(iRow, self.ID_COL_FRAME) iFrame = pFrameCell.data(QtCore.Qt.UserRole) menu = QtWidgets.QMenu() action_sel_parent = menu.addAction('Remove') action_sel_parent.triggered.connect(partial(self._event_rcMenu_deleteKey, iFrame)) menu.exec_(QtGui.QCursor.pos()) def _event_cbParent_wheel(self, event): """ Empty override to prevent the user to change the parent with the mouse wheel """ pass def _event_rcMenu_deleteKey(self, iFrame): """ Right-Click menu action to delete a constrained key """ self.pSelSpSys.deleteKey(iFrame) self._update_tblFrameInfo(self.pSelSpSys) def _event_btnUpdateAll_pressed(self): """ Update all the constraint offset, can be usefull if a parent have been moved """ self.pSelSpSys.update_constraint_keys(_updateAll=True) def _event_btnAction_pressed(self): """ Manage the different action that can happen on the tool. Will change depending on the selection """ if self.mode == Mode.Create: if pymel.referenceQuery(self.nSelDriven, isNodeReferenced=True): bCreateParent = False else: if self.nSelDriven.getParent() is not None: bCreateParent = False else: bCreateParent = True #Block undo and selection changed callback for the moment we need export the network pymel.undoInfo(stateWithoutFlush=False) self.bBlockSelJob = True pNewSp = SpaceSwitcherLogic() if self.aSelDrivers: pNewSp.setup_space_switch(self.nSelDriven, self.aSelDrivers, bCreateWolrdNode=False, bCreateParent=bCreateParent) else: # There is no drivers, so the user want the world to be one of them pNewSp.setup_space_switch(self.nSelDriven, self.aSelDrivers, bCreateWolrdNode=True, bCreateParent=bCreateParent) libSerialization.export_network(pNewSp) self.bBlockSelJob = False pymel.undoInfo(stateWithoutFlush=True) self.ui.cbSysList.addItem(pNewSp.nDriven.name()) self.aSceneSpaceSwitch.append(pNewSp) elif self.mode == Mode.Add: #Block undo and selection changed callback for the moment we need export the network pymel.undoInfo(stateWithoutFlush=False) self.bBlockSelJob = True self.pSelSpSys.add_target(self.aSelDrivers) # Delete the old network before updating a new one aNetwork = libSerialization.getConnectedNetworks(self.pSelSpSys.nDriven, recursive=False) pymel.delete(aNetwork) libSerialization.export_network(self.pSelSpSys) self.bBlockSelJob = False pymel.undoInfo(stateWithoutFlush=True) elif self.mode == Mode.Switch: pCurParent = self.ui.lstParent.selectedIndexes()[0] # Remove one to the index since the original parent doesn't really exist in the list of parent in the system self.pSelSpSys.do_switch(pCurParent.row() - 1) self._update_tblFrameInfo(self.pSelSpSys) elif self.mode == Mode.SwitchSelect: # Find the selected parent index if len(self.aSelDrivers) == 0: iSwitchIdx = -1 else: iSwitchIdx = 0 for idx, nDriver in enumerate(self.pSelSpSys.aDrivers): if nDriver == self.aSelDrivers[0]: iSwitchIdx = idx self.pSelSpSys.do_switch(iSwitchIdx) self._update_tblFrameInfo(self.pSelSpSys) elif self.mode == Mode.Remove: iNbTarget = len(self.pSelSpSys.aDrivers) self.toRemove.sort(reverse=True) # Ensure to remove from the bigger to the smaller index # Delete the network aNetwork = libSerialization.getConnectedNetworks(self.pSelSpSys.nDriven, recursive=False) pymel.delete(aNetwork) if iNbTarget == len(self.toRemove): # Totally remove the constraint self.pSelSpSys.remove_target(-1, _all=True) self.aSceneSpaceSwitch.remove(self.pSelSpSys) self.pSelSpSys = None else: #Block undo and selection changed callback for the moment we need export the network pymel.undoInfo(stateWithoutFlush=False) self.bBlockSelJob = True for iIdx in self.toRemove: self.pSelSpSys.remove_target(iIdx - 1) # Recreate the network with refreshed data libSerialization.export_network(self.pSelSpSys) self.bBlockSelJob = True pymel.undoInfo(stateWithoutFlush=True) pymel.select(self.nSelDriven) def _event_lstParent_selChanged(self): """ Manage the parent list selection change """ # First look if there is any checked out item if not self.bInSelChanged: self.toRemove = [] for iIdx in xrange(self.createModel.rowCount()): pItem = self.createModel.item(iIdx) if pItem.isCheckable(): if pItem.checkState() == QtCore.Qt.Checked: self.toRemove.append(iIdx) if self.toRemove: self._set_mode_info(Mode.Remove, True) else: # Prevent a stuck status when unchecking all item self._set_mode_info(Mode.Switch, False) if self.mode == Mode.Switch: pSel = self.ui.lstParent.selectedIndexes() if pSel: self._set_mode_info(Mode.Switch, True) else: self._set_mode_info(Mode.Switch, False) def _event_edtFrame_changed(self, iRow): """ Manage a frame change in the frame info table """ pCellQLine = self.ui.tblFrameInfo.cellWidget(iRow, self.ID_COL_FRAME) if pCellQLine.text() != "": pCellFrame = self.ui.tblFrameInfo.item(iRow, self.ID_COL_FRAME) iOldFrame = pCellFrame.data(QtCore.Qt.UserRole) # pCellParent = self.ui.tblFrameInfo.item(iRow, self.ID_COL_PARENT) # iParentIdx = pCellParent.data(QtCore.Qt.UserRole) iNewFrame = int(pCellQLine.text()) # Prevent the user to move a key on a frame already constrained if not (iNewFrame in self.aConstrainedFrame): self.pSelSpSys.moveKey(iNewFrame, iOldFrame) self._update_tblFrameInfo(self.pSelSpSys) else: pCellQLine.setText(str(iOldFrame)) pymel.select(self.nSelDriven) def _event_edtFrame_endEdit(self, iRow): """ Ensure that the frame data is still shown if the user put no frame """ pCell = self.ui.tblFrameInfo.item(iRow, self.ID_COL_FRAME) iCurFrame = pCell.data(QtCore.Qt.UserRole) pCellQLine = self.ui.tblFrameInfo.cellWidget(iRow, self.ID_COL_FRAME) if pCellQLine and pCellQLine.text() == "": pCellQLine.setText(str(iCurFrame)) def _event_cbParent_indexChanged(self, _iRow, _iIndex): """ Manage a parent change in the frame info table """ iCurTime = pymel.currentTime() pFrameQLine = self.ui.tblFrameInfo.cellWidget(_iRow, self.ID_COL_FRAME) iFrame = int(pFrameQLine.text()) pymel.refresh(su=True) pymel.setCurrentTime(iFrame) self.pSelSpSys.do_switch(_iIndex - 1) # Combo Box index are bigger than the real index system pymel.setCurrentTime(iCurTime) pymel.refresh(su=False) def _event_cbSys_selChanged(self, _iIdx): """ Manage the system change with the combo box. select the node related to the system selected in the combo box """ # Prevent a node selection when the comboBox index changed during selection changed callback if not self.bInSelChanged: if _iIdx > 0: pCurSys = self.aSceneSpaceSwitch[_iIdx - 1] pymel.select(pCurSys.nDriven) else: # If the no system index is selected, but a node is selected in the scene, put back the selected system for i, pSp in enumerate(self.aSceneSpaceSwitch): if pSp.nDriven == self.nSelDriven: # Change the index, but prevent the event to select back the node self.bInSelChanged = True self.ui.cbSysList.setCurrentIndex(i + 1) self.bInSelChanged = False break def _event_cbPosition_selChanged(self, _iIdx): """ Manage a system constrained axis change. Change the connected position attributes between the constraint and the constraint recept """ if self.pSelSpSys is not None and not self.bInSelChanged: with pymel.UndoChunk(): sPos = self.ui.cbPosition.itemText(_iIdx) if sPos.find("X") != -1: if not self.pSelSpSys.nSwConstRecept.translateX.listConnections(): self.pSelSpSys.nSwConst.constraintTranslateX.connect(self.pSelSpSys.nSwConstRecept.translateX) else: if self.pSelSpSys.nSwConstRecept.translateX.listConnections(): self.pSelSpSys.nSwConstRecept.translateX.disconnect() # Set to 0, we don't want the constraint to affect pos self.pSelSpSys.nSwConstRecept.translateX.set(0.0) if sPos.find("Y") != -1: if not self.pSelSpSys.nSwConstRecept.translateY.listConnections(): self.pSelSpSys.nSwConst.constraintTranslateY.connect(self.pSelSpSys.nSwConstRecept.translateY) else: if self.pSelSpSys.nSwConstRecept.translateY.listConnections(): self.pSelSpSys.nSwConstRecept.translateY.disconnect() # Set to 0, we don't want the constraint to affect pos self.pSelSpSys.nSwConstRecept.translateY.set(0.0) if sPos.find("Z") != -1: if not self.pSelSpSys.nSwConstRecept.translateZ.listConnections(): self.pSelSpSys.nSwConst.constraintTranslateZ.connect(self.pSelSpSys.nSwConstRecept.translateZ) else: if self.pSelSpSys.nSwConstRecept.translateZ.listConnections(): self.pSelSpSys.nSwConstRecept.translateZ.disconnect() # Set to 0, we don't want the constraint to affect pos self.pSelSpSys.nSwConstRecept.translateZ.set(0.0) self.pSelSpSys.update_constraint_keys() def _event_cbRotation_selChanged(self, _iIdx): """ Manage a system constrained axis change. Change the connected rotation attributes between the constraint and the constraint recept """ if self.pSelSpSys is not None and not self.bInSelChanged: with pymel.UndoChunk(): sRot = self.ui.cbRotation.itemText(_iIdx) if sRot.find("X") != -1: if not self.pSelSpSys.nSwConstRecept.rotateX.listConnections(): self.pSelSpSys.nSwConst.constraintRotateX.connect(self.pSelSpSys.nSwConstRecept.rotateX) else: if self.pSelSpSys.nSwConstRecept.rotateX.listConnections(): self.pSelSpSys.nSwConstRecept.rotateX.disconnect() # Set to 0, we don't want the constraint to affect rot self.pSelSpSys.nSwConstRecept.rotateX.set(0.0) if sRot.find("Y") != -1: if not self.pSelSpSys.nSwConstRecept.rotateY.listConnections(): self.pSelSpSys.nSwConst.constraintRotateY.connect(self.pSelSpSys.nSwConstRecept.rotateY) else: if self.pSelSpSys.nSwConstRecept.rotateY.listConnections(): self.pSelSpSys.nSwConstRecept.rotateY.disconnect() # Set to 0, we don't want the constraint to affect rot self.pSelSpSys.nSwConstRecept.rotateY.set(0.0) if sRot.find("Z") != -1: if not self.pSelSpSys.nSwConstRecept.rotateZ.listConnections(): self.pSelSpSys.nSwConst.constraintRotateZ.connect(self.pSelSpSys.nSwConstRecept.rotateZ) else: if self.pSelSpSys.nSwConstRecept.rotateZ.listConnections(): self.pSelSpSys.nSwConstRecept.rotateZ.disconnect() # Set to 0, we don't want the constraint to affect rot self.pSelSpSys.nSwConstRecept.rotateZ.set(0.0) self.pSelSpSys.update_constraint_keys() def _event_btnRefresh_pressed(self): """ Refresh Button pressed Event """ self.refresh() def _event_btnRemove_pressed(self, iRow): """ Delete the key in the same row than the delete button """ pFrameCell = self.ui.tblFrameInfo.item(iRow, self.ID_COL_FRAME) iFrame = pFrameCell.data(QtCore.Qt.UserRole) self.pSelSpSys.deleteKey(iFrame) self._update_tblFrameInfo(self.pSelSpSys) class SpaceSwitcher(object): """ This class is used to create the main dialog and have the name of the file, so it need to exist to be correctly called """ def __init__(self, *args, **kwargs): # Try to kill the existing window try: if cmds.window("SpaceSwitcher", ex=True): cmds.deleteUI("SpaceSwitcher") except: pass self.pDialog = SpaceSwitcherDialog(getMayaWindow()) self.centerDialog() self.pDialog.setWindowTitle("Space Switcher") self.pDialog.setObjectName("SpaceSwitcher") self.pDialog.show() def centerDialog(self): # Create a frame geo to easilly move it from the center pFrame = self.pDialog.frameGeometry() pScreen = QtWidgets.QApplication.desktop().screenNumber(QtWidgets.QApplication.desktop().cursor().pos()) ptCenter = QtWidgets.QApplication.desktop().screenGeometry(pScreen).center() pFrame.moveCenter(ptCenter) self.pDialog.move(pFrame.topLeft())