Skip to content
Closed
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
2 changes: 1 addition & 1 deletion client/ayon_usd/ayon_bin_client
53 changes: 47 additions & 6 deletions client/ayon_usd/hooks/pre_resolver_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

import json
import os
from ayon_applications import LaunchTypes, PreLaunchHook
from ayon_applications import PreLaunchHook
from ayon_applications.defs import LaunchTypes
from ayon_usd import config, utils
from ayon_usd.addon import ADDON_DATA_JSON_PATH

Expand All @@ -14,9 +15,6 @@ class InitializeAssetResolver(PreLaunchHook):
"""

app_groups = {"maya", "houdini", "unreal"}
# TODO Use `farm_render` instead of `farm_publish`
# once this issue is resolved
# https://github.com/ynput/ayon-applications/issues/2
launch_types = {LaunchTypes.local, LaunchTypes.farm_publish}

def execute(self):
Expand All @@ -28,6 +26,37 @@ def execute(self):
" disabled.")
return

is_farm = (
hasattr(self, "launch_type")
and self.launch_type == LaunchTypes.farm_publish
)

# Check for a locally-configured resolver path first
local_path = utils.get_local_resolver_path(
project_settings, self.app_name
)
if local_path:
if not os.path.isdir(local_path):
self.log.error(
f"Local resolver path does not exist: {local_path}"
)
return
self.log.info(
f"Using local resolver path for {self.app_name}: {local_path}"
)
self._setup_resolver(local_path, project_settings)
return
Comment on lines +40 to +48
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a local_resolver_path entry matches the app/platform but the directory does not exist, the hook returns early and prevents the lakeFS fallback for local launches. Consider logging a warning and falling back to lakeFS when launch_type is local (and only skipping on farm), so a misconfigured path doesn’t break artist workstations.

Suggested change
self.log.error(
f"Local resolver path does not exist: {local_path}"
)
return
self.log.info(
f"Using local resolver path for {self.app_name}: {local_path}"
)
self._setup_resolver(local_path, project_settings)
return
if is_farm:
self.log.error(
"Local resolver path does not exist for farm launch: "
f"{local_path}. Skipping lakeFS download on farm worker."
)
return
self.log.warning(
"Local resolver path does not exist: "
f"{local_path}. Falling back to lakeFS resolver download."
)
else:
self.log.info(
f"Using local resolver path for {self.app_name}: {local_path}"
)
self._setup_resolver(local_path, project_settings)
return

Copilot uses AI. Check for mistakes.

# On the farm, skip the lakeFS download — workers may not have access
if is_farm:
self.log.warning(
"No local resolver path configured for "
f"'{self.app_name}' on this platform. "
"Skipping lakeFS download on farm worker."
)
return

# Fall through to lakeFS-based resolver download
resolver_lake_fs_path = utils.get_resolver_to_download(
project_settings, self.app_name)
if not resolver_lake_fs_path:
Expand All @@ -52,8 +81,20 @@ def execute(self):
return

# Check for existing local resolver that matches the lakefs timestamp
with open(ADDON_DATA_JSON_PATH, "r") as data_json:
addon_data_json = json.load(data_json)
addon_data_json = {}
if os.path.exists(ADDON_DATA_JSON_PATH):
try:
with open(ADDON_DATA_JSON_PATH, "r") as data_json:
addon_data_json = json.load(data_json)
except (json.JSONDecodeError, OSError):
self.log.warning(
"Could not read addon data JSON, starting fresh."
)
addon_data_json = {}

if not addon_data_json:
# Ensure the downloads directory exists
os.makedirs(os.path.dirname(ADDON_DATA_JSON_PATH), exist_ok=True)

key = str(self.app_name).replace("/", "_")
local_resolver_key = f"resolver_data_{key}"
Expand Down
70 changes: 70 additions & 0 deletions client/ayon_usd/plugins/publish/collect_resolver_env_vars.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
"""Collect USD resolver environment variables for farm job submission.

Emits platform-independent config vars (TF_DEBUG, logger settings) into
the farm job environment. Path-based vars (PXR_PLUGINPATH_NAME, PYTHONPATH,
LD_LIBRARY_PATH) are NOT set here — they are injected by the
``pre_resolver_init`` hook which runs during ``extractenvironments`` on each
worker, producing paths correct for that worker's OS.
"""

import os

Comment on lines +10 to +11
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused import: os is imported but never referenced in this plugin. Please remove it to avoid lint failures and keep the module minimal.

Suggested change
import os

Copilot uses AI. Check for mistakes.
import pyblish.api

from ayon_core.pipeline.publish import FARM_JOB_ENV_DATA_KEY


class CollectResolverEnvVars(pyblish.api.InstancePlugin):
"""Collect USD resolver env vars for farm jobs.

