From c6b3fc3c6c847b5de1a1f78af0f5c963dbe2f91a Mon Sep 17 00:00:00 2001 From: ordinc <35759494+ordinc@users.noreply.github.com> Date: Thu, 4 Sep 2025 21:12:47 +0800 Subject: [PATCH 1/3] Add compatible component related functions --- release/scripts/mgear/core/dagmenu.py | 7 +++++++ .../mgear/shifter/guide_manager_component.py | 20 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/release/scripts/mgear/core/dagmenu.py b/release/scripts/mgear/core/dagmenu.py index a8dacbed..4786ddfe 100644 --- a/release/scripts/mgear/core/dagmenu.py +++ b/release/scripts/mgear/core/dagmenu.py @@ -42,6 +42,7 @@ from .six import string_types from mgear.vendor.Qt import QtWidgets +from mgear.compatible import compatible_comp_dagmenu def __change_rotate_order_callback(*args): @@ -821,6 +822,12 @@ def mgear_dagmenu_guide_fill(parent_menu, current_guide_locator): command=guide_template.updateGuide, image="mgear_loader.svg", ) + cmds.menuItem( + parent=parent_menu, + label="Update Setected Components Type", + command=compatible_comp_dagmenu.update_component_type_and_update_guide_with_dagmenu, + image="mgear_loader.svg", + ) cmds.menuItem( parent=parent_menu, label="Reload Components", diff --git a/release/scripts/mgear/shifter/guide_manager_component.py b/release/scripts/mgear/shifter/guide_manager_component.py index 98c1ec06..5c209d66 100644 --- a/release/scripts/mgear/shifter/guide_manager_component.py +++ b/release/scripts/mgear/shifter/guide_manager_component.py @@ -13,8 +13,12 @@ from mgear import shifter from mgear.shifter import guide_manager from mgear.shifter import guide_manager_component_ui as gmcUI +from mgear.compatible import guide_manager_compatible_comp as gmcc + import importlib +importlib.reload(gmcc) + PY2 = sys.version_info[0] == 2 @@ -157,10 +161,15 @@ def _component_menu(self, QPos): return self.comp_menu = QtWidgets.QMenu() parentPosition = comp_widget.mapToGlobal(QtCore.QPoint(0, 0)) + menu_item_00 = self.comp_menu.addAction( + "Syn The Selected Component Type To The Type Selected In The Manager" + ) + self.comp_menu.addSeparator() menu_item_01 = self.comp_menu.addAction("Draw Component") self.comp_menu.addSeparator() menu_item_02 = self.comp_menu.addAction("Refresh List") + menu_item_00.triggered.connect(self.set_component) menu_item_01.triggered.connect(self.draw_component) menu_item_02.triggered.connect(self._refreshList) @@ -226,6 +235,9 @@ def create_connections(self): self.gmcUIInst.search_lineEdit.customContextMenuRequested.connect( self._search_menu ) + self.gmcUIInst.update_guide_checkBox.stateChanged.connect( + self.on_update_changed + ) ############# # SLOTS @@ -271,6 +283,14 @@ def draw_component(self, parent=None): for x in self.gmcUIInst.component_listView.selectedIndexes(): guide_manager.draw_comp(x.data(), parent, showUI) + def on_update_changed(self, state): + self.update_flag = self.gmcUIInst.update_guide_checkBox.isChecked() + + def set_component(self): + self.update_flag = self.gmcUIInst.update_guide_checkBox.isChecked() + for x in self.gmcUIInst.component_listView.selectedIndexes(): + gmcc.update_component_type_and_update_guide(x.data(), self.update_flag) + def filter_changed(self, filter_str): """Filter out the elements in the list view""" # pyside2 From 59d66cf9cbb54d1b24fd28ec218a14b6d52772e4 Mon Sep 17 00:00:00 2001 From: ordinc <35759494+ordinc@users.noreply.github.com> Date: Thu, 4 Sep 2025 21:15:09 +0800 Subject: [PATCH 2/3] compatible components modules Add functional modules related to compatible components --- release/scripts/mgear/compatible/__init__.py | 0 .../mgear/compatible/compatible_comp.py | 47 ++++++ .../compatible/compatible_comp_dagmenu.py | 123 ++++++++++++++ .../mgear/compatible/compatible_comp_ui.py | 58 +++++++ .../mgear/compatible/compatible_comp_ui.ui | 107 ++++++++++++ .../compatible_components_dagmenu.py | 123 ++++++++++++++ .../guide_manager_compatible_comp.py | 152 ++++++++++++++++++ 7 files changed, 610 insertions(+) create mode 100644 release/scripts/mgear/compatible/__init__.py create mode 100644 release/scripts/mgear/compatible/compatible_comp.py create mode 100644 release/scripts/mgear/compatible/compatible_comp_dagmenu.py create mode 100644 release/scripts/mgear/compatible/compatible_comp_ui.py create mode 100644 release/scripts/mgear/compatible/compatible_comp_ui.ui create mode 100644 release/scripts/mgear/compatible/compatible_components_dagmenu.py create mode 100644 release/scripts/mgear/compatible/guide_manager_compatible_comp.py diff --git a/release/scripts/mgear/compatible/__init__.py b/release/scripts/mgear/compatible/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/release/scripts/mgear/compatible/compatible_comp.py b/release/scripts/mgear/compatible/compatible_comp.py new file mode 100644 index 00000000..d61adf17 --- /dev/null +++ b/release/scripts/mgear/compatible/compatible_comp.py @@ -0,0 +1,47 @@ +import importlib +import mgear.pymaya as pm +from mgear.vendor.Qt import QtCore, QtWidgets +import mgear.compatible.compatible_comp_ui as rcUI + +importlib.reload(rcUI) + + +class RelatedComponents(QtWidgets.QDialog, rcUI.Ui_Dialog): + def __init__(self, related_components, parent=None): + self.toolName = "RelatedComponents" + super(RelatedComponents, self).__init__(parent) + self.setupUi(self) + self.result_components = None + self.update_flag = False + self.components_comboBox.addItems(related_components) + self.create_connections() + self.setWindowTitle("Related Components") + self.setAttribute(QtCore.Qt.WA_DeleteOnClose, True) + + def create_connections(self): + self.buttonBox.accepted.connect(self.ok) + self.update_checkBox.stateChanged.connect(self.on_update_changed) + + def on_update_changed(self, state): + self.update_flag = state == QtCore.Qt.Checked + + def ok(self): + self.result_components = str(self.components_comboBox.currentText()) + self.update_flag = self.update_checkBox.isChecked() + + def cancel(self): + pm.displayWarning("User cancels update.") + + +def exec_window(related_components, *args): + windw = RelatedComponents(related_components) + if windw.exec_(): + return windw + + +if __name__ == "__main__": + sample_components = ["Arm", "Leg", "Spine"] + w = exec_window(sample_components) + if w: + print(f"Selected: {w.result_components}") + print(f"Update enabled: {w.update_flag}") diff --git a/release/scripts/mgear/compatible/compatible_comp_dagmenu.py b/release/scripts/mgear/compatible/compatible_comp_dagmenu.py new file mode 100644 index 00000000..78e15e01 --- /dev/null +++ b/release/scripts/mgear/compatible/compatible_comp_dagmenu.py @@ -0,0 +1,123 @@ +import re +import importlib + +import maya.cmds as cmds +import mgear.pymaya as pm +from mgear.shifter import guide_template +from mgear.compatible import compatible_comp as rc +from mgear.compatible import ( + guide_manager_compatible_comp as gmcr, +) + +importlib.reload(rc) +importlib.reload(gmcr) + +SPECIAL_TYPES = ["EPIC", "lite"] + + +def update_component_type_and_update_guide_with_dagmenu(*args): + """ + Updates the component type of selected guide components and optionally refreshes the guide. + Provides a user interface for selecting related component types and filtering methods. + + Features: + - Validates selected components and their types + - Provides filtering options for related components + - Updates component types while maintaining guide structure + - Optionally refreshes the guide template after update + + """ + roots = gmcr.get_comp_root() + + if not roots: + pm.displayWarning("No components selected.") + return + + if not gmcr.are_comp_names_identical(roots): + pm.displayWarning("Selected components are not of the same comp type.") + return + + root_comp_type = roots[0].getAttr("comp_type") + if not isinstance(root_comp_type, str): + cmds.error( + f"Expected string type for comp_type, but got {type(root_comp_type).__name__}" + ) + elif not root_comp_type: + cmds.error("comp_type is empty or invalid") + if not re.fullmatch(r"^[a-zA-Z]+(?:_[a-zA-Z0-9_-]+)?$", root_comp_type): + cmds.error( + f"Invalid format: '{root_comp_type}'\n" + "Correct format should be: BASE_TYPE or BASE_TYPE_SUB_TYPE\n" + "Examples: EPIC_spine or arm_2jnt" + ) + + type_parts = root_comp_type.split("_") + base_type = type_parts[0] + sub_type = type_parts[1] if len(type_parts) > 1 else "" + all_components = gmcr.get_component_list() + + buttons = [] + if base_type in SPECIAL_TYPES: + buttons = [ + f"Only match '{base_type}' type", + f"Match all containing '{sub_type}' type", + "Cancel", + ] + else: + buttons = [ + f"Only match '{base_type}' type", + f"Match all containing '{base_type}' type", + "Cancel", + ] + + choice = pm.confirmDialog( + title="Select Related Component Scope", + message="Please select the scope of related components to include", + button=buttons, + defaultButton=f"Match all containing '{base_type}' type", + cancelButton="Cancel", + dismissString="Cancel", + ) + if choice == "Cancel": + return + + related_components = [] + for component in all_components: + comp_parts = component.split("_") + if choice == f"Only match '{base_type}' type": + if base_type in SPECIAL_TYPES: + if ( + len(comp_parts) > 1 + and comp_parts[0] == base_type + and comp_parts[1] == sub_type + ): + related_components.append(component) + else: + if comp_parts[0] == base_type: + related_components.append(component) + else: + if base_type in SPECIAL_TYPES: + if len(comp_parts) > 1 and comp_parts[1] == sub_type: + related_components.append(component) + elif sub_type and sub_type in component: + related_components.append(component) + else: + if base_type in component: + related_components.append(component) + + if not related_components: + pm.displayWarning(f"No components related to '{root_comp_type}' found.") + return + + custom_window = rc.exec_window(related_components) + if not custom_window or not custom_window.result_components: + return + + gmcr.set_selected_component_type_is_manager_current_selected_Component( + roots, custom_window.result_components + ) + + if custom_window.update_flag: + pm.select(roots) + guide_template.updateGuide() + pm.select(clear=True) diff --git a/release/scripts/mgear/compatible/compatible_comp_ui.py b/release/scripts/mgear/compatible/compatible_comp_ui.py new file mode 100644 index 00000000..7f944fb8 --- /dev/null +++ b/release/scripts/mgear/compatible/compatible_comp_ui.py @@ -0,0 +1,58 @@ +from mgear.vendor.Qt import QtCore, QtWidgets + + +class Ui_Dialog(object): + def setupUi(self, Dialog): + Dialog.setObjectName("Dialog") + Dialog.resize(236, 113) + self.gridLayout = QtWidgets.QGridLayout(Dialog) + self.gridLayout.setObjectName("gridLayout") + self.buttonBox = QtWidgets.QDialogButtonBox(Dialog) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons( + QtWidgets.QDialogButtonBox.Cancel | QtWidgets.QDialogButtonBox.Ok + ) + self.buttonBox.setObjectName("buttonBox") + self.gridLayout.addWidget(self.buttonBox, 3, 0, 1, 1) + self.horizontalLayout_2 = QtWidgets.QHBoxLayout() + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.components_label = QtWidgets.QLabel(Dialog) + sizePolicy = QtWidgets.QSizePolicy( + QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred + ) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth( + self.components_label.sizePolicy().hasHeightForWidth() + ) + self.components_label.setSizePolicy(sizePolicy) + self.components_label.setObjectName("components_label") + self.horizontalLayout_2.addWidget(self.components_label) + self.components_comboBox = QtWidgets.QComboBox(Dialog) + self.components_comboBox.setObjectName("components_comboBox") + self.horizontalLayout_2.addWidget(self.components_comboBox) + self.gridLayout.addLayout(self.horizontalLayout_2, 0, 0, 1, 1) + spacerItem = QtWidgets.QSpacerItem( + 20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding + ) + self.gridLayout.addItem(spacerItem, 4, 0, 1, 1) + self.update_checkBox = QtWidgets.QCheckBox(Dialog) + self.update_checkBox.setLayoutDirection(QtCore.Qt.RightToLeft) + self.update_checkBox.setObjectName("update_checkBox") + self.update_checkBox.setChecked(True) + self.gridLayout.addWidget(self.update_checkBox, 1, 0, 1, 1) + + self.retranslateUi(Dialog) + self.buttonBox.accepted.connect(Dialog.accept) + self.buttonBox.rejected.connect(Dialog.reject) + + def retranslateUi(self, Dialog): + Dialog.setWindowTitle( + QtWidgets.QApplication.translate("Dialog", "Dialog", None, -1) + ) + self.components_label.setText( + QtWidgets.QApplication.translate("Dialog", "Related components:", None, -1) + ) + self.update_checkBox.setText( + QtWidgets.QApplication.translate("Dialog", "Update Guide", None, -1) + ) diff --git a/release/scripts/mgear/compatible/compatible_comp_ui.ui b/release/scripts/mgear/compatible/compatible_comp_ui.ui new file mode 100644 index 00000000..dd705b66 --- /dev/null +++ b/release/scripts/mgear/compatible/compatible_comp_ui.ui @@ -0,0 +1,107 @@ + + + Dialog + + + + 0 + 0 + 236 + 113 + + + + Dialog + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + 0 + 0 + + + + Related components: + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::RightToLeft + + + Update Guide + + + + + + + + + buttonBox + accepted() + Dialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + Dialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/release/scripts/mgear/compatible/compatible_components_dagmenu.py b/release/scripts/mgear/compatible/compatible_components_dagmenu.py new file mode 100644 index 00000000..78e15e01 --- /dev/null +++ b/release/scripts/mgear/compatible/compatible_components_dagmenu.py @@ -0,0 +1,123 @@ +import re +import importlib + +import maya.cmds as cmds +import mgear.pymaya as pm +from mgear.shifter import guide_template +from mgear.compatible import compatible_comp as rc +from mgear.compatible import ( + guide_manager_compatible_comp as gmcr, +) + +importlib.reload(rc) +importlib.reload(gmcr) + +SPECIAL_TYPES = ["EPIC", "lite"] + + +def update_component_type_and_update_guide_with_dagmenu(*args): + """ + Updates the component type of selected guide components and optionally refreshes the guide. + Provides a user interface for selecting related component types and filtering methods. + + Features: + - Validates selected components and their types + - Provides filtering options for related components + - Updates component types while maintaining guide structure + - Optionally refreshes the guide template after update + + """ + roots = gmcr.get_comp_root() + + if not roots: + pm.displayWarning("No components selected.") + return + + if not gmcr.are_comp_names_identical(roots): + pm.displayWarning("Selected components are not of the same comp type.") + return + + root_comp_type = roots[0].getAttr("comp_type") + if not isinstance(root_comp_type, str): + cmds.error( + f"Expected string type for comp_type, but got {type(root_comp_type).__name__}" + ) + elif not root_comp_type: + cmds.error("comp_type is empty or invalid") + if not re.fullmatch(r"^[a-zA-Z]+(?:_[a-zA-Z0-9_-]+)?$", root_comp_type): + cmds.error( + f"Invalid format: '{root_comp_type}'\n" + "Correct format should be: BASE_TYPE or BASE_TYPE_SUB_TYPE\n" + "Examples: EPIC_spine or arm_2jnt" + ) + + type_parts = root_comp_type.split("_") + base_type = type_parts[0] + sub_type = type_parts[1] if len(type_parts) > 1 else "" + all_components = gmcr.get_component_list() + + buttons = [] + if base_type in SPECIAL_TYPES: + buttons = [ + f"Only match '{base_type}' type", + f"Match all containing '{sub_type}' type", + "Cancel", + ] + else: + buttons = [ + f"Only match '{base_type}' type", + f"Match all containing '{base_type}' type", + "Cancel", + ] + + choice = pm.confirmDialog( + title="Select Related Component Scope", + message="Please select the scope of related components to include", + button=buttons, + defaultButton=f"Match all containing '{base_type}' type", + cancelButton="Cancel", + dismissString="Cancel", + ) + if choice == "Cancel": + return + + related_components = [] + for component in all_components: + comp_parts = component.split("_") + if choice == f"Only match '{base_type}' type": + if base_type in SPECIAL_TYPES: + if ( + len(comp_parts) > 1 + and comp_parts[0] == base_type + and comp_parts[1] == sub_type + ): + related_components.append(component) + else: + if comp_parts[0] == base_type: + related_components.append(component) + else: + if base_type in SPECIAL_TYPES: + if len(comp_parts) > 1 and comp_parts[1] == sub_type: + related_components.append(component) + elif sub_type and sub_type in component: + related_components.append(component) + else: + if base_type in component: + related_components.append(component) + + if not related_components: + pm.displayWarning(f"No components related to '{root_comp_type}' found.") + return + + custom_window = rc.exec_window(related_components) + if not custom_window or not custom_window.result_components: + return + + gmcr.set_selected_component_type_is_manager_current_selected_Component( + roots, custom_window.result_components + ) + + if custom_window.update_flag: + pm.select(roots) + guide_template.updateGuide() + pm.select(clear=True) diff --git a/release/scripts/mgear/compatible/guide_manager_compatible_comp.py b/release/scripts/mgear/compatible/guide_manager_compatible_comp.py new file mode 100644 index 00000000..6db13627 --- /dev/null +++ b/release/scripts/mgear/compatible/guide_manager_compatible_comp.py @@ -0,0 +1,152 @@ +import os +import sys +import traceback +import importlib + +import maya.cmds as cmds +import mgear.pymaya as pm +from mgear import shifter +from mgear.shifter import guide_template +from mgear.compatible import compatible_comp as rc + +importlib.reload(rc) + +PY2 = sys.version_info[0] == 2 + + +def get_component_list(): + """ + Retrieves a list of all available mGear component types. + Scans both default and custom component directories. + + """ + comp_list = [] + compDir = shifter.getComponentDirectories() + trackLoadComponent = [] + + for path, comps in compDir.items(): + for comp_name in comps: + if comp_name == "__init__.py" or comp_name == "__pycache__": + continue + if comp_name in trackLoadComponent: + pm.displayWarning( + "Custom component name: %s conflicts with default component. " + "Skipping load." % comp_name + ) + continue + else: + trackLoadComponent.append(comp_name) + if not os.path.exists(os.path.join(path, comp_name, "__init__.py")): + continue + try: + module = shifter.importComponentGuide(comp_name) + if PY2: + reload(module) + else: + importlib.reload(module) + comp_list.append(module.TYPE) + except Exception as e: + pm.displayWarning("Failed to load component: {}".format(comp_name)) + pm.displayError(str(e)) + pm.displayError(traceback.format_exc()) + + return comp_list + + +def get_comp_root(): + """ + Finds the root component node for selected objects. + Traverses up the hierarchy to find nodes with 'comp_type' or 'ismodel' attributes. + + """ + oSel = pm.selected() + if not oSel: + pm.displayWarning("Please select at least one object from the component guide") + return [] + + roots = [] + for obj in oSel: + current = obj + root = None + while current: + if pm.attributeQuery("comp_type", node=current, ex=True): + root = current + break + elif pm.attributeQuery("ismodel", node=current, ex=True): + root = current + break + current = current.getParent() + roots.append(root) + + return roots + + +def are_comp_names_identical(roots): + """ + Checks if all selected components have the same base type. + Handles special case for EPIC components. + + """ + base_value = None + for obj in roots: + if not obj: + continue + if not cmds.attributeQuery("comp_type", exists=True, node=obj): + cmds.warning(f"Object {obj} is missing comp_type attribute") + return False + + root_comp_type = obj.getAttr("comp_type") + type_parts = root_comp_type.split("_") + base_type = type_parts[0] + sub_type = type_parts[1] if len(type_parts) > 1 else "" + + if base_type == "EPIC": + current_value = f"{base_type}_{sub_type}" + else: + current_value = base_type + if base_value is None: + base_value = current_value + continue + if current_value != base_value: + return False + + return True + + +def set_selected_component_type_is_manager_current_selected_Component(roots, comp): + """ + Sets the comp_type attribute for selected component roots. + + """ + if roots: + for node in roots: + if node and node.hasAttr("comp_type"): + node.comp_type.set(comp) + else: + pm.displayWarning("Selected object is not a valid mGear component root") + else: + pm.displayWarning("Please select at least one mGear component") + + +def update_component_type_and_update_guide(component, update_guide=False): + """ + Updates component type and optionally refreshes the guide. + + Args: + component: Component type to set + update_guide (bool): Whether to update the guide after change + """ + root_components = get_comp_root() + if not root_components: + pm.displayWarning("No valid components selected") + return + if not are_comp_names_identical(root_components): + pm.displayWarning("Selected components are not of the same type") + return + set_selected_component_type_is_manager_current_selected_Component( + root_components, component + ) + if update_guide: + pm.select(root_components) + guide_template.updateGuide() + pm.select(clear=True) From 3866733217403a69bd8e7a772d76593736644696 Mon Sep 17 00:00:00 2001 From: ordinc <35759494+ordinc@users.noreply.github.com> Date: Thu, 4 Sep 2025 21:21:41 +0800 Subject: [PATCH 3/3] Add checkboxes related to compatible components on the UI. --- .../mgear/shifter/guide_manager_component_ui.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/release/scripts/mgear/shifter/guide_manager_component_ui.py b/release/scripts/mgear/shifter/guide_manager_component_ui.py index 6a952224..fcadd179 100644 --- a/release/scripts/mgear/shifter/guide_manager_component_ui.py +++ b/release/scripts/mgear/shifter/guide_manager_component_ui.py @@ -82,6 +82,10 @@ def setupUi(self, Form): self.showUI_checkBox.setChecked(True) self.showUI_checkBox.setObjectName("showUI_checkBox") self.verticalLayout.addWidget(self.showUI_checkBox) + self.update_guide_checkBox = QtWidgets.QCheckBox(self.list_groupBox) + self.update_guide_checkBox.setChecked(True) + self.update_guide_checkBox.setObjectName("update_guide_checkBox") + self.verticalLayout.addWidget(self.update_guide_checkBox) self.verticalLayout_3.addWidget(self.list_groupBox) self.retranslateUi(Form) @@ -105,5 +109,14 @@ def retranslateUi(self, Form): self.draw_pushButton.setToolTip(gqt.fakeTranslate("Form", "

Draw selected component.

", None, -1)) self.draw_pushButton.setText(gqt.fakeTranslate("Form", "Draw Component", None, -1)) self.showUI_checkBox.setText(gqt.fakeTranslate("Form", "Show Setting After Create New Component.", None, -1)) + self.update_guide_checkBox.setText( + gqt.fakeTranslate( + "Form", + "Update Guide After Setting The Specified Component Type.", + None, + -1, + ) + ) + from mgear.core.widgets import DragQListView