# Copyright 2020 by Kurt Rathjen. All Rights Reserved. # # This library is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. This library is distributed in the # hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # See the GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public # License along with this library. If not, see . import os import logging import studiolibrary from studiovendor.Qt import QtGui from studiovendor.Qt import QtCore from studiovendor.Qt import QtWidgets from studiolibrarymaya import baseitem from studiolibrarymaya import baseloadwidget try: import mutils import maya.cmds except ImportError as error: print(error) logger = logging.getLogger(__name__) def save(path, *args, **kwargs): """Convenience function for saving a PoseItem.""" PoseItem(path).safeSave(*args, **kwargs) def load(path, *args, **kwargs): """Convenience function for loading a PoseItem.""" PoseItem(path).load(*args, **kwargs) class PoseLoadWidget(baseloadwidget.BaseLoadWidget): @classmethod def createFromPath(cls, path, theme=None): item = PoseItem(path) widget = cls(item) if not theme: import studiolibrary.widgets theme = studiolibrary.widgets.Theme() widget.setStyleSheet(theme.styleSheet()) widget.show() def __init__(self, *args, **kwargs): super(PoseLoadWidget, self).__init__(*args, **kwargs) self._options = None self._pose = mutils.Pose.fromPath(self.item().transferPath()) self.ui.blendFrame = QtWidgets.QFrame(self) layout = QtWidgets.QHBoxLayout() self.ui.blendFrame.setLayout(layout) if self.item().libraryWindow(): self.item().libraryWindow().itemsWidget().itemSliderMoved.connect(self._sliderMoved) self.item().libraryWindow().itemsWidget().itemSliderReleased.connect(self._sliderReleased) self.item().libraryWindow().itemsWidget().itemDoubleClicked.connect(self._itemDoubleClicked) self.ui.blendSlider = QtWidgets.QSlider(self) self.ui.blendSlider.setObjectName("blendSlider") self.ui.blendSlider.setMinimum(-30) self.ui.blendSlider.setMaximum(130) self.ui.blendSlider.setOrientation(QtCore.Qt.Horizontal) self.ui.blendSlider.sliderMoved.connect(self._sliderMoved) self.ui.blendSlider.sliderReleased.connect(self._sliderReleased) self.ui.blendEdit = QtWidgets.QLineEdit(self) self.ui.blendEdit.setObjectName("blendEdit") self.ui.blendEdit.setText("0") self.ui.blendEdit.editingFinished.connect(self._blendEditChanged) validator = QtGui.QIntValidator(-200, 200, self) self.ui.blendEdit.setValidator(validator) layout.addWidget(self.ui.blendSlider) layout.addWidget(self.ui.blendEdit) self.setCustomWidget(self.ui.blendFrame) def _itemDoubleClicked(self): """Triggered when the user double-clicks a pose.""" self.accept() def _sliderMoved(self, value): """Triggered when the user moves the slider handle.""" self.load(blend=value, batchMode=True) def _sliderReleased(self): """Triggered when the user releases the slider handle.""" try: self.load(blend=self.ui.blendSlider.value(), refresh=False) self._options = {} except Exception as error: self.item().showErrorDialog("Item Error", str(error)) raise def _blendEditChanged(self, *args): """Triggered when the user changes the blend edit value.""" try: self._options = {} self.load(blend=int(self.ui.blendEdit.text()), clearSelection=False) except Exception as error: self.item().showErrorDialog("Item Error", str(error)) raise def loadValidator(self, *args, **kwargs): self._options = {} return super(PoseLoadWidget, self).loadValidator(*args, **kwargs) def accept(self): """Triggered when the user clicks the apply button.""" try: self._options = {} self.load(clearCache=True, clearSelection=False) except Exception as error: self.item().showErrorDialog("Item Error", str(error)) raise def load( self, blend=100.0, refresh=True, batchMode=False, clearCache=False, clearSelection=True, ): """ Load the pose item with the current user settings from disc. :type blend: float :type refresh: bool :type batchMode: bool :type clearCache: bool :type clearSelection: bool """ if batchMode: self.formWidget().setValidatorEnabled(False) else: self.formWidget().setValidatorEnabled(True) if not self._options: self._options = self.formWidget().values() self._options['mirrorTable'] = self.item().mirrorTable() self._options['objects'] = maya.cmds.ls(selection=True) or [] if not self._options["searchAndReplaceEnabled"]: self._options["searchAndReplace"] = None del self._options["namespaceOption"] del self._options["searchAndReplaceEnabled"] self.ui.blendEdit.blockSignals(True) self.ui.blendSlider.setValue(blend) self.ui.blendEdit.setText(str(int(blend))) self.ui.blendEdit.blockSignals(False) if self.item().libraryWindow(): self.item().libraryWindow().itemsWidget().blockSignals(True) self.item().setSliderValue(blend) self.item().libraryWindow().itemsWidget().blockSignals(False) if batchMode: self.item().libraryWindow().showToastMessage("Blend: {0}%".format(blend)) try: self._pose.load( blend=blend, refresh=refresh, batchMode=batchMode, clearCache=clearCache, clearSelection=clearSelection, **self._options ) finally: self.item().setSliderDown(batchMode) def findMirrorTable(path): """ Get the mirror table object for this item. :rtype: mutils.MirrorTable or None """ mirrorTable = None mirrorTablePaths = list(studiolibrary.walkup( path, match=lambda path: path.endswith(".mirror"), depth=10, ) ) if mirrorTablePaths: mirrorTablePath = mirrorTablePaths[0] path = os.path.join(mirrorTablePath, "mirrortable.json") if path: mirrorTable = mutils.MirrorTable.fromPath(path) return mirrorTable class PoseItem(baseitem.BaseItem): NAME = "Pose" EXTENSION = ".pose" ICON_PATH = os.path.join(os.path.dirname(__file__), "icons", "pose.png") LOAD_WIDGET_CLASS = PoseLoadWidget TRANSFER_CLASS = mutils.Pose TRANSFER_BASENAME = "pose.json" def __init__(self, *args, **kwargs): """ Create a new instance of the pose item from the given path. :type path: str :type args: list :type kwargs: dict """ super(PoseItem, self).__init__(*args, **kwargs) self.setSliderEnabled(True) def mirrorTableSearchAndReplace(self): """ Get the values for search and replace from the mirror table. :rtype: (str, str) """ mirrorTable = findMirrorTable(self.path()) return mirrorTable.leftSide(), mirrorTable.rightSide() def switchSearchAndReplace(self): """ Switch the values of the search and replace field. :rtype: (str, str) """ values = self.currentLoadValue("searchAndReplace") return values[1], values[0] def clearSearchAndReplace(self): """ Clear the search and replace field. :rtype: (str, str) """ return '', '' def doubleClicked(self): """Triggered when the user double-click the item.""" pass def loadSchema(self): """ Get schema used to load the pose item. :rtype: list[dict] """ schema = [ { "name": "optionsGroup", "title": "Options", "type": "group", "order": 2, }, { "name": "key", "type": "bool", "inline": True, "default": False, "persistent": True, }, { "name": "mirror", "type": "bool", "inline": True, "default": False, "persistent": True, }, { "name": "additive", "type": "bool", "inline": True, "default": False, "persistent": True, }, { "name": "searchAndReplaceEnabled", "title": "Search and Replace", "type": "bool", "inline": True, "default": False, "persistent": True, }, { "name": "searchAndReplace", "title": "", "type": "stringDouble", "default": ("", ""), "placeholder": ("search", "replace"), "persistent": True, "actions": [ { "name": "Switch", "callback": self.switchSearchAndReplace, }, { "name": "Clear", "callback": self.clearSearchAndReplace, }, { "name": "From Mirror Table", "enabled": bool(findMirrorTable(self.path())), "callback": self.mirrorTableSearchAndReplace, }, ] }, ] schema.extend(super(PoseItem, self).loadSchema()) return schema def mirrorTable(self): return findMirrorTable(self.path()) def loadValidator(self, **values): """ Using the validator to change the state of the mirror option. :type values: dict :rtype: list[dict] """ # Mirror check box mirrorTip = "Cannot find a mirror table!" mirrorTable = findMirrorTable(self.path()) if mirrorTable: mirrorTip = "Using mirror table: %s" % mirrorTable.path() fields = [ { "name": "mirror", "toolTip": mirrorTip, "enabled": mirrorTable is not None, }, { "name": "searchAndReplace", "visible": values.get("searchAndReplaceEnabled") }, ] fields.extend(super(PoseItem, self).loadValidator(**values)) return fields def save(self, objects, **kwargs): """ Save all the given object data to the item path on disc. :type objects: list[str] :type kwargs: dict """ super(PoseItem, self).save(**kwargs) # Save the pose to the temp location mutils.savePose( self.transferPath(), objects, metadata={"description": kwargs.get("comment", "")} )