This commit is contained in:
2025-05-12 09:25:20 +08:00
parent ba01b8fc6d
commit f7d61bddec
3 changed files with 226 additions and 133 deletions

View File

@@ -56,98 +56,6 @@ TOOL_HEIGHT = config.TOOL_HEIGHT
from scripts.ui import localization from scripts.ui import localization
TEXT = localization.TEXT 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): class RiggingUI(ui_utils.BaseUI):
""" """
@@ -217,6 +125,7 @@ class RiggingUI(ui_utils.BaseUI):
# 创建主分割器 # 创建主分割器
self.splitters["main_splitter"] = QtWidgets.QSplitter(QtCore.Qt.Vertical) self.splitters["main_splitter"] = QtWidgets.QSplitter(QtCore.Qt.Vertical)
self.splitters["main_splitter"].setObjectName("riggingMainSplitter") self.splitters["main_splitter"].setObjectName("riggingMainSplitter")
self.splitters["main_splitter"].setHandleWidth(1) # 设置分割手柄宽度为最小值
# 1. Presets 面板 # 1. Presets 面板
self.controls["presets_panel"] = QtWidgets.QWidget() self.controls["presets_panel"] = QtWidgets.QWidget()
@@ -240,13 +149,15 @@ class RiggingUI(ui_utils.BaseUI):
self.controls["presets_content"].setStyleSheet(""" self.controls["presets_content"].setStyleSheet("""
#presetsContent { #presetsContent {
background-color: #252526; background-color: #252526;
margin: 0px;
padding: 0px;
} }
""") """)
# 使用流式布局替代原来的网格布局 # 使用流式布局替代原来的网格布局
self.controls["presets_flow_layout"] = FlowLayout(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"].setObjectName("presetsFlowLayout")
self.controls["presets_flow_layout"].setContentsMargins(10, 10, 10, 5) # 进一步减小底部边距 self.controls["presets_flow_layout"].setContentsMargins(10, 10, 10, 10) # 设置内边距
self.controls["presets_flow_layout"].setSpacing(10) # 保持按钮间距 self.controls["presets_flow_layout"].setSpacing(10) # 保持按钮间距
# 设置滚动区域内容 # 设置滚动区域内容
@@ -281,9 +192,9 @@ class RiggingUI(ui_utils.BaseUI):
self.controls["presets_slider"].setFixedHeight(22) # 设置固定高度使滑块更粗 self.controls["presets_slider"].setFixedHeight(22) # 设置固定高度使滑块更粗
self.controls["presets_slider"].setStyleSheet(""" self.controls["presets_slider"].setStyleSheet("""
QSlider::groove:horizontal { QSlider::groove:horizontal {
border: 1px solid #999999; border: 1px solid #444444;
height: 8px; height: 8px;
background: #3E3E42; background: #333333;
margin: 2px 0; margin: 2px 0;
border-radius: 4px; border-radius: 4px;
} }
@@ -297,6 +208,7 @@ class RiggingUI(ui_utils.BaseUI):
} }
QSlider::handle:horizontal:hover { QSlider::handle:horizontal:hover {
background: #1C97EA; background: #1C97EA;
border: 1px solid #7ABEF5;
} }
""") """)
@@ -465,7 +377,7 @@ class RiggingUI(ui_utils.BaseUI):
# 创建主布局 # 创建主布局
main_layout = QtWidgets.QVBoxLayout(self.main_widget) main_layout = QtWidgets.QVBoxLayout(self.main_widget)
main_layout.setContentsMargins(5, 5, 5, 5) main_layout.setContentsMargins(5, 5, 5, 5)
main_layout.setSpacing(5) main_layout.setSpacing(0) # 移除主布局中的间距
# 添加标题 # 添加标题
main_layout.addWidget(self.controls["title_label"]) main_layout.addWidget(self.controls["title_label"])
@@ -476,35 +388,46 @@ class RiggingUI(ui_utils.BaseUI):
# 1. Presets 面板布局 # 1. Presets 面板布局
presets_layout = QtWidgets.QVBoxLayout(self.controls["presets_panel"]) presets_layout = QtWidgets.QVBoxLayout(self.controls["presets_panel"])
presets_layout.setContentsMargins(0, 0, 0, 0) presets_layout.setContentsMargins(0, 0, 0, 0)
presets_layout.setSpacing(5) presets_layout.setSpacing(0)
# 设置预设面板样式 - 确保无边距无内边距
self.controls["presets_panel"].setStyleSheet("""
QWidget {
margin: 0px;
padding: 0px;
border: none;
}
""")
# Presets 组布局 # Presets 组布局
presets_group_layout = QtWidgets.QVBoxLayout(self.controls["presets_group"]) presets_group_layout = QtWidgets.QVBoxLayout(self.controls["presets_group"])
presets_group_layout.setContentsMargins(5, 5, 5, 0) # 移除底部内边距 presets_group_layout.setContentsMargins(5, 5, 5, 5)
presets_group_layout.setSpacing(0) # 移除内部间距 presets_group_layout.setSpacing(0)
# 添加预设滚动区域 # 添加预设滚动区域
presets_group_layout.addWidget(self.controls["presets_scroll_area"]) presets_group_layout.addWidget(self.controls["presets_scroll_area"])
# 添加分隔线 # 添加分隔线
self.controls["presets_separator"].setFixedHeight(1) # 减小分隔线高度 self.controls["presets_separator"].setFixedHeight(2) # 减小分隔线高度
self.controls["presets_separator"].setStyleSheet("background-color: #444444;") self.controls["presets_separator"].setStyleSheet("background-color: #444444;")
presets_group_layout.addWidget(self.controls["presets_separator"]) presets_group_layout.addWidget(self.controls["presets_separator"])
# 创建滑块容器,与上方内容无缝对接 # 创建滑块容器,与上方内容无缝对接
slider_container = QtWidgets.QWidget() slider_container = QtWidgets.QWidget()
slider_container.setObjectName("sliderContainer") slider_container.setObjectName("sliderContainer")
slider_container.setFixedHeight(34) # 高度更小 slider_container.setFixedHeight(35) # 高度
slider_container.setStyleSheet(""" slider_container.setStyleSheet("""
#sliderContainer { #sliderContainer {
background-color: #222222; background-color: #252526;
border-top: 1px solid #444444; border-top: 1px solid #303030;
border-bottom: none;
margin: 0px; margin: 0px;
margin-bottom: -1px; /* 使其与下方元素重叠1px */
padding: 0px; padding: 0px;
} }
""") """)
slider_container_layout = QtWidgets.QVBoxLayout(slider_container) slider_container_layout = QtWidgets.QVBoxLayout(slider_container)
slider_container_layout.setContentsMargins(5, 2, 5, 2) # 上下边距更小 slider_container_layout.setContentsMargins(5, 5, 5, 5) # 上下边距更小
slider_container_layout.setSpacing(0) slider_container_layout.setSpacing(0)
slider_container_layout.addLayout(self.controls["presets_slider_layout"]) slider_container_layout.addLayout(self.controls["presets_slider_layout"])
@@ -516,8 +439,18 @@ class RiggingUI(ui_utils.BaseUI):
# 2. Assets 面板布局 # 2. Assets 面板布局
assets_layout = QtWidgets.QVBoxLayout(self.controls["assets_panel"]) assets_layout = QtWidgets.QVBoxLayout(self.controls["assets_panel"])
assets_layout.setContentsMargins(0, 0, 0, 0) assets_layout.setContentsMargins(0, 0, 0, 0) # 完全移除内边距
assets_layout.setSpacing(5) 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 组布局
assets_group_layout = QtWidgets.QGridLayout(self.controls["assets_group"]) assets_group_layout = QtWidgets.QGridLayout(self.controls["assets_group"])
@@ -628,9 +561,53 @@ class RiggingUI(ui_utils.BaseUI):
self.splitters["main_splitter"].setStretchFactor(0, 4) # 预设面板占4份 self.splitters["main_splitter"].setStretchFactor(0, 4) # 预设面板占4份
self.splitters["main_splitter"].setStretchFactor(1, 1) # Assets面板占1份 self.splitters["main_splitter"].setStretchFactor(1, 1) # Assets面板占1份
self.splitters["main_splitter"].setStretchFactor(2, 1) # Descriptor面板占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网格布局 # 监听预设内容区域大小变化更新DNA网格布局
self.controls["presets_content"].resizeEvent = lambda event: utils_rigging.on_presets_content_resize(event, self) 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 ====================================== #======================================= FUNCTIONS ======================================
def create_connections(self): def create_connections(self):
@@ -652,4 +629,98 @@ class RiggingUI(ui_utils.BaseUI):
# 底部按钮连接 # 底部按钮连接
self.buttons["remove_all"].clicked.connect(utils_rigging.remove_all) self.buttons["remove_all"].clicked.connect(utils_rigging.remove_all)
self.buttons["import_skeleton"].clicked.connect(utils_rigging.import_skeleton) self.buttons["import_skeleton"].clicked.connect(utils_rigging.import_skeleton)
self.buttons["build_rigging"].clicked.connect(utils_rigging.build_rigging) 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()

