524 lines
20 KiB
Python
524 lines
20 KiB
Python
#!/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)}")
|