Non-path settings (``TF_DEBUG``, logger config) are read directly
from the USD addon settings and added to the farm job environment.
"""

order = pyblish.api.CollectorOrder + 0.251
label = "Collect USD Resolver Env Vars (Farm Job)"

families = [
# Maya
"renderlayer",
# Houdini
"usdrender",
"publish.hou",
"remote_publish_on_farm",
"redshift_rop",
"arnold_rop",
"mantra_rop",
"karma_rop",
"vray_rop",
]
targets = ["local"]
hosts = ["maya", "houdini"]

def process(self, instance):
if not instance.data.get("farm"):
self.log.debug("Not a farm instance, skipping.")
return

settings = instance.context.data["project_settings"]
job_env = instance.data.setdefault(FARM_JOB_ENV_DATA_KEY, {})

# --- Non-path config (from USD addon settings) -------------------
usd_settings = settings.get("usd", {})
resolver_cfg = usd_settings.get("ayon_usd_resolver", {})
config_vars = {
"TF_DEBUG": usd_settings.get("usd", {}).get("usd_tf_debug", ""),
"AYONLOGGERLOGLVL": resolver_cfg.get("ayon_log_lvl", ""),
"AYONLOGGERSFILELOGGING": resolver_cfg.get(
"ayon_file_logger_enabled", ""
),
"AYONLOGGERSFILEPOS": resolver_cfg.get(
"file_logger_file_path", ""
),
"AYON_LOGGIN_LOGGIN_KEYS": resolver_cfg.get(
"ayon_logger_logging_keys", ""
),
}
for key, value in config_vars.items():
if value:
self.log.debug(f"Setting job env (config): {key}: {value}")
job_env[key] = str(value)
26 changes: 26 additions & 0 deletions client/ayon_usd/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,32 @@ def lakefs_download_and_extract(resolver_lake_fs_path: str,
return str(extract_zip_item.func_return)


def get_local_resolver_path(settings, app_name: str):
"""Check local_resolver_paths for a matching app + platform entry.

Args:
settings (dict): Project settings.
app_name (str): Application name, e.g. "houdini/20-5".

Returns:
str | None: Local filesystem path to the resolver directory,
or None if no match found.

"""
local_paths = (
settings["usd"]["distribution"].get("local_resolver_paths", [])
)
current_platform = platform.system().lower()
for entry in local_paths:
if entry["platform"] != current_platform:
continue
if entry["name"] == app_name or app_name in entry.get(
"app_alias_list", []
):
return entry["path"]
return None


def get_resolver_to_download(settings, app_name: str) -> str:
"""
Gets LakeFs path that can be used with copy element to download
Expand Down
2 changes: 1 addition & 1 deletion client/ayon_usd/version.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
"""Package declaring AYON addon 'usd' version."""
__version__ = "0.1.6+dev"
__version__ = "0.1.6+ls.0.0.1"
2 changes: 1 addition & 1 deletion package.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

name = "usd"
title = "USD"
version = "0.1.6+dev"
version = "0.1.6+ls.0.0.1"
client_dir = "ayon_usd"

services = {}
Expand Down
38 changes: 38 additions & 0 deletions server/settings/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,51 @@ class AppPlatformURIModel(BaseSettingsModel):
)


class LocalResolverPathModel(BaseSettingsModel):
"""Local filesystem path to a pre-installed resolver."""

_layout = "collapsed"
name: str = SettingsField(
title="App Name",
description="Application name, e.g. houdini/20-5",
)
app_alias_list: list[str] = SettingsField(
title="Application Alias",
description="Define a list of App Names that use the same "
"resolver as the parent application",
default_factory=list,
)
platform: str = SettingsField(
title="Platform",
enum_resolver=platform_enum,
description="windows / linux / darwin",
)
path: str = SettingsField(
title="Resolver Directory Path",
description=(
"Local filesystem path to the resolver directory "
"(the directory containing `ayonUsdResolver/`)"
),
)


class BinaryDistributionSettings(BaseSettingsModel):
"""Binary distribution of USD and AYON USD Resolver"""

_layout = "collapsed"

enabled: bool = SettingsField(False)

local_resolver_paths: list[LocalResolverPathModel] = SettingsField(
title="Local Resolver Paths",
description=(
"Local filesystem paths to pre-installed resolver binaries. "
"When a match is found for the current app and platform, "
"the resolver is loaded directly without downloading from lakeFS."
),
default_factory=list,
)

server_uri: str = SettingsField(
"https://lake.ayon.cloud",
title="Server URL",
Expand Down