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: Publish any ROP node (Generic Creator) #2

Open
wants to merge 24 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
5b7fdfd
Implement Generic ROP publish from https://github.com/ynput/ayon-core…
BigRoy Jul 2, 2024
b5d5e09
Add argument to docstring
BigRoy Jul 3, 2024
421adbf
Merge branch 'develop' into enhancement/houdini_generic_publish
MustafaJafar Jul 5, 2024
a267cd2
Merge branch 'develop' into enhancement/houdini_generic_publish
BigRoy Jul 11, 2024
d6d7e9e
Merge branches 'enhancement/houdini_generic_publish' and 'enhancement…
BigRoy Jul 11, 2024
5b6f0ec
Add publish button
BigRoy Jul 11, 2024
46da010
Add help
BigRoy Jul 11, 2024
83e70d5
Merge branch 'develop' into enhancement/houdini_generic_publish
BigRoy Jul 17, 2024
e74903e
Merge branch 'develop' into enhancement/houdini_generic_publish
MustafaJafar Aug 21, 2024
7e9bbd0
Merge branch 'develop' of https://github.com/ynput/ayon-houdini into …
BigRoy Sep 20, 2024
6934ba6
Fix refactored merge conflict, skip non local renders like "local_no_…
BigRoy Sep 20, 2024
3791df1
Do not error on existing `ayon_self_publish` parm but log a warning
BigRoy Sep 20, 2024
6126888
Cosmetics
BigRoy Sep 20, 2024
c14710c
Elaborate TODO
BigRoy Sep 20, 2024
36c65ad
Merge branch 'develop' into enhancement/houdini_generic_publish
MustafaJafar Sep 23, 2024
5e5cef2
Merge branch 'develop' of https://github.com/ynput/ayon-houdini into …
BigRoy Oct 3, 2024
930615b
Merge branch 'enhancement/houdini_generic_publish' of https://github.…
BigRoy Oct 3, 2024
db57f1e
Cosmetics
BigRoy Oct 3, 2024
21ab75e
Move logic around, so we don't need to import the plugin.
BigRoy Oct 3, 2024
adf9236
Fix 0 and 1 integer values turning into `bool` incorrectly - now only…
BigRoy Oct 3, 2024
ddcf908
Update client/ayon_houdini/api/lib.py
BigRoy Oct 4, 2024
41dfc25
Fix auto-create for LABS Karma node
BigRoy Oct 4, 2024
fae1a89
Add todo
BigRoy Oct 4, 2024
3f70401
Shush ruff linter
BigRoy Oct 4, 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
3 changes: 3 additions & 0 deletions client/ayon_houdini/api/lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,9 @@ def read(node):
except json.JSONDecodeError:
# not a json
pass
elif parameter.parmTemplate().type() == hou.parmTemplateType.Toggle:
BigRoy marked this conversation as resolved.
Show resolved Hide resolved
# Toggles should be returned as boolean instead of int of 1 or 0
value = bool(value)
data[parameter.name()] = value

return data
Expand Down
102 changes: 91 additions & 11 deletions client/ayon_houdini/api/node_wrap.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,100 @@
import dataclasses
from typing import Dict, List, Optional
import hou

from ayon_core.pipeline import registered_host
from ayon_core.pipeline.create import CreateContext


@dataclasses.dataclass
class NodeTypeProductTypes:
"""Product type settings for a node type.

Define the available product types the user can set on a ROP based on
node type.

When 'strict' an enum attribute is created and the user can not type a
custom product type, otherwise a string attribute is
created with a menu right hand side to help pick a type but allow custom
types.
"""
product_types: List[str]
default: Optional[str] = None
strict: bool = True


