class AddInfluenceWidget(ui.QWidget): """ Widget used to add influences. Will emit the 'addInfluence' signal when the add button is released. :param QWidget parent: """ addInfluence = ui.Signal() def __init__(self, parent): ui.QWidget.__init__(self, parent) # create layout layout = ui.QHBoxLayout(self) layout.setContentsMargins(3, 3, 3, 3) layout.setSpacing(5) # create add button add = IconButton(self, ADD_ICON) add.setFlat(True) add.released.connect(self.addInfluence.emit) layout.addWidget(add) # create title label = ui.QLabel(self) label.setText("Add Influence") label.setFont(ui.BOLT_FONT) layout.addWidget(label)
class VertexInfluenceWidget(ui.QWidget): """ Widget used to manage the influence weight. A label, slider and spinbox are created that can be manipulated by the user. Also can an influence be locked so its values cannot be changed. :param QWidget parent: :param str influence: :param float value: """ signal = ui.Signal(str, float) def __init__(self, parent, influence, value): ui.QWidget.__init__(self, parent) # variables self.influence = influence self.value = value isLocked = cmds.getAttr("{0}.liw".format(influence)) # create layout layout = ui.QHBoxLayout(self) layout.setContentsMargins(3, 3, 3, 3) layout.setSpacing(5) # create lock self.lock = ui.QPushButton(self) self.lock.setFlat(True) self.lock.setCheckable(True) self.lock.setIcon(ui.QIcon(LOCK_ICON.get(isLocked))) self.lock.setFixedSize(ui.QSize(18, 18)) self.lock.released.connect(self.toggleLocked) layout.addWidget(self.lock) # create label name = ui.QLabel(self) name.setText(influence) name.setFont(ui.FONT) name.setMinimumWidth(150) layout.addWidget(name) # create slider self.slider = ui.QSlider(self) self.slider.setInputMethodHints(ui.Qt.ImhNone) self.slider.setMaximum(1000) self.slider.setSingleStep(1) self.slider.setOrientation(ui.Qt.Horizontal) self.slider.valueChanged.connect(self.sliderValueChanged) layout.addWidget(self.slider) # create label self.spinbox = ui.QDoubleSpinBox(self) self.spinbox.setFont(ui.FONT) self.spinbox.setDecimals(3) self.spinbox.setRange(0, 1) self.spinbox.setSingleStep(0.001) self.spinbox.valueChanged.connect(self.spinboxValueChanged) layout.addWidget(self.spinbox) # set ui self.setValue(value) if isLocked: self.lock.setChecked(True) self.spinbox.setEnabled(False) self.slider.setEnabled(False) # ------------------------------------------------------------------------ def isLocked(self): """ :return: Locked state of the influence :rtype: bool """ return self.lock.isChecked() def toggleLocked(self): """ Toggle the locked state of the influence. Will enable or disable the input fields of the influence and update the lock icon. """ isLocked = self.isLocked() self.lock.setIcon(LOCK_ICON.get(isLocked)) self.spinbox.setEnabled(not isLocked) self.slider.setEnabled(not isLocked) # ------------------------------------------------------------------------ def getSliderValue(self): """ :return: Weight value read from the slider. :rtype: float """ return float(self.slider.value()) / 1000 def getSpinboxValue(self): """ :return: Weight value read from the spinbox. :rtype: float """ return self.spinbox.value() def setValue(self, value): """ Set the value of the influence. Will block the signals as the valueChanged callback shouldn't be triggered. :param float value: Influence weight value """ self.value = value with ui.BlockSignals(self.spinbox, self.slider): self.spinbox.setValue(value) self.slider.setValue(int(value * 1000)) # ------------------------------------------------------------------------ def spinboxValueChanged(self): self.signal.emit(self.influence, self.getSpinboxValue()) def sliderValueChanged(self): self.signal.emit(self.influence, self.getSliderValue())
class VertexInfluencesWidget(ui.QWidget): """ Widget used to manage the collection of influences. Will loop over all of the influences and instance widgets :class:`VertexInfluenceWidget`. :param QWidget parent: :param list data: Sorted list of values and influences """ signal = ui.Signal(list) warningSignal = ui.Signal(bool) def __init__(self, parent, skinCluster, data): ui.QWidget.__init__(self, parent) #self.setFrameShape(ui.QFrame.StyledPanel) #self.setFrameShadow(ui.QFrame.Sunken) # variables self.widgets = [] # skin cluster data self.normalizeWeights = cmds.getAttr( "{0}.normalizeWeights".format(skinCluster)) self.maxInfluences = cmds.getAttr( "{0}.maxInfluences".format(skinCluster)) self.maintainMaxInfluences = cmds.getAttr( "{0}.maintainMaxInfluences".format(skinCluster)) # create layout layout = ui.QVBoxLayout(self) layout.setContentsMargins(3, 3, 3, 3) layout.setSpacing(3) # create widgets for value, influence in data: widget = VertexInfluenceWidget(self, influence, value) widget.signal.connect(self.calculateWeights) layout.addWidget(widget) self.widgets.append(widget) # set visibility self.displayInfluences(False) # ------------------------------------------------------------------------ def displayInfluences(self, state): """ Based on the state the influences will be displayed always or only when the value is more than 0. This is done to make the influences overview less crowded and easier to manage. :param bool state: Influence visibility state """ for widget in self.widgets: if widget.value > sys.float_info.epsilon: widget.setVisible(True) continue widget.setVisible(state) def displayMaxInfluences(self): """ Determines if the maximum influences is exceeded and will emit a warning signal so the user can be alerted this is happening. """ if self.maintainMaxInfluences: num = len([ 0 for widget in self.widgets if widget.value > sys.float_info.epsilon ]) if num > self.maxInfluences: self.warningSignal.emit(True) return self.warningSignal.emit(False) # ------------------------------------------------------------------------ def resetWeights(self): """ Reset the influence weights to the values before the input was changed. This sometimes needs to happen if the user does invalid actions. Like trying to change the weights, while all other influences are locked. """ for widget in self.widgets: widget.setValue(widget.value) def calculateWeights(self, changedInfluence, changedValue): """ Calculate the new weights based on the values of the skin cluster and updated ui fields. Of normalization mode is activated the new weights will be normalized, this will also be reflected in the ui. Locked weights are respected and won't be changed. The new weights will be formatted in a list that can be used by Maya's skinPercent command. This list will then be emmited to be picked up by another function. :param str changedInfluence: Influence name :param float changedValue: Influence value """ # get new weights weightsList = [] weightsDict = { widget.influence: widget.value for widget in self.widgets } weightsDict[changedInfluence] = changedValue # normalize weights if self.normalizeWeights == 1: factor = 1 # get normalizable weights normalizeable = [ widget.influence for widget in self.widgets if widget.value > 0 and widget.influence != changedInfluence and not widget.isLocked() ] # get normalization factor if normalizeable: # get total = sum(weightsDict.values()) normal = sum([ weight for influence, weight in weightsDict.iteritems() if influence in normalizeable ]) factor = (1 - total + normal) / normal # reset and return if no normalizable weights are found else: self.resetWeights() return # set updated weights for influence, weight in weightsDict.iteritems(): if influence in normalizeable: weightsDict[influence] = weight * factor # update ui for widget in self.widgets: widget.setValue(weightsDict[widget.influence]) # update list weightsList = [[influence, weight] for influence, weight in weightsDict.iteritems()] self.signal.emit(weightsList) self.displayMaxInfluences()
class VertexLabelWidget(ui.QWidget): """ Widget used to manage the vertex that is parsed. Will create a label, warning label and checkbox to display. :param QWidget parent: :param str vertex: """ signal = ui.Signal(bool) def __init__(self, parent, vertex): ui.QWidget.__init__(self, parent) # create layout layout = ui.QHBoxLayout(self) layout.setContentsMargins(3, 3, 3, 3) layout.setSpacing(10) # create label name = ui.QLabel(self) name.setText(vertex.split("|")[-1]) name.setFont(ui.BOLT_FONT) name.setStyleSheet(ORANGE_STYLESHEET) # set label size policy sizePolicy = ui.QSizePolicy(ui.QSizePolicy.Preferred, ui.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(1) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(name.sizePolicy().hasHeightForWidth()) name.setSizePolicy(sizePolicy) layout.addWidget(name) # create warning self.warning = ui.QLabel(self) self.warning.setText("Exceeded Maximum Influences") self.warning.setFont(ui.FONT) self.warning.setStyleSheet(RED_STYLESHEET) self.warning.setVisible(False) layout.addWidget(self.warning) # create checkbox self.checkbox = ui.QCheckBox(self) self.checkbox.setText("Display All Influences") self.checkbox.setFont(ui.FONT) self.checkbox.stateChanged.connect(self.checkboxStateChanged) layout.addWidget(self.checkbox) # ------------------------------------------------------------------------ def displayWarning(self, state): """ Update the visibility of the warning based on the input. Shows that the user has exceeded the maximum allowed influences. :param bool state: Visible state of the warning """ self.warning.setVisible(state) # ------------------------------------------------------------------------ def checkboxStateChanged(self): self.signal.emit(self.checkbox.isChecked())
class InfluenceWidget(ui.QWidget): """ Widget used to set the influence and soft selection. Once a new soft selection is made the 'setSoftSelection' signal will be emitted. :param QWidget parent: """ setSoftSelection = ui.Signal() def __init__(self, parent): ui.QWidget.__init__(self, parent) # variable self._influence = None self._ssActive = None self._ssData = {} self._ssSettings = {} # create layout layout = ui.QHBoxLayout(self) layout.setContentsMargins(3, 3, 3, 3) layout.setSpacing(5) # create joint joint = IconButton(self, JOINT_ICON) joint.released.connect(self.setInfluenceFromSelection) layout.addWidget(joint) # create soft select soft = IconButton(self, SOFTSELECT_ICON) soft.released.connect(self.setSoftSelectionFromSelection) layout.addWidget(soft) # create label self.label = ui.QLabel(self) self.label.setText("< influence >") self.label.setFont(ui.FONT) layout.addWidget(self.label) # create remove button remove = IconButton(self, REMOVE_ICON) remove.setFlat(True) remove.released.connect(self.deleteLater) layout.addWidget(remove) # ------------------------------------------------------------------------ @property def influence(self): return self._influence @influence.setter def influence(self, influence): self._influence = influence # ------------------------------------------------------------------------ def setInfluenceFromSelection(self): """ Get all of the joints in the current selection. If no joints are selected a RuntimeError will be raised and the UI reset. :raises RuntimeError: if no joints are selected """ # get selected joints joints = cmds.ls(sl=True, l=True, type="joint") # if no joints selected reset ui if not joints: self.influence = None self.label.setText("< influence >") raise RuntimeError("No joint selection detected!") self.influence = joints[0] self.label.setText(joints[0].split("|")[-1]) # ------------------------------------------------------------------------ @property def ssActive(self): return self._ssActive @ssActive.setter def ssActive(self, value): self._ssActive = value @property def ssSettings(self): return self._ssSettings @ssSettings.setter def ssSettings(self, value): self._ssSettings = value @property def ssData(self): return self._ssData @ssData.setter def ssData(self, value): self._ssData = value # ------------------------------------------------------------------------ def setSoftSelectionFromSelection(self): """ Get the current soft selection. If no soft selection is made a RuntimeError will be raised. :raises RuntimeError: if no soft selection is made """ self.ssActive, self.ssData = getSoftSelection() self.setSoftSelection.emit() # reset values if no soft selection if not self.ssData: self.ssActive = None self.ssData = {} self.ssSettings = {} raise RuntimeError("No soft selection detected!") self.ssSettings = { "ssc": cmds.softSelect(query=True, ssc=True), "ssf": cmds.softSelect(query=True, ssf=True), "ssd": cmds.softSelect(query=True, ssd=True) } def selectSoftSelection(self): """ Set the stored soft selection. """ cmds.softSelect(sse=1, **self.ssSettings) OpenMaya.MGlobal.setActiveSelectionList(self.ssActive) # ------------------------------------------------------------------------ def contextMenuEvent(self, event): menu = ui.QMenu(self) influence = menu.addAction("Select: Influence", partial(cmds.select, self.influence)) influence.setIcon(ui.QIcon(SELECT_ICON)) influence.setEnabled(True if self.influence else False) soft = menu.addAction("Select: Soft Selection", self.selectSoftSelection) soft.setIcon(ui.QIcon(SELECT_ICON)) soft.setEnabled(True if self.ssData else False) menu.exec_(self.mapToGlobal(event.pos()))