551 lines
21 KiB
Python
551 lines
21 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
Tool - DNA Editor UI
|
|
Provides the UI for editing DNA files and parameters
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import maya.cmds as cmds
|
|
|
|
try:
|
|
from PySide2 import QtCore, QtGui, QtWidgets
|
|
except ImportError:
|
|
try:
|
|
from PySide6 import QtCore, QtGui, QtWidgets
|
|
except ImportError:
|
|
from PySide import QtCore, QtGui, QtWidgets
|
|
|
|
# Import configuration
|
|
import config
|
|
|
|
class DNAEditorWidget(QtWidgets.QWidget):
|
|
"""Widget for editing DNA files and parameters"""
|
|
|
|
def __init__(self, parent=None):
|
|
"""Initialize the DNA editor widget"""
|
|
super(DNAEditorWidget, self).__init__(parent)
|
|
|
|
# Initialize UI
|
|
self._create_widgets()
|
|
self._create_layouts()
|
|
self._create_connections()
|
|
|
|
def _create_widgets(self):
|
|
"""Create widgets for the DNA editor"""
|
|
# DNA File section
|
|
self.dna_file_group = QtWidgets.QGroupBox("DNA File")
|
|
|
|
self.dna_file_label = QtWidgets.QLabel("Current DNA File:")
|
|
self.dna_file_path = QtWidgets.QLineEdit()
|
|
self.dna_file_path.setReadOnly(True)
|
|
self.dna_file_path.setPlaceholderText("No DNA file loaded")
|
|
|
|
self.load_dna_button = QtWidgets.QPushButton("Load DNA")
|
|
self.load_dna_button.setIcon(QtGui.QIcon(os.path.join(config.ICONS_PATH, "open.png")))
|
|
|
|
self.save_dna_button = QtWidgets.QPushButton("Save DNA")
|
|
self.save_dna_button.setIcon(QtGui.QIcon(os.path.join(config.ICONS_PATH, "save.png")))
|
|
self.save_dna_button.setEnabled(False)
|
|
|
|
# DNA Parameters section
|
|
self.dna_params_group = QtWidgets.QGroupBox("DNA Parameters")
|
|
|
|
self.dna_version_label = QtWidgets.QLabel("DNA Version:")
|
|
self.dna_version_combo = QtWidgets.QComboBox()
|
|
self.dna_version_combo.addItems(["MH.4", "MH.3", "Custom"])
|
|
|
|
self.dna_mesh_label = QtWidgets.QLabel("Mesh Model:")
|
|
self.dna_mesh_combo = QtWidgets.QComboBox()
|
|
self.dna_mesh_combo.setEditable(True)
|
|
self.dna_mesh_combo.setInsertPolicy(QtWidgets.QComboBox.NoInsert)
|
|
|
|
self.refresh_meshes_button = QtWidgets.QPushButton()
|
|
self.refresh_meshes_button.setIcon(QtGui.QIcon(os.path.join(config.ICONS_PATH, "refresh.png")))
|
|
self.refresh_meshes_button.setToolTip("Refresh mesh models in the scene")
|
|
self.refresh_meshes_button.setFixedSize(24, 24)
|
|
|
|
self.dna_skeleton_label = QtWidgets.QLabel("Skeleton:")
|
|
self.dna_skeleton_combo = QtWidgets.QComboBox()
|
|
self.dna_skeleton_combo.setEditable(True)
|
|
self.dna_skeleton_combo.setInsertPolicy(QtWidgets.QComboBox.NoInsert)
|
|
|
|
self.refresh_skeletons_button = QtWidgets.QPushButton()
|
|
self.refresh_skeletons_button.setIcon(QtGui.QIcon(os.path.join(config.ICONS_PATH, "refresh.png")))
|
|
self.refresh_skeletons_button.setToolTip("Refresh skeletons in the scene")
|
|
self.refresh_skeletons_button.setFixedSize(24, 24)
|
|
|
|
# DNA Attributes section
|
|
self.dna_attrs_group = QtWidgets.QGroupBox("DNA Attributes")
|
|
|
|
self.dna_attrs_tree = QtWidgets.QTreeWidget()
|
|
self.dna_attrs_tree.setHeaderLabels(["Attribute", "Value"])
|
|
self.dna_attrs_tree.setColumnWidth(0, 200)
|
|
self.dna_attrs_tree.setAlternatingRowColors(True)
|
|
|
|
# DNA Operations section
|
|
self.dna_ops_group = QtWidgets.QGroupBox("DNA Operations")
|
|
|
|
self.create_dna_button = QtWidgets.QPushButton("Create DNA from Selected Model")
|
|
self.create_dna_button.setIcon(QtGui.QIcon(os.path.join(config.ICONS_PATH, "create.png")))
|
|
|
|
self.update_dna_button = QtWidgets.QPushButton("Update DNA")
|
|
self.update_dna_button.setIcon(QtGui.QIcon(os.path.join(config.ICONS_PATH, "update.png")))
|
|
self.update_dna_button.setEnabled(False)
|
|
|
|
self.export_dna_button = QtWidgets.QPushButton("Export DNA")
|
|
self.export_dna_button.setIcon(QtGui.QIcon(os.path.join(config.ICONS_PATH, "export.png")))
|
|
self.export_dna_button.setEnabled(False)
|
|
|
|
def _create_layouts(self):
|
|
"""Create layouts for the DNA editor"""
|
|
# DNA File layout
|
|
dna_file_layout = QtWidgets.QGridLayout()
|
|
dna_file_layout.addWidget(self.dna_file_label, 0, 0)
|
|
dna_file_layout.addWidget(self.dna_file_path, 0, 1, 1, 2)
|
|
dna_file_layout.addWidget(self.load_dna_button, 1, 1)
|
|
dna_file_layout.addWidget(self.save_dna_button, 1, 2)
|
|
self.dna_file_group.setLayout(dna_file_layout)
|
|
|
|
# DNA Parameters layout
|
|
dna_params_layout = QtWidgets.QGridLayout()
|
|
dna_params_layout.addWidget(self.dna_version_label, 0, 0)
|
|
dna_params_layout.addWidget(self.dna_version_combo, 0, 1, 1, 2)
|
|
|
|
dna_params_layout.addWidget(self.dna_mesh_label, 1, 0)
|
|
dna_params_layout.addWidget(self.dna_mesh_combo, 1, 1)
|
|
dna_params_layout.addWidget(self.refresh_meshes_button, 1, 2)
|
|
|
|
dna_params_layout.addWidget(self.dna_skeleton_label, 2, 0)
|
|
dna_params_layout.addWidget(self.dna_skeleton_combo, 2, 1)
|
|
dna_params_layout.addWidget(self.refresh_skeletons_button, 2, 2)
|
|
self.dna_params_group.setLayout(dna_params_layout)
|
|
|
|
# DNA Attributes layout
|
|
dna_attrs_layout = QtWidgets.QVBoxLayout()
|
|
dna_attrs_layout.addWidget(self.dna_attrs_tree)
|
|
self.dna_attrs_group.setLayout(dna_attrs_layout)
|
|
|
|
# DNA Operations layout
|
|
dna_ops_layout = QtWidgets.QHBoxLayout()
|
|
dna_ops_layout.addWidget(self.create_dna_button)
|
|
dna_ops_layout.addWidget(self.update_dna_button)
|
|
dna_ops_layout.addWidget(self.export_dna_button)
|
|
self.dna_ops_group.setLayout(dna_ops_layout)
|
|
|
|
# Main layout
|
|
main_layout = QtWidgets.QVBoxLayout()
|
|
main_layout.addWidget(self.dna_file_group)
|
|
main_layout.addWidget(self.dna_params_group)
|
|
main_layout.addWidget(self.dna_attrs_group)
|
|
main_layout.addWidget(self.dna_ops_group)
|
|
main_layout.addStretch()
|
|
|
|
self.setLayout(main_layout)
|
|
|
|
def _create_connections(self):
|
|
"""Create signal/slot connections for the DNA editor"""
|
|
# Connect buttons
|
|
self.load_dna_button.clicked.connect(self._on_load_dna)
|
|
self.save_dna_button.clicked.connect(self._on_save_dna)
|
|
self.refresh_meshes_button.clicked.connect(self._refresh_meshes)
|
|
self.refresh_skeletons_button.clicked.connect(self._refresh_skeletons)
|
|
self.create_dna_button.clicked.connect(self._on_create_dna)
|
|
self.update_dna_button.clicked.connect(self._on_update_dna)
|
|
self.export_dna_button.clicked.connect(self._on_export_dna)
|
|
|
|
# Connect combo boxes
|
|
self.dna_mesh_combo.currentIndexChanged.connect(self._on_mesh_changed)
|
|
self.dna_skeleton_combo.currentIndexChanged.connect(self._on_skeleton_changed)
|
|
|
|
# Connect tree widget
|
|
self.dna_attrs_tree.itemChanged.connect(self._on_attr_changed)
|
|
|
|
# Initial refresh
|
|
self._refresh_meshes()
|
|
self._refresh_skeletons()
|
|
|
|
def _refresh_meshes(self):
|
|
"""Refresh the list of meshes in the scene"""
|
|
current_text = self.dna_mesh_combo.currentText()
|
|
|
|
self.dna_mesh_combo.clear()
|
|
|
|
# Get all mesh shapes in the scene
|
|
meshes = cmds.ls(type="mesh")
|
|
mesh_transforms = []
|
|
|
|
for mesh in meshes:
|
|
# Get the transform node for the mesh
|
|
transform = cmds.listRelatives(mesh, parent=True)
|
|
if transform:
|
|
mesh_transforms.append(transform[0])
|
|
|
|
# Add unique mesh transforms to the combo box
|
|
unique_transforms = list(set(mesh_transforms))
|
|
unique_transforms.sort()
|
|
|
|
self.dna_mesh_combo.addItems(unique_transforms)
|
|
|
|
# Restore previous selection if possible
|
|
index = self.dna_mesh_combo.findText(current_text)
|
|
if index >= 0:
|
|
self.dna_mesh_combo.setCurrentIndex(index)
|
|
|
|
def _refresh_skeletons(self):
|
|
"""Refresh the list of skeletons in the scene"""
|
|
current_text = self.dna_skeleton_combo.currentText()
|
|
|
|
self.dna_skeleton_combo.clear()
|
|
|
|
# Get all joint hierarchies in the scene
|
|
root_joints = []
|
|
all_joints = cmds.ls(type="joint")
|
|
|
|
for joint in all_joints:
|
|
# Check if it's a root joint (no joint parent)
|
|
parent = cmds.listRelatives(joint, parent=True)
|
|
if not parent or cmds.nodeType(parent[0]) != "joint":
|
|
root_joints.append(joint)
|
|
|
|
# Add root joints to the combo box
|
|
root_joints.sort()
|
|
self.dna_skeleton_combo.addItems(root_joints)
|
|
|
|
# Restore previous selection if possible
|
|
index = self.dna_skeleton_combo.findText(current_text)
|
|
if index >= 0:
|
|
self.dna_skeleton_combo.setCurrentIndex(index)
|
|
|
|
def _on_load_dna(self):
|
|
"""Handle load DNA button click"""
|
|
file_path, _ = QtWidgets.QFileDialog.getOpenFileName(
|
|
self,
|
|
"选择 DNA 文件",
|
|
config.DNA_FILE_PATH,
|
|
"DNA Files (*.dna);;All Files (*.*)"
|
|
)
|
|
|
|
if file_path:
|
|
# Import the DNA utils here to avoid circular imports
|
|
from utils.dna_utils import DNAUtils
|
|
dna_utils = DNAUtils()
|
|
|
|
try:
|
|
result = dna_utils.load_dna(file_path)
|
|
if result:
|
|
self.dna_file_path.setText(file_path)
|
|
self.save_dna_button.setEnabled(True)
|
|
self.update_dna_button.setEnabled(True)
|
|
self.export_dna_button.setEnabled(True)
|
|
|
|
# Refresh meshes and skeletons
|
|
self._refresh_meshes()
|
|
self._refresh_skeletons()
|
|
|
|
# Load DNA attributes
|
|
self._load_dna_attributes(file_path)
|
|
|
|
# Show success message
|
|
QtWidgets.QMessageBox.information(
|
|
self,
|
|
"成功",
|
|
f"成功加载 DNA 文件: {os.path.basename(file_path)}"
|
|
)
|
|
else:
|
|
QtWidgets.QMessageBox.warning(
|
|
self,
|
|
"警告",
|
|
f"加载 DNA 文件失败: {os.path.basename(file_path)}"
|
|
)
|
|
except Exception as e:
|
|
QtWidgets.QMessageBox.critical(
|
|
self,
|
|
"Error",
|
|
f"加载 DNA 文件出错: {str(e)}"
|
|
)
|
|
|
|
def _on_save_dna(self):
|
|
"""Handle save DNA button click"""
|
|
file_path, _ = QtWidgets.QFileDialog.getSaveFileName(
|
|
self,
|
|
"保存 DNA 文件",
|
|
config.DNA_FILE_PATH,
|
|
"DNA Files (*.dna);;All Files (*.*)"
|
|
)
|
|
|
|
if file_path:
|
|
# Import the DNA utils here to avoid circular imports
|
|
from utils.dna_utils import DNAUtils
|
|
dna_utils = DNAUtils()
|
|
|
|
try:
|
|
result = dna_utils.save_dna(file_path)
|
|
if result:
|
|
self.dna_file_path.setText(file_path)
|
|
|
|
# Show success message
|
|
QtWidgets.QMessageBox.information(
|
|
self,
|
|
"成功",
|
|
f"成功保存 DNA 文件: {os.path.basename(file_path)}"
|
|
)
|
|
else:
|
|
QtWidgets.QMessageBox.warning(
|
|
self,
|
|
"警告",
|
|
f"保存 DNA 文件失败: {os.path.basename(file_path)}"
|
|
)
|
|
except Exception as e:
|
|
QtWidgets.QMessageBox.critical(
|
|
self,
|
|
"Error",
|
|
f"保存 DNA 文件出错: {str(e)}"
|
|
)
|
|
|
|
def _on_create_dna(self):
|
|
"""Handle create DNA button click"""
|
|
# Get selected mesh
|
|
mesh = self.dna_mesh_combo.currentText()
|
|
if not mesh:
|
|
QtWidgets.QMessageBox.warning(
|
|
self,
|
|
"警告",
|
|
"请先选择一个网格模型"
|
|
)
|
|
return
|
|
|
|
# Get selected skeleton
|
|
skeleton = self.dna_skeleton_combo.currentText()
|
|
if not skeleton:
|
|
QtWidgets.QMessageBox.warning(
|
|
self,
|
|
"警告",
|
|
"请先选择一个骨骼"
|
|
)
|
|
return
|
|
|
|
# Import the DNA utils here to avoid circular imports
|
|
from utils.dna_utils import DNAUtils
|
|
dna_utils = DNAUtils()
|
|
|
|
try:
|
|
result = dna_utils.create_dna(mesh, skeleton)
|
|
if result:
|
|
# Update UI
|
|
self.dna_file_path.setText("新建 DNA (未保存)")
|
|
self.save_dna_button.setEnabled(True)
|
|
self.update_dna_button.setEnabled(True)
|
|
self.export_dna_button.setEnabled(True)
|
|
|
|
# Load DNA attributes
|
|
self._load_dna_attributes()
|
|
|
|
# Show success message
|
|
QtWidgets.QMessageBox.information(
|
|
self,
|
|
"成功",
|
|
f"成功从 {mesh} 和 {skeleton} 创建 DNA"
|
|
)
|
|
else:
|
|
QtWidgets.QMessageBox.warning(
|
|
self,
|
|
"警告",
|
|
f"从 {mesh} 和 {skeleton} 创建 DNA 失败"
|
|
)
|
|
except Exception as e:
|
|
QtWidgets.QMessageBox.critical(
|
|
self,
|
|
"Error",
|
|
f"创建 DNA 出错: {str(e)}"
|
|
)
|
|
|
|
def _on_update_dna(self):
|
|
"""Handle update DNA button click"""
|
|
# Get selected mesh
|
|
mesh = self.dna_mesh_combo.currentText()
|
|
if not mesh:
|
|
QtWidgets.QMessageBox.warning(
|
|
self,
|
|
"警告",
|
|
"请先选择一个网格模型"
|
|
)
|
|
return
|
|
|
|
# Get selected skeleton
|
|
skeleton = self.dna_skeleton_combo.currentText()
|
|
if not skeleton:
|
|
QtWidgets.QMessageBox.warning(
|
|
self,
|
|
"警告",
|
|
"请先选择一个骨骼"
|
|
)
|
|
return
|
|
|
|
# Import the DNA utils here to avoid circular imports
|
|
from utils.dna_utils import DNAUtils
|
|
dna_utils = DNAUtils()
|
|
|
|
try:
|
|
result = dna_utils.update_dna(mesh, skeleton)
|
|
if result:
|
|
# Load DNA attributes
|
|
self._load_dna_attributes()
|
|
|
|
# Show success message
|
|
QtWidgets.QMessageBox.information(
|
|
self,
|
|
"成功",
|
|
f"成功更新 DNA"
|
|
)
|
|
else:
|
|
QtWidgets.QMessageBox.warning(
|
|
self,
|
|
"警告",
|
|
f"更新 DNA 失败"
|
|
)
|
|
except Exception as e:
|
|
QtWidgets.QMessageBox.critical(
|
|
self,
|
|
"Error",
|
|
f"更新 DNA 出错: {str(e)}"
|
|
)
|
|
|
|
def _on_export_dna(self):
|
|
"""Handle export DNA button click"""
|
|
file_path, _ = QtWidgets.QFileDialog.getSaveFileName(
|
|
self,
|
|
"导出 DNA 文件",
|
|
config.DNA_FILE_PATH,
|
|
"DNA Files (*.dna);;All Files (*.*)"
|
|
)
|
|
|
|
if file_path:
|
|
# Import the DNA utils here to avoid circular imports
|
|
from utils.dna_utils import DNAUtils
|
|
dna_utils = DNAUtils()
|
|
|
|
try:
|
|
result = dna_utils.export_dna(file_path)
|
|
if result:
|
|
# Show success message
|
|
QtWidgets.QMessageBox.information(
|
|
self,
|
|
"成功",
|
|
f"成功导出 DNA 文件: {os.path.basename(file_path)}"
|
|
)
|
|
else:
|
|
QtWidgets.QMessageBox.warning(
|
|
self,
|
|
"警告",
|
|
f"导出 DNA 文件失败: {os.path.basename(file_path)}"
|
|
)
|
|
except Exception as e:
|
|
QtWidgets.QMessageBox.critical(
|
|
self,
|
|
"Error",
|
|
f"导出 DNA 文件出错: {str(e)}"
|
|
)
|
|
|
|
def _on_mesh_changed(self, index):
|
|
"""Handle mesh combo box change"""
|
|
if index >= 0:
|
|
mesh = self.dna_mesh_combo.itemText(index)
|
|
# Select the mesh in the scene
|
|
cmds.select(mesh)
|
|
|
|
def _on_skeleton_changed(self, index):
|
|
"""Handle skeleton combo box change"""
|
|
if index >= 0:
|
|
skeleton = self.dna_skeleton_combo.itemText(index)
|
|
# Select the skeleton in the scene
|
|
cmds.select(skeleton)
|
|
|
|
def _on_attr_changed(self, item, column):
|
|
"""Handle attribute tree item change"""
|
|
if column == 1: # Value column
|
|
attr_path = []
|
|
current = item
|
|
|
|
# Build the attribute path
|
|
while current:
|
|
attr_path.insert(0, current.text(0))
|
|
current = current.parent()
|
|
|
|
# Remove the root item (category)
|
|
if attr_path:
|
|
attr_path.pop(0)
|
|
|
|
# Join the path
|
|
attr_name = ".".join(attr_path)
|
|
|
|
if attr_name:
|
|
# Get the new value
|
|
value = item.text(1)
|
|
|
|
# Import the DNA utils here to avoid circular imports
|
|
from utils.dna_utils import DNAUtils
|
|
dna_utils = DNAUtils()
|
|
|
|
try:
|
|
dna_utils.set_dna_attribute(attr_name, value)
|
|
except Exception as e:
|
|
QtWidgets.QMessageBox.critical(
|
|
self,
|
|
"Error",
|
|
f"Error setting DNA attribute: {str(e)}"
|
|
)
|
|
|
|
def _load_dna_attributes(self, dna_file=None):
|
|
"""Load DNA attributes into the tree widget"""
|
|
self.dna_attrs_tree.clear()
|
|
|
|
# Import the DNA utils here to avoid circular imports
|
|
from utils.dna_utils import DNAUtils
|
|
dna_utils = DNAUtils()
|
|
|
|
try:
|
|
# Get DNA attributes
|
|
attributes = dna_utils.get_dna_attributes(dna_file)
|
|
|
|
if attributes:
|
|
# Create categories
|
|
categories = {
|
|
"General": QtWidgets.QTreeWidgetItem(self.dna_attrs_tree, ["General"]),
|
|
"Mesh": QtWidgets.QTreeWidgetItem(self.dna_attrs_tree, ["Mesh"]),
|
|
"Skeleton": QtWidgets.QTreeWidgetItem(self.dna_attrs_tree, ["Skeleton"]),
|
|
"BlendShapes": QtWidgets.QTreeWidgetItem(self.dna_attrs_tree, ["BlendShapes"]),
|
|
"Animation": QtWidgets.QTreeWidgetItem(self.dna_attrs_tree, ["Animation"]),
|
|
"Other": QtWidgets.QTreeWidgetItem(self.dna_attrs_tree, ["Other"])
|
|
}
|
|
|
|
# Add attributes to categories
|
|
for attr_name, attr_value in attributes.items():
|
|
# Determine category
|
|
category = "Other"
|
|
if attr_name.startswith("general"):
|
|
category = "General"
|
|
elif attr_name.startswith("mesh"):
|
|
category = "Mesh"
|
|
elif attr_name.startswith("skeleton") or attr_name.startswith("joint"):
|
|
category = "Skeleton"
|
|
elif attr_name.startswith("blendshape") or attr_name.startswith("bs"):
|
|
category = "BlendShapes"
|
|
elif attr_name.startswith("anim"):
|
|
category = "Animation"
|
|
|
|
# Create item
|
|
item = QtWidgets.QTreeWidgetItem(categories[category], [attr_name, str(attr_value)])
|
|
item.setFlags(item.flags() | QtCore.Qt.ItemIsEditable)
|
|
|
|
# Expand all categories
|
|
self.dna_attrs_tree.expandAll()
|
|
except Exception as e:
|
|
QtWidgets.QMessageBox.critical(
|
|
self,
|
|
"Error",
|
|
f"Error loading DNA attributes: {str(e)}"
|
|
)
|