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

Houdini: Implement Load Asset LOP HDA #294

Merged
merged 70 commits into from
Jun 28, 2024
Merged
Show file tree
Hide file tree
Changes from 55 commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
352cbf8
Implement Houdini Load Asset LOP HDA
BigRoy Mar 27, 2024
6f2e40d
Remove print statement within hda
BigRoy Mar 27, 2024
454d299
Merge branch 'develop' into enhancement/houdini_load_asset_lop
BigRoy Mar 27, 2024
089284f
Fix refactor to AYON on Loader Plugin
BigRoy Mar 28, 2024
3c5b033
Merge branch 'develop' into enhancement/houdini_load_asset_lop
BigRoy Mar 28, 2024
a35b9cc
Cosmetics
BigRoy Mar 28, 2024
b361a1c
Merge branch 'enhancement/houdini_load_asset_lop' of https://github.c…
BigRoy Mar 28, 2024
6948d0c
changed Exception to ayon_api.exceptions.GraphQlQueryFailed in def se…
lyon040502003 Apr 1, 2024
2c52f93
Apply suggestions from code review
BigRoy Apr 15, 2024
54d00c7
Raise errors instead of assert
BigRoy Apr 15, 2024
3d1a5d1
Merge branch 'enhancement/houdini_load_asset_lop' of https://github.c…
BigRoy Apr 15, 2024
0e1f36e
Move imports
BigRoy Apr 15, 2024
ce4a4e4
Merge branch 'develop' of https://github.com/ynput/ayon-core into enh…
BigRoy Apr 15, 2024
bef6734
More explicit UUID check for representation entity to avoid shadowing…
BigRoy Apr 15, 2024
49a8ef4
Tweak comment wording
BigRoy Apr 15, 2024
e29640a
Merge branch 'develop' into enhancement/houdini_load_asset_lop
MustafaJafar Apr 25, 2024
644e458
Apply suggestions from code review by iLliCiTiT
BigRoy May 6, 2024
b2c74e0
Merge branch 'develop' into enhancement/houdini_load_asset_lop
BigRoy May 6, 2024
23ce776
Set `fields` explicitly per call as `{"id"}`
BigRoy May 6, 2024
ffb9426
Update client/ayon_core/hosts/houdini/api/hda_utils.py
BigRoy May 6, 2024
16476e4
Merge branch 'enhancement/houdini_load_asset_lop' of https://github.c…
BigRoy May 6, 2024
bb70229
Fix grammar
BigRoy May 6, 2024
c2d1470
Merge branch 'develop' of https://github.com/ynput/ayon-core into enh…
BigRoy Jun 3, 2024
cdb6bc7
Fix refactoring of imports
BigRoy Jun 3, 2024
eeb3839
Bumped addon version
BigRoy Jun 3, 2024
7e6ecc8
Move HDA
BigRoy Jun 3, 2024
ef5048d
Revert changes to `addon.py`
BigRoy Jun 3, 2024
05c5a42
Refactor `ayon_core.hosts.houdini` usage in HDA to `ayon_houdini`
BigRoy Jun 3, 2024
370974b
Merge branch 'develop' of https://github.com/ynput/ayon-core into enh…
BigRoy Jun 5, 2024
8aee5f1
Swap ayon_lop_import.hda with expanded otl
BigRoy Jun 5, 2024
e33c2c7
Add AYON to HDA label
BigRoy Jun 5, 2024
f6e9738
add two more functions
MustafaJafar Jun 6, 2024
9f73f59
add ayon::lop_import::1.1
MustafaJafar Jun 6, 2024
8e5e7d5
Fix tool submenu name to `AYON`
BigRoy Jun 6, 2024
73a9833
update doc strings
MustafaJafar Jun 10, 2024
c8f10a9
update the original hda instead of adding changes as new version
MustafaJafar Jun 10, 2024
56c02d5
remove products filter
MustafaJafar Jun 10, 2024
5bab335
fix hda version
MustafaJafar Jun 10, 2024
5529beb
Merge pull request #9 from ynput/enhancement/houdini_load_asset_lop_e…
BigRoy Jun 10, 2024
c811190
Merge branch 'develop' of https://github.com/ynput/ayon-core into enh…
BigRoy Jun 10, 2024
8581edd
Bump ayon houdini addon version
BigRoy Jun 10, 2024
b15f454
Fix line lengths + style
BigRoy Jun 10, 2024
f238104
Fix logic
BigRoy Jun 10, 2024
e0334bc
Use `SimpleFoldersWidget`
BigRoy Jun 10, 2024
d64c5ad
Add todo
BigRoy Jun 10, 2024
31f55fb
Update server_addon/houdini/client/ayon_houdini/api/hda_utils.py
BigRoy Jun 10, 2024
d332dd2
Only set folder path if user accepted the dialog
BigRoy Jun 10, 2024
16d7575
Merge branch 'enhancement/houdini_load_asset_lop' of https://github.c…
BigRoy Jun 10, 2024
00ccc87
Remove todo
BigRoy Jun 10, 2024
9dac041
Merge branch 'develop' into enhancement/houdini_load_asset_lop
BigRoy Jun 20, 2024
10410f2
Merge branch 'develop' of https://github.com/ynput/ayon-core into enh…
BigRoy Jun 27, 2024
20afb6b
Merge branch 'bugfix/folder_widget_set_selected_folder_path' into enh…
BigRoy Jun 27, 2024
7f3194f
Implement Pick Project and Folder Path dialog
BigRoy Jun 27, 2024
e69c075
Cosmetics + do not default to setting current project
BigRoy Jun 27, 2024
406fd80
Merge branch 'develop' of https://github.com/ynput/ayon-core into enh…
BigRoy Jun 27, 2024
791707b
add products drop down menu
MustafaJafar Jun 27, 2024
f756064
Update server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop…
BigRoy Jun 27, 2024
e5d684a
Merge pull request #11 from ynput/enhancement/houdini_load_asset_lop_…
BigRoy Jun 27, 2024
eb24c36
Cosmetics
BigRoy Jun 27, 2024
4a53f46
Add note about the sorting
BigRoy Jun 27, 2024
44289ce
Cosmetics
BigRoy Jun 27, 2024
c086a01
Merge branch 'develop' of https://github.com/ynput/ayon-core into enh…
BigRoy Jun 27, 2024
5e009db
Remove unused select folder logic
BigRoy Jun 27, 2024
d902593
Remove unused imports
BigRoy Jun 27, 2024
5c4b466
Typing
BigRoy Jun 27, 2024
159ee22
Update server_addon/houdini/client/ayon_houdini/api/pipeline.py
BigRoy Jun 28, 2024
926a32a
Refactor class to match more with the function name + `asset` is Open…
BigRoy Jun 28, 2024
3e5a9fd
Match filter placeholder text with Loader UI
BigRoy Jun 28, 2024
afedffc
Simplify window title
BigRoy Jun 28, 2024
cdadaf6
Merge branch 'develop' into enhancement/houdini_load_asset_lop
BigRoy Jun 28, 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
544 changes: 544 additions & 0 deletions server_addon/houdini/client/ayon_houdini/api/hda_utils.py

Large diffs are not rendered by default.

225 changes: 225 additions & 0 deletions server_addon/houdini/client/ayon_houdini/api/lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
24 changes: 23 additions & 1 deletion server_addon/houdini/client/ayon_houdini/api/pipeline.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-
"""Pipeline tools for OpenPype Houdini integration."""
import os
import six
BigRoy marked this conversation as resolved.
Show resolved Hide resolved
import json
import logging

import hou # noqa
Expand All @@ -26,6 +28,8 @@
env_value_to_bool,
)

from .lib import JSON_PREFIX


log = logging.getLogger("ayon_houdini")

Expand Down Expand Up @@ -258,7 +262,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")
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"hdaroot/warn_no_representation_set.def":1708980551,
"hdaroot/reference.def":1698150558,
"hdaroot/output0.def":1698215383,
"hdaroot.def":1717451587
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"values":["20.0.703"
],
"indexes":{
"hdaroot/warn_no_representation_set.userdata":0,
"hdaroot/reference.userdata":0,
"hdaroot/output0.userdata":0
}
}
Loading
Loading