Merge pull request #294 from BigRoy/enhancement/houdini_load_asset_lop

Houdini: Implement Load Asset LOP HDA
This commit is contained in:
Mustafa Taher 2024-06-28 18:35:10 +03:00 committed by GitHub
commit f87bd0871b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 1902 additions and 119 deletions

View file

@ -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]))

View file

@ -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()

View file

@ -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")

View file

@ -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.

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,6 @@
{
"hdaroot/warn_no_representation_set.def":1708980551,
"hdaroot/reference.def":1698150558,
"hdaroot/output0.def":1698215383,
"hdaroot.def":1717451587
}

View file

@ -0,0 +1,9 @@
{
"values":["20.0.703"
],
"indexes":{
"hdaroot/warn_no_representation_set.userdata":0,
"hdaroot/reference.userdata":0,
"hdaroot/output0.userdata":0
}
}

View file

@ -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--

View file

@ -0,0 +1,6 @@
{
"hdaroot/warn_no_representation_set.def":1711565807,
"hdaroot/reference.def":1711565598,
"hdaroot/output0.def":1708980807,
"hdaroot.def":1717451686
}

View file

@ -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

View file

@ -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\")`" }
}
}
}

View file

@ -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":""
}
}

View file

@ -0,0 +1,10 @@
{
"nodeconntype":{
"type":"bool",
"value":false
},
"nodeparmtype":{
"type":"bool",
"value":false
}
}

View file

@ -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)

View file

@ -0,0 +1,6 @@
from ayon_houdini.api.lib import remove_all_thumbnails
# Clear thumbnails
node = kwargs["node"]
remove_all_thumbnails(node)

View file

@ -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()

View file

@ -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)

View file

@ -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
)

View file

@ -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

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<shelfDocument>
<!-- This file contains definitions of shelves, toolbars, and tools.
It should not be hand-edited when it is being used by the application.
Note, that two definitions of the same element are not allowed in
a single file. -->
<tool name="$HDA_DEFAULT_TOOL" label="$HDA_LABEL" icon="$HDA_ICON">
<toolMenuContext name="viewer">
<contextNetType>LOP</contextNetType>
</toolMenuContext>
<toolMenuContext name="network">
<contextOpType>$HDA_TABLE_AND_NAME</contextOpType>
</toolMenuContext>
<toolSubmenu>AYON</toolSubmenu>
<script scriptType="python"><![CDATA[import loptoolutils
loptoolutils.genericTool(kwargs, '$HDA_NAME')]]></script>
</tool>
</shelfDocument>

View file

@ -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;