From f7d61bddec147a2b2ec793117feaf302f9a94fd6 Mon Sep 17 00:00:00 2001 From: Jeffreytsai1004 Date: Mon, 12 May 2025 09:25:20 +0800 Subject: [PATCH] Update --- scripts/ui/rigging.py | 285 ++++++++++++++++++++------------- scripts/ui/style.qss | 7 +- scripts/utils/utils_rigging.py | 67 +++++--- 3 files changed, 226 insertions(+), 133 deletions(-) diff --git a/scripts/ui/rigging.py b/scripts/ui/rigging.py index bb40d9c..21e0898 100644 --- a/scripts/ui/rigging.py +++ b/scripts/ui/rigging.py @@ -56,98 +56,6 @@ 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): """ @@ -217,6 +125,7 @@ class RiggingUI(ui_utils.BaseUI): # 创建主分割器 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() @@ -240,13 +149,15 @@ class RiggingUI(ui_utils.BaseUI): 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, 5) # 进一步减小底部边距 + self.controls["presets_flow_layout"].setContentsMargins(10, 10, 10, 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"].setStyleSheet(""" QSlider::groove:horizontal { - border: 1px solid #999999; + border: 1px solid #444444; height: 8px; - background: #3E3E42; + background: #333333; margin: 2px 0; border-radius: 4px; } @@ -297,6 +208,7 @@ class RiggingUI(ui_utils.BaseUI): } QSlider::handle:horizontal:hover { background: #1C97EA; + border: 1px solid #7ABEF5; } """) @@ -465,7 +377,7 @@ class RiggingUI(ui_utils.BaseUI): # 创建主布局 main_layout = QtWidgets.QVBoxLayout(self.main_widget) main_layout.setContentsMargins(5, 5, 5, 5) - main_layout.setSpacing(5) + main_layout.setSpacing(0) # 移除主布局中的间距 # 添加标题 main_layout.addWidget(self.controls["title_label"]) @@ -476,35 +388,46 @@ class RiggingUI(ui_utils.BaseUI): # 1. Presets 面板布局 presets_layout = QtWidgets.QVBoxLayout(self.controls["presets_panel"]) 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_group_layout = QtWidgets.QVBoxLayout(self.controls["presets_group"]) - presets_group_layout.setContentsMargins(5, 5, 5, 0) # 移除底部内边距 - presets_group_layout.setSpacing(0) # 移除内部间距 + 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(1) # 减小分隔线高度 + 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(34) # 高度更小 + slider_container.setFixedHeight(35) # 高度 slider_container.setStyleSheet(""" #sliderContainer { - background-color: #222222; - border-top: 1px solid #444444; + 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, 2, 5, 2) # 上下边距更小 + slider_container_layout.setContentsMargins(5, 5, 5, 5) # 上下边距更小 slider_container_layout.setSpacing(0) slider_container_layout.addLayout(self.controls["presets_slider_layout"]) @@ -516,8 +439,18 @@ class RiggingUI(ui_utils.BaseUI): # 2. Assets 面板布局 assets_layout = QtWidgets.QVBoxLayout(self.controls["assets_panel"]) - assets_layout.setContentsMargins(0, 0, 0, 0) - assets_layout.setSpacing(5) + 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"]) @@ -628,9 +561,53 @@ class RiggingUI(ui_utils.BaseUI): 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): @@ -652,4 +629,98 @@ 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) \ No newline at end of file + 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() diff --git a/scripts/ui/style.qss b/scripts/ui/style.qss index dec8181..ff733c4 100644 --- a/scripts/ui/style.qss +++ b/scripts/ui/style.qss @@ -22,8 +22,8 @@ QLabel#mainTitleLabel { /* ==================== DNA预览按钮样式 ==================== */ QPushButton.dna-preview-button { - background-color: #2D2D30; - border: 1px solid #3E3E42; + background-color: #333333; + border: 1px solid #444444; border-radius: 8px; padding: 0; margin: 4px; @@ -42,7 +42,8 @@ QPushButton.dna-preview-button:pressed { QPushButton.dna-preview-button QLabel { color: #FFFFFF; - font-size: 11px; + font-size: 10px; + font-weight: bold; border-radius: 0; border-bottom-left-radius: 7px; border-bottom-right-radius: 7px; diff --git a/scripts/utils/utils_rigging.py b/scripts/utils/utils_rigging.py index 1c897cf..dd3642d 100644 --- a/scripts/utils/utils_rigging.py +++ b/scripts/utils/utils_rigging.py @@ -265,17 +265,19 @@ def create_dna_preview_button(dna_info, item_size, on_click_callback): # 创建垂直布局 layout = QtWidgets.QVBoxLayout(dna_button) - layout.setContentsMargins(2, 2, 2, 2) - layout.setSpacing(1) # 更小的间距 + layout.setContentsMargins(4, 4, 4, 4) # 设置小的内边距,让按钮内容不会顶到边缘 + layout.setSpacing(2) # 图像和文字之间的小间距 # 创建图像标签 img_label = QtWidgets.QLabel() img_label.setAlignment(QtCore.Qt.AlignCenter) - # 加载并缩放图像 - img_height = int(item_size[1] * 0.8) # 图像占80%高度 - img_width = item_size[0] - 4 # 减去边距 + # 计算图像区域高度 - 预留足够空间给文字 + 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']}") @@ -293,30 +295,32 @@ def create_dna_preview_button(dna_info, item_size, on_click_callback): # 设置图像标签的图像 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.setAlignment(QtCore.Qt.AlignCenter) text_label.setStyleSheet(""" color: white; - font-size: 9px; /* 更小的字体 */ + font-size: 10px; font-weight: bold; - margin-top: 0px; - padding-top: 0px; + padding: 2px; """) - text_label.setWordWrap(True) # 允许文本换行 - text_label.setFixedHeight(15) # 固定文本高度 + text_label.setWordWrap(True) + text_label.setFixedHeight(text_height) # 添加控件到布局 - layout.addWidget(img_label) - layout.addWidget(text_label) + layout.addWidget(img_label, 1) # 图像区域占据更多空间 + layout.addWidget(text_label, 0) # 文字区域固定高度 - # 设置整体样式 - dna_button.setStyleSheet(""" - background-color: #333333; - border: 1px solid #444444; - border-radius: 3px; + # 设置整体样式 - 包括边框和圆角 + dna_button.setStyleSheet(f""" + QWidget#{dna_button.objectName()} {{ + background-color: #333333; + border: 1px solid #444444; + border-radius: 8px; + }} """) # 创建透明按钮覆盖整个区域以处理点击 @@ -326,12 +330,17 @@ def create_dna_preview_button(dna_info, item_size, on_click_callback): #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()) @@ -349,9 +358,14 @@ def create_dna_preview_button(dna_info, item_size, on_click_callback): # 返回一个简单的替代按钮 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(2, 2, 2, 2) + fallback_layout.setContentsMargins(4, 4, 4, 4) fallback_label = QtWidgets.QLabel(dna_info.get('name', 'DNA')) 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: 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.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)) - # 从基础尺寸计算缩放后的尺寸,保持宽高比 + # 从基础尺寸计算缩放后的尺寸 new_width = int(DEFAULT_DNA_BUTTON_SIZE[0] * 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_height = max(80, min(170, new_height)) - # 确保宽高比例正确 - 约为3:4 - aspect_ratio = DEFAULT_DNA_BUTTON_SIZE[0] / DEFAULT_DNA_BUTTON_SIZE[1] + # 确保宽高比例正确 - 3:4比例 + aspect_ratio = 0.75 # 宽度/高度 = 3/4 # 优先以高度为准,调整宽度 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) + # 强制刷新布局 - 先清空布局,然后重新加载 + flow_layout = ui_instance.controls["presets_flow_layout"] + # 重新加载预览按钮 load_dna_preview_buttons(ui_instance)