Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generic Loader OTLs #121

Merged
merged 37 commits into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
02d3e41
add generic loader HDAs
MustafaJafar Oct 14, 2024
6dcc9cf
Implement `GenericLoader`
MustafaJafar Oct 14, 2024
c910b8e
Add logic to generic loader otls and extend `hda_utils` functions
MustafaJafar Oct 14, 2024
3e32e96
make find_active_network more generic
MustafaJafar Oct 14, 2024
0450291
Merge branch 'develop' into enhancement/generic_load_test
MustafaJafar Oct 17, 2024
4dc818c
support frames and UDIM token
MustafaJafar Oct 17, 2024
60e0a43
adjust the order of generic loader
MustafaJafar Oct 17, 2024
bccc305
import the functions instead of access them through `hou.phm()`
MustafaJafar Oct 17, 2024
b75c9bb
add `ayon_load` button on create to file and alembic nodes
MustafaJafar Oct 18, 2024
f853386
Show nodes referencing the file parm in generic loader nodes
MustafaJafar Oct 18, 2024
ce9b4df
Merge branch 'develop' into enhancement/generic_load_test
BigRoy Oct 20, 2024
f7d719c
revert `bccc305d952993e55e69176d613a50cb28540181` - access functions …
MustafaJafar Oct 21, 2024
baa2082
replace list of `operator path`s with `operator list`
MustafaJafar Oct 21, 2024
fef855c
add `Load with AYON` action to `PARMmenu` and remove ayon load button
MustafaJafar Oct 23, 2024
bace635
Make `GenericLoader` create the node by default in the avalon container
MustafaJafar Oct 23, 2024
92bb97f
remove unused import and re-arrange imports
MustafaJafar Oct 23, 2024
02f687e
show parameter panel for the generic loader
MustafaJafar Oct 29, 2024
f006874
use a simple hard coded template for the name of the created generic …
MustafaJafar Oct 29, 2024
51f0899
Re-use Load Filepath to Node loader
BigRoy Oct 30, 2024
7a1ed8d
Merge branch 'enhancement/generic_load_test' of https://github.com/yn…
BigRoy Oct 30, 2024
3aa4dc4
Fix typo
BigRoy Oct 30, 2024
5ed1357
show parm panel when using `FilePathLoader`
MustafaJafar Oct 30, 2024
9cda781
Merge branch 'enhancement/generic_load_test' of https://github.com/yn…
BigRoy Oct 30, 2024
e11c12e
refactor `show_generic_loader_parmpanel` to ` show_node_parmeditor`
MustafaJafar Oct 30, 2024
314e04a
set the parameter editor to the node path without selecting the node
MustafaJafar Oct 30, 2024
53eff90
Apply suggestions from code review
BigRoy Oct 30, 2024
552116c
Merge branch 'enhancement/generic_load_test' of https://github.com/yn…
BigRoy Oct 30, 2024
e2479c4
Tweak readability
BigRoy Oct 30, 2024
699c97b
Revert changes
BigRoy Oct 30, 2024
6088dca
Remove unused import
BigRoy Oct 30, 2024
08540c0
Cosmetics
BigRoy Oct 30, 2024
02f26a2
Merge branch 'develop' of https://github.com/ynput/ayon-houdini into …
BigRoy Oct 31, 2024
5dcbc56
Update HDAs after refactoring
BigRoy Oct 31, 2024
941f775
Fix missing imports
BigRoy Oct 31, 2024
e95a9c7
Remove callback
BigRoy Oct 31, 2024
c31d90e
Fix loader plugin
BigRoy Oct 31, 2024
1b06f3d
Fix bug on thumbnails for `ObjNode`
BigRoy Oct 31, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 118 additions & 5 deletions client/ayon_houdini/api/hda_utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Heper functions for load HDA"""

import os
import re
import uuid
from typing import List

Expand All @@ -15,8 +16,11 @@
get_folder_by_path,
get_product_by_name,
get_version_by_name,
get_representation_by_name
get_representation_by_name,
get_representations
)
from ayon_core.pipeline import Anatomy
from ayon_core.lib import StringTemplate
from ayon_core.pipeline.context_tools import (
get_current_project_name,
get_current_folder_path
Expand Down Expand Up @@ -145,14 +149,63 @@ def get_representation_path(
if use_ayon_entity_uri:
path = get_ayon_entity_uri_from_representation_context(context)
else:
path = get_representation_path_from_context(context)
path = _get_filepath_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("\\", "/")
return path


def _remove_format_spec(template: str, key: str) -> str:
"""Remove format specifier from a format token in formatting string.
For example, change `{frame:0>4d}` into `{frame}`
Examples:
>>> remove_format_spec("{frame:0>4d}", "frame")
'{frame}'
>>> remove_format_spec("{digit:04d}/{frame:0>4d}", "frame")
'{digit:04d}/{udim}_{frame}'
>>> remove_format_spec("{a: >4}/{aa: >4}", "a")
'{a}/{aa: >4}'
"""
# Find all {key:foobar} and remove the `:foobar`
# Pattern will be like `({key):[^}]+(})` where we use the captured groups
# to keep those parts in the resulting string
pattern = f"({{{key}):[^}}]+(}})"
return re.sub(pattern, r"\1\2", template)


def _get_filepath_from_context(context: dict):
"""Format file path for sequence with $F or <UDIM>."""
# The path is either a single file or sequence in a folder.
# Format frame as $F and udim as <UDIM>
representation = context["representation"]
frame = representation["context"].get("frame")
udim = representation["context"].get("udim")
if frame is not None or udim is not None:
template: str = representation["attrib"]["template"]
repre_context: dict = representation["context"]
if udim is not None:
repre_context["udim"] = "<UDIM>"
template = _remove_format_spec(template, "udim")
if frame is not None:
# Substitute frame number in sequence with $F with padding
repre_context["frame"] = "$F{}".format(len(frame)) # e.g. $F4
template = _remove_format_spec(template, "frame")

project_name: str = repre_context["project"]["name"]
anatomy = Anatomy(project_name, project_entity=context["project"])
repre_context["root"] = anatomy.roots
path = StringTemplate(template).format(repre_context)
else:
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
return os.path.normpath(path).replace("\\", "/")


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))
Expand Down Expand Up @@ -357,7 +410,10 @@ def on_flag_changed(node, **kwargs):
if not images:
return

brightness = 0.3 if node.isBypassed() else 1.0
# This may trigger on a node that can't be bypassed, like `ObjNode` so
# consider those never bypassed
is_bypassed = hasattr(node, "isBypassed") and node.isBypassed()
brightness = 0.3 if is_bypassed else 1.0
has_changes = False
node_path = node.path()
for image in images:
Expand Down Expand Up @@ -653,11 +709,64 @@ def select_product_name(node):
product_parm.pressButton() # allow any callbacks to trigger


def get_available_representations(node):
"""Return the representation list for node.

