Skip to content
This repository has been archived by the owner on Sep 20, 2024. It is now read-only.

Enhancement: Houdini use instance transient data for instance_node #5616

Conversation

BigRoy
Copy link
Collaborator

@BigRoy BigRoy commented Sep 13, 2023

Changelog Description

Use transient data to store the actual houdini node object in the instance as opposed to the path so hou.node() isn't needed to retrieve the node per plugin.

Also removed:

  • Instance toggled callback which does nothing with new publisher
  • Remove legacy collect instances plugin which is not used for new publisher

Additional notes

👇 Please consider this question

I wasn't sure whether I should open this as a Draft PR or not. I'm not sure whether everyone agrees this is a design improvement or whether it's worse. Thoughts?

☝️

Testing notes:

  1. Use houdini to create and publish all families

@BigRoy BigRoy added host: Houdini type: refactor Structural changes not affecting functionality community contribution labels Sep 13, 2023
@@ -56,7 +56,7 @@ def process(self, instance):
layer_inst.data["subset"] = "__stub__"
layer_inst.data["label"] = label
layer_inst.data["asset"] = instance.data["asset"]
layer_inst.data["instance_node"] = instance.data["instance_node"]
layer_inst.data["instance_node"] = instance.data["transientData"]["instance_node"]
Copy link

Choose a reason for hiding this comment

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

line too long (94 > 79 characters)

Copy link
Member

Choose a reason for hiding this comment

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

instance_node = instance.data["transientData"]["instance_node"]
for layer in usdlib.get_configured_save_layers(rop_node):
    ...
    layer_inst.data["instance_node"] = instance_node
    ...

@@ -38,7 +38,7 @@ def process(self, instance):
validate_nodes = []

if len(instance) > 0:
validate_nodes.append(hou.node(instance.data.get("instance_node")))
validate_nodes.append(instance.data["transientData"]["instance_node"])
Copy link

Choose a reason for hiding this comment

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

line too long (82 > 79 characters)

Copy link
Member

Choose a reason for hiding this comment

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

I think this should be:

instance_node = instance.data["transientData"].get("instance_node")
if instance_node is not None:
    validate_nodes.append(instance_node)

@@ -98,7 +98,7 @@ def process(self, instance):
"Invalid VDB content: {}".format(message),
formatting_data={
"message": message,
"rop_path": instance.data.get("instance_node"),
"rop_path": instance.data["transientData"]["instance_node"].path(),
Copy link

Choose a reason for hiding this comment

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

line too long (87 > 79 characters)

Copy link
Member

@iLLiCiTiT iLLiCiTiT Dec 21, 2023

Choose a reason for hiding this comment

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

                instance_node = instance.data["transientData"]["instance_node"]
                formatting_data={
                    "message": message,
                    "rop_path": instance_node.path(),
                    "sop_path": output_path
                }

@ynbot ynbot added type: enhancement Enhancements to existing functionality size/S Denotes a PR changes 100-499 lines, ignoring general files labels Sep 13, 2023
@MustafaJafar
Copy link
Contributor

Will this PR be still useful if #5490 is merged ?

Copy link
Contributor

@MustafaJafar MustafaJafar left a comment

Choose a reason for hiding this comment

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

Tested as a User these product types work fine

  • camer
  • imsage sequence
  • ABC point cache
  • BGEO point cache
  • VDB cache
  • Review

image

These families (that submits to farm) error

  • Mantra
  • Karma

image

After applying this fix, it worked

driver = hou.node(instance.data["instance_node"])

        driver = instance.data["transientData"]["instance_node"]

image

@MustafaJafar
Copy link
Contributor

I was Misled and thought that instance.data["instance_node"] is no longer exists after merging Use host's node uniqueness for instance id in new publisher #5490, but I found out that it exists because of the following line:

instance.data["instance_node"] = node.path()


So, This PR

  1. Save instnace_node in instance.data["transientData"]["instance_node"] instead of instance.data["instance_node"]
  2. removes collect_instances.py

where transientData are saved ? and how instance_node value is maintained/updated ?

@BigRoy
Copy link
Collaborator Author

BigRoy commented Oct 5, 2023

The idea is that instance.data["instance_node"] should indeed not exist anymore since it's now transient data. That CollectInstances should've been removed some time ago :)

where transientData are saved ? and how instance_node value is maintained/updated ?

How do you mean? The Creators set it. They don't need to be 'maintained/updated' because it always represents the physical hou.Node that the Creator collects.

Save instnace_node in instance.data["transientData"]["instance_node"] instead of instance.data["instance_node"]

Yes, I mean. Not by choice really ;) I'd prefer it instance.data because it's shorter but it's by design for transient data in the new publisher.

@MustafaJafar
Copy link
Contributor

MustafaJafar commented Oct 5, 2023

Let me rephrase my questions:

@BigRoy
Copy link
Collaborator Author

BigRoy commented Oct 5, 2023

Transient Data

Transient Data was once added because all instance data generated by the Creator had to be json serializable - the hou.Node is not and transient data refers to the fact that it's runtime data that is really only valid for the current runtime session. You can't use that object instance after say, closing down houdini and opening it again.

It got added with this PR: #3897 where at first it was referred to as lifetime data but later got renamed to transient data.

It seems that Transient Data unfortunately was never added to the documentation: https://openpype.io/docs/dev_publishing - probably best to create an issue about that.


what will be instance.transient_data["instance_node"] value when the rop is duplicated ? in the light of the recently merged PR #5490

It will still refer to the ROP node, but by hou.Node instead of the node's string path. So it refers to the python hou.Node instance as opposed to a string path value.

It's likely however that some conflicts need to be resolved since that PR - but before anyone would do so I'd rather first know whether this PR is even remotely worth the effort and whether it's a good design choice.

As a reference, Fusion also uses the transientData and that's what it was initially added for so you knew exactly what object you were referring to since just a node name/path there wasn't sufficient since Fusion allows to have multiple workfiles open at the same time in a single session. 🤯

@MustafaJafar
Copy link
Contributor

MustafaJafar commented Oct 5, 2023

It seems that Transient Data unfortunately was never added to the documentation: https://openpype.io/docs/dev_publishing - probably best to create an issue about that.

yeah, I will add that to the community guide AYON / Openpype Publish process - Development guide

When I started learning about Openpype 4 or 5 months ago, I didn't expect all these details!
and I love that!

@MustafaJafar
Copy link
Contributor

MustafaJafar commented Oct 5, 2023

That's pretty interesting!
transient data: These data are not stored to scene and will be lost on object deletion.
let, me re-draw this uml diagram from my guide.

I can recognize these three types of data, they are used in the Houdini creator.
image


So, running this in Houdini's python source editor, can possibly tell us something.
I don't know how it works but it's working, I tried opening old files and run the following script and it seems to catch old instances too.

from openpype.pipeline.create import CreateContext
from openpype.pipeline import registered_host

host = registered_host()
context = CreateContext(host, reset=True)

for instance in context.instances:
    print(instance.subset_name, instance.transient_data)

how Openpype is able to retrieve these transient data ??

@BigRoy
Copy link
Collaborator Author

BigRoy commented Oct 5, 2023

The Creator collects the transientData - exactly as shown in this PR. Based on searching for the nodes relevant to the creator identifier the Creator assumes this is an instance node belonging to this creator - and then stores the hou.Node() in transient data, exactly like how previously the node's path was stored in the instance data.

Personally I see it like this: The only reason for me that hou.Node() is in transient data is because it can't be converted to JSON data and thus by design it has to be put in transientData.

