Updated
This commit is contained in:
430
Scripts/Modeling/Edit/LDMT/faceTransfer/ui.py
Normal file
430
Scripts/Modeling/Edit/LDMT/faceTransfer/ui.py
Normal file
@ -0,0 +1,430 @@
|
||||
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()
|
Reference in New Issue
Block a user