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 a1a7c3dd7d0..efe0ff014c4 100644 --- a/client/ayon_core/plugins/publish/extract_usd_layer_contributions.py +++ b/client/ayon_core/plugins/publish/extract_usd_layer_contributions.py @@ -4,7 +4,7 @@ import platform from collections import defaultdict from operator import attrgetter -from typing import Any, Dict, List +from typing import Any, Dict, List, Literal import pyblish.api try: @@ -74,7 +74,16 @@ class VariantContribution(_BaseContribution): # Variant variant_set_name: str variant_name: str - variant_is_default: bool # Whether to author variant selection opinion + variant_default_policy: Literal[ + "if_not_set", "always", "never" + ] # Policy controlling variant selection opinion + + +CONTRIBUTION_VARIANT_DEFAULT_POLICY = { + "if_not_set": "Set as default if no current default", + "always": "Set as default", + "never": "Do not set" +} def get_representation_path_in_publish_context( @@ -349,13 +358,16 @@ def process(self, instance): # Define contribution in_layer_order: int = attr_values.get("contribution_in_layer_order", 0) if attr_values["contribution_apply_as_variant"]: + variant_default_policy = attr_values[ + "contribution_variant_default_policy"] + contribution = VariantContribution( instance=instance, layer_id=attr_values["contribution_layer"], target_product=attr_values["contribution_target_product"], variant_set_name=attr_values["contribution_variant_set_name"], variant_name=attr_values["contribution_variant"], - variant_is_default=attr_values["contribution_variant_is_default"], # noqa: E501 + variant_default_policy=variant_default_policy, order=in_layer_order ) else: @@ -536,6 +548,7 @@ def get_attr_defs_for_instance(cls, create_context, instance): "task_names": create_context.get_current_task_name() } profile = filter_profiles(cls.profiles, filtering_criteria) + if not profile: profile = { "contribution_enabled": True, @@ -544,7 +557,8 @@ def get_attr_defs_for_instance(cls, create_context, instance): "contribution_apply_as_variant": False, "contribution_variant_set_name": "{layer}", "contribution_variant": "{variant}", - "contribution_variant_is_default": False, + "contribution_variant_default_policy": + "if_not_set", } # Define defaults @@ -560,6 +574,18 @@ def get_attr_defs_for_instance(cls, create_context, instance): publish_attributes = instance["publish_attributes"].get( cls.__name__, {}) + # Convert legacy attribute value + if "contribution_variant_is_default" in publish_attributes: + contribution_variant_is_default = publish_attributes.pop( + "contribution_variant_is_default" + ) + + publish_attributes["contribution_variant_default_policy"] = ( + "always" + if contribution_variant_is_default + else "if_not_set" + ) + visible = publish_attributes.get("contribution_enabled", True) variant_visible = visible and publish_attributes.get( "contribution_apply_as_variant", True) @@ -654,18 +680,31 @@ def get_attr_defs_for_instance(cls, create_context, instance): label="Variant Name", default=profile["contribution_variant"], visible=variant_visible), - BoolDef("contribution_variant_is_default", - label="Set as default variant selection", - tooltip=( - "Whether to set this instance's variant name as the " - "default selected variant name for the variant set.\n" - "It is always expected to be enabled for only one " - "variant name in the variant set.\n" - "The behavior is unpredictable if multiple instances " - "for the same variant set have this enabled." - ), - default=profile["contribution_variant_is_default"], - visible=variant_visible), + EnumDef( + "contribution_variant_default_policy", + label="Set as default variant selection", + tooltip=( + "Controls whether this contribution's variant name is " + "authored as the selected default for the variant set.\n" + f"'{CONTRIBUTION_VARIANT_DEFAULT_POLICY['never']}' leaves " + "the variant selection unchanged.\n" + f"'{CONTRIBUTION_VARIANT_DEFAULT_POLICY['if_not_set']}' " + "sets it only when " + "the variant set has no default selection yet.\n" + f"'{CONTRIBUTION_VARIANT_DEFAULT_POLICY['always']}' always" + " sets it as the default and may " + "override a selection authored by another contribution.\n" + f"When multiple contributions use " + f"'{CONTRIBUTION_VARIANT_DEFAULT_POLICY['always']}', " + "the final result depends on their contribution order." + ), + items=CONTRIBUTION_VARIANT_DEFAULT_POLICY, + default=profile.get( + "contribution_variant_default_policy", + "if_not_set", + ), + visible=variant_visible, + ), UISeparatorDef("usd_container_settings3"), ] @@ -790,8 +829,14 @@ def process(self, instance): # Set default variant selection variant_set_name = contribution.variant_set_name variant_name = contribution.variant_name - if contribution.variant_is_default or \ - variant_set_name not in prim_spec.variantSelections: + policy = contribution.variant_default_policy + if ( + policy == "always" + or ( + policy == "if_not_set" + and variant_set_name not in prim_spec.variantSelections + ) + ): prim_spec.variantSelections[variant_set_name] = variant_name # noqa: E501 elif isinstance(contribution, SublayerContribution): diff --git a/server/settings/conversion.py b/server/settings/conversion.py index 6e44cceafa7..66273ab2d68 100644 --- a/server/settings/conversion.py +++ b/server/settings/conversion.py @@ -287,11 +287,36 @@ def _convert_oiio_transcode_0_4_5(publish_overrides): } +def _convert_usd_contribution_variant_default_policy_1_9_11(overrides): + """Convert the USD contribution default boolean to a policy enum.""" + profiles = ( + overrides + .get("publish", {}) + .get("CollectUSDLayerContributions", {}) + .get("profiles") + ) + if not profiles: + return + + for profile in profiles: + if "contribution_variant_default_policy" in profile: + continue + + if "contribution_variant_is_default" not in profile: + continue + + is_default = profile.pop("contribution_variant_is_default") + profile["contribution_variant_default_policy"] = ( + "always" if is_default else "if_not_set" + ) + + def _convert_publish_plugins(overrides): if "publish" not in overrides: return _convert_validate_version_0_3_3(overrides["publish"]) _convert_oiio_transcode_0_4_5(overrides["publish"]) + _convert_usd_contribution_variant_default_policy_1_9_11(overrides) def _convert_extract_thumbnail(overrides, version: VersionInfo): diff --git a/server/settings/publish_plugins.py b/server/settings/publish_plugins.py index dba9a0bd54f..f700bdcd8e3 100644 --- a/server/settings/publish_plugins.py +++ b/server/settings/publish_plugins.py @@ -26,6 +26,21 @@ StatusesEnumResolver = None +CONTRIBUTION_VARIANT_DEFAULT_POLICY = { + "if_not_set": "Set as default if no current default", + "always": "Set as default", + "never": "Do not set", +} + + +def contribution_variant_default_policy_enum(): + """Return the available variant default policies for the settings UI.""" + return [ + {"value": value, "label": label} + for value, label in CONTRIBUTION_VARIANT_DEFAULT_POLICY.items() + ] + + async def _get_anatomy(project_name: str | None = None) -> Anatomy: if project_name: return await get_project_anatomy(project_name) @@ -261,16 +276,19 @@ class CollectUSDLayerContributionsProfileModel(BaseSettingsModel): "The default variant name for instances matching this profile." ), ) - contribution_variant_is_default: bool = SettingsField( - False, + contribution_variant_default_policy: str = SettingsField( + "if_not_set", title="Set as default variant selection", + enum_resolver=contribution_variant_default_policy_enum, description=( - "Whether to set this instance's variant name as the " - "default selected variant name for the variant set.\n" - "It is always expected to be enabled for only one " - "variant name in the variant set.\n" - "The behavior is unpredictable if multiple instances " - "for the same variant set have this enabled." + "Controls whether this contribution's variant name is authored " + "as the selected default for the variant set. Use " + f"'{CONTRIBUTION_VARIANT_DEFAULT_POLICY['never']}' to leave the " + "variant selection unchanged, " + f"'{CONTRIBUTION_VARIANT_DEFAULT_POLICY['if_not_set']}' to set " + "it only when no selection exists, or " + f"'{CONTRIBUTION_VARIANT_DEFAULT_POLICY['always']}' to always " + "override the current selection." ), ) @@ -1656,7 +1674,8 @@ class PublishPuginsModel(BaseSettingsModel): "contribution_apply_as_variant": True, "contribution_variant_set_name": "{layer}", "contribution_variant": "{variant}", - "contribution_variant_is_default": False, + "contribution_variant_default_policy": + "if_not_set", }, { "product_base_types": ["look"], @@ -1668,7 +1687,8 @@ class PublishPuginsModel(BaseSettingsModel): "contribution_apply_as_variant": True, "contribution_variant_set_name": "{layer}", "contribution_variant": "{variant}", - "contribution_variant_is_default": False, + "contribution_variant_default_policy": + "if_not_set", }, { "product_base_types": ["groom"], @@ -1680,7 +1700,8 @@ class PublishPuginsModel(BaseSettingsModel): "contribution_apply_as_variant": True, "contribution_variant_set_name": "{layer}", "contribution_variant": "{variant}", - "contribution_variant_is_default": False, + "contribution_variant_default_policy": + "if_not_set", }, { "product_base_types": ["rig"], @@ -1692,7 +1713,8 @@ class PublishPuginsModel(BaseSettingsModel): "contribution_apply_as_variant": True, "contribution_variant_set_name": "{layer}", "contribution_variant": "{variant}", - "contribution_variant_is_default": False, + "contribution_variant_default_policy": + "if_not_set", }, { "product_base_types": ["usd"], @@ -1704,7 +1726,8 @@ class PublishPuginsModel(BaseSettingsModel): "contribution_apply_as_variant": False, "contribution_variant_set_name": "{layer}", "contribution_variant": "{variant}", - "contribution_variant_is_default": False, + "contribution_variant_default_policy": + "if_not_set", }, ] },