727 lines
32 KiB
Python
727 lines
32 KiB
Python
#!/usr/bin/env python
|
||
# -*- coding: utf-8 -*-
|
||
|
||
"""
|
||
Rigging UI Module for Plugin
|
||
绑定系统UI模块 - 负责显示骨骼绑定编辑界面和基础操作
|
||
基本功能:
|
||
- DNA浏览器
|
||
- 根据DNA导入骨骼
|
||
- 根据DNA生成身体
|
||
- DNA校准
|
||
- 骨骼位置校准
|
||
- 创建绑定
|
||
- 复制蒙皮
|
||
"""
|
||
#========================================= IMPORT =========================================
|
||
from scripts.ui.Qt import QtWidgets, QtCore, QtGui
|
||
from scripts.ui.Qt.QtCompat import wrapInstance
|
||
from maya import OpenMayaUI as omui
|
||
import maya.cmds as cmds
|
||
import maya.mel as mel
|
||
import maya.utils as utils
|
||
import webbrowser
|
||
import subprocess
|
||
import importlib
|
||
import traceback
|
||
import locale
|
||
import sys
|
||
import os
|
||
from scripts.ui import ui_utils
|
||
from scripts.utils import utils_rigging
|
||
#========================================== CONFIG ========================================
|
||
import config
|
||
TOOL_NAME = config.TOOL_NAME
|
||
TOOL_VERSION = config.TOOL_VERSION
|
||
TOOL_AUTHOR = config.TOOL_AUTHOR
|
||
TOOL_YEAR = config.TOOL_YEAR
|
||
TOOL_MOD_FILENAME = config.TOOL_MOD_FILENAME
|
||
TOOL_LANG = config.TOOL_LANG
|
||
TOOL_WSCL_NAME = config.TOOL_WSCL_NAME
|
||
TOOL_HELP_URL = config.TOOL_HELP_URL
|
||
TOOL_PATH = config.TOOL_PATH
|
||
SCRIPTS_PATH = config.SCRIPTS_PATH
|
||
TOOL_MAIN_SCRIPT = config.TOOL_MAIN_SCRIPT
|
||
UI_PATH = config.UI_PATH
|
||
STYLE_FILE = config.STYLE_FILE
|
||
ICONS_PATH = config.ICONS_PATH
|
||
TOOL_ICON = config.TOOL_ICON
|
||
ASSETS_PATH = config.ASSETS_PATH
|
||
DNA_FILE_PATH = config.DNA_FILE_PATH
|
||
DNA_IMG_PATH = config.DNA_IMG_PATH
|
||
TOOL_COMMAND_ICON = config.TOOL_COMMAND_ICON
|
||
TOOL_WIDTH = config.TOOL_WIDTH
|
||
TOOL_HEIGHT = config.TOOL_HEIGHT
|
||
#========================================= LOCATION =======================================
|
||
from scripts.ui import localization
|
||
TEXT = localization.TEXT
|
||
|
||
|
||
class RiggingUI(ui_utils.BaseUI):
|
||
"""
|
||
绑定系统UI类 - 负责显示骨骼绑定编辑界面和基础操作
|
||
继承自BaseUI类,实现绑定系统相关的UI功能
|
||
"""
|
||
#========================================== INIT ========================================
|
||
def __init__(self, parent=None):
|
||
"""
|
||
初始化绑定系统UI
|
||
创建主控件和布局,并连接信号和槽
|
||
"""
|
||
super().__init__(parent)
|
||
|
||
# 创建主控件
|
||
self.main_widget = QtWidgets.QWidget()
|
||
self.main_widget.setObjectName("riggingMainWidget")
|
||
|
||
# 初始化DNA文件列表和按钮尺寸
|
||
self.dna_files = []
|
||
self.dna_button_size = (90, 120) # 保持3:4的宽高比例
|
||
|
||
# 初始化UI
|
||
self.create_widgets()
|
||
self.create_layouts()
|
||
self.create_connections()
|
||
|
||
# 更新UI文本
|
||
self.update_language()
|
||
|
||
# 加载DNA文件和预览图 - 移到最后执行确保所有控件已创建
|
||
utils_rigging.load_dna_files(self)
|
||
|
||
def closeEvent(self, event):
|
||
"""
|
||
窗口关闭事件处理
|
||
清理缓存和资源
|
||
"""
|
||
try:
|
||
# 清理图片缓存
|
||
from scripts.utils import utils_rigging
|
||
utils_rigging.clear_pixmap_cache()
|
||
|
||
# 调用父类的关闭事件
|
||
super().closeEvent(event)
|
||
except Exception as e:
|
||
print(f"窗口关闭处理失败: {str(e)}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
|
||
# 确保窗口正常关闭
|
||
event.accept()
|
||
|
||
#========================================= WIDGET =======================================
|
||
def create_widgets(self):
|
||
"""
|
||
创建绑定系统UI控件
|
||
包括按钮、标签、列表等
|
||
"""
|
||
# 标题标签 - 使用HTML格式化标题
|
||
title_text = f"<h4 style='margin:0;padding:5px;'>{TEXT('rigging_title', '骨骼绑定')}</h4>"
|
||
self.controls["title_label"] = QtWidgets.QLabel(title_text)
|
||
self.controls["title_label"].setObjectName("riggingTitleLabel")
|
||
self.controls["title_label"].setAlignment(QtCore.Qt.AlignCenter)
|
||
self.controls["title_label"].setMaximumHeight(30) # 限制标题高度
|
||
|
||
# 创建主分割器
|
||
self.splitters["main_splitter"] = QtWidgets.QSplitter(QtCore.Qt.Vertical)
|
||
self.splitters["main_splitter"].setObjectName("riggingMainSplitter")
|
||
self.splitters["main_splitter"].setHandleWidth(1) # 设置分割手柄宽度为最小值
|
||
|
||
# 1. Presets 面板
|
||
self.controls["presets_panel"] = QtWidgets.QWidget()
|
||
self.controls["presets_panel"].setObjectName("presetsPanel")
|
||
|
||
# Presets 组
|
||
self.controls["presets_group"] = QtWidgets.QGroupBox(TEXT("presets", "Presets"))
|
||
self.controls["presets_group"].setObjectName("presetsGroup")
|
||
|
||
# 创建预设显示区域
|
||
self.controls["presets_scroll_area"] = QtWidgets.QScrollArea()
|
||
self.controls["presets_scroll_area"].setWidgetResizable(True)
|
||
self.controls["presets_scroll_area"].setObjectName("presetsScrollArea")
|
||
self.controls["presets_scroll_area"].setMinimumHeight(400) # 大幅增加最小高度,确保能容纳多行按钮
|
||
self.controls["presets_scroll_area"].setMinimumWidth(600) # 设置最小宽度,确保有足够空间显示按钮
|
||
|
||
# 预设内容区域
|
||
self.controls["presets_content"] = QtWidgets.QWidget()
|
||
self.controls["presets_content"].setObjectName("presetsContent")
|
||
self.controls["presets_content"].setMinimumHeight(380) # 保持内容区域最小高度
|
||
self.controls["presets_content"].setStyleSheet("""
|
||
#presetsContent {
|
||
background-color: #252526;
|
||
margin: 0px;
|
||
padding: 0px;
|
||
}
|
||
""")
|
||
|
||
# 使用流式布局替代原来的网格布局
|
||
self.controls["presets_flow_layout"] = FlowLayout(self.controls["presets_content"])
|
||
self.controls["presets_flow_layout"].setObjectName("presetsFlowLayout")
|
||
self.controls["presets_flow_layout"].setContentsMargins(10, 10, 10, 10) # 设置内边距
|
||
self.controls["presets_flow_layout"].setSpacing(10) # 保持按钮间距
|
||
|
||
# 设置滚动区域内容
|
||
self.controls["presets_scroll_area"].setWidget(self.controls["presets_content"])
|
||
|
||
# 创建布局分隔器,添加垂直空间和分隔线
|
||
self.controls["presets_separator"] = QtWidgets.QFrame()
|
||
self.controls["presets_separator"].setFrameShape(QtWidgets.QFrame.HLine)
|
||
self.controls["presets_separator"].setFrameShadow(QtWidgets.QFrame.Sunken)
|
||
self.controls["presets_separator"].setFixedHeight(1) # 减小分隔线高度
|
||
self.controls["presets_separator"].setStyleSheet("background-color: #444444;")
|
||
|
||
# 预设底部滑块和按钮
|
||
self.controls["presets_slider_layout"] = QtWidgets.QHBoxLayout()
|
||
self.controls["presets_slider_layout"].setContentsMargins(3, 0, 3, 0) # 减小左右内边距
|
||
self.controls["presets_slider_layout"].setSpacing(4) # 减小间距
|
||
|
||
# 数量显示
|
||
self.controls["presets_count_label"] = QtWidgets.QLabel("0")
|
||
self.controls["presets_count_label"].setObjectName("presetsCountLabel")
|
||
self.controls["presets_count_label"].setAlignment(QtCore.Qt.AlignCenter)
|
||
self.controls["presets_count_label"].setStyleSheet("font-weight: bold; font-size: 12px;")
|
||
self.controls["presets_count_label"].setFixedWidth(40) # 增加宽度
|
||
|
||
# 滑块 - 用于调整预览图大小
|
||
self.controls["presets_slider"] = QtWidgets.QSlider(QtCore.Qt.Horizontal)
|
||
self.controls["presets_slider"].setObjectName("presetsSlider")
|
||
self.controls["presets_slider"].setMinimum(70) # 最小值70,对应缩放因子约0.8
|
||
self.controls["presets_slider"].setMaximum(130) # 最大值130,对应缩放因子约1.2
|
||
self.controls["presets_slider"].setValue(100) # 中间值100,对应缩放因子1.0
|
||
self.controls["presets_slider"].setToolTip("调整预览图大小")
|
||
self.controls["presets_slider"].setFixedHeight(22) # 设置固定高度使滑块更粗
|
||
self.controls["presets_slider"].setStyleSheet("""
|
||
QSlider::groove:horizontal {
|
||
border: 1px solid #444444;
|
||
height: 8px;
|
||
background: #333333;
|
||
margin: 2px 0;
|
||
border-radius: 4px;
|
||
}
|
||
QSlider::handle:horizontal {
|
||
background: #007ACC;
|
||
border: 1px solid #5AB2F5;
|
||
width: 16px;
|
||
height: 16px;
|
||
margin: -5px 0;
|
||
border-radius: 8px;
|
||
}
|
||
QSlider::handle:horizontal:hover {
|
||
background: #1C97EA;
|
||
border: 1px solid #7ABEF5;
|
||
}
|
||
""")
|
||
|
||
# 导出预设按钮
|
||
self.buttons["export_presets"] = QtWidgets.QPushButton(TEXT("export_presets", "导出预设"))
|
||
self.buttons["export_presets"].setObjectName("exportPresetsButton")
|
||
self.buttons["export_presets"].setIcon(ui_utils.load_icon("export"))
|
||
self.buttons["export_presets"].setFixedHeight(28) # 设置固定高度
|
||
|
||
# 导入预设按钮
|
||
self.buttons["import_presets"] = QtWidgets.QPushButton(TEXT("import_presets", "导入预设"))
|
||
self.buttons["import_presets"].setObjectName("importPresetsButton")
|
||
self.buttons["import_presets"].setIcon(ui_utils.load_icon("import"))
|
||
self.buttons["import_presets"].setFixedHeight(28) # 设置固定高度
|
||
|
||
# 添加到滑块布局
|
||
self.controls["presets_slider_layout"].addWidget(self.controls["presets_count_label"])
|
||
self.controls["presets_slider_layout"].addWidget(self.controls["presets_slider"])
|
||
self.controls["presets_slider_layout"].addWidget(self.buttons["export_presets"])
|
||
self.controls["presets_slider_layout"].addWidget(self.buttons["import_presets"])
|
||
|
||
# 2. Assets 面板
|
||
self.controls["assets_panel"] = QtWidgets.QWidget()
|
||
self.controls["assets_panel"].setObjectName("assetsPanel")
|
||
|
||
# Assets 组
|
||
self.controls["assets_group"] = QtWidgets.QGroupBox(TEXT("assets", "资产"))
|
||
self.controls["assets_group"].setObjectName("assetsGroup")
|
||
|
||
# 项目路径标签和输入框
|
||
self.controls["project_path_label"] = QtWidgets.QLabel(TEXT("project_path", "项目路径:"))
|
||
self.controls["project_path_label"].setObjectName("projectPathLabel")
|
||
|
||
self.controls["project_path_input"] = QtWidgets.QLineEdit()
|
||
self.controls["project_path_input"].setObjectName("projectPathInput")
|
||
self.controls["project_path_input"].setText("D:/Personal/Document/maya/SuperRiggingEditor/files/data/MetaHuman")
|
||
|
||
# 浏览按钮
|
||
self.buttons["browse_path"] = QtWidgets.QPushButton(" . . . ")
|
||
self.buttons["browse_path"].setObjectName("browsePathButton")
|
||
self.buttons["browse_path"].setIcon(ui_utils.load_icon("import"))
|
||
self.buttons["browse_path"].setMinimumWidth(0)
|
||
self.buttons["browse_path"].setFixedWidth(100)
|
||
|
||
# Presets DNA 标签和输入框
|
||
self.controls["presets_dna_label"] = QtWidgets.QLabel(TEXT("Presets DNA:", "预设 DNA:"))
|
||
self.controls["presets_dna_label"].setObjectName("presetsDnaLabel")
|
||
|
||
self.controls["presets_dna_input"] = QtWidgets.QLineEdit()
|
||
self.controls["presets_dna_input"].setObjectName("presetsDnaInput")
|
||
|
||
# 浏览按钮
|
||
self.buttons["browse_dna"] = QtWidgets.QPushButton(" . . . ")
|
||
self.buttons["browse_dna"].setObjectName("browseDnaButton")
|
||
self.buttons["browse_dna"].setIcon(ui_utils.load_icon("import"))
|
||
self.buttons["browse_dna"].setMinimumWidth(0)
|
||
self.buttons["browse_dna"].setFixedWidth(100)
|
||
|
||
# 3. Descriptor 面板
|
||
self.controls["descriptor_panel"] = QtWidgets.QWidget()
|
||
self.controls["descriptor_panel"].setObjectName("descriptorPanel")
|
||
|
||
# Descriptor 组
|
||
self.controls["descriptor_group"] = QtWidgets.QGroupBox(TEXT("descriptor", "描述"))
|
||
self.controls["descriptor_group"].setObjectName("descriptorGroup")
|
||
|
||
# 名称标签和输入框
|
||
self.controls["name_label"] = QtWidgets.QLabel(TEXT("name", "名称:"))
|
||
self.controls["name_label"].setObjectName("nameLabel")
|
||
|
||
self.controls["name_input"] = QtWidgets.QLineEdit()
|
||
self.controls["name_input"].setObjectName("nameInput")
|
||
|
||
# 原型标签和下拉框
|
||
self.controls["archetype_label"] = QtWidgets.QLabel(TEXT("archetype", "原型:"))
|
||
self.controls["archetype_label"].setObjectName("archetypeLabel")
|
||
|
||
self.controls["archetype_combo"] = QtWidgets.QComboBox()
|
||
self.controls["archetype_combo"].setObjectName("archetypeCombo")
|
||
self.controls["archetype_combo"].addItem("asian")
|
||
self.controls["archetype_combo"].addItem("caucasian")
|
||
self.controls["archetype_combo"].addItem("african")
|
||
|
||
# 性别标签和下拉框
|
||
self.controls["gender_label"] = QtWidgets.QLabel(TEXT("gender", "性别:"))
|
||
self.controls["gender_label"].setObjectName("genderLabel")
|
||
|
||
self.controls["gender_combo"] = QtWidgets.QComboBox()
|
||
self.controls["gender_combo"].setObjectName("genderCombo")
|
||
self.controls["gender_combo"].addItem("female")
|
||
self.controls["gender_combo"].addItem("male")
|
||
|
||
# 年龄标签和输入框
|
||
self.controls["age_label"] = QtWidgets.QLabel(TEXT("age", "年龄:"))
|
||
self.controls["age_label"].setObjectName("ageLabel")
|
||
|
||
self.controls["age_spinner"] = QtWidgets.QSpinBox()
|
||
self.controls["age_spinner"].setObjectName("ageSpinner")
|
||
self.controls["age_spinner"].setMinimum(1)
|
||
self.controls["age_spinner"].setMaximum(100)
|
||
self.controls["age_spinner"].setValue(24)
|
||
|
||
# 平移单位标签和下拉框
|
||
self.controls["translation_unit_label"] = QtWidgets.QLabel(TEXT("translation_unit", "平移单位:"))
|
||
self.controls["translation_unit_label"].setObjectName("translationUnitLabel")
|
||
|
||
self.controls["translation_unit_combo"] = QtWidgets.QComboBox()
|
||
self.controls["translation_unit_combo"].setObjectName("translationUnitCombo")
|
||
self.controls["translation_unit_combo"].addItem("cm")
|
||
self.controls["translation_unit_combo"].addItem("mm")
|
||
self.controls["translation_unit_combo"].addItem("m")
|
||
|
||
# 旋转单位标签和下拉框
|
||
self.controls["rotation_unit_label"] = QtWidgets.QLabel(TEXT("rotation_unit", "旋转单位:"))
|
||
self.controls["rotation_unit_label"].setObjectName("rotationUnitLabel")
|
||
|
||
self.controls["rotation_unit_combo"] = QtWidgets.QComboBox()
|
||
self.controls["rotation_unit_combo"].setObjectName("rotationUnitCombo")
|
||
self.controls["rotation_unit_combo"].addItem("degrees")
|
||
self.controls["rotation_unit_combo"].addItem("radians")
|
||
|
||
# 坐标系统标签和下拉框
|
||
self.controls["coordinate_system_label"] = QtWidgets.QLabel(TEXT("coordinate_system", "坐标系统:"))
|
||
self.controls["coordinate_system_label"].setObjectName("coordinateSystemLabel")
|
||
|
||
self.controls["coordinate_system_combo"] = QtWidgets.QComboBox()
|
||
self.controls["coordinate_system_combo"].setObjectName("coordinateSystemCombo")
|
||
self.controls["coordinate_system_combo"].addItem("YAxisUp")
|
||
self.controls["coordinate_system_combo"].addItem("ZAxisUp")
|
||
|
||
# LOD数量标签和输入框
|
||
self.controls["lod_count_label"] = QtWidgets.QLabel(TEXT("lod_count", "LOD数量:"))
|
||
self.controls["lod_count_label"].setObjectName("lodCountLabel")
|
||
|
||
self.controls["lod_count_spinner"] = QtWidgets.QSpinBox()
|
||
self.controls["lod_count_spinner"].setObjectName("lodCountSpinner")
|
||
self.controls["lod_count_spinner"].setMinimum(0)
|
||
self.controls["lod_count_spinner"].setMaximum(8)
|
||
self.controls["lod_count_spinner"].setValue(0)
|
||
|
||
# 底部按钮区域
|
||
self.controls["bottom_buttons_panel"] = QtWidgets.QWidget()
|
||
self.controls["bottom_buttons_panel"].setObjectName("bottomButtonsPanel")
|
||
|
||
# 删除所有按钮
|
||
self.buttons["remove_all"] = QtWidgets.QPushButton(TEXT("remove_all", "删除全部"))
|
||
self.buttons["remove_all"].setObjectName("removeAllButton")
|
||
self.buttons["remove_all"].setIcon(ui_utils.load_icon("delete.png"))
|
||
|
||
# 导入骨骼按钮
|
||
self.buttons["import_skeleton"] = QtWidgets.QPushButton(TEXT("import_skeleton", "导入骨骼"))
|
||
self.buttons["import_skeleton"].setObjectName("importSkeletonButton")
|
||
self.buttons["import_skeleton"].setIcon(ui_utils.load_icon("HIKCharacterToolSkeleton.png"))
|
||
|
||
# 生成绑定按钮
|
||
self.buttons["build_rigging"] = QtWidgets.QPushButton(TEXT("build_rigging", "创建绑定"))
|
||
self.buttons["build_rigging"].setObjectName("buildRiggingButton")
|
||
self.buttons["build_rigging"].setIcon(ui_utils.load_icon("HIKcreateControlRig.png"))
|
||
|
||
#========================================= LAYOUT =======================================
|
||
def create_layouts(self):
|
||
"""
|
||
创建绑定系统UI布局
|
||
组织控件的排列和层次结构
|
||
"""
|
||
# 创建主布局
|
||
main_layout = QtWidgets.QVBoxLayout(self.main_widget)
|
||
main_layout.setContentsMargins(5, 5, 5, 5)
|
||
main_layout.setSpacing(0) # 移除主布局中的间距
|
||
|
||
# 添加标题
|
||
main_layout.addWidget(self.controls["title_label"])
|
||
|
||
# 添加主分割器到主布局
|
||
main_layout.addWidget(self.splitters["main_splitter"])
|
||
|
||
# 1. Presets 面板布局
|
||
presets_layout = QtWidgets.QVBoxLayout(self.controls["presets_panel"])
|
||
presets_layout.setContentsMargins(0, 0, 0, 0)
|
||
presets_layout.setSpacing(0)
|
||
|
||
# 设置预设面板样式 - 确保无边距无内边距
|
||
self.controls["presets_panel"].setStyleSheet("""
|
||
QWidget {
|
||
margin: 0px;
|
||
padding: 0px;
|
||
border: none;
|
||
}
|
||
""")
|
||
|
||
# Presets 组布局
|
||
presets_group_layout = QtWidgets.QVBoxLayout(self.controls["presets_group"])
|
||
presets_group_layout.setContentsMargins(5, 5, 5, 5)
|
||
presets_group_layout.setSpacing(0)
|
||
|
||
# 添加预设滚动区域
|
||
presets_group_layout.addWidget(self.controls["presets_scroll_area"])
|
||
|
||
# 添加分隔线
|
||
self.controls["presets_separator"].setFixedHeight(2) # 减小分隔线高度
|
||
self.controls["presets_separator"].setStyleSheet("background-color: #444444;")
|
||
presets_group_layout.addWidget(self.controls["presets_separator"])
|
||
|
||
# 创建滑块容器,与上方内容无缝对接
|
||
slider_container = QtWidgets.QWidget()
|
||
slider_container.setObjectName("sliderContainer")
|
||
slider_container.setFixedHeight(35) # 高度
|
||
slider_container.setStyleSheet("""
|
||
#sliderContainer {
|
||
background-color: #252526;
|
||
border-top: 1px solid #303030;
|
||
border-bottom: none;
|
||
margin: 0px;
|
||
margin-bottom: -1px; /* 使其与下方元素重叠1px */
|
||
padding: 0px;
|
||
}
|
||
""")
|
||
slider_container_layout = QtWidgets.QVBoxLayout(slider_container)
|
||
slider_container_layout.setContentsMargins(5, 5, 5, 5) # 上下边距更小
|
||
slider_container_layout.setSpacing(0)
|
||
slider_container_layout.addLayout(self.controls["presets_slider_layout"])
|
||
|
||
# 添加滑块容器到布局
|
||
presets_group_layout.addWidget(slider_container)
|
||
|
||
# 添加Presets组到Presets面板
|
||
presets_layout.addWidget(self.controls["presets_group"])
|
||
|
||
# 2. Assets 面板布局
|
||
assets_layout = QtWidgets.QVBoxLayout(self.controls["assets_panel"])
|
||
assets_layout.setContentsMargins(0, 0, 0, 0) # 完全移除内边距
|
||
assets_layout.setSpacing(0) # 移除间距
|
||
|
||
# 设置Assets组的样式,确保紧贴在滑块容器下方
|
||
self.controls["assets_group"].setStyleSheet("""
|
||
QGroupBox {
|
||
margin-top: -1px; /* 使其与上方元素重叠1px */
|
||
padding-top: 5px;
|
||
border-top: none;
|
||
background-color: #252526;
|
||
}
|
||
""")
|
||
|
||
# Assets 组布局
|
||
assets_group_layout = QtWidgets.QGridLayout(self.controls["assets_group"])
|
||
assets_group_layout.setContentsMargins(5, 5, 5, 5)
|
||
assets_group_layout.setSpacing(5)
|
||
|
||
# 添加项目路径标签和输入框
|
||
assets_group_layout.addWidget(self.controls["project_path_label"], 0, 0)
|
||
assets_group_layout.addWidget(self.controls["project_path_input"], 0, 1)
|
||
assets_group_layout.addWidget(self.buttons["browse_path"], 0, 2)
|
||
|
||
# 添加Presets DNA标签和输入框
|
||
assets_group_layout.addWidget(self.controls["presets_dna_label"], 1, 0)
|
||
assets_group_layout.addWidget(self.controls["presets_dna_input"], 1, 1)
|
||
assets_group_layout.addWidget(self.buttons["browse_dna"], 1, 2)
|
||
|
||
# 添加Assets组到Assets面板
|
||
assets_layout.addWidget(self.controls["assets_group"])
|
||
|
||
# 3. Descriptor 面板布局
|
||
descriptor_layout = QtWidgets.QVBoxLayout(self.controls["descriptor_panel"])
|
||
descriptor_layout.setContentsMargins(0, 0, 0, 0)
|
||
descriptor_layout.setSpacing(5)
|
||
|
||
# Descriptor 组布局(优化:紧凑表单式,参考主流DCC风格)
|
||
descriptor_group_layout = QtWidgets.QGridLayout(self.controls["descriptor_group"])
|
||
descriptor_group_layout.setContentsMargins(16, 8, 16, 8) # 左右加大
|
||
descriptor_group_layout.setHorizontalSpacing(48) # 增大左右间距
|
||
descriptor_group_layout.setVerticalSpacing(10)
|
||
|
||
# 统一控件高度和字体
|
||
for key in [
|
||
"name_input", "archetype_combo", "gender_combo", "age_spinner", "lod_count_spinner",
|
||
"translation_unit_combo", "rotation_unit_combo", "coordinate_system_combo"
|
||
]:
|
||
self.controls[key].setFixedHeight(24)
|
||
self.controls[key].setStyleSheet("font-size: 13px;")
|
||
for key in [
|
||
"name_label", "archetype_label", "gender_label", "age_label", "lod_count_label",
|
||
"translation_unit_label", "rotation_unit_label", "coordinate_system_label"
|
||
]:
|
||
self.controls[key].setStyleSheet("font-size: 13px;")
|
||
self.controls[key].setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||
|
||
# 设置每列最小宽度/拉伸,保证左右均衡
|
||
descriptor_group_layout.setColumnMinimumWidth(0, 80) # 左标签
|
||
descriptor_group_layout.setColumnMinimumWidth(1, 180) # 左输入
|
||
descriptor_group_layout.setColumnMinimumWidth(2, 80) # 右标签
|
||
descriptor_group_layout.setColumnMinimumWidth(3, 180) # 右输入
|
||
descriptor_group_layout.setColumnStretch(1, 2)
|
||
descriptor_group_layout.setColumnStretch(3, 2)
|
||
|
||
# 第一行:名称
|
||
descriptor_group_layout.addWidget(self.controls["name_label"], 0, 0)
|
||
descriptor_group_layout.addWidget(self.controls["name_input"], 0, 1, 1, 3)
|
||
|
||
# 第二行:原型、性别
|
||
descriptor_group_layout.addWidget(self.controls["archetype_label"], 1, 0)
|
||
descriptor_group_layout.addWidget(self.controls["archetype_combo"], 1, 1)
|
||
descriptor_group_layout.addWidget(self.controls["gender_label"], 1, 2)
|
||
descriptor_group_layout.addWidget(self.controls["gender_combo"], 1, 3)
|
||
|
||
# 第三行:年龄、LOD数量
|
||
descriptor_group_layout.addWidget(self.controls["age_label"], 2, 0)
|
||
descriptor_group_layout.addWidget(self.controls["age_spinner"], 2, 1)
|
||
descriptor_group_layout.addWidget(self.controls["lod_count_label"], 2, 2)
|
||
descriptor_group_layout.addWidget(self.controls["lod_count_spinner"], 2, 3)
|
||
|
||
# 第四行:平移单位、旋转单位
|
||
descriptor_group_layout.addWidget(self.controls["translation_unit_label"], 3, 0)
|
||
descriptor_group_layout.addWidget(self.controls["translation_unit_combo"], 3, 1)
|
||
descriptor_group_layout.addWidget(self.controls["rotation_unit_label"], 3, 2)
|
||
descriptor_group_layout.addWidget(self.controls["rotation_unit_combo"], 3, 3)
|
||
|
||
# 第五行:坐标系统
|
||
descriptor_group_layout.addWidget(self.controls["coordinate_system_label"], 4, 0)
|
||
descriptor_group_layout.addWidget(self.controls["coordinate_system_combo"], 4, 1)
|
||
# 右侧空白占位,保持对齐美观
|
||
descriptor_group_layout.addItem(QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum), 4, 2, 1, 2)
|
||
|
||
# 添加Descriptor组到Descriptor面板
|
||
descriptor_layout.addWidget(self.controls["descriptor_group"])
|
||
|
||
# 添加各个面板到主分割器
|
||
self.splitters["main_splitter"].addWidget(self.controls["presets_panel"])
|
||
self.splitters["main_splitter"].addWidget(self.controls["assets_panel"])
|
||
self.splitters["main_splitter"].addWidget(self.controls["descriptor_panel"])
|
||
|
||
# 底部按钮区域布局
|
||
bottom_buttons_layout = QtWidgets.QHBoxLayout(self.controls["bottom_buttons_panel"])
|
||
bottom_buttons_layout.setContentsMargins(5, 5, 5, 5)
|
||
bottom_buttons_layout.setSpacing(10)
|
||
|
||
# 添加底部按钮(均等宽度)
|
||
for btn in [self.buttons["remove_all"], self.buttons["import_skeleton"], self.buttons["build_rigging"]]:
|
||
btn.setMinimumWidth(0)
|
||
btn.setMaximumWidth(16777215)
|
||
btn.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
|
||
bottom_buttons_layout.addWidget(btn)
|
||
|
||
# 添加底部按钮区域到主布局
|
||
main_layout.addWidget(self.controls["bottom_buttons_panel"])
|
||
|
||
# 设置分割器初始大小
|
||
self.splitters["main_splitter"].setSizes([800, 100, 200]) # 进一步增加顶部预设面板的高度
|
||
|
||
# 设置分割器的伸缩因子 - 让预设面板获得更多空间
|
||
self.splitters["main_splitter"].setStretchFactor(0, 4) # 预设面板占4份
|
||
self.splitters["main_splitter"].setStretchFactor(1, 1) # Assets面板占1份
|
||
self.splitters["main_splitter"].setStretchFactor(2, 1) # Descriptor面板占1份
|
||
|
||
# 设置分割器的样式,减少间隙
|
||
self.splitters["main_splitter"].setStyleSheet("""
|
||
QSplitter {
|
||
margin: 0px;
|
||
padding: 0px;
|
||
spacing: 0px;
|
||
border: none;
|
||
}
|
||
QSplitter::handle {
|
||
background-color: #252526;
|
||
height: 0px;
|
||
margin: 0px;
|
||
padding: 0px;
|
||
}
|
||
""")
|
||
self.splitters["main_splitter"].setHandleWidth(0) # 设置分割器手柄宽度为0
|
||
|
||
# 监听预设内容区域大小变化,更新DNA网格布局
|
||
self.controls["presets_content"].resizeEvent = lambda event: utils_rigging.on_presets_content_resize(event, self)
|
||
|
||
# 应用全局紧凑样式,移除所有可能的间隙
|
||
self.setStyleSheet("""
|
||
/* 全局无边框无缝样式 */
|
||
QGroupBox, QFrame {
|
||
border: none;
|
||
margin: 0px;
|
||
padding: 0px;
|
||
}
|
||
QSplitter::handle:hover {
|
||
background-color: #252526;
|
||
}
|
||
QWidget#riggingMainWidget QVBoxLayout {
|
||
margin: 0px;
|
||
padding: 0px;
|
||
spacing: 0px;
|
||
}
|
||
/* 确保Assets组紧贴上方 */
|
||
QGroupBox#assetsGroup {
|
||
margin-top: -1px;
|
||
padding-top: 5px;
|
||
}
|
||
/* 确保滑块容器紧贴底部 */
|
||
QWidget#sliderContainer {
|
||
margin-bottom: -1px;
|
||
}
|
||
""")
|
||
|
||
#======================================= FUNCTIONS ======================================
|
||
def create_connections(self):
|
||
"""
|
||
创建信号连接,设置UI控件的交互行为
|
||
"""
|
||
|
||
# 预设导入和导出按钮
|
||
self.buttons["export_presets"].clicked.connect(utils_rigging.export_dna)
|
||
self.buttons["import_presets"].clicked.connect(utils_rigging.import_dna)
|
||
|
||
# 预设滑块连接 - 控制预览按钮大小
|
||
self.controls["presets_slider"].valueChanged.connect(lambda value: utils_rigging.on_presets_slider_changed(self, value))
|
||
|
||
# 浏览文件按钮连接
|
||
self.buttons["browse_path"].clicked.connect(lambda: utils_rigging.browse_file(self, "项目路径", self.controls["project_path_input"]))
|
||
self.buttons["browse_dna"].clicked.connect(lambda: utils_rigging.browse_file(self, "DNA文件", self.controls["presets_dna_input"], "dna"))
|
||
|
||
# 底部按钮连接
|
||
self.buttons["remove_all"].clicked.connect(utils_rigging.remove_all)
|
||
self.buttons["import_skeleton"].clicked.connect(utils_rigging.import_skeleton)
|
||
self.buttons["build_rigging"].clicked.connect(utils_rigging.build_rigging)
|
||
|
||
|
||
class FlowLayout(QtWidgets.QLayout):
|
||
"""
|
||
流式布局 - 自动调整每行的项目数量,保持固定间距
|
||
来源: Qt文档示例改进版
|
||
"""
|
||
def __init__(self, parent=None, margin=0, spacing=-1):
|
||
super(FlowLayout, self).__init__(parent)
|
||
|
||
if parent is not None:
|
||
self.setContentsMargins(margin, margin, margin, margin)
|
||
|
||
self.setSpacing(spacing)
|
||
|
||
self._item_list = []
|
||
|
||
def __del__(self):
|
||
item = self.takeAt(0)
|
||
while item:
|
||
item = self.takeAt(0)
|
||
|
||
def addItem(self, item):
|
||
self._item_list.append(item)
|
||
|
||
def count(self):
|
||
return len(self._item_list)
|
||
|
||
def itemAt(self, index):
|
||
if 0 <= index < len(self._item_list):
|
||
return self._item_list[index]
|
||
|
||
return None
|
||
|
||
def takeAt(self, index):
|
||
if 0 <= index < len(self._item_list):
|
||
return self._item_list.pop(index)
|
||
|
||
return None
|
||
|
||
def expandingDirections(self):
|
||
return QtCore.Qt.Orientations(QtCore.Qt.Orientation(0))
|
||
|
||
def hasHeightForWidth(self):
|
||
return True
|
||
|
||
def heightForWidth(self, width):
|
||
height = self._do_layout(QtCore.QRect(0, 0, width, 0), True)
|
||
return height
|
||
|
||
def setGeometry(self, rect):
|
||
super(FlowLayout, self).setGeometry(rect)
|
||
self._do_layout(rect, False)
|
||
|
||
def sizeHint(self):
|
||
return self.minimumSize()
|
||
|
||
def minimumSize(self):
|
||
size = QtCore.QSize()
|
||
|
||
for item in self._item_list:
|
||
size = size.expandedTo(item.minimumSize())
|
||
|
||
margin = self.contentsMargins()
|
||
size += QtCore.QSize(margin.left() + margin.right(), margin.top() + margin.bottom())
|
||
return size
|
||
|
||
def _do_layout(self, rect, test_only=False):
|
||
x = rect.x()
|
||
y = rect.y()
|
||
line_height = 0
|
||
spacing = self.spacing()
|
||
|
||
for item in self._item_list:
|
||
widget = item.widget()
|
||
item_width = item.sizeHint().width()
|
||
item_height = item.sizeHint().height()
|
||
|
||
# 检查是否需要换行 - 当前项放不下时
|
||
next_x = x + item_width + spacing
|
||
|
||
if next_x - spacing > rect.right() and line_height > 0:
|
||
x = rect.x()
|
||
y = y + line_height + spacing
|
||
next_x = x + item_width + spacing
|
||
line_height = 0
|
||
|
||
if not test_only:
|
||
item.setGeometry(QtCore.QRect(QtCore.QPoint(x, y), item.sizeHint()))
|
||
|
||
x = next_x
|
||
line_height = max(line_height, item_height)
|
||
|
||
return y + line_height - rect.y()
|