Files
Nexus/2023/scripts/animation_tools/studiolibrary/studiolibrarymaya/poseitem.py
2025-11-24 00:15:32 +08:00

393 lines
12 KiB
Python

# 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 <http://www.gnu.org/licenses/>.
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", "")}
)