Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 6 additions & 3 deletions client/ayon_core/pipeline/entity_uri.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,13 @@ def construct_ayon_entity_uri(
"""
if isinstance(version, int) and version < 0:
version = "hero"
if not (isinstance(version, int) or version in {"latest", "hero"}):
if not (
isinstance(version, int)
or version in {"latest", "latestDone", "hero"}
):
raise ValueError(
"Version must either be integer, 'latest' or 'hero'. "
"Got: {}".format(version)
"Version must either be integer, 'latest', 'latestDone' or "
f"'hero'. Got: {version}"
)
return (
"ayon://{project}/{folder_path}?product={product}&version={version}"
Expand Down
155 changes: 106 additions & 49 deletions client/ayon_core/plugins/publish/extract_usd_layer_contributions.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@
# all the time at the same time
BUILD_INTO_LAST_VERSIONS = True

USDContributionURI = Literal[
"filepath",
"ayon_entity_uri",
"ayon_entity_uri_latest",
"ayon_entity_uri_latest_approved",
]


@dataclasses.dataclass
class _BaseContribution:
Expand Down Expand Up @@ -151,59 +158,75 @@ def get_representation_path_in_publish_context(


def get_instance_uri_path(
instance,
resolve=True
):
instance: pyblish.api.Instance,
uri_mode: USDContributionURI = "filepath"
) -> str:
"""Return path for instance's usd representation"""
context = instance.context
folder_path = instance.data["folderPath"]
product_name = instance.data["productName"]
project_name = context.data["projectName"]
version_name = instance.data["version"]

# Get the layer's published path
path = construct_ayon_entity_uri(
project_name=project_name,
folder_path=folder_path,
product=product_name,
version=version_name,
representation_name="usd"
)
project_name: str = context.data["projectName"]
folder_path: str = instance.data["folderPath"]
product_name: str = instance.data["productName"]
version: int = instance.data["version"]
representation_name: str = "usd"

# Handle AYON entity URI modes
if uri_mode in {
"ayon_entity_uri",
"ayon_entity_uri_latest",
"ayon_entity_uri_latest_approved",
}:
uri_version: int | str = version
if uri_mode == "ayon_entity_uri_latest":
uri_version = "latest"
elif uri_mode == "ayon_entity_uri_latest_approved":
uri_version = "latestDone"
return construct_ayon_entity_uri(
project_name=project_name,
folder_path=folder_path,
product=product_name,
version=uri_version,
representation_name=representation_name
)
Comment thread
Copilot marked this conversation as resolved.

# Resolve contribution path
# TODO: Remove this when Asset Resolver is used
if resolve:
query = parse_ayon_entity_uri(path)
names = {
"project_name": query["project"],
"folder_path": query["folderPath"],
"product_name": query["product"],
"version_name": query["version"],
"representation_name": query["representation"],
}
# Resolve the layer's published path.
names = {
"project_name": project_name,
"folder_path": folder_path,
"product_name": product_name,
"version_name": version,
"representation_name": representation_name,
}

# We want to resolve the paths live from the publishing context
path = get_representation_path_in_publish_context(context, **names)
if path:
return path
# We want to resolve the paths live from the publishing context.
path = get_representation_path_in_publish_context(context, **names)
if path:
return path

# If for whatever reason we were unable to retrieve from the context
# then get the path from an existing database entry
path = get_representation_path_by_names(
anatomy=context.data["anatomy"],
**names
)
if not path:
raise RuntimeError(f"Unable to resolve publish path for: {names}")
# If for whatever reason we were unable to retrieve from the context
# then get the path from an existing database entry.
path = get_representation_path_by_names(
anatomy=context.data["anatomy"],
**names
)
if not path:
raise RuntimeError(f"Unable to resolve publish path for: {names}")

# Ensure `None` for now is also a string
path = str(path)
if platform.system().lower() == "windows":
path = path.replace("\\", "/")
# Ensure `None` for now is also a string.
path = str(path)
if platform.system().lower() == "windows":
path = path.replace("\\", "/")

return path


def _layer_contents(layer: Sdf.Layer | None) -> str | None:
"""Return a stable serialized representation of an SDF layer."""
if layer is None:
return None
layer: Sdf.Layer
return layer.ExportToString()


def get_last_publish(instance, representation="usd"):
"""Wrapper to quickly get last representation publish path"""
return get_representation_path_by_names(
Expand Down Expand Up @@ -761,7 +784,7 @@ class ExtractUSDLayerContribution(publish.Extractor):

settings_category = "core"

use_ayon_entity_uri = False
use_ayon_entity_uri: USDContributionURI = "filepath"
enforce_default_prim = False

def process(self, instance):
Expand All @@ -773,6 +796,7 @@ def process(self, instance):
path = get_last_publish(instance)
if path and BUILD_INTO_LAST_VERSIONS:
sdf_layer = Sdf.Layer.OpenAsAnonymous(path)
original_contents = _layer_contents(sdf_layer)

# If enabled in settings, ignore any default prim specified on
# older publish versions and always publish with the AYON
Expand All @@ -787,11 +811,14 @@ def process(self, instance):
default_prim = get_standard_default_prim_name(folder_path)
sdf_layer = Sdf.Layer.CreateAnonymous()
set_layer_defaults(sdf_layer, default_prim=default_prim)
original_contents = None

contributions = instance.data.get("usd_contributions", [])
for contribution in sorted(contributions, key=attrgetter("order")):
path = get_instance_uri_path(contribution.instance,
resolve=not self.use_ayon_entity_uri)
path = get_instance_uri_path(
contribution.instance,
uri_mode=self.use_ayon_entity_uri
)
if isinstance(contribution, VariantContribution):
# Add contribution as a reference inside a variant
self.log.debug(f"Adding variant: {contribution}")
Expand Down Expand Up @@ -857,6 +884,18 @@ def process(self, instance):
else:
raise TypeError(f"Unsupported contribution: {contribution}")

# Only publish if there are changes compared to last version,
# otherwise do not generate a new file.
if (
original_contents is not None
and original_contents == _layer_contents(sdf_layer)
):
self.log.info(
"USD contribution layer is unchanged; skipping publish."
)
instance.data["publish"] = False
return

# Save the file
staging_dir = self.staging_dir(instance)
filename = f"{instance.name}.usd"
Expand All @@ -881,6 +920,8 @@ def remove_previous_reference_contribution(self,
ref: "Sdf.Reference"

uri = ref.customData.get("ayon_uri")
if not uri or not parse_ayon_entity_uri(uri):
uri = ref.customData.get("ayon_entity_uri")
if uri and self.instance_match_ayon_uri(instance, uri):
self.log.debug("Removing existing reference: %s", ref)
remove_indices.add(index)
Expand Down Expand Up @@ -941,7 +982,7 @@ class ExtractUSDAssetContribution(publish.Extractor):

settings_category = "core"

use_ayon_entity_uri = False
use_ayon_entity_uri: USDContributionURI = "filepath"

def process(self, instance):

Expand All @@ -954,14 +995,18 @@ def process(self, instance):
# Use existing asset and add to it, or initialize a new asset layer
path = get_last_publish(instance)
payload_layer = None
original_asset_contents = None
original_payload_contents = None
if path and BUILD_INTO_LAST_VERSIONS:
# If there's a payload file, put it in the payload instead
folder = os.path.dirname(path)
payload_path = os.path.join(folder, "payload.usd")
if os.path.exists(payload_path):
payload_layer = Sdf.Layer.OpenAsAnonymous(payload_path)
original_payload_contents = _layer_contents(payload_layer)

asset_layer = Sdf.Layer.OpenAsAnonymous(path)
original_asset_contents = _layer_contents(asset_layer)
else:
# If no existing publish of this product exists then we initialize
# the layer as either a default asset or shot structure.
Expand Down Expand Up @@ -1019,8 +1064,10 @@ def sort_by_order(instance):
layer_id = layer_instance.data["usd_layer_id"]
order = layer_instance.data["usd_layer_order"]

path = get_instance_uri_path(instance=layer_instance,
resolve=not self.use_ayon_entity_uri)
path = get_instance_uri_path(
instance=layer_instance,
uri_mode=self.use_ayon_entity_uri
)
add_ordered_sublayer(target_layer,
contribution_path=path,
layer_id=layer_id,
Expand All @@ -1029,6 +1076,16 @@ def sort_by_order(instance):
# us to later detect whether another path
# has the same layer id, so we can replace it.
add_sdf_arguments_metadata=True)
if (
original_asset_contents is not None
and original_asset_contents == _layer_contents(asset_layer)
and original_payload_contents == _layer_contents(payload_layer)
):
self.log.info(
"USD asset contribution is unchanged; skipping publish."
)
instance.data["publish"] = False
return

# Save the file
staging_dir = self.staging_dir(instance)
Expand Down
29 changes: 27 additions & 2 deletions server/settings/conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,12 +311,37 @@ def _convert_usd_contribution_variant_default_policy_1_9_11(overrides):
)


def _convert_publish_plugins(overrides):
def _convert_usd_contribution_uri_modes_1_9_11(
publish_overrides,
version: VersionInfo
):
"""Convert legacy USD contribution URI booleans to URI modes."""
if (version.major, version.minor, version.patch) >= (1, 9, 11):
return
for plugin_name in (
"ExtractUSDAssetContribution",
"ExtractUSDLayerContribution",
):
plugin_settings = publish_overrides.get(plugin_name)
if not plugin_settings:
continue

value = plugin_settings.get("use_ayon_entity_uri")
if not isinstance(value, bool):
continue

plugin_settings["use_ayon_entity_uri"] = (
"ayon_entity_uri" if value else "filepath"
)


def _convert_publish_plugins(overrides, version: VersionInfo):
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)
_convert_usd_contribution_uri_modes_1_9_11(overrides["publish"], version)


def _convert_extract_thumbnail(overrides, version: VersionInfo):
Expand Down Expand Up @@ -437,7 +462,7 @@ def convert_settings_overrides(
_convert_imageio_configs_0_4_5(overrides)
_convert_product_name_templates_1_6_5(overrides)
_convert_product_name_templates_1_7_0(overrides)
_convert_publish_plugins(overrides)
_convert_publish_plugins(overrides, version)
_convert_extract_thumbnail(overrides, version)
_convert_product_base_types_1_8_0(overrides)
_convert_unify_profile_keys_1_8_0(overrides)
Expand Down
35 changes: 27 additions & 8 deletions server/settings/publish_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -494,14 +494,33 @@ def validate_unique_resolution_options(cls, value):
return value


def usd_contribution_path_types():
return [
{"value": "filepath", "label": "Filepath"},
{
"value": "ayon_entity_uri",
"label": "AYON Entity URI (explicit version)"
},
{
"value": "ayon_entity_uri_latest",
"label": "AYON Entity URI as latest version"
},
Comment thread
BigRoy marked this conversation as resolved.
{
"value": "ayon_entity_uri_latest_approved",
"label": "AYON Entity URI as latest approved"
},
Comment thread
BigRoy marked this conversation as resolved.
]


class AyonEntityURIModel(BaseSettingsModel):
use_ayon_entity_uri: bool = SettingsField(
title="Use AYON Entity URI",
use_ayon_entity_uri: str = SettingsField(
"filepath",
title="Contribution path",
description=(
"When enabled the USD paths written using the contribution "
"workflow will use ayon entity URIs instead of resolved published "
"paths. You can only load these if you use the AYON USD Resolver."
)
"Choose how paths written by the USD contribution workflow are "
"authored. Entity URI options require the AYON USD Resolver."
),
enum_resolver=usd_contribution_path_types,
)


Expand Down Expand Up @@ -2125,10 +2144,10 @@ class PublishPuginsModel(BaseSettingsModel):
]
},
"ExtractUSDAssetContribution": {
"use_ayon_entity_uri": False,
"use_ayon_entity_uri": "filepath",
},
"ExtractUSDLayerContribution": {
"use_ayon_entity_uri": False,
"use_ayon_entity_uri": "filepath",
"enforce_default_prim": False,
},
"PreIntegrateThumbnails": {
Expand Down
Loading