# Re-usable defaults
GEO_PRODUCT_TYPES = NodeTypeProductTypes(
product_types=["pointcache", "model"],
default="pointcache"
)
FBX_PRODUCT_TYPES = NodeTypeProductTypes(
product_types=["fbx", "pointcache", "model"],
default="fbx"
)
FBX_ONLY_PRODUCT_TYPES = NodeTypeProductTypes(
product_types=["fbx"],
default="fbx"
)
USD_PRODUCT_TYPES = NodeTypeProductTypes(
product_types=["usd", "pointcache"],
default="usd"
)
COMP_PRODUCT_TYPES = NodeTypeProductTypes(
product_types=["imagesequence", "render"],
default="imagesequence"
)
REVIEW_PRODUCT_TYPES = NodeTypeProductTypes(
product_types=["review"],
default="review"
)
RENDER_PRODUCT_TYPES = NodeTypeProductTypes(
product_types=["render", "prerender"],
default="render"
)
GLTF_PRODUCT_TYPES = NodeTypeProductTypes(
product_types=["gltf"],
default="gltf"
)

# TODO: Move this to project settings
NODE_TYPE_PRODUCT_TYPES: Dict[str, NodeTypeProductTypes] = {
"alembic": GEO_PRODUCT_TYPES,
"rop_alembic": GEO_PRODUCT_TYPES,
"geometry": GEO_PRODUCT_TYPES,
"rop_geometry": GEO_PRODUCT_TYPES,
"filmboxfbx": FBX_PRODUCT_TYPES,
"rop_fbx": FBX_PRODUCT_TYPES,
"usd": USD_PRODUCT_TYPES,
"usd_rop": USD_PRODUCT_TYPES,
"usdexport": USD_PRODUCT_TYPES,
"comp": COMP_PRODUCT_TYPES,
"opengl": REVIEW_PRODUCT_TYPES,
"arnold": RENDER_PRODUCT_TYPES,
"karma": RENDER_PRODUCT_TYPES,
"ifd": RENDER_PRODUCT_TYPES,
"usdrender": RENDER_PRODUCT_TYPES,
"usdrender_rop": RENDER_PRODUCT_TYPES,
"vray_renderer": RENDER_PRODUCT_TYPES,
"labs::karma::2.0": RENDER_PRODUCT_TYPES,
"kinefx::rop_fbxanimoutput": FBX_ONLY_PRODUCT_TYPES,
"kinefx::rop_fbxcharacteroutput": FBX_ONLY_PRODUCT_TYPES,
"kinefx::rop_gltfcharacteroutput": GLTF_PRODUCT_TYPES,
"rop_gltf": GLTF_PRODUCT_TYPES
}

NODE_TYPE_PRODUCT_TYPES_DEFAULT = NodeTypeProductTypes(
product_types=list(sorted(
{
"ass", "pointcache", "model", "render", "camera",
"imagesequence", "review", "vdbcache", "fbx"
})),
default="pointcache",
strict=False
)
AUTO_CREATE_NODE_TYPES = set(NODE_TYPE_PRODUCT_TYPES.keys())


def make_publishable(node):
# TODO: Can we make this imprinting much faster? Unfortunately
# CreateContext initialization is very slow.
Expand All @@ -20,17 +111,6 @@ def make_publishable(node):
)


# TODO: Move this choice of automatic 'imprint' to settings so studio can
# configure which nodes should get automatically imprinted on creation
# TODO: Do not import and reload the creator plugin file
from ayon_houdini.plugins.create import create_generic
import importlib
importlib.reload(create_generic)
AUTO_CREATE_NODE_TYPES = set(
create_generic.CreateHoudiniGeneric.node_type_product_types.keys()
)


def autocreate_publishable(node):
# For now only consider RopNode
if not isinstance(node, hou.RopNode):
Expand Down
109 changes: 14 additions & 95 deletions client/ayon_houdini/plugins/create/create_generic.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import dataclasses
from typing import Dict, List, Optional
from typing import Optional, TYPE_CHECKING

