-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #70 from ynput/enhancement/AY-6953_publish_usd_wit…
…h_relative_paths Allow to process USD files to use relative paths when published
- Loading branch information
Showing
3 changed files
with
172 additions
and
0 deletions.
There are no files selected for viewing
135 changes: 135 additions & 0 deletions
135
client/ayon_usd/plugins/publish/integrate_usd_output_processor_remap_relative.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
import os | ||
|
||
import pyblish.api | ||
|
||
from ayon_core.pipeline import OptionalPyblishPluginMixin | ||
from ayon_core.pipeline.publish.lib import get_instance_expected_output_path | ||
|
||
# Avoid USD imports turning into errors when running in a host that does | ||
# not support the USD libs. | ||
try: | ||
from pxr import Sdf, UsdUtils | ||
HAS_USD_LIBS = True | ||
except ImportError: | ||
HAS_USD_LIBS = False | ||
|
||
|
||
RELATIVE_ANCHOR_PREFIXES = ("./", "../", ".\\", "..\\") | ||
|
||
|
||
def get_drive(path) -> str: | ||
"""Return disk drive from path""" | ||
return os.path.splitdrive(path)[0] | ||
|
||
|
||
class USDOutputProcessorRemapToRelativePaths(pyblish.api.InstancePlugin, | ||
OptionalPyblishPluginMixin): | ||
"""Remap all paths in a USD Layer to be relative to its published path""" | ||
|
||
label = "Process USD files to use relative paths" | ||
families = ["usd"] | ||
settings_category = "usd" | ||
|
||
# Run just before the Integrator | ||
order = pyblish.api.IntegratorOrder - 0.01 | ||
|
||
def process(self, instance): | ||
if not self.is_active(instance.data): | ||
return | ||
|
||
# Skip instance if not marked for integration | ||
if not instance.data.get("integrate", True): | ||
return | ||
|
||
# Some hosts may not have USD libs available but can publish USD data. | ||
# For those we'll log a warning. | ||
if not HAS_USD_LIBS: | ||
self.log.warning( | ||
"Unable to process USD files to relative paths because " | ||
"`pxr` USD libraries could not be imported.") | ||
return | ||
|
||
# For each USD representation, process the file. | ||
for representation in instance.data.get("representations", []): | ||
representation: dict | ||
|
||
if representation.get("name") != "usd": | ||
continue | ||
|
||
# Get expected publish path | ||
published_path = get_instance_expected_output_path( | ||
instance, | ||
representation_name=representation["name"], | ||
ext=representation.get("ext") | ||
) | ||
published_path_root = os.path.dirname(published_path) | ||
self.log.debug( | ||
f"Making USD paths relative to {published_path_root}") | ||
|
||
# Process all files of the representation | ||
staging_dir: str = representation.get( | ||
"stagingDir", instance.data.get("stagingDir")) | ||
|
||
# Process single file or sequence of the representation | ||
filenames = representation["files"] | ||
if isinstance(filenames, str): | ||
# Single file is stored as `str` in `instance.data["files"]` | ||
filenames = [filenames] | ||
|
||
filenames: "list[str]" | ||
for filename in filenames: | ||
path = os.path.join(staging_dir, filename) | ||
self.process_usd(path, start=published_path_root) | ||
|
||
# Some instance may have additional transferred files which | ||
# themselves are not a representation. For those we need to look in | ||
# the `instance.data["transfers"]` | ||
for src, dest in instance.data.get("transfers", []): | ||
if not dest.endswith(".usd"): | ||
continue | ||
|
||
# Process USD file at `src` and turn all paths relative to | ||
# the `dest` path the file will end up at. | ||
dest_root = os.path.dirname(dest) | ||
self.process_usd(src, start=dest_root) | ||
|
||
def process_usd(self, usd_path, start): | ||
"""Process a USD layer making all paths relative to `start`""" | ||
self.log.debug(f"Processing '{usd_path}'") | ||
layer = Sdf.Layer.FindOrOpen(usd_path) | ||
|
||
def modify_fn(asset_path: str): | ||
"""Make all absolute non-anchored paths relative to `start`""" | ||
self.log.debug(f"Processing asset path: {asset_path}") | ||
|
||
# Do not touch paths already anchored paths | ||
if not os.path.isabs(asset_path): | ||
return asset_path | ||
|
||
# Do not touch paths already anchored paths | ||
if asset_path.startswith(RELATIVE_ANCHOR_PREFIXES): | ||
# Already anchored | ||
return asset_path | ||
|
||
# Do not touch what we know are AYON URIs | ||
if asset_path.startswith(("ayon://", "ayon+entity://")): | ||
return asset_path | ||
|
||
# Consider only files on the same drive, because otherwise no | ||
# 'relative' path exists for the file. | ||
if get_drive(start) != get_drive(asset_path): | ||
# Log a warning if different drive | ||
self.log.warning( | ||
f"USD Asset Path '{asset_path}' can not be made relative" | ||
f" to '{start}' because they are not on the same drive.") | ||
return asset_path | ||
|
||
anchored_path = "./" + os.path.relpath(asset_path, start) | ||
self.log.debug(f"Anchored path: {anchored_path}") | ||
return anchored_path | ||
|
||
# Get all "asset path" specs, sublayer paths and references/payloads. | ||
# Make all the paths relative. | ||
UsdUtils.ModifyAssetPaths(layer, modify_fn) | ||
if layer.dirty: | ||
layer.Save() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
from ayon_server.settings import ( | ||
BaseSettingsModel, | ||
SettingsField, | ||
) | ||
|
||
|
||
class EnabledBaseModel(BaseSettingsModel): | ||
_isGroup = True | ||
enabled: bool = SettingsField(True) | ||
optional: bool = SettingsField(True, title="Optional") | ||
active: bool = SettingsField(True, title="Active") | ||
|
||
|
||
class PublishPluginsModel(BaseSettingsModel): | ||
USDOutputProcessorRemapToRelativePaths: EnabledBaseModel = SettingsField( | ||
default_factory=EnabledBaseModel, | ||
title="Process USD files to use relative paths", | ||
description=( | ||
"When enabled, published USD layers will anchor the asset paths to" | ||
" the published filepath. " | ||
) | ||
) | ||
|
||
|
||
DEFAULT_PUBLISH_VALUES = { | ||
"USDOutputProcessorRemapToRelativePaths": { | ||
"enabled": False, | ||
"optional": False, | ||
"active": True, | ||
}, | ||
} |