ayon-core icon indicating copy to clipboard operation
ayon-core copied to clipboard

Enhancement: Callbacks and groups with Publisher attributes

Open antirotor opened this issue 1 year ago • 5 comments

Is there an existing issue for this?

  • [X] I have searched the existing issues.

Please describe the feature you have in mind and explain what the current shortcomings are?

In publisher, both creator and publish plugins can define attributes that can be set to control specifics of the publishing process. Right now, all those attributes are basically standalone without having any connection to others.

We need to define relationship between individual attributes, so changing value of one can affect the whole group (or other groups, or other controls).

How would you imagine the implementation of the feature?

Since attributes are defined in individual plugins there are few things that needs to be implemented to overcome this:

  • attribute identifier: to be able to reference one attribute from somewhere else
  • callbacks: ability for plugin to define custom callback to be called when some attribute state is changed (connected to event system). Those callbacks should be able to report back to UI - for example to report invalid state.
  • labeling: show what controls belongs together, either plugin-wise, or logical group related

Are there any labels you wish to add?

  • [X] I have added the relevant labels to the enhancement request.

Describe alternatives you've considered:

No response

Additional context:

Rreferences

https://github.com/ynput/ayon-workgroups/issues/4

[cuID:AY-2420]

antirotor avatar Feb 28 '24 17:02 antirotor

So, based on our discussion with @BigRoy and @iLLiCiTiT , this is example mockup code that might be solution:

import subprocess
from ayon_core.lib import EnumDef, BoolDef, TextDef, ButtonDef
from  ayon_core.pipeline.create import CreateContext
import pyblish.api


class ExamplePlugin(pyblish.api.InstancePlugin):
    """Example plugin for demonstration purposes.
    
    Attributes:
        id (str): Unique identifier for this plugin.
            It is using reverse domain name notation.
    
    """

    @classmethod
    def get_attribute_defs(cls):
        """Get the attribute definitions for this plugin.
        
        Every attribute can be uniquely identified by its id. To get it's value,
        you need also instance itself.
        """
        return [
            BoolDef(
                "groupEnabled",  # io.ayon.publish.examplePlugin.attributes.groupEnabled
                label="Group Enabled",
                default=True,
            ),
            EnumDef("groupEnum",  # io.ayon.publish.examplePlugin.attributes.groupEnum
                    label="Enum Example",
                    items=["Foo", "Bar", "Baz"],
                    default="Foo"),
            TextDef("groupText",  # io.ayon.publish.examplePlugin.attributes.groupText
                    label="Text Example",
                    default="Hello World"),
            ButtonDef("groupButton",  # io.ayon.publish.examplePlugin.attributes.groupButton
                    label="Button Example")
        ]
    
    @classmethod
    def register_attribute_callback(cls, create_context):
        """Register attribute callbacks for this plugin.

        These callbacks are called when the value of the attribute changes.
        """
        # register callback should take either list of attribute ids or a single attribute id
        create_context.register_callback(
            ["io.ayon.publish.examplePlugin.attributes.groupEnabled"], cls.on_attribute_changed)
        create_context.register_callback("io.ayon.publish.examplePlugin.attributes.groupButton", cls.on_button_pressed)


    @classmethod
    def on_attribute_changed(cls, create_context: CreateContext, changes: list):
        """Callback for when the attribute changes.
        
        Args:
            create_context (CreateContext): The create context.
            changes (list): List of changes that happened.
                This should be a list of tuples where the first element is instance id,
                the second one is original value, the third one is new value.

        """
        enabled_attr = create_context.get_attribute_by_id("io.ayon.publish.examplePlugin.attributes.groupEnabled")
        group_attrs = create_context.get_attribute_by_group("io.ayon.publish.examplePlugin.attributes")
        if enabled_attr.value:
            print("Group is enabled")
            group_attrs["groupEnum"] = "Bar"
            group_attrs["groupText"] = "Hello Bar Enabled"
        else:
            print("Group is disabled")
            group_attrs["groupEnum"].set_disabled(True)
            group_attrs["groupText"].set_disabled(True)


    @classmethod
    def on_button_pressed(cls, create_context: CreateContext, changes: list):
        print("Button Pressed")
        output = subprocess.check_output(["ls", "-l"])
        instance_ids = []
        for instance in create_context.get_instances():
            if instance.family == "exampleFamily":
                instance_ids.append(instance.id)
        try:
            create_context.set_instance_attribute(instance_ids, "groupText", output)
        except AttributeError:
            # instance does not have the attribute
            pass

there are some mixed approaches together, but main idea is event based system where every plugin could express it's interest in changes of certain attribute values. Create context would then execute callback whenever attribute value changes and pass itself and information about changes to the callback. Callback will then decide what to do.

Pros

  • Flexible way where every plugin can decide what to do with changes
  • Tt can solve some issues where certain attribute definition can't be shown because we don't know all instances beforehand

Cons

  • There might be conflicting callbacks defined plugins (should we allow of forbid?)
  • It might be really slow

Notes Maybe changes can be passed as object with some helper methods to access instance, check types, etc.

This is just a quick summary, lets elaborate more on this (and other ways) here

antirotor avatar Mar 05 '24 20:03 antirotor

I quickly prototyped something in the following PR. This is without any guidance. So its a very rudimentary solution.

This was done before you posted your code example above

Comparing develop...enhancement/AY-2614_publisher_display_overridden_vs_default_values · ynput/ayon-core (github.com)

bradenjennings avatar Mar 05 '24 21:03 bradenjennings

I had a look at your code @antirotor.

and I'm not sure how I would implement something like this especially in regards to signals. I'm not assigned to the ticket yet.

perhaps this one is better handled by someone more experienced.

bradenjennings avatar Mar 05 '24 21:03 bradenjennings

I quickly prototyped something in the following PR. This is without any guidance. So its a very rudimentary solution.

This was done before you posted your code example above

Comparing develop...enhancement/AY-2614_publisher_display_overridden_vs_default_values · ynput/ayon-core (github.com)

This is not directly connected with your PR - this is mostly about ability to define custom logic in individual plugins that has access to all defined attributes within the system. Default values are definitely touching this, but are not the main topic.

antirotor avatar Mar 06 '24 08:03 antirotor

Sorry the PR was named incorrectly, I meant this one. Same code. https://github.com/ynput/ayon-core/compare/enhancement/AY-2420_callbacks_and_groups_with_publisher_attributes?expand=1

bradenjennings avatar Mar 06 '24 20:03 bradenjennings