From 4eece5e6e9157e61a69ef960cd1065414403319d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 10 Dec 2025 16:55:44 +0100 Subject: [PATCH 1/8] Allow to define department layers scoped only to a particular department layer type, e.g. "shot" versus "asset". This way, you can scope same layer names for both shot and asset at different orders if they have differing target scopes --- .../extract_usd_layer_contributions.py | 58 ++++++++++++------- server/settings/publish_plugins.py | 30 ++++++---- 2 files changed, 57 insertions(+), 31 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_usd_layer_contributions.py b/client/ayon_core/plugins/publish/extract_usd_layer_contributions.py index 2c4cc5aac2..7da14a714b 100644 --- a/client/ayon_core/plugins/publish/extract_usd_layer_contributions.py +++ b/client/ayon_core/plugins/publish/extract_usd_layer_contributions.py @@ -2,6 +2,7 @@ from operator import attrgetter import dataclasses import os import platform +from collections import defaultdict from typing import Any, Dict, List import pyblish.api @@ -278,19 +279,23 @@ class CollectUSDLayerContributions(pyblish.api.InstancePlugin, # level, you can add itdirectly from the publisher at that particular # order. Future publishes will then see the existing contribution and will # persist adding it to future bootstraps at that order - contribution_layers: Dict[str, int] = { + contribution_layers: Dict[str, Dict[str, int]] = { # asset layers - "model": 100, - "assembly": 150, - "groom": 175, - "look": 200, - "rig": 300, + "asset": { + "model": 100, + "assembly": 150, + "groom": 175, + "look": 200, + "rig": 300, + }, # shot layers - "layout": 200, - "animation": 300, - "simulation": 400, - "fx": 500, - "lighting": 600, + "shot": { + "layout": 200, + "animation": 300, + "simulation": 400, + "fx": 500, + "lighting": 600, + } } # Default profiles to set certain instance attribute defaults based on # profiles in settings @@ -305,12 +310,13 @@ class CollectUSDLayerContributions(pyblish.api.InstancePlugin, cls.enabled = plugin_settings.get("enabled", cls.enabled) - # Define contribution layers via settings - contribution_layers = {} + # Define contribution layers via settings by their scope + contribution_layers = defaultdict(dict) for entry in plugin_settings.get("contribution_layers", []): - contribution_layers[entry["name"]] = int(entry["order"]) + for scope in entry.get("scope", []): + contribution_layers[scope][entry["name"]] = int(entry["order"]) if contribution_layers: - cls.contribution_layers = contribution_layers + cls.contribution_layers = dict(contribution_layers) cls.profiles = plugin_settings.get("profiles", []) @@ -489,14 +495,14 @@ class CollectUSDLayerContributions(pyblish.api.InstancePlugin, profile = {} # Define defaults - default_enabled = profile.get("contribution_enabled", True) + default_enabled: bool = profile.get("contribution_enabled", True) default_contribution_layer = profile.get( "contribution_layer", None) - default_apply_as_variant = profile.get( + default_apply_as_variant: bool = profile.get( "contribution_apply_as_variant", False) - default_target_product = profile.get( + default_target_product: str = profile.get( "contribution_target_product", "usdAsset") - default_init_as = ( + default_init_as: str = ( "asset" if profile.get("contribution_target_product") == "usdAsset" else "shot") @@ -509,6 +515,12 @@ class CollectUSDLayerContributions(pyblish.api.InstancePlugin, visible = publish_attributes.get("contribution_enabled", True) variant_visible = visible and publish_attributes.get( "contribution_apply_as_variant", True) + init_as: str = publish_attributes.get( + "contribution_target_product_init", default_init_as) + + contribution_layers = cls.contribution_layers.get( + init_as, {} + ) return [ UISeparatorDef("usd_container_settings1"), @@ -558,7 +570,7 @@ class CollectUSDLayerContributions(pyblish.api.InstancePlugin, "predefined ordering.\nA higher order (further down " "the list) will contribute as a stronger opinion." ), - items=list(cls.contribution_layers.keys()), + items=list(contribution_layers.keys()), default=default_contribution_layer, visible=visible), BoolDef("contribution_apply_as_variant", @@ -606,7 +618,11 @@ class CollectUSDLayerContributions(pyblish.api.InstancePlugin, # Update attributes if any of the following plug-in attributes # change: - keys = ["contribution_enabled", "contribution_apply_as_variant"] + keys = { + "contribution_enabled", + "contribution_apply_as_variant", + "contribution_target_product_init", + } for instance_change in event["changes"]: instance = instance_change["instance"] diff --git a/server/settings/publish_plugins.py b/server/settings/publish_plugins.py index d7b794cb5b..5524f7920d 100644 --- a/server/settings/publish_plugins.py +++ b/server/settings/publish_plugins.py @@ -74,9 +74,19 @@ class CollectFramesFixDefModel(BaseSettingsModel): ) +def usd_contribution_layer_types(): + return [ + {"value": "asset", "label": "Asset"}, + {"value": "shot", "label": "Shot"}, + ] + + class ContributionLayersModel(BaseSettingsModel): _layout = "compact" name: str = SettingsField(title="Name") + scope: list[str] = SettingsField( + title="Scope", + enum_resolver=usd_contribution_layer_types) order: str = SettingsField( title="Order", description="Higher order means a higher strength and stacks the " @@ -1345,17 +1355,17 @@ DEFAULT_PUBLISH_VALUES = { "enabled": True, "contribution_layers": [ # Asset layers - {"name": "model", "order": 100}, - {"name": "assembly", "order": 150}, - {"name": "groom", "order": 175}, - {"name": "look", "order": 200}, - {"name": "rig", "order": 300}, + {"name": "model", "order": 100, "scope": ["asset"]}, + {"name": "assembly", "order": 150, "scope": ["asset"]}, + {"name": "groom", "order": 175, "scope": ["asset"]}, + {"name": "look", "order": 200, "scope": ["asset"]}, + {"name": "rig", "order": 300, "scope": ["asset"]}, # Shot layers - {"name": "layout", "order": 200}, - {"name": "animation", "order": 300}, - {"name": "simulation", "order": 400}, - {"name": "fx", "order": 500}, - {"name": "lighting", "order": 600}, + {"name": "layout", "order": 200, "scope": ["shot"]}, + {"name": "animation", "order": 300, "scope": ["shot"]}, + {"name": "simulation", "order": 400, "scope": ["shot"]}, + {"name": "fx", "order": 500, "scope": ["shot"]}, + {"name": "lighting", "order": 600, "scope": ["shot"]}, ], "profiles": [ { From 2871ecac7d319dd4e3b9dc8d1469b2a253b06453 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 12 Dec 2025 22:52:52 +0100 Subject: [PATCH 2/8] Fix setting the correct order --- .../plugins/publish/extract_usd_layer_contributions.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_usd_layer_contributions.py b/client/ayon_core/plugins/publish/extract_usd_layer_contributions.py index 7da14a714b..63b642b685 100644 --- a/client/ayon_core/plugins/publish/extract_usd_layer_contributions.py +++ b/client/ayon_core/plugins/publish/extract_usd_layer_contributions.py @@ -340,8 +340,9 @@ class CollectUSDLayerContributions(pyblish.api.InstancePlugin, attr_values[key] = attr_values[key].format(**data) # Define contribution - order = self.contribution_layers.get( - attr_values["contribution_layer"], 0 + scope: str = attr_values["contribution_target_product_init"] + order: int = ( + self.contribution_layers[scope][attr_values["contribution_layer"]] ) if attr_values["contribution_apply_as_variant"]: From c93eb31b54b10089ca2652dddeefe36d34f51915 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 12 Dec 2025 22:53:05 +0100 Subject: [PATCH 3/8] Fix typo --- .../plugins/publish/extract_usd_layer_contributions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/publish/extract_usd_layer_contributions.py b/client/ayon_core/plugins/publish/extract_usd_layer_contributions.py index 63b642b685..a78767a76d 100644 --- a/client/ayon_core/plugins/publish/extract_usd_layer_contributions.py +++ b/client/ayon_core/plugins/publish/extract_usd_layer_contributions.py @@ -276,7 +276,7 @@ class CollectUSDLayerContributions(pyblish.api.InstancePlugin, # the contributions so that we can design a system where custom # contributions outside the predefined orders are possible to be # managed. So that if a particular asset requires an extra contribution - # level, you can add itdirectly from the publisher at that particular + # level, you can add it directly from the publisher at that particular # order. Future publishes will then see the existing contribution and will # persist adding it to future bootstraps at that order contribution_layers: Dict[str, Dict[str, int]] = { From 19f84805bd45cf9fb33faa6178c7f32c370da3c5 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 15 Dec 2025 22:11:12 +0100 Subject: [PATCH 4/8] Fix scope defaults, fix order to `int` and enforce `name` to not be empty --- server/settings/publish_plugins.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/server/settings/publish_plugins.py b/server/settings/publish_plugins.py index a328465932..90f6d1c5ef 100644 --- a/server/settings/publish_plugins.py +++ b/server/settings/publish_plugins.py @@ -83,14 +83,21 @@ def usd_contribution_layer_types(): class ContributionLayersModel(BaseSettingsModel): _layout = "compact" - name: str = SettingsField(title="Name") + name: str = SettingsField( + default="", + regex="[A-Za-z0-9_-]+", + title="Name") scope: list[str] = SettingsField( + default_factory=list, title="Scope", enum_resolver=usd_contribution_layer_types) - order: str = SettingsField( + order: int = SettingsField( + default=0, title="Order", - description="Higher order means a higher strength and stacks the " - "layer on top.") + description=( + "Higher order means a higher strength and stacks the layer on top." + ) + ) class CollectUSDLayerContributionsProfileModel(BaseSettingsModel): From 74971bd3dcac94d87fd190ab83bdfd89384ef81c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 15 Dec 2025 22:14:14 +0100 Subject: [PATCH 5/8] Cosmetics --- .../plugins/publish/extract_usd_layer_contributions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/publish/extract_usd_layer_contributions.py b/client/ayon_core/plugins/publish/extract_usd_layer_contributions.py index c6f6497a6a..eb51f1d491 100644 --- a/client/ayon_core/plugins/publish/extract_usd_layer_contributions.py +++ b/client/ayon_core/plugins/publish/extract_usd_layer_contributions.py @@ -18,7 +18,7 @@ from ayon_core.lib import ( UISeparatorDef, UILabelDef, EnumDef, - filter_profiles + filter_profiles, ) try: from ayon_core.pipeline.usdlib import ( From cd1c2cdb0f38f611a4133fc4d8eaba9a94d5e3c7 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 17 Dec 2025 11:57:29 +0100 Subject: [PATCH 6/8] Set the default value for new entries to be scoped to `asset`, `task` so that copying from older releases automatically sets it to both. This way, also newly added entries will have both by default which is better than none. --- server/settings/publish_plugins.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/server/settings/publish_plugins.py b/server/settings/publish_plugins.py index 90f6d1c5ef..6b1ee5562f 100644 --- a/server/settings/publish_plugins.py +++ b/server/settings/publish_plugins.py @@ -88,7 +88,11 @@ class ContributionLayersModel(BaseSettingsModel): regex="[A-Za-z0-9_-]+", title="Name") scope: list[str] = SettingsField( - default_factory=list, + # This should actually be returned from a callable to `default_factory` + # because lists are mutable. However, the frontend can't interpret + # the callable. It will fail to apply it as the default. Specifying + # this default directly did not show any ill side effects. + default=["asset", "shot"], title="Scope", enum_resolver=usd_contribution_layer_types) order: int = SettingsField( From 3e3cd49beaddf28e1685d0147deb65ebc3bcf5d5 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 17 Dec 2025 11:59:48 +0100 Subject: [PATCH 7/8] Add a warning if plug-in defaults are used --- .../plugins/publish/extract_usd_layer_contributions.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/ayon_core/plugins/publish/extract_usd_layer_contributions.py b/client/ayon_core/plugins/publish/extract_usd_layer_contributions.py index eb51f1d491..ed3c16b5c2 100644 --- a/client/ayon_core/plugins/publish/extract_usd_layer_contributions.py +++ b/client/ayon_core/plugins/publish/extract_usd_layer_contributions.py @@ -318,6 +318,11 @@ class CollectUSDLayerContributions(pyblish.api.InstancePlugin, contribution_layers[scope][entry["name"]] = int(entry["order"]) if contribution_layers: cls.contribution_layers = dict(contribution_layers) + else: + cls.log.warning( + "No scoped contribution layers found in settings, falling back" + " to CollectUSDLayerContributions plug-in defaults..." + ) cls.profiles = plugin_settings.get("profiles", []) From 2baffc253ceeaf21c67727c84d1b322740abdf6c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 17 Dec 2025 12:13:21 +0100 Subject: [PATCH 8/8] Set `min_items=1` for `scope` attribute in `CollectUSDLayerContributions` --- server/settings/publish_plugins.py | 1 + 1 file changed, 1 insertion(+) diff --git a/server/settings/publish_plugins.py b/server/settings/publish_plugins.py index df76170a20..eb41c75699 100644 --- a/server/settings/publish_plugins.py +++ b/server/settings/publish_plugins.py @@ -94,6 +94,7 @@ class ContributionLayersModel(BaseSettingsModel): # this default directly did not show any ill side effects. default=["asset", "shot"], title="Scope", + min_items=1, enum_resolver=usd_contribution_layer_types) order: int = SettingsField( default=0,