Files
MetaBox/Scripts/Modeling/Edit/LDMT/faceTransfer/ui.py
2025-04-17 04:52:48 +08:00

430 lines
12 KiB
Python

from functools import partial
from maya import cmds, OpenMaya, OpenMayaUI
# import pyside, do qt version check for maya 2017 >
qtVersion = cmds.about(qtVersion=True)
from Qt.QtGui import *
from Qt.QtCore import *
from Qt.QtWidgets import *
from Qt.QtCompat import wrapInstance, getCppPointer
# import command line convert
from . import convert
# ----------------------------------------------------------------------------
SPACE = []
SPACE.append(OpenMaya.MSpace.kObject)
SPACE.append(OpenMaya.MSpace.kWorld)
# ----------------------------------------------------------------------------
FONT = QFont()
FONT.setFamily("Consolas")
BOLT_FONT = QFont()
BOLT_FONT.setFamily("Consolas")
BOLT_FONT.setWeight(QFont.Weight.Light)
# ----------------------------------------------------------------------------
def mayaWindow():
"""
Get Maya's main window.
:rtype: QMainWindow
"""
window = OpenMayaUI.MQtUtil.mainWindow()
window = wrapInstance(int(window), QMainWindow)
return window
# ----------------------------------------------------------------------------
def title(parent, name):
"""
Create title ui widget.
:param QWidget parent:
:param str name:
:rtype: QLabel
"""
title = QLabel(parent)
title.setText(name)
title.setFont(BOLT_FONT)
return title
def divider(parent):
"""
Create divider ui widget.
:param QWidget parent:
:rtype: QFrame
"""
line = QFrame(parent)
line.setFrameShape(QFrame.HLine)
line.setFrameShadow(QFrame.Sunken)
return line
# ----------------------------------------------------------------------------
def getSelectedMeshes():
"""
Get all selected meshes, the current selection will be looped and checked
if any of the selected transforms contain a mesh node. If this is the case
the transform will be added to the selection list.
:return: Parents nodes of all selected meshes
:rtype: list
"""
# get selection
selection = cmds.ls(sl=True, l=True)
extendedSelection = []
# extend selection
for sel in selection:
extendedSelection.extend(
cmds.listRelatives(sel, s=True, ni=True, f=True)
)
# return parent of meshes
return list(set([
cmds.listRelatives(m, p=True, f=True)[0]
for m in extendedSelection
if cmds.nodeType(m) == "mesh"
]))
# ----------------------------------------------------------------------------
class SelectionWidget(QWidget):
signal = Signal()
def __init__(self, parent, label, selectionMode="single"):
QWidget.__init__(self, parent)
# selection
self._selectionMode = selectionMode
self._selection = []
# create layout
layout = QHBoxLayout(self)
layout.setContentsMargins(3, 0, 3, 0)
layout.setSpacing(0)
# create label
self.label = QLabel(self)
self.label.setText("( 0 ) Mesh(es)")
self.label.setFont(FONT)
layout.addWidget(self.label)
# create button
button = QPushButton(self)
button.setText(label)
button.setFont(FONT)
button.released.connect(self.setSelection)
layout.addWidget(button)
# ------------------------------------------------------------------------
@property
def selection(self):
return self._selection
# ------------------------------------------------------------------------
def setSelection(self):
"""
Update the UI with the current selection, a signal is emit so the
parent widget can validate the current selection.
"""
# get selection
meshes = getSelectedMeshes()
# update ui
self.label.setText("( {0} ) Mesh(es)".format(len(meshes)))
self.label.setToolTip("\n".join(meshes))
# process selection mode
if self._selectionMode == "single" and meshes:
meshes = meshes[0]
self._selection = meshes
self.signal.emit()
# ----------------------------------------------------------------------------
class CheckBoxWidget(QWidget):
def __init__(self, parent, label, toolTip):
QWidget.__init__(self, parent)
layout = QHBoxLayout(self)
layout.setContentsMargins(3, 0, 3, 0)
layout.setSpacing(0)
# create checkbox
self.checkBox = QCheckBox(self)
self.checkBox.setText(label)
self.checkBox.setFont(FONT)
self.checkBox.setChecked(True)
self.checkBox.setToolTip(toolTip)
layout.addWidget(self.checkBox)
# ------------------------------------------------------------------------
def isChecked(self):
return self.checkBox.isChecked()
class SpinBoxWidget(QWidget):
def __init__(
self,
parent,
widget,
label,
toolTip,
value,
minimum,
maximum,
step
):
QWidget.__init__(self, parent)
layout = QHBoxLayout(self)
layout.setContentsMargins(3, 0, 3, 0)
layout.setSpacing(0)
# create label
l = QLabel(self)
l.setText(label)
l.setFont(FONT)
layout.addWidget(l)
# create spinbox
self.spinBox = widget(self)
self.spinBox.setFont(FONT)
self.spinBox.setToolTip(toolTip)
self.spinBox.setValue(value)
self.spinBox.setSingleStep(step)
self.spinBox.setMinimum(minimum)
self.spinBox.setMaximum(maximum)
layout.addWidget(self.spinBox)
# ------------------------------------------------------------------------
def value(self):
return self.spinBox.value()
class RetargetBlendshapeWidget(QWidget):
def __init__(self, parent):
QWidget.__init__(self, parent)
# set ui
self.setParent(parent)
self.setWindowFlags(Qt.Window)
self.setWindowIcon(QIcon(":/blendShape.png"))
self.setWindowTitle("Retarget Blendshapes")
self.setObjectName("RetargetUI")
self.resize(300, 200)
layout = QVBoxLayout(self)
layout.setContentsMargins(5, 5, 5, 5)
layout.setSpacing(5)
# create title
t = title(self, "Retarget Blendshapes")
layout.addWidget(t)
# create selection widget
self.sourceW = SelectionWidget(self, "Set Source")
self.blendshapeW = SelectionWidget(self, "Set Blendshape(s)", "multi")
self.targetW = SelectionWidget(self, "Set Target")
for widget in [self.sourceW, self.targetW, self.blendshapeW]:
widget.signal.connect(self.validate)
layout.addWidget(widget)
# create divider
d = divider(self)
layout.addWidget(d)
# create options
t = title(self, "Options")
layout.addWidget(t)
# scale settings
self.scaleW = CheckBoxWidget(
self,
"Scale Delta",
(
"If checked, the vertex delta will be scaled based on the \n"
"difference of the averaged connected edge length between \n"
"the source and the target."
)
)
layout.addWidget(self.scaleW)
# rotate settings
self.rotateW = CheckBoxWidget(
self,
"Rotate Delta",
(
"If checked, the vertex delta will be rotated based on the \n"
"difference of the vertex normal between the source and \n"
"the target."
)
)
layout.addWidget(self.rotateW)
# create divider
d = divider(self)
layout.addWidget(d)
# create smoothing
t = title(self, "Smoothing")
layout.addWidget(t)
# smooth settings
self.smoothW = SpinBoxWidget(
self,
QDoubleSpinBox,
"Factor",
(
"The targets will be smoothed based on the difference \n"
"between source and blendshape and original target and \n"
"output"
),
value=10,
minimum=0,
maximum=1000,
step=0.5
)
layout.addWidget(self.smoothW)
# smooth iter settings
self.smoothIterW = SpinBoxWidget(
self,
QSpinBox,
"Iterations",
(
"The amount of time the smoothing algorithm is applied to \n"
"the output."
),
value=2,
minimum=0,
maximum=10,
step=1
)
layout.addWidget(self.smoothIterW)
# create divider
d = divider(self)
layout.addWidget(d)
# create space
t = title(self, "Space")
layout.addWidget(t)
self.spaceW = QComboBox(self)
self.spaceW.setFont(FONT)
self.spaceW.addItems(["kObject", "kWorld"])
self.spaceW.setToolTip(
"Determine space in which all the calculations take place."
)
layout.addWidget(self.spaceW)
# create divider
d = divider(self)
layout.addWidget(d)
# create retarget button
self.retargetB = QPushButton(self)
self.retargetB.setText("Retarget")
self.retargetB.setFont(FONT)
self.retargetB.setEnabled(False)
self.retargetB.released.connect(self.retarget)
layout.addWidget(self.retargetB)
# create spacer
spacer = QSpacerItem(1, 1, QSizePolicy.Minimum, QSizePolicy.Expanding)
layout.addItem(spacer)
# create progress bar
self.progressBar = QProgressBar(self)
layout.addWidget(self.progressBar)
# ------------------------------------------------------------------------
def validate(self):
"""
Validate the current selection, if the selection is valid the retarget
button will be enabled. If not the button will stay disabled.
"""
# variables
source = self.sourceW.selection
target = self.targetW.selection
blendshapes = self.blendshapeW.selection
# set button invisible
self.retargetB.setEnabled(False)
if source and target and blendshapes:
self.retargetB.setEnabled(True)
# ------------------------------------------------------------------------
def retarget(self):
"""
Read the values from the UI and call the command line convert function
to retarget the blendshapes. Once the blendshapes are converted the
new geometry will be selected.
"""
# get selection
source = self.sourceW.selection
target = self.targetW.selection
blendshapes = self.blendshapeW.selection
# get settings
scale = self.scaleW.isChecked()
rotate = self.rotateW.isChecked()
# get smoothing
smooth = self.smoothW.value()
smoothIterations = self.smoothIterW.value()
# get space
space = SPACE[self.spaceW.currentIndex()]
# set progress bar
self.progressBar.setMinimum(0)
self.progressBar.setMaximum(len(blendshapes))
self.progressBar.setValue(0)
# convert
converted = []
for i, blendshape in enumerate(blendshapes):
converted.append(
convert(
source,
blendshape,
target,
scale,
rotate,
smooth,
smoothIterations,
space
)
)
# update spacebar
self.progressBar.setValue(i+1)
# select output
cmds.select(converted)
# ----------------------------------------------------------------------------
def show():
retargetBlendshape = RetargetBlendshapeWidget(mayaWindow())
retargetBlendshape.show()