496 lines
14 KiB
Python
496 lines
14 KiB
Python
|
import os
|
||
|
import json
|
||
|
import maya.cmds as cmds
|
||
|
from scripts.config import data
|
||
|
|
||
|
class DNADefinition:
|
||
|
"""DNA定义类"""
|
||
|
def __init__(self):
|
||
|
self.file_path = ""
|
||
|
self.content = {}
|
||
|
|
||
|
def load(self, file_path):
|
||
|
"""加载DNA文件"""
|
||
|
if not os.path.exists(file_path):
|
||
|
raise FileNotFoundError(f"DNA文件不存在: {file_path}")
|
||
|
|
||
|
try:
|
||
|
with open(file_path, "r") as f:
|
||
|
self.content = json.load(f)
|
||
|
self.file_path = file_path
|
||
|
return True
|
||
|
except Exception as e:
|
||
|
cmds.warning(f"加载DNA文件失败: {str(e)}")
|
||
|
return False
|
||
|
|
||
|
def save(self, file_path=None):
|
||
|
"""保存DNA文件"""
|
||
|
save_path = file_path or self.file_path
|
||
|
if not save_path:
|
||
|
cmds.warning("未指定保存路径")
|
||
|
return False
|
||
|
|
||
|
try:
|
||
|
with open(save_path, "w") as f:
|
||
|
json.dump(self.content, f, indent=4)
|
||
|
return True
|
||
|
except Exception as e:
|
||
|
cmds.warning(f"保存DNA文件失败: {str(e)}")
|
||
|
return False
|
||
|
|
||
|
def validate(self):
|
||
|
"""验证DNA内容"""
|
||
|
required_keys = ["joints", "blendshapes", "description"]
|
||
|
for key in required_keys:
|
||
|
if key not in self.content:
|
||
|
return False
|
||
|
return True
|
||
|
|
||
|
# 全局DNA定义实例
|
||
|
dna_definition = DNADefinition()
|
||
|
|
||
|
def browse_dna_file():
|
||
|
"""浏览DNA文件"""
|
||
|
file_path, _ = cmds.fileDialog2(
|
||
|
fileFilter="DNA Files (*.dna)",
|
||
|
dialogStyle=2,
|
||
|
fileMode=1
|
||
|
)
|
||
|
|
||
|
if not file_path:
|
||
|
return
|
||
|
|
||
|
# 加载DNA文件
|
||
|
if dna_definition.load(file_path[0]):
|
||
|
# 更新UI预览
|
||
|
update_dna_preview()
|
||
|
# 更新骨骼列表
|
||
|
update_joint_list()
|
||
|
# 更新BlendShape列表
|
||
|
update_blendshape_list()
|
||
|
|
||
|
def update_dna_preview():
|
||
|
"""更新DNA预览"""
|
||
|
# 获取UI实例
|
||
|
from scripts.MetaFusion import app
|
||
|
if not app:
|
||
|
return
|
||
|
|
||
|
define_tab = app.window.findChild(QtWidgets.QWidget, "DefineTab")
|
||
|
if not define_tab:
|
||
|
return
|
||
|
|
||
|
# 更新预览内容
|
||
|
preview_text = json.dumps(dna_definition.content, indent=4)
|
||
|
define_tab.dna_preview.setText(preview_text)
|
||
|
|
||
|
def update_joint_list():
|
||
|
"""更新骨骼列表"""
|
||
|
from scripts.MetaFusion import app
|
||
|
if not app:
|
||
|
return
|
||
|
|
||
|
define_tab = app.window.findChild(QtWidgets.QWidget, "DefineTab")
|
||
|
if not define_tab:
|
||
|
return
|
||
|
|
||
|
# 清空列表
|
||
|
define_tab.joint_list.clear()
|
||
|
|
||
|
# 添加骨骼项
|
||
|
joints = dna_definition.content.get("joints", [])
|
||
|
for joint in joints:
|
||
|
item = QtWidgets.QTreeWidgetItem([
|
||
|
joint["name"],
|
||
|
str(joint["position"]),
|
||
|
str(joint["rotation"]),
|
||
|
str(joint["scale"])
|
||
|
])
|
||
|
define_tab.joint_list.addTopLevelItem(item)
|
||
|
|
||
|
def update_blendshape_list():
|
||
|
"""更新BlendShape列表"""
|
||
|
from scripts.MetaFusion import app
|
||
|
if not app:
|
||
|
return
|
||
|
|
||
|
define_tab = app.window.findChild(QtWidgets.QWidget, "DefineTab")
|
||
|
if not define_tab:
|
||
|
return
|
||
|
|
||
|
# 清空列表
|
||
|
define_tab.bs_list.clear()
|
||
|
|
||
|
# 添加BlendShape项
|
||
|
blendshapes = dna_definition.content.get("blendshapes", [])
|
||
|
for bs in blendshapes:
|
||
|
item = QtWidgets.QTreeWidgetItem([
|
||
|
bs["name"],
|
||
|
bs["target"],
|
||
|
str(bs["weight"])
|
||
|
])
|
||
|
define_tab.bs_list.addTopLevelItem(item)
|
||
|
|
||
|
# 骨骼功能
|
||
|
def add_joint():
|
||
|
"""添加骨骼"""
|
||
|
# 获取选中的骨骼
|
||
|
sel = cmds.ls(sl=True, type="joint")
|
||
|
if not sel:
|
||
|
cmds.warning("请先选择要添加的骨骼")
|
||
|
return
|
||
|
|
||
|
# 获取骨骼信息
|
||
|
joint = sel[0]
|
||
|
pos = cmds.xform(joint, q=True, ws=True, t=True)
|
||
|
rot = cmds.xform(joint, q=True, ws=True, ro=True)
|
||
|
scl = cmds.xform(joint, q=True, ws=True, s=True)
|
||
|
|
||
|
# 添加到DNA定义
|
||
|
joint_data = {
|
||
|
"name": joint,
|
||
|
"position": pos,
|
||
|
"rotation": rot,
|
||
|
"scale": scl
|
||
|
}
|
||
|
|
||
|
if "joints" not in dna_definition.content:
|
||
|
dna_definition.content["joints"] = []
|
||
|
dna_definition.content["joints"].append(joint_data)
|
||
|
|
||
|
# 更新UI
|
||
|
update_joint_list()
|
||
|
|
||
|
def delete_joint():
|
||
|
"""删除骨骼"""
|
||
|
from scripts.MetaFusion import app
|
||
|
if not app:
|
||
|
return
|
||
|
|
||
|
define_tab = app.window.findChild(QtWidgets.QWidget, "DefineTab")
|
||
|
if not define_tab:
|
||
|
return
|
||
|
|
||
|
# 获取选中项
|
||
|
items = define_tab.joint_list.selectedItems()
|
||
|
if not items:
|
||
|
cmds.warning("请先选择要删除的骨骼")
|
||
|
return
|
||
|
|
||
|
# 从DNA定义中删除
|
||
|
for item in items:
|
||
|
joint_name = item.text(0)
|
||
|
joints = dna_definition.content.get("joints", [])
|
||
|
dna_definition.content["joints"] = [
|
||
|
j for j in joints if j["name"] != joint_name
|
||
|
]
|
||
|
|
||
|
# 更新UI
|
||
|
update_joint_list()
|
||
|
|
||
|
def modify_joint():
|
||
|
"""修改骨骼"""
|
||
|
from scripts.MetaFusion import app
|
||
|
if not app:
|
||
|
return
|
||
|
|
||
|
define_tab = app.window.findChild(QtWidgets.QWidget, "DefineTab")
|
||
|
if not define_tab:
|
||
|
return
|
||
|
|
||
|
# 获取选中项
|
||
|
items = define_tab.joint_list.selectedItems()
|
||
|
if not items:
|
||
|
cmds.warning("请先选择要修改的骨骼")
|
||
|
return
|
||
|
|
||
|
# 获取场景中选中的骨骼
|
||
|
sel = cmds.ls(sl=True, type="joint")
|
||
|
if not sel:
|
||
|
cmds.warning("请先选择要用于更新的骨骼")
|
||
|
return
|
||
|
|
||
|
# 更新骨骼信息
|
||
|
joint = sel[0]
|
||
|
pos = cmds.xform(joint, q=True, ws=True, t=True)
|
||
|
rot = cmds.xform(joint, q=True, ws=True, ro=True)
|
||
|
scl = cmds.xform(joint, q=True, ws=True, s=True)
|
||
|
|
||
|
# 更新DNA定义
|
||
|
joint_name = items[0].text(0)
|
||
|
joints = dna_definition.content.get("joints", [])
|
||
|
for j in joints:
|
||
|
if j["name"] == joint_name:
|
||
|
j["position"] = pos
|
||
|
j["rotation"] = rot
|
||
|
j["scale"] = scl
|
||
|
break
|
||
|
|
||
|
# 更新UI
|
||
|
update_joint_list()
|
||
|
|
||
|
# BlendShape功能
|
||
|
def add_blendshape():
|
||
|
"""添加BlendShape"""
|
||
|
# 获取选中的BlendShape
|
||
|
sel = cmds.ls(sl=True, type="blendShape")
|
||
|
if not sel:
|
||
|
cmds.warning("请先选择要添加的BlendShape")
|
||
|
return
|
||
|
|
||
|
# 获取BlendShape信息
|
||
|
bs = sel[0]
|
||
|
target = cmds.blendShape(bs, q=True, g=True)[0]
|
||
|
weight = cmds.getAttr(f"{bs}.weight[0]")
|
||
|
|
||
|
# 添加到DNA定义
|
||
|
bs_data = {
|
||
|
"name": bs,
|
||
|
"target": target,
|
||
|
"weight": weight
|
||
|
}
|
||
|
|
||
|
if "blendshapes" not in dna_definition.content:
|
||
|
dna_definition.content["blendshapes"] = []
|
||
|
dna_definition.content["blendshapes"].append(bs_data)
|
||
|
|
||
|
# 更新UI
|
||
|
update_blendshape_list()
|
||
|
|
||
|
def delete_blendshape():
|
||
|
"""删除BlendShape"""
|
||
|
from scripts.MetaFusion import app
|
||
|
if not app:
|
||
|
return
|
||
|
|
||
|
define_tab = app.window.findChild(QtWidgets.QWidget, "DefineTab")
|
||
|
if not define_tab:
|
||
|
return
|
||
|
|
||
|
# 获取选中项
|
||
|
items = define_tab.bs_list.selectedItems()
|
||
|
if not items:
|
||
|
cmds.warning("请先选择要删除的BlendShape")
|
||
|
return
|
||
|
|
||
|
# 从DNA定义中删除
|
||
|
for item in items:
|
||
|
bs_name = item.text(0)
|
||
|
blendshapes = dna_definition.content.get("blendshapes", [])
|
||
|
dna_definition.content["blendshapes"] = [
|
||
|
bs for bs in blendshapes if bs["name"] != bs_name
|
||
|
]
|
||
|
|
||
|
# 更新UI
|
||
|
update_blendshape_list()
|
||
|
|
||
|
def modify_blendshape():
|
||
|
"""修改BlendShape"""
|
||
|
from scripts.MetaFusion import app
|
||
|
if not app:
|
||
|
return
|
||
|
|
||
|
define_tab = app.window.findChild(QtWidgets.QWidget, "DefineTab")
|
||
|
if not define_tab:
|
||
|
return
|
||
|
|
||
|
# 获取选中项
|
||
|
items = define_tab.bs_list.selectedItems()
|
||
|
if not items:
|
||
|
cmds.warning("请先选择要修改的BlendShape")
|
||
|
return
|
||
|
|
||
|
# 获取场景中选中的BlendShape
|
||
|
sel = cmds.ls(sl=True, type="blendShape")
|
||
|
if not sel:
|
||
|
cmds.warning("请先选择要用于更新的BlendShape")
|
||
|
return
|
||
|
|
||
|
# 更新BlendShape信息
|
||
|
bs = sel[0]
|
||
|
target = cmds.blendShape(bs, q=True, g=True)[0]
|
||
|
weight = cmds.getAttr(f"{bs}.weight[0]")
|
||
|
|
||
|
# 更新DNA定义
|
||
|
bs_name = items[0].text(0)
|
||
|
blendshapes = dna_definition.content.get("blendshapes", [])
|
||
|
for b in blendshapes:
|
||
|
if b["name"] == bs_name:
|
||
|
b["target"] = target
|
||
|
b["weight"] = weight
|
||
|
break
|
||
|
|
||
|
# 更新UI
|
||
|
update_blendshape_list()
|
||
|
|
||
|
# DNA相关功能
|
||
|
def load_dna_preview(dna_file):
|
||
|
"""加载DNA预览
|
||
|
Args:
|
||
|
dna_file: DNA文件路径
|
||
|
Returns:
|
||
|
bool: 是否加载成功
|
||
|
"""
|
||
|
try:
|
||
|
# 加载DNA文件
|
||
|
if not dna_definition.load(dna_file):
|
||
|
return False
|
||
|
|
||
|
# 更新UI预览
|
||
|
update_dna_preview()
|
||
|
return True
|
||
|
|
||
|
except Exception as e:
|
||
|
cmds.warning(f"加载DNA预览失败: {str(e)}")
|
||
|
return False
|
||
|
|
||
|
def validate_dna(dna_file):
|
||
|
"""验证DNA文件
|
||
|
Args:
|
||
|
dna_file: DNA文件路径
|
||
|
Returns:
|
||
|
bool: 是否验证通过
|
||
|
"""
|
||
|
try:
|
||
|
# 加载DNA文件
|
||
|
if not dna_definition.load(dna_file):
|
||
|
return False
|
||
|
|
||
|
# 验证必要字段
|
||
|
if not dna_definition.validate():
|
||
|
cmds.warning("DNA文件缺少必要字段")
|
||
|
return False
|
||
|
|
||
|
# 验证骨骼数据
|
||
|
joints = dna_definition.content.get("joints", [])
|
||
|
for joint in joints:
|
||
|
required = ["name", "position", "rotation", "scale"]
|
||
|
if not all(key in joint for key in required):
|
||
|
cmds.warning(f"骨骼数据不完整: {joint.get('name', 'unknown')}")
|
||
|
return False
|
||
|
|
||
|
# 验证BlendShape数据
|
||
|
blendshapes = dna_definition.content.get("blendshapes", [])
|
||
|
for bs in blendshapes:
|
||
|
required = ["name", "target", "weight"]
|
||
|
if not all(key in bs for key in required):
|
||
|
cmds.warning(f"BlendShape数据不完整: {bs.get('name', 'unknown')}")
|
||
|
return False
|
||
|
|
||
|
return True
|
||
|
|
||
|
except Exception as e:
|
||
|
cmds.warning(f"验证DNA文件失败: {str(e)}")
|
||
|
return False
|
||
|
|
||
|
def export_dna_definition(dna_file):
|
||
|
"""导出DNA定义
|
||
|
Args:
|
||
|
dna_file: 导出文件路径
|
||
|
Returns:
|
||
|
bool: 是否导出成功
|
||
|
"""
|
||
|
try:
|
||
|
# 收集场景中的骨骼数据
|
||
|
joints = []
|
||
|
for joint in cmds.ls(type="joint"):
|
||
|
pos = cmds.xform(joint, q=True, ws=True, t=True)
|
||
|
rot = cmds.xform(joint, q=True, ws=True, ro=True)
|
||
|
scl = cmds.xform(joint, q=True, ws=True, s=True)
|
||
|
|
||
|
joints.append({
|
||
|
"name": joint,
|
||
|
"position": pos,
|
||
|
"rotation": rot,
|
||
|
"scale": scl
|
||
|
})
|
||
|
|
||
|
# 收集场景中的BlendShape数据
|
||
|
blendshapes = []
|
||
|
for bs in cmds.ls(type="blendShape"):
|
||
|
target = cmds.blendShape(bs, q=True, g=True)[0]
|
||
|
weight = cmds.getAttr(f"{bs}.weight[0]")
|
||
|
|
||
|
blendshapes.append({
|
||
|
"name": bs,
|
||
|
"target": target,
|
||
|
"weight": weight
|
||
|
})
|
||
|
|
||
|
# 更新DNA内容
|
||
|
dna_definition.content.update({
|
||
|
"joints": joints,
|
||
|
"blendshapes": blendshapes,
|
||
|
"description": {
|
||
|
"name": cmds.file(q=True, sn=True, shn=True),
|
||
|
"version": data.TOOL_VERSION,
|
||
|
"author": data.TOOL_AUTHOR,
|
||
|
"date": cmds.date(format="YYYY-MM-DD HH:mm:ss")
|
||
|
}
|
||
|
})
|
||
|
|
||
|
# 保存文件
|
||
|
return dna_definition.save(dna_file)
|
||
|
|
||
|
except Exception as e:
|
||
|
cmds.warning(f"导出DNA定义失败: {str(e)}")
|
||
|
return False
|
||
|
|
||
|
def import_dna_definition(dna_file):
|
||
|
"""导入DNA定义
|
||
|
Args:
|
||
|
dna_file: DNA文件路径
|
||
|
Returns:
|
||
|
bool: 是否导入成功
|
||
|
"""
|
||
|
try:
|
||
|
# 验证DNA文件
|
||
|
if not validate_dna(dna_file):
|
||
|
return False
|
||
|
|
||
|
# 导入骨骼
|
||
|
joints = dna_definition.content.get("joints", [])
|
||
|
for joint_data in joints:
|
||
|
# 检查骨骼是否存在
|
||
|
joint_name = joint_data["name"]
|
||
|
if not cmds.objExists(joint_name):
|
||
|
# 创建骨骼
|
||
|
joint = cmds.joint(name=joint_name)
|
||
|
else:
|
||
|
joint = joint_name
|
||
|
|
||
|
# 设置变换
|
||
|
cmds.xform(joint,
|
||
|
ws=True,
|
||
|
t=joint_data["position"],
|
||
|
ro=joint_data["rotation"],
|
||
|
s=joint_data["scale"]
|
||
|
)
|
||
|
|
||
|
# 导入BlendShape
|
||
|
blendshapes = dna_definition.content.get("blendshapes", [])
|
||
|
for bs_data in blendshapes:
|
||
|
# 检查目标是否存在
|
||
|
if not cmds.objExists(bs_data["target"]):
|
||
|
cmds.warning(f"BlendShape目标不存在: {bs_data['target']}")
|
||
|
continue
|
||
|
|
||
|
# 创建或获取BlendShape
|
||
|
bs_name = bs_data["name"]
|
||
|
if not cmds.objExists(bs_name):
|
||
|
bs = cmds.blendShape(
|
||
|
bs_data["target"],
|
||
|
name=bs_name,
|
||
|
frontOfChain=True
|
||
|
)[0]
|
||
|
else:
|
||
|
bs = bs_name
|
||
|
|
||
|
# 设置权重
|
||
|
cmds.setAttr(f"{bs}.weight[0]", bs_data["weight"])
|
||
|
|
||
|
return True
|
||
|
|
||
|
except Exception as e:
|
||
|
cmds.warning(f"导入DNA定义失败: {str(e)}")
|
||
|
return False
|