Args:
node (hou.Node): Node to query selected version's representations for.

Returns:
list[str]: representation names for the product version.
"""

project_name = node.evalParm("project_name") or get_current_project_name()
folder_path = node.evalParm("folder_path")
product_name = node.evalParm("product_name")
version = node.evalParm("version")

if not all([
project_name, folder_path, product_name, version
]):
return []

try:
version = int(version.strip())
except ValueError:
load_message_parm = node.parm("load_message")
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"}
)
product_entity = get_product_by_name(
project_name,
product_name=product_name,
folder_id=folder_entity["id"],
fields={"id"})
version_entity = get_version_by_name(
project_name,
version,
product_id=product_entity["id"],
fields={"id"})
representations = get_representations(
project_name,
version_ids={version_entity["id"]},
fields={"name"}
)
representations_names = [n["name"] for n in representations]
return representations_names


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.
Refresh version and representation parameters value by setting
their value to the latest version and representation of
the selected product.

Args:
node (hou.OpNode): The HDA node.
Expand All @@ -667,6 +776,10 @@ def set_to_latest_version(node):
if versions:
node.parm("version").set(str(versions[0]))

representations = get_available_representations(node)
if representations:
node.parm("representation_name").set(representations[0])


# region Parm Expressions
# Callbacks used for expression on HDAs (e.g. Load Asset or Load Shot LOP)
Expand Down
70 changes: 67 additions & 3 deletions client/ayon_houdini/api/lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import six
import ayon_api

import hou

from ayon_core.lib import StringTemplate
from ayon_core.settings import get_current_project_settings
from ayon_core.pipeline import (
Expand All @@ -27,8 +29,6 @@
from ayon_core.tools.utils import PopupUpdateKeys, SimplePopup
from ayon_core.tools.utils.host_tools import get_tool_by_name

import hou


self = sys.modules[__name__]
self._parent = None
Expand Down Expand Up @@ -1400,7 +1400,7 @@ def find_active_network(category, default):
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.
is found with the given category, e.g. "/obj"

Returns:
hou.Node: The node network to return.
Expand Down Expand Up @@ -1521,3 +1521,67 @@ def start_workfile_template_builder():
build_workfile_template(workfile_creation_enabled=True)
except TemplateProfileNotFound:
log.warning("Template profile not found. Skipping...")


def show_node_parmeditor(node):
"""Show Parameter Editor for the Node.

Args:
node (hou.Node): node instance
"""

# Check if there's a floating parameter editor pane with its node set to the specified node.
for tab in hou.ui.paneTabs():
if (
tab.type() == hou.paneTabType.Parm
and tab.isFloating()
and tab.currentNode() == node
):
tab.setIsCurrentTab()
return

# We are using the hscript to create and set the network path of the pane
# because hscript can set the node path without selecting the node.
# Create a floating pane and set its name to the node path.
hou.hscript(
f"pane -F -m parmeditor -n {node.path()}"
)
# Hide network controls, turn linking off and set operator node path.
hou.hscript(
f"pane -a 1 -l 0 -H {node.path()} {node.path()}"
)


def connect_file_parm_to_loader(file_parm: hou.Parm):
"""Connect the given file parm to a generic loader.
If the parm is already connected to a generic loader node, go to that node.
"""

from .pipeline import get_or_create_avalon_container

referenced_parm = file_parm.getReferencedParm()

# If the parm has reference
if file_parm != referenced_parm:
referenced_node = referenced_parm.getReferencedParm().node()
if referenced_node.type().name() == "ayon::generic_loader::1.0":
show_node_parmeditor(referenced_node)
return

# Create a generic loader node and reference its file parm
main_container = get_or_create_avalon_container()

node_name = f"{file_parm.node().name()}_{file_parm.name()}_loader"
load_node = main_container.createNode("ayon::generic_loader",
node_name=node_name)
load_node.moveToGoodPosition()

# Set relative reference via hscript. This avoids the issues of
# `setExpression` e.g. having a keyframe.
relative_path = file_parm.node().relativePathTo(load_node)
expression = rf'chs\(\"{relative_path}/file\"\)' # noqa
hou.hscript(
'opparm -r'
f' {file_parm.node().path()} {file_parm.name()} \`{expression}\`'
)
show_node_parmeditor(load_node)
Loading