View File

@@ -22,8 +22,8 @@ QLabel#mainTitleLabel {
/* ==================== DNA预览按钮样式 ==================== */ /* ==================== DNA预览按钮样式 ==================== */
QPushButton.dna-preview-button { QPushButton.dna-preview-button {
background-color: #2D2D30; background-color: #333333;
border: 1px solid #3E3E42; border: 1px solid #444444;
border-radius: 8px; border-radius: 8px;
padding: 0; padding: 0;
margin: 4px; margin: 4px;
@@ -42,7 +42,8 @@ QPushButton.dna-preview-button:pressed {
QPushButton.dna-preview-button QLabel { QPushButton.dna-preview-button QLabel {
color: #FFFFFF; color: #FFFFFF;
font-size: 11px; font-size: 10px;
font-weight: bold;
border-radius: 0; border-radius: 0;
border-bottom-left-radius: 7px; border-bottom-left-radius: 7px;
border-bottom-right-radius: 7px; border-bottom-right-radius: 7px;

View File

@@ -265,17 +265,19 @@ def create_dna_preview_button(dna_info, item_size, on_click_callback):
# 创建垂直布局 # 创建垂直布局
layout = QtWidgets.QVBoxLayout(dna_button) layout = QtWidgets.QVBoxLayout(dna_button)
layout.setContentsMargins(2, 2, 2, 2) layout.setContentsMargins(4, 4, 4, 4) # 设置小的内边距,让按钮内容不会顶到边缘
layout.setSpacing(1) # 更小的间距 layout.setSpacing(2) # 图像和文字之间的小间距
# 创建图像标签 # 创建图像标签
img_label = QtWidgets.QLabel() img_label = QtWidgets.QLabel()
img_label.setAlignment(QtCore.Qt.AlignCenter) img_label.setAlignment(QtCore.Qt.AlignCenter)
# 加载并缩放图像 # 计算图像区域高度 - 预留足够空间给文字
img_height = int(item_size[1] * 0.8) # 图像占80%高度 text_height = 20 # 文字区域高度
img_width = item_size[0] - 4 # 减去边距 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']) pixmap = QtGui.QPixmap(dna_info['image_path'])
if pixmap.isNull(): if pixmap.isNull():
print(f"错误: 无法加载图像 {dna_info['image_path']}") print(f"错误: 无法加载图像 {dna_info['image_path']}")
@@ -293,30 +295,32 @@ def create_dna_preview_button(dna_info, item_size, on_click_callback):
# 设置图像标签的图像 # 设置图像标签的图像
img_label.setPixmap(pixmap) img_label.setPixmap(pixmap)
img_label.setFixedHeight(img_height) img_label.setMinimumHeight(img_height)
img_label.setMaximumHeight(img_height)
# 创建文本标签 # 创建文本标签
text_label = QtWidgets.QLabel(dna_info['name']) text_label = QtWidgets.QLabel(dna_info['name'])
text_label.setAlignment(QtCore.Qt.AlignCenter) text_label.setAlignment(QtCore.Qt.AlignCenter)
text_label.setStyleSheet(""" text_label.setStyleSheet("""
color: white; color: white;
font-size: 9px; /* 更小的字体 */ font-size: 10px;
font-weight: bold; font-weight: bold;
margin-top: 0px; padding: 2px;
padding-top: 0px;
""") """)
text_label.setWordWrap(True) # 允许文本换行 text_label.setWordWrap(True)
text_label.setFixedHeight(15) # 固定文本高度 text_label.setFixedHeight(text_height)
# 添加控件到布局 # 添加控件到布局
layout.addWidget(img_label) layout.addWidget(img_label, 1) # 图像区域占据更多空间
layout.addWidget(text_label) layout.addWidget(text_label, 0) # 文字区域固定高度
# 设置整体样式 # 设置整体样式 - 包括边框和圆角
dna_button.setStyleSheet(""" dna_button.setStyleSheet(f"""
background-color: #333333; QWidget#{dna_button.objectName()} {{
border: 1px solid #444444; background-color: #333333;
border-radius: 3px; border: 1px solid #444444;
border-radius: 8px;
}}
""") """)
# 创建透明按钮覆盖整个区域以处理点击 # 创建透明按钮覆盖整个区域以处理点击
@@ -326,12 +330,17 @@ def create_dna_preview_button(dna_info, item_size, on_click_callback):
#overlayButton { #overlayButton {
background-color: transparent; background-color: transparent;
border: none; border: none;
border-radius: 8px;
} }
#overlayButton:hover { #overlayButton:hover {
background-color: rgba(0, 122, 204, 0.2); background-color: rgba(0, 122, 204, 0.2);
border: 1px solid #007ACC;
border-radius: 8px;
} }
#overlayButton:pressed { #overlayButton:pressed {
background-color: rgba(0, 122, 204, 0.3); background-color: rgba(0, 122, 204, 0.3);
border: 1px solid #007ACC;
border-radius: 8px;
} }
""") """)
overlay_button.resize(dna_button.size()) overlay_button.resize(dna_button.size())
@@ -349,9 +358,14 @@ def create_dna_preview_button(dna_info, item_size, on_click_callback):
# 返回一个简单的替代按钮 # 返回一个简单的替代按钮
fallback_widget = QtWidgets.QWidget() fallback_widget = QtWidgets.QWidget()
fallback_widget.setFixedSize(item_size[0], item_size[1]) 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 = QtWidgets.QVBoxLayout(fallback_widget)
fallback_layout.setContentsMargins(2, 2, 2, 2) fallback_layout.setContentsMargins(4, 4, 4, 4)
fallback_label = QtWidgets.QLabel(dna_info.get('name', 'DNA')) fallback_label = QtWidgets.QLabel(dna_info.get('name', 'DNA'))
fallback_label.setAlignment(QtCore.Qt.AlignCenter) fallback_label.setAlignment(QtCore.Qt.AlignCenter)
@@ -362,7 +376,11 @@ def create_dna_preview_button(dna_info, item_size, on_click_callback):
# 添加点击功能 # 添加点击功能
if on_click_callback: if on_click_callback:
fallback_button = QtWidgets.QPushButton(fallback_widget) fallback_button = QtWidgets.QPushButton(fallback_widget)
fallback_button.setStyleSheet("background: transparent; border: none;") fallback_button.setStyleSheet("""
background: transparent;
border: none;
border-radius: 8px;
""")
fallback_button.resize(fallback_widget.size()) fallback_button.resize(fallback_widget.size())
fallback_button.clicked.connect(lambda: on_click_callback(dna_info.get('dna_path', ''))) fallback_button.clicked.connect(lambda: on_click_callback(dna_info.get('dna_path', '')))
@@ -819,7 +837,7 @@ def on_presets_slider_changed(ui_instance, value):
# 确保在合理范围内 # 确保在合理范围内
scale_factor = max(0.7, min(1.3, scale_factor)) scale_factor = max(0.7, min(1.3, scale_factor))
# 从基础尺寸计算缩放后的尺寸,保持宽高比 # 从基础尺寸计算缩放后的尺寸
new_width = int(DEFAULT_DNA_BUTTON_SIZE[0] * scale_factor) new_width = int(DEFAULT_DNA_BUTTON_SIZE[0] * scale_factor)
new_height = int(DEFAULT_DNA_BUTTON_SIZE[1] * scale_factor) new_height = int(DEFAULT_DNA_BUTTON_SIZE[1] * scale_factor)
@@ -827,8 +845,8 @@ def on_presets_slider_changed(ui_instance, value):
new_width = max(60, min(130, new_width)) new_width = max(60, min(130, new_width))
new_height = max(80, min(170, new_height)) new_height = max(80, min(170, new_height))
# 确保宽高比例正确 - 约为3:4 # 确保宽高比例正确 - 3:4比例
aspect_ratio = DEFAULT_DNA_BUTTON_SIZE[0] / DEFAULT_DNA_BUTTON_SIZE[1] aspect_ratio = 0.75 # 宽度/高度 = 3/4
# 优先以高度为准,调整宽度 # 优先以高度为准,调整宽度
new_width = int(new_height * aspect_ratio) new_width = int(new_height * aspect_ratio)
@@ -845,6 +863,9 @@ def on_presets_slider_changed(ui_instance, value):
# 更新按钮尺寸 # 更新按钮尺寸
ui_instance.dna_button_size = (new_width, new_height) ui_instance.dna_button_size = (new_width, new_height)
# 强制刷新布局 - 先清空布局,然后重新加载
flow_layout = ui_instance.controls["presets_flow_layout"]
# 重新加载预览按钮 # 重新加载预览按钮
load_dna_preview_buttons(ui_instance) load_dna_preview_buttons(ui_instance)