Exemple #1
0
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)
Exemple #2
0
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())
Exemple #3
0
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()
Exemple #4
0
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())
Exemple #5
0
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()))