Merge branch 'develop' into enhancement/usd_workflow_use_entity_uri

This commit is contained in:
Roy Nieterau 2024-07-26 14:12:22 +02:00 committed by GitHub
commit a8a0161799
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 497 additions and 225 deletions

View file

@ -5,6 +5,7 @@ import sys
import code
import traceback
from pathlib import Path
import warnings
import click
import acre
@ -18,7 +19,6 @@ from ayon_core.lib import (
Logger,
)
from .cli_commands import Commands
class AliasedGroup(click.Group):
@ -116,14 +116,25 @@ def extractenvironments(
This function is deprecated and will be removed in future. Please use
'addon applications extractenvironments ...' instead.
"""
Commands.extractenvironments(
output_json_path,
project,
asset,
task,
app,
envgroup,
ctx.obj["addons_manager"]
warnings.warn(
(
"Command 'extractenvironments' is deprecated and will be"
" removed in future. Please use"
" 'addon applications extractenvironments ...' instead."
),
DeprecationWarning
)
addons_manager = ctx.obj["addons_manager"]
applications_addon = addons_manager.get_enabled_addon("applications")
if applications_addon is None:
raise RuntimeError(
"Applications addon is not available or enabled."
)
# Please ignore the fact this is using private method
applications_addon._cli_extract_environments(
output_json_path, project, asset, task, app, envgroup
)
@ -132,15 +143,15 @@ def extractenvironments(
@click.argument("path", required=True)
@click.option("-t", "--targets", help="Targets", default=None,
multiple=True)
@click.option("-g", "--gui", is_flag=True,
help="Show Publish UI", default=False)
def publish(ctx, path, targets, gui):
def publish(ctx, path, targets):
"""Start CLI publishing.
Publish collects json from path provided as an argument.
"""
Commands.publish(path, targets, gui, ctx.obj["addons_manager"])
from ayon_core.pipeline.publish import main_cli_publish
main_cli_publish(path, targets, ctx.obj["addons_manager"])
@main_cli.command(context_settings={"ignore_unknown_options": True})
@ -170,12 +181,10 @@ def contextselection(
Context is project name, folder path and task name. The result is stored
into json file which path is passed in first argument.
"""
Commands.contextselection(
output_path,
project,
folder,
strict
)
from ayon_core.tools.context_dialog import main
main(output_path, project, folder, strict)
@main_cli.command(

View file

@ -1,171 +0,0 @@
# -*- coding: utf-8 -*-
"""Implementation of AYON commands."""
import os
import sys
import warnings
from typing import Optional, List
from ayon_core.addon import AddonsManager
class Commands:
"""Class implementing commands used by AYON.
Most of its methods are called by :mod:`cli` module.
"""
@staticmethod
def publish(
path: str,
targets: Optional[List[str]] = None,
gui: Optional[bool] = False,
addons_manager: Optional[AddonsManager] = None,
) -> None:
"""Start headless publishing.
Publish use json from passed path argument.
Args:
path (str): Path to JSON.
targets (Optional[List[str]]): List of pyblish targets.
gui (Optional[bool]): Show publish UI.
addons_manager (Optional[AddonsManager]): Addons manager instance.
Raises:
RuntimeError: When there is no path to process.
RuntimeError: When executed with list of JSON paths.
"""
from ayon_core.lib import Logger
from ayon_core.addon import AddonsManager
from ayon_core.pipeline import (
install_ayon_plugins,
get_global_context,
)
import ayon_api
import pyblish.util
# Register target and host
if not isinstance(path, str):
raise RuntimeError("Path to JSON must be a string.")
# Fix older jobs
for src_key, dst_key in (
("AVALON_PROJECT", "AYON_PROJECT_NAME"),
("AVALON_ASSET", "AYON_FOLDER_PATH"),
("AVALON_TASK", "AYON_TASK_NAME"),
("AVALON_WORKDIR", "AYON_WORKDIR"),
("AVALON_APP_NAME", "AYON_APP_NAME"),
("AVALON_APP", "AYON_HOST_NAME"),
):
if src_key in os.environ and dst_key not in os.environ:
os.environ[dst_key] = os.environ[src_key]
# Remove old keys, so we're sure they're not used
os.environ.pop(src_key, None)
log = Logger.get_logger("CLI-publish")
# Make public ayon api behave as other user
# - this works only if public ayon api is using service user
username = os.environ.get("AYON_USERNAME")
if username:
# NOTE: ayon-python-api does not have public api function to find
# out if is used service user. So we need to have try > except
# block.
con = ayon_api.get_server_api_connection()
try:
con.set_default_service_username(username)
except ValueError:
pass
install_ayon_plugins()
if addons_manager is None:
addons_manager = AddonsManager()
publish_paths = addons_manager.collect_plugin_paths()["publish"]
for plugin_path in publish_paths:
pyblish.api.register_plugin_path(plugin_path)
applications_addon = addons_manager.get_enabled_addon("applications")
if applications_addon is not None:
context = get_global_context()
env = applications_addon.get_farm_publish_environment_variables(
context["project_name"],
context["folder_path"],
context["task_name"],
)
os.environ.update(env)
pyblish.api.register_host("shell")
if targets:
for target in targets:
print(f"setting target: {target}")
pyblish.api.register_target(target)
else:
pyblish.api.register_target("farm")
os.environ["AYON_PUBLISH_DATA"] = path
os.environ["HEADLESS_PUBLISH"] = 'true' # to use in app lib
log.info("Running publish ...")
plugins = pyblish.api.discover()
print("Using plugins:")
for plugin in plugins:
print(plugin)
if gui:
from ayon_core.tools.utils.host_tools import show_publish
from ayon_core.tools.utils.lib import qt_app_context
with qt_app_context():
show_publish()
else:
# Error exit as soon as any error occurs.
error_format = ("Failed {plugin.__name__}: "
"{error} -- {error.traceback}")
for result in pyblish.util.publish_iter():
if result["error"]:
log.error(error_format.format(**result))
# uninstall()
sys.exit(1)
log.info("Publish finished.")
@staticmethod
def extractenvironments(
output_json_path, project, asset, task, app, env_group, addons_manager
):
"""Produces json file with environment based on project and app.
Called by Deadline plugin to propagate environment into render jobs.
"""
warnings.warn(
(
"Command 'extractenvironments' is deprecated and will be"
" removed in future. Please use "
"'addon applications extractenvironments ...' instead."
),
DeprecationWarning
)
applications_addon = addons_manager.get_enabled_addon("applications")
if applications_addon is None:
raise RuntimeError(
"Applications addon is not available or enabled."
)
# Please ignore the fact this is using private method
applications_addon._cli_extract_environments(
output_json_path, project, asset, task, app, env_group
)
@staticmethod
def contextselection(output_path, project_name, folder_path, strict):
from ayon_core.tools.context_dialog import main
main(output_path, project_name, folder_path, strict)

View file

@ -0,0 +1,72 @@
import re
from ayon_applications import PreLaunchHook, LaunchTypes
from ayon_core.lib import filter_profiles
class FilterFarmEnvironments(PreLaunchHook):
"""Filter or modify calculated environment variables for farm rendering.
This hook must run last, only after all other hooks are finished to get
correct environment for launch context.
Implemented modifications to self.launch_context.env:
- skipping (list) of environment variable keys
- removing value in environment variable:
- supports regular expression in pattern
"""
order = 1000
launch_types = {LaunchTypes.farm_publish}
def execute(self):
data = self.launch_context.data
project_settings = data["project_settings"]
filter_env_profiles = (
project_settings["core"]["filter_env_profiles"])
if not filter_env_profiles:
self.log.debug("No profiles found for env var filtering")
return
task_entity = data["task_entity"]
filter_data = {
"host_names": self.host_name,
"task_types": task_entity["taskType"],
"task_names": task_entity["name"],
"folder_paths": data["folder_path"]
}
matching_profile = filter_profiles(
filter_env_profiles, filter_data, logger=self.log
)
if not matching_profile:
self.log.debug("No matching profile found for env var filtering "
f"for {filter_data}")
return
self._skip_environment_variables(
self.launch_context.env, matching_profile)
self._modify_environment_variables(
self.launch_context.env, matching_profile)
def _modify_environment_variables(self, calculated_env, matching_profile):
"""Modify environment variable values."""
for env_item in matching_profile["replace_in_environment"]:
key = env_item["environment_key"]
value = calculated_env.get(key)
if not value:
continue
value = re.sub(value, env_item["pattern"], env_item["replacement"])
if value:
calculated_env[key] = value
else:
calculated_env.pop(key)
def _skip_environment_variables(self, calculated_env, matching_profile):
"""Skips list of environment variable names"""
for skip_env in matching_profile["skip_env_keys"]:
self.log.info(f"Skipping {skip_env}")
calculated_env.pop(skip_env)

View file

@ -42,6 +42,8 @@ from .lib import (
get_plugin_settings,
get_publish_instance_label,
get_publish_instance_families,
main_cli_publish,
)
from .abstract_expected_files import ExpectedFiles
@ -92,6 +94,8 @@ __all__ = (
"get_publish_instance_label",
"get_publish_instance_families",
"main_cli_publish",
"ExpectedFiles",
"RenderInstance",

View file

@ -4,8 +4,9 @@ import inspect
import copy
import tempfile
import xml.etree.ElementTree
from typing import Optional, Union
from typing import Optional, Union, List
import ayon_api
import pyblish.util
import pyblish.plugin
import pyblish.api
@ -16,6 +17,7 @@ from ayon_core.lib import (
filter_profiles,
)
from ayon_core.settings import get_project_settings
from ayon_core.addon import AddonsManager
from ayon_core.pipeline import (
tempdir,
Anatomy
@ -978,3 +980,113 @@ def get_instance_expected_output_path(
path_template_obj = anatomy.get_template_item("publish", "default")["path"]
template_filled = path_template_obj.format_strict(template_data)
return os.path.normpath(template_filled)
def main_cli_publish(
path: str,
targets: Optional[List[str]] = None,
addons_manager: Optional[AddonsManager] = None,
):
"""Start headless publishing.
Publish use json from passed path argument.
Args:
path (str): Path to JSON.
targets (Optional[List[str]]): List of pyblish targets.
addons_manager (Optional[AddonsManager]): Addons manager instance.
Raises:
RuntimeError: When there is no path to process or when executed with
list of JSON paths.
"""
from ayon_core.pipeline import (
install_ayon_plugins,
get_global_context,
)
# Register target and host
if not isinstance(path, str):
raise RuntimeError("Path to JSON must be a string.")
# Fix older jobs
for src_key, dst_key in (
("AVALON_PROJECT", "AYON_PROJECT_NAME"),
("AVALON_ASSET", "AYON_FOLDER_PATH"),
("AVALON_TASK", "AYON_TASK_NAME"),
("AVALON_WORKDIR", "AYON_WORKDIR"),
("AVALON_APP_NAME", "AYON_APP_NAME"),
("AVALON_APP", "AYON_HOST_NAME"),
):
if src_key in os.environ and dst_key not in os.environ:
os.environ[dst_key] = os.environ[src_key]
# Remove old keys, so we're sure they're not used
os.environ.pop(src_key, None)
log = Logger.get_logger("CLI-publish")
# Make public ayon api behave as other user
# - this works only if public ayon api is using service user
username = os.environ.get("AYON_USERNAME")
if username:
# NOTE: ayon-python-api does not have public api function to find
# out if is used service user. So we need to have try > except
# block.
con = ayon_api.get_server_api_connection()
try:
con.set_default_service_username(username)
except ValueError:
pass
install_ayon_plugins()
if addons_manager is None:
addons_manager = AddonsManager()
# TODO validate if this has to happen
# - it should happen during 'install_ayon_plugins'
publish_paths = addons_manager.collect_plugin_paths()["publish"]
for plugin_path in publish_paths:
pyblish.api.register_plugin_path(plugin_path)
applications_addon = addons_manager.get_enabled_addon("applications")
if applications_addon is not None:
context = get_global_context()
env = applications_addon.get_farm_publish_environment_variables(
context["project_name"],
context["folder_path"],
context["task_name"],
)
os.environ.update(env)
pyblish.api.register_host("shell")
if targets:
for target in targets:
print(f"setting target: {target}")
pyblish.api.register_target(target)
else:
pyblish.api.register_target("farm")
os.environ["AYON_PUBLISH_DATA"] = path
os.environ["HEADLESS_PUBLISH"] = 'true' # to use in app lib
log.info("Running publish ...")
plugins = pyblish.api.discover()
print("Using plugins:")
for plugin in plugins:
print(plugin)
# Error exit as soon as any error occurs.
error_format = ("Failed {plugin.__name__}: "
"{error} -- {error.traceback}")
for result in pyblish.util.publish_iter():
if result["error"]:
log.error(error_format.format(**result))
# uninstall()
sys.exit(1)
log.info("Publish finished.")

View file

@ -1,4 +1,5 @@
from abc import ABC, abstractmethod
from typing import List
from ayon_core.lib.attribute_definitions import (
AbstractAttrDef,
@ -13,19 +14,16 @@ class ProductTypeItem:
Args:
name (str): Product type name.
icon (dict[str, Any]): Product type icon definition.
checked (bool): Is product type checked for filtering.
"""
def __init__(self, name, icon, checked):
def __init__(self, name, icon):
self.name = name
self.icon = icon
self.checked = checked
def to_data(self):
return {
"name": self.name,
"icon": self.icon,
"checked": self.checked,
}
@classmethod
@ -346,6 +344,16 @@ class ActionItem:
return cls(**data)
class ProductTypesFilter:
"""Product types filter.
Defines the filtering for product types.
"""
def __init__(self, product_types: List[str], is_allow_list: bool):
self.product_types: List[str] = product_types
self.is_allow_list: bool = is_allow_list
class _BaseLoaderController(ABC):
"""Base loader controller abstraction.
@ -1006,3 +1014,13 @@ class FrontendLoaderController(_BaseLoaderController):
"""
pass
@abstractmethod
def get_product_types_filter(self):
"""Return product type filter for current context.
Returns:
ProductTypesFilter: Product type filter for current context
"""
pass

View file

@ -3,7 +3,9 @@ import uuid
import ayon_api
from ayon_core.lib import NestedCacheItem, CacheItem
from ayon_core.settings import get_project_settings
from ayon_core.pipeline import get_current_host_name
from ayon_core.lib import NestedCacheItem, CacheItem, filter_profiles
from ayon_core.lib.events import QueuedEventSystem
from ayon_core.pipeline import Anatomy, get_current_context
from ayon_core.host import ILoadHost
@ -13,7 +15,11 @@ from ayon_core.tools.common_models import (
ThumbnailsModel,
)
from .abstract import BackendLoaderController, FrontendLoaderController
from .abstract import (
BackendLoaderController,
FrontendLoaderController,
ProductTypesFilter
)
from .models import (
SelectionModel,
ProductsModel,
@ -331,11 +337,11 @@ class LoaderController(BackendLoaderController, FrontendLoaderController):
project_name = context.get("project_name")
folder_path = context.get("folder_path")
if project_name and folder_path:
folder = ayon_api.get_folder_by_path(
folder_entity = ayon_api.get_folder_by_path(
project_name, folder_path, fields=["id"]
)
if folder:
folder_id = folder["id"]
if folder_entity:
folder_id = folder_entity["id"]
return {
"project_name": project_name,
"folder_id": folder_id,
@ -425,3 +431,59 @@ class LoaderController(BackendLoaderController, FrontendLoaderController):
def _emit_event(self, topic, data=None):
self._event_system.emit(topic, data or {}, "controller")
def get_product_types_filter(self):
output = ProductTypesFilter(
is_allow_list=False,
product_types=[]
)
# Without host is not determined context
if self._host is None:
return output
context = self.get_current_context()
project_name = context.get("project_name")
if not project_name:
return output
settings = get_project_settings(project_name)
profiles = (
settings
["core"]
["tools"]
["loader"]
["product_type_filter_profiles"]
)
if not profiles:
return output
folder_id = context.get("folder_id")
task_name = context.get("task_name")
task_type = None
if folder_id and task_name:
task_entity = ayon_api.get_task_by_name(
project_name,
folder_id,
task_name,
fields={"taskType"}
)
if task_entity:
task_type = task_entity.get("taskType")
host_name = getattr(self._host, "name", get_current_host_name())
profile = filter_profiles(
profiles,
{
"hosts": host_name,
"task_types": task_type,
}
)
if profile:
# TODO remove 'is_include' after release '0.4.3'
is_allow_list = profile.get("is_include")
if is_allow_list is None:
is_allow_list = profile["filter_type"] == "is_allow_list"
output = ProductTypesFilter(
is_allow_list=is_allow_list,
product_types=profile["filter_product_types"]
)
return output

View file

@ -123,7 +123,7 @@ def product_type_item_from_data(product_type_data):
"color": "#0091B2",
}
# TODO implement checked logic
return ProductTypeItem(product_type_data["name"], icon, True)
return ProductTypeItem(product_type_data["name"], icon)
def create_default_product_type_item(product_type):
@ -132,7 +132,7 @@ def create_default_product_type_item(product_type):
"name": "fa.folder",
"color": "#0091B2",
}
return ProductTypeItem(product_type, icon, True)
return ProductTypeItem(product_type, icon)
class ProductsModel:

View file

@ -13,10 +13,17 @@ class ProductTypesQtModel(QtGui.QStandardItemModel):
super(ProductTypesQtModel, self).__init__()
self._controller = controller
self._reset_filters_on_refresh = True
self._refreshing = False
self._bulk_change = False
self._last_project = None
self._items_by_name = {}
controller.register_event_callback(
"controller.reset.finished",
self._on_controller_reset_finish,
)
def is_refreshing(self):
return self._refreshing
@ -37,14 +44,19 @@ class ProductTypesQtModel(QtGui.QStandardItemModel):
self._refreshing = True
product_type_items = self._controller.get_product_type_items(
project_name)
self._last_project = project_name
items_to_remove = set(self._items_by_name.keys())
new_items = []
items_filter_required = {}
for product_type_item in product_type_items:
name = product_type_item.name
items_to_remove.discard(name)
item = self._items_by_name.get(product_type_item.name)
item = self._items_by_name.get(name)
# Apply filter to new items or if filters reset is requested
filter_required = self._reset_filters_on_refresh
if item is None:
filter_required = True
item = QtGui.QStandardItem(name)
item.setData(name, PRODUCT_TYPE_ROLE)
item.setEditable(False)
@ -52,14 +64,26 @@ class ProductTypesQtModel(QtGui.QStandardItemModel):
new_items.append(item)
self._items_by_name[name] = item
item.setCheckState(
QtCore.Qt.Checked
if product_type_item.checked
else QtCore.Qt.Unchecked
)
if filter_required:
items_filter_required[name] = item
icon = get_qt_icon(product_type_item.icon)
item.setData(icon, QtCore.Qt.DecorationRole)
if items_filter_required:
product_types_filter = self._controller.get_product_types_filter()
for product_type, item in items_filter_required.items():
matching = (
int(product_type in product_types_filter.product_types)
+ int(product_types_filter.is_allow_list)
)
state = (
QtCore.Qt.Checked
if matching % 2 == 0
else QtCore.Qt.Unchecked
)
item.setCheckState(state)
root_item = self.invisibleRootItem()
if new_items:
root_item.appendRows(new_items)
@ -68,9 +92,13 @@ class ProductTypesQtModel(QtGui.QStandardItemModel):
item = self._items_by_name.pop(name)
root_item.removeRow(item.row())
self._reset_filters_on_refresh = False
self._refreshing = False
self.refreshed.emit()
def reset_product_types_filter_on_refresh(self):
self._reset_filters_on_refresh = True
def setData(self, index, value, role=None):
checkstate_changed = False
if role is None:
@ -122,6 +150,9 @@ class ProductTypesQtModel(QtGui.QStandardItemModel):
if changed:
self.filter_changed.emit()
def _on_controller_reset_finish(self):
self.refresh(self._last_project)
class ProductTypesView(QtWidgets.QListView):
filter_changed = QtCore.Signal()
@ -151,6 +182,7 @@ class ProductTypesView(QtWidgets.QListView):
)
self._controller = controller
self._refresh_product_types_filter = False
self._product_types_model = product_types_model
self._product_types_proxy_model = product_types_proxy_model
@ -158,11 +190,15 @@ class ProductTypesView(QtWidgets.QListView):
def get_filter_info(self):
return self._product_types_model.get_filter_info()
def reset_product_types_filter_on_refresh(self):
self._product_types_model.reset_product_types_filter_on_refresh()
def _on_project_change(self, event):
project_name = event["project_name"]
self._product_types_model.refresh(project_name)
def _on_refresh_finished(self):
# Apply product types filter on first show
self.filter_changed.emit()
def _on_filter_change(self):

View file

@ -345,6 +345,8 @@ class LoaderWindow(QtWidgets.QWidget):
def closeEvent(self, event):
super(LoaderWindow, self).closeEvent(event)
self._product_types_widget.reset_product_types_filter_on_refresh()
self._reset_on_show = True
def keyPressEvent(self, event):

View file

@ -356,6 +356,39 @@ def is_tray_running(
return state != TrayState.NOT_RUNNING
def show_message_in_tray(
title, message, icon=None, msecs=None, tray_url=None
):
"""Show message in tray.
Args:
title (str): Message title.
message (str): Message content.
icon (Optional[Literal["information", "warning", "critical"]]): Icon
for the message.
msecs (Optional[int]): Duration of the message.
tray_url (Optional[str]): Tray server url.
"""
if not tray_url:
tray_url = get_tray_server_url()
# TODO handle this case, e.g. raise an error?
if not tray_url:
return
# TODO handle response, can fail whole request or can fail on status
requests.post(
f"{tray_url}/tray/message",
json={
"title": title,
"message": message,
"icon": icon,
"msecs": msecs
}
)
def make_sure_tray_is_running(
ayon_url: Optional[str] = None,
variant: Optional[str] = None,
@ -412,6 +445,10 @@ def main(force=False):
state = TrayState.NOT_RUNNING
if state == TrayState.RUNNING:
show_message_in_tray(
"Tray is already running",
"Your AYON tray application is already running."
)
print("Tray is already running.")
return

View file

@ -3,12 +3,11 @@ import sys
import time
import collections
import atexit
import json
import platform
from aiohttp.web_response import Response
import ayon_api
from qtpy import QtCore, QtGui, QtWidgets
from aiohttp.web import Response, json_response, Request
from ayon_core import resources, style
from ayon_core.lib import (
@ -91,6 +90,10 @@ class TrayManager:
self._services_submenu = None
self._start_time = time.time()
# Cache AYON username used in process
# - it can change only by changing ayon_api global connection
# should be safe for tray application to cache the value only once
self._cached_username = None
self._closing = False
try:
set_tray_server_url(
@ -133,6 +136,7 @@ class TrayManager:
kwargs["msecs"] = msecs
self.tray_widget.showMessage(*args, **kwargs)
# TODO validate 'self.tray_widget.supportsMessages()'
def initialize_addons(self):
"""Add addons to tray."""
@ -143,7 +147,10 @@ class TrayManager:
self._addons_manager.initialize(tray_menu)
self._addons_manager.add_route(
"GET", "/tray", self._get_web_tray_info
"GET", "/tray", self._web_get_tray_info
)
self._addons_manager.add_route(
"POST", "/tray/message", self._web_show_tray_message
)
admin_submenu = ITrayAction.admin_submenu(tray_menu)
@ -274,8 +281,12 @@ class TrayManager:
return item
async def _get_web_tray_info(self, request):
return Response(text=json.dumps({
async def _web_get_tray_info(self, _request: Request) -> Response:
if self._cached_username is None:
self._cached_username = ayon_api.get_user()["name"]
return json_response({
"username": self._cached_username,
"bundle": os.getenv("AYON_BUNDLE_NAME"),
"dev_mode": is_dev_mode_enabled(),
"staging_mode": is_staging_enabled(),
@ -285,7 +296,37 @@ class TrayManager:
},
"installer_version": os.getenv("AYON_VERSION"),
"running_time": time.time() - self._start_time,
}))
})
async def _web_show_tray_message(self, request: Request) -> Response:
data = await request.json()
try:
title = data["title"]
message = data["message"]
icon = data.get("icon")
msecs = data.get("msecs")
except KeyError as exc:
return json_response(
{
"error": f"Missing required data. {exc}",
"success": False,
},
status=400,
)
if icon == "information":
icon = QtWidgets.QSystemTrayIconInformation
elif icon == "warning":
icon = QtWidgets.QSystemTrayIconWarning
elif icon == "critical":
icon = QtWidgets.QSystemTrayIcon.Critical
else:
icon = None
self.execute_in_main_thread(
self.show_tray_message, title, message, icon, msecs
)
return json_response({"success": True})
def _on_update_check_timer(self):
try:

View file

@ -169,6 +169,46 @@ class VersionStartCategoryModel(BaseSettingsModel):
)
class EnvironmentReplacementModel(BaseSettingsModel):
environment_key: str = SettingsField("", title="Enviroment variable")
pattern: str = SettingsField("", title="Pattern")
replacement: str = SettingsField("", title="Replacement")
class FilterEnvsProfileModel(BaseSettingsModel):
_layout = "expanded"
host_names: list[str] = SettingsField(
default_factory=list,
title="Host names"
)
task_types: list[str] = SettingsField(
default_factory=list,
title="Task types",
enum_resolver=task_types_enum
)
task_names: list[str] = SettingsField(
default_factory=list,
title="Task names"
)
folder_paths: list[str] = SettingsField(
default_factory=list,
title="Folder paths"
)
skip_env_keys: list[str] = SettingsField(
default_factory=list,
title="Skip environment variables"
)
replace_in_environment: list[EnvironmentReplacementModel] = SettingsField(
default_factory=list,
title="Replace values in environment"
)
class CoreSettings(BaseSettingsModel):
studio_name: str = SettingsField("", title="Studio name", scope=["studio"])
studio_code: str = SettingsField("", title="Studio code", scope=["studio"])
@ -219,6 +259,9 @@ class CoreSettings(BaseSettingsModel):
title="Project environments",
section="---"
)
filter_env_profiles: list[FilterEnvsProfileModel] = SettingsField(
default_factory=list,
)
@validator(
"environments",
@ -313,5 +356,6 @@ DEFAULT_VALUES = {
"project_environments": json.dumps(
{},
indent=4
)
),
"filter_env_profiles": [],
}

View file

@ -195,6 +195,7 @@ def _product_types_enum():
"editorial",
"gizmo",
"image",
"imagesequence",
"layout",
"look",
"matchmove",
@ -212,7 +213,6 @@ def _product_types_enum():
"setdress",
"take",
"usd",
"usdShade",
"vdbcache",
"vrayproxy",
"workfile",
@ -222,6 +222,13 @@ def _product_types_enum():
]
def filter_type_enum():
return [
{"value": "is_allow_list", "label": "Allow list"},
{"value": "is_deny_list", "label": "Deny list"},
]
class LoaderProductTypeFilterProfile(BaseSettingsModel):
_layout = "expanded"
# TODO this should use hosts enum
@ -231,9 +238,15 @@ class LoaderProductTypeFilterProfile(BaseSettingsModel):
title="Task types",
enum_resolver=task_types_enum
)
is_include: bool = SettingsField(True, title="Exclude / Include")
filter_type: str = SettingsField(
"is_allow_list",
title="Filter type",
section="Product type filter",
enum_resolver=filter_type_enum
)
filter_product_types: list[str] = SettingsField(
default_factory=list,
title="Product types",
enum_resolver=_product_types_enum
)
@ -499,14 +512,7 @@ DEFAULT_TOOLS_VALUES = {
"workfile_lock_profiles": []
},
"loader": {
"product_type_filter_profiles": [
{
"hosts": [],
"task_types": [],
"is_include": True,
"filter_product_types": []
}
]
"product_type_filter_profiles": []
},
"publish": {
"template_name_profiles": [