diff --git a/server_addon/houdini/client/ayon_houdini/api/hda_utils.py b/server_addon/houdini/client/ayon_houdini/api/hda_utils.py new file mode 100644 index 0000000000..412364bc04 --- /dev/null +++ b/server_addon/houdini/client/ayon_houdini/api/hda_utils.py @@ -0,0 +1,593 @@ +"""Helper functions for load HDA""" + +import os +import contextlib +import uuid +from typing import List + +import ayon_api +from ayon_api import ( + get_project, + get_representation_by_id, + get_versions, + get_folder_by_path, + get_product_by_name, + get_version_by_name, + get_representation_by_name +) +from ayon_core.pipeline.load import ( + get_representation_context, + get_representation_path_from_context +) +from ayon_core.pipeline.context_tools import ( + get_current_project_name, + get_current_folder_path +) +from ayon_core.tools.utils import SimpleFoldersWidget +from ayon_core.style import load_stylesheet + +from ayon_houdini.api import lib + +from qtpy import QtCore, QtWidgets +import hou + + +def is_valid_uuid(value) -> bool: + """Return whether value is a valid UUID""" + try: + uuid.UUID(value) + except ValueError: + return False + return True + + +@contextlib.contextmanager +def _unlocked_parm(parm): + """Unlock parm during context; will always lock after""" + try: + parm.lock(False) + yield + finally: + parm.lock(True) + + +def get_available_versions(node): + """Return the versions list for node. + + The versions are sorted with the latest version first and oldest lower + version last. + + Args: + node (hou.Node): Node to query selected products' versions for. + + Returns: + list[int]: Version numbers for the product + """ + + project_name = node.evalParm("project_name") or get_current_project_name() + folder_path = node.evalParm("folder_path") + product_name = node.evalParm("product_name") + + if not all([ + project_name, folder_path, product_name + ]): + return [] + + folder_entity = get_folder_by_path( + project_name, + folder_path, + fields={"id"}) + if not folder_entity: + return [] + product_entity = get_product_by_name( + project_name, + product_name=product_name, + folder_id=folder_entity["id"], + fields={"id"}) + if not product_entity: + return [] + + # TODO: Support hero versions + versions = get_versions( + project_name, + product_ids={product_entity["id"]}, + fields={"version"}, + hero=False) + version_names = [version["version"] for version in versions] + version_names.reverse() + return version_names + + +def update_info(node, context): + """Update project, folder, product, version, representation name parms. + + Arguments: + node (hou.Node): Node to update + context (dict): Context of representation + + """ + # TODO: Avoid 'duplicate' taking over the expression if originally + # it was $OS and by duplicating, e.g. the `folder` does not exist + # anymore since it is now `hero1` instead of `hero` + # TODO: Support hero versions + version = str(context["version"]["version"]) + + # We only set the values if the value does not match the currently + # evaluated result of the other parms, so that if the project name + # value was dynamically set by the user with an expression or alike + # then if it still matches the value of the current representation id + # we preserve it. In essence, only update the value if the current + # *evaluated* value of the parm differs. + parms = { + "project_name": context["project"]["name"], + "folder_path": context["folder"]["path"], + "product_name": context["product"]["name"], + "version": version, + "representation_name": context["representation"]["name"], + } + parms = {key: value for key, value in parms.items() + if node.evalParm(key) != value} + parms["load_message"] = "" # clear any warnings/errors + + # 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 + node.setParms(parms) + + +def _get_thumbnail(project_name: str, version_id: str, thumbnail_dir: str): + folder = hou.text.expandString(thumbnail_dir) + path = os.path.join(folder, "{}_thumbnail.jpg".format(version_id)) + expanded_path = hou.text.expandString(path) + if os.path.isfile(expanded_path): + return path + + # Try and create a thumbnail cache file + data = ayon_api.get_thumbnail(project_name, + entity_type="version", + entity_id=version_id) + if data: + thumbnail_dir_expanded = hou.text.expandString(thumbnail_dir) + os.makedirs(thumbnail_dir_expanded, exist_ok=True) + with open(expanded_path, "wb") as f: + f.write(data.content) + return path + + +def set_representation(node, representation_id: str): + file_parm = node.parm("file") + if not representation_id: + # Clear filepath and thumbnail + with _unlocked_parm(file_parm): + file_parm.set("") + set_node_thumbnail(node, None) + return + + project_name = ( + node.evalParm("project_name") + or get_current_project_name() + ) + + # Ignore invalid representation ids silently + # TODO remove - added for backwards compatibility with OpenPype scenes + if not is_valid_uuid(representation_id): + return + + repre_entity = get_representation_by_id(project_name, representation_id) + if not repre_entity: + return + + context = get_representation_context(project_name, repre_entity) + update_info(node, context) + path = get_representation_path_from_context(context) + # Load fails on UNC paths with backslashes and also + # fails to resolve @sourcename var with backslashed + # paths correctly. So we force forward slashes + path = path.replace("\\", "/") + with _unlocked_parm(file_parm): + file_parm.set(path) + + if node.evalParm("show_thumbnail"): + # Update thumbnail + # TODO: Cache thumbnail path as well + version_id = repre_entity["versionId"] + thumbnail_dir = node.evalParm("thumbnail_cache_dir") + thumbnail_path = _get_thumbnail( + project_name, version_id, thumbnail_dir + ) + set_node_thumbnail(node, thumbnail_path) + + +def set_node_thumbnail(node, thumbnail: str): + """Update node thumbnail to thumbnail""" + if thumbnail is None: + lib.set_node_thumbnail(node, None) + + rect = compute_thumbnail_rect(node) + lib.set_node_thumbnail(node, thumbnail, rect) + + +def compute_thumbnail_rect(node): + """Compute thumbnail bounding rect based on thumbnail parms""" + offset_x = node.evalParm("thumbnail_offsetx") + offset_y = node.evalParm("thumbnail_offsety") + width = node.evalParm("thumbnail_size") + # todo: compute height from aspect of actual image file. + aspect = 0.5625 # for now assume 16:9 + height = width * aspect + + center = 0.5 + half_width = (width * .5) + + return hou.BoundingRect( + offset_x + center - half_width, + offset_y, + offset_x + center + half_width, + offset_y + height + ) + + +def on_thumbnail_show_changed(node): + """Callback on thumbnail show parm changed""" + if node.evalParm("show_thumbnail"): + # For now, update all + on_representation_id_changed(node) + else: + lib.remove_all_thumbnails(node) + + +def on_thumbnail_size_changed(node): + """Callback on thumbnail offset or size parms changed""" + thumbnail = lib.get_node_thumbnail(node) + if thumbnail: + rect = compute_thumbnail_rect(node) + thumbnail.setRect(rect) + lib.set_node_thumbnail(node, thumbnail) + + +def on_representation_id_changed(node): + """Callback on representation id changed + + Args: + node (hou.Node): Node to update. + """ + repre_id = node.evalParm("representation") + set_representation(node, repre_id) + + +def on_representation_parms_changed(node): + """ + Usually used as callback to the project, folder, product, version and + representation parms which on change - would result in a different + representation id to be resolved. + + Args: + node (hou.Node): Node to update. + """ + project_name = node.evalParm("project_name") or get_current_project_name() + representation_id = get_representation_id( + project_name=project_name, + folder_path=node.evalParm("folder_path"), + product_name=node.evalParm("product_name"), + version=node.evalParm("version"), + representation_name=node.evalParm("representation_name"), + load_message_parm=node.parm("load_message") + ) + if representation_id is None: + representation_id = "" + else: + representation_id = str(representation_id) + + if node.evalParm("representation") != representation_id: + node.parm("representation").set(representation_id) + node.parm("representation").pressButton() # trigger callback + + +def get_representation_id( + project_name, + folder_path, + product_name, + version, + representation_name, + load_message_parm, +): + """Get representation id. + + Args: + project_name (str): Project name + folder_path (str): Folder name + product_name (str): Product name + version (str): Version name as string + representation_name (str): Representation name + load_message_parm (hou.Parm): A string message parm to report + any error messages to. + + Returns: + Optional[str]: Representation id or None if not found. + + """ + + if not all([ + project_name, folder_path, product_name, version, representation_name + ]): + labels = { + "project": project_name, + "folder": folder_path, + "product": product_name, + "version": version, + "representation": representation_name + } + missing = ", ".join(key for key, value in labels.items() if not value) + load_message_parm.set(f"Load info incomplete. Found empty: {missing}") + return + + try: + version = int(version.strip()) + except ValueError: + load_message_parm.set(f"Invalid version format: '{version}'\n" + "Make sure to set a valid version number.") + return + + folder_entity = get_folder_by_path(project_name, + folder_path=folder_path, + fields={"id"}) + if not folder_entity: + # This may be due to the project not existing - so let's validate + # that first + if not get_project(project_name): + load_message_parm.set(f"Project not found: '{project_name}'") + return + load_message_parm.set(f"Folder not found: '{folder_path}'") + return + + product_entity = get_product_by_name( + project_name, + product_name=product_name, + folder_id=folder_entity["id"], + fields={"id"}) + if not product_entity: + load_message_parm.set(f"Product not found: '{product_name}'") + return + version_entity = get_version_by_name( + project_name, + version, + product_id=product_entity["id"], + fields={"id"}) + if not version_entity: + load_message_parm.set(f"Version not found: '{version}'") + return + representation_entity = get_representation_by_name( + project_name, + representation_name, + version_id=version_entity["id"], + fields={"id"}) + if not representation_entity: + load_message_parm.set( + f"Representation not found: '{representation_name}'.") + return + return representation_entity["id"] + + +def setup_flag_changed_callback(node): + """Register flag changed callback (for thumbnail brightness)""" + node.addEventCallback( + (hou.nodeEventType.FlagChanged,), + on_flag_changed + ) + + +def on_flag_changed(node, **kwargs): + """On node flag changed callback. + + Updates the brightness of attached thumbnails + """ + # Showing thumbnail is disabled so can return early since + # there should be no thumbnail to update. + if not node.evalParm('show_thumbnail'): + return + + # Update node thumbnails brightness with the + # bypass state of the node. + parent = node.parent() + images = lib.get_background_images(parent) + if not images: + return + + brightness = 0.3 if node.isBypassed() else 1.0 + has_changes = False + node_path = node.path() + for image in images: + if image.relativeToPath() == node_path: + image.setBrightness(brightness) + has_changes = True + + if has_changes: + lib.set_background_images(parent, images) + + +def keep_background_images_linked(node, old_name): + """Reconnect background images to node from old name. + + Used as callback on node name changes to keep thumbnails linked.""" + from ayon_houdini.api.lib import ( + get_background_images, + set_background_images + ) + + parent = node.parent() + images = get_background_images(parent) + if not images: + return + + changes = False + old_path = f"{node.parent().path()}/{old_name}" + for image in images: + if image.relativeToPath() == old_path: + image.setRelativeToPath(node.path()) + changes = True + + if changes: + set_background_images(parent, images) + + +class SelectFolderPathDialog(QtWidgets.QDialog): + """Simple dialog to allow a user to select project and asset.""" + + def __init__(self, parent=None): + super(SelectFolderPathDialog, self).__init__(parent) + self.setWindowTitle("Set project and folder path") + self.setStyleSheet(load_stylesheet()) + + project_widget = QtWidgets.QComboBox() + project_widget.addItems(self.get_projects()) + + filter_widget = QtWidgets.QLineEdit() + filter_widget.setPlaceholderText("Folder name filter...") + + folder_widget = SimpleFoldersWidget(parent=self) + + accept_button = QtWidgets.QPushButton("Accept") + + main_layout = QtWidgets.QVBoxLayout(self) + main_layout.addWidget(project_widget, 0) + main_layout.addWidget(filter_widget, 0) + main_layout.addWidget(folder_widget, 1) + main_layout.addWidget(accept_button, 0) + + self.project_widget = project_widget + self.folder_widget = folder_widget + + project_widget.currentTextChanged.connect(self.on_project_changed) + filter_widget.textChanged.connect(folder_widget.set_name_filter) + folder_widget.double_clicked.connect(self.accept) + accept_button.clicked.connect(self.accept) + + def get_selected_folder_path(self) -> str: + return self.folder_widget.get_selected_folder_path() + + def get_selected_project_name(self) -> str: + return self.project_widget.currentText() + + def get_projects(self) -> List[str]: + projects = ayon_api.get_projects(fields=["name"]) + return [p["name"] for p in projects] + + def on_project_changed(self, project_name: str): + self.folder_widget.set_project_name(project_name) + + def set_project_name(self, project_name: str): + self.project_widget.setCurrentText(project_name) + + if self.project_widget.currentText() != project_name: + # Project does not exist + return + + # Force the set of widget because even though a callback exist on the + # project widget it may have been initialized to that value and hence + # detect no change. + self.folder_widget.set_project_name(project_name) + + +def select_folder_path(node): + """Show dialog to select folder path. + + When triggered it opens a dialog that shows the available + folder paths within a given project. + + Note: + This function should be refactored. + It currently shows the available + folder paths within the current project only. + + Args: + node (hou.OpNode): The HDA node. + """ + main_window = lib.get_main_window() + + project_name = node.evalParm("project_name") + folder_path = node.evalParm("folder_path") + + dialog = SelectFolderPathDialog(parent=main_window) + dialog.set_project_name(project_name) + if folder_path: + # We add a small delay to the setting of the selected folder + # because the folder widget's set project logic itself also runs + # with a bit of a delay, and unfortunately otherwise the project + # has not been selected yet and thus selection does not work. + def _select_folder_path(): + dialog.folder_widget.set_selected_folder_path(folder_path) + QtCore.QTimer.singleShot(100, _select_folder_path) + + dialog.setStyleSheet(load_stylesheet()) + + result = dialog.exec_() + if result != QtWidgets.QDialog.Accepted: + return + + # Set project + selected_project_name = dialog.get_selected_project_name() + if selected_project_name == get_current_project_name(): + selected_project_name = '$AYON_PROJECT_NAME' + + project_parm = node.parm("project_name") + project_parm.set(selected_project_name) + project_parm.pressButton() # allow any callbacks to trigger + + # Set folder path + selected_folder_path = dialog.get_selected_folder_path() + if not selected_folder_path: + # Do nothing if user accepted with nothing selected + return + + if selected_folder_path == get_current_folder_path(): + selected_folder_path = '$AYON_FOLDER_PATH' + + folder_parm = node.parm("folder_path") + folder_parm.set(selected_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. + + Args: + node (hou.OpNode): The HDA 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") + + folder_entity = ayon_api.get_folder_by_path(project_name, + folder_path, + fields={"id"}) + if not folder_entity: + return [] + + products = ayon_api.get_products( + project_name, + folder_ids=[folder_entity["id"]], + product_types=[product_type] + ) + + return [product["name"] for product in products] + + +def set_to_latest_version(node): + """Callback on product name change + + Refresh version parameter value by setting its value to + the latest version of the selected product. + + Args: + node (hou.OpNode): The HDA node. + """ + + versions = get_available_versions(node) + if versions: + node.parm("version").set(str(versions[0])) diff --git a/server_addon/houdini/client/ayon_houdini/api/lib.py b/server_addon/houdini/client/ayon_houdini/api/lib.py index a536b27f08..cfaaedc560 100644 --- a/server_addon/houdini/client/ayon_houdini/api/lib.py +++ b/server_addon/houdini/client/ayon_houdini/api/lib.py @@ -1037,6 +1037,231 @@ def sceneview_snapshot( log.debug("A snapshot of sceneview has been saved to: {}".format(filepath)) +def get_background_images(node, raw=False): + """"Return background images defined inside node. + + Similar to `nodegraphutils.saveBackgroundImages` but this method also + allows to retrieve the data as JSON encodable data instead of + `hou.NetworkImage` instances when using `raw=True` + """ + + def _parse(image_data): + image = hou.NetworkImage(image_data["path"], + hou.BoundingRect(*image_data["rect"])) + if "relativetopath" in image_data: + image.setRelativeToPath(image_data["relativetopath"]) + if "brightness" in image_data: + image.setBrightness(image_data["brightness"]) + return image + + data = node.userData("backgroundimages") + if not data: + return [] + + try: + images = json.loads(data) + except json.decoder.JSONDecodeError: + images = [] + + if not raw: + images = [_parse(_data) for _data in images] + return images + + +def set_background_images(node, images): + """Set hou.NetworkImage background images under given hou.Node + + Similar to: `nodegraphutils.loadBackgroundImages` + + """ + + def _serialize(image): + """Return hou.NetworkImage as serialized dict""" + if isinstance(image, dict): + # Assume already serialized, only do some minor validations + if "path" not in image: + raise ValueError("Missing `path` key in image dictionary.") + if "rect" not in image: + raise ValueError("Missing `rect` key in image dictionary.") + if len(image["rect"]) != 4: + raise ValueError("`rect` value must be list of four floats.") + return image + + rect = image.rect() + rect_min = rect.min() + rect_max = rect.max() + data = { + "path": image.path(), + "rect": [rect_min.x(), rect_min.y(), rect_max.x(), rect_max.y()], + } + if image.brightness() != 1.0: + data["brightness"] = image.brightness() + if image.relativeToPath(): + data["relativetopath"] = image.relativeToPath() + return data + + with hou.undos.group('Edit Background Images'): + if images: + assert all(isinstance(image, (dict, hou.NetworkImage)) + for image in images) + data = json.dumps([_serialize(image) for image in images]) + node.setUserData("backgroundimages", data) + else: + node.destroyUserData("backgroundimages", must_exist=False) + + +def set_node_thumbnail(node, image_path, rect=None): + """Set hou.NetworkImage attached to node. + + If an existing connected image is found it assumes that is the existing + thumbnail and will update that particular instance instead. + + When `image_path` is None an existing attached `hou.NetworkImage` will be + removed. + + Arguments: + node (hou.Node): Node to set thumbnail for. + image_path (Union[str, None]): Path to image to set. + If None is set then the thumbnail will be removed if it exists. + rect (hou.BoundingRect): Bounding rect for the relative placement + to the node. + + Returns: + hou.NetworkImage or None: The network image that was set or None if + instead it not set or removed. + + """ + + parent = node.parent() + images = get_background_images(parent) + + node_path = node.path() + # Find first existing image attached to node + index, image = next( + ( + (index, image) for index, image in enumerate(images) if + image.relativeToPath() == node_path + ), + (None, None) + ) + if image_path is None: + # Remove image if it exists + if image: + images.remove(image) + set_background_images(parent, images) + return + + if rect is None: + rect = hou.BoundingRect(-1, -1, 1, 1) + + if isinstance(image_path, hou.NetworkImage): + image = image_path + if index is not None: + images[index] = image + else: + images.append(image) + elif image is None: + # Create the image + image = hou.NetworkImage(image_path, rect) + image.setRelativeToPath(node.path()) + images.append(image) + else: + # Update first existing image + image.setRect(rect) + image.setPath(image_path) + + set_background_images(parent, images) + + return image + + +def remove_all_thumbnails(node): + """Remove all node thumbnails. + + Removes all network background images that are linked to the given node. + """ + parent = node.parent() + images = get_background_images(parent) + node_path = node.path() + images = [ + image for image in images if image.relativeToPath() != node_path + ] + set_background_images(parent, images) + + +def get_node_thumbnail(node, first_only=True): + """Return node thumbnails. + + Return network background images that are linked to the given node. + By default, only returns the first one found, unless `first_only` is False. + + Returns: + Union[hou.NetworkImage, List[hou.NetworkImage]]: + Connected network images + + """ + parent = node.parent() + images = get_background_images(parent) + node_path = node.path() + + def is_attached_to_node(image): + return image.relativeToPath() == node_path + + attached_images = filter(is_attached_to_node, images) + + # Find first existing image attached to node + if first_only: + return next(attached_images, None) + else: + return attached_images + + +def find_active_network(category, default): + """Find the first active network editor in the UI. + + If no active network editor pane is found at the given category then the + `default` path will be used as fallback. + + For example, to find an active LOPs network: + >>> network = find_active_network( + ... category=hou.lopNodeTypeCategory(), + ... fallback="/stage" + ... ) + hou.Node("/stage/lopnet1") + + Arguments: + category (hou.NodeTypeCategory): The node network category type. + default (str): The default path to fallback to if no active pane + is found with the given category. + + Returns: + hou.Node: The node network to return. + + """ + # Find network editors that are current tab of given category + index = 0 + while True: + pane = hou.ui.paneTabOfType(hou.paneTabType.NetworkEditor, index) + if pane is None: + break + + index += 1 + if not pane.isCurrentTab(): + continue + + pwd = pane.pwd() + if pwd.type().category() != category: + continue + + if not pwd.isEditable(): + continue + + return pwd + + # Default to the fallback if no valid candidate was found + return hou.node(default) + + def update_content_on_context_change(): """Update all Creator instances to current asset""" host = registered_host() diff --git a/server_addon/houdini/client/ayon_houdini/api/pipeline.py b/server_addon/houdini/client/ayon_houdini/api/pipeline.py index 5efbcc6ff9..c6fbbd5b62 100644 --- a/server_addon/houdini/client/ayon_houdini/api/pipeline.py +++ b/server_addon/houdini/client/ayon_houdini/api/pipeline.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- """Pipeline tools for OpenPype Houdini integration.""" import os +import json import logging import hou # noqa @@ -26,6 +27,8 @@ from ayon_core.lib import ( env_value_to_bool, ) +from .lib import JSON_PREFIX + log = logging.getLogger("ayon_houdini") @@ -258,7 +261,25 @@ def parse_container(container): dict: The container schema data for this container node. """ - data = lib.read(container) + # Read only relevant parms + # TODO: Clean up this hack replacing `lib.read(container)` + + data = {} + for name in ["name", "namespace", "loader", "representation", "id"]: + parm = container.parm(name) + if not parm: + return {} + + value = parm.eval() + + # test if value is json encoded dict + if isinstance(value, str) and value.startswith(JSON_PREFIX): + try: + value = json.loads(value[len(JSON_PREFIX):]) + except json.JSONDecodeError: + # not a json + pass + data[name] = value # Backwards compatibility pre-schemas for containers data["schema"] = data.get("schema", "openpype:container-1.0") diff --git a/server_addon/houdini/client/ayon_houdini/api/usd.py b/server_addon/houdini/client/ayon_houdini/api/usd.py index ed33fbf590..f7824dfc5c 100644 --- a/server_addon/houdini/client/ayon_houdini/api/usd.py +++ b/server_addon/houdini/client/ayon_houdini/api/usd.py @@ -3,130 +3,12 @@ import contextlib import logging -import ayon_api -from qtpy import QtWidgets, QtCore, QtGui - -from ayon_core import style -from ayon_core.pipeline import get_current_project_name -from ayon_core.tools.utils import ( - PlaceholderLineEdit, - RefreshButton, - SimpleFoldersWidget, -) - from pxr import Sdf log = logging.getLogger(__name__) -class SelectFolderDialog(QtWidgets.QWidget): - """Frameless folders dialog to select folder with double click. - - Args: - parm: Parameter where selected folder path is set. - """ - - def __init__(self, parm): - self.setWindowTitle("Pick Folder") - self.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.Popup) - - header_widget = QtWidgets.QWidget(self) - - filter_input = PlaceholderLineEdit(header_widget) - filter_input.setPlaceholderText("Filter folders..") - - refresh_btn = RefreshButton(self) - - header_layout = QtWidgets.QHBoxLayout(header_widget) - header_layout.setContentsMargins(0, 0, 0, 0) - header_layout.addWidget(filter_input) - header_layout.addWidget(refresh_btn) - - for widget in ( - refresh_btn, - filter_input, - ): - size_policy = widget.sizePolicy() - size_policy.setVerticalPolicy( - QtWidgets.QSizePolicy.MinimumExpanding) - widget.setSizePolicy(size_policy) - - folders_widget = SimpleFoldersWidget(self) - folders_widget.set_project_name(get_current_project_name()) - - layout = QtWidgets.QHBoxLayout(self) - layout.addWidget(header_widget, 0) - layout.addWidget(folders_widget, 1) - - folders_widget.double_clicked.connect(self._set_parameter) - filter_input.textChanged.connect(self._on_filter_change) - refresh_btn.clicked.connect(self._on_refresh_clicked) - - self._folders_widget = folders_widget - self._parm = parm - - def _on_refresh_clicked(self): - self._folders_widget.refresh() - - def _on_filter_change(self, text): - self._folders_widget.set_name_filter(text) - - def _set_parameter(self): - folder_path = self._folders_widget.get_selected_folder_path() - self._parm.set(folder_path) - self.close() - - def _on_show(self): - pos = QtGui.QCursor.pos() - # Select the current folder if there is any - select_id = None - folder_path = self._parm.eval() - if folder_path: - project_name = get_current_project_name() - folder_entity = ayon_api.get_folder_by_path( - project_name, folder_path, fields={"id"} - ) - if folder_entity: - select_id = folder_entity["id"] - - # Set stylesheet - self.setStyleSheet(style.load_stylesheet()) - # Refresh folders (is threaded) - self._folders_widget.refresh() - # Select folder - must be done after refresh - if select_id is not None: - self._folders_widget.set_selected_folder(select_id) - - # Show cursor (top right of window) near cursor - self.resize(250, 400) - self.move(self.mapFromGlobal(pos) - QtCore.QPoint(self.width(), 0)) - - def showEvent(self, event): - super(SelectFolderDialog, self).showEvent(event) - self._on_show() - - -def pick_folder(node): - """Show a user interface to select an Folder in the project - - When double clicking an folder it will set the Folder value in the - 'folderPath' parameter. - - """ - - parm = node.parm("folderPath") - if not parm: - log.error("Node has no 'folderPath' parameter: %s", node) - return - - # Construct a frameless popup so it automatically - # closes when clicked outside of it. - global tool - tool = SelectFolderDialog(parm) - tool.show() - - def add_usd_output_processor(ropnode, processor): """Add USD Output Processor to USD Rop node. diff --git a/server_addon/houdini/client/ayon_houdini/plugins/load/load_asset_lop.py b/server_addon/houdini/client/ayon_houdini/plugins/load/load_asset_lop.py new file mode 100644 index 0000000000..d9ab438d6d --- /dev/null +++ b/server_addon/houdini/client/ayon_houdini/plugins/load/load_asset_lop.py @@ -0,0 +1,52 @@ +from ayon_core.pipeline import load +from ayon_houdini.api.lib import find_active_network + +import hou + + +class LOPLoadAssetLoader(load.LoaderPlugin): + """Load reference/payload into Solaris using AYON `lop_import` LOP""" + + product_types = {"*"} + label = "Load Asset (LOPs)" + representations = ["usd", "abc", "usda", "usdc"] + order = -10 + icon = "code-fork" + color = "orange" + + def load(self, context, name=None, namespace=None, data=None): + + # Define node name + namespace = namespace if namespace else context["folder"]["name"] + node_name = "{}_{}".format(namespace, name) if namespace else name + + # Create node + network = find_active_network( + category=hou.lopNodeTypeCategory(), + default="/stage" + ) + node = network.createNode("ayon::lop_import", node_name=node_name) + node.moveToGoodPosition() + + # Set representation id + parm = node.parm("representation") + parm.set(context["representation"]["id"]) + parm.pressButton() # trigger callbacks + + nodes = [node] + self[:] = nodes + + def update(self, container, context): + node = container["node"] + + # Set representation id + parm = node.parm("representation") + parm.set(context["representation"]["id"]) + parm.pressButton() # trigger callbacks + + def remove(self, container): + node = container["node"] + node.destroy() + + def switch(self, container, context): + self.update(container, context) diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/INDEX__SECTION b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/INDEX__SECTION new file mode 100644 index 0000000000..5b5d5a1340 --- /dev/null +++ b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/INDEX__SECTION @@ -0,0 +1,13 @@ +Operator: ayon::lop_import::1.0 +Label: AYON Load Asset +Path: oplib:/ayon::Lop/lop_import::1.0?ayon::Lop/lop_import::1.0 +Icon: opdef:/ayon::Lop/lop_import::1.0?IconImage +Table: Lop +License: +Extra: +User: +Inputs: 0 to 1 +Subnet: true +Python: false +Empty: false +Modified: Thu Jun 10 16:44:00 2024 diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/Sections.list b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/Sections.list new file mode 100644 index 0000000000..0a1bfe4e69 --- /dev/null +++ b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/Sections.list @@ -0,0 +1,4 @@ +"" +INDEX__SECTION INDEX_SECTION +houdini.hdalibrary houdini.hdalibrary +ayon_8_8Lop_1lop__import_8_81.0 ayon::Lop/lop_import::1.0 diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/AYON__icon.png b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/AYON__icon.png new file mode 100644 index 0000000000..ed13aeea52 Binary files /dev/null and b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/AYON__icon.png differ diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/Contents.dir/Contents.createtimes b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/Contents.dir/Contents.createtimes new file mode 100644 index 0000000000..2d6bdf00aa --- /dev/null +++ b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/Contents.dir/Contents.createtimes @@ -0,0 +1,6 @@ +{ + "hdaroot/warn_no_representation_set.def":1708980551, + "hdaroot/reference.def":1698150558, + "hdaroot/output0.def":1698215383, + "hdaroot.def":1717451587 +} diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/Contents.dir/Contents.houdini_versions b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/Contents.dir/Contents.houdini_versions new file mode 100644 index 0000000000..fb7f024db3 --- /dev/null +++ b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/Contents.dir/Contents.houdini_versions @@ -0,0 +1,9 @@ +{ + "values":["20.0.703" + ], + "indexes":{ + "hdaroot/warn_no_representation_set.userdata":0, + "hdaroot/reference.userdata":0, + "hdaroot/output0.userdata":0 + } +} diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/Contents.dir/Contents.mime b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/Contents.dir/Contents.mime new file mode 100644 index 0000000000..ec12230a75 --- /dev/null +++ b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/Contents.dir/Contents.mime @@ -0,0 +1,384 @@ +MIME-Version: 1.0 +Content-Type: multipart/mixed; boundary="HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY" + +--HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY +Content-Disposition: attachment; filename="node_type" +Content-Type: text/plain + +Lop + +--HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY +Content-Disposition: attachment; filename="hdaroot.init" +Content-Type: text/plain + +type = ayon::lop_import::1.0 +matchesdef = 0 + +--HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY +Content-Disposition: attachment; filename="hdaroot.def" +Content-Type: text/plain + +comment "" +position -1.67827 1.26636 +connectornextid 0 +flags = lock off model off template off footprint off xray off bypass off display on render on highlight off unload off savedata off compress on colordefault on exposed on debug off +outputsNamed3 +{ +} +inputsNamed3 +{ +} +inputs +{ +} +stat +{ + create -1 + modify -1 + author Mustafa_Taher@Major-Kalawy + access 0777 +} +color UT_Color RGB 0.8 0.8 0.8 +delscript "" +exprlanguage hscript +end + +--HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY +Content-Disposition: attachment; filename="hdaroot.userdata" +Content-Type: text/plain + +{ + "___Version___":{ + "type":"string", + "value":"" + }, + "wirestyle":{ + "type":"string", + "value":"rounded" + } +} + +--HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY +Content-Disposition: attachment; filename="hdaroot.inp" +Content-Type: text/plain + +1 +0 -1.3044269152357999 6.2635205889365251 2 0 __NO_OWNER_NETWORK_BOX__ "FROMOUTPUT" + +--HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY +Content-Disposition: attachment; filename="hdaroot/output0.init" +Content-Type: text/plain + +type = output +matchesdef = 1 + +--HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY +Content-Disposition: attachment; filename="hdaroot/output0.def" +Content-Type: text/plain + +comment "" +position -1.13898e-08 2.03328 +connectornextid 1 +flags = lock off model off template off footprint off xray off bypass off display off render on highlight off unload off savedata off compress on colordefault on exposed on debug off +outputsNamed3 +{ +} +inputsNamed3 +{ +0 reference 0 1 "input1" +} +inputs +{ +0 reference 0 1 +} +stat +{ + create -1 + modify -1 + author Maqina-05@Maqina-05 + access 0777 +} +color UT_Color RGB 0.8 0.8 0.8 +delscript "" +exprlanguage hscript +end + +--HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY +Content-Disposition: attachment; filename="hdaroot/output0.parm" +Content-Type: text/plain + +{ +version 0.8 +outputidx [ 0 locks=0 ] ( 0 ) +modifiedprims [ 0 locks=0 ] ( "`lopinputprims(\".\", 0)`" ) +} + +--HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY +Content-Disposition: attachment; filename="hdaroot/output0.userdata" +Content-Type: text/plain + +{ + "___Version___":{ + "type":"string", + "value":"___EXTERNAL___" + } +} + +--HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY +Content-Disposition: attachment; filename="hdaroot/reference.init" +Content-Type: text/plain + +type = reference::2.0 +matchesdef = 1 + +--HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY +Content-Disposition: attachment; filename="hdaroot/reference.def" +Content-Type: text/plain + +comment "" +position -0.00125004 4.19692 +connectornextid 2 +flags = lock off model off template off footprint off xray off bypass off display off render off highlight off unload off savedata off compress on colordefault on exposed on debug off +outputsNamed3 +{ +0 "output1" +} +inputsNamed3 +{ +1 warn_no_representation_set 1 1 "input1" +} +inputs +{ +0 warn_no_representation_set 0 1 +} +stat +{ + create -1 + modify -1 + author Maqina-05@Maqina-05 + access 0777 +} +color UT_Color RGB 0.8 0.8 0.8 +delscript "" +exprlanguage hscript +end + +--HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY +Content-Disposition: attachment; filename="hdaroot/reference.editableinputdata" +Content-Type: text/plain + +[ + { + } +] + +--HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY +Content-Disposition: attachment; filename="hdaroot/reference.chn" +Content-Type: text/plain + +{ + channel primpath1 { + lefttype = extend + righttype = extend + defaultString = \"/character\" + flags = 0 + start = 40 + segment { length = 0 expr = chs(\"../primpath1\") } + } + channel reftype1 { + lefttype = extend + righttype = extend + defaultString = \"file\" + flags = 0 + start = 40 + segment { length = 0 expr = chs(\"../reftype1\") } + } + channel instanceable1 { + lefttype = extend + righttype = extend + flags = 0 + start = 40 + segment { length = 0 expr = ch(\"../instanceable1\") } + } + channel filerefprim1 { + lefttype = extend + righttype = extend + defaultString = \"automaticPrim\" + flags = 0 + start = 40 + segment { length = 0 expr = chs(\"../filerefprim1\") } + } + channel filerefprimpath1 { + lefttype = extend + righttype = extend + defaultString = \"\" + flags = 0 + start = 40 + segment { length = 0 expr = chs(\"../filerefprimpath1\") } + } + channel timeoffset1 { + lefttype = extend + righttype = extend + flags = 0 + start = 40 + segment { length = 0 expr = ch(\"../timeoffset1\") } + } + channel timescale1 { + lefttype = extend + righttype = extend + default = 1 + flags = 0 + start = 40 + segment { length = 0 value = 1 1 expr = ch(\"../timescale1\") } + } + channel reload { + lefttype = extend + righttype = extend + flags = 0 + start = 40 + segment { length = 0 expr = ch(\"../reload\") } + } + } + +--HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY +Content-Disposition: attachment; filename="hdaroot/reference.parm" +Content-Type: text/plain + +{ +version 0.8 +main_switcher [ 0 locks=0 ] ( 0 0 0 ) +enable [ 0 locks=0 ] ( "on" ) +input_group [ 0 locks=0 ] ( 0 ) +primpath [ 0 locks=0 ] ( /`@sourcename` ) +createprims [ 0 locks=0 ] ( "on" ) +primcount [ 0 locks=0 ] ( 1 ) +reftype [ 0 locks=0 ] ( file ) +instanceable [ 0 locks=0 ] ( "off" ) +refprim [ 0 locks=0 ] ( automaticPrim ) +refprimpath [ 0 locks=0 ] ( automaticPrim ) +files_group [ 0 locks=0 ] ( 1 ) +num_files [ 0 locks=0 ] ( 1 ) +reload [ 0 locks=0 ] ( [ reload 0 ] ) +primkind [ 0 locks=0 ] ( "" ) +parentprimtype [ 0 locks=0 ] ( UsdGeomXform ) +handlemissingfiles [ 0 locks=0 ] ( error ) +preop [ 0 locks=0 ] ( none ) +refeditop [ 0 locks=0 ] ( prependfront ) +enable1 [ 0 locks=0 ] ( "on" ) +parameterorder1 [ 0 locks=0 ] ( "filefirst" ) +createprims1 [ 0 locks=0 ] ( "on" ) +primpath1 [ 0 locks=0 ] ( [ primpath1 /`@sourcename` ] ) +primcount1 [ 0 locks=0 ] ( 1 ) +reftype1 [ 0 locks=0 ] ( [ reftype1 file ] ) +instanceable1 [ 0 locks=0 ] ( [ instanceable1 0 ] ) +filepath1 [ 0 locks=0 ] ( `chs(\"../file\")` ) +filerefprim1 [ 0 locks=0 ] ( [ filerefprim1 automaticPrim ] ) +filerefprimpath1 [ 0 locks=0 ] ( [ filerefprimpath1 "" ] ) +timeoffset1 [ 0 locks=0 ] ( [ timeoffset1 0 ] ) +timescale1 [ 0 locks=0 ] ( [ timescale1 1 ] ) +file_spacer1 [ 0 locks=0 ] ( ) +} + +--HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY +Content-Disposition: attachment; filename="hdaroot/reference.userdata" +Content-Type: text/plain + +{ + "___Version___":{ + "type":"string", + "value":"___EXTERNAL___" + } +} + +--HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY +Content-Disposition: attachment; filename="hdaroot/warn_no_representation_set.init" +Content-Type: text/plain + +type = error +matchesdef = 1 + +--HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY +Content-Disposition: attachment; filename="hdaroot/warn_no_representation_set.def" +Content-Type: text/plain + +comment "" +position -1.13898e-08 5.20999 +connectornextid 2 +flags = lock off model off template off footprint off xray off bypass off display on render off highlight off unload off savedata off compress on colordefault on exposed on debug off +outputsNamed3 +{ +1 "output1" +} +inputsNamed3 +{ +0 (0) "" 1 "input1" +} +inputs +{ +0 (0) 0 1 +} +stat +{ + create -1 + modify -1 + author User@HP-Z820-03 + access 0777 +} +color UT_Color RGB 0.8 0.8 0.8 +delscript "" +exprlanguage hscript +end + +--HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY +Content-Disposition: attachment; filename="hdaroot/warn_no_representation_set.chn" +Content-Type: text/plain + +{ + channel enable1 { + lefttype = extend + righttype = extend + flags = 0 + start = 39.800000000000004 + segment { length = 0 expr = "if(ch(\"../representation\"), 0, 1)" } + } + } + +--HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY +Content-Disposition: attachment; filename="hdaroot/warn_no_representation_set.parm" +Content-Type: text/plain + +{ +version 0.8 +numerror [ 0 locks=0 ] ( 1 ) +errormsg1 [ 0 locks=0 ] ( `chs(\"../load_message\")` ) +severity1 [ 0 locks=0 ] ( "warn" ) +enable1 [ 0 locks=0 ] ( [ enable1 0 ] ) +} + +--HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY +Content-Disposition: attachment; filename="hdaroot/warn_no_representation_set.userdata" +Content-Type: text/plain + +{ + "___Version___":{ + "type":"string", + "value":"___EXTERNAL___" + } +} + +--HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY +Content-Disposition: attachment; filename="hdaroot.order" +Content-Type: text/plain + +3 +output0 +reference +warn_no_representation_set + +--HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY +Content-Disposition: attachment; filename="hdaroot.net" +Content-Type: text/plain + +1 + +--HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY-- diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/Contents.dir/Contents.modtimes b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/Contents.dir/Contents.modtimes new file mode 100644 index 0000000000..dbbe22dd33 --- /dev/null +++ b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/Contents.dir/Contents.modtimes @@ -0,0 +1,6 @@ +{ + "hdaroot/warn_no_representation_set.def":1711565807, + "hdaroot/reference.def":1711565598, + "hdaroot/output0.def":1708980807, + "hdaroot.def":1717451686 +} diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/Contents.dir/Sections.list b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/Contents.dir/Sections.list new file mode 100644 index 0000000000..49f2c1522a --- /dev/null +++ b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/Contents.dir/Sections.list @@ -0,0 +1,2 @@ +"" +Contents.mime Contents.mime diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/CreateScript b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/CreateScript new file mode 100644 index 0000000000..9a59e191ee --- /dev/null +++ b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/CreateScript @@ -0,0 +1,15 @@ +# Automatically generated script +\set noalias = 1 +# +# Creation script for ayon::lop_import::1.0 operator +# + +if ( "$arg1" == "" ) then + echo This script is intended as a creation script + exit +endif + +# Node $arg1 (ayon::Lop/lop_import::1.0) +opexprlanguage -s hscript $arg1 +opuserdata -n '___Version___' -v '' $arg1 +opuserdata -n 'wirestyle' -v 'rounded' $arg1 diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/DialogScript b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/DialogScript new file mode 100644 index 0000000000..3c97d10f35 --- /dev/null +++ b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/DialogScript @@ -0,0 +1,345 @@ +# Dialog script for ayon::lop_import::1.0 automatically generated + +{ + name ayon::lop_import::1.0 + script load_asset::1.0 + label "Load Asset" + + help { + "" + } + + inputlabel 1 "Input Stage" + inputlabel 2 "Input 2" + inputlabel 3 "Input 3" + inputlabel 4 "Input 4" + + groupsimple { + name "info2" + label "Info" + parmtag { "script_callback" "hou.phm().refresh_available_versions(kwargs['node'])" } + parmtag { "script_callback_language" "python" } + + parm { + name "assetinfo_labelparm" + label "Heading" + type label + default { "Choose Product" } + parmtag { "sidefx::look" "heading" } + } + parm { + name "project_name" + label "Project" + type string + default { "$AYON_PROJECT_NAME" } + parmtag { "script_action" "from ayon_houdini.api.hda_utils import select_folder_path;select_folder_path(kwargs['node'])" } + parmtag { "script_action_icon" "BUTTONS_reselect" } + parmtag { "script_callback" "hou.phm().on_representation_parms_changed(kwargs['node'])" } + parmtag { "script_callback_language" "python" } + } + parm { + name "folder_path" + label "Folder Path" + type string + default { "$AYON_FOLDER_PATH" } + parmtag { "script_action" "from ayon_houdini.api.hda_utils import select_folder_path;select_folder_path(kwargs['node'])" } + parmtag { "script_action_icon" "BUTTONS_reselect" } + 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" } + } + 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_callback" "hou.phm().set_to_latest_version(kwargs['node'])\nhou.phm().on_representation_parms_changed(kwargs['node'])" } + parmtag { "script_callback_language" "python" } + } + parm { + name "version" + label "Version" + type string + default { "" } + disablewhen "{ representation_old == \"\" }" + menureplace { + [ "versions = hou.phm().get_available_versions(kwargs['node'])" ] + [ "" ] + [ "result = []" ] + [ "for version in versions:" ] + [ " result.append(str(version))" ] + [ " result.append(f\"v{version:03d}\")" ] + [ " " ] + [ "return result" ] + language python + } + parmtag { "script_callback" "hou.phm().on_representation_parms_changed(kwargs['node'])" } + parmtag { "script_callback_language" "python" } + } + parm { + name "representation_name" + label "Representation" + type string + default { "usd" } + parmtag { "script_callback" "hou.phm().on_representation_parms_changed(kwargs['node'])" } + parmtag { "script_callback_language" "python" } + } + parm { + name "load_refresh" + label "Refresh" + type button + joinnext + default { "0" } + help "Click to refresh and retry applying the product load parameters to load the correct file" + parmtag { "button_icon" "" } + parmtag { "script_callback" "hou.phm().on_representation_parms_changed(kwargs['node'])" } + parmtag { "script_callback_language" "python" } + } + parm { + name "load_message" + label "Message" + type label + default { "" } + hidewhen "{ load_message == \"\" }" + parmtag { "sidefx::look" "block" } + } + parm { + name "sepparm" + label "Separator" + type separator + default { "" } + } + parm { + name "reload" + label "Reload Files" + type button + default { "0" } + parmtag { "autoscope" "0000000000000000" } + parmtag { "script_callback_language" "python" } + } + parm { + name "file" + label "File" + type string + default { "" } + parmtag { "script_callback_language" "python" } + } + parm { + name "primpath1" + label "Primitive Root" + type string + default { "`chs(\"folder_path\")`/$OS" } + menureplace { + [ "opmenu -l -a reference_character primpath1" ] + } + parmtag { "autoscope" "0000000000000000" } + parmtag { "script_action" "import loputils\nloputils.selectPrimsInParm(kwargs, False)" } + parmtag { "script_action_help" "Select a primitive in the Scene Viewer or Scene Graph Tree pane.\nCtrl-click to select using the primitive picker dialog.\nAlt-click to toggle movement of the display flag." } + parmtag { "script_action_icon" "BUTTONS_reselect" } + parmtag { "sidefx::usdpathtype" "prim" } + } + groupcollapsible { + name "extra_options" + label "Load Options" + + parm { + name "reftype1" + label "Reference Type" + type string + default { "file" } + menu { + "file" "Reference File" + "payload" "Payload File" + } + parmtag { "autoscope" "0000000000000000" } + parmtag { "script_callback_language" "python" } + } + parm { + name "instanceable1" + label "Make Instanceable" + type toggle + default { "off" } + parmtag { "autoscope" "0000000000000000" } + parmtag { "script_callback_language" "python" } + } + parm { + name "filerefprim1" + label "Reference Primitive" + type string + default { "automaticPrim" } + hidewhen "{ reftype1 == prim } { reftype1 == inherit } { reftype1 == specialize }" + menu { + "automaticPrim" "Reference Automatically Chosen Primitive" + "defaultPrim" "Reference Default Primitive" + "" "Reference Specific Primitive" + } + parmtag { "autoscope" "0000000000000000" } + parmtag { "script_callback_language" "python" } + } + parm { + name "filerefprimpath1" + label "Reference Primitive Path" + type string + default { "" } + disablewhen "{ filerefprim1 != \"\" reftype1 != prim reftype1 != inherit reftype1 != specialize }" + parmtag { "autoscope" "0000000000000000" } + parmtag { "script_action" "import loputils\nnode = kwargs['node']\nparm = kwargs['parmtuple'][0]\nreftype = node.evalParm(parm.name().replace(\n 'filerefprimpath', 'reftype'))\nif reftype in ('prim', 'inherit', 'specialize'):\n prims = loputils.selectPrimsInParm(kwargs, True)\nelse:\n parm = node.parm(parm.name().replace(\n 'filerefprimpath', 'filepath'))\n prims = loputils.selectPrimsInParmFromFile(kwargs, False,\n parm.evalAsString().strip('\\'\"'))" } + parmtag { "script_action_help" "Select a primitive from a primitive picker dialog." } + parmtag { "script_action_icon" "BUTTONS_reselect" } + parmtag { "sidefx::usdpathinput" "if(index(\"prim inherit specialize\", chs(\"reftype#\")) >= 0, 0, -1)" } + parmtag { "sidefx::usdpathtype" "prim" } + } + parm { + name "timeoffset1" + label "Time Offset (in Frames)" + type float + default { "0" } + range { -100 100 } + parmtag { "autoscope" "0000000000000000" } + parmtag { "script_callback_language" "python" } + } + parm { + name "timescale1" + label "Time Scale" + type float + default { "1" } + range { 0 5 } + parmtag { "autoscope" "0000000000000000" } + parmtag { "script_callback_language" "python" } + } + } + + } + + groupcollapsible { + name "info_display2" + label "Info Display" + + parm { + name "show_thumbnail" + label "Show Entity Thumbnail" + type toggle + joinnext + default { "0" } + parmtag { "script_callback" "hou.phm().on_thumbnail_show_changed(kwargs['node'])" } + parmtag { "script_callback_language" "python" } + } + parm { + name "thumbnail_size" + label "Size" + type float + joinnext + default { "2" } + hidewhen "{ show_thumbnail == 0 }" + range { 0 10 } + parmtag { "script_callback" "hou.phm().on_thumbnail_size_changed(kwargs['node'])" } + parmtag { "script_callback_language" "python" } + } + parm { + name "thumbnail_cache_dir" + label "Thumbnail Cache Dir" + type directory + invisible + default { "$JOB/.houdini_loader_thumbnails" } + parmtag { "script_callback_language" "python" } + } + parm { + name "thumbnail_padding" + label "Padding" + type float + invisible + default { "1" } + range { 0 10 } + parmtag { "script_callback_language" "python" } + } + parm { + name "thumbnail_offset" + label "Offset" + type vector2 + size 2 + default { "0" "0.35" } + hidewhen "{ show_thumbnail == 0 }" + range { -1 1 } + parmtag { "script_callback" "hou.phm().on_thumbnail_size_changed(kwargs['node'])" } + parmtag { "script_callback_language" "python" } + } + parm { + name "show_pipeline_parms" + label "Show Pipeline Parms" + type toggle + default { "0" } + parmtag { "script_callback" "hou.phm().on_thumbnail_show_changed(kwargs['node'])" } + parmtag { "script_callback_language" "python" } + } + } + + group { + name "ayon_folder0" + label "Ayon" + hidewhen "{ show_pipeline_parms == 0 }" + + parm { + name "name" + label "Name" + type label + default { "$OS" } + } + parm { + name "namespace" + label "Namespace" + type label + default { "`opfullpath(\".\")`" } + } + parm { + name "loader" + label "Loader" + type label + default { "LOPLoadAssetLoader" } + } + parm { + name "id" + label "ID" + type label + default { "pyblish.avalon.container" } + } + parm { + name "representation" + label "Representation ID" + type string + default { "" } + parmtag { "script_callback" "hou.phm().on_representation_id_changed(kwargs['node'])" } + parmtag { "script_callback_language" "python" } + } + parm { + name "version_name" + label "Current Version Label" + type label + invisible + default { "" } + } + parm { + name "subset_name" + label "Subset (backwards compatibility)" + type label + invisible + default { "`chs(\"product_name\")`" } + } + } + +} diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/ExtraFileOptions b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/ExtraFileOptions new file mode 100644 index 0000000000..fb58b6889f --- /dev/null +++ b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/ExtraFileOptions @@ -0,0 +1,122 @@ +{ + "AYON_icon.png/Cursor":{ + "type":"intarray", + "value":[0,0] + }, + "AYON_icon.png/IsExpr":{ + "type":"bool", + "value":false + }, + "AYON_icon.png/IsPython":{ + "type":"bool", + "value":false + }, + "AYON_icon.png/IsScript":{ + "type":"bool", + "value":false + }, + "AYON_icon.png/Source":{ + "type":"string", + "value":"C:/Users/Maqina-05/Desktop/AYON_icon.png" + }, + "OnCreated/Cursor":{ + "type":"intarray", + "value":[5,29] + }, + "OnCreated/IsExpr":{ + "type":"bool", + "value":false + }, + "OnCreated/IsPython":{ + "type":"bool", + "value":true + }, + "OnCreated/IsScript":{ + "type":"bool", + "value":true + }, + "OnCreated/Source":{ + "type":"string", + "value":"" + }, + "OnDeleted/Cursor":{ + "type":"intarray", + "value":[1,15] + }, + "OnDeleted/IsExpr":{ + "type":"bool", + "value":false + }, + "OnDeleted/IsPython":{ + "type":"bool", + "value":true + }, + "OnDeleted/IsScript":{ + "type":"bool", + "value":true + }, + "OnDeleted/Source":{ + "type":"string", + "value":"" + }, + "OnLoaded/Cursor":{ + "type":"intarray", + "value":[9,76] + }, + "OnLoaded/IsExpr":{ + "type":"bool", + "value":false + }, + "OnLoaded/IsPython":{ + "type":"bool", + "value":true + }, + "OnLoaded/IsScript":{ + "type":"bool", + "value":true + }, + "OnLoaded/Source":{ + "type":"string", + "value":"" + }, + "OnNameChanged/Cursor":{ + "type":"intarray", + "value":[1,15] + }, + "OnNameChanged/IsExpr":{ + "type":"bool", + "value":false + }, + "OnNameChanged/IsPython":{ + "type":"bool", + "value":true + }, + "OnNameChanged/IsScript":{ + "type":"bool", + "value":true + }, + "OnNameChanged/Source":{ + "type":"string", + "value":"" + }, + "PythonModule/Cursor":{ + "type":"intarray", + "value":[10,1] + }, + "PythonModule/IsExpr":{ + "type":"bool", + "value":false + }, + "PythonModule/IsPython":{ + "type":"bool", + "value":true + }, + "PythonModule/IsScript":{ + "type":"bool", + "value":true + }, + "PythonModule/Source":{ + "type":"string", + "value":"" + } +} diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/Help b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/Help new file mode 100644 index 0000000000..e69de29bb2 diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/IconImage b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/IconImage new file mode 100644 index 0000000000..aa0d42021d Binary files /dev/null and b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/IconImage differ diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/InternalFileOptions b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/InternalFileOptions new file mode 100644 index 0000000000..222988aa02 --- /dev/null +++ b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/InternalFileOptions @@ -0,0 +1,10 @@ +{ + "nodeconntype":{ + "type":"bool", + "value":false + }, + "nodeparmtype":{ + "type":"bool", + "value":false + } +} diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/MessageNodes b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/MessageNodes new file mode 100644 index 0000000000..ff389a716b --- /dev/null +++ b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/MessageNodes @@ -0,0 +1 @@ +warn_no_representation_set reference diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/OnCreated b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/OnCreated new file mode 100644 index 0000000000..06ccdaa653 --- /dev/null +++ b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/OnCreated @@ -0,0 +1,6 @@ +node = kwargs["node"] +hda_module = node.hdaModule() +hda_module.setup_flag_changed_callback(node) + +node.parm("product_type").lock(True) +node.parm("file").lock(True) diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/OnDeleted b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/OnDeleted new file mode 100644 index 0000000000..42e4ef2401 --- /dev/null +++ b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/OnDeleted @@ -0,0 +1,6 @@ +from ayon_houdini.api.lib import remove_all_thumbnails + + +# Clear thumbnails +node = kwargs["node"] +remove_all_thumbnails(node) diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/OnLoaded b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/OnLoaded new file mode 100644 index 0000000000..42e3c6e640 --- /dev/null +++ b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/OnLoaded @@ -0,0 +1,14 @@ +node = kwargs["node"] +hda_module = node.hdaModule() +hda_module.setup_flag_changed_callback(node) + + +# Duplicate callback +def on_duplicate(): + """Duplicate thumbnail on node duplicate""" + if node.evalParm("show_thumbnail") and node.evalParm("representation"): + hda_module.on_representation_id_changed(node) + + +if not hou.hipFile.isLoadingHipFile(): + on_duplicate() diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/OnNameChanged b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/OnNameChanged new file mode 100644 index 0000000000..2076f5ad9d --- /dev/null +++ b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/OnNameChanged @@ -0,0 +1,8 @@ +from ayon_houdini.api.hda_utils import ( + keep_background_images_linked +) + + +node = kwargs["node"] +old_name = kwargs["old_name"] +keep_background_images_linked(node, old_name) diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/PythonModule b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/PythonModule new file mode 100644 index 0000000000..934fb2f612 --- /dev/null +++ b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/PythonModule @@ -0,0 +1,10 @@ +from ayon_houdini.api.hda_utils import ( + on_thumbnail_show_changed, + on_thumbnail_size_changed, + on_representation_id_changed, + on_representation_parms_changed, + setup_flag_changed_callback, + get_available_versions, + get_available_products, + set_to_latest_version +) diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/Sections.list b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/Sections.list new file mode 100644 index 0000000000..fe6cf4f0ef --- /dev/null +++ b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/Sections.list @@ -0,0 +1,17 @@ +"" +DialogScript DialogScript +CreateScript CreateScript +InternalFileOptions InternalFileOptions +Contents.gz Contents.gz +TypePropertiesOptions TypePropertiesOptions +Tools.shelf Tools.shelf +Help Help +IconImage IconImage +MessageNodes MessageNodes +PythonModule PythonModule +OnDeleted OnDeleted +OnNameChanged OnNameChanged +OnLoaded OnLoaded +OnCreated OnCreated +ExtraFileOptions ExtraFileOptions +AYON__icon.png AYON_icon.png diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/Tools.shelf b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/Tools.shelf new file mode 100644 index 0000000000..0c76942883 --- /dev/null +++ b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/Tools.shelf @@ -0,0 +1,18 @@ + + + + + + LOP + + + $HDA_TABLE_AND_NAME + + AYON + + + diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/TypePropertiesOptions b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/TypePropertiesOptions new file mode 100644 index 0000000000..a6d52acf2a --- /dev/null +++ b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/TypePropertiesOptions @@ -0,0 +1,14 @@ +CheckExternal := 1; +ContentsCompressionType := 1; +ForbidOutsideParms := 1; +GzipContents := 1; +LockContents := 1; +MakeDefault := 1; +ParmsFromVfl := 0; +PrefixDroppedParmLabel := 0; +PrefixDroppedParmName := 0; +SaveCachedCode := 0; +SaveIcon := 1; +SaveSpareParms := 0; +UnlockOnCreate := 0; +UseDSParms := 1; diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/houdini.hdalibrary b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/houdini.hdalibrary new file mode 100644 index 0000000000..e69de29bb2