But transient data tells a bit more as well - it says 'do not store these on the node' - which happens to be the case for non-JSON data (because we can't serialize it well) but also for this instance node. We don't need to store it on the node, because it is the node.

Not sure how to better explain it.

I know @iLLiCiTiT has a view of his own on it and likely can explain it better in a different way.

I don't know how it works but it's working, I tried opening old files and run the following script and it seems to catch old instances too.

Exactly - the Creator already knows how to find the nodes, that logic never relied on the node's path being stored on the node, etc. All the Creator does was "find all nodes with attribute creator_identifier - if identifier matches this Creator class then it's an instance of this Creator. That's all how they are found. That hasn't changed.

The fact that instance_node was stored as a parm on the node was - in my view - a design flaw / bug from the beginning so it's good that since instance_node would be the actual node we're querying the parm from haha, so we don't need to store that data.

Similarly we can have frameStart and frameEnd instance data collected by the Creator but when updating the instance not store the data in the JSON data parms but directly into the ROP nodes time parameters so that we frame start and end can both be set from the publisher AND on the Houdini ROP node. (This is sligthly more complex due to the fact that in Houdini the values can also be expression so the UI would have to likely show it as a TextDef, etc.) Anyway, I deviate too much. I hope this helps.

@MustafaJafar
Copy link
Contributor

MustafaJafar commented Oct 5, 2023

The Creator collects the transientData - exactly as shown in this PR. Based on searching for the nodes relevant to the creator identifier the Creator assumes this is an instance node belonging to this creator - and then stores the hou.Node() in transient data, exactly like how previously the node's path was stored in the instance data.

I think I realized how it works, Thanks!

So, there are two ways to create instances in Houdini

  • HoudiniCreator(...).create(...) # which creates nodes and instances from user input
  • HoudiniCreator(...).collect_instances() # which create instances from existing nodes

and this snippet some where inside calls HoudiniCreator(...).collect_instances() which recreate instances from existing openpype/ayon rop nodes

from openpype.pipeline.create import CreateContext
from openpype.pipeline import registered_host

host = registered_host()
context = CreateContext(host)

for instance in context.instances:
    print(instance.subset_name, instance.transient_data)

Eureka !

creator.collect_instances()

@MustafaJafar
Copy link
Contributor

MustafaJafar commented Oct 6, 2023

I like the idea introduced by this PR, I'm amazed that same approach is followed in Nuke, Fusion and also Maya.

@BigRoy
Copy link
Collaborator Author

BigRoy commented Nov 15, 2023

Would you just need me to resolve the conflicts, or are there other things to implement?

@MustafaJafar
Copy link
Contributor

Nah, It's a casual PR revisiting during PRs cleaning up.
what is the status of this PR :D ? and what's missing ?

@BigRoy
Copy link
Collaborator Author

BigRoy commented Nov 15, 2023

Is it worth our while to refactor like this. Since it doesn't change anything end-user wise it doesn't bring any new features. So, biggest question is - do we want this?

If so, I can resolve conflicts and prepare it for testing.

@MustafaJafar
Copy link
Contributor

MustafaJafar commented Nov 20, 2023

So, biggest question is - do we want this?

I'm afraid I can't answer this question, I'll leave it to @moonyuet or @antirotor
but, I can tell that this PR is helpful because plugins/publish/collect_instances.py is redundant as it does the same work as HoudiniCreator.collect_instances() .

when dealing with contexts HoudiniCreator.collect_instances() is called as mentioned here #5616 (comment)
and we only need plugins/publish/collect_instances.py when publishing which is an overwork.

So, my opinion this PR is helpful and I like it.

@iLLiCiTiT
Copy link
Member

Is this PR still relevant? I think most of the changes are, but not sure if are up to date.

@mkolar
Copy link
Member

mkolar commented Feb 7, 2024

Not sure how to deal with this one right now. Lot's of conflicts, but probably a reasonable change. @antirotor I might need your guidance here.

Seems that it should be OP and AYON compatible out of the box though

@mkolar
Copy link
Member

mkolar commented Feb 9, 2024

Because we're splitting OpenPype into ayon-core and individual host addons, this PR would have to be re-created to target one of those.

We're closing it down, but we'll he happy for a new PR to ynput/ayon-core or the host addon repository once it's up.

@mkolar mkolar closed this Feb 9, 2024
@ynbot ynbot added this to the next-patch milestone Feb 9, 2024
@jakubjezek001 jakubjezek001 removed this from the next-patch milestone Feb 15, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
community contribution host: Houdini port to AYON size/S Denotes a PR changes 100-499 lines, ignoring general files target: AYON target: OpenPype type: enhancement Enhancements to existing functionality type: refactor Structural changes not affecting functionality
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

6 participants