from ayon_houdini.api import plugin
from ayon_houdini.api import plugin, node_wrap
from ayon_houdini.api.lib import (
lsattr, read
)
Expand All @@ -11,7 +10,6 @@
)
from ayon_api import get_folder_by_path, get_task_by_name
from ayon_core.lib import (
AbstractAttrDef,
BoolDef,
NumberDef,
EnumDef,
Expand All @@ -24,6 +22,9 @@
import hou
import json

if TYPE_CHECKING:
from ayon_core.lib import AbstractAttrDef # noqa: F401


def attribute_def_to_parm_template(attribute_def, key=None):
"""AYON Attribute Definition to Houdini Parm Template.
Expand Down Expand Up @@ -149,58 +150,6 @@ def set_values(node: "hou.OpNode", values: dict):
parm.set(value)


@dataclasses.dataclass
class NodeTypeProductTypes:
"""Product type settings for a node type.

Define the available product types the user can set on a ROP based on
node type.

When 'strict' an enum attribute is created and the user can not type a
custom product type, otherwise a string attribute is
created with a menu right hand side to help pick a type but allow custom
types.
"""
product_types: List[str]
default: Optional[str] = None
strict: bool = True


# Re-usable defaults
GEO_PRODUCT_TYPES = NodeTypeProductTypes(
product_types=["pointcache", "model"],
default="pointcache"
)
FBX_PRODUCT_TYPES = NodeTypeProductTypes(
product_types=["fbx", "pointcache", "model"],
default="fbx"
)
FBX_ONLY_PRODUCT_TYPES = NodeTypeProductTypes(
product_types=["fbx"],
default="fbx"
)
USD_PRODUCT_TYPES = NodeTypeProductTypes(
product_types=["usd", "pointcache"],
default="usd"
)
COMP_PRODUCT_TYPES = NodeTypeProductTypes(
product_types=["imagesequence", "render"],
default="imagesequence"
)
REVIEW_PRODUCT_TYPES = NodeTypeProductTypes(
product_types=["review"],
default="review"
)
RENDER_PRODUCT_TYPES = NodeTypeProductTypes(
product_types=["render", "prerender"],
default="render"
)
GLTF_PRODUCT_TYPES = NodeTypeProductTypes(
product_types=["gltf"],
default="gltf"
)


class CreateHoudiniGeneric(plugin.HoudiniCreator):
"""Generic creator to ingest arbitrary products"""

Expand All @@ -220,40 +169,8 @@ class CreateHoudiniGeneric(plugin.HoudiniCreator):
default_variants = ["Main", USE_DEFAULT_NODE_NAME]

# TODO: Move this to project settings
node_type_product_types: Dict[str, NodeTypeProductTypes] = {
"alembic": GEO_PRODUCT_TYPES,
"rop_alembic": GEO_PRODUCT_TYPES,
"geometry": GEO_PRODUCT_TYPES,
"rop_geometry": GEO_PRODUCT_TYPES,
"filmboxfbx": FBX_PRODUCT_TYPES,
"rop_fbx": FBX_PRODUCT_TYPES,
"usd": USD_PRODUCT_TYPES,
"usd_rop": USD_PRODUCT_TYPES,
"usdexport": USD_PRODUCT_TYPES,
"comp": COMP_PRODUCT_TYPES,
"opengl": REVIEW_PRODUCT_TYPES,
"arnold": RENDER_PRODUCT_TYPES,
"karma": RENDER_PRODUCT_TYPES,
"ifd": RENDER_PRODUCT_TYPES,
"usdrender": RENDER_PRODUCT_TYPES,
"usdrender_rop": RENDER_PRODUCT_TYPES,
"vray_renderer": RENDER_PRODUCT_TYPES,
"labs::karma::2.0": RENDER_PRODUCT_TYPES,
"kinefx::rop_fbxanimoutput": FBX_ONLY_PRODUCT_TYPES,
"kinefx::rop_fbxcharacteroutput": FBX_ONLY_PRODUCT_TYPES,
"kinefx::rop_gltfcharacteroutput": GLTF_PRODUCT_TYPES,
"rop_gltf": GLTF_PRODUCT_TYPES
}

node_type_product_types_default = NodeTypeProductTypes(
product_types=list(sorted(
{
"ass", "pointcache", "model", "render", "camera",
"imagesequence", "review", "vdbcache", "fbx"
})),
default="pointcache",
strict=False
)
node_type_product_types = node_wrap.NODE_TYPE_PRODUCT_TYPES
node_type_product_types_default = node_wrap.NODE_TYPE_PRODUCT_TYPES_DEFAULT

def get_detail_description(self):
return "Publish any ROP node."
Expand Down Expand Up @@ -363,10 +280,12 @@ def collect_instances(self):
creator_attributes = {}
for key, value in dict(node_data).items():
if key.startswith("publish_attributes_"):
Copy link
Member

@iLLiCiTiT iLLiCiTiT Sep 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why it's not stored as json string? (talking about "publish_attributes" and "creator_attributes").

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So that the attributes are actually accessible as options on the node for the user to click. We want them to be like native houdini parms so houdini users can do with them using houdini logic whatever they want - that's the whole point. ;)

Copy link
Member

@iLLiCiTiT iLLiCiTiT Sep 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So "handleStart" = 0 will become "handleStart": True (as an example).

BTW considering in progress feature on create context: "Value change based callbacks to update attribute definitions or values." will break this assumption that you can freely change create and publish attributes with no consequences.

Copy link
Contributor Author

@BigRoy BigRoy Sep 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does exposing these publisher UI attributes on the node actually make sense?

BTW considering in progress feature on create context: "Value change based callbacks to update attribute definitions or values." will break this assumption that you can freely change create and publish attributes with no consequences.

Yes, makes sense. There's just a plethora of reasons why one WOULD want to actually exploit this type of full control in Houdini but I'm not confident whether those are production use cases that really make sense over time.

@fabiaserra @krishnaavril @MustafaJafar how much use would you say there is in actually having all these publisher UI options exposed as separate Houdini parms? Like, would you really ever have those parms reference some other attribute in a production scenario? Or is setting those via the publisher UI sufficient (it would allow us to create more dynamic UI over time without 'unexpected changes' coming back from Houdini).

See here for a screenshot with some of those exposed attributes.

In this case in particular the Creator Attributes and Publish Attributes

image


So "handleStart" = 0 will become "handleStart": True (as an example).

Luckily those are not exposed like that in Houdini 🙈 - but yes, that definitely points to a crucial bug that would need solving. However, 'setting' them om the attribute definitions in publisher UI themselves would then automatically turn that back into 0 and 1 so it may be 'ok' but it's definitely illogical code. Thanks for point it out!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand the questions but let me try explain why:

how much use would you say there is in actually having all these publisher UI options exposed as separate Houdini parms?

What do you mean by "separate"? If you just mean if there's a point on having these options exposed on the Houdini nodes natively... yeah, there's a lot of use, it allows artists to use Houdini workflows and not be limited by another UI, making it very easy entry point for Houdini users (the advanced options are probably confusing and irrelevant for anyone though?)

Like, would you really ever have those parms reference some other attribute in a production scenario?

Potentially, that's the beauty of Houdini

Or is setting those via the publisher UI sufficient

Not as it is, but not because just of the UI setting parameters... the whole publish framework being coupled with the export of the nodes is very limited IMO as I have already explained a few times

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additionally, the node parameters and the publisher attributes are synced.
From the node to the publisher and from the publisher to the node.
That logic is implemented in collect_instances by creating the instance manually and adding it to the context and update_instances by the imprint method.

Copy link
Contributor

@krishnaavril krishnaavril Sep 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does exposing these publisher UI attributes on the node actually make sense?

BTW considering in progress feature on create context: "Value change based callbacks to update attribute definitions or values." will break this assumption that you can freely change create and publish attributes with no consequences.

Yes, makes sense. There's just a plethora of reasons why one WOULD want to actually exploit this type of full control in Houdini but I'm not confident whether those are production use cases that really make sense over time.

@fabiaserra @krishnaavril @MustafaJafar how much use would you say there is in actually having all these publisher UI options exposed as separate Houdini parms? Like, would you really ever have those parms reference some other attribute in a production scenario? Or is setting those via the publisher UI sufficient (it would allow us to create more dynamic UI over time without 'unexpected changes' coming back from Houdini).

See here for a screenshot with some of those exposed attributes.

In this case in particular the Creator Attributes and Publish Attributes

image

So "handleStart" = 0 will become "handleStart": True (as an example).

Luckily those are not exposed like that in Houdini 🙈 - but yes, that definitely points to a crucial bug that would need solving. However, 'setting' them om the attribute definitions in publisher UI themselves would then automatically turn that back into 0 and 1 so it may be 'ok' but it's definitely illogical code. Thanks for point it out!

Does exposing these publisher UI attributes on the node actually make sense?

BTW considering in progress feature on create context: "Value change based callbacks to update attribute definitions or values." will break this assumption that you can freely change create and publish attributes with no consequences.

Yes, makes sense. There's just a plethora of reasons why one WOULD want to actually exploit this type of full control in Houdini but I'm not confident whether those are production use cases that really make sense over time.

@fabiaserra @krishnaavril @MustafaJafar how much use would you say there is in actually having all these publisher UI options exposed as separate Houdini parms? Like, would you really ever have those parms reference some other attribute in a production scenario? Or is setting those via the publisher UI sufficient (it would allow us to create more dynamic UI over time without 'unexpected changes' coming back from Houdini).

See here for a screenshot with some of those exposed attributes.

In this case in particular the Creator Attributes and Publish Attributes

image

So "handleStart" = 0 will become "handleStart": True (as an example).

Luckily those are not exposed like that in Houdini 🙈 - but yes, that definitely points to a crucial bug that would need solving. However, 'setting' them om the attribute definitions in publisher UI themselves would then automatically turn that back into 0 and 1 so it may be 'ok' but it's definitely illogical code. Thanks for point it out!

Hello @BigRoy ! I think it was completly fine having properties in ayon publisher window, Honestly Me and my teams uses only visualise purpose, there is no change scenario seen in production from our end. (I was writing this coz we tested this rop publish when houdini is in core)

Can we write a logic where ayon filters the rop node inside any studio specific otl?
This will help to identify what kind of files will be writting(pointcache/renders) by node or by the location of node we adding(obj -> pointcache/usd, stage-> Usd/USD render rop), this will help on creating generic for any otl(without we hardcoded in the script), this should essentially open the possiblity of adding generic for any custom otl with no code changes(or a ayon frontend parameter will be good too, where we mention our node names and parameters). we also know studio uses there own otls for render, we can put in the documentation that to add duplicate there parameter and name it as a picture parameter, thus it refers the path and publishes.

Let me know if this make sense.

if value == 0 or value == 1:
value = bool(value)
plugin_name, plugin_key = key[len("publish_attributes_"):].split("_", 1)
publish_attributes.setdefault(plugin_name, {})[plugin_key] = value
# TODO: Technically this isn't entirely safe. We are
# splitting after the first `_` after
# `publish_attributes_` with the assumption that the
# plug-in class name never contains an underscore.
plugin_name, plugin_key = key[len("publish_attributes_"):].split("_", 1) # noqa: E501
publish_attributes.setdefault(plugin_name, {})[plugin_key] = value # noqa: E501
del node_data[key] # remove from original
elif key.startswith("creator_attributes_"):
creator_key = key[len("creator_attributes_"):]
Expand Down Expand Up @@ -539,7 +458,7 @@ def create_attribute_def_parms(self,
publish_attributes_folder.addParmTemplate(parm_template)

# Define the product types picker options
node_type_product_types: NodeTypeProductTypes = (
node_type_product_types: node_wrap.NodeTypeProductTypes = (
self.node_type_product_types.get(
node.type().name(), self.node_type_product_types_default
))
Expand Down
2 changes: 1 addition & 1 deletion client/ayon_houdini/startup/scripts/OnCreated.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@


if env_value_to_bool("AYON_HOUDINI_AUTOCREATE", default=True):
node = kwargs["node"]
node = kwargs["node"] # noqa: F821
node_wrap.autocreate_publishable(node)
Loading