Skip to content

Commit

Permalink
Merge pull request #1 from ynput/feature/AY-5949_Applications-addon-d…
Browse files Browse the repository at this point in the history
…efines-webactions

Define WebActions
  • Loading branch information
iLLiCiTiT authored Sep 6, 2024
2 parents 696623c + c2248d6 commit abd4bce
Show file tree
Hide file tree
Showing 11 changed files with 574 additions and 80 deletions.
19 changes: 18 additions & 1 deletion client/ayon_applications/action.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import copy

from ayon_core import resources
from ayon_core.lib import Logger, NestedCacheItem
from ayon_core.lib import (
Logger,
NestedCacheItem,
)
from ayon_core.settings import get_studio_settings, get_project_settings
from ayon_core.pipeline.actions import LauncherAction

Expand Down Expand Up @@ -132,3 +135,17 @@ def process(self, selection, **kwargs):
msg = str(exc)
self.log.warning(msg, exc_info=True)
self._show_message_box("Application launch failed", msg)

# TODO use subprocess
# - Launch using subprocess is slower because of AYON launcher bootstrap
# for now was this functionality postponed, but it means the process
# initialization does not happen.
# def launch(self, selection):
# args = get_ayon_launcher_args(
# "addon", "applications", "launch",
# "--project", selection.project_name,
# "--folder", selection.folder_path,
# "--task", selection.task_name,
# "--app", self.application.full_name
# )
# run_detached_process(args)
130 changes: 118 additions & 12 deletions client/ayon_applications/addon.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,31 @@
import os
import sys
import json
import traceback
import tempfile

import ayon_api

from ayon_core.addon import AYONAddon, IPluginPaths, click_wrap
from ayon_core.lib import (
run_ayon_launcher_process,
is_headless_mode_enabled,
)
from ayon_core.addon import (
AYONAddon,
IPluginPaths,
click_wrap,
ensure_addons_are_process_ready,
)

from .version import __version__
from .constants import APPLICATIONS_ADDON_ROOT
from .defs import LaunchTypes
from .manager import ApplicationManager
from .exceptions import (
ApplicationLaunchFailed,
ApplicationExecutableNotFound,
ApplicationNotFound,
)
from .utils import get_app_icon_path


