Files
MetaFusion/scripts/ui/rigging.py
2025-05-12 09:25:20 +08:00

727 lines
32 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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