#!/usr/bin/env python # -*- coding: utf-8 -*- """ Tool - Calibration UI Provides the UI for calibrating skeleton and joint positions """ 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 CalibrationWidget(QtWidgets.QWidget): """Widget for calibrating skeleton and joint positions""" def __init__(self, parent=None): """Initialize the calibration widget""" super(CalibrationWidget, self).__init__(parent) # Initialize UI self._create_widgets() self._create_layouts() self._create_connections() def _create_widgets(self): """Create widgets for the calibration interface""" # Skeleton Selection section self.skeleton_group = QtWidgets.QGroupBox("Skeleton Selection") self.skeleton_label = QtWidgets.QLabel("Skeleton Root:") self.skeleton_combo = QtWidgets.QComboBox() self.skeleton_combo.setEditable(True) self.skeleton_combo.setInsertPolicy(QtWidgets.QComboBox.NoInsert) self.refresh_skeleton_button = QtWidgets.QPushButton() self.refresh_skeleton_button.setIcon(QtGui.QIcon(os.path.join(config.ICONS_PATH, "refresh.png"))) self.refresh_skeleton_button.setToolTip("Refresh skeletons in the scene") self.refresh_skeleton_button.setFixedSize(24, 24) self.select_skeleton_button = QtWidgets.QPushButton("Select Skeleton") self.select_skeleton_button.setIcon(QtGui.QIcon(os.path.join(config.ICONS_PATH, "select.png"))) # Reference Skeleton section self.reference_group = QtWidgets.QGroupBox("Reference Skeleton") self.reference_label = QtWidgets.QLabel("Reference Skeleton:") self.reference_combo = QtWidgets.QComboBox() self.reference_combo.addItems(["MetaHuman Standard", "Custom"]) self.load_reference_button = QtWidgets.QPushButton("Load Reference") self.load_reference_button.setIcon(QtGui.QIcon(os.path.join(config.ICONS_PATH, "load.png"))) # Joint List section self.joints_group = QtWidgets.QGroupBox("Joint List") self.joints_tree = QtWidgets.QTreeWidget() self.joints_tree.setHeaderLabels(["Joint Name", "Position", "Rotation", "Status"]) self.joints_tree.setColumnWidth(0, 150) self.joints_tree.setColumnWidth(1, 150) self.joints_tree.setColumnWidth(2, 150) self.joints_tree.setAlternatingRowColors(True) # Calibration Controls section self.controls_group = QtWidgets.QGroupBox("Calibration Controls") self.auto_calibrate_button = QtWidgets.QPushButton("Auto Calibrate") self.auto_calibrate_button.setIcon(QtGui.QIcon(os.path.join(config.ICONS_PATH, "auto.png"))) self.manual_calibrate_button = QtWidgets.QPushButton("Manual Calibrate") self.manual_calibrate_button.setIcon(QtGui.QIcon(os.path.join(config.ICONS_PATH, "manual.png"))) self.reset_calibration_button = QtWidgets.QPushButton("Reset Calibration") self.reset_calibration_button.setIcon(QtGui.QIcon(os.path.join(config.ICONS_PATH, "reset.png"))) self.apply_calibration_button = QtWidgets.QPushButton("Apply Calibration") self.apply_calibration_button.setIcon(QtGui.QIcon(os.path.join(config.ICONS_PATH, "apply.png"))) # Visualization section self.visual_group = QtWidgets.QGroupBox("Visualization") self.show_reference_check = QtWidgets.QCheckBox("Show Reference Skeleton") self.show_reference_check.setChecked(True) self.show_current_check = QtWidgets.QCheckBox("Show Current Skeleton") self.show_current_check.setChecked(True) self.show_differences_check = QtWidgets.QCheckBox("Show Differences") self.show_differences_check.setChecked(True) def _create_layouts(self): """Create layouts for the calibration interface""" # Skeleton Selection layout skeleton_layout = QtWidgets.QGridLayout() skeleton_layout.addWidget(self.skeleton_label, 0, 0) skeleton_layout.addWidget(self.skeleton_combo, 0, 1) skeleton_layout.addWidget(self.refresh_skeleton_button, 0, 2) skeleton_layout.addWidget(self.select_skeleton_button, 1, 1) self.skeleton_group.setLayout(skeleton_layout) # Reference Skeleton layout reference_layout = QtWidgets.QGridLayout() reference_layout.addWidget(self.reference_label, 0, 0) reference_layout.addWidget(self.reference_combo, 0, 1) reference_layout.addWidget(self.load_reference_button, 1, 1) self.reference_group.setLayout(reference_layout) # Joint List layout joints_layout = QtWidgets.QVBoxLayout() joints_layout.addWidget(self.joints_tree) self.joints_group.setLayout(joints_layout) # Calibration Controls layout controls_layout = QtWidgets.QHBoxLayout() controls_layout.addWidget(self.auto_calibrate_button) controls_layout.addWidget(self.manual_calibrate_button) controls_layout.addWidget(self.reset_calibration_button) controls_layout.addWidget(self.apply_calibration_button) self.controls_group.setLayout(controls_layout) # Visualization layout visual_layout = QtWidgets.QHBoxLayout() visual_layout.addWidget(self.show_reference_check) visual_layout.addWidget(self.show_current_check) visual_layout.addWidget(self.show_differences_check) visual_layout.addStretch() self.visual_group.setLayout(visual_layout) # Main layout main_layout = QtWidgets.QVBoxLayout() main_layout.addWidget(self.skeleton_group) main_layout.addWidget(self.reference_group) main_layout.addWidget(self.joints_group) main_layout.addWidget(self.controls_group) main_layout.addWidget(self.visual_group) self.setLayout(main_layout) def _create_connections(self): """Create signal/slot connections for the calibration interface""" # Connect buttons self.refresh_skeleton_button.clicked.connect(self._refresh_skeletons) self.select_skeleton_button.clicked.connect(self._select_skeleton) self.load_reference_button.clicked.connect(self._load_reference) self.auto_calibrate_button.clicked.connect(self._auto_calibrate) self.manual_calibrate_button.clicked.connect(self._manual_calibrate) self.reset_calibration_button.clicked.connect(self._reset_calibration) self.apply_calibration_button.clicked.connect(self._apply_calibration) # Connect combo boxes self.skeleton_combo.currentIndexChanged.connect(self._on_skeleton_changed) self.reference_combo.currentIndexChanged.connect(self._on_reference_changed) # Connect checkboxes self.show_reference_check.stateChanged.connect(self._update_visualization) self.show_current_check.stateChanged.connect(self._update_visualization) self.show_differences_check.stateChanged.connect(self._update_visualization) # Connect tree widget self.joints_tree.itemClicked.connect(self._on_joint_selected) # Initial refresh self._refresh_skeletons() def _refresh_skeletons(self): """Refresh the list of skeletons in the scene""" current_text = self.skeleton_combo.currentText() self.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.skeleton_combo.addItems(root_joints) # Restore previous selection if possible index = self.skeleton_combo.findText(current_text) if index >= 0: self.skeleton_combo.setCurrentIndex(index) # Update joint tree self._update_joint_tree() def _select_skeleton(self): """Select the current skeleton in the scene""" skeleton = self.skeleton_combo.currentText() if skeleton: cmds.select(skeleton) def _load_reference(self): """Load reference skeleton""" reference_type = self.reference_combo.currentText() if reference_type == "MetaHuman Standard": # Import the joint utils here to avoid circular imports from utils.joint_utils import JointUtils joint_utils = JointUtils() try: result = joint_utils.load_metahuman_reference() if result: self._update_joint_tree() QtWidgets.QMessageBox.information( self, "Success", "成功加载 MetaHuman 标准骨骼参考" ) else: QtWidgets.QMessageBox.warning( self, "Warning", "加载 MetaHuman 标准骨骼参考失败" ) except Exception as e: QtWidgets.QMessageBox.critical( self, "Error", f"加载骨骼参考出错: {str(e)}" ) elif reference_type == "Custom": file_path, _ = QtWidgets.QFileDialog.getOpenFileName( self, "选择参考骨骼文件", "", "Maya Files (*.ma *.mb);;JSON Files (*.json);;All Files (*.*)" ) if file_path: # Import the joint utils here to avoid circular imports from utils.joint_utils import JointUtils joint_utils = JointUtils() try: result = joint_utils.load_custom_reference(file_path) if result: self._update_joint_tree() QtWidgets.QMessageBox.information( self, "Success", f"成功加载自定义骨骼参考: {os.path.basename(file_path)}" ) else: QtWidgets.QMessageBox.warning( self, "Warning", f"加载自定义骨骼参考失败: {os.path.basename(file_path)}" ) except Exception as e: QtWidgets.QMessageBox.critical( self, "Error", f"加载骨骼参考出错: {str(e)}" ) def _auto_calibrate(self): """Auto-calibrate the skeleton""" skeleton = self.skeleton_combo.currentText() if not skeleton: QtWidgets.QMessageBox.warning( self, "Warning", "请先选择一个骨骼" ) return # Import the joint utils here to avoid circular imports from utils.joint_utils import JointUtils joint_utils = JointUtils() try: result = joint_utils.auto_calibrate(skeleton) if result: self._update_joint_tree() QtWidgets.QMessageBox.information( self, "Success", f"成功自动校准骨骼: {skeleton}" ) else: QtWidgets.QMessageBox.warning( self, "Warning", f"自动校准骨骼失败: {skeleton}" ) except Exception as e: QtWidgets.QMessageBox.critical( self, "Error", f"自动校准骨骼出错: {str(e)}" ) def _manual_calibrate(self): """Manual calibrate the skeleton""" skeleton = self.skeleton_combo.currentText() if not skeleton: QtWidgets.QMessageBox.warning( self, "Warning", "请先选择一个骨骼" ) return # Get selected joint from tree selected_items = self.joints_tree.selectedItems() if not selected_items: QtWidgets.QMessageBox.warning( self, "Warning", "请先在关节列表中选择一个关节" ) return joint_name = selected_items[0].text(0) # Import the joint utils here to avoid circular imports from utils.joint_utils import JointUtils joint_utils = JointUtils() try: # Select the joint in the scene cmds.select(f"{skeleton}|{joint_name}") # Show manual calibration UI result = joint_utils.show_manual_calibration_ui(f"{skeleton}|{joint_name}") if result: self._update_joint_tree() else: QtWidgets.QMessageBox.warning( self, "Warning", f"手动校准关节失败: {joint_name}" ) except Exception as e: QtWidgets.QMessageBox.critical( self, "Error", f"手动校准关节出错: {str(e)}" ) def _reset_calibration(self): """Reset calibration to default""" skeleton = self.skeleton_combo.currentText() if not skeleton: QtWidgets.QMessageBox.warning( self, "Warning", "请先选择一个骨骼" ) return # Confirm reset result = QtWidgets.QMessageBox.question( self, "确认", "确定要重置所有校准数据吗?", QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No ) if result == QtWidgets.QMessageBox.Yes: # Import the joint utils here to avoid circular imports from utils.joint_utils import JointUtils joint_utils = JointUtils() try: result = joint_utils.reset_calibration(skeleton) if result: self._update_joint_tree() QtWidgets.QMessageBox.information( self, "Success", f"成功重置骨骼校准: {skeleton}" ) else: QtWidgets.QMessageBox.warning( self, "Warning", f"重置骨骼校准失败: {skeleton}" ) except Exception as e: QtWidgets.QMessageBox.critical( self, "Error", f"重置骨骼校准出错: {str(e)}" ) def _apply_calibration(self): """Apply calibration to the skeleton""" skeleton = self.skeleton_combo.currentText() if not skeleton: QtWidgets.QMessageBox.warning( self, "Warning", "请先选择一个骨骼" ) return # Import the joint utils here to avoid circular imports from utils.joint_utils import JointUtils joint_utils = JointUtils() try: result = joint_utils.apply_calibration(skeleton) if result: self._update_joint_tree() QtWidgets.QMessageBox.information( self, "Success", f"Successfully applied skeleton calibration: {skeleton}" ) else: QtWidgets.QMessageBox.warning( self, "Warning", f"Failed to apply skeleton calibration: {skeleton}" ) except Exception as e: QtWidgets.QMessageBox.critical( self, "Error", f"Error applying skeleton calibration: {str(e)}" ) def _on_skeleton_changed(self, index): """Handle skeleton combo box change""" if index >= 0: # Update joint tree self._update_joint_tree() def _on_reference_changed(self, index): """Handle reference combo box change""" if index >= 0: # Update joint tree self._update_joint_tree() def _on_joint_selected(self, item, column): """Handle joint tree item selection""" joint_name = item.text(0) skeleton = self.skeleton_combo.currentText() if skeleton and joint_name: # Select the joint in the scene try: cmds.select(f"{skeleton}|{joint_name}") except Exception: pass def _update_joint_tree(self): """Update the joint tree with current skeleton information""" self.joints_tree.clear() skeleton = self.skeleton_combo.currentText() if not skeleton: return # Import the joint utils here to avoid circular imports from utils.joint_utils import JointUtils joint_utils = JointUtils() try: # Get joint information joints_info = joint_utils.get_joints_info(skeleton) if joints_info: # Add joints to tree for joint_name, joint_info in joints_info.items(): position = joint_info.get("position", "") rotation = joint_info.get("rotation", "") status = joint_info.get("status", "Not Calibrated") item = QtWidgets.QTreeWidgetItem(self.joints_tree, [ joint_name, str(position), str(rotation), status ]) # Set item color based on status if status == "Calibrated": item.setForeground(3, QtGui.QBrush(QtGui.QColor("green"))) elif status == "Partially Calibrated": item.setForeground(3, QtGui.QBrush(QtGui.QColor("orange"))) else: item.setForeground(3, QtGui.QBrush(QtGui.QColor("red"))) except Exception as e: print(f"Error updating joint tree: {str(e)}") def _update_visualization(self): """Update visualization based on checkbox states""" show_reference = self.show_reference_check.isChecked() show_current = self.show_current_check.isChecked() show_differences = self.show_differences_check.isChecked() # Import the joint utils here to avoid circular imports from utils.joint_utils import JointUtils joint_utils = JointUtils() try: joint_utils.update_visualization( show_reference=show_reference, show_current=show_current, show_differences=show_differences ) except Exception as e: print(f"Error updating visualization: {str(e)}")