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

908 lines
31 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 function module
绑定系统功能模块 - 提供绑定系统相关的功能函数
"""
#===================================== IMPORT MODULES =====================================
import maya.cmds as cmds
import pymel.core as pm
import maya.mel as mel
from maya import OpenMayaUI as omui
from scripts.ui.Qt import QtWidgets, QtCore, QtGui
from scripts.ui.Qt.QtCompat import wrapInstance
import webbrowser
import subprocess
import importlib
import traceback
import sys
import os
# 导入配置
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
# Localization
from scripts.ui import localization
TEXT = localization.TEXT
#========================================== GLOBALS ========================================
# 存储当前选中的关节和控制器信息
selected_joint = None
selected_controller = None
# 存储关节和控制器的原始属性,用于撤销操作
original_joint_properties = {}
original_controller_properties = {}
# DNA预览按钮默认尺寸
DEFAULT_DNA_BUTTON_SIZE = (90, 120) # 保持3:4的宽高比例
#========================================== FUNCTIONS ========================================
#------------------------------------ DNA PREVIEW ------------------------------------
def get_dna_files():
"""
获取所有DNA文件及其对应的预览图
Returns:
list: 包含DNA文件信息的字典列表每个字典包含:
- name: DNA文件名不含扩展名
- dna_path: DNA文件的完整路径
- image_path: 预览图的完整路径
"""
dna_files = []
default_preview = os.path.join(DNA_IMG_PATH, "Preview.png")
# 调试信息
print(f"加载DNA文件目录: {DNA_FILE_PATH}")
print(f"加载DNA图片目录: {DNA_IMG_PATH}")
# 确保目录存在
if not os.path.exists(DNA_FILE_PATH):
print(f"错误: DNA文件目录不存在: {DNA_FILE_PATH}")
return dna_files
if not os.path.exists(DNA_IMG_PATH):
print(f"错误: DNA图片目录不存在: {DNA_IMG_PATH}")
if not os.path.exists(default_preview):
print(f"错误: 默认预览图不存在: {default_preview}")
# 列出所有DNA文件
try:
dna_file_list = os.listdir(DNA_FILE_PATH)
print(f"找到 {len(dna_file_list)} 个文件在 DNA目录")
except Exception as e:
print(f"列出DNA文件失败: {str(e)}")
return dna_files
# 获取所有可用的预览图文件名(不带扩展名),只查询一次目录提高性能
available_images = set()
try:
img_files = os.listdir(DNA_IMG_PATH)
for img_file in img_files:
if img_file.lower().endswith('.png'):
base_name = os.path.splitext(img_file)[0]
available_images.add(base_name)
except Exception as e:
print(f"列出图片文件失败: {str(e)}")
# 处理每个DNA文件
for file_name in dna_file_list:
if file_name.lower().endswith('.dna'):
# 获取不含扩展名的文件名
name = os.path.splitext(file_name)[0]
dna_path = os.path.join(DNA_FILE_PATH, file_name).replace("\\", "/")
# 检查预览图是否存在,不重复检查文件系统提高性能
if name in available_images:
image_path = os.path.join(DNA_IMG_PATH, f"{name}.png").replace("\\", "/")
else:
image_path = default_preview
print(f"未找到预览图: {name}.png, 使用默认预览图")
dna_files.append({
"name": name,
"dna_path": dna_path,
"image_path": image_path
})
print(f"成功加载 {len(dna_files)} 个DNA文件")
return dna_files
def load_selected_dna(dna_path, ui_instance=None):
"""
加载用户选择的DNA文件
Args:
dna_path (str): DNA文件路径
ui_instance: UI实例用于更新UI状态
Returns:
bool: 是否成功加载
"""
try:
if not os.path.exists(dna_path):
print(f"DNA文件不存在: {dna_path}")
return False
print(f"加载DNA文件: {dna_path}")
# 更新UI中的DNA路径输入框如果有UI实例
if ui_instance and hasattr(ui_instance, 'controls') and 'presets_dna_input' in ui_instance.controls:
ui_instance.controls['presets_dna_input'].setText(dna_path)
# 这里可以添加实际的DNA文件加载逻辑
# 例如通过MetaHuman DNA Calibration库读取DNA文件
return True
except Exception as e:
print(f"加载DNA文件失败: {str(e)}")
return False
def calculate_grid_layout(parent_width, item_width, spacing):
"""
根据父容器宽度计算每行可以容纳的项目数量
Args:
parent_width (int): 父容器宽度
item_width (int): 每个项目的宽度
spacing (int): 项目间的间距
Returns:
int: 每行可容纳的项目数量
"""
if parent_width <= 0 or item_width <= 0:
return 1
# 计算每行可容纳的项目数量
available_width = parent_width - spacing # 减去左边距
items_per_row = max(1, int(available_width / (item_width + spacing)))
return items_per_row
# 预缓存的图片缓存,减少重复加载
_pixmap_cache = {}
_pixmap_cache_max_size = 50 # 最多缓存50张图片
_pixmap_cache_lru = [] # 最近最少使用列表,用于清理缓存
def get_pixmap(image_path, width, height):
"""
从缓存获取QPixmap或创建新的
Args:
image_path: 图片路径
width: 所需宽度
height: 所需高度
Returns:
QPixmap: 图片对象
"""
cache_key = f"{image_path}_{width}_{height}"
# 检查缓存
if cache_key in _pixmap_cache:
# 更新LRU列表
global _pixmap_cache_lru
if cache_key in _pixmap_cache_lru:
_pixmap_cache_lru.remove(cache_key)
_pixmap_cache_lru.append(cache_key)
return _pixmap_cache[cache_key]
try:
# 加载原始图片
pixmap = QtGui.QPixmap(image_path)
if not pixmap.isNull():
# 缩放图片
if width > 0 and height > 0:
pixmap = pixmap.scaled(width, height, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
# 缓存清理 - 如果超出最大缓存大小,删除最不常用的项目
global _pixmap_cache_max_size
if len(_pixmap_cache) >= _pixmap_cache_max_size and _pixmap_cache_lru:
oldest_key = _pixmap_cache_lru.pop(0)
if oldest_key in _pixmap_cache:
del _pixmap_cache[oldest_key]
# 存入缓存
_pixmap_cache[cache_key] = pixmap
_pixmap_cache_lru.append(cache_key)
return pixmap
else:
# 创建一个纯色背景作为替代
fallback_pixmap = QtGui.QPixmap(width, height)
fallback_pixmap.fill(QtGui.QColor(60, 60, 60))
return fallback_pixmap
except Exception as e:
print(f"加载图像失败 ({image_path}): {str(e)}")
fallback_pixmap = QtGui.QPixmap(width, height)
fallback_pixmap.fill(QtGui.QColor(60, 60, 60))
return fallback_pixmap
def create_dna_preview_button(dna_info, item_size, on_click_callback):
"""
创建DNA预览按钮
Args:
dna_info (dict): DNA文件信息
item_size (tuple): 按钮尺寸 (宽, 高)
on_click_callback (function): 点击回调函数
Returns:
QPushButton: 创建的DNA预览按钮
"""
try:
# 调试信息
print(f"创建DNA按钮: {dna_info['name']}, 路径: {dna_info['image_path']}, 尺寸: {item_size}")
# 创建带有图标和文字的垂直布局按钮
dna_button = QtWidgets.QWidget()
dna_button.setFixedSize(item_size[0], item_size[1])
dna_button.setObjectName(f"dna_button_{dna_info['name']}")
dna_button.setCursor(QtCore.Qt.PointingHandCursor)
# 创建垂直布局
layout = QtWidgets.QVBoxLayout(dna_button)
layout.setContentsMargins(4, 4, 4, 4) # 设置小的内边距,让按钮内容不会顶到边缘
layout.setSpacing(2) # 图像和文字之间的小间距
# 创建图像标签
img_label = QtWidgets.QLabel()
img_label.setAlignment(QtCore.Qt.AlignCenter)
# 计算图像区域高度 - 预留足够空间给文字
text_height = 20 # 文字区域高度
img_height = item_size[1] - text_height - layout.contentsMargins().top() - layout.contentsMargins().bottom() - layout.spacing()
img_width = item_size[0] - layout.contentsMargins().left() - layout.contentsMargins().right()
# 加载并缩放图像
pixmap = QtGui.QPixmap(dna_info['image_path'])
if pixmap.isNull():
print(f"错误: 无法加载图像 {dna_info['image_path']}")
# 创建一个默认的纯色图片
pixmap = QtGui.QPixmap(img_width, img_height)
pixmap.fill(QtGui.QColor(60, 60, 60))
else:
# 缩放图像以适应,但保持纵横比
pixmap = pixmap.scaled(
img_width,
img_height,
QtCore.Qt.KeepAspectRatio,
QtCore.Qt.SmoothTransformation
)
# 设置图像标签的图像
img_label.setPixmap(pixmap)
img_label.setMinimumHeight(img_height)
img_label.setMaximumHeight(img_height)
# 创建文本标签
text_label = QtWidgets.QLabel(dna_info['name'])
text_label.setAlignment(QtCore.Qt.AlignCenter)
text_label.setStyleSheet("""
color: white;
font-size: 10px;
font-weight: bold;
padding: 2px;
""")
text_label.setWordWrap(True)
text_label.setFixedHeight(text_height)
# 添加控件到布局
layout.addWidget(img_label, 1) # 图像区域占据更多空间
layout.addWidget(text_label, 0) # 文字区域固定高度
# 设置整体样式 - 包括边框和圆角
dna_button.setStyleSheet(f"""
QWidget#{dna_button.objectName()} {{
background-color: #333333;
border: 1px solid #444444;
border-radius: 8px;
}}
""")
# 创建透明按钮覆盖整个区域以处理点击
overlay_button = QtWidgets.QPushButton(dna_button)
overlay_button.setObjectName("overlayButton")
overlay_button.setStyleSheet("""
#overlayButton {
background-color: transparent;
border: none;
border-radius: 8px;
}
#overlayButton:hover {
background-color: rgba(0, 122, 204, 0.2);
border: 1px solid #007ACC;
border-radius: 8px;
}
#overlayButton:pressed {
background-color: rgba(0, 122, 204, 0.3);
border: 1px solid #007ACC;
border-radius: 8px;
}
""")
overlay_button.resize(dna_button.size())
overlay_button.clicked.connect(lambda: on_click_callback(dna_info['dna_path']))
# 存储DNA路径属性
dna_button.setProperty("dna_path", dna_info['dna_path'])
return dna_button
except Exception as e:
print(f"创建DNA预览按钮失败 ({dna_info.get('name', 'unknown')}): {str(e)}")
import traceback
traceback.print_exc()
# 返回一个简单的替代按钮
fallback_widget = QtWidgets.QWidget()
fallback_widget.setFixedSize(item_size[0], item_size[1])
fallback_widget.setStyleSheet("""
background-color: #333333;
border: 1px solid #444444;
border-radius: 8px;
""")
fallback_layout = QtWidgets.QVBoxLayout(fallback_widget)
fallback_layout.setContentsMargins(4, 4, 4, 4)
fallback_label = QtWidgets.QLabel(dna_info.get('name', 'DNA'))
fallback_label.setAlignment(QtCore.Qt.AlignCenter)
fallback_label.setStyleSheet("color: white; font-weight: bold;")
fallback_layout.addWidget(fallback_label)
# 添加点击功能
if on_click_callback:
fallback_button = QtWidgets.QPushButton(fallback_widget)
fallback_button.setStyleSheet("""
background: transparent;
border: none;
border-radius: 8px;
""")
fallback_button.resize(fallback_widget.size())
fallback_button.clicked.connect(lambda: on_click_callback(dna_info.get('dna_path', '')))
return fallback_widget
# 缓存上一次的布局信息,避免不必要的重新计算
_last_layout_info = {
"parent_width": 0,
"item_size": (0, 0),
"spacing": 0,
"items_per_row": 0
}
def load_dna_preview_buttons(ui_instance):
"""
为UI加载DNA预览按钮
Args:
ui_instance: UI实例
"""
try:
# 检查UI实例是否有效
if not ui_instance or not hasattr(ui_instance, 'controls'):
print("无效的UI实例无法加载DNA预览按钮")
return False
# 检查是否有dna_button_size属性没有则初始化
if not hasattr(ui_instance, 'dna_button_size'):
ui_instance.dna_button_size = DEFAULT_DNA_BUTTON_SIZE
print(f"警告: UI实例没有dna_button_size属性使用默认值{DEFAULT_DNA_BUTTON_SIZE}")
# 获取DNA文件列表
dna_files = ui_instance.dna_files
# 检查是否有DNA文件
if not dna_files:
# 如果还没有加载DNA文件则先加载
ui_instance.dna_files = get_dna_files()
dna_files = ui_instance.dna_files
if not dna_files:
print("没有找到DNA文件无法加载预览按钮")
if 'presets_count_label' in ui_instance.controls:
ui_instance.controls['presets_count_label'].setText("0")
return False
# 更新预设数量标签
if 'presets_count_label' in ui_instance.controls:
ui_instance.controls['presets_count_label'].setText(str(len(dna_files)))
# 获取滚动区域宽度
if 'presets_content' in ui_instance.controls:
scroll_width = ui_instance.controls['presets_content'].width()
else:
scroll_width = 400 # 默认宽度
# 更新DNA网格布局
update_dna_grid_layout(
ui_instance.controls["presets_flow_layout"],
scroll_width,
ui_instance.dna_button_size,
10, # 间距
dna_files,
lambda dna_path: on_dna_button_clicked(dna_path, ui_instance)
)
return True
except Exception as e:
import traceback
print(f"加载DNA预览按钮失败: {str(e)}")
traceback.print_exc()
return False
def update_dna_grid_layout(flow_layout, parent_width, item_size, spacing, dna_files, on_dna_clicked):
"""
更新DNA布局根据父容器宽度动态调整DNA按钮布局
Args:
flow_layout: 流式布局
parent_width (int): 父容器宽度
item_size (tuple): 项目尺寸 (宽, 高)
spacing (int): 项目间的间距
dna_files (list): DNA文件信息列表
on_dna_clicked (function): DNA按钮点击回调函数
"""
try:
# 检查参数有效性
if flow_layout is None:
print("错误: flow_layout 为空")
return
if not dna_files:
print("警告: 没有DNA文件可以显示")
return
# 先清空现有控件
while flow_layout.count() > 0:
item = flow_layout.takeAt(0)
if item and item.widget():
widget = item.widget()
widget.setParent(None)
widget.deleteLater()
# 创建并添加DNA预览按钮
for dna_info in dna_files:
try:
# 创建DNA预览按钮
dna_button = create_dna_preview_button(dna_info, item_size, on_dna_clicked)
# 添加到流式布局
flow_layout.addWidget(dna_button)
except Exception as e:
print(f"添加DNA按钮失败 ({dna_info['name']}): {str(e)}")
except Exception as e:
print(f"更新DNA布局失败: {str(e)}")
import traceback
traceback.print_exc()
# 保存上次调整大小的时间,用于限制调整频率
_last_resize_time = 0
_resize_throttle = 200 # 毫秒
def on_presets_content_resize(event, ui_instance):
"""
预设内容区域大小变化事件,限制更新频率以提高性能
流式布局会自动处理大部分布局工作,这里只需更新缩放比例
Args:
event: 调整大小事件
ui_instance: UI实例
"""
try:
# 检查UI实例是否有效
if not ui_instance or not hasattr(ui_instance, 'controls') or "presets_content" not in ui_instance.controls:
# 如果UI实例无效只调用原始的resizeEvent
QtWidgets.QWidget.resizeEvent(event.widget(), event)
return
# 声明全局变量
global _last_resize_time
# 调用父类的resizeEvent
QtWidgets.QWidget.resizeEvent(ui_instance.controls["presets_content"], event)
# 获取当前时间
current_time = QtCore.QTime.currentTime().msecsSinceStartOfDay()
# 如果距离上次更新不足阈值时间,则跳过更新
if current_time - _last_resize_time < _resize_throttle:
return
# 更新上次调整时间
_last_resize_time = current_time
# 对于流式布局,大多数情况下不需要手动刷新
# 但在某些情况下可能需要手动触发布局更新
if hasattr(ui_instance.controls["presets_flow_layout"], "update"):
ui_instance.controls["presets_flow_layout"].update()
except Exception as e:
import traceback
print(f"调整DNA预览布局失败: {str(e)}")
traceback.print_exc()
def on_dna_button_clicked(dna_path, ui_instance):
"""
DNA按钮点击事件
Args:
dna_path: DNA文件路径
ui_instance: UI实例
"""
try:
# 检查UI实例是否有效
if not ui_instance or not hasattr(ui_instance, 'controls'):
print("警告: UI实例无效无法更新DNA路径")
# 仍然尝试加载DNA文件即使无法更新UI
load_selected_dna(dna_path)
return
# 加载选中的DNA文件
load_selected_dna(dna_path, ui_instance)
# 更新UI中的DNA路径输入框
if "presets_dna_input" in ui_instance.controls:
ui_instance.controls["presets_dna_input"].setText(dna_path)
else:
print("警告: 'presets_dna_input' 未创建")
except Exception as e:
import traceback
print(f"点击DNA按钮处理失败: {str(e)}")
traceback.print_exc()
#------------------------------------ UI UTILITIES ------------------------------------
def browse_file(parent_widget, title, line_edit, file_type=None):
"""
浏览文件对话框
Args:
parent_widget: 父窗口
title: 对话框标题
line_edit: 要更新的文本框
file_type: 文件类型筛选
"""
try:
# 设置文件过滤器
if file_type == "dna":
file_filter = "DNA文件 (*.dna);;所有文件 (*.*)"
elif file_type == "fbx":
file_filter = "FBX文件 (*.fbx);;所有文件 (*.*)"
else:
file_filter = "所有文件 (*.*)"
# 打开文件选择对话框
file_path = cmds.fileDialog2(
fileFilter=file_filter,
dialogStyle=2,
caption=TEXT(title, title)
)
if file_path:
# 更新文本框
line_edit.setText(file_path[0])
return file_path[0]
return None
except Exception as e:
print(f"浏览文件失败: {str(e)}")
return None
#------------------------------------ DNA OPERATIONS ------------------------------------
def import_dna():
"""
导入DNA文件
从文件中导入DNA数据
"""
try:
# 打开文件选择对话框
file_path = cmds.fileDialog2(
fileFilter="DNA文件 (*.dna);;所有文件 (*.*)",
dialogStyle=2,
caption="选择DNA文件"
)
if not file_path:
print("未选择文件")
return False
print(f"导入DNA文件: {file_path[0]}")
# 实现DNA导入逻辑
return True
except Exception as e:
print(f"导入DNA文件失败: {str(e)}")
return False
def export_dna():
"""
导出DNA文件
将当前绑定数据导出为DNA文件
"""
try:
# 打开文件保存对话框
file_path = cmds.fileDialog2(
fileFilter="DNA文件 (*.dna);;所有文件 (*.*)",
dialogStyle=2,
caption="保存DNA文件",
fileMode=0
)
if not file_path:
print("未选择保存位置")
return False
# 确保文件扩展名为.dna
if not file_path[0].lower().endswith('.dna'):
file_path[0] += '.dna'
print(f"导出DNA文件: {file_path[0]}")
# 实现DNA导出逻辑
return True
except Exception as e:
print(f"导出DNA文件失败: {str(e)}")
return False
#------------------------------------ Rigging ------------------------------------
def remove_all(*args):
"""移除所有绑定组件
从场景中删除所有绑定相关的组件,包括关节、控制器等
"""
try:
# 获取所有关节
all_joints = cmds.ls(type="joint")
# 获取所有可能的控制器(这里简化为所有曲线)
all_controllers = cmds.ls(type="nurbsCurve")
all_controller_transforms = []
# 获取控制器的变换节点
for ctrl in all_controllers:
parent = cmds.listRelatives(ctrl, parent=True)
if parent:
all_controller_transforms.extend(parent)
# 确认删除
result = cmds.confirmDialog(
title=TEXT("confirm_delete", "确认删除"),
message=TEXT("delete_all_confirm", "确定要删除所有绑定组件吗?"),
button=[TEXT("yes", ""), TEXT("no", "")],
defaultButton=TEXT("no", ""),
cancelButton=TEXT("no", ""),
dismissString=TEXT("no", "")
)
if result == TEXT("yes", ""):
# 删除所有控制器
if all_controller_transforms:
cmds.delete(all_controller_transforms)
# 删除所有关节
if all_joints:
cmds.delete(all_joints)
print("成功删除所有绑定组件")
return True
else:
print("取消删除操作")
return False
except Exception as e:
print(f"删除绑定组件失败: {str(e)}")
return False
def import_skeleton(*args):
"""导入骨骼
从文件中导入骨骼结构
"""
try:
# 打开文件选择对话框
file_path = cmds.fileDialog2(
fileFilter="Maya文件 (*.ma *.mb);;FBX文件 (*.fbx);;所有文件 (*.*)",
dialogStyle=2,
caption="选择骨骼文件"
)
if not file_path:
print("未选择文件")
return False
file_path = file_path[0] # fileDialog2返回的是列表
# 根据文件类型选择导入方法
if file_path.lower().endswith(('.ma', '.mb')):
# 导入Maya文件
cmds.file(file_path, i=True, type="mayaAscii" if file_path.lower().endswith('.ma') else "mayaBinary",
ignoreVersion=True, mergeNamespacesOnClash=False, namespace="skeleton")
elif file_path.lower().endswith('.fbx'):
# 导入FBX文件
cmds.file(file_path, i=True, type="FBX", ignoreVersion=True)
else:
print(f"不支持的文件类型: {file_path}")
return False
print(f"成功导入骨骼: {file_path}")
return True
except Exception as e:
print(f"导入骨骼失败: {str(e)}")
return False
def build_rigging(*args):
"""构建绑定系统
基于当前场景中的骨骼构建完整的绑定系统
"""
try:
# 获取场景中的所有关节
all_joints = cmds.ls(type="joint")
if not all_joints:
print("场景中没有骨骼,无法构建绑定")
return False
# 为每个关节创建控制器
for joint in all_joints:
# 获取关节位置
pos = cmds.xform(joint, query=True, translation=True, worldSpace=True)
# 创建控制器这里简化为创建一个NURBS圆环
ctrl = cmds.circle(name=f"{joint}_ctrl", normal=[1, 0, 0], radius=1)[0]
# 移动控制器到关节位置
cmds.move(pos[0], pos[1], pos[2], ctrl)
# 创建约束
cmds.parentConstraint(ctrl, joint, maintainOffset=True)
print("成功构建绑定系统")
return True
except Exception as e:
print(f"构建绑定系统失败: {str(e)}")
return False
def clear_pixmap_cache():
"""
清除图片缓存
在不再需要缓存时调用,例如关闭窗口时
"""
global _pixmap_cache, _pixmap_cache_lru
_pixmap_cache.clear()
_pixmap_cache_lru.clear()
print("图片缓存已清除")
#------------------------------------ UI 预览调整函数 ------------------------------------
def on_presets_slider_changed(ui_instance, value):
"""
预设滑块值变化处理 - 调整预览按钮大小
Args:
ui_instance: UI实例
value: 滑块当前值
"""
try:
# 检查UI实例是否有效
if not ui_instance or not hasattr(ui_instance, 'controls'):
print("警告: UI实例无效无法调整预览按钮大小")
return
# 检查是否有dna_button_size属性没有则初始化
if not hasattr(ui_instance, 'dna_button_size'):
ui_instance.dna_button_size = DEFAULT_DNA_BUTTON_SIZE
print(f"警告: UI实例没有dna_button_size属性使用默认值{DEFAULT_DNA_BUTTON_SIZE}")
# 调试尺寸信息
print(f"当前按钮基础尺寸: {DEFAULT_DNA_BUTTON_SIZE[0]}x{DEFAULT_DNA_BUTTON_SIZE[1]}")
# 获取滑块范围
min_val = ui_instance.controls["presets_slider"].minimum()
max_val = ui_instance.controls["presets_slider"].maximum()
mid_val = (min_val + max_val) / 2
# 确保滑块值在范围内
value = max(min_val, min(max_val, value))
# 计算缩放比例 - 更平滑的缩放效果
# 以100为中心点低于100缩小高于100放大
if value <= mid_val:
# 从0.8到1.0
scale_factor = 0.8 + 0.2 * ((value - min_val) / (mid_val - min_val))
else:
# 从1.0到1.2
scale_factor = 1.0 + 0.2 * ((value - mid_val) / (max_val - mid_val))
# 确保在合理范围内
scale_factor = max(0.7, min(1.3, scale_factor))
# 从基础尺寸计算缩放后的尺寸
new_width = int(DEFAULT_DNA_BUTTON_SIZE[0] * scale_factor)
new_height = int(DEFAULT_DNA_BUTTON_SIZE[1] * scale_factor)
# 确保尺寸不会太小或太大
new_width = max(60, min(130, new_width))
new_height = max(80, min(170, new_height))
# 确保宽高比例正确 - 3:4比例
aspect_ratio = 0.75 # 宽度/高度 = 3/4
# 优先以高度为准,调整宽度
new_width = int(new_height * aspect_ratio)
# 确保宽高为偶数
if new_width % 2 != 0:
new_width += 1
if new_height % 2 != 0:
new_height += 1
# 输出调试信息
print(f"调整DNA按钮大小: {new_width}x{new_height}, 缩放因子: {scale_factor:.2f}")
# 更新按钮尺寸
ui_instance.dna_button_size = (new_width, new_height)
# 强制刷新布局 - 先清空布局,然后重新加载
flow_layout = ui_instance.controls["presets_flow_layout"]
# 重新加载预览按钮
load_dna_preview_buttons(ui_instance)
except Exception as e:
import traceback
print(f"调整DNA预览按钮大小失败: {str(e)}")
traceback.print_exc()
def load_dna_files(ui_instance):
"""
加载DNA文件和预览
在所有UI控件创建完成后执行
Args:
ui_instance: UI实例
"""
try:
# 检查UI实例是否有效
if not ui_instance or not hasattr(ui_instance, 'controls'):
print("警告: UI实例无效无法加载DNA文件")
return
# 加载DNA文件和预览图
ui_instance.dna_files = get_dna_files()
# 更新预设数量标签
if 'presets_count_label' in ui_instance.controls:
ui_instance.controls['presets_count_label'].setText(str(len(ui_instance.dna_files)))
# 调用加载预览按钮函数
load_dna_preview_buttons(ui_instance)
# 自动选中第一个DNA文件 (如果存在)
if ui_instance.dna_files and 'presets_dna_input' in ui_instance.controls:
ui_instance.controls['presets_dna_input'].setText(ui_instance.dna_files[0]['dna_path'])
except Exception as e:
import traceback
print(f"加载DNA文件失败: {str(e)}")
traceback.print_exc()