MetaFusion/scripts/extractDeltas.py
2025-01-15 23:58:58 +08:00

442 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# ----------------------------------------------------------------------------------------------
#
# extractDeltas.py
# v1.4
#
# 从变形的蒙皮网格中提取建模的修正形状
#
# 原始 c++ extract deltas 插件作者James Jacobs
#
# Python 转换、改进和维护Ingo Clemens
# www.braverabbit.com
#
# brave rabbit, Ingo Clemens 2014
#
# 版本历史:
#
# 1.4 - 包含了 mel 脚本
# 1.3 - 改进了形状比较,无需使用混合变形
# 1.2 - 添加了顶点列表标志,仅在给定的组件列表上工作
# 1.1 - 优化了性能,因为现在只对雕刻的点进行处理
# (0.06 - c++ 版本, 1.88 - 1.0版本, 0.14 - 1.1版本)
# 1.0 - 初始 Python 转换
#
# ----------------------------------------------------------------------------------------------
# ----------------------------------------------------------------------------------------------
#
# 本程序是自由软件;您可以根据自由软件基金会发布的 GNU 通用公共许可证
# 第2版或您可以选择任何更新版本的条款重新发布和/或修改它。
#
# 本程序的发布是希望它能有用,但不提供任何保证;甚至没有
# 适销性或特定用途适用性的暗示保证。详情请参阅
# GNU 通用公共许可证。
#
# ----------------------------------------------------------------------------------------------
# ----------------------------------------------------------------------------------------------
#
# 使用和修改需自行承担风险!!
#
# ----------------------------------------------------------------------------------------------
import maya.OpenMaya as OpenMaya
import maya.OpenMayaMPx as OpenMayaMPx
import maya.cmds as cmds
from maya.mel import eval as meval
import re
import sys
kPluginCmdName = 'extractDeltas'
# --------------------------------------------------------------------------------
# 参数标志
# --------------------------------------------------------------------------------
helpFlag = '-h'
helpFlagLong = '-help'
skinFlag = '-s'
skinFlagLong = '-skin'
correctiveFlag = '-c'
correctiveFlagLong = '-corrective'
vertexListFlag = '-vl'
vertexListFlagLong = '-vertexList'
helpText = ''
helpText += '\n Description: Extract a modeled corrective shape from a deformed skinned mesh.'
helpText += '\n'
helpText += '\n Flags: extractDeltas -h -help <n/a> this message'
helpText += '\n -s -skin <string> the name of the skinned mesh'
helpText += '\n -c -corrective <string> the name of the sculpted shape'
helpText += '\n -vl -vertexList <string> optional list of vertices, comma separated string'
helpText += '\n Usage: Execute the command with the following arguments:'
helpText += '\n Execute: extractDeltas -s <mesh with skin cluster> -c <corrective mesh name>'
# --------------------------------------------------------------------------------
# 主命令
# --------------------------------------------------------------------------------
class extractDeltas(OpenMayaMPx.MPxCommand):
def __init__(self):
OpenMayaMPx.MPxCommand.__init__(self)
def doIt(self, args):
self.dagModifier = OpenMaya.MDagModifier()
skinName = ''
correctiveName = ''
resultName = ''
listString = ''
# --------------------------------------------------------------------------------
# 解析参数
# --------------------------------------------------------------------------------
argData = OpenMaya.MArgDatabase(self.syntax(), args)
# 帮助标志
if argData.isFlagSet(helpFlag):
self.setResult(helpText)
return
# 皮肤标志
if argData.isFlagSet(skinFlag):
skinName = argData.flagArgumentString(skinFlag, 0)
# 修正标志
if argData.isFlagSet(correctiveFlag):
correctiveName = argData.flagArgumentString(correctiveFlag, 0)
# 顶点列表标志
if argData.isFlagSet(vertexListFlag):
listString = argData.flagArgumentString(vertexListFlag, 0)
# --------------------------------------------------------------------------------
# 检查选择
# --------------------------------------------------------------------------------
sel = []
if skinName != '' and correctiveName != '':
sel.append(skinName)
sel.append(correctiveName)
else:
sel = cmds.ls(sl = True, tr = True)
shapeList = []
for i in range(len(sel)):
shapes = cmds.listRelatives(sel[i], s = True)
if shapes == None:
OpenMaya.MGlobal.displayError(sel[i] + ' 没有形状节点。')
return
if cmds.nodeType(shapes[0]) != 'mesh':
OpenMaya.MGlobal.displayError(shapes[0] + ' 不是一个网格对象。')
return
elif i == 0 and len(shapes) > 1:
skin = cmds.listConnections(shapes[0], type = 'skinCluster')
if skin == None:
OpenMaya.MGlobal.displayError(shapes[0] + ' 没有绑定到皮肤簇。')
return
if cmds.getAttr(shapes[1] + '.intermediateObject'):
shapeList.append(shapes[1])
else:
OpenMaya.MGlobal.displayError(shapes[1] + ' 不是一个中间/原始形状节点。')
return
shapeList.append(shapes[0])
if len(shapeList) != 3:
OpenMaya.MGlobal.displayError('选择一个带有有效原始形状节点和目标网格对象的蒙皮网格。')
return
selList = OpenMaya.MSelectionList()
for sl in shapeList:
selList.add(sl)
intermediateObj = OpenMaya.MObject()
skinObj = OpenMaya.MObject()
targetObj = OpenMaya.MObject()
selList.getDependNode(0, intermediateObj)
selList.getDependNode(1, skinObj)
selList.getDependNode(2, targetObj)
# --------------------------------------------------------------------------------
# 定义网格函数并获取点
# --------------------------------------------------------------------------------
skinFn = OpenMaya.MFnMesh()
skinFn.setObject(skinObj)
targetFn = OpenMaya.MFnMesh()
targetFn.setObject(targetObj)
intermediateFn = OpenMaya.MFnMesh()
intermediateFn.setObject(intermediateObj)
skinPoints = OpenMaya.MPointArray()
skinFn.getPoints(skinPoints)
targetPoints = OpenMaya.MPointArray()
targetFn.getPoints(targetPoints)
intermediatePoints = OpenMaya.MPointArray()
intermediateFn.getPoints(intermediatePoints)
extractPoints = OpenMaya.MPointArray(intermediatePoints)
# --------------------------------------------------------------------------------
# 通过临时 blendShape 节点获取 delta 点
# --------------------------------------------------------------------------------
pointList = []
for i in range(0, skinPoints.length()):
if skinPoints[i] != targetPoints[i]:
pointList.append(i)
if len(pointList) == 0:
OpenMaya.MGlobal.displayError('没有形状提取。两个网格相同。')
return
# 创建 delta 点与给定顶点列表之间的交集列表
vList = []
if listString != '':
array = listString.split(',')
array = map(int, array)
intersectList = list(set(pointList) & set(array))
pointList = intersectList
# --------------------------------------------------------------------------------
# 复制原始网格
# --------------------------------------------------------------------------------
resultFn = OpenMaya.MFnMesh()
resultObj = OpenMaya.MObject()
# 使用 API 函数复制网格,但不容易撤消
# resultObj = resultFn.copy(intermediateObj, OpenMaya.cvar.MObject_kNullObj)
# 通过 Maya 命令复制网格有点复杂
# 但撤消是免费的
resultMesh = cmds.duplicate(shapeList[0], rc = True)
shapes = cmds.listRelatives(resultMesh, s = True)
# 删除主形状节点并禁用中间对象
cmds.delete(shapes[0])
cmds.setAttr(shapes[1] + '.intermediateObject', 0)
cmds.rename(shapes[1], shapes[0])
attrList = ['tx', 'ty', 'tz', 'rx', 'ry', 'rz', 'sx', 'sy', 'sz']
for a in attrList:
cmds.setAttr(resultMesh[0] + '.' + a, l = False)
selList.clear()
selList.add(shapes[0])
selList.getDependNode(0, resultObj)
resultFn.setObject(resultObj)
resultPoints = OpenMaya.MPointArray()
resultFn.getPoints(resultPoints)
# --------------------------------------------------------------------------------
# 通过首先对原始网格进行预扰动,然后在蒙皮网格上构建坐标空间来构建相对坐标空间
# --------------------------------------------------------------------------------
xArray = OpenMaya.MPointArray(intermediatePoints)
yArray = OpenMaya.MPointArray(intermediatePoints)
zArray = OpenMaya.MPointArray(intermediatePoints)
xPointArray = OpenMaya.MPointArray()
yPointArray = OpenMaya.MPointArray()
zPointArray = OpenMaya.MPointArray()
for i in pointList:
xArray.set(i, intermediatePoints[i].x + 1.0, intermediatePoints[i].y, intermediatePoints[i].z)
yArray.set(i, intermediatePoints[i].x, intermediatePoints[i].y + 1.0, intermediatePoints[i].z)
zArray.set(i, intermediatePoints[i].x, intermediatePoints[i].y, intermediatePoints[i].z + 1.0)
intermediateFn.setPoints(xArray)
skinFn.getPoints(xPointArray)
for i in pointList:
offX = xPointArray[i].x - skinPoints[i].x
offY = xPointArray[i].y - skinPoints[i].y
offZ = xPointArray[i].z - skinPoints[i].z
xPointArray.set(i, offX, offY, offZ)
intermediateFn.setPoints(yArray)
skinFn.getPoints(yPointArray)
for i in pointList:
offX = yPointArray[i].x - skinPoints[i].x
offY = yPointArray[i].y - skinPoints[i].y
offZ = yPointArray[i].z - skinPoints[i].z
yPointArray.set(i, offX, offY, offZ)
intermediateFn.setPoints(zArray)
skinFn.getPoints(zPointArray)
for i in pointList:
offX = zPointArray[i].x - skinPoints[i].x
offY = zPointArray[i].y - skinPoints[i].y
offZ = zPointArray[i].z - skinPoints[i].z
zPointArray.set(i, offX, offY, offZ)
# 将原始点设置回去
intermediateFn.setPoints(intermediatePoints)
# --------------------------------------------------------------------------------
# 从蒙皮网格中提取
# --------------------------------------------------------------------------------
for i in pointList:
extractItems = [zPointArray[i].x, zPointArray[i].y, zPointArray[i].z, 0.0,
xPointArray[i].x, xPointArray[i].y, xPointArray[i].z, 0.0,
yPointArray[i].x, yPointArray[i].y, yPointArray[i].z, 0.0,
skinPoints[i].x, skinPoints[i].y, skinPoints[i].z, 1.0]
resultItems = [0.0, 0.0, 1.0, 0.0,
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
resultPoints[i].x, resultPoints[i].y, resultPoints[i].z, 1.0]
extractMatrix = OpenMaya.MMatrix()
OpenMaya.MScriptUtil.createMatrixFromList(extractItems, extractMatrix)
resultMatrix = OpenMaya.MMatrix()
OpenMaya.MScriptUtil.createMatrixFromList(resultItems, resultMatrix)
point = OpenMaya.MPoint()
point = targetPoints[i] * extractMatrix.inverse()
point *= resultMatrix
extractPoints.set(point, i)
resultFn.setPoints(extractPoints)
# --------------------------------------------------------------------------------
# 清理
# --------------------------------------------------------------------------------
cmds.sets(resultFn.fullPathName(), e = True, fe = 'initialShadingGroup')
parentNode = cmds.listRelatives(resultFn.fullPathName(), p = True)
resultName = cmds.rename(parentNode, sel[1] + '_corrective')
self.setResult(resultName)
return self.redoIt()
def redoIt(self):
self.dagModifier.doIt()
def undoIt(self):
self.dagModifier.undoIt()
def isUndoable(self):
return True
# --------------------------------------------------------------------------------
# 定义语法,需要与 mel 和 python 一起工作
# --------------------------------------------------------------------------------
# 创建者
def cmdCreator():
return OpenMayaMPx.asMPxPtr(extractDeltas())
def syntaxCreator():
syn = OpenMaya.MSyntax()
syn.addFlag(helpFlag, helpFlagLong)
syn.addFlag(skinFlag, skinFlagLong, OpenMaya.MSyntax.kString)
syn.addFlag(correctiveFlag, correctiveFlagLong, OpenMaya.MSyntax.kString)
syn.addFlag(vertexListFlag, vertexListFlagLong, OpenMaya.MSyntax.kString)
return syn
# 初始化
def initializePlugin(mobject):
mplugin = OpenMayaMPx.MFnPlugin(mobject, 'Original plugin by James Jacobs / Python adaption by Ingo Clemens', '1.4', 'Any')
try:
mplugin.registerCommand(kPluginCmdName, cmdCreator, syntaxCreator)
except:
sys.stderr.write('Failed to register command: %s\n' % kPluginCmdName)
raise
def uninitializePlugin(mobject):
mplugin = OpenMayaMPx.MFnPlugin(mobject)
try:
mplugin.deregisterCommand(kPluginCmdName)
except:
sys.stderr.write( 'Failed to unregister command: %s\n' % kPluginCmdName )
raise
# --------------------------------------------------------------------------------
# mel 过程
# --------------------------------------------------------------------------------
mel = '''
global proc extractDeltasDuplicateMesh()
{
string $sel[] = `ls -sl`;
string $shapes[] = `listRelatives -s $sel[0]`;
string $skin[] = `listConnections -type "skinCluster" $shapes[0]`;
if (`size($skin)`)
{
string $dup[] = `duplicate -rr -rc $sel`;
$shapes = `listRelatives -s $dup[0]`;
for ($s in $shapes)
{
if (`getAttr ($s + ".intermediateObject")`)
{
delete $s;
}
}
setAttr -l 0 ($dup[0] + ".tx");
setAttr -l 0 ($dup[0] + ".ty");
setAttr -l 0 ($dup[0] + ".tz");
setAttr -l 0 ($dup[0] + ".rx");
setAttr -l 0 ($dup[0] + ".ry");
setAttr -l 0 ($dup[0] + ".rz");
setAttr -l 0 ($dup[0] + ".sx");
setAttr -l 0 ($dup[0] + ".sy");
setAttr -l 0 ($dup[0] + ".sz");
}
}
global proc performExtractDeltas()
{
string $sel[] = `ls -sl -tr`;
string $shapes[];
for ($s in $sel)
{
$shapes = `listRelatives -s $s`;
for ($sh in $shapes)
{
if (`nodeType $sh` != "mesh")
{
error "The selected geometry is no polygon object!";
}
}
}
if (size($sel) == 2)
{
$shapes = `listRelatives -s $sel[0]`;
string $skin[] = `listConnections -type "skinCluster" $shapes[0]`;
if (!`size($skin)`)
{
error "The first selected object is not bound to a skin cluster!";
}
}
else
{
error "Please select two polygonal objects!";
}
extractDeltas -s $sel[0] -c $sel[1];
}
'''
meval(mel)