From 01226ec4ebb60bc8e8c3d42150ed572649cc84cf Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Thu, 11 Jul 2024 18:57:29 +0300 Subject: [PATCH 1/4] implement and use select product dialog --- client/ayon_houdini/api/hda_utils.py | 123 +++++++++++++++--- .../DialogScript | 27 +--- .../PythonModule | 2 +- 3 files changed, 105 insertions(+), 47 deletions(-) diff --git a/client/ayon_houdini/api/hda_utils.py b/client/ayon_houdini/api/hda_utils.py index 8b8b0a323b..cbbb447c06 100644 --- a/client/ayon_houdini/api/hda_utils.py +++ b/client/ayon_houdini/api/hda_utils.py @@ -555,38 +555,119 @@ def _select_folder_path(): folder_parm.pressButton() # allow any callbacks to trigger -def get_available_products(node): - """Return products menu items - It gets a list of available products of the specified product types - within the specified folder path with in the specified project. - Users can specify those in the HDA parameters. +class SelectProductDialog(QtWidgets.QDialog): + """Simple dialog to allow a user to select a product.""" - Args: - node (hou.OpNode): The HDA node. + def __init__(self, project_name, folder_id, parent=None): + super(SelectProductDialog, self).__init__(parent) + self.setWindowTitle("Select a Product") + self.setStyleSheet(load_stylesheet()) + + self.project_name = project_name + self.folder_id = folder_id + + # Create widgets and layout + product_types_widget = QtWidgets.QComboBox() + products_widget = QtWidgets.QListWidget() + accept_button = QtWidgets.QPushButton("Accept") + + main_layout = QtWidgets.QVBoxLayout(self) + main_layout.addWidget(product_types_widget, 0) + main_layout.addWidget(products_widget, 1) + main_layout.addWidget(accept_button, 0) + + self.product_types_widget = product_types_widget + self.products_widget = products_widget + + # Connect Signals + product_types_widget.currentTextChanged.connect(self.on_product_type_changed) + products_widget.itemDoubleClicked.connect(self.accept) + accept_button.clicked.connect(self.accept) + + # Initialize widgets contents + product_types_widget.addItems(self.get_product_types()) + product_type = self.get_selected_product_type() + self.set_product_name(product_type) + + def get_selected_product(self) -> str: + if self.products_widget.currentItem(): + return self.products_widget.currentItem().text() + return "" + + def get_selected_product_type(self) -> str: + return self.product_types_widget.currentText() + + def get_product_types(self) -> List[str]: + """return default product types. + """ + + return [ + "*", + "animation", + "camera", + "model", + "pointcache", + "usd", + ] + + def on_product_type_changed(self, product_type: str): + self.set_product_name(product_type) + + def set_product_name(self, product_type: str): + self.product_types_widget.setCurrentText(product_type) + + if self.product_types_widget.currentText() != product_type: + # Product type does not exist + return + + # Populate products list + products = self.get_available_products(product_type) + self.products_widget.clear() + if products: + self.products_widget.addItems(products) + + def get_available_products(self, product_type): + + if product_type == "*": + product_type = "" + + product_types = [product_type] if product_type else None + + products = ayon_api.get_products( + self.project_name, + folder_ids=[self.folder_id], + product_types=product_types + ) + + return list(sorted(product["name"] for product in products)) + + +def select_a_product(node): - Returns: - list[str]: Product names for Products menu. - """ project_name = node.evalParm("project_name") folder_path = node.evalParm("folder_path") - product_type = node.evalParm("product_type") + product_parm = node.parm("product_name") folder_entity = ayon_api.get_folder_by_path(project_name, folder_path, fields={"id"}) if not folder_entity: - return [] - - # Apply filter only if any value is set - product_types = [product_type] if product_type else None - - products = ayon_api.get_products( + return + + dialog = SelectProductDialog( project_name, - folder_ids=[folder_entity["id"]], - product_types=product_types - ) + folder_entity["id"], + parent=lib.get_main_window() + ) + result = dialog.exec_() + + if result != QtWidgets.QDialog.Accepted: + return + selected_product = dialog.get_selected_product() - return list(sorted(product["name"] for product in products)) + if selected_product: + product_parm.set(selected_product) + product_parm.pressButton() # allow any callbacks to trigger def set_to_latest_version(node): diff --git a/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/DialogScript b/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/DialogScript index dee8fa4c8f..85e5c631f4 100644 --- a/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/DialogScript +++ b/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/DialogScript @@ -47,36 +47,13 @@ parmtag { "script_callback" "hou.phm().on_representation_parms_changed(kwargs['node'])" } parmtag { "script_callback_language" "python" } } - parm { - name "product_type" - label "Product Type" - type string - default { "usd" } - menu { - "" "*" - "animation" "animation" - "camera" "camera" - "model" "model" - "pointcache" "pointcache" - "usd" "usd" - } - } parm { name "product_name" label "Product" type string default { "usdAsset" } - menureplace { - [ "products = hou.phm().get_available_products(kwargs['node'])" ] - [ "" ] - [ "result = []" ] - [ "for product in products:" ] - [ " result.append(product)" ] - [ " result.append(product)" ] - [ " " ] - [ "return result" ] - language python - } + parmtag { "script_action" "from ayon_houdini.api.hda_utils import select_a_product;select_a_product(kwargs['node'])" } + parmtag { "script_action_icon" "BUTTONS_reselect" } parmtag { "script_callback" "hou.phm().set_to_latest_version(kwargs['node'])\nhou.phm().on_representation_parms_changed(kwargs['node'])" } parmtag { "script_callback_language" "python" } } diff --git a/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/PythonModule b/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/PythonModule index 934fb2f612..ac17b275f9 100644 --- a/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/PythonModule +++ b/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/PythonModule @@ -5,6 +5,6 @@ from ayon_houdini.api.hda_utils import ( on_representation_parms_changed, setup_flag_changed_callback, get_available_versions, - get_available_products, + select_a_product, set_to_latest_version ) From d294d4c922cfb4038c8a690ea71b0df29973571a Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Thu, 11 Jul 2024 19:14:20 +0300 Subject: [PATCH 2/4] remove redundant code --- client/ayon_houdini/api/hda_utils.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/client/ayon_houdini/api/hda_utils.py b/client/ayon_houdini/api/hda_utils.py index cbbb447c06..ecc38eea00 100644 --- a/client/ayon_houdini/api/hda_utils.py +++ b/client/ayon_houdini/api/hda_utils.py @@ -129,14 +129,6 @@ def update_info(node, context): if node.evalParm(key) != value} parms["load_message"] = "" # clear any warnings/errors - # Update the product type filter to match the type - current = node.evalParm("product_type") - product_type = context["product"]["productType"] - if current and current != product_type: - # If current is empty we consider no filtering applied and we allow - # that to be a state that needs no switching - parms["product_type"] = product_type - # Note that these never trigger any parm callbacks since we do not # trigger the `parm.pressButton` and programmatically setting values # in Houdini does not trigger callbacks automatically From 5ab78f43f987906e0941bd523ad15c95e4f97c12 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Jul 2024 09:16:25 +0200 Subject: [PATCH 3/4] Make select product dialog appear more like popup menu and appear near the mouse. Also auto-select the current value if any matches. --- client/ayon_houdini/api/hda_utils.py | 29 +++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/client/ayon_houdini/api/hda_utils.py b/client/ayon_houdini/api/hda_utils.py index ecc38eea00..a3b557a961 100644 --- a/client/ayon_houdini/api/hda_utils.py +++ b/client/ayon_houdini/api/hda_utils.py @@ -28,7 +28,7 @@ from ayon_houdini.api import lib -from qtpy import QtCore, QtWidgets +from qtpy import QtCore, QtWidgets, QtGui import hou @@ -561,9 +561,10 @@ def __init__(self, project_name, folder_id, parent=None): # Create widgets and layout product_types_widget = QtWidgets.QComboBox() products_widget = QtWidgets.QListWidget() - accept_button = QtWidgets.QPushButton("Accept") + accept_button = QtWidgets.QPushButton("Set product name") main_layout = QtWidgets.QVBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) main_layout.addWidget(product_types_widget, 0) main_layout.addWidget(products_widget, 1) main_layout.addWidget(accept_button, 0) @@ -579,7 +580,7 @@ def __init__(self, project_name, folder_id, parent=None): # Initialize widgets contents product_types_widget.addItems(self.get_product_types()) product_type = self.get_selected_product_type() - self.set_product_name(product_type) + self.set_product_type(product_type) def get_selected_product(self) -> str: if self.products_widget.currentItem(): @@ -603,9 +604,9 @@ def get_product_types(self) -> List[str]: ] def on_product_type_changed(self, product_type: str): - self.set_product_name(product_type) + self.set_product_type(product_type) - def set_product_name(self, product_type: str): + def set_product_type(self, product_type: str): self.product_types_widget.setCurrentText(product_type) if self.product_types_widget.currentText() != product_type: @@ -618,6 +619,12 @@ def set_product_name(self, product_type: str): if products: self.products_widget.addItems(products) + def set_selected_product_name(self, product_name: str): + matching_items = self.products_widget.findItems( + product_name, QtCore.Qt.MatchFixedString) + if matching_items: + self.products_widget.setCurrentItem(matching_items[0]) + def get_available_products(self, product_type): if product_type == "*": @@ -636,9 +643,11 @@ def get_available_products(self, product_type): def select_a_product(node): + cursor_pos = QtGui.QCursor.pos() + project_name = node.evalParm("project_name") folder_path = node.evalParm("folder_path") - product_parm = node.parm("product_name") + product_parm = node.parm("product_name") folder_entity = ayon_api.get_folder_by_path(project_name, folder_path, @@ -650,7 +659,13 @@ def select_a_product(node): project_name, folder_entity["id"], parent=lib.get_main_window() - ) + ) + dialog.set_selected_product_name(product_parm.eval()) + + dialog.resize(300, 600) + dialog.setWindowFlags(QtCore.Qt.Popup) + pos = dialog.mapToGlobal(cursor_pos - QtCore.QPoint(300, 0)) + dialog.move(pos) result = dialog.exec_() if result != QtWidgets.QDialog.Accepted: From 04430426fd0f92d64a8e31b40ae41b2216a1a75b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Jul 2024 09:19:05 +0200 Subject: [PATCH 4/4] Refactor `select_a_product` dialog to `select_product_name` + add docstring to function --- client/ayon_houdini/api/hda_utils.py | 6 +++++- .../ayon_8_8Lop_1lop__import_8_81.0/DialogScript | 2 +- .../ayon_8_8Lop_1lop__import_8_81.0/PythonModule | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/client/ayon_houdini/api/hda_utils.py b/client/ayon_houdini/api/hda_utils.py index a3b557a961..1edbb11f5e 100644 --- a/client/ayon_houdini/api/hda_utils.py +++ b/client/ayon_houdini/api/hda_utils.py @@ -641,7 +641,11 @@ def get_available_products(self, product_type): return list(sorted(product["name"] for product in products)) -def select_a_product(node): +def select_product_name(node): + """Show a modal pop-up dialog to allow user to select a product name + under the current folder entity as defined on the node's parameters. + + Applies the chosen value to the `product_name` parm on the node.""" cursor_pos = QtGui.QCursor.pos() diff --git a/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/DialogScript b/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/DialogScript index 85e5c631f4..8af187792d 100644 --- a/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/DialogScript +++ b/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/DialogScript @@ -52,7 +52,7 @@ label "Product" type string default { "usdAsset" } - parmtag { "script_action" "from ayon_houdini.api.hda_utils import select_a_product;select_a_product(kwargs['node'])" } + parmtag { "script_action" "from ayon_houdini.api.hda_utils import select_product_name;select_product_name(kwargs['node'])" } parmtag { "script_action_icon" "BUTTONS_reselect" } parmtag { "script_callback" "hou.phm().set_to_latest_version(kwargs['node'])\nhou.phm().on_representation_parms_changed(kwargs['node'])" } parmtag { "script_callback_language" "python" } diff --git a/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/PythonModule b/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/PythonModule index ac17b275f9..0e91d023b2 100644 --- a/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/PythonModule +++ b/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/PythonModule @@ -5,6 +5,6 @@ from ayon_houdini.api.hda_utils import ( on_representation_parms_changed, setup_flag_changed_callback, get_available_versions, - select_a_product, + select_product_name, set_to_latest_version )