mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
[Automated] Merged develop into main
This commit is contained in:
commit
99fd1c2fe6
46 changed files with 1064 additions and 472 deletions
|
|
@ -0,0 +1,10 @@
|
|||
from .addon import (
|
||||
CELACTION_ROOT_DIR,
|
||||
CelactionAddon,
|
||||
)
|
||||
|
||||
|
||||
__all__ = (
|
||||
"CELACTION_ROOT_DIR",
|
||||
"CelactionAddon",
|
||||
)
|
||||
31
openpype/hosts/celaction/addon.py
Normal file
31
openpype/hosts/celaction/addon.py
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import os
|
||||
from openpype.modules import OpenPypeModule, IHostAddon
|
||||
|
||||
CELACTION_ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
class CelactionAddon(OpenPypeModule, IHostAddon):
|
||||
name = "celaction"
|
||||
host_name = "celaction"
|
||||
|
||||
def initialize(self, module_settings):
|
||||
self.enabled = True
|
||||
|
||||
def get_launch_hook_paths(self, app):
|
||||
if app.host_name != self.host_name:
|
||||
return []
|
||||
return [
|
||||
os.path.join(CELACTION_ROOT_DIR, "hooks")
|
||||
]
|
||||
|
||||
def add_implementation_envs(self, env, _app):
|
||||
# Set default values if are not already set via settings
|
||||
defaults = {
|
||||
"LOGLEVEL": "DEBUG"
|
||||
}
|
||||
for key, value in defaults.items():
|
||||
if not env.get(key):
|
||||
env[key] = value
|
||||
|
||||
def get_workfile_extensions(self):
|
||||
return [".scn"]
|
||||
|
|
@ -1 +0,0 @@
|
|||
kwargs = None
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
import os
|
||||
import sys
|
||||
import copy
|
||||
import argparse
|
||||
|
||||
import pyblish.api
|
||||
import pyblish.util
|
||||
|
||||
import openpype.hosts.celaction
|
||||
from openpype.lib import Logger
|
||||
from openpype.hosts.celaction import api as celaction
|
||||
from openpype.tools.utils import host_tools
|
||||
from openpype.pipeline import install_openpype_plugins
|
||||
|
||||
|
||||
log = Logger.get_logger("Celaction_cli_publisher")
|
||||
|
||||
publish_host = "celaction"
|
||||
|
||||
HOST_DIR = os.path.dirname(os.path.abspath(openpype.hosts.celaction.__file__))
|
||||
PLUGINS_DIR = os.path.join(HOST_DIR, "plugins")
|
||||
PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish")
|
||||
|
||||
|
||||
def cli():
|
||||
parser = argparse.ArgumentParser(prog="celaction_publish")
|
||||
|
||||
parser.add_argument("--currentFile",
|
||||
help="Pass file to Context as `currentFile`")
|
||||
|
||||
parser.add_argument("--chunk",
|
||||
help=("Render chanks on farm"))
|
||||
|
||||
parser.add_argument("--frameStart",
|
||||
help=("Start of frame range"))
|
||||
|
||||
parser.add_argument("--frameEnd",
|
||||
help=("End of frame range"))
|
||||
|
||||
parser.add_argument("--resolutionWidth",
|
||||
help=("Width of resolution"))
|
||||
|
||||
parser.add_argument("--resolutionHeight",
|
||||
help=("Height of resolution"))
|
||||
|
||||
celaction.kwargs = parser.parse_args(sys.argv[1:]).__dict__
|
||||
|
||||
|
||||
def _prepare_publish_environments():
|
||||
"""Prepares environments based on request data."""
|
||||
env = copy.deepcopy(os.environ)
|
||||
|
||||
project_name = os.getenv("AVALON_PROJECT")
|
||||
asset_name = os.getenv("AVALON_ASSET")
|
||||
|
||||
env["AVALON_PROJECT"] = project_name
|
||||
env["AVALON_ASSET"] = asset_name
|
||||
env["AVALON_TASK"] = os.getenv("AVALON_TASK")
|
||||
env["AVALON_WORKDIR"] = os.getenv("AVALON_WORKDIR")
|
||||
env["AVALON_APP"] = f"hosts.{publish_host}"
|
||||
env["AVALON_APP_NAME"] = "celaction/local"
|
||||
|
||||
env["PYBLISH_HOSTS"] = publish_host
|
||||
|
||||
os.environ.update(env)
|
||||
|
||||
|
||||
def main():
|
||||
# prepare all environments
|
||||
_prepare_publish_environments()
|
||||
|
||||
# Registers pype's Global pyblish plugins
|
||||
install_openpype_plugins()
|
||||
|
||||
if os.path.exists(PUBLISH_PATH):
|
||||
log.info(f"Registering path: {PUBLISH_PATH}")
|
||||
pyblish.api.register_plugin_path(PUBLISH_PATH)
|
||||
|
||||
pyblish.api.register_host(publish_host)
|
||||
|
||||
return host_tools.show_publish()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
cli()
|
||||
result = main()
|
||||
sys.exit(not bool(result))
|
||||
|
|
@ -1,122 +0,0 @@
|
|||
import os
|
||||
import shutil
|
||||
import winreg
|
||||
from openpype.lib import PreLaunchHook
|
||||
from openpype.hosts.celaction import api as celaction
|
||||
|
||||
|
||||
class CelactionPrelaunchHook(PreLaunchHook):
|
||||
"""
|
||||
Bootstrap celacion with pype
|
||||
"""
|
||||
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(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: this will need to be checked more thoroughly
|
||||
pype_exe = os.getenv("OPENPYPE_EXECUTABLE")
|
||||
|
||||
winreg.SetValueEx(hKey, "SubmitAppTitle", 0, winreg.REG_SZ, pype_exe)
|
||||
|
||||
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.dirname(
|
||||
os.path.abspath(celaction.__file__)
|
||||
))
|
||||
template_path = os.path.join(
|
||||
pype_celaction_dir,
|
||||
"resources",
|
||||
"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
|
||||
137
openpype/hosts/celaction/hooks/pre_celaction_setup.py
Normal file
137
openpype/hosts/celaction/hooks/pre_celaction_setup.py
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
import os
|
||||
import shutil
|
||||
import winreg
|
||||
import subprocess
|
||||
from openpype.lib import PreLaunchHook, get_openpype_execute_args
|
||||
from openpype.hosts.celaction import scripts
|
||||
|
||||
CELACTION_SCRIPTS_DIR = os.path.dirname(
|
||||
os.path.abspath(scripts.__file__)
|
||||
)
|
||||
|
||||
|
||||
class CelactionPrelaunchHook(PreLaunchHook):
|
||||
"""
|
||||
Bootstrap celacion with pype
|
||||
"""
|
||||
app_groups = ["celaction"]
|
||||
platforms = ["windows"]
|
||||
|
||||
def execute(self):
|
||||
asset_doc = self.data["asset_doc"]
|
||||
width = asset_doc["data"]["resolutionWidth"]
|
||||
height = asset_doc["data"]["resolutionHeight"]
|
||||
|
||||
# Add workfile path to launch arguments
|
||||
workfile_path = self.workfile_path()
|
||||
if workfile_path:
|
||||
self.launch_context.launch_args.append(workfile_path)
|
||||
|
||||
# setting output parameters
|
||||
path_user_settings = "\\".join([
|
||||
"Software", "CelAction", "CelAction2D", "User Settings"
|
||||
])
|
||||
winreg.CreateKey(winreg.HKEY_CURRENT_USER, path_user_settings)
|
||||
hKey = winreg.OpenKey(
|
||||
winreg.HKEY_CURRENT_USER, path_user_settings, 0,
|
||||
winreg.KEY_ALL_ACCESS
|
||||
)
|
||||
|
||||
path_to_cli = os.path.join(CELACTION_SCRIPTS_DIR, "publish_cli.py")
|
||||
subproces_args = get_openpype_execute_args("run", path_to_cli)
|
||||
openpype_executable = subproces_args.pop(0)
|
||||
|
||||
winreg.SetValueEx(
|
||||
hKey,
|
||||
"SubmitAppTitle",
|
||||
0,
|
||||
winreg.REG_SZ,
|
||||
openpype_executable
|
||||
)
|
||||
|
||||
parameters = subproces_args + [
|
||||
"--currentFile", "*SCENE*",
|
||||
"--chunk", "*CHUNK*",
|
||||
"--frameStart", "*START*",
|
||||
"--frameEnd", "*END*",
|
||||
"--resolutionWidth", "*X*",
|
||||
"--resolutionHeight", "*Y*"
|
||||
]
|
||||
|
||||
winreg.SetValueEx(
|
||||
hKey, "SubmitParametersTitle", 0, winreg.REG_SZ,
|
||||
subprocess.list2cmdline(parameters)
|
||||
)
|
||||
|
||||
# setting resolution parameters
|
||||
path_submit = "\\".join([
|
||||
path_user_settings, "Dialogs", "SubmitOutput"
|
||||
])
|
||||
winreg.CreateKey(winreg.HKEY_CURRENT_USER, path_submit)
|
||||
hKey = winreg.OpenKey(
|
||||
winreg.HKEY_CURRENT_USER, path_submit, 0,
|
||||
winreg.KEY_ALL_ACCESS
|
||||
)
|
||||
winreg.SetValueEx(hKey, "SaveScene", 0, winreg.REG_DWORD, 1)
|
||||
winreg.SetValueEx(hKey, "CustomX", 0, winreg.REG_DWORD, width)
|
||||
winreg.SetValueEx(hKey, "CustomY", 0, winreg.REG_DWORD, height)
|
||||
|
||||
# making sure message dialogs don't appear when overwriting
|
||||
path_overwrite_scene = "\\".join([
|
||||
path_user_settings, "Messages", "OverwriteScene"
|
||||
])
|
||||
winreg.CreateKey(winreg.HKEY_CURRENT_USER, path_overwrite_scene)
|
||||
hKey = winreg.OpenKey(
|
||||
winreg.HKEY_CURRENT_USER, path_overwrite_scene, 0,
|
||||
winreg.KEY_ALL_ACCESS
|
||||
)
|
||||
winreg.SetValueEx(hKey, "Result", 0, winreg.REG_DWORD, 6)
|
||||
winreg.SetValueEx(hKey, "Valid", 0, winreg.REG_DWORD, 1)
|
||||
|
||||
# set scane as not saved
|
||||
path_scene_saved = "\\".join([
|
||||
path_user_settings, "Messages", "SceneSaved"
|
||||
])
|
||||
winreg.CreateKey(winreg.HKEY_CURRENT_USER, path_scene_saved)
|
||||
hKey = winreg.OpenKey(
|
||||
winreg.HKEY_CURRENT_USER, path_scene_saved, 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
|
||||
openpype_celaction_dir = os.path.dirname(CELACTION_SCRIPTS_DIR)
|
||||
template_path = os.path.join(
|
||||
openpype_celaction_dir,
|
||||
"resources",
|
||||
"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
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
import pyblish.api
|
||||
from openpype.hosts.celaction import api as celaction
|
||||
import argparse
|
||||
import sys
|
||||
from pprint import pformat
|
||||
|
||||
|
||||
class CollectCelactionCliKwargs(pyblish.api.Collector):
|
||||
|
|
@ -9,15 +11,31 @@ class CollectCelactionCliKwargs(pyblish.api.Collector):
|
|||
order = pyblish.api.Collector.order - 0.1
|
||||
|
||||
def process(self, context):
|
||||
kwargs = celaction.kwargs.copy()
|
||||
parser = argparse.ArgumentParser(prog="celaction")
|
||||
parser.add_argument("--currentFile",
|
||||
help="Pass file to Context as `currentFile`")
|
||||
parser.add_argument("--chunk",
|
||||
help=("Render chanks on farm"))
|
||||
parser.add_argument("--frameStart",
|
||||
help=("Start of frame range"))
|
||||
parser.add_argument("--frameEnd",
|
||||
help=("End of frame range"))
|
||||
parser.add_argument("--resolutionWidth",
|
||||
help=("Width of resolution"))
|
||||
parser.add_argument("--resolutionHeight",
|
||||
help=("Height of resolution"))
|
||||
passing_kwargs = parser.parse_args(sys.argv[1:]).__dict__
|
||||
|
||||
self.log.info("Storing kwargs: %s" % kwargs)
|
||||
context.set_data("kwargs", kwargs)
|
||||
self.log.info("Storing kwargs ...")
|
||||
self.log.debug("_ passing_kwargs: {}".format(pformat(passing_kwargs)))
|
||||
|
||||
# set kwargs to context data
|
||||
context.set_data("passingKwargs", passing_kwargs)
|
||||
|
||||
# get kwargs onto context data as keys with values
|
||||
for k, v in kwargs.items():
|
||||
for k, v in passing_kwargs.items():
|
||||
self.log.info(f"Setting `{k}` to instance.data with value: `{v}`")
|
||||
if k in ["frameStart", "frameEnd"]:
|
||||
context.data[k] = kwargs[k] = int(v)
|
||||
context.data[k] = passing_kwargs[k] = int(v)
|
||||
else:
|
||||
context.data[k] = v
|
||||
|
|
|
|||
|
|
@ -36,7 +36,8 @@ class CollectCelactionInstances(pyblish.api.ContextPlugin):
|
|||
"version": version
|
||||
}
|
||||
|
||||
celaction_kwargs = context.data.get("kwargs", {})
|
||||
celaction_kwargs = context.data.get(
|
||||
"passingKwargs", {})
|
||||
|
||||
if celaction_kwargs:
|
||||
shared_instance_data.update(celaction_kwargs)
|
||||
|
|
@ -52,8 +53,8 @@ class CollectCelactionInstances(pyblish.api.ContextPlugin):
|
|||
"subset": subset,
|
||||
"label": scene_file,
|
||||
"family": family,
|
||||
"families": [family, "ftrack"],
|
||||
"representations": list()
|
||||
"families": [],
|
||||
"representations": []
|
||||
})
|
||||
|
||||
# adding basic script data
|
||||
|
|
@ -72,7 +73,6 @@ class CollectCelactionInstances(pyblish.api.ContextPlugin):
|
|||
self.log.info('Publishing Celaction workfile')
|
||||
|
||||
# render instance
|
||||
family = "render.farm"
|
||||
subset = f"render{task}Main"
|
||||
instance = context.create_instance(name=subset)
|
||||
# getting instance state
|
||||
|
|
@ -81,8 +81,8 @@ class CollectCelactionInstances(pyblish.api.ContextPlugin):
|
|||
# add assetEntity data into instance
|
||||
instance.data.update({
|
||||
"label": "{} - farm".format(subset),
|
||||
"family": family,
|
||||
"families": [family],
|
||||
"family": "render.farm",
|
||||
"families": [],
|
||||
"subset": subset
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -11,28 +11,31 @@ class CollectRenderPath(pyblish.api.InstancePlugin):
|
|||
families = ["render.farm"]
|
||||
|
||||
# Presets
|
||||
anatomy_render_key = None
|
||||
publish_render_metadata = None
|
||||
output_extension = "png"
|
||||
anatomy_template_key_render_files = None
|
||||
anatomy_template_key_metadata = None
|
||||
|
||||
def process(self, instance):
|
||||
anatomy = instance.context.data["anatomy"]
|
||||
anatomy_data = copy.deepcopy(instance.data["anatomyData"])
|
||||
anatomy_data["family"] = "render"
|
||||
padding = anatomy.templates.get("frame_padding", 4)
|
||||
anatomy_data.update({
|
||||
"frame": f"%0{padding}d",
|
||||
"representation": "png"
|
||||
"family": "render",
|
||||
"representation": self.output_extension,
|
||||
"ext": self.output_extension
|
||||
})
|
||||
|
||||
anatomy_filled = anatomy.format(anatomy_data)
|
||||
|
||||
# get anatomy rendering keys
|
||||
anatomy_render_key = self.anatomy_render_key or "render"
|
||||
publish_render_metadata = self.publish_render_metadata or "render"
|
||||
r_anatomy_key = self.anatomy_template_key_render_files
|
||||
m_anatomy_key = self.anatomy_template_key_metadata
|
||||
|
||||
# get folder and path for rendering images from celaction
|
||||
render_dir = anatomy_filled[anatomy_render_key]["folder"]
|
||||
render_path = anatomy_filled[anatomy_render_key]["path"]
|
||||
render_dir = anatomy_filled[r_anatomy_key]["folder"]
|
||||
render_path = anatomy_filled[r_anatomy_key]["path"]
|
||||
self.log.debug("__ render_path: `{}`".format(render_path))
|
||||
|
||||
# create dir if it doesnt exists
|
||||
try:
|
||||
|
|
@ -46,9 +49,9 @@ class CollectRenderPath(pyblish.api.InstancePlugin):
|
|||
instance.data["path"] = render_path
|
||||
|
||||
# get anatomy for published renders folder path
|
||||
if anatomy_filled.get(publish_render_metadata):
|
||||
if anatomy_filled.get(m_anatomy_key):
|
||||
instance.data["publishRenderMetadataFolder"] = anatomy_filled[
|
||||
publish_render_metadata]["folder"]
|
||||
m_anatomy_key]["folder"]
|
||||
self.log.info("Metadata render path: `{}`".format(
|
||||
instance.data["publishRenderMetadataFolder"]
|
||||
))
|
||||
|
|
|
|||
0
openpype/hosts/celaction/scripts/__init__.py
Normal file
0
openpype/hosts/celaction/scripts/__init__.py
Normal file
37
openpype/hosts/celaction/scripts/publish_cli.py
Normal file
37
openpype/hosts/celaction/scripts/publish_cli.py
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import os
|
||||
import sys
|
||||
|
||||
import pyblish.api
|
||||
import pyblish.util
|
||||
|
||||
import openpype.hosts.celaction
|
||||
from openpype.lib import Logger
|
||||
from openpype.tools.utils import host_tools
|
||||
from openpype.pipeline import install_openpype_plugins
|
||||
|
||||
|
||||
log = Logger.get_logger("celaction")
|
||||
|
||||
PUBLISH_HOST = "celaction"
|
||||
HOST_DIR = os.path.dirname(os.path.abspath(openpype.hosts.celaction.__file__))
|
||||
PLUGINS_DIR = os.path.join(HOST_DIR, "plugins")
|
||||
PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish")
|
||||
|
||||
|
||||
def main():
|
||||
# Registers pype's Global pyblish plugins
|
||||
install_openpype_plugins()
|
||||
|
||||
if os.path.exists(PUBLISH_PATH):
|
||||
log.info(f"Registering path: {PUBLISH_PATH}")
|
||||
pyblish.api.register_plugin_path(PUBLISH_PATH)
|
||||
|
||||
pyblish.api.register_host(PUBLISH_HOST)
|
||||
pyblish.api.register_target("local")
|
||||
|
||||
return host_tools.show_publish()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
result = main()
|
||||
sys.exit(not bool(result))
|
||||
|
|
@ -596,18 +596,28 @@ class PublishableClip:
|
|||
if not hero_track and self.vertical_sync:
|
||||
# driving layer is set as negative match
|
||||
for (_in, _out), hero_data in self.vertical_clip_match.items():
|
||||
hero_data.update({"heroTrack": False})
|
||||
if _in == self.clip_in and _out == self.clip_out:
|
||||
"""
|
||||
Since only one instance of hero clip is expected in
|
||||
`self.vertical_clip_match`, this will loop only once
|
||||
until none hero clip will be matched with hero clip.
|
||||
|
||||
`tag_hierarchy_data` will be set only once for every
|
||||
clip which is not hero clip.
|
||||
"""
|
||||
_hero_data = deepcopy(hero_data)
|
||||
_hero_data.update({"heroTrack": False})
|
||||
if _in <= self.clip_in and _out >= self.clip_out:
|
||||
data_subset = hero_data["subset"]
|
||||
# add track index in case duplicity of names in hero data
|
||||
if self.subset in data_subset:
|
||||
hero_data["subset"] = self.subset + str(
|
||||
_hero_data["subset"] = self.subset + str(
|
||||
self.track_index)
|
||||
# in case track name and subset name is the same then add
|
||||
if self.subset_name == self.track_name:
|
||||
hero_data["subset"] = self.subset
|
||||
_hero_data["subset"] = self.subset
|
||||
# assing data to return hierarchy data to tag
|
||||
tag_hierarchy_data = hero_data
|
||||
tag_hierarchy_data = _hero_data
|
||||
break
|
||||
|
||||
# add data to return data dict
|
||||
self.marker_data.update(tag_hierarchy_data)
|
||||
|
|
|
|||
|
|
@ -27,7 +27,12 @@ class HieroAddon(OpenPypeModule, IHostAddon):
|
|||
new_hiero_paths.append(norm_path)
|
||||
|
||||
env["HIERO_PLUGIN_PATH"] = os.pathsep.join(new_hiero_paths)
|
||||
# Remove auto screen scale factor for Qt
|
||||
# - let Hiero decide it's value
|
||||
env.pop("QT_AUTO_SCREEN_SCALE_FACTOR", None)
|
||||
# Remove tkinter library paths if are set
|
||||
env.pop("TK_LIBRARY", None)
|
||||
env.pop("TCL_LIBRARY", None)
|
||||
|
||||
# Add vendor to PYTHONPATH
|
||||
python_path = env["PYTHONPATH"]
|
||||
|
|
|
|||
|
|
@ -27,7 +27,12 @@ class NukeAddon(OpenPypeModule, IHostAddon):
|
|||
new_nuke_paths.append(norm_path)
|
||||
|
||||
env["NUKE_PATH"] = os.pathsep.join(new_nuke_paths)
|
||||
# Remove auto screen scale factor for Qt
|
||||
# - let Nuke decide it's value
|
||||
env.pop("QT_AUTO_SCREEN_SCALE_FACTOR", None)
|
||||
# Remove tkinter library paths if are set
|
||||
env.pop("TK_LIBRARY", None)
|
||||
env.pop("TCL_LIBRARY", None)
|
||||
|
||||
# Add vendor to PYTHONPATH
|
||||
python_path = env["PYTHONPATH"]
|
||||
|
|
|
|||
|
|
@ -2961,7 +2961,7 @@ def get_viewer_config_from_string(input_string):
|
|||
viewer = split[1]
|
||||
display = split[0]
|
||||
elif "(" in viewer:
|
||||
pattern = r"([\w\d\s]+).*[(](.*)[)]"
|
||||
pattern = r"([\w\d\s\.\-]+).*[(](.*)[)]"
|
||||
result = re.findall(pattern, viewer)
|
||||
try:
|
||||
result = result.pop()
|
||||
|
|
|
|||
|
|
@ -298,7 +298,7 @@ class ExtractSlateFrame(publish.Extractor):
|
|||
|
||||
def add_comment_slate_node(self, instance, node):
|
||||
|
||||
comment = instance.context.data.get("comment")
|
||||
comment = instance.data["comment"]
|
||||
intent = instance.context.data.get("intent")
|
||||
if not isinstance(intent, dict):
|
||||
intent = {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import os
|
||||
import pyblish.api
|
||||
|
||||
from openpype.settings import get_project_settings
|
||||
from openpype.pipeline.publish import (
|
||||
ValidateContentsOrder,
|
||||
PublishXmlValidationError,
|
||||
|
|
@ -18,23 +20,38 @@ class ValidateTextureBatchWorkfiles(pyblish.api.InstancePlugin):
|
|||
families = ["texture_batch_workfile"]
|
||||
optional = True
|
||||
|
||||
# from presets
|
||||
main_workfile_extensions = ['mra']
|
||||
|
||||
def process(self, instance):
|
||||
if instance.data["family"] == "workfile":
|
||||
ext = instance.data["representations"][0]["ext"]
|
||||
if ext not in self.main_workfile_extensions:
|
||||
main_workfile_extensions = self.get_main_workfile_extensions()
|
||||
if ext not in main_workfile_extensions:
|
||||
self.log.warning("Only secondary workfile present!")
|
||||
return
|
||||
|
||||
if not instance.data.get("resources"):
|
||||
msg = "No secondary workfile present for workfile '{}'". \
|
||||
format(instance.data["name"])
|
||||
ext = self.main_workfile_extensions[0]
|
||||
ext = main_workfile_extensions[0]
|
||||
formatting_data = {"file_name": instance.data["name"],
|
||||
"extension": ext}
|
||||
|
||||
raise PublishXmlValidationError(self, msg,
|
||||
formatting_data=formatting_data
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_main_workfile_extensions():
|
||||
project_settings = get_project_settings(os.environ["AVALON_PROJECT"])
|
||||
|
||||
try:
|
||||
extensions = (project_settings["standalonepublisher"]
|
||||
["publish"]
|
||||
["CollectTextures"]
|
||||
["main_workfile_extensions"])
|
||||
except KeyError:
|
||||
raise Exception("Setting 'Main workfile extensions' not found."
|
||||
" The setting must be set for the"
|
||||
" 'Collect Texture' publish plugin of the"
|
||||
" 'Standalone Publish' tool.")
|
||||
|
||||
return extensions
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@ else:
|
|||
|
||||
|
||||
class FileTransaction(object):
|
||||
"""
|
||||
"""File transaction with rollback options.
|
||||
|
||||
The file transaction is a three step process.
|
||||
The file transaction is a three-step process.
|
||||
|
||||
1) Rename any existing files to a "temporary backup" during `process()`
|
||||
2) Copy the files to final destination during `process()`
|
||||
|
|
@ -39,14 +39,12 @@ class FileTransaction(object):
|
|||
|
||||
Warning:
|
||||
Any folders created during the transfer will not be removed.
|
||||
|
||||
"""
|
||||
|
||||
MODE_COPY = 0
|
||||
MODE_HARDLINK = 1
|
||||
|
||||
def __init__(self, log=None):
|
||||
|
||||
if log is None:
|
||||
log = logging.getLogger("FileTransaction")
|
||||
|
||||
|
|
@ -63,49 +61,64 @@ class FileTransaction(object):
|
|||
self._backup_to_original = {}
|
||||
|
||||
def add(self, src, dst, mode=MODE_COPY):
|
||||
"""Add a new file to transfer queue"""
|
||||
"""Add a new file to transfer queue.
|
||||
|
||||
Args:
|
||||
src (str): Source path.
|
||||
dst (str): Destination path.
|
||||
mode (MODE_COPY, MODE_HARDLINK): Transfer mode.
|
||||
"""
|
||||
|
||||
opts = {"mode": mode}
|
||||
|
||||
src = os.path.abspath(src)
|
||||
dst = os.path.abspath(dst)
|
||||
src = os.path.normpath(os.path.abspath(src))
|
||||
dst = os.path.normpath(os.path.abspath(dst))
|
||||
|
||||
if dst in self._transfers:
|
||||
queued_src = self._transfers[dst][0]
|
||||
if src == queued_src:
|
||||
self.log.debug("File transfer was already "
|
||||
"in queue: {} -> {}".format(src, dst))
|
||||
self.log.debug(
|
||||
"File transfer was already in queue: {} -> {}".format(
|
||||
src, dst))
|
||||
return
|
||||
else:
|
||||
self.log.warning("File transfer in queue replaced..")
|
||||
self.log.debug("Removed from queue: "
|
||||
"{} -> {}".format(queued_src, dst))
|
||||
self.log.debug("Added to queue: {} -> {}".format(src, dst))
|
||||
self.log.debug(
|
||||
"Removed from queue: {} -> {} replaced by {} -> {}".format(
|
||||
queued_src, dst, src, dst))
|
||||
|
||||
self._transfers[dst] = (src, opts)
|
||||
|
||||
def process(self):
|
||||
|
||||
# Backup any existing files
|
||||
for dst in self._transfers.keys():
|
||||
if os.path.exists(dst):
|
||||
# Backup original file
|
||||
# todo: add timestamp or uuid to ensure unique
|
||||
backup = dst + ".bak"
|
||||
self._backup_to_original[backup] = dst
|
||||
self.log.debug("Backup existing file: "
|
||||
"{} -> {}".format(dst, backup))
|
||||
os.rename(dst, backup)
|
||||
for dst, (src, _) in self._transfers.items():
|
||||
if dst == src or not os.path.exists(dst):
|
||||
continue
|
||||
|
||||
# Backup original file
|
||||
# todo: add timestamp or uuid to ensure unique
|
||||
backup = dst + ".bak"
|
||||
self._backup_to_original[backup] = dst
|
||||
self.log.debug(
|
||||
"Backup existing file: {} -> {}".format(dst, backup))
|
||||
os.rename(dst, backup)
|
||||
|
||||
# Copy the files to transfer
|
||||
for dst, (src, opts) in self._transfers.items():
|
||||
if dst == src:
|
||||
self.log.debug(
|
||||
"Source and destionation are same files {} -> {}".format(
|
||||
src, dst))
|
||||
continue
|
||||
|
||||
self._create_folder_for_file(dst)
|
||||
|
||||
if opts["mode"] == self.MODE_COPY:
|
||||
self.log.debug("Copying file ... {} -> {}".format(src, dst))
|
||||
copyfile(src, dst)
|
||||
elif opts["mode"] == self.MODE_HARDLINK:
|
||||
self.log.debug("Hardlinking file ... {} -> {}".format(src,
|
||||
dst))
|
||||
self.log.debug("Hardlinking file ... {} -> {}".format(
|
||||
src, dst))
|
||||
create_hard_link(src, dst)
|
||||
|
||||
self._transferred.append(dst)
|
||||
|
|
@ -116,23 +129,21 @@ class FileTransaction(object):
|
|||
try:
|
||||
os.remove(backup)
|
||||
except OSError:
|
||||
self.log.error("Failed to remove backup file: "
|
||||
"{}".format(backup),
|
||||
exc_info=True)
|
||||
self.log.error(
|
||||
"Failed to remove backup file: {}".format(backup),
|
||||
exc_info=True)
|
||||
|
||||
def rollback(self):
|
||||
|
||||
errors = 0
|
||||
|
||||
# Rollback any transferred files
|
||||
for path in self._transferred:
|
||||
try:
|
||||
os.remove(path)
|
||||
except OSError:
|
||||
errors += 1
|
||||
self.log.error("Failed to rollback created file: "
|
||||
"{}".format(path),
|
||||
exc_info=True)
|
||||
self.log.error(
|
||||
"Failed to rollback created file: {}".format(path),
|
||||
exc_info=True)
|
||||
|
||||
# Rollback the backups
|
||||
for backup, original in self._backup_to_original.items():
|
||||
|
|
@ -140,13 +151,15 @@ class FileTransaction(object):
|
|||
os.rename(backup, original)
|
||||
except OSError:
|
||||
errors += 1
|
||||
self.log.error("Failed to restore original file: "
|
||||
"{} -> {}".format(backup, original),
|
||||
exc_info=True)
|
||||
self.log.error(
|
||||
"Failed to restore original file: {} -> {}".format(
|
||||
backup, original),
|
||||
exc_info=True)
|
||||
|
||||
if errors:
|
||||
self.log.error("{} errors occurred during "
|
||||
"rollback.".format(errors), exc_info=True)
|
||||
self.log.error(
|
||||
"{} errors occurred during rollback.".format(errors),
|
||||
exc_info=True)
|
||||
six.reraise(*sys.exc_info())
|
||||
|
||||
@property
|
||||
|
|
|
|||
|
|
@ -77,26 +77,38 @@ def get_transcode_temp_directory():
|
|||
)
|
||||
|
||||
|
||||
def get_oiio_info_for_input(filepath, logger=None):
|
||||
def get_oiio_info_for_input(filepath, logger=None, subimages=False):
|
||||
"""Call oiiotool to get information about input and return stdout.
|
||||
|
||||
Stdout should contain xml format string.
|
||||
"""
|
||||
args = [
|
||||
get_oiio_tools_path(), "--info", "-v", "-i:infoformat=xml", filepath
|
||||
get_oiio_tools_path(),
|
||||
"--info",
|
||||
"-v"
|
||||
]
|
||||
if subimages:
|
||||
args.append("-a")
|
||||
|
||||
args.extend(["-i:infoformat=xml", filepath])
|
||||
|
||||
output = run_subprocess(args, logger=logger)
|
||||
output = output.replace("\r\n", "\n")
|
||||
|
||||
xml_started = False
|
||||
subimages_lines = []
|
||||
lines = []
|
||||
for line in output.split("\n"):
|
||||
if not xml_started:
|
||||
if not line.startswith("<"):
|
||||
continue
|
||||
xml_started = True
|
||||
|
||||
if xml_started:
|
||||
lines.append(line)
|
||||
if line == "</ImageSpec>":
|
||||
subimages_lines.append(lines)
|
||||
lines = []
|
||||
|
||||
if not xml_started:
|
||||
raise ValueError(
|
||||
|
|
@ -105,12 +117,19 @@ def get_oiio_info_for_input(filepath, logger=None):
|
|||
)
|
||||
)
|
||||
|
||||
xml_text = "\n".join(lines)
|
||||
return parse_oiio_xml_output(xml_text, logger=logger)
|
||||
output = []
|
||||
for subimage_lines in subimages_lines:
|
||||
xml_text = "\n".join(subimage_lines)
|
||||
output.append(parse_oiio_xml_output(xml_text, logger=logger))
|
||||
|
||||
if subimages:
|
||||
return output
|
||||
return output[0]
|
||||
|
||||
|
||||
class RationalToInt:
|
||||
"""Rational value stored as division of 2 integers using string."""
|
||||
|
||||
def __init__(self, string_value):
|
||||
parts = string_value.split("/")
|
||||
top = float(parts[0])
|
||||
|
|
@ -157,16 +176,16 @@ def convert_value_by_type_name(value_type, value, logger=None):
|
|||
if value_type == "int":
|
||||
return int(value)
|
||||
|
||||
if value_type == "float":
|
||||
if value_type in ("float", "double"):
|
||||
return float(value)
|
||||
|
||||
# Vectors will probably have more types
|
||||
if value_type in ("vec2f", "float2"):
|
||||
if value_type in ("vec2f", "float2", "float2d"):
|
||||
return [float(item) for item in value.split(",")]
|
||||
|
||||
# Matrix should be always have square size of element 3x3, 4x4
|
||||
# - are returned as list of lists
|
||||
if value_type == "matrix":
|
||||
if value_type in ("matrix", "matrixd"):
|
||||
output = []
|
||||
current_index = -1
|
||||
parts = value.split(",")
|
||||
|
|
@ -198,7 +217,7 @@ def convert_value_by_type_name(value_type, value, logger=None):
|
|||
if value_type == "rational2i":
|
||||
return RationalToInt(value)
|
||||
|
||||
if value_type == "vector":
|
||||
if value_type in ("vector", "vectord"):
|
||||
parts = [part.strip() for part in value.split(",")]
|
||||
output = []
|
||||
for part in parts:
|
||||
|
|
@ -380,6 +399,10 @@ def should_convert_for_ffmpeg(src_filepath):
|
|||
if not input_info:
|
||||
return None
|
||||
|
||||
subimages = input_info.get("subimages")
|
||||
if subimages is not None and subimages > 1:
|
||||
return True
|
||||
|
||||
# Check compression
|
||||
compression = input_info["attribs"].get("compression")
|
||||
if compression in ("dwaa", "dwab"):
|
||||
|
|
@ -453,7 +476,7 @@ def convert_for_ffmpeg(
|
|||
if input_frame_start is not None and input_frame_end is not None:
|
||||
is_sequence = int(input_frame_end) != int(input_frame_start)
|
||||
|
||||
input_info = get_oiio_info_for_input(first_input_path)
|
||||
input_info = get_oiio_info_for_input(first_input_path, logger=logger)
|
||||
|
||||
# Change compression only if source compression is "dwaa" or "dwab"
|
||||
# - they're not supported in ffmpeg
|
||||
|
|
@ -488,13 +511,21 @@ def convert_for_ffmpeg(
|
|||
input_channels.append(alpha)
|
||||
input_channels_str = ",".join(input_channels)
|
||||
|
||||
oiio_cmd.extend([
|
||||
subimages = input_info.get("subimages")
|
||||
input_arg = "-i"
|
||||
if subimages is None or subimages == 1:
|
||||
# Tell oiiotool which channels should be loaded
|
||||
# - other channels are not loaded to memory so helps to avoid memory
|
||||
# leak issues
|
||||
"-i:ch={}".format(input_channels_str), first_input_path,
|
||||
# - this option is crashing if used on multipart/subimages exrs
|
||||
input_arg += ":ch={}".format(input_channels_str)
|
||||
|
||||
oiio_cmd.extend([
|
||||
input_arg, first_input_path,
|
||||
# Tell oiiotool which channels should be put to top stack (and output)
|
||||
"--ch", channels_arg
|
||||
"--ch", channels_arg,
|
||||
# Use first subimage
|
||||
"--subimage", "0"
|
||||
])
|
||||
|
||||
# Add frame definitions to arguments
|
||||
|
|
@ -588,7 +619,7 @@ def convert_input_paths_for_ffmpeg(
|
|||
" \".exr\" extension. Got \"{}\"."
|
||||
).format(ext))
|
||||
|
||||
input_info = get_oiio_info_for_input(first_input_path)
|
||||
input_info = get_oiio_info_for_input(first_input_path, logger=logger)
|
||||
|
||||
# Change compression only if source compression is "dwaa" or "dwab"
|
||||
# - they're not supported in ffmpeg
|
||||
|
|
@ -606,12 +637,22 @@ def convert_input_paths_for_ffmpeg(
|
|||
|
||||
red, green, blue, alpha = review_channels
|
||||
input_channels = [red, green, blue]
|
||||
# TODO find subimage inder where rgba is available for multipart exrs
|
||||
channels_arg = "R={},G={},B={}".format(red, green, blue)
|
||||
if alpha is not None:
|
||||
channels_arg += ",A={}".format(alpha)
|
||||
input_channels.append(alpha)
|
||||
input_channels_str = ",".join(input_channels)
|
||||
|
||||
subimages = input_info.get("subimages")
|
||||
input_arg = "-i"
|
||||
if subimages is None or subimages == 1:
|
||||
# Tell oiiotool which channels should be loaded
|
||||
# - other channels are not loaded to memory so helps to avoid memory
|
||||
# leak issues
|
||||
# - this option is crashing if used on multipart exrs
|
||||
input_arg += ":ch={}".format(input_channels_str)
|
||||
|
||||
for input_path in input_paths:
|
||||
# Prepare subprocess arguments
|
||||
oiio_cmd = [
|
||||
|
|
@ -625,13 +666,12 @@ def convert_input_paths_for_ffmpeg(
|
|||
oiio_cmd.extend(["--compression", compression])
|
||||
|
||||
oiio_cmd.extend([
|
||||
# Tell oiiotool which channels should be loaded
|
||||
# - other channels are not loaded to memory so helps to
|
||||
# avoid memory leak issues
|
||||
"-i:ch={}".format(input_channels_str), input_path,
|
||||
input_arg, input_path,
|
||||
# Tell oiiotool which channels should be put to top stack
|
||||
# (and output)
|
||||
"--ch", channels_arg
|
||||
"--ch", channels_arg,
|
||||
# Use first subimage
|
||||
"--subimage", "0"
|
||||
])
|
||||
|
||||
for attr_name, attr_value in input_info["attribs"].items():
|
||||
|
|
|
|||
|
|
@ -2,16 +2,14 @@ import os
|
|||
import re
|
||||
import json
|
||||
import getpass
|
||||
|
||||
import requests
|
||||
import pyblish.api
|
||||
|
||||
|
||||
class ExtractCelactionDeadline(pyblish.api.InstancePlugin):
|
||||
class CelactionSubmitDeadline(pyblish.api.InstancePlugin):
|
||||
"""Submit CelAction2D scene to Deadline
|
||||
|
||||
Renders are submitted to a Deadline Web Service as
|
||||
supplied via settings key "DEADLINE_REST_URL".
|
||||
Renders are submitted to a Deadline Web Service.
|
||||
|
||||
"""
|
||||
|
||||
|
|
@ -26,27 +24,21 @@ class ExtractCelactionDeadline(pyblish.api.InstancePlugin):
|
|||
deadline_pool_secondary = ""
|
||||
deadline_group = ""
|
||||
deadline_chunk_size = 1
|
||||
|
||||
enviro_filter = [
|
||||
"FTRACK_API_USER",
|
||||
"FTRACK_API_KEY",
|
||||
"FTRACK_SERVER"
|
||||
]
|
||||
deadline_job_delay = "00:00:08:00"
|
||||
|
||||
def process(self, instance):
|
||||
instance.data["toBeRenderedOn"] = "deadline"
|
||||
context = instance.context
|
||||
|
||||
deadline_url = (
|
||||
context.data["system_settings"]
|
||||
["modules"]
|
||||
["deadline"]
|
||||
["DEADLINE_REST_URL"]
|
||||
)
|
||||
assert deadline_url, "Requires DEADLINE_REST_URL"
|
||||
# get default deadline webservice url from deadline module
|
||||
deadline_url = instance.context.data["defaultDeadline"]
|
||||
# if custom one is set in instance, use that
|
||||
if instance.data.get("deadlineUrl"):
|
||||
deadline_url = instance.data.get("deadlineUrl")
|
||||
assert deadline_url, "Requires Deadline Webservice URL"
|
||||
|
||||
self.deadline_url = "{}/api/jobs".format(deadline_url)
|
||||
self._comment = context.data.get("comment", "")
|
||||
self._comment = instance.data["comment"]
|
||||
self._deadline_user = context.data.get(
|
||||
"deadlineUser", getpass.getuser())
|
||||
self._frame_start = int(instance.data["frameStart"])
|
||||
|
|
@ -82,6 +74,26 @@ class ExtractCelactionDeadline(pyblish.api.InstancePlugin):
|
|||
render_dir = os.path.normpath(os.path.dirname(render_path))
|
||||
render_path = os.path.normpath(render_path)
|
||||
script_name = os.path.basename(script_path)
|
||||
|
||||
for item in instance.context:
|
||||
if "workfile" in item.data["family"]:
|
||||
msg = "Workfile (scene) must be published along"
|
||||
assert item.data["publish"] is True, msg
|
||||
|
||||
template_data = item.data.get("anatomyData")
|
||||
rep = item.data.get("representations")[0].get("name")
|
||||
template_data["representation"] = rep
|
||||
template_data["ext"] = rep
|
||||
template_data["comment"] = None
|
||||
anatomy_filled = instance.context.data["anatomy"].format(
|
||||
template_data)
|
||||
template_filled = anatomy_filled["publish"]["path"]
|
||||
script_path = os.path.normpath(template_filled)
|
||||
|
||||
self.log.info(
|
||||
"Using published scene for render {}".format(script_path)
|
||||
)
|
||||
|
||||
jobname = "%s - %s" % (script_name, instance.name)
|
||||
|
||||
output_filename_0 = self.preview_fname(render_path)
|
||||
|
|
@ -98,7 +110,7 @@ class ExtractCelactionDeadline(pyblish.api.InstancePlugin):
|
|||
chunk_size = self.deadline_chunk_size
|
||||
|
||||
# search for %02d pattern in name, and padding number
|
||||
search_results = re.search(r"(.%0)(\d)(d)[._]", render_path).groups()
|
||||
search_results = re.search(r"(%0)(\d)(d)[._]", render_path).groups()
|
||||
split_patern = "".join(search_results)
|
||||
padding_number = int(search_results[1])
|
||||
|
||||
|
|
@ -145,10 +157,11 @@ class ExtractCelactionDeadline(pyblish.api.InstancePlugin):
|
|||
# frames from Deadline Monitor
|
||||
"OutputFilename0": output_filename_0.replace("\\", "/"),
|
||||
|
||||
# # Asset dependency to wait for at least the scene file to sync.
|
||||
# # Asset dependency to wait for at least
|
||||
# the scene file to sync.
|
||||
# "AssetDependency0": script_path
|
||||
"ScheduledType": "Once",
|
||||
"JobDelay": "00:00:08:00"
|
||||
"JobDelay": self.deadline_job_delay
|
||||
},
|
||||
"PluginInfo": {
|
||||
# Input
|
||||
|
|
@ -173,19 +186,6 @@ class ExtractCelactionDeadline(pyblish.api.InstancePlugin):
|
|||
plugin = payload["JobInfo"]["Plugin"]
|
||||
self.log.info("using render plugin : {}".format(plugin))
|
||||
|
||||
i = 0
|
||||
for key, values in dict(os.environ).items():
|
||||
if key.upper() in self.enviro_filter:
|
||||
payload["JobInfo"].update(
|
||||
{
|
||||
"EnvironmentKeyValue%d"
|
||||
% i: "{key}={value}".format(
|
||||
key=key, value=values
|
||||
)
|
||||
}
|
||||
)
|
||||
i += 1
|
||||
|
||||
self.log.info("Submitting..")
|
||||
self.log.info(json.dumps(payload, indent=4, sort_keys=True))
|
||||
|
||||
|
|
@ -193,10 +193,15 @@ class ExtractCelactionDeadline(pyblish.api.InstancePlugin):
|
|||
self.expected_files(instance, render_path)
|
||||
self.log.debug("__ expectedFiles: `{}`".format(
|
||||
instance.data["expectedFiles"]))
|
||||
|
||||
response = requests.post(self.deadline_url, json=payload)
|
||||
|
||||
if not response.ok:
|
||||
raise Exception(response.text)
|
||||
self.log.error(
|
||||
"Submission failed! [{}] {}".format(
|
||||
response.status_code, response.content))
|
||||
self.log.debug(payload)
|
||||
raise SystemExit(response.text)
|
||||
|
||||
return response
|
||||
|
||||
|
|
@ -234,32 +239,29 @@ class ExtractCelactionDeadline(pyblish.api.InstancePlugin):
|
|||
split_path = path.split(split_patern)
|
||||
hashes = "#" * int(search_results[1])
|
||||
return "".join([split_path[0], hashes, split_path[-1]])
|
||||
if "#" in path:
|
||||
self.log.debug("_ path: `{}`".format(path))
|
||||
return path
|
||||
else:
|
||||
return path
|
||||
|
||||
def expected_files(self,
|
||||
instance,
|
||||
path):
|
||||
self.log.debug("_ path: `{}`".format(path))
|
||||
return path
|
||||
|
||||
def expected_files(self, instance, filepath):
|
||||
""" Create expected files in instance data
|
||||
"""
|
||||
if not instance.data.get("expectedFiles"):
|
||||
instance.data["expectedFiles"] = list()
|
||||
instance.data["expectedFiles"] = []
|
||||
|
||||
dir = os.path.dirname(path)
|
||||
file = os.path.basename(path)
|
||||
dirpath = os.path.dirname(filepath)
|
||||
filename = os.path.basename(filepath)
|
||||
|
||||
if "#" in file:
|
||||
pparts = file.split("#")
|
||||
if "#" in filename:
|
||||
pparts = filename.split("#")
|
||||
padding = "%0{}d".format(len(pparts) - 1)
|
||||
file = pparts[0] + padding + pparts[-1]
|
||||
filename = pparts[0] + padding + pparts[-1]
|
||||
|
||||
if "%" not in file:
|
||||
instance.data["expectedFiles"].append(path)
|
||||
if "%" not in filename:
|
||||
instance.data["expectedFiles"].append(filepath)
|
||||
return
|
||||
|
||||
for i in range(self._frame_start, (self._frame_end + 1)):
|
||||
instance.data["expectedFiles"].append(
|
||||
os.path.join(dir, (file % i)).replace("\\", "/"))
|
||||
os.path.join(dirpath, (filename % i)).replace("\\", "/")
|
||||
)
|
||||
|
|
@ -241,6 +241,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
|
|||
environment["AVALON_ASSET"] = legacy_io.Session["AVALON_ASSET"]
|
||||
environment["AVALON_TASK"] = legacy_io.Session["AVALON_TASK"]
|
||||
environment["AVALON_APP_NAME"] = os.environ.get("AVALON_APP_NAME")
|
||||
environment["OPENPYPE_VERSION"] = os.environ.get("OPENPYPE_VERSION")
|
||||
environment["OPENPYPE_LOG_NO_COLORS"] = "1"
|
||||
environment["OPENPYPE_USERNAME"] = instance.context.data["user"]
|
||||
environment["OPENPYPE_PUBLISH_JOB"] = "1"
|
||||
|
|
@ -776,6 +777,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
|
|||
"handleEnd": handle_end,
|
||||
"frameStartHandle": start - handle_start,
|
||||
"frameEndHandle": end + handle_end,
|
||||
"comment": instance.data["comment"],
|
||||
"fps": fps,
|
||||
"source": source,
|
||||
"extendFrames": data.get("extendFrames"),
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 101 KiB |
|
|
@ -0,0 +1,38 @@
|
|||
[About]
|
||||
Type=label
|
||||
Label=About
|
||||
Category=About Plugin
|
||||
CategoryOrder=-1
|
||||
Index=0
|
||||
Default=Celaction Plugin for Deadline
|
||||
Description=Not configurable
|
||||
|
||||
[ConcurrentTasks]
|
||||
Type=label
|
||||
Label=ConcurrentTasks
|
||||
Category=About Plugin
|
||||
CategoryOrder=-1
|
||||
Index=0
|
||||
Default=True
|
||||
Description=Not configurable
|
||||
|
||||
[Executable]
|
||||
Type=filename
|
||||
Label=Executable
|
||||
Category=Config
|
||||
CategoryOrder=0
|
||||
CategoryIndex=0
|
||||
Description=The command executable to run
|
||||
Required=false
|
||||
DisableIfBlank=true
|
||||
|
||||
[RenderNameSeparator]
|
||||
Type=string
|
||||
Label=RenderNameSeparator
|
||||
Category=Config
|
||||
CategoryOrder=0
|
||||
CategoryIndex=1
|
||||
Description=The separator to use for naming
|
||||
Required=false
|
||||
DisableIfBlank=true
|
||||
Default=.
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
from System.Text.RegularExpressions import *
|
||||
|
||||
from Deadline.Plugins import *
|
||||
from Deadline.Scripting import *
|
||||
|
||||
import _winreg
|
||||
|
||||
######################################################################
|
||||
# This is the function that Deadline calls to get an instance of the
|
||||
# main DeadlinePlugin class.
|
||||
######################################################################
|
||||
|
||||
|
||||
def GetDeadlinePlugin():
|
||||
return CelActionPlugin()
|
||||
|
||||
|
||||
def CleanupDeadlinePlugin(deadlinePlugin):
|
||||
deadlinePlugin.Cleanup()
|
||||
|
||||
######################################################################
|
||||
# This is the main DeadlinePlugin class for the CelAction plugin.
|
||||
######################################################################
|
||||
|
||||
|
||||
class CelActionPlugin(DeadlinePlugin):
|
||||
|
||||
def __init__(self):
|
||||
self.InitializeProcessCallback += self.InitializeProcess
|
||||
self.RenderExecutableCallback += self.RenderExecutable
|
||||
self.RenderArgumentCallback += self.RenderArgument
|
||||
self.StartupDirectoryCallback += self.StartupDirectory
|
||||
|
||||
def Cleanup(self):
|
||||
for stdoutHandler in self.StdoutHandlers:
|
||||
del stdoutHandler.HandleCallback
|
||||
|
||||
del self.InitializeProcessCallback
|
||||
del self.RenderExecutableCallback
|
||||
del self.RenderArgumentCallback
|
||||
del self.StartupDirectoryCallback
|
||||
|
||||
def GetCelActionRegistryKey(self):
|
||||
# Modify registry for frame separation
|
||||
path = r'Software\CelAction\CelAction2D\User Settings'
|
||||
_winreg.CreateKey(_winreg.HKEY_CURRENT_USER, path)
|
||||
regKey = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, path, 0,
|
||||
_winreg.KEY_ALL_ACCESS)
|
||||
return regKey
|
||||
|
||||
def GetSeparatorValue(self, regKey):
|
||||
useSeparator, _ = _winreg.QueryValueEx(
|
||||
regKey, 'RenderNameUseSeparator')
|
||||
separator, _ = _winreg.QueryValueEx(regKey, 'RenderNameSeparator')
|
||||
|
||||
return useSeparator, separator
|
||||
|
||||
def SetSeparatorValue(self, regKey, useSeparator, separator):
|
||||
_winreg.SetValueEx(regKey, 'RenderNameUseSeparator',
|
||||
0, _winreg.REG_DWORD, useSeparator)
|
||||
_winreg.SetValueEx(regKey, 'RenderNameSeparator',
|
||||
0, _winreg.REG_SZ, separator)
|
||||
|
||||
def InitializeProcess(self):
|
||||
# Set the plugin specific settings.
|
||||
self.SingleFramesOnly = False
|
||||
|
||||
# Set the process specific settings.
|
||||
self.StdoutHandling = True
|
||||
self.PopupHandling = True
|
||||
|
||||
# Ignore 'celaction' Pop-up dialog
|
||||
self.AddPopupIgnorer(".*Rendering.*")
|
||||
self.AddPopupIgnorer(".*AutoRender.*")
|
||||
|
||||
# Ignore 'celaction' Pop-up dialog
|
||||
self.AddPopupIgnorer(".*Wait.*")
|
||||
|
||||
# Ignore 'celaction' Pop-up dialog
|
||||
self.AddPopupIgnorer(".*Timeline Scrub.*")
|
||||
|
||||
celActionRegKey = self.GetCelActionRegistryKey()
|
||||
|
||||
self.SetSeparatorValue(celActionRegKey, 1, self.GetConfigEntryWithDefault(
|
||||
"RenderNameSeparator", ".").strip())
|
||||
|
||||
def RenderExecutable(self):
|
||||
return RepositoryUtils.CheckPathMapping(self.GetConfigEntry("Executable").strip())
|
||||
|
||||
def RenderArgument(self):
|
||||
arguments = RepositoryUtils.CheckPathMapping(
|
||||
self.GetPluginInfoEntry("Arguments").strip())
|
||||
arguments = arguments.replace(
|
||||
"<STARTFRAME>", str(self.GetStartFrame()))
|
||||
arguments = arguments.replace("<ENDFRAME>", str(self.GetEndFrame()))
|
||||
arguments = self.ReplacePaddedFrame(
|
||||
arguments, "<STARTFRAME%([0-9]+)>", self.GetStartFrame())
|
||||
arguments = self.ReplacePaddedFrame(
|
||||
arguments, "<ENDFRAME%([0-9]+)>", self.GetEndFrame())
|
||||
arguments = arguments.replace("<QUOTE>", "\"")
|
||||
return arguments
|
||||
|
||||
def StartupDirectory(self):
|
||||
return self.GetPluginInfoEntryWithDefault("StartupDirectory", "").strip()
|
||||
|
||||
def ReplacePaddedFrame(self, arguments, pattern, frame):
|
||||
frameRegex = Regex(pattern)
|
||||
while True:
|
||||
frameMatch = frameRegex.Match(arguments)
|
||||
if frameMatch.Success:
|
||||
paddingSize = int(frameMatch.Groups[1].Value)
|
||||
if paddingSize > 0:
|
||||
padding = StringUtils.ToZeroPaddedString(
|
||||
frame, paddingSize, False)
|
||||
else:
|
||||
padding = str(frame)
|
||||
arguments = arguments.replace(
|
||||
frameMatch.Groups[0].Value, padding)
|
||||
else:
|
||||
break
|
||||
|
||||
return arguments
|
||||
|
|
@ -38,7 +38,7 @@ class IntegrateFtrackDescription(pyblish.api.InstancePlugin):
|
|||
self.log.info("There are any integrated AssetVersions")
|
||||
return
|
||||
|
||||
comment = (instance.context.data.get("comment") or "").strip()
|
||||
comment = instance.data["comment"]
|
||||
if not comment:
|
||||
self.log.info("Comment is not set.")
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ class IntegrateFtrackNote(pyblish.api.InstancePlugin):
|
|||
host_name = context.data["hostName"]
|
||||
app_name = context.data["appName"]
|
||||
app_label = context.data["appLabel"]
|
||||
comment = (context.data.get("comment") or "").strip()
|
||||
comment = instance.data["comment"]
|
||||
if not comment:
|
||||
self.log.info("Comment is not set.")
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import inspect
|
||||
from abc import ABCMeta
|
||||
|
||||
import pyblish.api
|
||||
|
|
@ -132,6 +133,25 @@ class OpenPypePyblishPluginMixin:
|
|||
)
|
||||
return attribute_values
|
||||
|
||||
@staticmethod
|
||||
def get_attr_values_from_data_for_plugin(plugin, data):
|
||||
"""Get attribute values for attribute definitions from data.
|
||||
|
||||
Args:
|
||||
plugin (Union[publish.api.Plugin, Type[publish.api.Plugin]]): The
|
||||
plugin for which attributes are extracted.
|
||||
data(dict): Data from instance or context.
|
||||
"""
|
||||
|
||||
if not inspect.isclass(plugin):
|
||||
plugin = plugin.__class__
|
||||
|
||||
return (
|
||||
data
|
||||
.get("publish_attributes", {})
|
||||
.get(plugin.__name__, {})
|
||||
)
|
||||
|
||||
def get_attr_values_from_data(self, data):
|
||||
"""Get attribute values for attribute definitions from data.
|
||||
|
||||
|
|
@ -139,11 +159,7 @@ class OpenPypePyblishPluginMixin:
|
|||
data(dict): Data from instance or context.
|
||||
"""
|
||||
|
||||
return (
|
||||
data
|
||||
.get("publish_attributes", {})
|
||||
.get(self.__class__.__name__, {})
|
||||
)
|
||||
return self.get_attr_values_from_data_for_plugin(self.__class__, data)
|
||||
|
||||
|
||||
class OptionalPyblishPluginMixin(OpenPypePyblishPluginMixin):
|
||||
|
|
|
|||
|
|
@ -1,21 +1,27 @@
|
|||
import collections
|
||||
import pyblish.api
|
||||
|
||||
from openpype.client import (
|
||||
get_last_version_by_subset_name,
|
||||
get_assets,
|
||||
get_subsets,
|
||||
get_last_versions,
|
||||
get_representations,
|
||||
)
|
||||
from openpype.pipeline import (
|
||||
legacy_io,
|
||||
get_representation_path,
|
||||
)
|
||||
from openpype.pipeline.load import get_representation_path_with_anatomy
|
||||
|
||||
|
||||
class CollectAudio(pyblish.api.InstancePlugin):
|
||||
class CollectAudio(pyblish.api.ContextPlugin):
|
||||
"""Collect asset's last published audio.
|
||||
|
||||
The audio subset name searched for is defined in:
|
||||
project settings > Collect Audio
|
||||
|
||||
Note:
|
||||
The plugin was instance plugin but because of so much queries the
|
||||
plugin was slowing down whole collection phase a lot thus was
|
||||
converted to context plugin which requires only 4 queries top.
|
||||
"""
|
||||
|
||||
label = "Collect Asset Audio"
|
||||
order = pyblish.api.CollectorOrder + 0.1
|
||||
families = ["review"]
|
||||
|
|
@ -39,67 +45,134 @@ class CollectAudio(pyblish.api.InstancePlugin):
|
|||
|
||||
audio_subset_name = "audioMain"
|
||||
|
||||
def process(self, instance):
|
||||
if instance.data.get("audio"):
|
||||
self.log.info(
|
||||
"Skipping Audio collecion. It is already collected"
|
||||
)
|
||||
def process(self, context):
|
||||
# Fake filtering by family inside context plugin
|
||||
filtered_instances = []
|
||||
for instance in pyblish.api.instances_by_plugin(
|
||||
context, self.__class__
|
||||
):
|
||||
# Skip instances that already have audio filled
|
||||
if instance.data.get("audio"):
|
||||
self.log.info(
|
||||
"Skipping Audio collecion. It is already collected"
|
||||
)
|
||||
continue
|
||||
filtered_instances.append(instance)
|
||||
|
||||
# Skip if none of instances remained
|
||||
if not filtered_instances:
|
||||
return
|
||||
|
||||
# Add audio to instance if exists.
|
||||
instances_by_asset_name = collections.defaultdict(list)
|
||||
for instance in filtered_instances:
|
||||
asset_name = instance.data["asset"]
|
||||
instances_by_asset_name[asset_name].append(instance)
|
||||
|
||||
asset_names = set(instances_by_asset_name.keys())
|
||||
self.log.info((
|
||||
"Searching for audio subset '{subset}'"
|
||||
" in asset '{asset}'"
|
||||
"Searching for audio subset '{subset}' in assets {assets}"
|
||||
).format(
|
||||
subset=self.audio_subset_name,
|
||||
asset=instance.data["asset"]
|
||||
assets=", ".join([
|
||||
'"{}"'.format(asset_name)
|
||||
for asset_name in asset_names
|
||||
])
|
||||
))
|
||||
|
||||
repre_doc = self._get_repre_doc(instance)
|
||||
# Query all required documents
|
||||
project_name = context.data["projectName"]
|
||||
anatomy = context.data["anatomy"]
|
||||
repre_docs_by_asset_names = self.query_representations(
|
||||
project_name, asset_names)
|
||||
|
||||
# Add audio to instance if representation was found
|
||||
if repre_doc:
|
||||
instance.data["audio"] = [{
|
||||
"offset": 0,
|
||||
"filename": get_representation_path(repre_doc)
|
||||
}]
|
||||
self.log.info("Audio Data added to instance ...")
|
||||
for asset_name, instances in instances_by_asset_name.items():
|
||||
repre_docs = repre_docs_by_asset_names[asset_name]
|
||||
if not repre_docs:
|
||||
continue
|
||||
|
||||
def _get_repre_doc(self, instance):
|
||||
cache = instance.context.data.get("__cache_asset_audio")
|
||||
if cache is None:
|
||||
cache = {}
|
||||
instance.context.data["__cache_asset_audio"] = cache
|
||||
asset_name = instance.data["asset"]
|
||||
repre_doc = repre_docs[0]
|
||||
repre_path = get_representation_path_with_anatomy(
|
||||
repre_doc, anatomy
|
||||
)
|
||||
for instance in instances:
|
||||
instance.data["audio"] = [{
|
||||
"offset": 0,
|
||||
"filename": repre_path
|
||||
}]
|
||||
self.log.info("Audio Data added to instance ...")
|
||||
|
||||
# first try to get it from cache
|
||||
if asset_name in cache:
|
||||
return cache[asset_name]
|
||||
def query_representations(self, project_name, asset_names):
|
||||
"""Query representations related to audio subsets for passed assets.
|
||||
|
||||
project_name = legacy_io.active_project()
|
||||
Args:
|
||||
project_name (str): Project in which we're looking for all
|
||||
entities.
|
||||
asset_names (Iterable[str]): Asset names where to look for audio
|
||||
subsets and their representations.
|
||||
|
||||
# Find latest versions document
|
||||
last_version_doc = get_last_version_by_subset_name(
|
||||
Returns:
|
||||
collections.defaultdict[str, List[Dict[Str, Any]]]: Representations
|
||||
related to audio subsets by asset name.
|
||||
"""
|
||||
|
||||
output = collections.defaultdict(list)
|
||||
# Query asset documents
|
||||
asset_docs = get_assets(
|
||||
project_name,
|
||||
self.audio_subset_name,
|
||||
asset_name=asset_name,
|
||||
fields=["_id"]
|
||||
asset_names=asset_names,
|
||||
fields=["_id", "name"]
|
||||
)
|
||||
|
||||
repre_doc = None
|
||||
if last_version_doc:
|
||||
# Try to find it's representation (Expected there is only one)
|
||||
repre_docs = list(get_representations(
|
||||
project_name, version_ids=[last_version_doc["_id"]]
|
||||
))
|
||||
if not repre_docs:
|
||||
self.log.warning(
|
||||
"Version document does not contain any representations"
|
||||
)
|
||||
else:
|
||||
repre_doc = repre_docs[0]
|
||||
asset_id_by_name = {}
|
||||
for asset_doc in asset_docs:
|
||||
asset_id_by_name[asset_doc["name"]] = asset_doc["_id"]
|
||||
asset_ids = set(asset_id_by_name.values())
|
||||
|
||||
# update cache
|
||||
cache[asset_name] = repre_doc
|
||||
# Query subsets with name define by 'audio_subset_name' attr
|
||||
# - one or none subsets with the name should be available on an asset
|
||||
subset_docs = get_subsets(
|
||||
project_name,
|
||||
subset_names=[self.audio_subset_name],
|
||||
asset_ids=asset_ids,
|
||||
fields=["_id", "parent"]
|
||||
)
|
||||
subset_id_by_asset_id = {}
|
||||
for subset_doc in subset_docs:
|
||||
asset_id = subset_doc["parent"]
|
||||
subset_id_by_asset_id[asset_id] = subset_doc["_id"]
|
||||
|
||||
return repre_doc
|
||||
subset_ids = set(subset_id_by_asset_id.values())
|
||||
if not subset_ids:
|
||||
return output
|
||||
|
||||
# Find all latest versions for the subsets
|
||||
version_docs_by_subset_id = get_last_versions(
|
||||
project_name, subset_ids=subset_ids, fields=["_id", "parent"]
|
||||
)
|
||||
version_id_by_subset_id = {
|
||||
subset_id: version_doc["_id"]
|
||||
for subset_id, version_doc in version_docs_by_subset_id.items()
|
||||
}
|
||||
version_ids = set(version_id_by_subset_id.values())
|
||||
if not version_ids:
|
||||
return output
|
||||
|
||||
# Find representations under latest versions of audio subsets
|
||||
repre_docs = get_representations(
|
||||
project_name, version_ids=version_ids
|
||||
)
|
||||
repre_docs_by_version_id = collections.defaultdict(list)
|
||||
for repre_doc in repre_docs:
|
||||
version_id = repre_doc["parent"]
|
||||
repre_docs_by_version_id[version_id].append(repre_doc)
|
||||
|
||||
if not repre_docs_by_version_id:
|
||||
return output
|
||||
|
||||
for asset_name in asset_names:
|
||||
asset_id = asset_id_by_name.get(asset_name)
|
||||
subset_id = subset_id_by_asset_id.get(asset_id)
|
||||
version_id = version_id_by_subset_id.get(subset_id)
|
||||
output[asset_name] = repre_docs_by_version_id[version_id]
|
||||
return output
|
||||
|
|
|
|||
|
|
@ -1,19 +1,123 @@
|
|||
"""
|
||||
Requires:
|
||||
None
|
||||
Provides:
|
||||
context -> comment (str)
|
||||
"""Collect comment and add option to enter comment per instance.
|
||||
|
||||
Combination of plugins. One define optional input for instances in Publisher
|
||||
UI (CollectInstanceCommentDef) and second cares that each instance during
|
||||
collection has available "comment" key in data (CollectComment).
|
||||
|
||||
Plugin 'CollectInstanceCommentDef' define "comment" attribute which won't be
|
||||
filled with any value if instance does not match families filter or when
|
||||
plugin is disabled.
|
||||
|
||||
Plugin 'CollectComment' makes sure that each instance in context has
|
||||
available "comment" key in data which can be set to 'str' or 'None' if is not
|
||||
set.
|
||||
- In case instance already has filled comment the plugin's logic is skipped
|
||||
- The comment is always set and value should be always 'str' even if is empty
|
||||
|
||||
Why are separated:
|
||||
- 'CollectInstanceCommentDef' can have specific settings to show comment
|
||||
attribute only to defined families in publisher UI
|
||||
- 'CollectComment' will run all the time
|
||||
|
||||
Todos:
|
||||
The comment per instance is not sent via farm.
|
||||
"""
|
||||
|
||||
import pyblish.api
|
||||
from openpype.lib.attribute_definitions import TextDef
|
||||
from openpype.pipeline.publish import OpenPypePyblishPluginMixin
|
||||
|
||||
|
||||
class CollectComment(pyblish.api.ContextPlugin):
|
||||
"""This plug-ins displays the comment dialog box per default"""
|
||||
class CollectInstanceCommentDef(
|
||||
pyblish.api.ContextPlugin,
|
||||
OpenPypePyblishPluginMixin
|
||||
):
|
||||
label = "Comment per instance"
|
||||
targets = ["local"]
|
||||
# Disable plugin by default
|
||||
families = []
|
||||
enabled = False
|
||||
|
||||
label = "Collect Comment"
|
||||
order = pyblish.api.CollectorOrder
|
||||
def process(self, instance):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def apply_settings(cls, project_setting, _):
|
||||
plugin_settings = project_setting["global"]["publish"].get(
|
||||
"collect_comment_per_instance"
|
||||
)
|
||||
if not plugin_settings:
|
||||
return
|
||||
|
||||
if plugin_settings.get("enabled") is not None:
|
||||
cls.enabled = plugin_settings["enabled"]
|
||||
|
||||
if plugin_settings.get("families") is not None:
|
||||
cls.families = plugin_settings["families"]
|
||||
|
||||
@classmethod
|
||||
def get_attribute_defs(cls):
|
||||
return [
|
||||
TextDef("comment", label="Comment")
|
||||
]
|
||||
|
||||
|
||||
class CollectComment(
|
||||
pyblish.api.ContextPlugin,
|
||||
OpenPypePyblishPluginMixin
|
||||
):
|
||||
"""Collect comment per each instance.
|
||||
|
||||
Plugin makes sure each instance to publish has set "comment" in data so any
|
||||
further plugin can use it directly.
|
||||
"""
|
||||
|
||||
label = "Collect Instance Comment"
|
||||
order = pyblish.api.CollectorOrder + 0.49
|
||||
|
||||
def process(self, context):
|
||||
comment = (context.data.get("comment") or "").strip()
|
||||
context.data["comment"] = comment
|
||||
context_comment = self.cleanup_comment(context.data.get("comment"))
|
||||
# Set it back
|
||||
context.data["comment"] = context_comment
|
||||
for instance in context:
|
||||
instance_label = str(instance)
|
||||
# Check if comment is already set
|
||||
instance_comment = self.cleanup_comment(
|
||||
instance.data.get("comment"))
|
||||
|
||||
# If comment on instance is not set then look for attributes
|
||||
if not instance_comment:
|
||||
attr_values = self.get_attr_values_from_data_for_plugin(
|
||||
CollectInstanceCommentDef, instance.data
|
||||
)
|
||||
instance_comment = self.cleanup_comment(
|
||||
attr_values.get("comment")
|
||||
)
|
||||
|
||||
# Use context comment if instance has all options of comment
|
||||
# empty
|
||||
if not instance_comment:
|
||||
instance_comment = context_comment
|
||||
|
||||
instance.data["comment"] = instance_comment
|
||||
if instance_comment:
|
||||
msg_end = " has comment set to: \"{}\"".format(
|
||||
instance_comment)
|
||||
else:
|
||||
msg_end = " does not have set comment"
|
||||
self.log.debug("Instance {} {}".format(instance_label, msg_end))
|
||||
|
||||
def cleanup_comment(self, comment):
|
||||
"""Cleanup comment value.
|
||||
|
||||
Args:
|
||||
comment (Union[str, None]): Comment value from data.
|
||||
|
||||
Returns:
|
||||
str: Cleaned comment which is stripped or empty string if input
|
||||
was 'None'.
|
||||
"""
|
||||
|
||||
if comment:
|
||||
return comment.strip()
|
||||
return ""
|
||||
|
|
|
|||
|
|
@ -468,7 +468,7 @@ class ExtractBurnin(publish.Extractor):
|
|||
|
||||
burnin_data.update({
|
||||
"version": int(version),
|
||||
"comment": context.data.get("comment") or ""
|
||||
"comment": instance.data["comment"]
|
||||
})
|
||||
|
||||
intent_label = context.data.get("intent") or ""
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ class ExtractThumbnailFromSource(pyblish.api.InstancePlugin):
|
|||
"Adding thumbnail representation: {}".format(new_repre)
|
||||
)
|
||||
instance.data["representations"].append(new_repre)
|
||||
instance.data["thumbnailPath"] = dst_filepath
|
||||
|
||||
def _create_thumbnail(self, context, thumbnail_source):
|
||||
if not thumbnail_source:
|
||||
|
|
|
|||
|
|
@ -769,7 +769,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin):
|
|||
"time": context.data["time"],
|
||||
"author": context.data["user"],
|
||||
"source": source,
|
||||
"comment": context.data.get("comment"),
|
||||
"comment": instance.data["comment"],
|
||||
"machine": context.data.get("machine"),
|
||||
"fps": instance.data.get("fps", context.data.get("fps"))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -968,7 +968,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin):
|
|||
"time": context.data["time"],
|
||||
"author": context.data["user"],
|
||||
"source": source,
|
||||
"comment": context.data.get("comment"),
|
||||
"comment": instance.data["comment"],
|
||||
"machine": context.data.get("machine"),
|
||||
"fps": context.data.get(
|
||||
"fps", instance.data.get("fps")
|
||||
|
|
|
|||
|
|
@ -102,8 +102,31 @@ class IntegrateThumbnails(pyblish.api.ContextPlugin):
|
|||
thumbnail_root
|
||||
)
|
||||
|
||||
def _get_thumbnail_from_instance(self, instance):
|
||||
# 1. Look for thumbnail in published representations
|
||||
published_repres = instance.data.get("published_representations")
|
||||
path = self._get_thumbnail_path_from_published(published_repres)
|
||||
if path and os.path.exists(path):
|
||||
return path
|
||||
|
||||
if path:
|
||||
self.log.warning(
|
||||
"Could not find published thumbnail path {}".format(path)
|
||||
)
|
||||
|
||||
# 2. Look for thumbnail in "not published" representations
|
||||
thumbnail_path = self._get_thumbnail_path_from_unpublished(instance)
|
||||
if thumbnail_path and os.path.exists(thumbnail_path):
|
||||
return thumbnail_path
|
||||
|
||||
# 3. Look for thumbnail path on instance in 'thumbnailPath'
|
||||
thumbnail_path = instance.data.get("thumbnailPath")
|
||||
if thumbnail_path and os.path.exists(thumbnail_path):
|
||||
return thumbnail_path
|
||||
return None
|
||||
|
||||
def _prepare_instances(self, context):
|
||||
context_thumbnail_path = context.get("thumbnailPath")
|
||||
context_thumbnail_path = context.data.get("thumbnailPath")
|
||||
valid_context_thumbnail = False
|
||||
if context_thumbnail_path and os.path.exists(context_thumbnail_path):
|
||||
valid_context_thumbnail = True
|
||||
|
|
@ -122,8 +145,7 @@ class IntegrateThumbnails(pyblish.api.ContextPlugin):
|
|||
continue
|
||||
|
||||
# Find thumbnail path on instance
|
||||
thumbnail_path = self._get_instance_thumbnail_path(
|
||||
published_repres)
|
||||
thumbnail_path = self._get_thumbnail_from_instance(instance)
|
||||
if thumbnail_path:
|
||||
self.log.debug((
|
||||
"Found thumbnail path for instance \"{}\"."
|
||||
|
|
@ -157,7 +179,10 @@ class IntegrateThumbnails(pyblish.api.ContextPlugin):
|
|||
for repre_info in published_representations.values():
|
||||
return repre_info["representation"]["parent"]
|
||||
|
||||
def _get_instance_thumbnail_path(self, published_representations):
|
||||
def _get_thumbnail_path_from_published(self, published_representations):
|
||||
if not published_representations:
|
||||
return None
|
||||
|
||||
thumb_repre_doc = None
|
||||
for repre_info in published_representations.values():
|
||||
repre_doc = repre_info["representation"]
|
||||
|
|
@ -179,6 +204,38 @@ class IntegrateThumbnails(pyblish.api.ContextPlugin):
|
|||
return None
|
||||
return os.path.normpath(path)
|
||||
|
||||
def _get_thumbnail_path_from_unpublished(self, instance):
|
||||
repres = instance.data.get("representations")
|
||||
if not repres:
|
||||
return None
|
||||
|
||||
thumbnail_repre = next(
|
||||
(
|
||||
repre
|
||||
for repre in repres
|
||||
if repre["name"] == "thumbnail"
|
||||
),
|
||||
None
|
||||
)
|
||||
if not thumbnail_repre:
|
||||
return None
|
||||
|
||||
staging_dir = thumbnail_repre.get("stagingDir")
|
||||
if not staging_dir:
|
||||
staging_dir = instance.data.get("stagingDir")
|
||||
|
||||
filename = thumbnail_repre.get("files")
|
||||
if not staging_dir or not filename:
|
||||
return None
|
||||
|
||||
if isinstance(filename, (list, tuple, set)):
|
||||
filename = filename[0]
|
||||
|
||||
thumbnail_path = os.path.join(staging_dir, filename)
|
||||
if os.path.exists(thumbnail_path):
|
||||
return thumbnail_path
|
||||
return None
|
||||
|
||||
def _integrate_thumbnails(
|
||||
self,
|
||||
filtered_instance_items,
|
||||
|
|
|
|||
BIN
openpype/resources/app_icons/celaction.png
Normal file
BIN
openpype/resources/app_icons/celaction.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.9 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 40 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 36 KiB |
|
|
@ -1,13 +1,9 @@
|
|||
{
|
||||
"publish": {
|
||||
"ExtractCelactionDeadline": {
|
||||
"enabled": true,
|
||||
"deadline_department": "",
|
||||
"deadline_priority": 50,
|
||||
"deadline_pool": "",
|
||||
"deadline_pool_secondary": "",
|
||||
"deadline_group": "",
|
||||
"deadline_chunk_size": 10
|
||||
"CollectRenderPath": {
|
||||
"output_extension": "png",
|
||||
"anatomy_template_key_render_files": "render",
|
||||
"anatomy_template_key_metadata": "render"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -70,6 +70,16 @@
|
|||
"department": "",
|
||||
"multiprocess": true
|
||||
},
|
||||
"CelactionSubmitDeadline": {
|
||||
"enabled": true,
|
||||
"deadline_department": "",
|
||||
"deadline_priority": 50,
|
||||
"deadline_pool": "",
|
||||
"deadline_pool_secondary": "",
|
||||
"deadline_group": "",
|
||||
"deadline_chunk_size": 10,
|
||||
"deadline_job_delay": "00:00:00:00"
|
||||
},
|
||||
"ProcessSubmittedJobOnFarm": {
|
||||
"enabled": true,
|
||||
"deadline_department": "",
|
||||
|
|
|
|||
|
|
@ -24,6 +24,10 @@
|
|||
],
|
||||
"skip_hosts_headless_publish": []
|
||||
},
|
||||
"collect_comment_per_instance": {
|
||||
"enabled": false,
|
||||
"families": []
|
||||
},
|
||||
"ValidateEditorialAssetName": {
|
||||
"enabled": true,
|
||||
"optional": false
|
||||
|
|
|
|||
|
|
@ -1268,12 +1268,12 @@
|
|||
"CELACTION_TEMPLATE": "{OPENPYPE_REPOS_ROOT}/openpype/hosts/celaction/celaction_template_scene.scn"
|
||||
},
|
||||
"variants": {
|
||||
"local": {
|
||||
"current": {
|
||||
"enabled": true,
|
||||
"variant_label": "Local",
|
||||
"variant_label": "Current",
|
||||
"use_python_2": false,
|
||||
"executables": {
|
||||
"windows": [],
|
||||
"windows": ["C:/Program Files/CelAction/CelAction2D Studio/CelAction2D.exe"],
|
||||
"darwin": [],
|
||||
"linux": []
|
||||
},
|
||||
|
|
|
|||
|
|
@ -14,45 +14,24 @@
|
|||
{
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"checkbox_key": "enabled",
|
||||
"key": "ExtractCelactionDeadline",
|
||||
"label": "ExtractCelactionDeadline",
|
||||
"key": "CollectRenderPath",
|
||||
"label": "CollectRenderPath",
|
||||
"is_group": true,
|
||||
"children": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "enabled",
|
||||
"label": "Enabled"
|
||||
"type": "text",
|
||||
"key": "output_extension",
|
||||
"label": "Output render file extension"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"key": "deadline_department",
|
||||
"label": "Deadline apartment"
|
||||
},
|
||||
{
|
||||
"type": "number",
|
||||
"key": "deadline_priority",
|
||||
"label": "Deadline priority"
|
||||
"key": "anatomy_template_key_render_files",
|
||||
"label": "Anatomy template key: render files"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"key": "deadline_pool",
|
||||
"label": "Deadline pool"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"key": "deadline_pool_secondary",
|
||||
"label": "Deadline pool (secondary)"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"key": "deadline_group",
|
||||
"label": "Deadline Group"
|
||||
},
|
||||
{
|
||||
"type": "number",
|
||||
"key": "deadline_chunk_size",
|
||||
"label": "Deadline Chunk size"
|
||||
"key": "anatomy_template_key_metadata",
|
||||
"label": "Anatomy template key: metadata job file"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -387,6 +387,56 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"checkbox_key": "enabled",
|
||||
"key": "CelactionSubmitDeadline",
|
||||
"label": "Celaction Submit Deadline",
|
||||
"is_group": true,
|
||||
"children": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "enabled",
|
||||
"label": "Enabled"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"key": "deadline_department",
|
||||
"label": "Deadline apartment"
|
||||
},
|
||||
{
|
||||
"type": "number",
|
||||
"key": "deadline_priority",
|
||||
"label": "Deadline priority"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"key": "deadline_pool",
|
||||
"label": "Deadline pool"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"key": "deadline_pool_secondary",
|
||||
"label": "Deadline pool (secondary)"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"key": "deadline_group",
|
||||
"label": "Deadline Group"
|
||||
},
|
||||
{
|
||||
"type": "number",
|
||||
"key": "deadline_chunk_size",
|
||||
"label": "Deadline Chunk size"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"key": "deadline_job_delay",
|
||||
"label": "Delay job (timecode dd:hh:mm:ss)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
|
|
|
|||
|
|
@ -60,6 +60,27 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"key": "collect_comment_per_instance",
|
||||
"label": "Collect comment per instance",
|
||||
"checkbox_key": "enabled",
|
||||
"is_group": true,
|
||||
"children": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "enabled",
|
||||
"label": "Enabled"
|
||||
},
|
||||
{
|
||||
"key": "families",
|
||||
"label": "Families",
|
||||
"type": "list",
|
||||
"object_type": "text"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
|
|
|
|||
|
|
@ -28,8 +28,8 @@
|
|||
"name": "template_host_variant",
|
||||
"template_data": [
|
||||
{
|
||||
"app_variant_label": "Local",
|
||||
"app_variant": "local"
|
||||
"app_variant_label": "Current",
|
||||
"app_variant": "current"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ exclude =
|
|||
docs,
|
||||
*/vendor,
|
||||
website,
|
||||
openpype/vendor
|
||||
openpype/vendor,
|
||||
*deadline/repository/custom/plugins
|
||||
|
||||
max-complexity = 30
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue