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

Create & Publish: Attribute definitions per instance #860

Closed
Closed
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
d1bae6d
don't store 'attr_plugins'
iLLiCiTiT Aug 19, 2024
a1ca6a2
Merge branch 'develop' into enhancement/AY-2420_Callbacks-and-groups-…
iLLiCiTiT Aug 29, 2024
c337f2c
added 2 new methods to be able to receive attribute definitions per i…
iLLiCiTiT Aug 29, 2024
738cc82
added helper methods to create plugin
iLLiCiTiT Aug 29, 2024
cb3df92
attribute definitions are defined per instance
iLLiCiTiT Aug 29, 2024
e457580
Merge branch 'develop' into enhancement/AY-5552_Publisher-attr-defs-p…
iLLiCiTiT Aug 30, 2024
35d7277
removed unused imports
iLLiCiTiT Aug 30, 2024
d970a1c
remove line
iLLiCiTiT Sep 4, 2024
f526245
Merge branch 'develop' into enhancement/AY-5552_Publisher-attr-defs-p…
BigRoy Sep 5, 2024
f03983d
use 'get_attr_defs_for_instance' after creation
iLLiCiTiT Sep 6, 2024
bbd3881
implemented 'update_create_attr_defs'
iLLiCiTiT Sep 6, 2024
b3f39ba
data to store does not have to have 'AttributeValues'
iLLiCiTiT Sep 6, 2024
0770a26
Merge branch 'develop' into enhancement/AY-5552_Publisher-attr-defs-p…
iLLiCiTiT Sep 9, 2024
cf41ab6
Merge branch 'develop' into enhancement/AY-5552_Publisher-attr-defs-p…
iLLiCiTiT Sep 12, 2024
ee4ecf1
Merge branch 'develop' into enhancement/AY-5552_Publisher-attr-defs-p…
iLLiCiTiT Sep 16, 2024
688c253
modified signature of '_create_instance'
iLLiCiTiT Sep 19, 2024
dbb427f
Merge branch 'develop' into enhancement/AY-5552_Publisher-attr-defs-p…
iLLiCiTiT Sep 25, 2024
7427a07
Merge branch 'develop' into enhancement/AY-5552_Publisher-attr-defs-p…
iLLiCiTiT Oct 1, 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
86 changes: 38 additions & 48 deletions client/ayon_core/pipeline/create/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,6 @@ def __init__(
self.publish_plugins_mismatch_targets = []
self.publish_plugins = []
self.plugins_with_defs = []
self._attr_plugins_by_product_type = {}

# Helpers for validating context of collected instances
# - they can be validation for multiple instances at one time
Expand Down Expand Up @@ -575,9 +574,6 @@ def _reset_publish_plugins(self, discover_publish_plugins):
publish_plugins_discover
)

# Reset publish plugins
self._attr_plugins_by_product_type = {}

discover_result = DiscoverResult(pyblish.api.Plugin)
plugins_with_defs = []
plugins_by_targets = []
Expand Down Expand Up @@ -705,11 +701,29 @@ def reset_context_data(self):

publish_attributes = original_data.get("publish_attributes") or {}

attr_plugins = self._get_publish_plugins_with_attr_for_context()
self._publish_attributes = PublishAttributes(
self, publish_attributes, attr_plugins
self, publish_attributes
)

for plugin in self.plugins_with_defs:
if is_func_signature_supported(
plugin.convert_attribute_values, self, None
):
plugin.convert_attribute_values(self, None)

elif not plugin.__instanceEnabled__:
output = plugin.convert_attribute_values(publish_attributes)
if output:
publish_attributes.update(output)

for plugin in self.plugins_with_defs:
attr_defs = plugin.get_attribute_defs_for_context(self)
if not attr_defs:
continue
self._publish_attributes.set_publish_plugin_attr_defs(
plugin.__name__, attr_defs
)

def context_data_to_store(self):
"""Data that should be stored by host function.

Expand Down Expand Up @@ -745,11 +759,25 @@ def creator_adds_instance(self, instance: "CreatedInstance"):
return

self._instances_by_id[instance.id] = instance

publish_attributes = instance.publish_attributes
# Prepare publish plugin attributes and set it on instance
attr_plugins = self._get_publish_plugins_with_attr_for_product_type(
instance.product_type
)
instance.set_publish_plugins(attr_plugins)
for plugin in self.plugins_with_defs:
if is_func_signature_supported(
plugin.convert_attribute_values, self, instance
):
plugin.convert_attribute_values(self, instance)

elif plugin.__instanceEnabled__:
output = plugin.convert_attribute_values(publish_attributes)
if output:
publish_attributes.update(output)

for plugin in self.plugins_with_defs:
attr_defs = plugin.get_attribute_defs_for_instance(self, instance)
if not attr_defs:
continue
instance.set_publish_plugin_attr_defs(plugin.__name__, attr_defs)

# Add instance to be validated inside 'bulk_instances_collection'
# context manager if is inside bulk
Expand Down Expand Up @@ -1368,44 +1396,6 @@ def remove_instances(self, instances):
if failed_info:
raise CreatorsRemoveFailed(failed_info)

def _get_publish_plugins_with_attr_for_product_type(self, product_type):
"""Publish plugin attributes for passed product type.

Attribute definitions for specific product type are cached.

Args:
product_type(str): Instance product type for which should be
attribute definitions returned.
"""

if product_type not in self._attr_plugins_by_product_type:
import pyblish.logic

filtered_plugins = pyblish.logic.plugins_by_families(
self.plugins_with_defs, [product_type]
)
plugins = []
for plugin in filtered_plugins:
if plugin.__instanceEnabled__:
plugins.append(plugin)
self._attr_plugins_by_product_type[product_type] = plugins

return self._attr_plugins_by_product_type[product_type]

def _get_publish_plugins_with_attr_for_context(self):
"""Publish plugins attributes for Context plugins.

