mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-26 13:52:15 +01:00
Merge pull request #747 from pypeclub/feature/launch_hooks
Application launch hooks
This commit is contained in:
commit
650ffb5e04
30 changed files with 2204 additions and 647 deletions
45
pype/hooks/aftereffects/pre_launch_args.py
Normal file
45
pype/hooks/aftereffects/pre_launch_args.py
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import os
|
||||
|
||||
from pype.lib import PreLaunchHook
|
||||
|
||||
|
||||
class AfterEffectsPrelaunchHook(PreLaunchHook):
|
||||
"""Launch arguments preparation.
|
||||
|
||||
Hook add python executable and execute python script of AfterEffects
|
||||
implementation before AfterEffects executable.
|
||||
"""
|
||||
app_groups = ["aftereffects"]
|
||||
|
||||
def execute(self):
|
||||
# Pop tvpaint executable
|
||||
aftereffects_executable = 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))
|
||||
|
||||
new_launch_args = [
|
||||
self.python_executable(),
|
||||
"-c",
|
||||
(
|
||||
"import avalon.aftereffects;"
|
||||
"avalon.aftereffects.launch(\"{}\")"
|
||||
).format(aftereffects_executable)
|
||||
]
|
||||
|
||||
# Append as whole list as these areguments should not be separated
|
||||
self.launch_context.launch_args.append(new_launch_args)
|
||||
|
||||
if remainders:
|
||||
self.log.warning((
|
||||
"There are unexpected launch arguments "
|
||||
"in AfterEffects launch. {}"
|
||||
).format(str(remainders)))
|
||||
self.launch_context.launch_args.extend(remainders)
|
||||
|
||||
def python_executable(self):
|
||||
"""Should lead to python executable."""
|
||||
# TODO change in Pype 3
|
||||
return os.environ["PYPE_PYTHON_EXE"]
|
||||
127
pype/hooks/celaction/pre_celaction_registers.py
Normal file
127
pype/hooks/celaction/pre_celaction_registers.py
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
import os
|
||||
import shutil
|
||||
import winreg
|
||||
from pype.lib import PreLaunchHook
|
||||
from pype.hosts import celaction
|
||||
|
||||
|
||||
class CelactionPrelaunchHook(PreLaunchHook):
|
||||
"""
|
||||
This hook will check if current workfile path has Unreal
|
||||
project inside. IF not, it initialize it and finally it pass
|
||||
path to the project by environment variable to Unreal launcher
|
||||
shell script.
|
||||
"""
|
||||
workfile_ext = "scn"
|
||||
app_groups = ["celaction"]
|
||||
platforms = ["windows"]
|
||||
|
||||
def execute(self):
|
||||
# Add workfile path to launch arguments
|
||||
workfile_path = self.workfile_path()
|
||||
if workfile_path:
|
||||
self.launch_context.launch_args.append(
|
||||
"\"{}\"".format(workfile_path)
|
||||
)
|
||||
|
||||
project_name = self.data["project_name"]
|
||||
asset_name = self.data["asset_name"]
|
||||
task_name = self.data["task_name"]
|
||||
|
||||
# get publish version of celaction
|
||||
app = "celaction_publish"
|
||||
|
||||
# setting output parameters
|
||||
path = r"Software\CelAction\CelAction2D\User Settings"
|
||||
winreg.CreateKey(winreg.HKEY_CURRENT_USER, path)
|
||||
hKey = winreg.OpenKey(
|
||||
winreg.HKEY_CURRENT_USER,
|
||||
"Software\\CelAction\\CelAction2D\\User Settings", 0,
|
||||
winreg.KEY_ALL_ACCESS)
|
||||
|
||||
# TODO: change to root path and pyblish standalone to premiere way
|
||||
pype_root_path = os.getenv("PYPE_SETUP_PATH")
|
||||
path = os.path.join(pype_root_path, "pype.bat")
|
||||
|
||||
winreg.SetValueEx(hKey, "SubmitAppTitle", 0, winreg.REG_SZ, path)
|
||||
|
||||
parameters = [
|
||||
"launch",
|
||||
f"--app {app}",
|
||||
f"--project {project_name}",
|
||||
f"--asset {asset_name}",
|
||||
f"--task {task_name}",
|
||||
"--currentFile \\\"\"*SCENE*\"\\\"",
|
||||
"--chunk 10",
|
||||
"--frameStart *START*",
|
||||
"--frameEnd *END*",
|
||||
"--resolutionWidth *X*",
|
||||
"--resolutionHeight *Y*",
|
||||
# "--programDir \"'*PROGPATH*'\""
|
||||
]
|
||||
winreg.SetValueEx(hKey, "SubmitParametersTitle", 0, winreg.REG_SZ,
|
||||
" ".join(parameters))
|
||||
|
||||
# setting resolution parameters
|
||||
path = r"Software\CelAction\CelAction2D\User Settings\Dialogs"
|
||||
path += r"\SubmitOutput"
|
||||
winreg.CreateKey(winreg.HKEY_CURRENT_USER, path)
|
||||
hKey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, path, 0,
|
||||
winreg.KEY_ALL_ACCESS)
|
||||
winreg.SetValueEx(hKey, "SaveScene", 0, winreg.REG_DWORD, 1)
|
||||
winreg.SetValueEx(hKey, "CustomX", 0, winreg.REG_DWORD, 1920)
|
||||
winreg.SetValueEx(hKey, "CustomY", 0, winreg.REG_DWORD, 1080)
|
||||
|
||||
# making sure message dialogs don't appear when overwriting
|
||||
path = r"Software\CelAction\CelAction2D\User Settings\Messages"
|
||||
path += r"\OverwriteScene"
|
||||
winreg.CreateKey(winreg.HKEY_CURRENT_USER, path)
|
||||
hKey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, path, 0,
|
||||
winreg.KEY_ALL_ACCESS)
|
||||
winreg.SetValueEx(hKey, "Result", 0, winreg.REG_DWORD, 6)
|
||||
winreg.SetValueEx(hKey, "Valid", 0, winreg.REG_DWORD, 1)
|
||||
|
||||
path = r"Software\CelAction\CelAction2D\User Settings\Messages"
|
||||
path += r"\SceneSaved"
|
||||
winreg.CreateKey(winreg.HKEY_CURRENT_USER, path)
|
||||
hKey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, path, 0,
|
||||
winreg.KEY_ALL_ACCESS)
|
||||
winreg.SetValueEx(hKey, "Result", 0, winreg.REG_DWORD, 1)
|
||||
winreg.SetValueEx(hKey, "Valid", 0, winreg.REG_DWORD, 1)
|
||||
|
||||
def workfile_path(self):
|
||||
workfile_path = self.data["last_workfile_path"]
|
||||
|
||||
# copy workfile from template if doesnt exist any on path
|
||||
if not os.path.exists(workfile_path):
|
||||
# TODO add ability to set different template workfile path via
|
||||
# settings
|
||||
pype_celaction_dir = os.path.dirname(
|
||||
os.path.abspath(celaction.__file__)
|
||||
)
|
||||
template_path = os.path.join(
|
||||
pype_celaction_dir,
|
||||
"celaction_template_scene.scn"
|
||||
)
|
||||
|
||||
if not os.path.exists(template_path):
|
||||
self.log.warning(
|
||||
"Couldn't find workfile template file in {}".format(
|
||||
template_path
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
self.log.info(
|
||||
f"Creating workfile from template: \"{template_path}\""
|
||||
)
|
||||
|
||||
# Copy template workfile to new destinantion
|
||||
shutil.copy2(
|
||||
os.path.normpath(template_path),
|
||||
os.path.normpath(workfile_path)
|
||||
)
|
||||
|
||||
self.log.info(f"Workfile to open: \"{workfile_path}\"")
|
||||
|
||||
return workfile_path
|
||||
50
pype/hooks/fusion/pre_fusion_setup.py
Normal file
50
pype/hooks/fusion/pre_fusion_setup.py
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import os
|
||||
import importlib
|
||||
from pype.lib import PreLaunchHook
|
||||
from pype.hosts.fusion import utils
|
||||
|
||||
|
||||
class FusionPrelaunch(PreLaunchHook):
|
||||
"""
|
||||
This hook will check if current workfile path has Fusion
|
||||
project inside.
|
||||
"""
|
||||
app_groups = ["fusion"]
|
||||
|
||||
def execute(self):
|
||||
# making sure pyton 3.6 is installed at provided path
|
||||
py36_dir = os.path.normpath(self.env.get("PYTHON36", ""))
|
||||
assert os.path.isdir(py36_dir), (
|
||||
"Python 3.6 is not installed at the provided folder path. Either "
|
||||
"make sure the `environments\resolve.json` is having correctly "
|
||||
"set `PYTHON36` or make sure Python 3.6 is installed "
|
||||
f"in given path. \nPYTHON36E: `{py36_dir}`"
|
||||
)
|
||||
self.log.info(f"Path to Fusion Python folder: `{py36_dir}`...")
|
||||
self.env["PYTHON36"] = py36_dir
|
||||
|
||||
# setting utility scripts dir for scripts syncing
|
||||
us_dir = os.path.normpath(
|
||||
self.env.get("FUSION_UTILITY_SCRIPTS_DIR", "")
|
||||
)
|
||||
assert os.path.isdir(us_dir), (
|
||||
"Fusion utility script dir does not exists. Either make sure "
|
||||
"the `environments\fusion.json` is having correctly set "
|
||||
"`FUSION_UTILITY_SCRIPTS_DIR` or reinstall DaVinci Resolve. \n"
|
||||
f"FUSION_UTILITY_SCRIPTS_DIR: `{us_dir}`"
|
||||
)
|
||||
|
||||
try:
|
||||
__import__("avalon.fusion")
|
||||
__import__("pyblish")
|
||||
|
||||
except ImportError:
|
||||
self.log.warning(
|
||||
"pyblish: Could not load Fusion integration.",
|
||||
exc_info=True
|
||||
)
|
||||
|
||||
else:
|
||||
# Resolve Setup integration
|
||||
importlib.reload(utils)
|
||||
utils.setup(self.env)
|
||||
184
pype/hooks/global/post_ftrack_changes.py
Normal file
184
pype/hooks/global/post_ftrack_changes.py
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
import os
|
||||
|
||||
import ftrack_api
|
||||
from pype.api import config
|
||||
from pype.lib import PostLaunchHook
|
||||
|
||||
|
||||
class PostFtrackHook(PostLaunchHook):
|
||||
order = None
|
||||
|
||||
def execute(self):
|
||||
project_name = self.data.get("project_name")
|
||||
asset_name = self.data.get("asset_name")
|
||||
task_name = self.data.get("task_name")
|
||||
|
||||
missing_context_keys = set()
|
||||
if not project_name:
|
||||
missing_context_keys.add("project_name")
|
||||
if not asset_name:
|
||||
missing_context_keys.add("asset_name")
|
||||
if not task_name:
|
||||
missing_context_keys.add("task_name")
|
||||
|
||||
if missing_context_keys:
|
||||
missing_keys_str = ", ".join([
|
||||
"\"{}\"".format(key) for key in missing_context_keys
|
||||
])
|
||||
self.log.debug("Hook {} skipped. Missing data keys: {}".format(
|
||||
self.__class__.__name__, missing_keys_str
|
||||
))
|
||||
return
|
||||
|
||||
required_keys = ("FTRACK_SERVER", "FTRACK_API_USER", "FTRACK_API_KEY")
|
||||
for key in required_keys:
|
||||
if not os.environ.get(key):
|
||||
self.log.debug((
|
||||
"Missing required environment \"{}\""
|
||||
" for Ftrack after launch procedure."
|
||||
).format(key))
|
||||
return
|
||||
|
||||
try:
|
||||
session = ftrack_api.Session(auto_connect_event_hub=True)
|
||||
self.log.debug("Ftrack session created")
|
||||
except Exception:
|
||||
self.log.warning("Couldn't create Ftrack session")
|
||||
return
|
||||
|
||||
try:
|
||||
entity = self.find_ftrack_task_entity(
|
||||
session, project_name, asset_name, task_name
|
||||
)
|
||||
if entity:
|
||||
self.ftrack_status_change(session, entity, project_name)
|
||||
self.start_timer(session, entity, ftrack_api)
|
||||
except Exception:
|
||||
self.log.warning(
|
||||
"Couldn't finish Ftrack procedure.", exc_info=True
|
||||
)
|
||||
return
|
||||
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
def find_ftrack_task_entity(
|
||||
self, session, project_name, asset_name, task_name
|
||||
):
|
||||
project_entity = session.query(
|
||||
"Project where full_name is \"{}\"".format(project_name)
|
||||
).first()
|
||||
if not project_entity:
|
||||
self.log.warning(
|
||||
"Couldn't find project \"{}\" in Ftrack.".format(project_name)
|
||||
)
|
||||
return
|
||||
|
||||
potential_task_entities = session.query((
|
||||
"TypedContext where parent.name is \"{}\" and project_id is \"{}\""
|
||||
).format(asset_name, project_entity["id"])).all()
|
||||
filtered_entities = []
|
||||
for _entity in potential_task_entities:
|
||||
if (
|
||||
_entity.entity_type.lower() == "task"
|
||||
and _entity["name"] == task_name
|
||||
):
|
||||
filtered_entities.append(_entity)
|
||||
|
||||
if not filtered_entities:
|
||||
self.log.warning((
|
||||
"Couldn't find task \"{}\" under parent \"{}\" in Ftrack."
|
||||
).format(task_name, asset_name))
|
||||
return
|
||||
|
||||
if len(filtered_entities) > 1:
|
||||
self.log.warning((
|
||||
"Found more than one task \"{}\""
|
||||
" under parent \"{}\" in Ftrack."
|
||||
).format(task_name, asset_name))
|
||||
return
|
||||
|
||||
return filtered_entities[0]
|
||||
|
||||
def ftrack_status_change(self, session, entity, project_name):
|
||||
# TODO use settings
|
||||
presets = config.get_presets(project_name)["ftrack"]["ftrack_config"]
|
||||
statuses = presets.get("status_update")
|
||||
if not statuses:
|
||||
return
|
||||
|
||||
actual_status = entity["status"]["name"].lower()
|
||||
already_tested = set()
|
||||
ent_path = "/".join(
|
||||
[ent["name"] for ent in entity["link"]]
|
||||
)
|
||||
while True:
|
||||
next_status_name = None
|
||||
for key, value in statuses.items():
|
||||
if key in already_tested:
|
||||
continue
|
||||
if actual_status in value or "_any_" in value:
|
||||
if key != "_ignore_":
|
||||
next_status_name = key
|
||||
already_tested.add(key)
|
||||
break
|
||||
already_tested.add(key)
|
||||
|
||||
if next_status_name is None:
|
||||
break
|
||||
|
||||
try:
|
||||
query = "Status where name is \"{}\"".format(
|
||||
next_status_name
|
||||
)
|
||||
status = session.query(query).one()
|
||||
|
||||
entity["status"] = status
|
||||
session.commit()
|
||||
self.log.debug("Changing status to \"{}\" <{}>".format(
|
||||
next_status_name, ent_path
|
||||
))
|
||||
break
|
||||
|
||||
except Exception:
|
||||
session.rollback()
|
||||
msg = (
|
||||
"Status \"{}\" in presets wasn't found"
|
||||
" on Ftrack entity type \"{}\""
|
||||
).format(next_status_name, entity.entity_type)
|
||||
self.log.warning(msg)
|
||||
|
||||
def start_timer(self, session, entity, _ftrack_api):
|
||||
"""Start Ftrack timer on task from context."""
|
||||
self.log.debug("Triggering timer start.")
|
||||
|
||||
user_entity = session.query("User where username is \"{}\"".format(
|
||||
os.environ["FTRACK_API_USER"]
|
||||
)).first()
|
||||
if not user_entity:
|
||||
self.log.warning(
|
||||
"Couldn't find user with username \"{}\" in Ftrack".format(
|
||||
os.environ["FTRACK_API_USER"]
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
source = {
|
||||
"user": {
|
||||
"id": user_entity["id"],
|
||||
"username": user_entity["username"]
|
||||
}
|
||||
}
|
||||
event_data = {
|
||||
"actionIdentifier": "start.timer",
|
||||
"selection": [{"entityId": entity["id"], "entityType": "task"}]
|
||||
}
|
||||
session.event_hub.publish(
|
||||
_ftrack_api.event.base.Event(
|
||||
topic="ftrack.action.launch",
|
||||
data=event_data,
|
||||
source=source
|
||||
),
|
||||
on_error="ignore"
|
||||
)
|
||||
self.log.debug("Timer start triggered successfully.")
|
||||
363
pype/hooks/global/pre_global_host_data.py
Normal file
363
pype/hooks/global/pre_global_host_data.py
Normal file
|
|
@ -0,0 +1,363 @@
|
|||
import os
|
||||
import re
|
||||
import json
|
||||
import getpass
|
||||
import copy
|
||||
|
||||
from pype.api import (
|
||||
Anatomy,
|
||||
config
|
||||
)
|
||||
from pype.lib import (
|
||||
env_value_to_bool,
|
||||
PreLaunchHook,
|
||||
ApplicationLaunchFailed
|
||||
)
|
||||
|
||||
import acre
|
||||
import avalon.api
|
||||
|
||||
|
||||
class GlobalHostDataHook(PreLaunchHook):
|
||||
order = -100
|
||||
|
||||
def execute(self):
|
||||
"""Prepare global objects to `data` that will be used for sure."""
|
||||
if not self.application.is_host:
|
||||
self.log.info(
|
||||
"Skipped hook {}. Application is not marked as host.".format(
|
||||
self.__class__.__name__
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
self.prepare_global_data()
|
||||
self.prepare_host_environments()
|
||||
self.prepare_context_environments()
|
||||
|
||||
def prepare_global_data(self):
|
||||
"""Prepare global objects to `data` that will be used for sure."""
|
||||
# Mongo documents
|
||||
project_name = self.data.get("project_name")
|
||||
if not project_name:
|
||||
self.log.info(
|
||||
"Skipping global data preparation."
|
||||
" Key `project_name` was not found in launch context."
|
||||
)
|
||||
return
|
||||
|
||||
self.log.debug("Project name is set to \"{}\"".format(project_name))
|
||||
# Anatomy
|
||||
self.data["anatomy"] = Anatomy(project_name)
|
||||
|
||||
# Mongo connection
|
||||
dbcon = avalon.api.AvalonMongoDB()
|
||||
dbcon.Session["AVALON_PROJECT"] = project_name
|
||||
dbcon.install()
|
||||
|
||||
self.data["dbcon"] = dbcon
|
||||
|
||||
# Project document
|
||||
project_doc = dbcon.find_one({"type": "project"})
|
||||
self.data["project_doc"] = project_doc
|
||||
|
||||
asset_name = self.data.get("asset_name")
|
||||
if not asset_name:
|
||||
self.log.warning(
|
||||
"Asset name was not set. Skipping asset document query."
|
||||
)
|
||||
return
|
||||
|
||||
asset_doc = dbcon.find_one({
|
||||
"type": "asset",
|
||||
"name": asset_name
|
||||
})
|
||||
self.data["asset_doc"] = asset_doc
|
||||
|
||||
def _merge_env(self, env, current_env):
|
||||
"""Modified function(merge) from acre module."""
|
||||
result = current_env.copy()
|
||||
for key, value in env.items():
|
||||
# Keep missing keys by not filling `missing` kwarg
|
||||
value = acre.lib.partial_format(value, data=current_env)
|
||||
result[key] = value
|
||||
return result
|
||||
|
||||
def prepare_host_environments(self):
|
||||
"""Modify launch environments based on launched app and context."""
|
||||
# Keys for getting environments
|
||||
env_keys = [self.app_group, self.app_name]
|
||||
|
||||
asset_doc = self.data.get("asset_doc")
|
||||
if asset_doc:
|
||||
# Add tools environments
|
||||
for key in asset_doc["data"].get("tools_env") or []:
|
||||
tool = self.manager.tools.get(key)
|
||||
if tool:
|
||||
if tool.group_name not in env_keys:
|
||||
env_keys.append(tool.group_name)
|
||||
|
||||
if tool.name not in env_keys:
|
||||
env_keys.append(tool.name)
|
||||
|
||||
self.log.debug(
|
||||
"Finding environment groups for keys: {}".format(env_keys)
|
||||
)
|
||||
|
||||
settings_env = self.data["settings_env"]
|
||||
env_values = {}
|
||||
for env_key in env_keys:
|
||||
_env_values = settings_env.get(env_key)
|
||||
if not _env_values:
|
||||
continue
|
||||
|
||||
# Choose right platform
|
||||
tool_env = acre.parse(_env_values)
|
||||
# Merge dictionaries
|
||||
env_values = self._merge_env(tool_env, env_values)
|
||||
|
||||
final_env = self._merge_env(
|
||||
acre.compute(env_values), self.launch_context.env
|
||||
)
|
||||
|
||||
# Update env
|
||||
self.launch_context.env.update(final_env)
|
||||
|
||||
def prepare_context_environments(self):
|
||||
"""Modify launch environemnts with context data for launched host."""
|
||||
# Context environments
|
||||
project_doc = self.data.get("project_doc")
|
||||
asset_doc = self.data.get("asset_doc")
|
||||
task_name = self.data.get("task_name")
|
||||
if (
|
||||
not project_doc
|
||||
or not asset_doc
|
||||
or not task_name
|
||||
):
|
||||
self.log.info(
|
||||
"Skipping context environments preparation."
|
||||
" Launch context does not contain required data."
|
||||
)
|
||||
return
|
||||
|
||||
workdir_data = self._prepare_workdir_data(
|
||||
project_doc, asset_doc, task_name
|
||||
)
|
||||
self.data["workdir_data"] = workdir_data
|
||||
|
||||
hierarchy = workdir_data["hierarchy"]
|
||||
anatomy = self.data["anatomy"]
|
||||
|
||||
try:
|
||||
anatomy_filled = anatomy.format(workdir_data)
|
||||
workdir = os.path.normpath(anatomy_filled["work"]["folder"])
|
||||
if not os.path.exists(workdir):
|
||||
self.log.debug(
|
||||
"Creating workdir folder: \"{}\"".format(workdir)
|
||||
)
|
||||
os.makedirs(workdir)
|
||||
|
||||
except Exception as exc:
|
||||
raise ApplicationLaunchFailed(
|
||||
"Error in anatomy.format: {}".format(str(exc))
|
||||
)
|
||||
|
||||
context_env = {
|
||||
"AVALON_PROJECT": project_doc["name"],
|
||||
"AVALON_ASSET": asset_doc["name"],
|
||||
"AVALON_TASK": task_name,
|
||||
"AVALON_APP": self.host_name,
|
||||
"AVALON_APP_NAME": self.app_name,
|
||||
"AVALON_HIERARCHY": hierarchy,
|
||||
"AVALON_WORKDIR": workdir
|
||||
}
|
||||
self.log.debug(
|
||||
"Context environemnts set:\n{}".format(
|
||||
json.dumps(context_env, indent=4)
|
||||
)
|
||||
)
|
||||
self.launch_context.env.update(context_env)
|
||||
|
||||
self.prepare_last_workfile(workdir)
|
||||
|
||||
def _prepare_workdir_data(self, project_doc, asset_doc, task_name):
|
||||
hierarchy = "/".join(asset_doc["data"]["parents"])
|
||||
|
||||
data = {
|
||||
"project": {
|
||||
"name": project_doc["name"],
|
||||
"code": project_doc["data"].get("code")
|
||||
},
|
||||
"task": task_name,
|
||||
"asset": asset_doc["name"],
|
||||
"app": self.host_name,
|
||||
"hierarchy": hierarchy
|
||||
}
|
||||
return data
|
||||
|
||||
def prepare_last_workfile(self, workdir):
|
||||
"""last workfile workflow preparation.
|
||||
|
||||
Function check if should care about last workfile workflow and tries
|
||||
to find the last workfile. Both information are stored to `data` and
|
||||
environments.
|
||||
|
||||
Last workfile is filled always (with version 1) even if any workfile
|
||||
exists yet.
|
||||
|
||||
Args:
|
||||
workdir (str): Path to folder where workfiles should be stored.
|
||||
"""
|
||||
_workdir_data = self.data.get("workdir_data")
|
||||
if not _workdir_data:
|
||||
self.log.info(
|
||||
"Skipping last workfile preparation."
|
||||
" Key `workdir_data` not filled."
|
||||
)
|
||||
return
|
||||
|
||||
workdir_data = copy.deepcopy(_workdir_data)
|
||||
project_name = self.data["project_name"]
|
||||
task_name = self.data["task_name"]
|
||||
start_last_workfile = self.should_start_last_workfile(
|
||||
project_name, self.host_name, task_name
|
||||
)
|
||||
self.data["start_last_workfile"] = start_last_workfile
|
||||
|
||||
# Store boolean as "0"(False) or "1"(True)
|
||||
self.launch_context.env["AVALON_OPEN_LAST_WORKFILE"] = (
|
||||
str(int(bool(start_last_workfile)))
|
||||
)
|
||||
|
||||
_sub_msg = "" if start_last_workfile else " not"
|
||||
self.log.debug(
|
||||
"Last workfile should{} be opened on start.".format(_sub_msg)
|
||||
)
|
||||
|
||||
# Last workfile path
|
||||
last_workfile_path = ""
|
||||
extensions = avalon.api.HOST_WORKFILE_EXTENSIONS.get(
|
||||
self.host_name
|
||||
)
|
||||
if extensions:
|
||||
anatomy = self.data["anatomy"]
|
||||
# Find last workfile
|
||||
file_template = anatomy.templates["work"]["file"]
|
||||
workdir_data.update({
|
||||
"version": 1,
|
||||
"user": os.environ.get("PYPE_USERNAME") or getpass.getuser(),
|
||||
"ext": extensions[0]
|
||||
})
|
||||
|
||||
last_workfile_path = avalon.api.last_workfile(
|
||||
workdir, file_template, workdir_data, extensions, True
|
||||
)
|
||||
|
||||
if os.path.exists(last_workfile_path):
|
||||
self.log.debug((
|
||||
"Workfiles for launch context does not exists"
|
||||
" yet but path will be set."
|
||||
))
|
||||
self.log.debug(
|
||||
"Setting last workfile path: {}".format(last_workfile_path)
|
||||
)
|
||||
|
||||
self.launch_context.env["AVALON_LAST_WORKFILE"] = last_workfile_path
|
||||
self.data["last_workfile_path"] = last_workfile_path
|
||||
|
||||
def should_start_last_workfile(self, project_name, host_name, task_name):
|
||||
"""Define if host should start last version workfile if possible.
|
||||
|
||||
Default output is `False`. Can be overriden with environment variable
|
||||
`AVALON_OPEN_LAST_WORKFILE`, valid values without case sensitivity are
|
||||
`"0", "1", "true", "false", "yes", "no"`.
|
||||
|
||||
Args:
|
||||
project_name (str): Name of project.
|
||||
host_name (str): Name of host which is launched. In avalon's
|
||||
application context it's value stored in app definition under
|
||||
key `"application_dir"`. Is not case sensitive.
|
||||
task_name (str): Name of task which is used for launching the host.
|
||||
Task name is not case sensitive.
|
||||
|
||||
Returns:
|
||||
bool: True if host should start workfile.
|
||||
|
||||
"""
|
||||
default_output = env_value_to_bool(
|
||||
"AVALON_OPEN_LAST_WORKFILE", default=False
|
||||
)
|
||||
# TODO convert to settings
|
||||
try:
|
||||
startup_presets = (
|
||||
config.get_presets(project_name)
|
||||
.get("tools", {})
|
||||
.get("workfiles", {})
|
||||
.get("last_workfile_on_startup")
|
||||
)
|
||||
except Exception:
|
||||
startup_presets = None
|
||||
self.log.warning("Couldn't load pype's presets", exc_info=True)
|
||||
|
||||
if not startup_presets:
|
||||
return default_output
|
||||
|
||||
host_name_lowered = host_name.lower()
|
||||
task_name_lowered = task_name.lower()
|
||||
|
||||
max_points = 2
|
||||
matching_points = -1
|
||||
matching_item = None
|
||||
for item in startup_presets:
|
||||
hosts = item.get("hosts") or tuple()
|
||||
tasks = item.get("tasks") or tuple()
|
||||
|
||||
hosts_lowered = tuple(_host_name.lower() for _host_name in hosts)
|
||||
# Skip item if has set hosts and current host is not in
|
||||
if hosts_lowered and host_name_lowered not in hosts_lowered:
|
||||
continue
|
||||
|
||||
tasks_lowered = tuple(_task_name.lower() for _task_name in tasks)
|
||||
# Skip item if has set tasks and current task is not in
|
||||
if tasks_lowered:
|
||||
task_match = False
|
||||
for task_regex in self.compile_list_of_regexes(tasks_lowered):
|
||||
if re.match(task_regex, task_name_lowered):
|
||||
task_match = True
|
||||
break
|
||||
|
||||
if not task_match:
|
||||
continue
|
||||
|
||||
points = int(bool(hosts_lowered)) + int(bool(tasks_lowered))
|
||||
if points > matching_points:
|
||||
matching_item = item
|
||||
matching_points = points
|
||||
|
||||
if matching_points == max_points:
|
||||
break
|
||||
|
||||
if matching_item is not None:
|
||||
output = matching_item.get("enabled")
|
||||
if output is None:
|
||||
output = default_output
|
||||
return output
|
||||
return default_output
|
||||
|
||||
@staticmethod
|
||||
def compile_list_of_regexes(in_list):
|
||||
"""Convert strings in entered list to compiled regex objects."""
|
||||
regexes = list()
|
||||
if not in_list:
|
||||
return regexes
|
||||
|
||||
for item in in_list:
|
||||
if item:
|
||||
try:
|
||||
regexes.append(re.compile(item))
|
||||
except TypeError:
|
||||
print((
|
||||
"Invalid type \"{}\" value \"{}\"."
|
||||
" Expected string based object. Skipping."
|
||||
).format(str(type(item)), str(item)))
|
||||
return regexes
|
||||
44
pype/hooks/harmony/pre_launch_args.py
Normal file
44
pype/hooks/harmony/pre_launch_args.py
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import os
|
||||
|
||||
from pype.lib import PreLaunchHook
|
||||
|
||||
|
||||
class HarmonyPrelaunchHook(PreLaunchHook):
|
||||
"""Launch arguments preparation.
|
||||
|
||||
Hook add python executable and execute python script of harmony
|
||||
implementation before harmony executable.
|
||||
"""
|
||||
app_groups = ["harmony"]
|
||||
|
||||
def execute(self):
|
||||
# Pop tvpaint executable
|
||||
harmony_executable = 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))
|
||||
|
||||
new_launch_args = [
|
||||
self.python_executable(),
|
||||
"-c",
|
||||
(
|
||||
"import avalon.harmony;"
|
||||
"avalon.harmony.launch(\"{}\")"
|
||||
).format(harmony_executable)
|
||||
]
|
||||
|
||||
# Append as whole list as these areguments should not be separated
|
||||
self.launch_context.launch_args.append(new_launch_args)
|
||||
|
||||
if remainders:
|
||||
self.log.warning((
|
||||
"There are unexpected launch arguments in Harmony launch. {}"
|
||||
).format(str(remainders)))
|
||||
self.launch_context.launch_args.extend(remainders)
|
||||
|
||||
def python_executable(self):
|
||||
"""Should lead to python executable."""
|
||||
# TODO change in Pype 3
|
||||
return os.environ["PYPE_PYTHON_EXE"]
|
||||
17
pype/hooks/hiero/pre_launch_args.py
Normal file
17
pype/hooks/hiero/pre_launch_args.py
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import os
|
||||
from pype.lib import PreLaunchHook
|
||||
|
||||
|
||||
class HieroLaunchArguments(PreLaunchHook):
|
||||
order = 0
|
||||
app_groups = ["hiero"]
|
||||
|
||||
def execute(self):
|
||||
"""Prepare suprocess launch arguments for Hiero."""
|
||||
# Add path to workfile to arguments
|
||||
if self.data.get("start_last_workfile"):
|
||||
last_workfile = self.data.get("last_workfile_path")
|
||||
if os.path.exists(last_workfile):
|
||||
self.launch_context.launch_args.append(
|
||||
"\"{}\"".format(last_workfile)
|
||||
)
|
||||
18
pype/hooks/maya/pre_launch_args.py
Normal file
18
pype/hooks/maya/pre_launch_args.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import os
|
||||
from pype.lib import PreLaunchHook
|
||||
|
||||
|
||||
class MayaLaunchArguments(PreLaunchHook):
|
||||
"""Add path to last workfile to launch arguments."""
|
||||
order = 0
|
||||
app_groups = ["maya"]
|
||||
|
||||
def execute(self):
|
||||
"""Prepare suprocess launch arguments for Maya."""
|
||||
# Add path to workfile to arguments
|
||||
if self.data.get("start_last_workfile"):
|
||||
last_workfile = self.data.get("last_workfile_path")
|
||||
if os.path.exists(last_workfile):
|
||||
self.launch_context.launch_args.append(
|
||||
"\"{}\"".format(last_workfile)
|
||||
)
|
||||
17
pype/hooks/nukestudio/pre_launch_args.py
Normal file
17
pype/hooks/nukestudio/pre_launch_args.py
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import os
|
||||
from pype.lib import PreLaunchHook
|
||||
|
||||
|
||||
class NukeStudioLaunchArguments(PreLaunchHook):
|
||||
order = 0
|
||||
app_groups = ["nukestudio"]
|
||||
|
||||
def execute(self):
|
||||
"""Prepare suprocess launch arguments for NukeStudio."""
|
||||
# Add path to workfile to arguments
|
||||
if self.data.get("start_last_workfile"):
|
||||
last_workfile = self.data.get("last_workfile_path")
|
||||
if os.path.exists(last_workfile):
|
||||
self.launch_context.launch_args.append(
|
||||
"\"{}\"".format(last_workfile)
|
||||
)
|
||||
17
pype/hooks/nukex/pre_launch_args.py
Normal file
17
pype/hooks/nukex/pre_launch_args.py
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import os
|
||||
from pype.lib import PreLaunchHook
|
||||
|
||||
|
||||
class NukeXLaunchArguments(PreLaunchHook):
|
||||
order = 0
|
||||
app_groups = ["nukex"]
|
||||
|
||||
def execute(self):
|
||||
"""Prepare suprocess launch arguments for NukeX."""
|
||||
# Add path to workfile to arguments
|
||||
if self.data.get("start_last_workfile"):
|
||||
last_workfile = self.data.get("last_workfile_path")
|
||||
if os.path.exists(last_workfile):
|
||||
self.launch_context.launch_args.append(
|
||||
"\"{}\"".format(last_workfile)
|
||||
)
|
||||
44
pype/hooks/photoshop/pre_launch_args.py
Normal file
44
pype/hooks/photoshop/pre_launch_args.py
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import os
|
||||
|
||||
from pype.lib import PreLaunchHook
|
||||
|
||||
|
||||
class PhotoshopPrelaunchHook(PreLaunchHook):
|
||||
"""Launch arguments preparation.
|
||||
|
||||
Hook add python executable and execute python script of photoshop
|
||||
implementation before photoshop executable.
|
||||
"""
|
||||
app_groups = ["photoshop"]
|
||||
|
||||
def execute(self):
|
||||
# Pop tvpaint executable
|
||||
photoshop_executable = 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))
|
||||
|
||||
new_launch_args = [
|
||||
self.python_executable(),
|
||||
"-c",
|
||||
(
|
||||
"import avalon.photoshop;"
|
||||
"avalon.photoshop.launch(\"{}\")"
|
||||
).format(photoshop_executable)
|
||||
]
|
||||
|
||||
# Append as whole list as these areguments should not be separated
|
||||
self.launch_context.launch_args.append(new_launch_args)
|
||||
|
||||
if remainders:
|
||||
self.log.warning((
|
||||
"There are unexpected launch arguments in Photoshop launch. {}"
|
||||
).format(str(remainders)))
|
||||
self.launch_context.launch_args.extend(remainders)
|
||||
|
||||
def python_executable(self):
|
||||
"""Should lead to python executable."""
|
||||
# TODO change in Pype 3
|
||||
return os.environ["PYPE_PYTHON_EXE"]
|
||||
58
pype/hooks/resolve/pre_resolve_setup.py
Normal file
58
pype/hooks/resolve/pre_resolve_setup.py
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import os
|
||||
import importlib
|
||||
from pype.lib import PreLaunchHook
|
||||
from pype.hosts.resolve import utils
|
||||
|
||||
|
||||
class ResolvePrelaunch(PreLaunchHook):
|
||||
"""
|
||||
This hook will check if current workfile path has Resolve
|
||||
project inside. IF not, it initialize it and finally it pass
|
||||
path to the project by environment variable to Premiere launcher
|
||||
shell script.
|
||||
"""
|
||||
app_groups = ["resolve"]
|
||||
|
||||
def execute(self):
|
||||
# making sure pyton 3.6 is installed at provided path
|
||||
py36_dir = os.path.normpath(self.env.get("PYTHON36_RESOLVE", ""))
|
||||
assert os.path.isdir(py36_dir), (
|
||||
"Python 3.6 is not installed at the provided folder path. Either "
|
||||
"make sure the `environments\resolve.json` is having correctly "
|
||||
"set `PYTHON36_RESOLVE` or make sure Python 3.6 is installed "
|
||||
f"in given path. \nPYTHON36_RESOLVE: `{py36_dir}`"
|
||||
)
|
||||
self.log.info(f"Path to Resolve Python folder: `{py36_dir}`...")
|
||||
self.env["PYTHON36_RESOLVE"] = py36_dir
|
||||
|
||||
# setting utility scripts dir for scripts syncing
|
||||
us_dir = os.path.normpath(
|
||||
self.env.get("RESOLVE_UTILITY_SCRIPTS_DIR", "")
|
||||
)
|
||||
assert os.path.isdir(us_dir), (
|
||||
"Resolve utility script dir does not exists. Either make sure "
|
||||
"the `environments\resolve.json` is having correctly set "
|
||||
"`RESOLVE_UTILITY_SCRIPTS_DIR` or reinstall DaVinci Resolve. \n"
|
||||
f"RESOLVE_UTILITY_SCRIPTS_DIR: `{us_dir}`"
|
||||
)
|
||||
self.log.debug(f"-- us_dir: `{us_dir}`")
|
||||
|
||||
# correctly format path for pre python script
|
||||
pre_py_sc = os.path.normpath(self.env.get("PRE_PYTHON_SCRIPT", ""))
|
||||
self.env["PRE_PYTHON_SCRIPT"] = pre_py_sc
|
||||
self.log.debug(f"-- pre_py_sc: `{pre_py_sc}`...")
|
||||
try:
|
||||
__import__("pype.hosts.resolve")
|
||||
__import__("pyblish")
|
||||
|
||||
except ImportError:
|
||||
self.log.warning(
|
||||
"pyblish: Could not load Resolve integration.",
|
||||
exc_info=True
|
||||
)
|
||||
|
||||
else:
|
||||
# Resolve Setup integration
|
||||
importlib.reload(utils)
|
||||
self.log.debug(f"-- utils.__file__: `{utils.__file__}`")
|
||||
utils.setup(self.env)
|
||||
35
pype/hooks/tvpaint/pre_install_pywin.py
Normal file
35
pype/hooks/tvpaint/pre_install_pywin.py
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
from pype.lib import (
|
||||
PreLaunchHook,
|
||||
ApplicationLaunchFailed,
|
||||
_subprocess
|
||||
)
|
||||
|
||||
|
||||
class PreInstallPyWin(PreLaunchHook):
|
||||
"""Hook makes sure there is installed python module pywin32 on windows."""
|
||||
# WARNING This hook will probably be deprecated in Pype 3 - kept for test
|
||||
order = 10
|
||||
app_groups = ["tvpaint"]
|
||||
platforms = ["windows"]
|
||||
|
||||
def execute(self):
|
||||
installed = False
|
||||
try:
|
||||
from win32com.shell import shell
|
||||
self.log.debug("Python module `pywin32` already installed.")
|
||||
installed = True
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if installed:
|
||||
return
|
||||
|
||||
try:
|
||||
output = _subprocess(
|
||||
["pip", "install", "pywin32==227"]
|
||||
)
|
||||
self.log.debug("Pip install pywin32 output:\n{}'".format(output))
|
||||
except RuntimeError:
|
||||
msg = "Installation of python module `pywin32` crashed."
|
||||
self.log.warning(msg, exc_info=True)
|
||||
raise ApplicationLaunchFailed(msg)
|
||||
105
pype/hooks/tvpaint/pre_launch_args.py
Normal file
105
pype/hooks/tvpaint/pre_launch_args.py
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
import os
|
||||
import shutil
|
||||
|
||||
from pype.hosts import tvpaint
|
||||
from pype.lib import PreLaunchHook
|
||||
|
||||
import avalon
|
||||
|
||||
|
||||
class TvpaintPrelaunchHook(PreLaunchHook):
|
||||
"""Launch arguments preparation.
|
||||
|
||||
Hook add python executable and script path to tvpaint implementation before
|
||||
tvpaint 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 = ["tvpaint"]
|
||||
|
||||
def execute(self):
|
||||
# Pop tvpaint executable
|
||||
tvpaint_executable = 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))
|
||||
|
||||
new_launch_args = [
|
||||
self.main_executable(),
|
||||
self.launch_script_path(),
|
||||
tvpaint_executable
|
||||
]
|
||||
|
||||
# Add workfile to launch arguments
|
||||
workfile_path = self.workfile_path()
|
||||
if workfile_path:
|
||||
new_launch_args.append(
|
||||
"\"{}\"".format(workfile_path)
|
||||
)
|
||||
|
||||
# How to create new command line
|
||||
# if platform.system().lower() == "windows":
|
||||
# new_launch_args = [
|
||||
# "cmd.exe",
|
||||
# "/c",
|
||||
# "Call cmd.exe /k",
|
||||
# *new_launch_args
|
||||
# ]
|
||||
|
||||
# Append as whole list as these areguments should not be separated
|
||||
self.launch_context.launch_args.append(new_launch_args)
|
||||
|
||||
if remainders:
|
||||
self.log.warning((
|
||||
"There are unexpected launch arguments in TVPaint launch. {}"
|
||||
).format(str(remainders)))
|
||||
self.launch_context.launch_args.extend(remainders)
|
||||
|
||||
def main_executable(self):
|
||||
"""Should lead to python executable."""
|
||||
# TODO change in Pype 3
|
||||
return os.path.normpath(os.environ["PYPE_PYTHON_EXE"])
|
||||
|
||||
def launch_script_path(self):
|
||||
avalon_dir = os.path.dirname(os.path.abspath(avalon.__file__))
|
||||
script_path = os.path.join(
|
||||
avalon_dir,
|
||||
"tvpaint",
|
||||
"launch_script.py"
|
||||
)
|
||||
return script_path
|
||||
|
||||
def workfile_path(self):
|
||||
workfile_path = self.data["last_workfile_path"]
|
||||
|
||||
# copy workfile from template if doesnt exist any on path
|
||||
if not os.path.exists(workfile_path):
|
||||
# TODO add ability to set different template workfile path via
|
||||
# settings
|
||||
pype_dir = os.path.dirname(os.path.abspath(tvpaint.__file__))
|
||||
template_path = os.path.join(pype_dir, "template.tvpp")
|
||||
|
||||
if not os.path.exists(template_path):
|
||||
self.log.warning(
|
||||
"Couldn't find workfile template file in {}".format(
|
||||
template_path
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
self.log.info(
|
||||
f"Creating workfile from template: \"{template_path}\""
|
||||
)
|
||||
|
||||
# Copy template workfile to new destinantion
|
||||
shutil.copy2(
|
||||
os.path.normpath(template_path),
|
||||
os.path.normpath(workfile_path)
|
||||
)
|
||||
|
||||
self.log.info(f"Workfile to open: \"{workfile_path}\"")
|
||||
|
||||
return workfile_path
|
||||
95
pype/hooks/unreal/pre_workfile_preparation.py
Normal file
95
pype/hooks/unreal/pre_workfile_preparation.py
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
import os
|
||||
|
||||
from pype.lib import (
|
||||
PreLaunchHook,
|
||||
ApplicationLaunchFailed
|
||||
)
|
||||
from pype.hosts.unreal import lib as unreal_lib
|
||||
|
||||
|
||||
class UnrealPrelaunchHook(PreLaunchHook):
|
||||
"""
|
||||
This hook will check if current workfile path has Unreal
|
||||
project inside. IF not, it initialize it and finally it pass
|
||||
path to the project by environment variable to Unreal launcher
|
||||
shell script.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.signature = "( {} )".format(self.__class__.__name__)
|
||||
|
||||
def execute(self):
|
||||
asset_name = self.data["asset_name"]
|
||||
task_name = self.data["task_name"]
|
||||
workdir = self.env["AVALON_WORKDIR"]
|
||||
engine_version = self.app_name.split("_")[-1]
|
||||
unreal_project_name = f"{asset_name}_{task_name}"
|
||||
|
||||
# Unreal is sensitive about project names longer then 20 chars
|
||||
if len(unreal_project_name) > 20:
|
||||
self.log.warning((
|
||||
f"Project name exceed 20 characters ({unreal_project_name})!"
|
||||
))
|
||||
|
||||
# Unreal doesn't accept non alphabet characters at the start
|
||||
# of the project name. This is because project name is then used
|
||||
# in various places inside c++ code and there variable names cannot
|
||||
# start with non-alpha. We append 'P' before project name to solve it.
|
||||
# 😱
|
||||
if not unreal_project_name[:1].isalpha():
|
||||
self.log.warning((
|
||||
"Project name doesn't start with alphabet "
|
||||
f"character ({unreal_project_name}). Appending 'P'"
|
||||
))
|
||||
unreal_project_name = f"P{unreal_project_name}"
|
||||
|
||||
project_path = os.path.join(workdir, unreal_project_name)
|
||||
|
||||
self.log.info((
|
||||
f"{self.signature} requested UE4 version: "
|
||||
f"[ {engine_version} ]"
|
||||
))
|
||||
|
||||
detected = unreal_lib.get_engine_versions()
|
||||
detected_str = ', '.join(detected.keys()) or 'none'
|
||||
self.log.info((
|
||||
f"{self.signature} detected UE4 versions: "
|
||||
f"[ {detected_str} ]"
|
||||
))
|
||||
|
||||
engine_version = ".".join(engine_version.split(".")[:2])
|
||||
if engine_version not in detected.keys():
|
||||
raise ApplicationLaunchFailed((
|
||||
f"{self.signature} requested version not "
|
||||
f"detected [ {engine_version} ]"
|
||||
))
|
||||
|
||||
os.makedirs(project_path, exist_ok=True)
|
||||
|
||||
project_file = os.path.join(
|
||||
project_path,
|
||||
f"{unreal_project_name}.uproject"
|
||||
)
|
||||
if not os.path.isfile(project_file):
|
||||
engine_path = detected[engine_version]
|
||||
self.log.info((
|
||||
f"{self.signature} creating unreal "
|
||||
f"project [ {unreal_project_name} ]"
|
||||
))
|
||||
# Set "AVALON_UNREAL_PLUGIN" to current process environment for
|
||||
# execution of `create_unreal_project`
|
||||
env_key = "AVALON_UNREAL_PLUGIN"
|
||||
if self.env.get(env_key):
|
||||
os.environ[env_key] = self.env[env_key]
|
||||
|
||||
unreal_lib.create_unreal_project(
|
||||
unreal_project_name,
|
||||
engine_version,
|
||||
project_path,
|
||||
engine_path=engine_path
|
||||
)
|
||||
|
||||
# Append project file to launch arguments
|
||||
self.launch_context.launch_args.append(f"\"{project_file}\"")
|
||||
|
|
@ -11,6 +11,12 @@ from .env_tools import (
|
|||
get_paths_from_environ
|
||||
)
|
||||
|
||||
from .python_module_tools import (
|
||||
modules_from_path,
|
||||
recursive_bases_from_class,
|
||||
classes_from_module
|
||||
)
|
||||
|
||||
from .avalon_context import (
|
||||
is_latest,
|
||||
any_outdated,
|
||||
|
|
@ -28,6 +34,8 @@ from .applications import (
|
|||
ApplictionExecutableNotFound,
|
||||
ApplicationNotFound,
|
||||
ApplicationManager,
|
||||
PreLaunchHook,
|
||||
PostLaunchHook,
|
||||
launch_application,
|
||||
ApplicationAction,
|
||||
_subprocess
|
||||
|
|
@ -53,6 +61,10 @@ __all__ = [
|
|||
"env_value_to_bool",
|
||||
"get_paths_from_environ",
|
||||
|
||||
"modules_from_path",
|
||||
"recursive_bases_from_class",
|
||||
"classes_from_module",
|
||||
|
||||
"is_latest",
|
||||
"any_outdated",
|
||||
"get_asset",
|
||||
|
|
@ -68,6 +80,8 @@ __all__ = [
|
|||
"ApplictionExecutableNotFound",
|
||||
"ApplicationNotFound",
|
||||
"ApplicationManager",
|
||||
"PreLaunchHook",
|
||||
"PostLaunchHook",
|
||||
"launch_application",
|
||||
"ApplicationAction",
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -20,9 +20,9 @@ def env_value_to_bool(env_key=None, value=None, default=False):
|
|||
|
||||
if value is not None:
|
||||
value = str(value).lower()
|
||||
if value in ("true", "yes", "1"):
|
||||
if value in ("true", "yes", "1", "on"):
|
||||
return True
|
||||
elif value in ("false", "no", "0"):
|
||||
elif value in ("false", "no", "0", "off"):
|
||||
return False
|
||||
return default
|
||||
|
||||
|
|
|
|||
113
pype/lib/python_module_tools.py
Normal file
113
pype/lib/python_module_tools.py
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
import os
|
||||
import sys
|
||||
import types
|
||||
import importlib
|
||||
import inspect
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
PY3 = sys.version_info[0] == 3
|
||||
|
||||
|
||||
def modules_from_path(folder_path):
|
||||
"""Get python scripts as modules from a path.
|
||||
|
||||
Arguments:
|
||||
path (str): Path to folder containing python scripts.
|
||||
|
||||
Returns:
|
||||
List of modules.
|
||||
"""
|
||||
|
||||
folder_path = os.path.normpath(folder_path)
|
||||
|
||||
modules = []
|
||||
if not os.path.isdir(folder_path):
|
||||
log.warning("Not a directory path: {}".format(folder_path))
|
||||
return modules
|
||||
|
||||
for filename in os.listdir(folder_path):
|
||||
# Ignore files which start with underscore
|
||||
if filename.startswith("_"):
|
||||
continue
|
||||
|
||||
mod_name, mod_ext = os.path.splitext(filename)
|
||||
if not mod_ext == ".py":
|
||||
continue
|
||||
|
||||
full_path = os.path.join(folder_path, filename)
|
||||
if not os.path.isfile(full_path):
|
||||
continue
|
||||
|
||||
try:
|
||||
# Prepare module object where content of file will be parsed
|
||||
module = types.ModuleType(mod_name)
|
||||
|
||||
if PY3:
|
||||
# Use loader so module has full specs
|
||||
module_loader = importlib.machinery.SourceFileLoader(
|
||||
mod_name, full_path
|
||||
)
|
||||
module_loader.exec_module(module)
|
||||
else:
|
||||
# Execute module code and store content to module
|
||||
with open(full_path) as _stream:
|
||||
# Execute content and store it to module object
|
||||
exec(_stream.read(), module.__dict__)
|
||||
|
||||
module.__file__ = full_path
|
||||
|
||||
modules.append(module)
|
||||
|
||||
except Exception:
|
||||
log.warning(
|
||||
"Failed to load path: \"{0}\"".format(full_path),
|
||||
exc_info=True
|
||||
)
|
||||
continue
|
||||
|
||||
return modules
|
||||
|
||||
|
||||
def recursive_bases_from_class(klass):
|
||||
"""Extract all bases from entered class."""
|
||||
result = []
|
||||
bases = klass.__bases__
|
||||
result.extend(bases)
|
||||
for base in bases:
|
||||
result.extend(recursive_bases_from_class(base))
|
||||
return result
|
||||
|
||||
|
||||
def classes_from_module(superclass, module):
|
||||
"""Return plug-ins from module
|
||||
|
||||
Arguments:
|
||||
superclass (superclass): Superclass of subclasses to look for
|
||||
module (types.ModuleType): Imported module from which to
|
||||
parse valid Avalon plug-ins.
|
||||
|
||||
Returns:
|
||||
List of plug-ins, or empty list if none is found.
|
||||
|
||||
"""
|
||||
|
||||
classes = list()
|
||||
for name in dir(module):
|
||||
# It could be anything at this point
|
||||
obj = getattr(module, name)
|
||||
if not inspect.isclass(obj):
|
||||
continue
|
||||
|
||||
# These are subclassed from nothing, not even `object`
|
||||
if not len(obj.__bases__) > 0:
|
||||
continue
|
||||
|
||||
# Use string comparison rather than `issubclass`
|
||||
# in order to support reloading of this module.
|
||||
bases = recursive_bases_from_class(obj)
|
||||
if not any(base.__name__ == superclass.__name__ for base in bases):
|
||||
continue
|
||||
|
||||
classes.append(obj)
|
||||
return classes
|
||||
|
|
@ -17,7 +17,10 @@ from bson.errors import InvalidId
|
|||
from pymongo import UpdateOne
|
||||
import ftrack_api
|
||||
from pype.api import config
|
||||
|
||||
from pype.lib import (
|
||||
ApplicationManager,
|
||||
env_value_to_bool
|
||||
)
|
||||
|
||||
log = Logger().get_logger(__name__)
|
||||
|
||||
|
|
@ -186,12 +189,28 @@ def get_project_apps(in_app_list):
|
|||
dictionary of warnings
|
||||
"""
|
||||
apps = []
|
||||
warnings = collections.defaultdict(list)
|
||||
|
||||
if env_value_to_bool("PYPE_USE_APP_MANAGER", default=False):
|
||||
missing_app_msg = "Missing definition of application"
|
||||
application_manager = ApplicationManager()
|
||||
for app_name in in_app_list:
|
||||
app = application_manager.applications.get(app_name)
|
||||
if app:
|
||||
apps.append({
|
||||
"name": app_name,
|
||||
"label": app.full_label
|
||||
})
|
||||
else:
|
||||
warnings[missing_app_msg].append(app_name)
|
||||
return apps, warnings
|
||||
|
||||
# TODO report
|
||||
missing_toml_msg = "Missing config file for application"
|
||||
error_msg = (
|
||||
"Unexpected error happend during preparation of application"
|
||||
)
|
||||
warnings = collections.defaultdict(list)
|
||||
|
||||
for app in in_app_list:
|
||||
try:
|
||||
toml_path = avalon.lib.which_app(app)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"enabled": true,
|
||||
"label": "Autodesk Maya",
|
||||
"icon": "{}/app_icons/maya.png",
|
||||
"is_host": true,
|
||||
"host_name": "maya",
|
||||
"environment": {
|
||||
"__environment_keys__": {
|
||||
"maya": [
|
||||
|
|
@ -39,11 +39,17 @@
|
|||
"icon": "",
|
||||
"executables": {
|
||||
"windows": [
|
||||
"C:\\Program Files\\Autodesk\\Maya2020\\bin\\maya.exe"
|
||||
[
|
||||
"C:\\Program Files\\Autodesk\\Maya2020\\bin\\maya.exe",
|
||||
""
|
||||
]
|
||||
],
|
||||
"darwin": [],
|
||||
"linux": [
|
||||
"/usr/autodesk/maya2020/bin/maya"
|
||||
[
|
||||
"/usr/autodesk/maya2020/bin/maya",
|
||||
""
|
||||
]
|
||||
]
|
||||
},
|
||||
"environment": {
|
||||
|
|
@ -62,11 +68,17 @@
|
|||
"icon": "",
|
||||
"executables": {
|
||||
"windows": [
|
||||
"C:\\Program Files\\Autodesk\\Maya2019\\bin\\maya.exe"
|
||||
[
|
||||
"C:\\Program Files\\Autodesk\\Maya2019\\bin\\maya.exe",
|
||||
""
|
||||
]
|
||||
],
|
||||
"darwin": [],
|
||||
"linux": [
|
||||
"/usr/autodesk/maya2019/bin/maya"
|
||||
[
|
||||
"/usr/autodesk/maya2019/bin/maya",
|
||||
""
|
||||
]
|
||||
]
|
||||
},
|
||||
"environment": {
|
||||
|
|
@ -85,11 +97,17 @@
|
|||
"icon": "",
|
||||
"executables": {
|
||||
"windows": [
|
||||
"C:\\Program Files\\Autodesk\\Maya2017\\bin\\maya.exe"
|
||||
[
|
||||
"C:\\Program Files\\Autodesk\\Maya2017\\bin\\maya.exe",
|
||||
""
|
||||
]
|
||||
],
|
||||
"darwin": [],
|
||||
"linux": [
|
||||
"/usr/autodesk/maya2018/bin/maya"
|
||||
[
|
||||
"/usr/autodesk/maya2018/bin/maya",
|
||||
""
|
||||
]
|
||||
]
|
||||
},
|
||||
"environment": {
|
||||
|
|
@ -107,7 +125,7 @@
|
|||
"enabled": true,
|
||||
"label": "Autodesk MayaBatch",
|
||||
"icon": "{}/app_icons/maya.png",
|
||||
"is_host": false,
|
||||
"host_name": "maya",
|
||||
"environment": {
|
||||
"__environment_keys__": {
|
||||
"mayabatch": [
|
||||
|
|
@ -143,7 +161,10 @@
|
|||
"icon": "",
|
||||
"executables": {
|
||||
"windows": [
|
||||
"C:\\Program Files\\Autodesk\\Maya2020\\bin\\mayabatch.exe"
|
||||
[
|
||||
"C:\\Program Files\\Autodesk\\Maya2020\\bin\\mayabatch.exe",
|
||||
""
|
||||
]
|
||||
],
|
||||
"darwin": [],
|
||||
"linux": []
|
||||
|
|
@ -164,7 +185,10 @@
|
|||
"icon": "",
|
||||
"executables": {
|
||||
"windows": [
|
||||
"C:\\Program Files\\Autodesk\\Maya2019\\bin\\mayabatch.exe"
|
||||
[
|
||||
"C:\\Program Files\\Autodesk\\Maya2019\\bin\\mayabatch.exe",
|
||||
""
|
||||
]
|
||||
],
|
||||
"darwin": [],
|
||||
"linux": []
|
||||
|
|
@ -185,7 +209,10 @@
|
|||
"icon": "",
|
||||
"executables": {
|
||||
"windows": [
|
||||
"C:\\Program Files\\Autodesk\\Maya2018\\bin\\mayabatch.exe"
|
||||
[
|
||||
"C:\\Program Files\\Autodesk\\Maya2018\\bin\\mayabatch.exe",
|
||||
""
|
||||
]
|
||||
],
|
||||
"darwin": [],
|
||||
"linux": []
|
||||
|
|
@ -205,7 +232,7 @@
|
|||
"enabled": true,
|
||||
"label": "Nuke",
|
||||
"icon": "{}/app_icons/nuke.png",
|
||||
"is_host": true,
|
||||
"host_name": "nuke",
|
||||
"environment": {
|
||||
"__environment_keys__": {
|
||||
"nuke": [
|
||||
|
|
@ -230,11 +257,17 @@
|
|||
"icon": "",
|
||||
"executables": {
|
||||
"windows": [
|
||||
"C:\\Program Files\\Nuke12.0v1\\Nuke12.0.exe"
|
||||
[
|
||||
"C:\\Program Files\\Nuke12.0v1\\Nuke12.0.exe",
|
||||
""
|
||||
]
|
||||
],
|
||||
"darwin": [],
|
||||
"linux": [
|
||||
"/usr/local/Nuke12.0v1/Nuke12.0"
|
||||
[
|
||||
"/usr/local/Nuke12.0v1/Nuke12.0",
|
||||
""
|
||||
]
|
||||
]
|
||||
},
|
||||
"environment": {
|
||||
|
|
@ -250,11 +283,17 @@
|
|||
"icon": "",
|
||||
"executables": {
|
||||
"windows": [
|
||||
"C:\\Program Files\\Nuke11.3v1\\Nuke11.3.exe"
|
||||
[
|
||||
"C:\\Program Files\\Nuke11.3v1\\Nuke11.3.exe",
|
||||
""
|
||||
]
|
||||
],
|
||||
"darwin": [],
|
||||
"linux": [
|
||||
"/usr/local/Nuke11.3v5/Nuke11.3"
|
||||
[
|
||||
"/usr/local/Nuke11.3v5/Nuke11.3",
|
||||
""
|
||||
]
|
||||
]
|
||||
},
|
||||
"environment": {
|
||||
|
|
@ -270,7 +309,10 @@
|
|||
"icon": "",
|
||||
"executables": {
|
||||
"windows": [
|
||||
"C:\\Program Files\\Nuke11.2v2\\Nuke11.2.exe"
|
||||
[
|
||||
"C:\\Program Files\\Nuke11.2v2\\Nuke11.2.exe",
|
||||
""
|
||||
]
|
||||
],
|
||||
"darwin": [],
|
||||
"linux": []
|
||||
|
|
@ -287,7 +329,7 @@
|
|||
"enabled": true,
|
||||
"label": "Nuke X",
|
||||
"icon": "{}/app_icons/nuke.png",
|
||||
"is_host": true,
|
||||
"host_name": "nuke",
|
||||
"environment": {
|
||||
"__environment_keys__": {
|
||||
"nukex": [
|
||||
|
|
@ -312,11 +354,17 @@
|
|||
"icon": "",
|
||||
"executables": {
|
||||
"windows": [
|
||||
"C:\\Program Files\\Nuke12.0v1\\Nuke12.0.exe"
|
||||
[
|
||||
"C:\\Program Files\\Nuke12.0v1\\Nuke12.0.exe",
|
||||
"--nukex"
|
||||
]
|
||||
],
|
||||
"darwin": [],
|
||||
"linux": [
|
||||
"/usr/local/Nuke12.0v1/Nuke12.0"
|
||||
[
|
||||
"/usr/local/Nuke12.0v1/Nuke12.0",
|
||||
"--nukex"
|
||||
]
|
||||
]
|
||||
},
|
||||
"environment": {
|
||||
|
|
@ -332,11 +380,17 @@
|
|||
"icon": "",
|
||||
"executables": {
|
||||
"windows": [
|
||||
"C:\\Program Files\\Nuke11.3v1\\Nuke11.3.exe"
|
||||
[
|
||||
"C:\\Program Files\\Nuke11.3v1\\Nuke11.3.exe",
|
||||
"--nukex"
|
||||
]
|
||||
],
|
||||
"darwin": [],
|
||||
"linux": [
|
||||
"/usr/local/Nuke11.3v5/Nuke11.3"
|
||||
[
|
||||
"/usr/local/Nuke11.3v5/Nuke11.3",
|
||||
"--nukex"
|
||||
]
|
||||
]
|
||||
},
|
||||
"environment": {
|
||||
|
|
@ -352,7 +406,10 @@
|
|||
"icon": "",
|
||||
"executables": {
|
||||
"windows": [
|
||||
"C:\\Program Files\\Nuke11.2v2\\Nuke11.2.exe"
|
||||
[
|
||||
"C:\\Program Files\\Nuke11.2v2\\Nuke11.2.exe",
|
||||
"--nukex"
|
||||
]
|
||||
],
|
||||
"darwin": [],
|
||||
"linux": []
|
||||
|
|
@ -369,7 +426,7 @@
|
|||
"enabled": true,
|
||||
"label": "Nuke Studio",
|
||||
"icon": "{}/app_icons/nuke.png",
|
||||
"is_host": true,
|
||||
"host_name": "hiero",
|
||||
"environment": {
|
||||
"__environment_keys__": {
|
||||
"nukestudio": [
|
||||
|
|
@ -398,11 +455,17 @@
|
|||
"icon": "",
|
||||
"executables": {
|
||||
"windows": [
|
||||
"C:\\Program Files\\Nuke12.0v1\\Nuke12.0.exe"
|
||||
[
|
||||
"C:\\Program Files\\Nuke12.0v1\\Nuke12.0.exe",
|
||||
"--studio"
|
||||
]
|
||||
],
|
||||
"darwin": [],
|
||||
"linux": [
|
||||
"/usr/local/Nuke12.0v1/Nuke12.0"
|
||||
[
|
||||
"/usr/local/Nuke12.0v1/Nuke12.0",
|
||||
"--studio"
|
||||
]
|
||||
]
|
||||
},
|
||||
"environment": {
|
||||
|
|
@ -418,11 +481,17 @@
|
|||
"icon": "",
|
||||
"executables": {
|
||||
"windows": [
|
||||
"C:\\Program Files\\Nuke11.3v1\\Nuke11.3.exe"
|
||||
[
|
||||
"C:\\Program Files\\Nuke11.3v1\\Nuke11.3.exe",
|
||||
"--studio"
|
||||
]
|
||||
],
|
||||
"darwin": [],
|
||||
"linux": [
|
||||
"/usr/local/Nuke11.3v5/Nuke11.3"
|
||||
[
|
||||
"/usr/local/Nuke11.3v5/Nuke11.3",
|
||||
"--studio"
|
||||
]
|
||||
]
|
||||
},
|
||||
"environment": {
|
||||
|
|
@ -453,7 +522,7 @@
|
|||
"enabled": true,
|
||||
"label": "Hiero",
|
||||
"icon": "{}/app_icons/hiero.png",
|
||||
"is_host": true,
|
||||
"host_name": "hiero",
|
||||
"environment": {
|
||||
"__environment_keys__": {
|
||||
"hiero": [
|
||||
|
|
@ -482,11 +551,17 @@
|
|||
"icon": "",
|
||||
"executables": {
|
||||
"windows": [
|
||||
"C:\\Program Files\\Nuke12.0v1\\Nuke12.0.exe"
|
||||
[
|
||||
"C:\\Program Files\\Nuke12.0v1\\Nuke12.0.exe",
|
||||
"--hiero"
|
||||
]
|
||||
],
|
||||
"darwin": [],
|
||||
"linux": [
|
||||
"/usr/local/Nuke12.0v1/Nuke12.0"
|
||||
[
|
||||
"/usr/local/Nuke12.0v1/Nuke12.0",
|
||||
"--hiero"
|
||||
]
|
||||
]
|
||||
},
|
||||
"environment": {
|
||||
|
|
@ -502,11 +577,17 @@
|
|||
"icon": "",
|
||||
"executables": {
|
||||
"windows": [
|
||||
"C:\\Program Files\\Nuke11.3v1\\Nuke11.3.exe"
|
||||
[
|
||||
"C:\\Program Files\\Nuke11.3v1\\Nuke11.3.exe",
|
||||
"--hiero"
|
||||
]
|
||||
],
|
||||
"darwin": [],
|
||||
"linux": [
|
||||
"/usr/local/Nuke11.3v5/Nuke11.3"
|
||||
[
|
||||
"/usr/local/Nuke11.3v5/Nuke11.3",
|
||||
"--hiero"
|
||||
]
|
||||
]
|
||||
},
|
||||
"environment": {
|
||||
|
|
@ -522,7 +603,10 @@
|
|||
"icon": "",
|
||||
"executables": {
|
||||
"windows": [
|
||||
"C:\\Program Files\\Nuke11.2v2\\Nuke11.2.exe"
|
||||
[
|
||||
"C:\\Program Files\\Nuke11.2v2\\Nuke11.2.exe",
|
||||
"--hiero"
|
||||
]
|
||||
],
|
||||
"darwin": [],
|
||||
"linux": []
|
||||
|
|
@ -539,7 +623,7 @@
|
|||
"enabled": true,
|
||||
"label": "BlackMagic Fusion",
|
||||
"icon": "{}/app_icons/fusion.png",
|
||||
"is_host": true,
|
||||
"host_name": "fusion",
|
||||
"environment": {
|
||||
"__environment_keys__": {
|
||||
"fusion": []
|
||||
|
|
@ -584,7 +668,7 @@
|
|||
"enabled": true,
|
||||
"label": "Blackmagic DaVinci Resolve",
|
||||
"icon": "{}/app_icons/resolve.png",
|
||||
"is_host": true,
|
||||
"host_name": "resolve",
|
||||
"environment": {
|
||||
"__environment_keys__": {
|
||||
"resolve": [
|
||||
|
|
@ -662,7 +746,7 @@
|
|||
"enabled": true,
|
||||
"label": "SideFX Houdini",
|
||||
"icon": "{}/app_icons/houdini.png",
|
||||
"is_host": true,
|
||||
"host_name": "houdini",
|
||||
"environment": {
|
||||
"__environment_keys__": {
|
||||
"houdini": [
|
||||
|
|
@ -720,7 +804,7 @@
|
|||
"enabled": true,
|
||||
"label": "Blender",
|
||||
"icon": "{}/app_icons/blender.png",
|
||||
"is_host": true,
|
||||
"host_name": "blender",
|
||||
"environment": {
|
||||
"__environment_keys__": {
|
||||
"blender": [
|
||||
|
|
@ -743,7 +827,12 @@
|
|||
"variant_label": "2.90",
|
||||
"icon": "",
|
||||
"executables": {
|
||||
"windows": [],
|
||||
"windows": [
|
||||
[
|
||||
"C:\\Program Files\\Blender Foundation\\Blender 2.90\\blender.exe",
|
||||
""
|
||||
]
|
||||
],
|
||||
"darwin": [],
|
||||
"linux": []
|
||||
},
|
||||
|
|
@ -759,7 +848,12 @@
|
|||
"variant_label": "2.83",
|
||||
"icon": "",
|
||||
"executables": {
|
||||
"windows": [],
|
||||
"windows": [
|
||||
[
|
||||
"C:\\Program Files\\Blender Foundation\\Blender 2.83\\blender.exe",
|
||||
""
|
||||
]
|
||||
],
|
||||
"darwin": [],
|
||||
"linux": []
|
||||
},
|
||||
|
|
@ -775,7 +869,7 @@
|
|||
"enabled": true,
|
||||
"label": "Toon Boom Harmony",
|
||||
"icon": "{}/app_icons/harmony.png",
|
||||
"is_host": true,
|
||||
"host_name": "harmony",
|
||||
"environment": {
|
||||
"__environment_keys__": {
|
||||
"harmony": [
|
||||
|
|
@ -843,7 +937,10 @@
|
|||
"executables": {
|
||||
"windows": [],
|
||||
"darwin": [
|
||||
"/Applications/Toon Boom Harmony 17 Premium/Harmony Premium.app/Contents/MacOS/Harmony Premium"
|
||||
[
|
||||
"/Applications/Toon Boom Harmony 17 Premium/Harmony Premium.app/Contents/MacOS/Harmony Premium",
|
||||
""
|
||||
]
|
||||
],
|
||||
"linux": []
|
||||
},
|
||||
|
|
@ -855,11 +952,69 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"tvpaint": {
|
||||
"enabled": true,
|
||||
"label": "TVPaint",
|
||||
"icon": "{}/app_icons/tvpaint.png",
|
||||
"host_name": "tvpaint",
|
||||
"environment": {
|
||||
"__environment_keys__": {
|
||||
"tvpaint": [
|
||||
"PYPE_LOG_NO_COLORS"
|
||||
]
|
||||
},
|
||||
"PYPE_LOG_NO_COLORS": "True"
|
||||
},
|
||||
"variants": {
|
||||
"tvpaint_Animation 11 (64bits)": {
|
||||
"enabled": true,
|
||||
"label": "",
|
||||
"variant_label": "Animation 11 (64bits)",
|
||||
"icon": "",
|
||||
"executables": {
|
||||
"windows": [
|
||||
[
|
||||
"C:\\Program Files\\TVPaint Developpement\\TVPaint Animation 11 (64bits)\\TVPaint Animation 11 (64bits).exe",
|
||||
""
|
||||
]
|
||||
],
|
||||
"darwin": [],
|
||||
"linux": []
|
||||
},
|
||||
"environment": {
|
||||
"__environment_keys__": {
|
||||
"tvpaint_Animation 11 (64bits)": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"tvpaint_Animation 11 (32bits)": {
|
||||
"enabled": true,
|
||||
"label": "",
|
||||
"variant_label": "Animation 11 (32bits)",
|
||||
"icon": "",
|
||||
"executables": {
|
||||
"windows": [
|
||||
[
|
||||
"C:\\Program Files (x86)\\TVPaint Developpement\\TVPaint Animation 11 (32bits)\\TVPaint Animation 11 (32bits).exe",
|
||||
""
|
||||
]
|
||||
],
|
||||
"darwin": [],
|
||||
"linux": []
|
||||
},
|
||||
"environment": {
|
||||
"__environment_keys__": {
|
||||
"tvpaint_Animation 11 (32bits)": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"photoshop": {
|
||||
"enabled": true,
|
||||
"label": "Adobe Photoshop",
|
||||
"icon": "{}/app_icons/photoshop.png",
|
||||
"is_host": true,
|
||||
"host_name": "photoshop",
|
||||
"environment": {
|
||||
"__environment_keys__": {
|
||||
"photoshop": [
|
||||
|
|
@ -883,7 +1038,12 @@
|
|||
"variant_label": "2020",
|
||||
"icon": "",
|
||||
"executables": {
|
||||
"windows": [],
|
||||
"windows": [
|
||||
[
|
||||
"C:\\Program Files\\Adobe\\Adobe Photoshop 2020\\Photoshop.exe",
|
||||
""
|
||||
]
|
||||
],
|
||||
"darwin": [],
|
||||
"linux": []
|
||||
},
|
||||
|
|
@ -892,6 +1052,93 @@
|
|||
"photoshop_2020": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"photoshop_2021": {
|
||||
"enabled": true,
|
||||
"label": "",
|
||||
"variant_label": "2021",
|
||||
"icon": "",
|
||||
"executables": {
|
||||
"windows": [
|
||||
[
|
||||
"C:\\Program Files\\Adobe\\Adobe Photoshop 2021\\Photoshop.exe",
|
||||
""
|
||||
]
|
||||
],
|
||||
"darwin": [],
|
||||
"linux": []
|
||||
},
|
||||
"environment": {
|
||||
"__environment_keys__": {
|
||||
"photoshop_2021": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"aftereffects": {
|
||||
"enabled": true,
|
||||
"label": "Adobe AfterEffects",
|
||||
"icon": "{}/app_icons/aftereffects.png",
|
||||
"host_name": "aftereffects",
|
||||
"environment": {
|
||||
"__environment_keys__": {
|
||||
"aftereffects": [
|
||||
"AVALON_AFTEREFFECTS_WORKFILES_ON_LAUNCH",
|
||||
"PYTHONPATH",
|
||||
"PYPE_LOG_NO_COLORS",
|
||||
"WEBSOCKET_URL",
|
||||
"WORKFILES_SAVE_AS"
|
||||
]
|
||||
},
|
||||
"AVALON_AFTEREFFECTS_WORKFILES_ON_LAUNCH": "1",
|
||||
"PYTHONPATH": "{PYTHONPATH}",
|
||||
"PYPE_LOG_NO_COLORS": "Yes",
|
||||
"WEBSOCKET_URL": "ws://localhost:8097/ws/",
|
||||
"WORKFILES_SAVE_AS": "Yes"
|
||||
},
|
||||
"variants": {
|
||||
"aftereffects_2020": {
|
||||
"enabled": true,
|
||||
"label": "",
|
||||
"variant_label": "2020",
|
||||
"icon": "",
|
||||
"executables": {
|
||||
"windows": [
|
||||
[
|
||||
"C:\\Program Files\\Adobe\\Adobe After Effects 2020\\Support Files\\AfterFX.exe",
|
||||
""
|
||||
]
|
||||
],
|
||||
"darwin": [],
|
||||
"linux": []
|
||||
},
|
||||
"environment": {
|
||||
"__environment_keys__": {
|
||||
"aftereffects_2020": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"aftereffects_2021": {
|
||||
"enabled": true,
|
||||
"label": "",
|
||||
"variant_label": "2021",
|
||||
"icon": "",
|
||||
"executables": {
|
||||
"windows": [
|
||||
[
|
||||
"C:\\Program Files\\Adobe\\Adobe After Effects 2021\\Support Files\\AfterFX.exe",
|
||||
""
|
||||
]
|
||||
],
|
||||
"darwin": [],
|
||||
"linux": []
|
||||
},
|
||||
"environment": {
|
||||
"__environment_keys__": {
|
||||
"aftereffects_2021": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -899,7 +1146,7 @@
|
|||
"enabled": true,
|
||||
"label": "CelAction 2D",
|
||||
"icon": "app_icons/celaction.png",
|
||||
"is_host": true,
|
||||
"host_name": "celaction",
|
||||
"environment": {
|
||||
"__environment_keys__": {
|
||||
"celaction": [
|
||||
|
|
@ -914,7 +1161,10 @@
|
|||
"label": "",
|
||||
"variant_label": "Local",
|
||||
"icon": "{}/app_icons/celaction_local.png",
|
||||
"executables": "",
|
||||
"executables": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"environment": {
|
||||
"__environment_keys__": {
|
||||
"celation_Local": []
|
||||
|
|
@ -926,7 +1176,10 @@
|
|||
"label": "",
|
||||
"variant_label": "Pulblish",
|
||||
"icon": "",
|
||||
"executables": "",
|
||||
"executables": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"environment": {
|
||||
"__environment_keys__": {
|
||||
"celation_Publish": []
|
||||
|
|
@ -939,7 +1192,7 @@
|
|||
"enabled": true,
|
||||
"label": "Unreal Editor",
|
||||
"icon": "{}/app_icons/ue4.png'",
|
||||
"is_host": true,
|
||||
"host_name": "unreal",
|
||||
"environment": {
|
||||
"__environment_keys__": {
|
||||
"unreal": [
|
||||
|
|
@ -1033,7 +1286,7 @@
|
|||
"enabled": true,
|
||||
"label": "DJV View",
|
||||
"icon": "{}/app_icons/djvView.png",
|
||||
"is_host": false,
|
||||
"host_name": "",
|
||||
"environment": {
|
||||
"__environment_keys__": {
|
||||
"djvview": []
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"type": "dict",
|
||||
"key": "aftereffects",
|
||||
"label": "Adobe AfterEffects",
|
||||
"collapsable": true,
|
||||
"checkbox_key": "enabled",
|
||||
"children": [{
|
||||
"type": "boolean",
|
||||
"key": "enabled",
|
||||
"label": "Enabled"
|
||||
},
|
||||
{
|
||||
"type": "schema_template",
|
||||
"name": "template_host_unchangables"
|
||||
},
|
||||
{
|
||||
"key": "environment",
|
||||
"label": "Environment",
|
||||
"type": "raw-json",
|
||||
"env_group_key": "aftereffects"
|
||||
},
|
||||
{
|
||||
"type": "dict-invisible",
|
||||
"key": "variants",
|
||||
"children": [{
|
||||
"type": "schema_template",
|
||||
"name": "template_host_variant",
|
||||
"template_data": [
|
||||
{
|
||||
"host_version": "2020",
|
||||
"host_name": "aftereffects"
|
||||
},
|
||||
{
|
||||
"host_version": "2021",
|
||||
"host_name": "aftereffects"
|
||||
}
|
||||
]
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -30,14 +30,6 @@
|
|||
"host_version": "20",
|
||||
"host_name": "harmony"
|
||||
},
|
||||
{
|
||||
"host_version": "19",
|
||||
"host_name": "harmony"
|
||||
},
|
||||
{
|
||||
"host_version": "18",
|
||||
"host_name": "harmony"
|
||||
},
|
||||
{
|
||||
"host_version": "17",
|
||||
"host_name": "harmony"
|
||||
|
|
|
|||
|
|
@ -29,6 +29,10 @@
|
|||
{
|
||||
"host_version": "2020",
|
||||
"host_name": "photoshop"
|
||||
},
|
||||
{
|
||||
"host_version": "2021",
|
||||
"host_name": "photoshop"
|
||||
}
|
||||
]
|
||||
}]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"type": "dict",
|
||||
"key": "tvpaint",
|
||||
"label": "TVPaint",
|
||||
"collapsable": true,
|
||||
"checkbox_key": "enabled",
|
||||
"children": [{
|
||||
"type": "boolean",
|
||||
"key": "enabled",
|
||||
"label": "Enabled"
|
||||
},
|
||||
{
|
||||
"type": "schema_template",
|
||||
"name": "template_host_unchangables"
|
||||
},
|
||||
{
|
||||
"key": "environment",
|
||||
"label": "Environment",
|
||||
"type": "raw-json",
|
||||
"env_group_key": "tvpaint"
|
||||
},
|
||||
{
|
||||
"type": "dict-invisible",
|
||||
"key": "variants",
|
||||
"children": [{
|
||||
"type": "schema_template",
|
||||
"name": "template_host_variant",
|
||||
"template_data": [
|
||||
{
|
||||
"host_version": "Animation 11 (64bits)",
|
||||
"host_name": "tvpaint"
|
||||
},
|
||||
{
|
||||
"host_version": "Animation 11 (32bits)",
|
||||
"host_name": "tvpaint"
|
||||
}
|
||||
]
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -13,8 +13,24 @@
|
|||
"roles": ["developer"]
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "is_host",
|
||||
"label": "Has host implementation",
|
||||
"type": "enum",
|
||||
"key": "host_name",
|
||||
"label": "Host implementation",
|
||||
"enum_items": [
|
||||
{"": "< without host >"},
|
||||
{"aftereffects": "aftereffects"},
|
||||
{"blender": "blender"},
|
||||
{"celaction": "celaction"},
|
||||
{"fusion": "fusion"},
|
||||
{"harmony": "harmony"},
|
||||
{"hiero": "hiero"},
|
||||
{"houdini": "houdini"},
|
||||
{"maya": "maya"},
|
||||
{"nuke": "nuke"},
|
||||
{"photoshop": "photoshop"},
|
||||
{"resolve": "resolve"},
|
||||
{"tvpaint": "tvpaint"},
|
||||
{"unreal": "unreal"}
|
||||
],
|
||||
"roles": ["developer"]
|
||||
}]
|
||||
|
|
|
|||
|
|
@ -43,7 +43,8 @@
|
|||
"key": "executables",
|
||||
"label": "Executables",
|
||||
"multiplatform": "{multiplatform}",
|
||||
"multipath": "{multipath_executables}"
|
||||
"multipath": "{multipath_executables}",
|
||||
"with_arguments": true
|
||||
},
|
||||
{
|
||||
"key": "environment",
|
||||
|
|
|
|||
|
|
@ -65,10 +65,18 @@
|
|||
"type": "schema",
|
||||
"name": "schema_harmony"
|
||||
},
|
||||
{
|
||||
"type": "schema",
|
||||
"name": "schema_tvpaint"
|
||||
},
|
||||
{
|
||||
"type": "schema",
|
||||
"name": "schema_photoshop"
|
||||
},
|
||||
{
|
||||
"type": "schema",
|
||||
"name": "schema_aftereffects"
|
||||
},
|
||||
{
|
||||
"type": "schema",
|
||||
"name": "schema_celaction"
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ from .widgets import (
|
|||
IconButton,
|
||||
ExpandingWidget,
|
||||
NumberSpinBox,
|
||||
PathInput,
|
||||
GridLabelWidget,
|
||||
ComboBox,
|
||||
NiceCheckbox
|
||||
|
|
@ -1084,7 +1083,7 @@ class TextWidget(QtWidgets.QWidget, InputObject):
|
|||
class PathInputWidget(QtWidgets.QWidget, InputObject):
|
||||
default_input_value = ""
|
||||
value_changed = QtCore.Signal(object)
|
||||
valid_value_types = (str, )
|
||||
valid_value_types = (str, list)
|
||||
|
||||
def __init__(
|
||||
self, schema_data, parent, as_widget=False, parent_widget=None
|
||||
|
|
@ -1095,6 +1094,8 @@ class PathInputWidget(QtWidgets.QWidget, InputObject):
|
|||
|
||||
self.initial_attributes(schema_data, parent, as_widget)
|
||||
|
||||
self.with_arguments = schema_data.get("with_arguments", False)
|
||||
|
||||
def create_ui(self, label_widget=None):
|
||||
layout = QtWidgets.QHBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
|
@ -1106,22 +1107,37 @@ class PathInputWidget(QtWidgets.QWidget, InputObject):
|
|||
layout.addWidget(label_widget, 0)
|
||||
self.label_widget = label_widget
|
||||
|
||||
self.input_field = PathInput(self)
|
||||
self.setFocusProxy(self.input_field)
|
||||
layout.addWidget(self.input_field, 1)
|
||||
self.input_field = QtWidgets.QLineEdit(self)
|
||||
self.args_input_field = None
|
||||
if self.with_arguments:
|
||||
self.input_field.setPlaceholderText("Executable path")
|
||||
self.args_input_field = QtWidgets.QLineEdit(self)
|
||||
self.args_input_field.setPlaceholderText("Arguments")
|
||||
|
||||
self.setFocusProxy(self.input_field)
|
||||
layout.addWidget(self.input_field, 8)
|
||||
self.input_field.textChanged.connect(self._on_value_change)
|
||||
|
||||
if self.args_input_field:
|
||||
layout.addWidget(self.args_input_field, 2)
|
||||
self.args_input_field.textChanged.connect(self._on_value_change)
|
||||
|
||||
def set_value(self, value):
|
||||
self.validate_value(value)
|
||||
self.input_field.setText(value)
|
||||
|
||||
def focusOutEvent(self, event):
|
||||
self.input_field.clear_end_path()
|
||||
super(PathInput, self).focusOutEvent(event)
|
||||
if not isinstance(value, list):
|
||||
self.input_field.setText(value)
|
||||
elif self.with_arguments:
|
||||
self.input_field.setText(value[0])
|
||||
self.args_input_field.setText(value[1])
|
||||
else:
|
||||
self.input_field.setText(value[0])
|
||||
|
||||
def item_value(self):
|
||||
return self.input_field.text()
|
||||
path_value = self.input_field.text()
|
||||
if self.with_arguments:
|
||||
return [path_value, self.args_input_field.text()]
|
||||
return path_value
|
||||
|
||||
|
||||
class EnumeratorWidget(QtWidgets.QWidget, InputObject):
|
||||
|
|
@ -3191,6 +3207,7 @@ class PathWidget(QtWidgets.QWidget, SettingObject):
|
|||
|
||||
self.multiplatform = schema_data.get("multiplatform", False)
|
||||
self.multipath = schema_data.get("multipath", False)
|
||||
self.with_arguments = schema_data.get("with_arguments", False)
|
||||
|
||||
self.input_field = None
|
||||
|
||||
|
|
@ -3230,8 +3247,11 @@ class PathWidget(QtWidgets.QWidget, SettingObject):
|
|||
|
||||
def create_ui_inputs(self):
|
||||
if not self.multiplatform and not self.multipath:
|
||||
input_data = {"key": self.key}
|
||||
path_input = PathInputWidget(input_data, self, as_widget=True)
|
||||
item_schema = {
|
||||
"key": self.key,
|
||||
"with_arguments": self.with_arguments
|
||||
}
|
||||
path_input = PathInputWidget(item_schema, self, as_widget=True)
|
||||
path_input.create_ui(label_widget=self.label_widget)
|
||||
|
||||
self.setFocusProxy(path_input)
|
||||
|
|
@ -3243,7 +3263,10 @@ class PathWidget(QtWidgets.QWidget, SettingObject):
|
|||
if not self.multiplatform:
|
||||
item_schema = {
|
||||
"key": self.key,
|
||||
"object_type": "path-input"
|
||||
"object_type": {
|
||||
"type": "path-input",
|
||||
"with_arguments": self.with_arguments
|
||||
}
|
||||
}
|
||||
input_widget = ListWidget(item_schema, self, as_widget=True)
|
||||
input_widget.create_ui(label_widget=self.label_widget)
|
||||
|
|
@ -3266,9 +3289,13 @@ class PathWidget(QtWidgets.QWidget, SettingObject):
|
|||
}
|
||||
if self.multipath:
|
||||
child_item["type"] = "list"
|
||||
child_item["object_type"] = "path-input"
|
||||
child_item["object_type"] = {
|
||||
"type": "path-input",
|
||||
"with_arguments": self.with_arguments
|
||||
}
|
||||
else:
|
||||
child_item["type"] = "path-input"
|
||||
child_item["with_arguments"] = self.with_arguments
|
||||
|
||||
item_schema["children"].append(child_item)
|
||||
|
||||
|
|
|
|||
|
|
@ -51,6 +51,11 @@ class ComboBox(QtWidgets.QComboBox):
|
|||
super(ComboBox, self).__init__(*args, **kwargs)
|
||||
|
||||
self.currentIndexChanged.connect(self._on_change)
|
||||
self.setFocusPolicy(QtCore.Qt.StrongFocus)
|
||||
|
||||
def wheelEvent(self, event):
|
||||
if self.hasFocus():
|
||||
return super(ComboBox, self).wheelEvent(event)
|
||||
|
||||
def _on_change(self, *args, **kwargs):
|
||||
self.value_changed.emit()
|
||||
|
|
@ -66,35 +71,6 @@ class ComboBox(QtWidgets.QComboBox):
|
|||
return self.itemData(self.currentIndex(), role=QtCore.Qt.UserRole)
|
||||
|
||||
|
||||
class PathInput(QtWidgets.QLineEdit):
|
||||
def clear_end_path(self):
|
||||
value = self.text().strip()
|
||||
if value.endswith("/"):
|
||||
while value and value[-1] == "/":
|
||||
value = value[:-1]
|
||||
self.setText(value)
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
# Always change backslash `\` for forwardslash `/`
|
||||
if event.key() == QtCore.Qt.Key_Backslash:
|
||||
event.accept()
|
||||
new_event = QtGui.QKeyEvent(
|
||||
event.type(),
|
||||
QtCore.Qt.Key_Slash,
|
||||
event.modifiers(),
|
||||
"/",
|
||||
event.isAutoRepeat(),
|
||||
event.count()
|
||||
)
|
||||
QtWidgets.QApplication.sendEvent(self, new_event)
|
||||
return
|
||||
super(PathInput, self).keyPressEvent(event)
|
||||
|
||||
def focusOutEvent(self, event):
|
||||
super(PathInput, self).focusOutEvent(event)
|
||||
self.clear_end_path()
|
||||
|
||||
|
||||
class ClickableWidget(QtWidgets.QWidget):
|
||||
clicked = QtCore.Signal()
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue