This commit is contained in:
2025-05-11 13:29:35 +08:00
parent eea67c91c8
commit 5e5aa4f670
2 changed files with 254 additions and 106 deletions

View File

@@ -56,6 +56,98 @@ TOOL_HEIGHT = config.TOOL_HEIGHT
from scripts.ui import localization
TEXT = localization.TEXT
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()
class RiggingUI(ui_utils.BaseUI):
"""
绑定系统UI类 - 负责显示骨骼绑定编辑界面和基础操作
@@ -75,7 +167,7 @@ class RiggingUI(ui_utils.BaseUI):
# 初始化DNA文件列表和按钮尺寸
self.dna_files = []
self.dna_button_size = (120, 150)
self.dna_button_size = (140, 170) # 增加按钮尺寸,提供更好的视觉效果
# 初始化UI
self.create_widgets()
@@ -86,7 +178,7 @@ class RiggingUI(ui_utils.BaseUI):
self.update_language()
# 加载DNA文件和预览图 - 移到最后执行确保所有控件已创建
self.load_dna_files()
utils_rigging.load_dna_files(self)
def closeEvent(self, event):
"""
@@ -142,11 +234,11 @@ class RiggingUI(ui_utils.BaseUI):
self.controls["presets_content"] = QtWidgets.QWidget()
self.controls["presets_content"].setObjectName("presetsContent")
# 预设流布局
self.controls["presets_flow_layout"] = QtWidgets.QGridLayout(self.controls["presets_content"])
# 使用流式布局替代原来的网格布局
self.controls["presets_flow_layout"] = FlowLayout(self.controls["presets_content"])
self.controls["presets_flow_layout"].setObjectName("presetsFlowLayout")
self.controls["presets_flow_layout"].setContentsMargins(5, 5, 5, 5)
self.controls["presets_flow_layout"].setSpacing(10)
self.controls["presets_flow_layout"].setContentsMargins(10, 10, 10, 10)
self.controls["presets_flow_layout"].setSpacing(15) # 固定间距
# 设置滚动区域内容
self.controls["presets_scroll_area"].setWidget(self.controls["presets_content"])
@@ -162,12 +254,13 @@ class RiggingUI(ui_utils.BaseUI):
self.controls["presets_count_label"].setAlignment(QtCore.Qt.AlignCenter)
self.controls["presets_count_label"].setStyleSheet("font-weight: bold;")
# 滑块
# 滑块 - 用于调整预览图大小
self.controls["presets_slider"] = QtWidgets.QSlider(QtCore.Qt.Horizontal)
self.controls["presets_slider"].setObjectName("presetsSlider")
self.controls["presets_slider"].setMinimum(0)
self.controls["presets_slider"].setMaximum(100)
self.controls["presets_slider"].setValue(50)
self.controls["presets_slider"].setMinimum(20) # 最小值
self.controls["presets_slider"].setMaximum(100) # 最大值
self.controls["presets_slider"].setValue(60) # 默认值
self.controls["presets_slider"].setToolTip("调整预览图大小")
# 导出预设按钮
self.buttons["export_presets"] = QtWidgets.QPushButton(TEXT("export_presets", "导出预设"))
@@ -486,6 +579,9 @@ class RiggingUI(ui_utils.BaseUI):
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"))
@@ -493,25 +589,4 @@ class RiggingUI(ui_utils.BaseUI):
# 底部按钮连接
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)
def load_dna_files(self):
"""
加载DNA文件和预览
在所有UI控件创建完成后执行
"""
try:
# 从utils_rigging加载DNA文件和预览图
from scripts.utils import utils_rigging
self.dna_files = utils_rigging.get_dna_files()
# 更新预设数量标签
if 'presets_count_label' in self.controls:
self.controls['presets_count_label'].setText(str(len(self.dna_files)))
# 调用utils_rigging中的加载预览按钮函数
utils_rigging.load_dna_preview_buttons(self)
except Exception as e:
import traceback
print(f"加载DNA文件失败: {str(e)}")
traceback.print_exc()
self.buttons["build_rigging"].clicked.connect(utils_rigging.build_rigging)

View File

@@ -256,31 +256,63 @@ def create_dna_preview_button(dna_info, item_size, on_click_callback):
button.setObjectName(f"dna_button_{dna_info['name']}")
button.setFixedSize(item_size[0], item_size[1])
button.setProperty("class", "dna-preview-button")
button.setCursor(QtCore.Qt.PointingHandCursor) # 鼠标悬停时显示手型光标
# 创建垂直布局
layout = QtWidgets.QVBoxLayout(button)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(2)
# 创建图像容器
image_container = QtWidgets.QWidget()
image_container.setObjectName(f"image_container_{dna_info['name']}")
image_container.setFixedSize(item_size[0], item_size[0] - 10) # 留出底部名称空间
image_container.setStyleSheet("background-color: #2A2A2A; border-radius: 4px;")
# 创建图像标签
image_label = QtWidgets.QLabel()
image_label = QtWidgets.QLabel(image_container)
image_label.setObjectName(f"image_{dna_info['name']}")
image_label.setFixedSize(item_size[0], item_size[0]) # 正方形图像区域
image_label.setScaledContents(True)
image_label.setAlignment(QtCore.Qt.AlignCenter)
# 使用预缓存加载图像
pixmap = get_pixmap(dna_info['image_path'], item_size[0], item_size[0])
pixmap = get_pixmap(dna_info['image_path'], item_size[0] - 16, item_size[0] - 26)
image_label.setPixmap(pixmap)
# 创建图像标签布局
image_layout = QtWidgets.QVBoxLayout(image_container)
image_layout.setContentsMargins(8, 8, 8, 8)
image_layout.addWidget(image_label, 0, QtCore.Qt.AlignCenter)
# 创建名称标签
name_label = QtWidgets.QLabel(dna_info['name'])
name_label.setObjectName(f"name_{dna_info['name']}")
name_label.setAlignment(QtCore.Qt.AlignCenter)
name_label.setStyleSheet("background-color: rgba(0, 0, 0, 128); padding: 2px;")
name_label.setStyleSheet("""
background-color: transparent;
color: #E0E0E0;
font-weight: bold;
padding: 4px;
""")
# 添加到布局
layout.addWidget(image_label)
layout.addWidget(name_label)
layout.addWidget(image_container, 0, QtCore.Qt.AlignCenter)
layout.addWidget(name_label, 0, QtCore.Qt.AlignCenter)
# 设置按钮样式
button.setStyleSheet("""
QPushButton {
background-color: #333333;
border: none;
border-radius: 6px;
padding: 0px;
}
QPushButton:hover {
background-color: #444444;
}
QPushButton:pressed {
background-color: #555555;
}
""")
# 设置按钮数据
button.setProperty("dna_path", dna_info['dna_path'])
@@ -365,10 +397,10 @@ def load_dna_preview_buttons(ui_instance):
def update_dna_grid_layout(flow_layout, parent_width, item_size, spacing, dna_files, on_dna_clicked):
"""
更新DNA网格布局,根据父容器宽度动态调整每行显示的DNA项目数量
更新DNA布局根据父容器宽度动态调整DNA按钮布局
Args:
flow_layout (QGridLayout): 网格布局
flow_layout: 流式布局
parent_width (int): 父容器宽度
item_size (tuple): 项目尺寸 (宽, 高)
spacing (int): 项目间的间距
@@ -384,56 +416,28 @@ def update_dna_grid_layout(flow_layout, parent_width, item_size, spacing, dna_fi
if not dna_files:
print("警告: 没有DNA文件可以显示")
return
if parent_width <= 0:
print(f"警告: 父容器宽度无效 ({parent_width})")
parent_width = 400 # 使用默认宽度
global _last_layout_info
# 计算每行可容纳的项目数量
items_per_row = calculate_grid_layout(parent_width, item_size[0], spacing)
# 如果布局信息与上次相同,则不需要重新布局
if (_last_layout_info["parent_width"] == parent_width and
_last_layout_info["item_size"] == item_size and
_last_layout_info["spacing"] == spacing and
_last_layout_info["items_per_row"] == items_per_row and
flow_layout.count() > 0):
return
# 更新布局信息缓存
_last_layout_info = {
"parent_width": parent_width,
"item_size": item_size,
"spacing": spacing,
"items_per_row": items_per_row
}
print(f"布局信息: 宽度 = {parent_width}, 项目宽度 = {item_size[0]}, 每行项目数 = {items_per_row}")
# 先移除所有现有控件
for i in reversed(range(flow_layout.count())):
item = flow_layout.itemAt(i)
# 先清空现有控件
while flow_layout.count() > 0:
item = flow_layout.takeAt(0)
if item and item.widget():
item.widget().setParent(None) # 从父窗口分离
item.widget().deleteLater() # 稍后删除控件
# 重新添加DNA预览按钮
for i, dna_info in enumerate(dna_files):
widget = item.widget()
widget.setParent(None)
widget.deleteLater()
# 创建并添加DNA预览按钮
for dna_info in dna_files:
try:
row = i // items_per_row
col = i % items_per_row
# 创建DNA预览按钮
dna_button = create_dna_preview_button(dna_info, item_size, on_dna_clicked)
# 添加到网格布局
flow_layout.addWidget(dna_button, row, col)
# 添加到流式布局
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)}")
print(f"更新DNA布局失败: {str(e)}")
import traceback
traceback.print_exc()
@@ -444,6 +448,7 @@ _resize_throttle = 200 # 毫秒
def on_presets_content_resize(event, ui_instance):
"""
预设内容区域大小变化事件,限制更新频率以提高性能
流式布局会自动处理大部分布局工作,这里只需更新缩放比例
Args:
event: 调整大小事件
@@ -456,6 +461,7 @@ def on_presets_content_resize(event, ui_instance):
QtWidgets.QWidget.resizeEvent(event.widget(), event)
return
# 声明全局变量
global _last_resize_time
# 调用父类的resizeEvent
@@ -471,29 +477,11 @@ def on_presets_content_resize(event, ui_instance):
# 更新上次调整时间
_last_resize_time = current_time
# 确保所有必要的控件和属性都存在
required_keys = ["presets_flow_layout"]
required_attrs = ["dna_button_size", "dna_files"]
for key in required_keys:
if key not in ui_instance.controls:
print(f"警告: '{key}' 未在UI控件中找到")
return
for attr in required_attrs:
if not hasattr(ui_instance, attr):
print(f"警告: '{attr}' 属性未在UI实例中找到")
return
# 更新DNA网格布局
update_dna_grid_layout(
ui_instance.controls["presets_flow_layout"],
event.size().width(),
ui_instance.dna_button_size,
10, # 间距
ui_instance.dna_files,
lambda dna_path: on_dna_button_clicked(dna_path, ui_instance)
)
# 对于流式布局,大多数情况下不需要手动刷新
# 但在某些情况下可能需要手动触发布局更新
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)}")
@@ -746,4 +734,89 @@ def clear_pixmap_cache():
global _pixmap_cache, _pixmap_cache_lru
_pixmap_cache.clear()
_pixmap_cache_lru.clear()
print("图片缓存已清除")
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
# 计算新的按钮尺寸 (基于滑块值进行缩放)
# 范围从最小100x120到最大180x220
base_width = 140 # 基础宽度
base_height = 170 # 基础高度
# 获取滑块范围
min_val = ui_instance.controls["presets_slider"].minimum()
max_val = ui_instance.controls["presets_slider"].maximum()
# 避免除以零
scale_range = max(1, max_val - min_val)
# 计算缩放比例 (0.7 到 1.3)
scale_factor = 0.7 + 0.6 * ((value - min_val) / scale_range)
# 应用缩放
new_width = int(base_width * scale_factor)
new_height = int(base_height * scale_factor)
# 确保尺寸在合理范围内
new_width = max(100, min(180, new_width))
new_height = max(120, min(220, new_height))
# 更新按钮尺寸
old_size = ui_instance.dna_button_size
ui_instance.dna_button_size = (new_width, new_height)
# 如果尺寸有明显变化重新加载DNA预览按钮
if abs(old_size[0] - new_width) > 10 or abs(old_size[1] - new_height) > 10:
# 重新加载预览按钮
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()