Returns:
List[pyblish.api.Plugin]: Publish plugins that have attribute
definitions for context.
"""

plugins = []
for plugin in self.plugins_with_defs:
if not plugin.__instanceEnabled__:
plugins.append(plugin)
return plugins

@property
def collection_shared_data(self):
"""Access to shared data that can be used during creator's collection.
Expand Down
44 changes: 42 additions & 2 deletions client/ayon_core/pipeline/create/creator_plugins.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
import copy
import collections
from typing import TYPE_CHECKING, Optional
from typing import TYPE_CHECKING, Optional, Dict, Any

from abc import ABC, abstractmethod

Expand All @@ -19,11 +19,12 @@
from .product_name import get_product_name
from .utils import get_next_versions_for_instances
from .legacy_create import LegacyCreator
from .structures import CreatedInstance

if TYPE_CHECKING:
from ayon_core.lib import AbstractAttrDef
# Avoid cyclic imports
from .context import CreateContext, CreatedInstance, UpdateData # noqa: F401
from .context import CreateContext, UpdateData # noqa: F401


class ProductConvertorPlugin(ABC):
Expand Down Expand Up @@ -362,6 +363,35 @@ def log(self):
self._log = Logger.get_logger(self.__class__.__name__)
return self._log

def _create_instance(
self,
product_name: str,
data: Dict[str, Any],
product_type: Optional[str] = None
) -> CreatedInstance:
"""Create instance and add instance to context.

Args:
product_name (str): Product name.
data (Dict[str, Any]): Instance data.
product_type (Optional[str]): Product type, object attribute
'product_type' is used if not passed.

Returns:
CreatedInstance: Created instance.

"""
if product_type is None:
product_type = self.product_type
instance = CreatedInstance(
product_type,
product_name,
data,
creator=self,
)
self._add_instance_to_context(instance)
return instance

def _add_instance_to_context(self, instance):
"""Helper method to add instance to create context.

Expand Down Expand Up @@ -551,6 +581,16 @@ def get_instance_attr_defs(self):

return self.instance_attr_defs

def get_attr_defs_for_instance(self, instance):
"""Get attribute definitions for an instance.

Args:
instance (CreatedInstance): Instance for which to get
attribute definitions.

"""
return self.get_instance_attr_defs()
Comment on lines +584 to +592
Copy link
Collaborator

Choose a reason for hiding this comment

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

So - how dynamic is this? Like, from the CreatedInstance to what data in it are we allowed to respond to make the returned attribute definitions unique? E.g. technically we can now differentiate attribute defs based on "other attributes" on the instance - however I wonder whether that's intended 'feature'?

  • Can we rely on doing something different based on e.g. folder path or task? If so, what happens if someone without resets confirms a target folder path change from within the UI? Does it refresh?
  • Can we rely on doing something differently based on e.g. families on the instance if it has it?
  • Can we rely on doing something based off of other attributes on the instance from other plug-ins, I suppose not? Since it'd only update on reset?

Copy link
Member Author

@iLLiCiTiT iLLiCiTiT Sep 3, 2024

Choose a reason for hiding this comment

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

Can we rely on doing something differently based on e.g. families on the instance if it has it?

Yes, but if I may request, please do not use families for it if possible. We can discuss each individual use-case (not here).

Why: In case of USD, you probably should be adding families based on the definitions, instead of showing definitions based on families? Meaning, you can add "isUsdInstance": True on instance data to know if is related to USD or not (please take this as very very basic example).

Can we rely on doing something different based on e.g. folder path or task? If so, what happens if someone without resets confirms a target folder path change from within the UI? Does it refresh?

Can we rely on doing something based off of other attributes on the instance from other plug-ins, I suppose not? Since it'd only update on reset?

Both question have same answer. Right now it collects definitions only when instance is created or collected (on create and on reset). For "real time update" we need callbacks. Future PR mentioned in description, which I think might require change of the methods or create context api.

Copy link
Collaborator

@BigRoy BigRoy Sep 3, 2024

Choose a reason for hiding this comment

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

Why: In case of USD, you probably should be adding families based on the definitions, instead of showing definitions based on families? Meaning, you can add "isUsdInstance": True on instance data to know if is related to USD or not (please take this as very very basic example).

I see, but preferably we work towards something similar to families (like standardizes like that, alsmost like "tags") but for the instance's features instead instead of resulting in TONS and TONS of isUsdInstance=True and isFbxInstance=True. Stepping away from pyblish terminology like families, yes! But just doing for example targetProductTypes = {"fbx", "usd"} or whatever name we come up seems like something we can standardize way better. Or maybe better yet, have a dedicated attribute for that on the instance. E.g. being able to do something like:

if instance.supports("usd"):
    # bla

Likely overlaps tons with @antirotor work with OpenAssetIO but that's a can of worms I'm afraid to touch. :)


Both question have same answer. Right now it collects definitions only when instance is created or collected (on create and on reset). For "real time update" we need callbacks. Future PR mentioned in description, which I think might require change of the methods or create context api.

👍

Copy link
Member Author

@iLLiCiTiT iLLiCiTiT Sep 3, 2024

Choose a reason for hiding this comment

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

Because I don't know the context, I can't react. Only from what you wrote I have 10 following questions that will raise another questions. Maybe with full context I would agree, but at this moment I don't.

It is possible to do anything, we should just discuss what would be the ideal approach, to make it clear from data flow.

Like I've said (wrote) we can discuss that out of this PR, as it is not directly related, and it requires more minds.


@property
def collection_shared_data(self):
"""Access to shared data that can be used during creator's collection.
Expand Down
Loading