Expand Down Expand Up @@ -215,13 +232,49 @@ def launch_application(
task_name (str): Task name.
"""
app_manager = self.get_applications_manager()
return app_manager.launch(
app_name,
ensure_addons_are_process_ready(
addon_name=self.name,
addon_version=self.version,
project_name=project_name,
folder_path=folder_path,
task_name=task_name,
)
headless = is_headless_mode_enabled()

# TODO handle raise errors
failed = True
message = None
detail = None
try:
app_manager = self.get_applications_manager()
app_manager.launch(
app_name,
project_name=project_name,
folder_path=folder_path,
task_name=task_name,
)
failed = False

except (
ApplicationLaunchFailed,
ApplicationExecutableNotFound,
ApplicationNotFound,
) as exc:
message = str(exc)
self.log.warning(f"Application launch failed: {message}")

except Exception as exc:
message = "An unexpected error happened"
detail = "".join(traceback.format_exception(*sys.exc_info()))
self.log.warning(
f"Application launch failed: {str(exc)}",
exc_info=True
)

if not failed:
return

if not headless:
self._show_launch_error_dialog(message, detail)
sys.exit(1)

def webserver_initialization(self, manager):
"""Initialize webserver.
Expand Down Expand Up @@ -252,7 +305,7 @@ def cli(self, addon_click_group):
.option("--project", help="Project name", default=None)
.option("--folder", help="Folder path", default=None)
.option("--task", help="Task name", default=None)
.option("--app", help="Application name", default=None)
.option("--app", help="Full application name", default=None)
.option(
"--envgroup",
help="Environment group (e.g. \"farm\")",
Expand All @@ -261,16 +314,27 @@ def cli(self, addon_click_group):
)
(
main_group.command(
self._cli_launch_applications,
self._cli_launch_context_names,
name="launch",
help="Launch application"
)
.option("--app", required=True, help="Application name")
.option("--app", required=True, help="Full application name")
.option("--project", required=True, help="Project name")
.option("--folder", required=True, help="Folder path")
.option("--task", required=True, help="Task name")
)
# Convert main command to click object and add it to parent group
(
main_group.command(
self._cli_launch_with_task_id,
name="launch-by-id",
help="Launch application"
)
.option("--app", required=True, help="Full application name")
.option("--project", required=True, help="Project name")
.option("--task-id", required=True, help="Task id")
)
# Convert main command to click object and add it to parent group
addon_click_group.add_command(
main_group.to_click_obj()
)
Expand Down Expand Up @@ -302,13 +366,12 @@ def _cli_extract_environments(
env = os.environ.copy()

output_dir = os.path.dirname(output_json_path)
if not os.path.exists(output_dir):
os.makedirs(output_dir)
os.makedirs(output_dir, exist_ok=True)

with open(output_json_path, "w") as file_stream:
json.dump(env, file_stream, indent=4)

def _cli_launch_applications(self, project, folder, task, app):
def _cli_launch_context_names(self, project, folder, task, app):
"""Launch application.
Args:
Expand All @@ -319,3 +382,46 @@ def _cli_launch_applications(self, project, folder, task, app):
"""
self.launch_application(app, project, folder, task)


def _cli_launch_with_task_id(self, project, task_id, app):
"""Launch application.
Args:
project (str): Project name.
task_id (str): Task id.
app (str): Full application name e.g. 'maya/2024'.
"""
task_entity = ayon_api.get_task_by_id(
project, task_id, fields={"name", "folderId"}
)
folder_entity = ayon_api.get_folder_by_id(
project, task_entity["folderId"], fields={"path"}
)
self.launch_application(
app, project, folder_entity["path"], task_entity["name"]
)

def _show_launch_error_dialog(self, message, detail):
script_path = os.path.join(
APPLICATIONS_ADDON_ROOT, "ui", "launch_failed_dialog.py"
)
with tempfile.NamedTemporaryFile("w", delete=False) as tmp:
tmp_path = tmp.name
json.dump(
{"message": message, "detail": detail},
tmp.file
)

try:
run_ayon_launcher_process(
"--skip-bootstrap",
script_path,
tmp_path,
add_sys_paths=True,
creationflags=0,
)

finally:
os.remove(tmp_path)
55 changes: 55 additions & 0 deletions client/ayon_applications/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,58 @@

PLATFORM_NAMES = {"windows", "linux", "darwin"}
DEFAULT_ENV_SUBGROUP = "standard"

LABELS_BY_GROUP_NAME = {
"adsk_3dsmax": "3ds Max",
"aftereffects": "After Effects",
"blender": "Blender",
"celaction": "Celaction 2D",
"equalizer": "3DEqualizer",
"flame": "Flame",
"fusion": "Fusion",
"harmony": "Harmony",
"hiero": "Hiero",
"houdini": "Houdini",
"maya": "Maya",
"motionbuilder": "Motion Builder",
"nuke": "Nuke",
"nukeassist": "Nuke Assist",
"nukestudio": "Nuke Studio",
"nukex": "Nuke X",
"openrv": "OpenRV",
"photoshop": "Photoshop",
"resolve": "Resolve",
"terminal": "Terminal",
"substancepainter": "Substance Painter",
"tvpaint": "TVPaint",
"unreal": "Unreal Editor",
"wrap": "Wrap",
"zbrush": "Zbrush",
}
ICONS_BY_GROUP_NAME = {
"adsk_3dsmax": "3dsmax.png",
"aftereffects": "aftereffects.png",
"blender": "blender.png",
"celaction": "celaction.png",
"equalizer": "3de4.png",
"flame": "flame.png",
"fusion": "fusion.png",
"harmony": "harmony.png",
"hiero": "hiero.png",
"houdini": "houdini.png",
"maya": "maya.png",
"motionbuilder": "motionbuilder.png",
"nuke": "nuke.png",
"nukeassist": "nuke.png",
"nukestudio": "nukestudio.png",
"nukex": "nukex.png",
"openrv": "openrv.png",
"photoshop": "photoshop.png",
"resolve": "resolve.png",
"substancepainter": "substancepainter.png",
"terminal": "terminal.png",
"tvpaint": "tvpaint.png",
"unreal": "ue4.png",
"wrap": "wrap.png",
"zbrush": "zbrush.png",
}
60 changes: 3 additions & 57 deletions client/ayon_applications/defs.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import copy

from ayon_core.lib import find_executable
from .constants import LABELS_BY_GROUP_NAME, ICONS_BY_GROUP_NAME


class LaunchTypes:
Expand Down Expand Up @@ -152,67 +153,12 @@ class ApplicationGroup:
data (dict): Group defying data loaded from settings.
manager (ApplicationManager): Manager that created the group.
"""
icons_by_group = {
"adsk_3dsmax": "3dsmax.png",
"aftereffects": "aftereffects.png",
"blender": "blender.png",
"celaction": "celaction.png",
"equalizer": "3de4.png",
"flame": "flame.png",
"fusion": "fusion.png",
"harmony": "harmony.png",
"hiero": "hiero.png",
"houdini": "houdini.png",
"maya": "maya.png",
"motionbuilder": "motionbuilder.png",
"nuke": "nuke.png",
"nukeassist": "nuke.png",
"nukestudio": "nukestudio.png",
"nukex": "nukex.png",
"openrv": "openrv.png",
"photoshop": "photoshop.png",
"resolve": "resolve.png",
"substancepainter": "substancepainter.png",
"terminal": "terminal.png",
"tvpaint": "tvpaint.png",
"unreal": "ue4.png",
"wrap": "wrap.png",
"zbrush": "zbrush.png",
}
labels_by_group = {
"adsk_3dsmax": "3ds Max",
"aftereffects": "After Effects",
"blender": "Blender",
"celaction": "Celaction 2D",
"equalizer": "3DEqualizer",
"flame": "Flame",
"fusion": "Fusion",
"harmony": "Harmony",
"hiero": "Hiero",
"houdini": "Houdini",
"maya": "Maya",
"motionbuilder": "Motion Builder",
"nuke": "Nuke",
"nukeassist": "Nuke Assist",
"nukestudio": "Nuke Studio",
"nukex": "Nuke X",
"openrv": "OpenRV",
"photoshop": "Photoshop",
"resolve": "Resolve",
"substancepainter": "Substance Painter",
"terminal": "Terminal",
"tvpaint": "TVPaint",
"unreal": "Unreal Editor",
"wrap": "Wrap",
"zbrush": "Zbrush",
}

def __init__(self, name, data, manager):
icon = self.icons_by_group.get(name)
icon = ICONS_BY_GROUP_NAME.get(name)
if not icon:
icon = data.get("icon")

label = self.labels_by_group.get(name)
label = LABELS_BY_GROUP_NAME.get(name)
if not label:
label = data.get("label")

Expand Down
6 changes: 3 additions & 3 deletions client/ayon_applications/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ class ApplicationNotFound(Exception):

def __init__(self, app_name):
self.app_name = app_name
super(ApplicationNotFound, self).__init__(
super().__init__(
"Application \"{}\" was not found.".format(app_name)
)

Expand Down Expand Up @@ -33,9 +33,9 @@ def __init__(self, application):
exc_mgs = str(self.msg)
if details:
# Is good idea to pass new line symbol to exception message?
exc_mgs += "\n" + details
exc_mgs += "\n\n" + details
self.exc_msg = exc_mgs
super(ApplicationExecutableNotFound, self).__init__(exc_mgs)
super().__init__(exc_mgs)


class ApplicationLaunchFailed(Exception):
Expand Down
Loading

0 comments on commit abd4bce

Please sign in to comment.