MetaWhiz/scripts/ui/dna_editor.py
2025-04-17 13:00:39 +08:00

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)}"
)