mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-01 08:24:53 +01:00
Merge pull request #235 from ynput/enhancement/non-python-host-launch-script
Copy non-python launch script in each integration
This commit is contained in:
commit
942ddddd09
20 changed files with 581 additions and 134 deletions
|
|
@ -1,58 +0,0 @@
|
||||||
import os
|
|
||||||
|
|
||||||
from ayon_core.lib import get_ayon_launcher_args
|
|
||||||
from ayon_core.lib.applications import (
|
|
||||||
get_non_python_host_kwargs,
|
|
||||||
PreLaunchHook,
|
|
||||||
LaunchTypes,
|
|
||||||
)
|
|
||||||
|
|
||||||
from ayon_core import AYON_CORE_ROOT
|
|
||||||
|
|
||||||
|
|
||||||
class NonPythonHostHook(PreLaunchHook):
|
|
||||||
"""Launch arguments preparation.
|
|
||||||
|
|
||||||
Non python host implementation do not launch host directly but use
|
|
||||||
python script which launch the host. For these cases it is necessary to
|
|
||||||
prepend python (or ayon) executable and script path before application's.
|
|
||||||
"""
|
|
||||||
app_groups = {"harmony", "photoshop", "aftereffects"}
|
|
||||||
|
|
||||||
order = 20
|
|
||||||
launch_types = {LaunchTypes.local}
|
|
||||||
|
|
||||||
def execute(self):
|
|
||||||
# Pop executable
|
|
||||||
executable_path = self.launch_context.launch_args.pop(0)
|
|
||||||
|
|
||||||
# Pop rest of launch arguments - There should not be other arguments!
|
|
||||||
remainders = []
|
|
||||||
while self.launch_context.launch_args:
|
|
||||||
remainders.append(self.launch_context.launch_args.pop(0))
|
|
||||||
|
|
||||||
script_path = os.path.join(
|
|
||||||
AYON_CORE_ROOT,
|
|
||||||
"scripts",
|
|
||||||
"non_python_host_launch.py"
|
|
||||||
)
|
|
||||||
|
|
||||||
new_launch_args = get_ayon_launcher_args(
|
|
||||||
"run", script_path, executable_path
|
|
||||||
)
|
|
||||||
# Add workfile path if exists
|
|
||||||
workfile_path = self.data["last_workfile_path"]
|
|
||||||
if (
|
|
||||||
self.data.get("start_last_workfile")
|
|
||||||
and workfile_path
|
|
||||||
and os.path.exists(workfile_path)):
|
|
||||||
new_launch_args.append(workfile_path)
|
|
||||||
|
|
||||||
# Append as whole list as these areguments should not be separated
|
|
||||||
self.launch_context.launch_args.append(new_launch_args)
|
|
||||||
|
|
||||||
if remainders:
|
|
||||||
self.launch_context.launch_args.extend(remainders)
|
|
||||||
|
|
||||||
self.launch_context.kwargs = \
|
|
||||||
get_non_python_host_kwargs(self.launch_context.kwargs)
|
|
||||||
|
|
@ -1,6 +1,12 @@
|
||||||
from .addon import AfterEffectsAddon
|
from .addon import (
|
||||||
|
AFTEREFFECTS_ADDON_ROOT,
|
||||||
|
AfterEffectsAddon,
|
||||||
|
get_launch_script_path,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
|
"AFTEREFFECTS_ADDON_ROOT",
|
||||||
"AfterEffectsAddon",
|
"AfterEffectsAddon",
|
||||||
|
"get_launch_script_path",
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
|
import os
|
||||||
|
|
||||||
from ayon_core.addon import AYONAddon, IHostAddon
|
from ayon_core.addon import AYONAddon, IHostAddon
|
||||||
|
|
||||||
|
AFTEREFFECTS_ADDON_ROOT = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
|
||||||
class AfterEffectsAddon(AYONAddon, IHostAddon):
|
class AfterEffectsAddon(AYONAddon, IHostAddon):
|
||||||
name = "aftereffects"
|
name = "aftereffects"
|
||||||
|
|
@ -17,3 +21,16 @@ class AfterEffectsAddon(AYONAddon, IHostAddon):
|
||||||
|
|
||||||
def get_workfile_extensions(self):
|
def get_workfile_extensions(self):
|
||||||
return [".aep"]
|
return [".aep"]
|
||||||
|
|
||||||
|
def get_launch_hook_paths(self, app):
|
||||||
|
if app.host_name != self.host_name:
|
||||||
|
return []
|
||||||
|
return [
|
||||||
|
os.path.join(AFTEREFFECTS_ADDON_ROOT, "hooks")
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def get_launch_script_path():
|
||||||
|
return os.path.join(
|
||||||
|
AFTEREFFECTS_ADDON_ROOT, "api", "launch_script.py"
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import asyncio
|
||||||
import functools
|
import functools
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
|
|
||||||
from wsrpc_aiohttp import (
|
from wsrpc_aiohttp import (
|
||||||
WebSocketRoute,
|
WebSocketRoute,
|
||||||
WebSocketAsync
|
WebSocketAsync
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
"""Script wraps launch mechanism of non python host implementations.
|
"""Script wraps launch mechanism of AfterEffects implementations.
|
||||||
|
|
||||||
Arguments passed to the script are passed to launch function in host
|
Arguments passed to the script are passed to launch function in host
|
||||||
implementation. In all cases requires host app executable and may contain
|
implementation. In all cases requires host app executable and may contain
|
||||||
|
|
@ -8,6 +8,8 @@ workfile or others.
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from ayon_core.hosts.aftereffects.api.launch_logic import main as host_main
|
||||||
|
|
||||||
# Get current file to locate start point of sys.argv
|
# Get current file to locate start point of sys.argv
|
||||||
CURRENT_FILE = os.path.abspath(__file__)
|
CURRENT_FILE = os.path.abspath(__file__)
|
||||||
|
|
||||||
|
|
@ -79,26 +81,9 @@ def main(argv):
|
||||||
if after_script_idx is not None:
|
if after_script_idx is not None:
|
||||||
launch_args = sys_args[after_script_idx:]
|
launch_args = sys_args[after_script_idx:]
|
||||||
|
|
||||||
host_name = os.environ["AYON_HOST_NAME"].lower()
|
|
||||||
if host_name == "photoshop":
|
|
||||||
# TODO refactor launch logic according to AE
|
|
||||||
from ayon_core.hosts.photoshop.api.lib import main
|
|
||||||
elif host_name == "aftereffects":
|
|
||||||
from ayon_core.hosts.aftereffects.api.launch_logic import main
|
|
||||||
elif host_name == "harmony":
|
|
||||||
from ayon_core.hosts.harmony.api.lib import main
|
|
||||||
else:
|
|
||||||
title = "Unknown host name"
|
|
||||||
message = (
|
|
||||||
"BUG: Environment variable AYON_HOST_NAME contains unknown"
|
|
||||||
" host name \"{}\""
|
|
||||||
).format(host_name)
|
|
||||||
show_error_messagebox(title, message)
|
|
||||||
return
|
|
||||||
|
|
||||||
if launch_args:
|
if launch_args:
|
||||||
# Launch host implementation
|
# Launch host implementation
|
||||||
main(*launch_args)
|
host_main(*launch_args)
|
||||||
else:
|
else:
|
||||||
# Show message box
|
# Show message box
|
||||||
on_invalid_args(after_script_idx is None)
|
on_invalid_args(after_script_idx is None)
|
||||||
91
client/ayon_core/hosts/aftereffects/hooks/pre_launch_args.py
Normal file
91
client/ayon_core/hosts/aftereffects/hooks/pre_launch_args.py
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
from ayon_core.lib import (
|
||||||
|
get_ayon_launcher_args,
|
||||||
|
is_using_ayon_console,
|
||||||
|
)
|
||||||
|
from ayon_core.lib.applications import (
|
||||||
|
PreLaunchHook,
|
||||||
|
LaunchTypes,
|
||||||
|
)
|
||||||
|
from ayon_core.hosts.aftereffects import get_launch_script_path
|
||||||
|
|
||||||
|
|
||||||
|
def get_launch_kwargs(kwargs):
|
||||||
|
"""Explicit setting of kwargs for Popen for AfterEffects.
|
||||||
|
|
||||||
|
Expected behavior
|
||||||
|
- ayon_console opens window with logs
|
||||||
|
- ayon has stdout/stderr available for capturing
|
||||||
|
|
||||||
|
Args:
|
||||||
|
kwargs (Union[dict, None]): Current kwargs or None.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if kwargs is None:
|
||||||
|
kwargs = {}
|
||||||
|
|
||||||
|
if platform.system().lower() != "windows":
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
if is_using_ayon_console():
|
||||||
|
kwargs.update({
|
||||||
|
"creationflags": subprocess.CREATE_NEW_CONSOLE
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
kwargs.update({
|
||||||
|
"creationflags": subprocess.CREATE_NO_WINDOW,
|
||||||
|
"stdout": subprocess.DEVNULL,
|
||||||
|
"stderr": subprocess.DEVNULL
|
||||||
|
})
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
|
||||||
|
class AEPrelaunchHook(PreLaunchHook):
|
||||||
|
"""Launch arguments preparation.
|
||||||
|
|
||||||
|
Hook add python executable and script path to AE implementation before
|
||||||
|
AE executable and add last workfile path to launch arguments.
|
||||||
|
|
||||||
|
Existence of last workfile is checked. If workfile does not exists tries
|
||||||
|
to copy templated workfile from predefined path.
|
||||||
|
"""
|
||||||
|
app_groups = {"aftereffects"}
|
||||||
|
|
||||||
|
order = 20
|
||||||
|
launch_types = {LaunchTypes.local}
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
# Pop executable
|
||||||
|
executable_path = self.launch_context.launch_args.pop(0)
|
||||||
|
|
||||||
|
# Pop rest of launch arguments - There should not be other arguments!
|
||||||
|
remainders = []
|
||||||
|
while self.launch_context.launch_args:
|
||||||
|
remainders.append(self.launch_context.launch_args.pop(0))
|
||||||
|
|
||||||
|
script_path = get_launch_script_path()
|
||||||
|
|
||||||
|
new_launch_args = get_ayon_launcher_args(
|
||||||
|
"run", script_path, executable_path
|
||||||
|
)
|
||||||
|
# Add workfile path if exists
|
||||||
|
workfile_path = self.data["last_workfile_path"]
|
||||||
|
if (
|
||||||
|
self.data.get("start_last_workfile")
|
||||||
|
and workfile_path
|
||||||
|
and os.path.exists(workfile_path)
|
||||||
|
):
|
||||||
|
new_launch_args.append(workfile_path)
|
||||||
|
|
||||||
|
# Append as whole list as these arguments should not be separated
|
||||||
|
self.launch_context.launch_args.append(new_launch_args)
|
||||||
|
|
||||||
|
if remainders:
|
||||||
|
self.launch_context.launch_args.extend(remainders)
|
||||||
|
|
||||||
|
self.launch_context.kwargs = get_launch_kwargs(
|
||||||
|
self.launch_context.kwargs
|
||||||
|
)
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
from .addon import (
|
from .addon import (
|
||||||
HARMONY_HOST_DIR,
|
HARMONY_ADDON_ROOT,
|
||||||
HarmonyAddon,
|
HarmonyAddon,
|
||||||
|
get_launch_script_path,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
"HARMONY_HOST_DIR",
|
"HARMONY_ADDON_ROOT",
|
||||||
"HarmonyAddon",
|
"HarmonyAddon",
|
||||||
|
"get_launch_script_path",
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import os
|
import os
|
||||||
from ayon_core.addon import AYONAddon, IHostAddon
|
from ayon_core.addon import AYONAddon, IHostAddon
|
||||||
|
|
||||||
HARMONY_HOST_DIR = os.path.dirname(os.path.abspath(__file__))
|
HARMONY_ADDON_ROOT = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
|
||||||
class HarmonyAddon(AYONAddon, IHostAddon):
|
class HarmonyAddon(AYONAddon, IHostAddon):
|
||||||
|
|
@ -11,10 +11,23 @@ class HarmonyAddon(AYONAddon, IHostAddon):
|
||||||
def add_implementation_envs(self, env, _app):
|
def add_implementation_envs(self, env, _app):
|
||||||
"""Modify environments to contain all required for implementation."""
|
"""Modify environments to contain all required for implementation."""
|
||||||
openharmony_path = os.path.join(
|
openharmony_path = os.path.join(
|
||||||
HARMONY_HOST_DIR, "vendor", "OpenHarmony"
|
HARMONY_ADDON_ROOT, "vendor", "OpenHarmony"
|
||||||
)
|
)
|
||||||
# TODO check if is already set? What to do if is already set?
|
# TODO check if is already set? What to do if is already set?
|
||||||
env["LIB_OPENHARMONY_PATH"] = openharmony_path
|
env["LIB_OPENHARMONY_PATH"] = openharmony_path
|
||||||
|
|
||||||
def get_workfile_extensions(self):
|
def get_workfile_extensions(self):
|
||||||
return [".zip"]
|
return [".zip"]
|
||||||
|
|
||||||
|
def get_launch_hook_paths(self, app):
|
||||||
|
if app.host_name != self.host_name:
|
||||||
|
return []
|
||||||
|
return [
|
||||||
|
os.path.join(HARMONY_ADDON_ROOT, "hooks")
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def get_launch_script_path():
|
||||||
|
return os.path.join(
|
||||||
|
HARMONY_ADDON_ROOT, "api", "launch_script.py"
|
||||||
|
)
|
||||||
|
|
|
||||||
93
client/ayon_core/hosts/harmony/api/launch_script.py
Normal file
93
client/ayon_core/hosts/harmony/api/launch_script.py
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
"""Script wraps launch mechanism of Harmony implementations.
|
||||||
|
|
||||||
|
Arguments passed to the script are passed to launch function in host
|
||||||
|
implementation. In all cases requires host app executable and may contain
|
||||||
|
workfile or others.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from ayon_core.hosts.harmony.api.lib import main as host_main
|
||||||
|
|
||||||
|
# Get current file to locate start point of sys.argv
|
||||||
|
CURRENT_FILE = os.path.abspath(__file__)
|
||||||
|
|
||||||
|
|
||||||
|
def show_error_messagebox(title, message, detail_message=None):
|
||||||
|
"""Function will show message and process ends after closing it."""
|
||||||
|
from qtpy import QtWidgets, QtCore
|
||||||
|
from ayon_core import style
|
||||||
|
|
||||||
|
app = QtWidgets.QApplication([])
|
||||||
|
app.setStyleSheet(style.load_stylesheet())
|
||||||
|
|
||||||
|
msgbox = QtWidgets.QMessageBox()
|
||||||
|
msgbox.setWindowTitle(title)
|
||||||
|
msgbox.setText(message)
|
||||||
|
|
||||||
|
if detail_message:
|
||||||
|
msgbox.setDetailedText(detail_message)
|
||||||
|
|
||||||
|
msgbox.setWindowModality(QtCore.Qt.ApplicationModal)
|
||||||
|
msgbox.show()
|
||||||
|
|
||||||
|
sys.exit(app.exec_())
|
||||||
|
|
||||||
|
|
||||||
|
def on_invalid_args(script_not_found):
|
||||||
|
"""Show to user message box saying that something went wrong.
|
||||||
|
|
||||||
|
Tell user that arguments to launch implementation are invalid with
|
||||||
|
arguments details.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
script_not_found (bool): Use different message based on this value.
|
||||||
|
"""
|
||||||
|
|
||||||
|
title = "Invalid arguments"
|
||||||
|
joined_args = ", ".join("\"{}\"".format(arg) for arg in sys.argv)
|
||||||
|
if script_not_found:
|
||||||
|
submsg = "Where couldn't find script path:\n\"{}\""
|
||||||
|
else:
|
||||||
|
submsg = "Expected Host executable after script path:\n\"{}\""
|
||||||
|
|
||||||
|
message = "BUG: Got invalid arguments so can't launch Host application."
|
||||||
|
detail_message = "Process was launched with arguments:\n{}\n\n{}".format(
|
||||||
|
joined_args,
|
||||||
|
submsg.format(CURRENT_FILE)
|
||||||
|
)
|
||||||
|
|
||||||
|
show_error_messagebox(title, message, detail_message)
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
# Modify current file path to find match in sys.argv which may be different
|
||||||
|
# on windows (different letter cases and slashes).
|
||||||
|
modified_current_file = CURRENT_FILE.replace("\\", "/").lower()
|
||||||
|
|
||||||
|
# Create a copy of sys argv
|
||||||
|
sys_args = list(argv)
|
||||||
|
after_script_idx = None
|
||||||
|
# Find script path in sys.argv to know index of argv where host
|
||||||
|
# executable should be.
|
||||||
|
for idx, item in enumerate(sys_args):
|
||||||
|
if item.replace("\\", "/").lower() == modified_current_file:
|
||||||
|
after_script_idx = idx + 1
|
||||||
|
break
|
||||||
|
|
||||||
|
# Validate that there is at least one argument after script path
|
||||||
|
launch_args = None
|
||||||
|
if after_script_idx is not None:
|
||||||
|
launch_args = sys_args[after_script_idx:]
|
||||||
|
|
||||||
|
if launch_args:
|
||||||
|
# Launch host implementation
|
||||||
|
host_main(*launch_args)
|
||||||
|
else:
|
||||||
|
# Show message box
|
||||||
|
on_invalid_args(after_script_idx is None)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main(sys.argv)
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""Utility functions used for Avalon - Harmony integration."""
|
"""Utility functions used for Avalon - Harmony integration."""
|
||||||
|
import platform
|
||||||
import subprocess
|
import subprocess
|
||||||
import threading
|
import threading
|
||||||
import os
|
import os
|
||||||
|
|
@ -14,15 +15,16 @@ import json
|
||||||
import signal
|
import signal
|
||||||
import time
|
import time
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
from qtpy import QtWidgets, QtCore, QtGui
|
|
||||||
import collections
|
import collections
|
||||||
|
|
||||||
from .server import Server
|
from qtpy import QtWidgets, QtCore, QtGui
|
||||||
|
|
||||||
|
from ayon_core.lib import is_using_ayon_console
|
||||||
from ayon_core.tools.stdout_broker.app import StdOutBroker
|
from ayon_core.tools.stdout_broker.app import StdOutBroker
|
||||||
from ayon_core.tools.utils import host_tools
|
from ayon_core.tools.utils import host_tools
|
||||||
from ayon_core import style
|
from ayon_core import style
|
||||||
from ayon_core.lib.applications import get_non_python_host_kwargs
|
|
||||||
|
from .server import Server
|
||||||
|
|
||||||
# Setup logging.
|
# Setup logging.
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
@ -324,7 +326,18 @@ def launch_zip_file(filepath):
|
||||||
return
|
return
|
||||||
|
|
||||||
print("Launching {}".format(scene_path))
|
print("Launching {}".format(scene_path))
|
||||||
kwargs = get_non_python_host_kwargs({}, False)
|
# QUESTION Could we use 'run_detached_process' from 'ayon_core.lib'?
|
||||||
|
kwargs = {}
|
||||||
|
if (
|
||||||
|
platform.system().lower() == "windows"
|
||||||
|
and not is_using_ayon_console()
|
||||||
|
):
|
||||||
|
kwargs.update({
|
||||||
|
"creationflags": subprocess.CREATE_NO_WINDOW,
|
||||||
|
"stdout": subprocess.DEVNULL,
|
||||||
|
"stderr": subprocess.DEVNULL
|
||||||
|
})
|
||||||
|
|
||||||
process = subprocess.Popen(
|
process = subprocess.Popen(
|
||||||
[ProcessContext.application_path, scene_path],
|
[ProcessContext.application_path, scene_path],
|
||||||
**kwargs
|
**kwargs
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,13 @@ from ayon_core.pipeline import (
|
||||||
from ayon_core.pipeline.load import get_outdated_containers
|
from ayon_core.pipeline.load import get_outdated_containers
|
||||||
from ayon_core.pipeline.context_tools import get_current_project_folder
|
from ayon_core.pipeline.context_tools import get_current_project_folder
|
||||||
|
|
||||||
from ayon_core.hosts.harmony import HARMONY_HOST_DIR
|
from ayon_core.hosts.harmony import HARMONY_ADDON_ROOT
|
||||||
import ayon_core.hosts.harmony.api as harmony
|
import ayon_core.hosts.harmony.api as harmony
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger("ayon_core.hosts.harmony")
|
log = logging.getLogger("ayon_core.hosts.harmony")
|
||||||
|
|
||||||
PLUGINS_DIR = os.path.join(HARMONY_HOST_DIR, "plugins")
|
PLUGINS_DIR = os.path.join(HARMONY_ADDON_ROOT, "plugins")
|
||||||
PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish")
|
PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish")
|
||||||
LOAD_PATH = os.path.join(PLUGINS_DIR, "load")
|
LOAD_PATH = os.path.join(PLUGINS_DIR, "load")
|
||||||
CREATE_PATH = os.path.join(PLUGINS_DIR, "create")
|
CREATE_PATH = os.path.join(PLUGINS_DIR, "create")
|
||||||
|
|
|
||||||
91
client/ayon_core/hosts/harmony/hooks/pre_launch_args.py
Normal file
91
client/ayon_core/hosts/harmony/hooks/pre_launch_args.py
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
from ayon_core.lib import (
|
||||||
|
get_ayon_launcher_args,
|
||||||
|
is_using_ayon_console,
|
||||||
|
)
|
||||||
|
from ayon_core.lib.applications import (
|
||||||
|
PreLaunchHook,
|
||||||
|
LaunchTypes,
|
||||||
|
)
|
||||||
|
from ayon_core.hosts.harmony import get_launch_script_path
|
||||||
|
|
||||||
|
|
||||||
|
def get_launch_kwargs(kwargs):
|
||||||
|
"""Explicit setting of kwargs for Popen for Harmony.
|
||||||
|
|
||||||
|
Expected behavior
|
||||||
|
- ayon_console opens window with logs
|
||||||
|
- ayon has stdout/stderr available for capturing
|
||||||
|
|
||||||
|
Args:
|
||||||
|
kwargs (Union[dict, None]): Current kwargs or None.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if kwargs is None:
|
||||||
|
kwargs = {}
|
||||||
|
|
||||||
|
if platform.system().lower() != "windows":
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
if is_using_ayon_console():
|
||||||
|
kwargs.update({
|
||||||
|
"creationflags": subprocess.CREATE_NEW_CONSOLE
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
kwargs.update({
|
||||||
|
"creationflags": subprocess.CREATE_NO_WINDOW,
|
||||||
|
"stdout": subprocess.DEVNULL,
|
||||||
|
"stderr": subprocess.DEVNULL
|
||||||
|
})
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
|
||||||
|
class HarmonyPrelaunchHook(PreLaunchHook):
|
||||||
|
"""Launch arguments preparation.
|
||||||
|
|
||||||
|
Hook add python executable and script path to Harmony implementation
|
||||||
|
before Harmony executable and add last workfile path to launch arguments.
|
||||||
|
|
||||||
|
Existence of last workfile is checked. If workfile does not exists tries
|
||||||
|
to copy templated workfile from predefined path.
|
||||||
|
"""
|
||||||
|
app_groups = {"harmony"}
|
||||||
|
|
||||||
|
order = 20
|
||||||
|
launch_types = {LaunchTypes.local}
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
# Pop executable
|
||||||
|
executable_path = self.launch_context.launch_args.pop(0)
|
||||||
|
|
||||||
|
# Pop rest of launch arguments - There should not be other arguments!
|
||||||
|
remainders = []
|
||||||
|
while self.launch_context.launch_args:
|
||||||
|
remainders.append(self.launch_context.launch_args.pop(0))
|
||||||
|
|
||||||
|
script_path = get_launch_script_path()
|
||||||
|
|
||||||
|
new_launch_args = get_ayon_launcher_args(
|
||||||
|
"run", script_path, executable_path
|
||||||
|
)
|
||||||
|
# Add workfile path if exists
|
||||||
|
workfile_path = self.data["last_workfile_path"]
|
||||||
|
if (
|
||||||
|
self.data.get("start_last_workfile")
|
||||||
|
and workfile_path
|
||||||
|
and os.path.exists(workfile_path)
|
||||||
|
):
|
||||||
|
new_launch_args.append(workfile_path)
|
||||||
|
|
||||||
|
# Append as whole list as these arguments should not be separated
|
||||||
|
self.launch_context.launch_args.append(new_launch_args)
|
||||||
|
|
||||||
|
if remainders:
|
||||||
|
self.launch_context.launch_args.extend(remainders)
|
||||||
|
|
||||||
|
self.launch_context.kwargs = get_launch_kwargs(
|
||||||
|
self.launch_context.kwargs
|
||||||
|
)
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
from .addon import (
|
from .addon import (
|
||||||
|
PHOTOSHOP_ADDON_ROOT,
|
||||||
PhotoshopAddon,
|
PhotoshopAddon,
|
||||||
PHOTOSHOP_HOST_DIR,
|
get_launch_script_path,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
|
"PHOTOSHOP_ADDON_ROOT",
|
||||||
"PhotoshopAddon",
|
"PhotoshopAddon",
|
||||||
"PHOTOSHOP_HOST_DIR",
|
"get_launch_script_path",
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import os
|
import os
|
||||||
from ayon_core.addon import AYONAddon, IHostAddon
|
from ayon_core.addon import AYONAddon, IHostAddon
|
||||||
|
|
||||||
PHOTOSHOP_HOST_DIR = os.path.dirname(os.path.abspath(__file__))
|
PHOTOSHOP_ADDON_ROOT = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
|
||||||
class PhotoshopAddon(AYONAddon, IHostAddon):
|
class PhotoshopAddon(AYONAddon, IHostAddon):
|
||||||
|
|
@ -20,3 +20,17 @@ class PhotoshopAddon(AYONAddon, IHostAddon):
|
||||||
|
|
||||||
def get_workfile_extensions(self):
|
def get_workfile_extensions(self):
|
||||||
return [".psd", ".psb"]
|
return [".psd", ".psb"]
|
||||||
|
|
||||||
|
def get_launch_hook_paths(self, app):
|
||||||
|
if app.host_name != self.host_name:
|
||||||
|
return []
|
||||||
|
return [
|
||||||
|
os.path.join(PHOTOSHOP_ADDON_ROOT, "hooks")
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def get_launch_script_path():
|
||||||
|
return os.path.join(
|
||||||
|
PHOTOSHOP_ADDON_ROOT, "api", "launch_script.py"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
|
||||||
93
client/ayon_core/hosts/photoshop/api/launch_script.py
Normal file
93
client/ayon_core/hosts/photoshop/api/launch_script.py
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
"""Script wraps launch mechanism of Photoshop implementations.
|
||||||
|
|
||||||
|
Arguments passed to the script are passed to launch function in host
|
||||||
|
implementation. In all cases requires host app executable and may contain
|
||||||
|
workfile or others.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from ayon_core.hosts.photoshop.api.lib import main as host_main
|
||||||
|
|
||||||
|
# Get current file to locate start point of sys.argv
|
||||||
|
CURRENT_FILE = os.path.abspath(__file__)
|
||||||
|
|
||||||
|
|
||||||
|
def show_error_messagebox(title, message, detail_message=None):
|
||||||
|
"""Function will show message and process ends after closing it."""
|
||||||
|
from qtpy import QtWidgets, QtCore
|
||||||
|
from ayon_core import style
|
||||||
|
|
||||||
|
app = QtWidgets.QApplication([])
|
||||||
|
app.setStyleSheet(style.load_stylesheet())
|
||||||
|
|
||||||
|
msgbox = QtWidgets.QMessageBox()
|
||||||
|
msgbox.setWindowTitle(title)
|
||||||
|
msgbox.setText(message)
|
||||||
|
|
||||||
|
if detail_message:
|
||||||
|
msgbox.setDetailedText(detail_message)
|
||||||
|
|
||||||
|
msgbox.setWindowModality(QtCore.Qt.ApplicationModal)
|
||||||
|
msgbox.show()
|
||||||
|
|
||||||
|
sys.exit(app.exec_())
|
||||||
|
|
||||||
|
|
||||||
|
def on_invalid_args(script_not_found):
|
||||||
|
"""Show to user message box saying that something went wrong.
|
||||||
|
|
||||||
|
Tell user that arguments to launch implementation are invalid with
|
||||||
|
arguments details.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
script_not_found (bool): Use different message based on this value.
|
||||||
|
"""
|
||||||
|
|
||||||
|
title = "Invalid arguments"
|
||||||
|
joined_args = ", ".join("\"{}\"".format(arg) for arg in sys.argv)
|
||||||
|
if script_not_found:
|
||||||
|
submsg = "Where couldn't find script path:\n\"{}\""
|
||||||
|
else:
|
||||||
|
submsg = "Expected Host executable after script path:\n\"{}\""
|
||||||
|
|
||||||
|
message = "BUG: Got invalid arguments so can't launch Host application."
|
||||||
|
detail_message = "Process was launched with arguments:\n{}\n\n{}".format(
|
||||||
|
joined_args,
|
||||||
|
submsg.format(CURRENT_FILE)
|
||||||
|
)
|
||||||
|
|
||||||
|
show_error_messagebox(title, message, detail_message)
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
# Modify current file path to find match in sys.argv which may be different
|
||||||
|
# on windows (different letter cases and slashes).
|
||||||
|
modified_current_file = CURRENT_FILE.replace("\\", "/").lower()
|
||||||
|
|
||||||
|
# Create a copy of sys argv
|
||||||
|
sys_args = list(argv)
|
||||||
|
after_script_idx = None
|
||||||
|
# Find script path in sys.argv to know index of argv where host
|
||||||
|
# executable should be.
|
||||||
|
for idx, item in enumerate(sys_args):
|
||||||
|
if item.replace("\\", "/").lower() == modified_current_file:
|
||||||
|
after_script_idx = idx + 1
|
||||||
|
break
|
||||||
|
|
||||||
|
# Validate that there is at least one argument after script path
|
||||||
|
launch_args = None
|
||||||
|
if after_script_idx is not None:
|
||||||
|
launch_args = sys_args[after_script_idx:]
|
||||||
|
|
||||||
|
if launch_args:
|
||||||
|
# Launch host implementation
|
||||||
|
host_main(*launch_args)
|
||||||
|
else:
|
||||||
|
# Show message box
|
||||||
|
on_invalid_args(after_script_idx is None)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main(sys.argv)
|
||||||
|
|
@ -21,14 +21,14 @@ from ayon_core.host import (
|
||||||
)
|
)
|
||||||
|
|
||||||
from ayon_core.pipeline.load import any_outdated_containers
|
from ayon_core.pipeline.load import any_outdated_containers
|
||||||
from ayon_core.hosts.photoshop import PHOTOSHOP_HOST_DIR
|
from ayon_core.hosts.photoshop import PHOTOSHOP_ADDON_ROOT
|
||||||
from ayon_core.tools.utils import get_ayon_qt_app
|
from ayon_core.tools.utils import get_ayon_qt_app
|
||||||
|
|
||||||
from . import lib
|
from . import lib
|
||||||
|
|
||||||
log = Logger.get_logger(__name__)
|
log = Logger.get_logger(__name__)
|
||||||
|
|
||||||
PLUGINS_DIR = os.path.join(PHOTOSHOP_HOST_DIR, "plugins")
|
PLUGINS_DIR = os.path.join(PHOTOSHOP_ADDON_ROOT, "plugins")
|
||||||
PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish")
|
PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish")
|
||||||
LOAD_PATH = os.path.join(PLUGINS_DIR, "load")
|
LOAD_PATH = os.path.join(PLUGINS_DIR, "load")
|
||||||
CREATE_PATH = os.path.join(PLUGINS_DIR, "create")
|
CREATE_PATH = os.path.join(PLUGINS_DIR, "create")
|
||||||
|
|
|
||||||
91
client/ayon_core/hosts/photoshop/hooks/pre_launch_args.py
Normal file
91
client/ayon_core/hosts/photoshop/hooks/pre_launch_args.py
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
from ayon_core.lib import (
|
||||||
|
get_ayon_launcher_args,
|
||||||
|
is_using_ayon_console,
|
||||||
|
)
|
||||||
|
from ayon_core.lib.applications import (
|
||||||
|
PreLaunchHook,
|
||||||
|
LaunchTypes,
|
||||||
|
)
|
||||||
|
from ayon_core.hosts.photoshop import get_launch_script_path
|
||||||
|
|
||||||
|
|
||||||
|
def get_launch_kwargs(kwargs):
|
||||||
|
"""Explicit setting of kwargs for Popen for Photoshop.
|
||||||
|
|
||||||
|
Expected behavior
|
||||||
|
- ayon_console opens window with logs
|
||||||
|
- ayon has stdout/stderr available for capturing
|
||||||
|
|
||||||
|
Args:
|
||||||
|
kwargs (Union[dict, None]): Current kwargs or None.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if kwargs is None:
|
||||||
|
kwargs = {}
|
||||||
|
|
||||||
|
if platform.system().lower() != "windows":
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
if not is_using_ayon_console():
|
||||||
|
kwargs.update({
|
||||||
|
"creationflags": subprocess.CREATE_NEW_CONSOLE
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
kwargs.update({
|
||||||
|
"creationflags": subprocess.CREATE_NO_WINDOW,
|
||||||
|
"stdout": subprocess.DEVNULL,
|
||||||
|
"stderr": subprocess.DEVNULL
|
||||||
|
})
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
|
||||||
|
class PhotoshopPrelaunchHook(PreLaunchHook):
|
||||||
|
"""Launch arguments preparation.
|
||||||
|
|
||||||
|
Hook add python executable and script path to Photoshop implementation
|
||||||
|
before Photoshop executable and add last workfile path to launch arguments.
|
||||||
|
|
||||||
|
Existence of last workfile is checked. If workfile does not exists tries
|
||||||
|
to copy templated workfile from predefined path.
|
||||||
|
"""
|
||||||
|
app_groups = {"photoshop"}
|
||||||
|
|
||||||
|
order = 20
|
||||||
|
launch_types = {LaunchTypes.local}
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
# Pop executable
|
||||||
|
executable_path = self.launch_context.launch_args.pop(0)
|
||||||
|
|
||||||
|
# Pop rest of launch arguments - There should not be other arguments!
|
||||||
|
remainders = []
|
||||||
|
while self.launch_context.launch_args:
|
||||||
|
remainders.append(self.launch_context.launch_args.pop(0))
|
||||||
|
|
||||||
|
script_path = get_launch_script_path()
|
||||||
|
|
||||||
|
new_launch_args = get_ayon_launcher_args(
|
||||||
|
"run", script_path, executable_path
|
||||||
|
)
|
||||||
|
# Add workfile path if exists
|
||||||
|
workfile_path = self.data["last_workfile_path"]
|
||||||
|
if (
|
||||||
|
self.data.get("start_last_workfile")
|
||||||
|
and workfile_path
|
||||||
|
and os.path.exists(workfile_path)
|
||||||
|
):
|
||||||
|
new_launch_args.append(workfile_path)
|
||||||
|
|
||||||
|
# Append as whole list as these arguments should not be separated
|
||||||
|
self.launch_context.launch_args.append(new_launch_args)
|
||||||
|
|
||||||
|
if remainders:
|
||||||
|
self.launch_context.launch_args.extend(remainders)
|
||||||
|
|
||||||
|
self.launch_context.kwargs = get_launch_kwargs(
|
||||||
|
self.launch_context.kwargs
|
||||||
|
)
|
||||||
|
|
@ -152,6 +152,7 @@ from .path_tools import (
|
||||||
|
|
||||||
from .ayon_info import (
|
from .ayon_info import (
|
||||||
is_running_from_build,
|
is_running_from_build,
|
||||||
|
is_using_ayon_console,
|
||||||
is_staging_enabled,
|
is_staging_enabled,
|
||||||
is_dev_mode_enabled,
|
is_dev_mode_enabled,
|
||||||
is_in_tests,
|
is_in_tests,
|
||||||
|
|
@ -269,6 +270,7 @@ __all__ = [
|
||||||
"Logger",
|
"Logger",
|
||||||
|
|
||||||
"is_running_from_build",
|
"is_running_from_build",
|
||||||
|
"is_using_ayon_console",
|
||||||
"is_staging_enabled",
|
"is_staging_enabled",
|
||||||
"is_dev_mode_enabled",
|
"is_dev_mode_enabled",
|
||||||
"is_in_tests",
|
"is_in_tests",
|
||||||
|
|
|
||||||
|
|
@ -1891,42 +1891,3 @@ def _prepare_last_workfile(data, workdir, addons_manager):
|
||||||
|
|
||||||
data["env"]["AYON_LAST_WORKFILE"] = last_workfile_path
|
data["env"]["AYON_LAST_WORKFILE"] = last_workfile_path
|
||||||
data["last_workfile_path"] = last_workfile_path
|
data["last_workfile_path"] = last_workfile_path
|
||||||
|
|
||||||
|
|
||||||
def get_non_python_host_kwargs(kwargs, allow_console=True):
|
|
||||||
"""Explicit setting of kwargs for Popen for AE/PS/Harmony.
|
|
||||||
|
|
||||||
Expected behavior
|
|
||||||
- ayon_console opens window with logs
|
|
||||||
- ayon has stdout/stderr available for capturing
|
|
||||||
|
|
||||||
Args:
|
|
||||||
kwargs (dict) or None
|
|
||||||
allow_console (bool): use False for inner Popen opening app itself or
|
|
||||||
it will open additional console (at least for Harmony)
|
|
||||||
"""
|
|
||||||
|
|
||||||
if kwargs is None:
|
|
||||||
kwargs = {}
|
|
||||||
|
|
||||||
if platform.system().lower() != "windows":
|
|
||||||
return kwargs
|
|
||||||
|
|
||||||
executable_path = os.environ.get("AYON_EXECUTABLE")
|
|
||||||
|
|
||||||
executable_filename = ""
|
|
||||||
if executable_path:
|
|
||||||
executable_filename = os.path.basename(executable_path)
|
|
||||||
|
|
||||||
is_gui_executable = "ayon_console" not in executable_filename
|
|
||||||
if is_gui_executable:
|
|
||||||
kwargs.update({
|
|
||||||
"creationflags": subprocess.CREATE_NO_WINDOW,
|
|
||||||
"stdout": subprocess.DEVNULL,
|
|
||||||
"stderr": subprocess.DEVNULL
|
|
||||||
})
|
|
||||||
elif allow_console:
|
|
||||||
kwargs.update({
|
|
||||||
"creationflags": subprocess.CREATE_NEW_CONSOLE
|
|
||||||
})
|
|
||||||
return kwargs
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,12 @@ from .local_settings import get_local_site_id
|
||||||
|
|
||||||
|
|
||||||
def get_ayon_launcher_version():
|
def get_ayon_launcher_version():
|
||||||
|
"""Get AYON launcher version.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Version string.
|
||||||
|
|
||||||
|
"""
|
||||||
version_filepath = os.path.join(os.environ["AYON_ROOT"], "version.py")
|
version_filepath = os.path.join(os.environ["AYON_ROOT"], "version.py")
|
||||||
if not os.path.exists(version_filepath):
|
if not os.path.exists(version_filepath):
|
||||||
return None
|
return None
|
||||||
|
|
@ -24,8 +30,8 @@ def is_running_from_build():
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: True if running from build.
|
bool: True if running from build.
|
||||||
"""
|
|
||||||
|
|
||||||
|
"""
|
||||||
executable_path = os.environ["AYON_EXECUTABLE"]
|
executable_path = os.environ["AYON_EXECUTABLE"]
|
||||||
executable_filename = os.path.basename(executable_path)
|
executable_filename = os.path.basename(executable_path)
|
||||||
if "python" in executable_filename.lower():
|
if "python" in executable_filename.lower():
|
||||||
|
|
@ -33,6 +39,32 @@ def is_running_from_build():
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def is_using_ayon_console():
|
||||||
|
"""AYON launcher console executable is used.
|
||||||
|
|
||||||
|
This function make sense only on Windows platform. For other platforms
|
||||||
|
always returns True. True is also returned if process is running from
|
||||||
|
code.
|
||||||
|
|
||||||
|
AYON launcher on windows has 2 executable files. First 'ayon_console.exe'
|
||||||
|
works as 'python.exe' executable, the second 'ayon.exe' works as
|
||||||
|
'pythonw.exe' executable. The difference is way how stdout/stderr is
|
||||||
|
handled (especially when calling subprocess).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if console executable is used.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if (
|
||||||
|
platform.system().lower() != "windows"
|
||||||
|
or is_running_from_build()
|
||||||
|
):
|
||||||
|
return True
|
||||||
|
executable_path = os.environ["AYON_EXECUTABLE"]
|
||||||
|
executable_filename = os.path.basename(executable_path)
|
||||||
|
return "ayon_console" in executable_filename
|
||||||
|
|
||||||
|
|
||||||
def is_staging_enabled():
|
def is_staging_enabled():
|
||||||
return os.getenv("AYON_USE_STAGING") == "1"
|
return os.getenv("AYON_USE_STAGING") == "1"
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue