#!/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)}" )