mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Merge branch 'develop' into enhancement/AY-4085_Houdini-adding-model-family
This commit is contained in:
commit
8fc726f77b
74 changed files with 2169 additions and 1274 deletions
|
|
@ -1,7 +1,7 @@
|
|||
from ayon_applications import PreLaunchHook
|
||||
|
||||
from ayon_core.pipeline.colorspace import get_imageio_config
|
||||
from ayon_core.pipeline.template_data import get_template_data_with_names
|
||||
from ayon_core.pipeline.colorspace import get_imageio_config_preset
|
||||
from ayon_core.pipeline.template_data import get_template_data
|
||||
|
||||
|
||||
class OCIOEnvHook(PreLaunchHook):
|
||||
|
|
@ -26,32 +26,38 @@ class OCIOEnvHook(PreLaunchHook):
|
|||
def execute(self):
|
||||
"""Hook entry method."""
|
||||
|
||||
template_data = get_template_data_with_names(
|
||||
project_name=self.data["project_name"],
|
||||
folder_path=self.data["folder_path"],
|
||||
task_name=self.data["task_name"],
|
||||
folder_entity = self.data["folder_entity"]
|
||||
|
||||
template_data = get_template_data(
|
||||
self.data["project_entity"],
|
||||
folder_entity=folder_entity,
|
||||
task_entity=self.data["task_entity"],
|
||||
host_name=self.host_name,
|
||||
settings=self.data["project_settings"]
|
||||
settings=self.data["project_settings"],
|
||||
)
|
||||
|
||||
config_data = get_imageio_config(
|
||||
project_name=self.data["project_name"],
|
||||
host_name=self.host_name,
|
||||
project_settings=self.data["project_settings"],
|
||||
anatomy_data=template_data,
|
||||
config_data = get_imageio_config_preset(
|
||||
self.data["project_name"],
|
||||
self.data["folder_path"],
|
||||
self.data["task_name"],
|
||||
self.host_name,
|
||||
anatomy=self.data["anatomy"],
|
||||
project_settings=self.data["project_settings"],
|
||||
template_data=template_data,
|
||||
env=self.launch_context.env,
|
||||
folder_id=folder_entity["id"],
|
||||
)
|
||||
|
||||
if config_data:
|
||||
ocio_path = config_data["path"]
|
||||
|
||||
if self.host_name in ["nuke", "hiero"]:
|
||||
ocio_path = ocio_path.replace("\\", "/")
|
||||
|
||||
self.log.info(
|
||||
f"Setting OCIO environment to config path: {ocio_path}")
|
||||
|
||||
self.launch_context.env["OCIO"] = ocio_path
|
||||
else:
|
||||
if not config_data:
|
||||
self.log.debug("OCIO not set or enabled")
|
||||
return
|
||||
|
||||
ocio_path = config_data["path"]
|
||||
|
||||
if self.host_name in ["nuke", "hiero"]:
|
||||
ocio_path = ocio_path.replace("\\", "/")
|
||||
|
||||
self.log.info(
|
||||
f"Setting OCIO environment to config path: {ocio_path}")
|
||||
|
||||
self.launch_context.env["OCIO"] = ocio_path
|
||||
|
|
|
|||
|
|
@ -58,3 +58,55 @@ class SelectInvalidAction(pyblish.api.Action):
|
|||
self.log.info(
|
||||
"Selecting invalid tools: %s" % ", ".join(sorted(names))
|
||||
)
|
||||
|
||||
|
||||
class SelectToolAction(pyblish.api.Action):
|
||||
"""Select invalid output tool in Fusion when plug-in failed.
|
||||
|
||||
"""
|
||||
|
||||
label = "Select saver"
|
||||
on = "failed" # This action is only available on a failed plug-in
|
||||
icon = "search" # Icon from Awesome Icon
|
||||
|
||||
def process(self, context, plugin):
|
||||
errored_instances = get_errored_instances_from_context(
|
||||
context,
|
||||
plugin=plugin,
|
||||
)
|
||||
|
||||
# Get the invalid nodes for the plug-ins
|
||||
self.log.info("Finding invalid nodes..")
|
||||
tools = []
|
||||
for instance in errored_instances:
|
||||
|
||||
tool = instance.data.get("tool")
|
||||
if tool is not None:
|
||||
tools.append(tool)
|
||||
else:
|
||||
self.log.warning(
|
||||
"Plug-in returned to be invalid, "
|
||||
f"but has no saver for instance {instance.name}."
|
||||
)
|
||||
|
||||
if not tools:
|
||||
# Assume relevant comp is current comp and clear selection
|
||||
self.log.info("No invalid tools found.")
|
||||
comp = get_current_comp()
|
||||
flow = comp.CurrentFrame.FlowView
|
||||
flow.Select() # No args equals clearing selection
|
||||
return
|
||||
|
||||
# Assume a single comp
|
||||
first_tool = tools[0]
|
||||
comp = first_tool.Comp()
|
||||
flow = comp.CurrentFrame.FlowView
|
||||
flow.Select() # No args equals clearing selection
|
||||
names = set()
|
||||
for tool in tools:
|
||||
flow.Select(tool, True)
|
||||
comp.SetActiveTool(tool)
|
||||
names.add(tool.Name)
|
||||
self.log.info(
|
||||
"Selecting invalid tools: %s" % ", ".join(sorted(names))
|
||||
)
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ class CollectFusionRender(
|
|||
if product_type not in ["render", "image"]:
|
||||
continue
|
||||
|
||||
task_name = context.data["task"]
|
||||
task_name = inst.data["task"]
|
||||
tool = inst.data["transientData"]["tool"]
|
||||
|
||||
instance_families = inst.data.get("families", [])
|
||||
|
|
|
|||
|
|
@ -0,0 +1,80 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Validate if instance context is the same as publish context."""
|
||||
|
||||
import pyblish.api
|
||||
from ayon_core.hosts.fusion.api.action import SelectToolAction
|
||||
from ayon_core.pipeline.publish import (
|
||||
RepairAction,
|
||||
ValidateContentsOrder,
|
||||
PublishValidationError,
|
||||
OptionalPyblishPluginMixin
|
||||
)
|
||||
|
||||
|
||||
class ValidateInstanceInContextFusion(pyblish.api.InstancePlugin,
|
||||
OptionalPyblishPluginMixin):
|
||||
"""Validator to check if instance context matches context of publish.
|
||||
|
||||
When working in per-shot style you always publish data in context of
|
||||
current asset (shot). This validator checks if this is so. It is optional
|
||||
so it can be disabled when needed.
|
||||
"""
|
||||
# Similar to maya and houdini-equivalent `ValidateInstanceInContext`
|
||||
|
||||
order = ValidateContentsOrder
|
||||
label = "Instance in same Context"
|
||||
optional = True
|
||||
hosts = ["fusion"]
|
||||
actions = [SelectToolAction, RepairAction]
|
||||
|
||||
def process(self, instance):
|
||||
if not self.is_active(instance.data):
|
||||
return
|
||||
|
||||
instance_context = self.get_context(instance.data)
|
||||
context = self.get_context(instance.context.data)
|
||||
if instance_context != context:
|
||||
context_label = "{} > {}".format(*context)
|
||||
instance_label = "{} > {}".format(*instance_context)
|
||||
|
||||
raise PublishValidationError(
|
||||
message=(
|
||||
"Instance '{}' publishes to different asset than current "
|
||||
"context: {}. Current context: {}".format(
|
||||
instance.name, instance_label, context_label
|
||||
)
|
||||
),
|
||||
description=(
|
||||
"## Publishing to a different asset\n"
|
||||
"There are publish instances present which are publishing "
|
||||
"into a different asset than your current context.\n\n"
|
||||
"Usually this is not what you want but there can be cases "
|
||||
"where you might want to publish into another asset or "
|
||||
"shot. If that's the case you can disable the validation "
|
||||
"on the instance to ignore it."
|
||||
)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def repair(cls, instance):
|
||||
|
||||
create_context = instance.context.data["create_context"]
|
||||
instance_id = instance.data.get("instance_id")
|
||||
created_instance = create_context.get_instance_by_id(
|
||||
instance_id
|
||||
)
|
||||
if created_instance is None:
|
||||
raise RuntimeError(
|
||||
f"No CreatedInstances found with id '{instance_id} "
|
||||
f"in {create_context.instances_by_id}"
|
||||
)
|
||||
|
||||
context_asset, context_task = cls.get_context(instance.context.data)
|
||||
created_instance["folderPath"] = context_asset
|
||||
created_instance["task"] = context_task
|
||||
create_context.save_changes()
|
||||
|
||||
@staticmethod
|
||||
def get_context(data):
|
||||
"""Return asset, task from publishing context data"""
|
||||
return data["folderPath"], data["task"]
|
||||
|
|
@ -1110,10 +1110,7 @@ def apply_colorspace_project():
|
|||
'''
|
||||
# backward compatibility layer
|
||||
# TODO: remove this after some time
|
||||
config_data = get_imageio_config(
|
||||
project_name=get_current_project_name(),
|
||||
host_name="hiero"
|
||||
)
|
||||
config_data = get_current_context_imageio_config_preset()
|
||||
|
||||
if config_data:
|
||||
presets.update({
|
||||
|
|
|
|||
|
|
@ -132,6 +132,6 @@ class CollectLocalRenderInstances(pyblish.api.InstancePlugin):
|
|||
]
|
||||
})
|
||||
|
||||
# Remove original render instance
|
||||
# I can't remove it here as I still need it to trigger the render.
|
||||
# context.remove(instance)
|
||||
# Skip integrating original render instance.
|
||||
# We are not removing it because it's used to trigger the render.
|
||||
instance.data["integrate"] = False
|
||||
|
|
|
|||
29
client/ayon_core/hosts/houdini/startup/OPmenu.xml
Normal file
29
client/ayon_core/hosts/houdini/startup/OPmenu.xml
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- OPMenu Stencil.
|
||||
It's used to extend the OPMenu.
|
||||
-->
|
||||
|
||||
<menuDocument>
|
||||
<menu>
|
||||
<!-- Operator type and asset options. -->
|
||||
<subMenu id="opmenu.vhda_options_create">
|
||||
<insertBefore>opmenu.unsynchronize</insertBefore>
|
||||
<scriptItem id="opmenu.vhda_create_ayon">
|
||||
<insertAfter>opmenu.vhda_create</insertAfter>
|
||||
<label>Create New (AYON)...</label>
|
||||
<context>
|
||||
</context>
|
||||
<scriptCode>
|
||||
<![CDATA[
|
||||
from ayon_core.hosts.houdini.api.creator_node_shelves import create_interactive
|
||||
|
||||
node = kwargs["node"]
|
||||
if node not in hou.selectedNodes():
|
||||
node.setSelected(True)
|
||||
create_interactive("io.openpype.creators.houdini.hda", **kwargs)
|
||||
]]>
|
||||
</scriptCode>
|
||||
</scriptItem>
|
||||
</subMenu>
|
||||
</menu>
|
||||
</menuDocument>
|
||||
|
|
@ -369,12 +369,8 @@ def reset_colorspace():
|
|||
"""
|
||||
if int(get_max_version()) < 2024:
|
||||
return
|
||||
project_name = get_current_project_name()
|
||||
colorspace_mgr = rt.ColorPipelineMgr
|
||||
project_settings = get_project_settings(project_name)
|
||||
|
||||
max_config_data = colorspace.get_imageio_config(
|
||||
project_name, "max", project_settings)
|
||||
max_config_data = colorspace.get_current_context_imageio_config_preset()
|
||||
if max_config_data:
|
||||
ocio_config_path = max_config_data["path"]
|
||||
colorspace_mgr = rt.ColorPipelineMgr
|
||||
|
|
@ -389,10 +385,7 @@ def check_colorspace():
|
|||
"because Max main window can't be found.")
|
||||
if int(get_max_version()) >= 2024:
|
||||
color_mgr = rt.ColorPipelineMgr
|
||||
project_name = get_current_project_name()
|
||||
project_settings = get_project_settings(project_name)
|
||||
max_config_data = colorspace.get_imageio_config(
|
||||
project_name, "max", project_settings)
|
||||
max_config_data = colorspace.get_current_context_imageio_config_preset()
|
||||
if max_config_data and color_mgr.Mode != rt.Name("OCIO_Custom"):
|
||||
if not is_headless():
|
||||
from ayon_core.tools.utils import SimplePopup
|
||||
|
|
|
|||
|
|
@ -52,11 +52,7 @@ class MaxHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost):
|
|||
|
||||
self._has_been_setup = True
|
||||
|
||||
def context_setting():
|
||||
return lib.set_context_setting()
|
||||
|
||||
rt.callbacks.addScript(rt.Name('systemPostNew'),
|
||||
context_setting)
|
||||
rt.callbacks.addScript(rt.Name('systemPostNew'), on_new)
|
||||
|
||||
rt.callbacks.addScript(rt.Name('filePostOpen'),
|
||||
lib.check_colorspace)
|
||||
|
|
@ -163,6 +159,14 @@ def ls() -> list:
|
|||
yield lib.read(container)
|
||||
|
||||
|
||||
def on_new():
|
||||
lib.set_context_setting()
|
||||
if rt.checkForSave():
|
||||
rt.resetMaxFile(rt.Name("noPrompt"))
|
||||
rt.clearUndoBuffer()
|
||||
rt.redrawViews()
|
||||
|
||||
|
||||
def containerise(name: str, nodes: list, context,
|
||||
namespace=None, loader=None, suffix="_CON"):
|
||||
data = {
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ class FBXExtractor:
|
|||
"smoothMesh": bool,
|
||||
"instances": bool,
|
||||
# "referencedContainersContent": bool, # deprecated in Maya 2016+
|
||||
"bakeComplexAnimation": int,
|
||||
"bakeComplexAnimation": bool,
|
||||
"bakeComplexStart": int,
|
||||
"bakeComplexEnd": int,
|
||||
"bakeComplexStep": int,
|
||||
|
|
@ -59,6 +59,7 @@ class FBXExtractor:
|
|||
"constraints": bool,
|
||||
"lights": bool,
|
||||
"embeddedTextures": bool,
|
||||
"includeChildren": bool,
|
||||
"inputConnections": bool,
|
||||
"upAxis": str, # x, y or z,
|
||||
"triangulate": bool,
|
||||
|
|
@ -102,6 +103,7 @@ class FBXExtractor:
|
|||
"constraints": False,
|
||||
"lights": True,
|
||||
"embeddedTextures": False,
|
||||
"includeChildren": True,
|
||||
"inputConnections": True,
|
||||
"upAxis": "y",
|
||||
"triangulate": False,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ from ayon_core.pipeline import (
|
|||
from ayon_core.pipeline.load.utils import get_representation_path_from_context
|
||||
from ayon_core.pipeline.colorspace import (
|
||||
get_imageio_file_rules_colorspace_from_filepath,
|
||||
get_imageio_config,
|
||||
get_current_context_imageio_config_preset,
|
||||
get_imageio_file_rules
|
||||
)
|
||||
from ayon_core.settings import get_project_settings
|
||||
|
|
@ -270,8 +270,7 @@ class FileNodeLoader(load.LoaderPlugin):
|
|||
host_name = get_current_host_name()
|
||||
project_settings = get_project_settings(project_name)
|
||||
|
||||
config_data = get_imageio_config(
|
||||
project_name, host_name,
|
||||
config_data = get_current_context_imageio_config_preset(
|
||||
project_settings=project_settings
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,8 @@ class ExtractFBXAnimation(publish.Extractor):
|
|||
fbx_exporter = fbx.FBXExtractor(log=self.log)
|
||||
out_members = instance.data.get("animated_skeleton", [])
|
||||
# Export
|
||||
instance.data["constraints"] = True
|
||||
# TODO: need to set up the options for users to set up
|
||||
# the flags they intended to export
|
||||
instance.data["skeletonDefinitions"] = True
|
||||
instance.data["referencedAssetsContent"] = True
|
||||
fbx_exporter.set_options_from_instance(instance)
|
||||
|
|
|
|||
|
|
@ -1,71 +0,0 @@
|
|||
import pyblish.api
|
||||
import ayon_core.hosts.maya.api.action
|
||||
from ayon_core.pipeline.publish import (
|
||||
PublishValidationError,
|
||||
ValidateContentsOrder,
|
||||
OptionalPyblishPluginMixin
|
||||
)
|
||||
from maya import cmds
|
||||
|
||||
|
||||
class ValidateAnimatedReferenceRig(pyblish.api.InstancePlugin,
|
||||
OptionalPyblishPluginMixin):
|
||||
"""Validate all nodes in skeletonAnim_SET are referenced"""
|
||||
|
||||
order = ValidateContentsOrder
|
||||
hosts = ["maya"]
|
||||
families = ["animation.fbx"]
|
||||
label = "Animated Reference Rig"
|
||||
accepted_controllers = ["transform", "locator"]
|
||||
actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction]
|
||||
optional = False
|
||||
|
||||
def process(self, instance):
|
||||
if not self.is_active(instance.data):
|
||||
return
|
||||
animated_sets = instance.data.get("animated_skeleton", [])
|
||||
if not animated_sets:
|
||||
self.log.debug(
|
||||
"No nodes found in skeletonAnim_SET. "
|
||||
"Skipping validation of animated reference rig..."
|
||||
)
|
||||
return
|
||||
|
||||
for animated_reference in animated_sets:
|
||||
is_referenced = cmds.referenceQuery(
|
||||
animated_reference, isNodeReferenced=True)
|
||||
if not bool(is_referenced):
|
||||
raise PublishValidationError(
|
||||
"All the content in skeletonAnim_SET"
|
||||
" should be referenced nodes"
|
||||
)
|
||||
invalid_controls = self.validate_controls(animated_sets)
|
||||
if invalid_controls:
|
||||
raise PublishValidationError(
|
||||
"All the content in skeletonAnim_SET"
|
||||
" should be transforms"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def validate_controls(self, set_members):
|
||||
"""Check if the controller set contains only accepted node types.
|
||||
|
||||
Checks if all its set members are within the hierarchy of the root
|
||||
Checks if the node types of the set members valid
|
||||
|
||||
Args:
|
||||
set_members: list of nodes of the skeleton_anim_set
|
||||
hierarchy: list of nodes which reside under the root node
|
||||
|
||||
Returns:
|
||||
errors (list)
|
||||
"""
|
||||
|
||||
# Validate control types
|
||||
invalid = []
|
||||
set_members = cmds.ls(set_members, long=True)
|
||||
for node in set_members:
|
||||
if cmds.nodeType(node) not in self.accepted_controllers:
|
||||
invalid.append(node)
|
||||
|
||||
return invalid
|
||||
|
|
@ -43,7 +43,9 @@ from ayon_core.pipeline import (
|
|||
from ayon_core.pipeline.context_tools import (
|
||||
get_current_context_custom_workfile_template
|
||||
)
|
||||
from ayon_core.pipeline.colorspace import get_imageio_config
|
||||
from ayon_core.pipeline.colorspace import (
|
||||
get_current_context_imageio_config_preset
|
||||
)
|
||||
from ayon_core.pipeline.workfile import BuildWorkfile
|
||||
from . import gizmo_menu
|
||||
from .constants import ASSIST
|
||||
|
|
@ -1552,10 +1554,7 @@ class WorkfileSettings(object):
|
|||
imageio_host (dict): host colorspace configurations
|
||||
|
||||
'''
|
||||
config_data = get_imageio_config(
|
||||
project_name=get_current_project_name(),
|
||||
host_name="nuke"
|
||||
)
|
||||
config_data = get_current_context_imageio_config_preset()
|
||||
|
||||
workfile_settings = imageio_host["workfile"]
|
||||
color_management = workfile_settings["color_management"]
|
||||
|
|
|
|||
|
|
@ -778,6 +778,7 @@ class ExporterReviewMov(ExporterReview):
|
|||
# deal with now lut defined in viewer lut
|
||||
self.viewer_lut_raw = klass.viewer_lut_raw
|
||||
self.write_colorspace = instance.data["colorspace"]
|
||||
self.color_channels = instance.data["color_channels"]
|
||||
|
||||
self.name = name or "baked"
|
||||
self.ext = ext or "mov"
|
||||
|
|
@ -834,7 +835,7 @@ class ExporterReviewMov(ExporterReview):
|
|||
self.log.info("Nodes exported...")
|
||||
return path
|
||||
|
||||
def generate_mov(self, farm=False, **kwargs):
|
||||
def generate_mov(self, farm=False, delete=True, **kwargs):
|
||||
# colorspace data
|
||||
colorspace = None
|
||||
# get colorspace settings
|
||||
|
|
@ -947,6 +948,8 @@ class ExporterReviewMov(ExporterReview):
|
|||
self.log.debug("Path: {}".format(self.path))
|
||||
write_node["file"].setValue(str(self.path))
|
||||
write_node["file_type"].setValue(str(self.ext))
|
||||
write_node["channels"].setValue(str(self.color_channels))
|
||||
|
||||
# Knobs `meta_codec` and `mov64_codec` are not available on centos.
|
||||
# TODO shouldn't this come from settings on outputs?
|
||||
try:
|
||||
|
|
@ -987,8 +990,13 @@ class ExporterReviewMov(ExporterReview):
|
|||
self.render(write_node.name())
|
||||
|
||||
# ---------- generate representation data
|
||||
tags = ["review", "need_thumbnail"]
|
||||
|
||||
if delete:
|
||||
tags.append("delete")
|
||||
|
||||
self.get_representation_data(
|
||||
tags=["review", "need_thumbnail", "delete"] + add_tags,
|
||||
tags=tags + add_tags,
|
||||
custom_tags=add_custom_tags,
|
||||
range=True,
|
||||
colorspace=colorspace
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ class LoadBackdropNodes(load.LoaderPlugin):
|
|||
}
|
||||
|
||||
# add attributes from the version to imprint to metadata knob
|
||||
for k in ["source", "author", "fps"]:
|
||||
for k in ["source", "fps"]:
|
||||
data_imprint[k] = version_attributes[k]
|
||||
|
||||
# getting file path
|
||||
|
|
@ -206,7 +206,7 @@ class LoadBackdropNodes(load.LoaderPlugin):
|
|||
"colorspaceInput": colorspace,
|
||||
}
|
||||
|
||||
for k in ["source", "author", "fps"]:
|
||||
for k in ["source", "fps"]:
|
||||
data_imprint[k] = version_attributes[k]
|
||||
|
||||
# adding nodes to node graph
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ class AlembicCameraLoader(load.LoaderPlugin):
|
|||
"frameEnd": last,
|
||||
"version": version_entity["version"],
|
||||
}
|
||||
for k in ["source", "author", "fps"]:
|
||||
for k in ["source", "fps"]:
|
||||
data_imprint[k] = version_attributes[k]
|
||||
|
||||
# getting file path
|
||||
|
|
@ -123,7 +123,7 @@ class AlembicCameraLoader(load.LoaderPlugin):
|
|||
}
|
||||
|
||||
# add attributes from the version to imprint to metadata knob
|
||||
for k in ["source", "author", "fps"]:
|
||||
for k in ["source", "fps"]:
|
||||
data_imprint[k] = version_attributes[k]
|
||||
|
||||
# getting file path
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ from ayon_core.pipeline import (
|
|||
get_representation_path,
|
||||
)
|
||||
from ayon_core.pipeline.colorspace import (
|
||||
get_imageio_file_rules_colorspace_from_filepath
|
||||
get_imageio_file_rules_colorspace_from_filepath,
|
||||
get_current_context_imageio_config_preset,
|
||||
)
|
||||
from ayon_core.hosts.nuke.api.lib import (
|
||||
get_imageio_input_colorspace,
|
||||
|
|
@ -197,7 +198,6 @@ class LoadClip(plugin.NukeLoader):
|
|||
"frameStart",
|
||||
"frameEnd",
|
||||
"source",
|
||||
"author",
|
||||
"fps",
|
||||
"handleStart",
|
||||
"handleEnd",
|
||||
|
|
@ -347,8 +347,7 @@ class LoadClip(plugin.NukeLoader):
|
|||
"source": version_attributes.get("source"),
|
||||
"handleStart": str(self.handle_start),
|
||||
"handleEnd": str(self.handle_end),
|
||||
"fps": str(version_attributes.get("fps")),
|
||||
"author": version_attributes.get("author")
|
||||
"fps": str(version_attributes.get("fps"))
|
||||
}
|
||||
|
||||
last_version_entity = ayon_api.get_last_version_by_product_id(
|
||||
|
|
@ -547,9 +546,10 @@ class LoadClip(plugin.NukeLoader):
|
|||
f"Colorspace from representation colorspaceData: {colorspace}"
|
||||
)
|
||||
|
||||
config_data = get_current_context_imageio_config_preset()
|
||||
# check if any filerules are not applicable
|
||||
new_parsed_colorspace = get_imageio_file_rules_colorspace_from_filepath( # noqa
|
||||
filepath, "nuke", project_name
|
||||
filepath, "nuke", project_name, config_data=config_data
|
||||
)
|
||||
self.log.debug(f"Colorspace new filerules: {new_parsed_colorspace}")
|
||||
|
||||
|
|
|
|||
|
|
@ -69,7 +69,6 @@ class LoadEffects(load.LoaderPlugin):
|
|||
"handleStart",
|
||||
"handleEnd",
|
||||
"source",
|
||||
"author",
|
||||
"fps"
|
||||
]:
|
||||
data_imprint[k] = version_attributes[k]
|
||||
|
|
@ -189,7 +188,6 @@ class LoadEffects(load.LoaderPlugin):
|
|||
"handleStart",
|
||||
"handleEnd",
|
||||
"source",
|
||||
"author",
|
||||
"fps",
|
||||
]:
|
||||
data_imprint[k] = version_attributes[k]
|
||||
|
|
|
|||
|
|
@ -69,7 +69,6 @@ class LoadEffectsInputProcess(load.LoaderPlugin):
|
|||
"handleStart",
|
||||
"handleEnd",
|
||||
"source",
|
||||
"author",
|
||||
"fps"
|
||||
]:
|
||||
data_imprint[k] = version_attributes[k]
|
||||
|
|
@ -192,7 +191,6 @@ class LoadEffectsInputProcess(load.LoaderPlugin):
|
|||
"handleStart",
|
||||
"handleEnd",
|
||||
"source",
|
||||
"author",
|
||||
"fps"
|
||||
]:
|
||||
data_imprint[k] = version_attributes[k]
|
||||
|
|
|
|||
|
|
@ -71,7 +71,6 @@ class LoadGizmo(load.LoaderPlugin):
|
|||
"handleStart",
|
||||
"handleEnd",
|
||||
"source",
|
||||
"author",
|
||||
"fps"
|
||||
]:
|
||||
data_imprint[k] = version_attributes[k]
|
||||
|
|
@ -139,7 +138,6 @@ class LoadGizmo(load.LoaderPlugin):
|
|||
"handleStart",
|
||||
"handleEnd",
|
||||
"source",
|
||||
"author",
|
||||
"fps"
|
||||
]:
|
||||
data_imprint[k] = version_attributes[k]
|
||||
|
|
|
|||
|
|
@ -73,7 +73,6 @@ class LoadGizmoInputProcess(load.LoaderPlugin):
|
|||
"handleStart",
|
||||
"handleEnd",
|
||||
"source",
|
||||
"author",
|
||||
"fps"
|
||||
]:
|
||||
data_imprint[k] = version_attributes[k]
|
||||
|
|
@ -145,7 +144,6 @@ class LoadGizmoInputProcess(load.LoaderPlugin):
|
|||
"handleStart",
|
||||
"handleEnd",
|
||||
"source",
|
||||
"author",
|
||||
"fps"
|
||||
]:
|
||||
data_imprint[k] = version_attributes[k]
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ class LoadImage(load.LoaderPlugin):
|
|||
"version": version_entity["version"],
|
||||
"colorspace": colorspace,
|
||||
}
|
||||
for k in ["source", "author", "fps"]:
|
||||
for k in ["source", "fps"]:
|
||||
data_imprint[k] = version_attributes.get(k, str(None))
|
||||
|
||||
r["tile_color"].setValue(int("0x4ecd25ff", 16))
|
||||
|
|
@ -207,7 +207,6 @@ class LoadImage(load.LoaderPlugin):
|
|||
"colorspace": version_attributes.get("colorSpace"),
|
||||
"source": version_attributes.get("source"),
|
||||
"fps": str(version_attributes.get("fps")),
|
||||
"author": version_attributes.get("author")
|
||||
}
|
||||
|
||||
# change color of node
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ class AlembicModelLoader(load.LoaderPlugin):
|
|||
"version": version_entity["version"]
|
||||
}
|
||||
# add attributes from the version to imprint to metadata knob
|
||||
for k in ["source", "author", "fps"]:
|
||||
for k in ["source", "fps"]:
|
||||
data_imprint[k] = version_attributes[k]
|
||||
|
||||
# getting file path
|
||||
|
|
@ -130,7 +130,7 @@ class AlembicModelLoader(load.LoaderPlugin):
|
|||
}
|
||||
|
||||
# add additional metadata from the version to imprint to Avalon knob
|
||||
for k in ["source", "author", "fps"]:
|
||||
for k in ["source", "fps"]:
|
||||
data_imprint[k] = version_attributes[k]
|
||||
|
||||
# getting file path
|
||||
|
|
|
|||
|
|
@ -55,7 +55,6 @@ class LinkAsGroup(load.LoaderPlugin):
|
|||
"handleStart",
|
||||
"handleEnd",
|
||||
"source",
|
||||
"author",
|
||||
"fps"
|
||||
]:
|
||||
data_imprint[k] = version_attributes[k]
|
||||
|
|
@ -131,7 +130,6 @@ class LinkAsGroup(load.LoaderPlugin):
|
|||
"colorspace": version_attributes.get("colorSpace"),
|
||||
"source": version_attributes.get("source"),
|
||||
"fps": version_attributes.get("fps"),
|
||||
"author": version_attributes.get("author")
|
||||
}
|
||||
|
||||
# Update the imprinted representation
|
||||
|
|
|
|||
|
|
@ -153,6 +153,9 @@ class CollectNukeWrites(pyblish.api.InstancePlugin,
|
|||
# Determine defined file type
|
||||
ext = write_node["file_type"].value()
|
||||
|
||||
# determine defined channel type
|
||||
color_channels = write_node["channels"].value()
|
||||
|
||||
# get frame range data
|
||||
handle_start = instance.context.data["handleStart"]
|
||||
handle_end = instance.context.data["handleEnd"]
|
||||
|
|
@ -172,7 +175,8 @@ class CollectNukeWrites(pyblish.api.InstancePlugin,
|
|||
"path": write_file_path,
|
||||
"outputDir": output_dir,
|
||||
"ext": ext,
|
||||
"colorspace": colorspace
|
||||
"colorspace": colorspace,
|
||||
"color_channels": color_channels
|
||||
})
|
||||
|
||||
if product_type == "render":
|
||||
|
|
|
|||
|
|
@ -136,11 +136,16 @@ class ExtractReviewIntermediates(publish.Extractor):
|
|||
self, instance, o_name, o_data["extension"],
|
||||
multiple_presets)
|
||||
|
||||
o_data["add_custom_tags"].append("intermediate")
|
||||
delete = not o_data.get("publish", False)
|
||||
|
||||
if instance.data.get("farm"):
|
||||
if "review" in instance.data["families"]:
|
||||
instance.data["families"].remove("review")
|
||||
|
||||
data = exporter.generate_mov(farm=True, **o_data)
|
||||
data = exporter.generate_mov(
|
||||
farm=True, delete=delete, **o_data
|
||||
)
|
||||
|
||||
self.log.debug(
|
||||
"_ data: {}".format(data))
|
||||
|
|
@ -154,7 +159,7 @@ class ExtractReviewIntermediates(publish.Extractor):
|
|||
"bakeWriteNodeName": data.get("bakeWriteNodeName")
|
||||
})
|
||||
else:
|
||||
data = exporter.generate_mov(**o_data)
|
||||
data = exporter.generate_mov(delete=delete, **o_data)
|
||||
|
||||
# add representation generated by exporter
|
||||
generated_repres.extend(data["representations"])
|
||||
|
|
|
|||
|
|
@ -156,14 +156,9 @@ This creator publishes color space look file (LUT).
|
|||
]
|
||||
|
||||
def apply_settings(self, project_settings):
|
||||
host = self.create_context.host
|
||||
host_name = host.name
|
||||
project_name = host.get_current_project_name()
|
||||
config_data = colorspace.get_imageio_config(
|
||||
project_name, host_name,
|
||||
config_data = colorspace.get_current_context_imageio_config_preset(
|
||||
project_settings=project_settings
|
||||
)
|
||||
|
||||
if not config_data:
|
||||
self.enabled = False
|
||||
return
|
||||
|
|
|
|||
|
|
@ -0,0 +1,96 @@
|
|||
from pathlib import Path
|
||||
|
||||
from ayon_core.pipeline import (
|
||||
CreatedInstance,
|
||||
)
|
||||
|
||||
from ayon_core.lib.attribute_definitions import (
|
||||
FileDef,
|
||||
BoolDef,
|
||||
TextDef,
|
||||
)
|
||||
from ayon_core.hosts.traypublisher.api.plugin import TrayPublishCreator
|
||||
|
||||
|
||||
class EditorialPackageCreator(TrayPublishCreator):
|
||||
"""Creates instance for OTIO file from published folder.
|
||||
|
||||
Folder contains OTIO file and exported .mov files. Process should publish
|
||||
whole folder as single `editorial_pckg` product type and (possibly) convert
|
||||
.mov files into different format and copy them into `publish` `resources`
|
||||
subfolder.
|
||||
"""
|
||||
identifier = "editorial_pckg"
|
||||
label = "Editorial package"
|
||||
product_type = "editorial_pckg"
|
||||
description = "Publish folder with OTIO file and resources"
|
||||
|
||||
# Position batch creator after simple creators
|
||||
order = 120
|
||||
|
||||
conversion_enabled = False
|
||||
|
||||
def apply_settings(self, project_settings):
|
||||
self.conversion_enabled = (
|
||||
project_settings["traypublisher"]
|
||||
["publish"]
|
||||
["ExtractEditorialPckgConversion"]
|
||||
["conversion_enabled"]
|
||||
)
|
||||
|
||||
def get_icon(self):
|
||||
return "fa.folder"
|
||||
|
||||
def create(self, product_name, instance_data, pre_create_data):
|
||||
folder_path = pre_create_data.get("folder_path")
|
||||
if not folder_path:
|
||||
return
|
||||
|
||||
instance_data["creator_attributes"] = {
|
||||
"folder_path": (Path(folder_path["directory"]) /
|
||||
Path(folder_path["filenames"][0])).as_posix(),
|
||||
"conversion_enabled": pre_create_data["conversion_enabled"]
|
||||
}
|
||||
|
||||
# Create new instance
|
||||
new_instance = CreatedInstance(self.product_type, product_name,
|
||||
instance_data, self)
|
||||
self._store_new_instance(new_instance)
|
||||
|
||||
def get_pre_create_attr_defs(self):
|
||||
# Use same attributes as for instance attributes
|
||||
return [
|
||||
FileDef(
|
||||
"folder_path",
|
||||
folders=True,
|
||||
single_item=True,
|
||||
extensions=[],
|
||||
allow_sequences=False,
|
||||
label="Folder path"
|
||||
),
|
||||
BoolDef("conversion_enabled",
|
||||
tooltip="Convert to output defined in Settings.",
|
||||
default=self.conversion_enabled,
|
||||
label="Convert resources"),
|
||||
]
|
||||
|
||||
def get_instance_attr_defs(self):
|
||||
return [
|
||||
TextDef(
|
||||
"folder_path",
|
||||
label="Folder path",
|
||||
disabled=True
|
||||
),
|
||||
BoolDef("conversion_enabled",
|
||||
tooltip="Convert to output defined in Settings.",
|
||||
label="Convert resources"),
|
||||
]
|
||||
|
||||
def get_detail_description(self):
|
||||
return """# Publish folder with OTIO file and video clips
|
||||
|
||||
Folder contains OTIO file and exported .mov files. Process should
|
||||
publish whole folder as single `editorial_pckg` product type and
|
||||
(possibly) convert .mov files into different format and copy them into
|
||||
`publish` `resources` subfolder.
|
||||
"""
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
"""Produces instance.data["editorial_pckg"] data used during integration.
|
||||
|
||||
Requires:
|
||||
instance.data["creator_attributes"]["path"] - from creator
|
||||
|
||||
Provides:
|
||||
instance -> editorial_pckg (dict):
|
||||
folder_path (str)
|
||||
otio_path (str) - from dragged folder
|
||||
resource_paths (list)
|
||||
|
||||
"""
|
||||
import os
|
||||
|
||||
import pyblish.api
|
||||
|
||||
from ayon_core.lib.transcoding import VIDEO_EXTENSIONS
|
||||
|
||||
|
||||
class CollectEditorialPackage(pyblish.api.InstancePlugin):
|
||||
"""Collects path to OTIO file and resources"""
|
||||
|
||||
label = "Collect Editorial Package"
|
||||
order = pyblish.api.CollectorOrder - 0.1
|
||||
|
||||
hosts = ["traypublisher"]
|
||||
families = ["editorial_pckg"]
|
||||
|
||||
def process(self, instance):
|
||||
folder_path = instance.data["creator_attributes"]["folder_path"]
|
||||
if not folder_path or not os.path.exists(folder_path):
|
||||
self.log.info((
|
||||
"Instance doesn't contain collected existing folder path."
|
||||
))
|
||||
return
|
||||
|
||||
instance.data["editorial_pckg"] = {}
|
||||
instance.data["editorial_pckg"]["folder_path"] = folder_path
|
||||
|
||||
otio_path, resource_paths = (
|
||||
self._get_otio_and_resource_paths(folder_path))
|
||||
|
||||
instance.data["editorial_pckg"]["otio_path"] = otio_path
|
||||
instance.data["editorial_pckg"]["resource_paths"] = resource_paths
|
||||
|
||||
def _get_otio_and_resource_paths(self, folder_path):
|
||||
otio_path = None
|
||||
resource_paths = []
|
||||
|
||||
file_names = os.listdir(folder_path)
|
||||
for filename in file_names:
|
||||
_, ext = os.path.splitext(filename)
|
||||
file_path = os.path.join(folder_path, filename)
|
||||
if ext == ".otio":
|
||||
otio_path = file_path
|
||||
elif ext in VIDEO_EXTENSIONS:
|
||||
resource_paths.append(file_path)
|
||||
return otio_path, resource_paths
|
||||
|
|
@ -1,10 +1,7 @@
|
|||
import pyblish.api
|
||||
from ayon_core.pipeline import (
|
||||
publish,
|
||||
registered_host
|
||||
)
|
||||
from ayon_core.lib import EnumDef
|
||||
from ayon_core.pipeline import colorspace
|
||||
from ayon_core.pipeline import publish
|
||||
from ayon_core.pipeline.publish import KnownPublishError
|
||||
|
||||
|
||||
|
|
@ -19,9 +16,10 @@ class CollectColorspace(pyblish.api.InstancePlugin,
|
|||
families = ["render", "plate", "reference", "image", "online"]
|
||||
enabled = False
|
||||
|
||||
colorspace_items = [
|
||||
default_colorspace_items = [
|
||||
(None, "Don't override")
|
||||
]
|
||||
colorspace_items = list(default_colorspace_items)
|
||||
colorspace_attr_show = False
|
||||
config_items = None
|
||||
|
||||
|
|
@ -69,14 +67,13 @@ class CollectColorspace(pyblish.api.InstancePlugin,
|
|||
|
||||
@classmethod
|
||||
def apply_settings(cls, project_settings):
|
||||
host = registered_host()
|
||||
host_name = host.name
|
||||
project_name = host.get_current_project_name()
|
||||
config_data = colorspace.get_imageio_config(
|
||||
project_name, host_name,
|
||||
config_data = colorspace.get_current_context_imageio_config_preset(
|
||||
project_settings=project_settings
|
||||
)
|
||||
|
||||
enabled = False
|
||||
colorspace_items = list(cls.default_colorspace_items)
|
||||
config_items = None
|
||||
if config_data:
|
||||
filepath = config_data["path"]
|
||||
config_items = colorspace.get_ocio_config_colorspaces(filepath)
|
||||
|
|
@ -85,9 +82,11 @@ class CollectColorspace(pyblish.api.InstancePlugin,
|
|||
include_aliases=True,
|
||||
include_roles=True
|
||||
)
|
||||
cls.config_items = config_items
|
||||
cls.colorspace_items.extend(labeled_colorspaces)
|
||||
cls.enabled = True
|
||||
colorspace_items.extend(labeled_colorspaces)
|
||||
|
||||
cls.config_items = config_items
|
||||
cls.colorspace_items = colorspace_items
|
||||
cls.enabled = enabled
|
||||
|
||||
@classmethod
|
||||
def get_attribute_defs(cls):
|
||||
|
|
|
|||
|
|
@ -0,0 +1,232 @@
|
|||
import copy
|
||||
import os.path
|
||||
import subprocess
|
||||
|
||||
import opentimelineio
|
||||
|
||||
import pyblish.api
|
||||
|
||||
from ayon_core.lib import get_ffmpeg_tool_args, run_subprocess
|
||||
from ayon_core.pipeline import publish
|
||||
|
||||
|
||||
class ExtractEditorialPckgConversion(publish.Extractor):
|
||||
"""Replaces movie paths in otio file with publish rootless
|
||||
|
||||
Prepares movie resources for integration (adds them to `transfers`).
|
||||
Converts .mov files according to output definition.
|
||||
"""
|
||||
|
||||
label = "Extract Editorial Package"
|
||||
order = pyblish.api.ExtractorOrder - 0.45
|
||||
hosts = ["traypublisher"]
|
||||
families = ["editorial_pckg"]
|
||||
|
||||
def process(self, instance):
|
||||
editorial_pckg_data = instance.data.get("editorial_pckg")
|
||||
|
||||
otio_path = editorial_pckg_data["otio_path"]
|
||||
otio_basename = os.path.basename(otio_path)
|
||||
staging_dir = self.staging_dir(instance)
|
||||
|
||||
editorial_pckg_repre = {
|
||||
'name': "editorial_pckg",
|
||||
'ext': "otio",
|
||||
'files': otio_basename,
|
||||
"stagingDir": staging_dir,
|
||||
}
|
||||
otio_staging_path = os.path.join(staging_dir, otio_basename)
|
||||
|
||||
instance.data["representations"].append(editorial_pckg_repre)
|
||||
|
||||
publish_resource_folder = self._get_publish_resource_folder(instance)
|
||||
resource_paths = editorial_pckg_data["resource_paths"]
|
||||
transfers = self._get_transfers(resource_paths,
|
||||
publish_resource_folder)
|
||||
|
||||
project_settings = instance.context.data["project_settings"]
|
||||
output_def = (project_settings["traypublisher"]
|
||||
["publish"]
|
||||
["ExtractEditorialPckgConversion"]
|
||||
["output"])
|
||||
|
||||
conversion_enabled = (instance.data["creator_attributes"]
|
||||
["conversion_enabled"])
|
||||
|
||||
if conversion_enabled and output_def["ext"]:
|
||||
transfers = self._convert_resources(output_def, transfers)
|
||||
|
||||
instance.data["transfers"] = transfers
|
||||
|
||||
source_to_rootless = self._get_resource_path_mapping(instance,
|
||||
transfers)
|
||||
|
||||
otio_data = editorial_pckg_data["otio_data"]
|
||||
otio_data = self._replace_target_urls(otio_data, source_to_rootless)
|
||||
|
||||
opentimelineio.adapters.write_to_file(otio_data, otio_staging_path)
|
||||
|
||||
self.log.info("Added Editorial Package representation: {}".format(
|
||||
editorial_pckg_repre))
|
||||
|
||||
def _get_publish_resource_folder(self, instance):
|
||||
"""Calculates publish folder and create it."""
|
||||
publish_path = self._get_published_path(instance)
|
||||
publish_folder = os.path.dirname(publish_path)
|
||||
publish_resource_folder = os.path.join(publish_folder, "resources")
|
||||
|
||||
if not os.path.exists(publish_resource_folder):
|
||||
os.makedirs(publish_resource_folder, exist_ok=True)
|
||||
return publish_resource_folder
|
||||
|
||||
def _get_resource_path_mapping(self, instance, transfers):
|
||||
"""Returns dict of {source_mov_path: rootless_published_path}."""
|
||||
replace_paths = {}
|
||||
anatomy = instance.context.data["anatomy"]
|
||||
for source, destination in transfers:
|
||||
rootless_path = self._get_rootless(anatomy, destination)
|
||||
source_file_name = os.path.basename(source)
|
||||
replace_paths[source_file_name] = rootless_path
|
||||
return replace_paths
|
||||
|
||||
def _get_transfers(self, resource_paths, publish_resource_folder):
|
||||
"""Returns list of tuples (source, destination) with movie paths."""
|
||||
transfers = []
|
||||
for res_path in resource_paths:
|
||||
res_basename = os.path.basename(res_path)
|
||||
pub_res_path = os.path.join(publish_resource_folder, res_basename)
|
||||
transfers.append((res_path, pub_res_path))
|
||||
return transfers
|
||||
|
||||
def _replace_target_urls(self, otio_data, replace_paths):
|
||||
"""Replace original movie paths with published rootless ones."""
|
||||
for track in otio_data.tracks:
|
||||
for clip in track:
|
||||
# Check if the clip has a media reference
|
||||
if clip.media_reference is not None:
|
||||
# Access the target_url from the media reference
|
||||
target_url = clip.media_reference.target_url
|
||||
if not target_url:
|
||||
continue
|
||||
file_name = os.path.basename(target_url)
|
||||
replace_path = replace_paths.get(file_name)
|
||||
if replace_path:
|
||||
clip.media_reference.target_url = replace_path
|
||||
if clip.name == file_name:
|
||||
clip.name = os.path.basename(replace_path)
|
||||
|
||||
return otio_data
|
||||
|
||||
def _get_rootless(self, anatomy, path):
|
||||
"""Try to find rootless {root[work]} path from `path`"""
|
||||
success, rootless_path = anatomy.find_root_template_from_path(
|
||||
path)
|
||||
if not success:
|
||||
# `rootless_path` is not set to `output_dir` if none of roots match
|
||||
self.log.warning(
|
||||
f"Could not find root path for remapping '{path}'."
|
||||
)
|
||||
rootless_path = path
|
||||
|
||||
return rootless_path
|
||||
|
||||
def _get_published_path(self, instance):
|
||||
"""Calculates expected `publish` folder"""
|
||||
# determine published path from Anatomy.
|
||||
template_data = instance.data.get("anatomyData")
|
||||
rep = instance.data["representations"][0]
|
||||
template_data["representation"] = rep.get("name")
|
||||
template_data["ext"] = rep.get("ext")
|
||||
template_data["comment"] = None
|
||||
|
||||
anatomy = instance.context.data["anatomy"]
|
||||
template_data["root"] = anatomy.roots
|
||||
template = anatomy.get_template_item("publish", "default", "path")
|
||||
template_filled = template.format_strict(template_data)
|
||||
return os.path.normpath(template_filled)
|
||||
|
||||
def _convert_resources(self, output_def, transfers):
|
||||
"""Converts all resource files to configured format."""
|
||||
out_extension = output_def["ext"]
|
||||
if not out_extension:
|
||||
self.log.warning("No output extension configured in "
|
||||
"ayon+settings://traypublisher/publish/ExtractEditorialPckgConversion") # noqa
|
||||
return transfers
|
||||
|
||||
final_transfers = []
|
||||
out_def_ffmpeg_args = output_def["ffmpeg_args"]
|
||||
ffmpeg_input_args = [
|
||||
value.strip()
|
||||
for value in out_def_ffmpeg_args["input"]
|
||||
if value.strip()
|
||||
]
|
||||
ffmpeg_video_filters = [
|
||||
value.strip()
|
||||
for value in out_def_ffmpeg_args["video_filters"]
|
||||
if value.strip()
|
||||
]
|
||||
ffmpeg_audio_filters = [
|
||||
value.strip()
|
||||
for value in out_def_ffmpeg_args["audio_filters"]
|
||||
if value.strip()
|
||||
]
|
||||
ffmpeg_output_args = [
|
||||
value.strip()
|
||||
for value in out_def_ffmpeg_args["output"]
|
||||
if value.strip()
|
||||
]
|
||||
ffmpeg_input_args = self._split_ffmpeg_args(ffmpeg_input_args)
|
||||
|
||||
generic_args = [
|
||||
subprocess.list2cmdline(get_ffmpeg_tool_args("ffmpeg"))
|
||||
]
|
||||
generic_args.extend(ffmpeg_input_args)
|
||||
if ffmpeg_video_filters:
|
||||
generic_args.append("-filter:v")
|
||||
generic_args.append(
|
||||
"\"{}\"".format(",".join(ffmpeg_video_filters)))
|
||||
|
||||
if ffmpeg_audio_filters:
|
||||
generic_args.append("-filter:a")
|
||||
generic_args.append(
|
||||
"\"{}\"".format(",".join(ffmpeg_audio_filters)))
|
||||
|
||||
for source, destination in transfers:
|
||||
base_name = os.path.basename(destination)
|
||||
file_name, ext = os.path.splitext(base_name)
|
||||
dest_path = os.path.join(os.path.dirname(destination),
|
||||
f"{file_name}.{out_extension}")
|
||||
final_transfers.append((source, dest_path))
|
||||
|
||||
all_args = copy.deepcopy(generic_args)
|
||||
all_args.append(f"-i \"{source}\"")
|
||||
all_args.extend(ffmpeg_output_args) # order matters
|
||||
all_args.append(f"\"{dest_path}\"")
|
||||
subprcs_cmd = " ".join(all_args)
|
||||
|
||||
# run subprocess
|
||||
self.log.debug("Executing: {}".format(subprcs_cmd))
|
||||
run_subprocess(subprcs_cmd, shell=True, logger=self.log)
|
||||
return final_transfers
|
||||
|
||||
def _split_ffmpeg_args(self, in_args):
|
||||
"""Makes sure all entered arguments are separated in individual items.
|
||||
|
||||
Split each argument string with " -" to identify if string contains
|
||||
one or more arguments.
|
||||
"""
|
||||
splitted_args = []
|
||||
for arg in in_args:
|
||||
sub_args = arg.split(" -")
|
||||
if len(sub_args) == 1:
|
||||
if arg and arg not in splitted_args:
|
||||
splitted_args.append(arg)
|
||||
continue
|
||||
|
||||
for idx, arg in enumerate(sub_args):
|
||||
if idx != 0:
|
||||
arg = "-" + arg
|
||||
|
||||
if arg and arg not in splitted_args:
|
||||
splitted_args.append(arg)
|
||||
return splitted_args
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
import os
|
||||
import opentimelineio
|
||||
|
||||
import pyblish.api
|
||||
from ayon_core.pipeline import PublishValidationError
|
||||
|
||||
|
||||
class ValidateEditorialPackage(pyblish.api.InstancePlugin):
|
||||
"""Checks that published folder contains all resources from otio
|
||||
|
||||
Currently checks only by file names and expects flat structure.
|
||||
It ignores path to resources in otio file as folder might be dragged in and
|
||||
published from different location than it was created.
|
||||
"""
|
||||
|
||||
label = "Validate Editorial Package"
|
||||
order = pyblish.api.ValidatorOrder - 0.49
|
||||
|
||||
hosts = ["traypublisher"]
|
||||
families = ["editorial_pckg"]
|
||||
|
||||
def process(self, instance):
|
||||
editorial_pckg_data = instance.data.get("editorial_pckg")
|
||||
if not editorial_pckg_data:
|
||||
raise PublishValidationError("Editorial package not collected")
|
||||
|
||||
folder_path = editorial_pckg_data["folder_path"]
|
||||
|
||||
otio_path = editorial_pckg_data["otio_path"]
|
||||
if not otio_path:
|
||||
raise PublishValidationError(
|
||||
f"Folder {folder_path} missing otio file")
|
||||
|
||||
resource_paths = editorial_pckg_data["resource_paths"]
|
||||
|
||||
resource_file_names = {os.path.basename(path)
|
||||
for path in resource_paths}
|
||||
|
||||
otio_data = opentimelineio.adapters.read_from_file(otio_path)
|
||||
|
||||
target_urls = self._get_all_target_urls(otio_data)
|
||||
missing_files = set()
|
||||
for target_url in target_urls:
|
||||
target_basename = os.path.basename(target_url)
|
||||
if target_basename not in resource_file_names:
|
||||
missing_files.add(target_basename)
|
||||
|
||||
if missing_files:
|
||||
raise PublishValidationError(
|
||||
f"Otio file contains missing files `{missing_files}`.\n\n"
|
||||
f"Please add them to `{folder_path}` and republish.")
|
||||
|
||||
instance.data["editorial_pckg"]["otio_data"] = otio_data
|
||||
|
||||
def _get_all_target_urls(self, otio_data):
|
||||
target_urls = []
|
||||
|
||||
# Iterate through tracks, clips, or other elements
|
||||
for track in otio_data.tracks:
|
||||
for clip in track:
|
||||
# Check if the clip has a media reference
|
||||
if clip.media_reference is not None:
|
||||
# Access the target_url from the media reference
|
||||
target_url = clip.media_reference.target_url
|
||||
if target_url:
|
||||
target_urls.append(target_url)
|
||||
|
||||
return target_urls
|
||||
|
|
@ -80,17 +80,21 @@ def get_engine_versions(env=None):
|
|||
def get_editor_exe_path(engine_path: Path, engine_version: str) -> Path:
|
||||
"""Get UE Editor executable path."""
|
||||
ue_path = engine_path / "Engine/Binaries"
|
||||
|
||||
ue_name = "UnrealEditor"
|
||||
|
||||
# handle older versions of Unreal Engine
|
||||
if engine_version.split(".")[0] == "4":
|
||||
ue_name = "UE4Editor"
|
||||
|
||||
if platform.system().lower() == "windows":
|
||||
if engine_version.split(".")[0] == "4":
|
||||
ue_path /= "Win64/UE4Editor.exe"
|
||||
elif engine_version.split(".")[0] == "5":
|
||||
ue_path /= "Win64/UnrealEditor.exe"
|
||||
ue_path /= f"Win64/{ue_name}.exe"
|
||||
|
||||
elif platform.system().lower() == "linux":
|
||||
ue_path /= "Linux/UE4Editor"
|
||||
ue_path /= f"Linux/{ue_name}"
|
||||
|
||||
elif platform.system().lower() == "darwin":
|
||||
ue_path /= "Mac/UE4Editor"
|
||||
ue_path /= f"Mac/{ue_name}"
|
||||
|
||||
return ue_path
|
||||
|
||||
|
|
|
|||
|
|
@ -29,15 +29,11 @@ from ayon_core.pipeline.publish.lib import (
|
|||
JSONDecodeError = getattr(json.decoder, "JSONDecodeError", ValueError)
|
||||
|
||||
|
||||
# TODO both 'requests_post' and 'requests_get' should not set 'verify' based
|
||||
# on environment variable. This should be done in a more controlled way,
|
||||
# e.g. each deadline url could have checkbox to enabled/disable
|
||||
# ssl verification.
|
||||
def requests_post(*args, **kwargs):
|
||||
"""Wrap request post method.
|
||||
|
||||
Disabling SSL certificate validation if ``DONT_VERIFY_SSL`` environment
|
||||
variable is found. This is useful when Deadline server is
|
||||
Disabling SSL certificate validation if ``verify`` kwarg is set to False.
|
||||
This is useful when Deadline server is
|
||||
running with self-signed certificates and its certificate is not
|
||||
added to trusted certificates on client machines.
|
||||
|
||||
|
|
@ -46,10 +42,6 @@ def requests_post(*args, **kwargs):
|
|||
of defense SSL is providing, and it is not recommended.
|
||||
|
||||
"""
|
||||
if 'verify' not in kwargs:
|
||||
kwargs['verify'] = False if os.getenv("OPENPYPE_DONT_VERIFY_SSL",
|
||||
True) else True # noqa
|
||||
|
||||
auth = kwargs.get("auth")
|
||||
if auth:
|
||||
kwargs["auth"] = tuple(auth) # explicit cast to tuple
|
||||
|
|
@ -61,8 +53,8 @@ def requests_post(*args, **kwargs):
|
|||
def requests_get(*args, **kwargs):
|
||||
"""Wrap request get method.
|
||||
|
||||
Disabling SSL certificate validation if ``DONT_VERIFY_SSL`` environment
|
||||
variable is found. This is useful when Deadline server is
|
||||
Disabling SSL certificate validation if ``verify`` kwarg is set to False.
|
||||
This is useful when Deadline server is
|
||||
running with self-signed certificates and its certificate is not
|
||||
added to trusted certificates on client machines.
|
||||
|
||||
|
|
@ -71,9 +63,6 @@ def requests_get(*args, **kwargs):
|
|||
of defense SSL is providing, and it is not recommended.
|
||||
|
||||
"""
|
||||
if 'verify' not in kwargs:
|
||||
kwargs['verify'] = False if os.getenv("OPENPYPE_DONT_VERIFY_SSL",
|
||||
True) else True # noqa
|
||||
auth = kwargs.get("auth")
|
||||
if auth:
|
||||
kwargs["auth"] = tuple(auth)
|
||||
|
|
@ -466,7 +455,8 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin,
|
|||
self.aux_files = self.get_aux_files()
|
||||
|
||||
auth = instance.data["deadline"]["auth"]
|
||||
job_id = self.process_submission(auth)
|
||||
verify = instance.data["deadline"]["verify"]
|
||||
job_id = self.process_submission(auth, verify)
|
||||
self.log.info("Submitted job to Deadline: {}.".format(job_id))
|
||||
|
||||
# TODO: Find a way that's more generic and not render type specific
|
||||
|
|
@ -479,10 +469,10 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin,
|
|||
job_info=render_job_info,
|
||||
plugin_info=render_plugin_info
|
||||
)
|
||||
render_job_id = self.submit(payload, auth)
|
||||
render_job_id = self.submit(payload, auth, verify)
|
||||
self.log.info("Render job id: %s", render_job_id)
|
||||
|
||||
def process_submission(self, auth=None):
|
||||
def process_submission(self, auth=None, verify=True):
|
||||
"""Process data for submission.
|
||||
|
||||
This takes Deadline JobInfo, PluginInfo, AuxFile, creates payload
|
||||
|
|
@ -493,7 +483,7 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin,
|
|||
|
||||
"""
|
||||
payload = self.assemble_payload()
|
||||
return self.submit(payload, auth)
|
||||
return self.submit(payload, auth, verify)
|
||||
|
||||
@abstractmethod
|
||||
def get_job_info(self):
|
||||
|
|
@ -583,7 +573,7 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin,
|
|||
"AuxFiles": aux_files or self.aux_files
|
||||
}
|
||||
|
||||
def submit(self, payload, auth):
|
||||
def submit(self, payload, auth, verify):
|
||||
"""Submit payload to Deadline API end-point.
|
||||
|
||||
This takes payload in the form of JSON file and POST it to
|
||||
|
|
@ -592,6 +582,7 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin,
|
|||
Args:
|
||||
payload (dict): dict to become json in deadline submission.
|
||||
auth (tuple): (username, password)
|
||||
verify (bool): verify SSL certificate if present
|
||||
|
||||
Returns:
|
||||
str: resulting Deadline job id.
|
||||
|
|
@ -601,8 +592,8 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin,
|
|||
|
||||
"""
|
||||
url = "{}/api/jobs".format(self._deadline_url)
|
||||
response = requests_post(url, json=payload,
|
||||
auth=auth)
|
||||
response = requests_post(
|
||||
url, json=payload, auth=auth, verify=verify)
|
||||
if not response.ok:
|
||||
self.log.error("Submission failed!")
|
||||
self.log.error(response.status_code)
|
||||
|
|
|
|||
|
|
@ -76,6 +76,9 @@ class CollectDeadlineUserCredentials(pyblish.api.InstancePlugin):
|
|||
)
|
||||
instance.data["deadline"]["auth"] = None
|
||||
|
||||
instance.data["deadline"]["verify"] = (
|
||||
not deadline_info["not_verify_ssl"])
|
||||
|
||||
if not deadline_info["require_authentication"]:
|
||||
return
|
||||
# TODO import 'get_addon_site_settings' when available
|
||||
|
|
|
|||
|
|
@ -174,8 +174,9 @@ class BlenderSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline,
|
|||
instance.data["toBeRenderedOn"] = "deadline"
|
||||
|
||||
payload = self.assemble_payload()
|
||||
return self.submit(payload,
|
||||
auth=instance.data["deadline"]["auth"])
|
||||
auth = instance.data["deadline"]["auth"]
|
||||
verify = instance.data["deadline"]["verify"]
|
||||
return self.submit(payload, auth=auth, verify=verify)
|
||||
|
||||
def from_published_scene(self):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -193,9 +193,11 @@ class CelactionSubmitDeadline(pyblish.api.InstancePlugin):
|
|||
self.expected_files(instance, render_path)
|
||||
self.log.debug("__ expectedFiles: `{}`".format(
|
||||
instance.data["expectedFiles"]))
|
||||
|
||||
auth = instance.data["deadline"]["auth"]
|
||||
verify = instance.data["deadline"]["verify"]
|
||||
response = requests_post(self.deadline_url, json=payload,
|
||||
auth=instance.data["deadline"]["require_authentication"])
|
||||
auth=auth,
|
||||
verify=verify)
|
||||
|
||||
if not response.ok:
|
||||
self.log.error(
|
||||
|
|
|
|||
|
|
@ -242,7 +242,8 @@ class FusionSubmitDeadline(
|
|||
# E.g. http://192.168.0.1:8082/api/jobs
|
||||
url = "{}/api/jobs".format(deadline_url)
|
||||
auth = instance.data["deadline"]["auth"]
|
||||
response = requests_post(url, json=payload, auth=auth)
|
||||
verify = instance.data["deadline"]["verify"]
|
||||
response = requests_post(url, json=payload, auth=auth, verify=verify)
|
||||
if not response.ok:
|
||||
raise Exception(response.text)
|
||||
|
||||
|
|
|
|||
|
|
@ -181,19 +181,27 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline,
|
|||
|
||||
self.log.debug("Submitting 3dsMax render..")
|
||||
project_settings = instance.context.data["project_settings"]
|
||||
auth = instance.data["deadline"]["auth"]
|
||||
verify = instance.data["deadline"]["verify"]
|
||||
if instance.data.get("multiCamera"):
|
||||
self.log.debug("Submitting jobs for multiple cameras..")
|
||||
payload = self._use_published_name_for_multiples(
|
||||
payload_data, project_settings)
|
||||
job_infos, plugin_infos = payload
|
||||
for job_info, plugin_info in zip(job_infos, plugin_infos):
|
||||
self.submit(self.assemble_payload(job_info, plugin_info),
|
||||
instance.data["deadline"]["auth"])
|
||||
self.submit(
|
||||
self.assemble_payload(job_info, plugin_info),
|
||||
auth=auth,
|
||||
verify=verify
|
||||
)
|
||||
else:
|
||||
payload = self._use_published_name(payload_data, project_settings)
|
||||
job_info, plugin_info = payload
|
||||
self.submit(self.assemble_payload(job_info, plugin_info),
|
||||
instance.data["deadline"]["auth"])
|
||||
self.submit(
|
||||
self.assemble_payload(job_info, plugin_info),
|
||||
auth=auth,
|
||||
verify=verify
|
||||
)
|
||||
|
||||
def _use_published_name(self, data, project_settings):
|
||||
# Not all hosts can import these modules.
|
||||
|
|
|
|||
|
|
@ -292,7 +292,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline,
|
|||
|
||||
return plugin_payload
|
||||
|
||||
def process_submission(self, auth=None):
|
||||
def process_submission(self, auth=None, verify=True):
|
||||
from maya import cmds
|
||||
instance = self._instance
|
||||
|
||||
|
|
@ -332,8 +332,10 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline,
|
|||
if "vrayscene" in instance.data["families"]:
|
||||
self.log.debug("Submitting V-Ray scene render..")
|
||||
vray_export_payload = self._get_vray_export_payload(payload_data)
|
||||
|
||||
export_job = self.submit(vray_export_payload,
|
||||
instance.data["deadline"]["auth"])
|
||||
auth=auth,
|
||||
verify=verify)
|
||||
|
||||
payload = self._get_vray_render_payload(payload_data)
|
||||
|
||||
|
|
@ -353,7 +355,8 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline,
|
|||
# Submit main render job
|
||||
job_info, plugin_info = payload
|
||||
self.submit(self.assemble_payload(job_info, plugin_info),
|
||||
instance.data["deadline"]["auth"])
|
||||
auth=auth,
|
||||
verify=verify)
|
||||
|
||||
def _tile_render(self, payload):
|
||||
"""Submit as tile render per frame with dependent assembly jobs."""
|
||||
|
|
@ -557,13 +560,18 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline,
|
|||
# Submit assembly jobs
|
||||
assembly_job_ids = []
|
||||
num_assemblies = len(assembly_payloads)
|
||||
auth = instance.data["deadline"]["auth"]
|
||||
verify = instance.data["deadline"]["verify"]
|
||||
for i, payload in enumerate(assembly_payloads):
|
||||
self.log.debug(
|
||||
"submitting assembly job {} of {}".format(i + 1,
|
||||
num_assemblies)
|
||||
)
|
||||
assembly_job_id = self.submit(payload,
|
||||
instance.data["deadline"]["auth"])
|
||||
assembly_job_id = self.submit(
|
||||
payload,
|
||||
auth=auth,
|
||||
verify=verify
|
||||
)
|
||||
assembly_job_ids.append(assembly_job_id)
|
||||
|
||||
instance.data["assemblySubmissionJobs"] = assembly_job_ids
|
||||
|
|
|
|||
|
|
@ -424,8 +424,12 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin,
|
|||
self.log.debug("__ expectedFiles: `{}`".format(
|
||||
instance.data["expectedFiles"]))
|
||||
auth = instance.data["deadline"]["auth"]
|
||||
response = requests_post(self.deadline_url, json=payload, timeout=10,
|
||||
auth=auth)
|
||||
verify = instance.data["deadline"]["verify"]
|
||||
response = requests_post(self.deadline_url,
|
||||
json=payload,
|
||||
timeout=10,
|
||||
auth=auth,
|
||||
verify=verify)
|
||||
|
||||
if not response.ok:
|
||||
raise Exception(response.text)
|
||||
|
|
|
|||
|
|
@ -210,8 +210,9 @@ class ProcessSubmittedCacheJobOnFarm(pyblish.api.InstancePlugin,
|
|||
|
||||
url = "{}/api/jobs".format(self.deadline_url)
|
||||
auth = instance.data["deadline"]["auth"]
|
||||
response = requests_post(url, json=payload, timeout=10,
|
||||
auth=auth)
|
||||
verify = instance.data["deadline"]["verify"]
|
||||
response = requests_post(
|
||||
url, json=payload, timeout=10, auth=auth, verify=verify)
|
||||
if not response.ok:
|
||||
raise Exception(response.text)
|
||||
|
||||
|
|
|
|||
|
|
@ -304,8 +304,9 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin,
|
|||
|
||||
url = "{}/api/jobs".format(self.deadline_url)
|
||||
auth = instance.data["deadline"]["auth"]
|
||||
response = requests_post(url, json=payload, timeout=10,
|
||||
auth=auth)
|
||||
verify = instance.data["deadline"]["verify"]
|
||||
response = requests_post(
|
||||
url, json=payload, timeout=10, auth=auth, verify=verify)
|
||||
if not response.ok:
|
||||
raise Exception(response.text)
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ from ayon_core.lib import Logger, run_subprocess, AYONSettingsRegistry
|
|||
from ayon_core.lib.vendor_bin_utils import find_tool_in_custom_paths
|
||||
|
||||
from .rr_job import SubmitFile
|
||||
from .rr_job import RRjob, SubmitterParameter # noqa F401
|
||||
from .rr_job import RRJob, SubmitterParameter # noqa F401
|
||||
|
||||
|
||||
class Api:
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -459,36 +459,6 @@ def is_representation_from_latest(representation):
|
|||
)
|
||||
|
||||
|
||||
def get_template_data_from_session(session=None, settings=None):
|
||||
"""Template data for template fill from session keys.
|
||||
|
||||
Args:
|
||||
session (Union[Dict[str, str], None]): The Session to use. If not
|
||||
provided use the currently active global Session.
|
||||
settings (Optional[Dict[str, Any]]): Prepared studio or project
|
||||
settings.
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: All available data from session.
|
||||
"""
|
||||
|
||||
if session is not None:
|
||||
project_name = session["AYON_PROJECT_NAME"]
|
||||
folder_path = session["AYON_FOLDER_PATH"]
|
||||
task_name = session["AYON_TASK_NAME"]
|
||||
host_name = session["AYON_HOST_NAME"]
|
||||
else:
|
||||
context = get_current_context()
|
||||
project_name = context["project_name"]
|
||||
folder_path = context["folder_path"]
|
||||
task_name = context["task_name"]
|
||||
host_name = get_current_host_name()
|
||||
|
||||
return get_template_data_with_names(
|
||||
project_name, folder_path, task_name, host_name, settings
|
||||
)
|
||||
|
||||
|
||||
def get_current_context_template_data(settings=None):
|
||||
"""Prepare template data for current context.
|
||||
|
||||
|
|
|
|||
|
|
@ -108,70 +108,6 @@ class IntegrateAsset(pyblish.api.InstancePlugin):
|
|||
|
||||
label = "Integrate Asset"
|
||||
order = pyblish.api.IntegratorOrder
|
||||
families = ["workfile",
|
||||
"pointcache",
|
||||
"pointcloud",
|
||||
"proxyAbc",
|
||||
"camera",
|
||||
"animation",
|
||||
"model",
|
||||
"maxScene",
|
||||
"mayaAscii",
|
||||
"mayaScene",
|
||||
"setdress",
|
||||
"layout",
|
||||
"ass",
|
||||
"assProxy",
|
||||
"vdbcache",
|
||||
"scene",
|
||||
"vrayproxy",
|
||||
"vrayscene_layer",
|
||||
"render",
|
||||
"prerender",
|
||||
"imagesequence",
|
||||
"review",
|
||||
"rendersetup",
|
||||
"rig",
|
||||
"plate",
|
||||
"look",
|
||||
"ociolook",
|
||||
"audio",
|
||||
"yetiRig",
|
||||
"yeticache",
|
||||
"nukenodes",
|
||||
"gizmo",
|
||||
"source",
|
||||
"matchmove",
|
||||
"image",
|
||||
"assembly",
|
||||
"fbx",
|
||||
"gltf",
|
||||
"textures",
|
||||
"action",
|
||||
"harmony.template",
|
||||
"harmony.palette",
|
||||
"editorial",
|
||||
"background",
|
||||
"camerarig",
|
||||
"redshiftproxy",
|
||||
"effect",
|
||||
"xgen",
|
||||
"hda",
|
||||
"usd",
|
||||
"staticMesh",
|
||||
"skeletalMesh",
|
||||
"mvLook",
|
||||
"mvUsd",
|
||||
"mvUsdComposition",
|
||||
"mvUsdOverride",
|
||||
"online",
|
||||
"uasset",
|
||||
"blendScene",
|
||||
"yeticacheUE",
|
||||
"tycache",
|
||||
"csv_ingest_file",
|
||||
"render.local.hou"
|
||||
]
|
||||
|
||||
default_template_name = "publish"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
import pyblish.api
|
||||
|
||||
from ayon_core.lib import filter_profiles
|
||||
from ayon_core.host import ILoadHost
|
||||
from ayon_core.pipeline.load import any_outdated_containers
|
||||
from ayon_core.pipeline import (
|
||||
get_current_host_name,
|
||||
registered_host,
|
||||
PublishXmlValidationError,
|
||||
OptionalPyblishPluginMixin
|
||||
)
|
||||
|
|
@ -18,17 +23,50 @@ class ShowInventory(pyblish.api.Action):
|
|||
host_tools.show_scene_inventory()
|
||||
|
||||
|
||||
class ValidateContainers(OptionalPyblishPluginMixin,
|
||||
pyblish.api.ContextPlugin):
|
||||
|
||||
class ValidateOutdatedContainers(
|
||||
OptionalPyblishPluginMixin,
|
||||
pyblish.api.ContextPlugin
|
||||
):
|
||||
"""Containers are must be updated to latest version on publish."""
|
||||
|
||||
label = "Validate Outdated Containers"
|
||||
order = pyblish.api.ValidatorOrder
|
||||
hosts = ["maya", "houdini", "nuke", "harmony", "photoshop", "aftereffects"]
|
||||
|
||||
optional = True
|
||||
actions = [ShowInventory]
|
||||
|
||||
@classmethod
|
||||
def apply_settings(cls, settings):
|
||||
# Disable plugin if host does not inherit from 'ILoadHost'
|
||||
# - not a host that can load containers
|
||||
host = registered_host()
|
||||
if not isinstance(host, ILoadHost):
|
||||
cls.enabled = False
|
||||
return
|
||||
|
||||
# Disable if no profile is found for the current host
|
||||
profiles = (
|
||||
settings
|
||||
["core"]
|
||||
["publish"]
|
||||
["ValidateOutdatedContainers"]
|
||||
["plugin_state_profiles"]
|
||||
)
|
||||
profile = filter_profiles(
|
||||
profiles, {"host_names": get_current_host_name()}
|
||||
)
|
||||
if not profile:
|
||||
cls.enabled = False
|
||||
return
|
||||
|
||||
# Apply settings from profile
|
||||
for attr_name in {
|
||||
"enabled",
|
||||
"optional",
|
||||
"active",
|
||||
}:
|
||||
setattr(cls, attr_name, profile[attr_name])
|
||||
|
||||
def process(self, context):
|
||||
if not self.is_active(context.data):
|
||||
return
|
||||
|
|
|
|||
|
|
@ -1,28 +1,31 @@
|
|||
"""OpenColorIO Wrapper.
|
||||
|
||||
Only to be interpreted by Python 3. It is run in subprocess in case
|
||||
Python 2 hosts needs to use it. Or it is used as module for Python 3
|
||||
processing.
|
||||
|
||||
Providing functionality:
|
||||
- get_colorspace - console command - python 2
|
||||
- returning all available color spaces
|
||||
found in input config path.
|
||||
- _get_colorspace_data - python 3 - module function
|
||||
- returning all available colorspaces
|
||||
found in input config path.
|
||||
- get_views - console command - python 2
|
||||
- returning all available viewers
|
||||
found in input config path.
|
||||
- _get_views_data - python 3 - module function
|
||||
- returning all available viewers
|
||||
found in input config path.
|
||||
Receive OpenColorIO information and store it in JSON format for processed
|
||||
that don't have access to OpenColorIO or their version of OpenColorIO is
|
||||
not compatible.
|
||||
"""
|
||||
|
||||
import click
|
||||
import json
|
||||
from pathlib import Path
|
||||
import PyOpenColorIO as ocio
|
||||
|
||||
import click
|
||||
|
||||
from ayon_core.pipeline.colorspace import (
|
||||
has_compatible_ocio_package,
|
||||
get_display_view_colorspace_name,
|
||||
get_config_file_rules_colorspace_from_filepath,
|
||||
get_config_version_data,
|
||||
get_ocio_config_views,
|
||||
get_ocio_config_colorspaces,
|
||||
)
|
||||
|
||||
|
||||
def _save_output_to_json_file(output, output_path):
|
||||
json_path = Path(output_path)
|
||||
with open(json_path, "w") as stream:
|
||||
json.dump(output, stream)
|
||||
|
||||
print(f"Data are saved to '{json_path}'")
|
||||
|
||||
|
||||
@click.group()
|
||||
|
|
@ -30,404 +33,185 @@ def main():
|
|||
pass # noqa: WPS100
|
||||
|
||||
|
||||
@main.group()
|
||||
def config():
|
||||
"""Config related commands group
|
||||
|
||||
Example of use:
|
||||
> pyton.exe ./ocio_wrapper.py config <command> *args
|
||||
"""
|
||||
pass # noqa: WPS100
|
||||
|
||||
|
||||
@main.group()
|
||||
def colorspace():
|
||||
"""Colorspace related commands group
|
||||
|
||||
Example of use:
|
||||
> pyton.exe ./ocio_wrapper.py config <command> *args
|
||||
"""
|
||||
pass # noqa: WPS100
|
||||
|
||||
|
||||
@config.command(
|
||||
name="get_colorspace",
|
||||
help=(
|
||||
"return all colorspaces from config file "
|
||||
"--path input arg is required"
|
||||
)
|
||||
)
|
||||
@click.option("--in_path", required=True,
|
||||
help="path where to read ocio config file",
|
||||
type=click.Path(exists=True))
|
||||
@click.option("--out_path", required=True,
|
||||
help="path where to write output json file",
|
||||
type=click.Path())
|
||||
def get_colorspace(in_path, out_path):
|
||||
@main.command(
|
||||
name="get_ocio_config_colorspaces",
|
||||
help="return all colorspaces from config file")
|
||||
@click.option(
|
||||
"--config_path",
|
||||
required=True,
|
||||
help="OCIO config path to read ocio config file.",
|
||||
type=click.Path(exists=True))
|
||||
@click.option(
|
||||
"--output_path",
|
||||
required=True,
|
||||
help="path where to write output json file",
|
||||
type=click.Path())
|
||||
def _get_ocio_config_colorspaces(config_path, output_path):
|
||||
"""Aggregate all colorspace to file.
|
||||
|
||||
Python 2 wrapped console command
|
||||
|
||||
Args:
|
||||
in_path (str): config file path string
|
||||
out_path (str): temp json file path string
|
||||
config_path (str): config file path string
|
||||
output_path (str): temp json file path string
|
||||
|
||||
Example of use:
|
||||
> pyton.exe ./ocio_wrapper.py config get_colorspace
|
||||
--in_path=<path> --out_path=<path>
|
||||
--config_path <path> --output_path <path>
|
||||
"""
|
||||
json_path = Path(out_path)
|
||||
|
||||
out_data = _get_colorspace_data(in_path)
|
||||
|
||||
with open(json_path, "w") as f_:
|
||||
json.dump(out_data, f_)
|
||||
|
||||
print(f"Colorspace data are saved to '{json_path}'")
|
||||
|
||||
|
||||
def _get_colorspace_data(config_path):
|
||||
"""Return all found colorspace data.
|
||||
|
||||
Args:
|
||||
config_path (str): path string leading to config.ocio
|
||||
|
||||
Raises:
|
||||
IOError: Input config does not exist.
|
||||
|
||||
Returns:
|
||||
dict: aggregated available colorspaces
|
||||
"""
|
||||
config_path = Path(config_path)
|
||||
|
||||
if not config_path.is_file():
|
||||
raise IOError(
|
||||
f"Input path `{config_path}` should be `config.ocio` file")
|
||||
|
||||
config = ocio.Config().CreateFromFile(str(config_path))
|
||||
|
||||
colorspace_data = {
|
||||
"roles": {},
|
||||
"colorspaces": {
|
||||
color.getName(): {
|
||||
"family": color.getFamily(),
|
||||
"categories": list(color.getCategories()),
|
||||
"aliases": list(color.getAliases()),
|
||||
"equalitygroup": color.getEqualityGroup(),
|
||||
}
|
||||
for color in config.getColorSpaces()
|
||||
},
|
||||
"displays_views": {
|
||||
f"{view} ({display})": {
|
||||
"display": display,
|
||||
"view": view
|
||||
|
||||
}
|
||||
for display in config.getDisplays()
|
||||
for view in config.getViews(display)
|
||||
},
|
||||
"looks": {}
|
||||
}
|
||||
|
||||
# add looks
|
||||
looks = config.getLooks()
|
||||
if looks:
|
||||
colorspace_data["looks"] = {
|
||||
look.getName(): {"process_space": look.getProcessSpace()}
|
||||
for look in looks
|
||||
}
|
||||
|
||||
# add roles
|
||||
roles = config.getRoles()
|
||||
if roles:
|
||||
colorspace_data["roles"] = {
|
||||
role: {"colorspace": colorspace}
|
||||
for (role, colorspace) in roles
|
||||
}
|
||||
|
||||
return colorspace_data
|
||||
|
||||
|
||||
@config.command(
|
||||
name="get_views",
|
||||
help=(
|
||||
"return all viewers from config file "
|
||||
"--path input arg is required"
|
||||
_save_output_to_json_file(
|
||||
get_ocio_config_colorspaces(config_path),
|
||||
output_path
|
||||
)
|
||||
)
|
||||
@click.option("--in_path", required=True,
|
||||
help="path where to read ocio config file",
|
||||
type=click.Path(exists=True))
|
||||
@click.option("--out_path", required=True,
|
||||
help="path where to write output json file",
|
||||
type=click.Path())
|
||||
def get_views(in_path, out_path):
|
||||
|
||||
|
||||
@main.command(
|
||||
name="get_ocio_config_views",
|
||||
help="All viewers from config file")
|
||||
@click.option(
|
||||
"--config_path",
|
||||
required=True,
|
||||
help="OCIO config path to read ocio config file.",
|
||||
type=click.Path(exists=True))
|
||||
@click.option(
|
||||
"--output_path",
|
||||
required=True,
|
||||
help="path where to write output json file",
|
||||
type=click.Path())
|
||||
def _get_ocio_config_views(config_path, output_path):
|
||||
"""Aggregate all viewers to file.
|
||||
|
||||
Python 2 wrapped console command
|
||||
|
||||
Args:
|
||||
in_path (str): config file path string
|
||||
out_path (str): temp json file path string
|
||||
config_path (str): config file path string
|
||||
output_path (str): temp json file path string
|
||||
|
||||
Example of use:
|
||||
> pyton.exe ./ocio_wrapper.py config get_views \
|
||||
--in_path=<path> --out_path=<path>
|
||||
--config_path <path> --output <path>
|
||||
"""
|
||||
json_path = Path(out_path)
|
||||
|
||||
out_data = _get_views_data(in_path)
|
||||
|
||||
with open(json_path, "w") as f_:
|
||||
json.dump(out_data, f_)
|
||||
|
||||
print(f"Viewer data are saved to '{json_path}'")
|
||||
|
||||
|
||||
def _get_views_data(config_path):
|
||||
"""Return all found viewer data.
|
||||
|
||||
Args:
|
||||
config_path (str): path string leading to config.ocio
|
||||
|
||||
Raises:
|
||||
IOError: Input config does not exist.
|
||||
|
||||
Returns:
|
||||
dict: aggregated available viewers
|
||||
"""
|
||||
config_path = Path(config_path)
|
||||
|
||||
if not config_path.is_file():
|
||||
raise IOError("Input path should be `config.ocio` file")
|
||||
|
||||
config = ocio.Config().CreateFromFile(str(config_path))
|
||||
|
||||
data_ = {}
|
||||
for display in config.getDisplays():
|
||||
for view in config.getViews(display):
|
||||
colorspace = config.getDisplayViewColorSpaceName(display, view)
|
||||
# Special token. See https://opencolorio.readthedocs.io/en/latest/guides/authoring/authoring.html#shared-views # noqa
|
||||
if colorspace == "<USE_DISPLAY_NAME>":
|
||||
colorspace = display
|
||||
|
||||
data_[f"{display}/{view}"] = {
|
||||
"display": display,
|
||||
"view": view,
|
||||
"colorspace": colorspace
|
||||
}
|
||||
|
||||
return data_
|
||||
|
||||
|
||||
@config.command(
|
||||
name="get_version",
|
||||
help=(
|
||||
"return major and minor version from config file "
|
||||
"--config_path input arg is required"
|
||||
"--out_path input arg is required"
|
||||
_save_output_to_json_file(
|
||||
get_ocio_config_views(config_path),
|
||||
output_path
|
||||
)
|
||||
)
|
||||
@click.option("--config_path", required=True,
|
||||
help="path where to read ocio config file",
|
||||
type=click.Path(exists=True))
|
||||
@click.option("--out_path", required=True,
|
||||
help="path where to write output json file",
|
||||
type=click.Path())
|
||||
def get_version(config_path, out_path):
|
||||
"""Get version of config.
|
||||
|
||||
Python 2 wrapped console command
|
||||
|
||||
@main.command(
|
||||
name="get_config_version_data",
|
||||
help="Get major and minor version from config file")
|
||||
@click.option(
|
||||
"--config_path",
|
||||
required=True,
|
||||
help="OCIO config path to read ocio config file.",
|
||||
type=click.Path(exists=True))
|
||||
@click.option(
|
||||
"--output_path",
|
||||
required=True,
|
||||
help="path where to write output json file",
|
||||
type=click.Path())
|
||||
def _get_config_version_data(config_path, output_path):
|
||||
"""Get version of config.
|
||||
|
||||
Args:
|
||||
config_path (str): ocio config file path string
|
||||
out_path (str): temp json file path string
|
||||
output_path (str): temp json file path string
|
||||
|
||||
Example of use:
|
||||
> pyton.exe ./ocio_wrapper.py config get_version \
|
||||
--config_path=<path> --out_path=<path>
|
||||
--config_path <path> --output_path <path>
|
||||
"""
|
||||
json_path = Path(out_path)
|
||||
|
||||
out_data = _get_version_data(config_path)
|
||||
|
||||
with open(json_path, "w") as f_:
|
||||
json.dump(out_data, f_)
|
||||
|
||||
print(f"Config version data are saved to '{json_path}'")
|
||||
|
||||
|
||||
def _get_version_data(config_path):
|
||||
"""Return major and minor version info.
|
||||
|
||||
Args:
|
||||
config_path (str): path string leading to config.ocio
|
||||
|
||||
Raises:
|
||||
IOError: Input config does not exist.
|
||||
|
||||
Returns:
|
||||
dict: minor and major keys with values
|
||||
"""
|
||||
config_path = Path(config_path)
|
||||
|
||||
if not config_path.is_file():
|
||||
raise IOError("Input path should be `config.ocio` file")
|
||||
|
||||
config = ocio.Config().CreateFromFile(str(config_path))
|
||||
|
||||
return {
|
||||
"major": config.getMajorVersion(),
|
||||
"minor": config.getMinorVersion()
|
||||
}
|
||||
|
||||
|
||||
@colorspace.command(
|
||||
name="get_config_file_rules_colorspace_from_filepath",
|
||||
help=(
|
||||
"return colorspace from filepath "
|
||||
"--config_path - ocio config file path (input arg is required) "
|
||||
"--filepath - any file path (input arg is required) "
|
||||
"--out_path - temp json file path (input arg is required)"
|
||||
_save_output_to_json_file(
|
||||
get_config_version_data(config_path),
|
||||
output_path
|
||||
)
|
||||
)
|
||||
@click.option("--config_path", required=True,
|
||||
help="path where to read ocio config file",
|
||||
type=click.Path(exists=True))
|
||||
@click.option("--filepath", required=True,
|
||||
help="path to file to get colorspace from",
|
||||
type=click.Path())
|
||||
@click.option("--out_path", required=True,
|
||||
help="path where to write output json file",
|
||||
type=click.Path())
|
||||
def get_config_file_rules_colorspace_from_filepath(
|
||||
config_path, filepath, out_path
|
||||
|
||||
|
||||
@main.command(
|
||||
name="get_config_file_rules_colorspace_from_filepath",
|
||||
help="Colorspace file rules from filepath")
|
||||
@click.option(
|
||||
"--config_path",
|
||||
required=True,
|
||||
help="OCIO config path to read ocio config file.",
|
||||
type=click.Path(exists=True))
|
||||
@click.option(
|
||||
"--filepath",
|
||||
required=True,
|
||||
help="Path to file to get colorspace from.",
|
||||
type=click.Path())
|
||||
@click.option(
|
||||
"--output_path",
|
||||
required=True,
|
||||
help="Path where to write output json file.",
|
||||
type=click.Path())
|
||||
def _get_config_file_rules_colorspace_from_filepath(
|
||||
config_path, filepath, output_path
|
||||
):
|
||||
"""Get colorspace from file path wrapper.
|
||||
|
||||
Python 2 wrapped console command
|
||||
|
||||
Args:
|
||||
config_path (str): config file path string
|
||||
filepath (str): path string leading to file
|
||||
out_path (str): temp json file path string
|
||||
output_path (str): temp json file path string
|
||||
|
||||
Example of use:
|
||||
> pyton.exe ./ocio_wrapper.py \
|
||||
> python.exe ./ocio_wrapper.py \
|
||||
colorspace get_config_file_rules_colorspace_from_filepath \
|
||||
--config_path=<path> --filepath=<path> --out_path=<path>
|
||||
--config_path <path> --filepath <path> --output_path <path>
|
||||
"""
|
||||
json_path = Path(out_path)
|
||||
|
||||
colorspace = _get_config_file_rules_colorspace_from_filepath(
|
||||
config_path, filepath)
|
||||
|
||||
with open(json_path, "w") as f_:
|
||||
json.dump(colorspace, f_)
|
||||
|
||||
print(f"Colorspace name is saved to '{json_path}'")
|
||||
_save_output_to_json_file(
|
||||
get_config_file_rules_colorspace_from_filepath(config_path, filepath),
|
||||
output_path
|
||||
)
|
||||
|
||||
|
||||
def _get_config_file_rules_colorspace_from_filepath(config_path, filepath):
|
||||
"""Return found colorspace data found in v2 file rules.
|
||||
|
||||
Args:
|
||||
config_path (str): path string leading to config.ocio
|
||||
filepath (str): path string leading to v2 file rules
|
||||
|
||||
Raises:
|
||||
IOError: Input config does not exist.
|
||||
|
||||
Returns:
|
||||
dict: aggregated available colorspaces
|
||||
"""
|
||||
config_path = Path(config_path)
|
||||
|
||||
if not config_path.is_file():
|
||||
raise IOError(
|
||||
f"Input path `{config_path}` should be `config.ocio` file")
|
||||
|
||||
config = ocio.Config().CreateFromFile(str(config_path))
|
||||
|
||||
# TODO: use `parseColorSpaceFromString` instead if ocio v1
|
||||
colorspace = config.getColorSpaceFromFilepath(str(filepath))
|
||||
|
||||
return colorspace
|
||||
|
||||
|
||||
def _get_display_view_colorspace_name(config_path, display, view):
|
||||
"""Returns the colorspace attribute of the (display, view) pair.
|
||||
|
||||
Args:
|
||||
config_path (str): path string leading to config.ocio
|
||||
display (str): display name e.g. "ACES"
|
||||
view (str): view name e.g. "sRGB"
|
||||
|
||||
|
||||
Raises:
|
||||
IOError: Input config does not exist.
|
||||
|
||||
Returns:
|
||||
view color space name (str) e.g. "Output - sRGB"
|
||||
"""
|
||||
|
||||
config_path = Path(config_path)
|
||||
|
||||
if not config_path.is_file():
|
||||
raise IOError("Input path should be `config.ocio` file")
|
||||
|
||||
config = ocio.Config.CreateFromFile(str(config_path))
|
||||
colorspace = config.getDisplayViewColorSpaceName(display, view)
|
||||
|
||||
return colorspace
|
||||
|
||||
|
||||
@config.command(
|
||||
@main.command(
|
||||
name="get_display_view_colorspace_name",
|
||||
help=(
|
||||
"return default view colorspace name "
|
||||
"for the given display and view "
|
||||
"--path input arg is required"
|
||||
)
|
||||
)
|
||||
@click.option("--in_path", required=True,
|
||||
help="path where to read ocio config file",
|
||||
type=click.Path(exists=True))
|
||||
@click.option("--out_path", required=True,
|
||||
help="path where to write output json file",
|
||||
type=click.Path())
|
||||
@click.option("--display", required=True,
|
||||
help="display name",
|
||||
type=click.STRING)
|
||||
@click.option("--view", required=True,
|
||||
help="view name",
|
||||
type=click.STRING)
|
||||
def get_display_view_colorspace_name(in_path, out_path,
|
||||
display, view):
|
||||
"Default view colorspace name for the given display and view"
|
||||
))
|
||||
@click.option(
|
||||
"--config_path",
|
||||
required=True,
|
||||
help="path where to read ocio config file",
|
||||
type=click.Path(exists=True))
|
||||
@click.option(
|
||||
"--display",
|
||||
required=True,
|
||||
help="Display name",
|
||||
type=click.STRING)
|
||||
@click.option(
|
||||
"--view",
|
||||
required=True,
|
||||
help="view name",
|
||||
type=click.STRING)
|
||||
@click.option(
|
||||
"--output_path",
|
||||
required=True,
|
||||
help="path where to write output json file",
|
||||
type=click.Path())
|
||||
def _get_display_view_colorspace_name(
|
||||
config_path, display, view, output_path
|
||||
):
|
||||
"""Aggregate view colorspace name to file.
|
||||
|
||||
Wrapper command for processes without access to OpenColorIO
|
||||
|
||||
Args:
|
||||
in_path (str): config file path string
|
||||
out_path (str): temp json file path string
|
||||
config_path (str): config file path string
|
||||
output_path (str): temp json file path string
|
||||
display (str): display name e.g. "ACES"
|
||||
view (str): view name e.g. "sRGB"
|
||||
|
||||
Example of use:
|
||||
> pyton.exe ./ocio_wrapper.py config \
|
||||
get_display_view_colorspace_name --in_path=<path> \
|
||||
--out_path=<path> --display=<display> --view=<view>
|
||||
get_display_view_colorspace_name --config_path <path> \
|
||||
--output_path <path> --display <display> --view <view>
|
||||
"""
|
||||
_save_output_to_json_file(
|
||||
get_display_view_colorspace_name(config_path, display, view),
|
||||
output_path
|
||||
)
|
||||
|
||||
out_data = _get_display_view_colorspace_name(in_path,
|
||||
display,
|
||||
view)
|
||||
|
||||
with open(out_path, "w") as f:
|
||||
json.dump(out_data, f)
|
||||
|
||||
print(f"Display view colorspace saved to '{out_path}'")
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
if not has_compatible_ocio_package():
|
||||
raise RuntimeError("OpenColorIO is not available.")
|
||||
main()
|
||||
|
|
|
|||
|
|
@ -290,6 +290,34 @@ class ActionDelegate(QtWidgets.QStyledItemDelegate):
|
|||
painter.drawPixmap(extender_x, extender_y, pix)
|
||||
|
||||
|
||||
class ActionsProxyModel(QtCore.QSortFilterProxyModel):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive)
|
||||
|
||||
def lessThan(self, left, right):
|
||||
# Sort by action order and then by label
|
||||
left_value = left.data(ACTION_SORT_ROLE)
|
||||
right_value = right.data(ACTION_SORT_ROLE)
|
||||
|
||||
# Values are same -> use super sorting
|
||||
if left_value == right_value:
|
||||
# Default behavior is using DisplayRole
|
||||
return super().lessThan(left, right)
|
||||
|
||||
# Validate 'None' values
|
||||
if right_value is None:
|
||||
return True
|
||||
if left_value is None:
|
||||
return False
|
||||
# Sort values and handle incompatible types
|
||||
try:
|
||||
return left_value < right_value
|
||||
except TypeError:
|
||||
return True
|
||||
|
||||
|
||||
class ActionsWidget(QtWidgets.QWidget):
|
||||
def __init__(self, controller, parent):
|
||||
super(ActionsWidget, self).__init__(parent)
|
||||
|
|
@ -316,10 +344,7 @@ class ActionsWidget(QtWidgets.QWidget):
|
|||
|
||||
model = ActionsQtModel(controller)
|
||||
|
||||
proxy_model = QtCore.QSortFilterProxyModel()
|
||||
proxy_model.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive)
|
||||
proxy_model.setSortRole(ACTION_SORT_ROLE)
|
||||
|
||||
proxy_model = ActionsProxyModel()
|
||||
proxy_model.setSourceModel(model)
|
||||
view.setModel(proxy_model)
|
||||
|
||||
|
|
@ -359,7 +384,8 @@ class ActionsWidget(QtWidgets.QWidget):
|
|||
def _on_model_refresh(self):
|
||||
self._proxy_model.sort(0)
|
||||
# Force repaint all items
|
||||
self._view.update()
|
||||
viewport = self._view.viewport()
|
||||
viewport.update()
|
||||
|
||||
def _on_animation(self):
|
||||
time_now = time.time()
|
||||
|
|
|
|||
|
|
@ -335,9 +335,7 @@ class LoaderWindow(QtWidgets.QWidget):
|
|||
|
||||
def closeEvent(self, event):
|
||||
super(LoaderWindow, self).closeEvent(event)
|
||||
# Deselect project so current context will be selected
|
||||
# on next 'showEvent'
|
||||
self._controller.set_selected_project(None)
|
||||
|
||||
self._reset_on_show = True
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
|
|
|
|||
|
|
@ -723,7 +723,6 @@ class ProjectPushItemProcess:
|
|||
dst_project_name = self._item.dst_project_name
|
||||
dst_folder_id = self._item.dst_folder_id
|
||||
dst_task_name = self._item.dst_task_name
|
||||
dst_task_name_low = dst_task_name.lower()
|
||||
new_folder_name = self._item.new_folder_name
|
||||
if not dst_folder_id and not new_folder_name:
|
||||
self._status.set_failed(
|
||||
|
|
@ -765,7 +764,7 @@ class ProjectPushItemProcess:
|
|||
dst_project_name, folder_ids=[folder_entity["id"]]
|
||||
)
|
||||
}
|
||||
task_info = folder_tasks.get(dst_task_name_low)
|
||||
task_info = folder_tasks.get(dst_task_name.lower())
|
||||
if not task_info:
|
||||
self._status.set_failed(
|
||||
f"Could find task with name \"{dst_task_name}\""
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ aiohttp_json_rpc = "*" # TVPaint server
|
|||
aiohttp-middlewares = "^2.0.0"
|
||||
wsrpc_aiohttp = "^3.1.1" # websocket server
|
||||
Click = "^8"
|
||||
OpenTimelineIO = "0.14.1"
|
||||
OpenTimelineIO = "0.16.0"
|
||||
opencolorio = "2.2.1"
|
||||
Pillow = "9.5.0"
|
||||
pynput = "^1.7.2" # Timers manager - TODO remove
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
from typing import Any
|
||||
|
||||
from ayon_server.addons import BaseServerAddon
|
||||
|
||||
from .settings import CoreSettings, DEFAULT_VALUES
|
||||
|
|
@ -9,3 +11,53 @@ class CoreAddon(BaseServerAddon):
|
|||
async def get_default_settings(self):
|
||||
settings_model_cls = self.get_settings_model()
|
||||
return settings_model_cls(**DEFAULT_VALUES)
|
||||
|
||||
async def convert_settings_overrides(
|
||||
self,
|
||||
source_version: str,
|
||||
overrides: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
self._convert_imagio_configs_0_3_1(overrides)
|
||||
# Use super conversion
|
||||
return await super().convert_settings_overrides(
|
||||
source_version, overrides
|
||||
)
|
||||
|
||||
def _convert_imagio_configs_0_3_1(self, overrides):
|
||||
"""Imageio config settings did change to profiles since 0.3.1. ."""
|
||||
imageio_overrides = overrides.get("imageio") or {}
|
||||
if (
|
||||
"ocio_config" not in imageio_overrides
|
||||
or "filepath" not in imageio_overrides["ocio_config"]
|
||||
):
|
||||
return
|
||||
|
||||
ocio_config = imageio_overrides.pop("ocio_config")
|
||||
|
||||
filepath = ocio_config["filepath"]
|
||||
if not filepath:
|
||||
return
|
||||
first_filepath = filepath[0]
|
||||
ocio_config_profiles = imageio_overrides.setdefault(
|
||||
"ocio_config_profiles", []
|
||||
)
|
||||
base_value = {
|
||||
"type": "builtin_path",
|
||||
"product_name": "",
|
||||
"host_names": [],
|
||||
"task_names": [],
|
||||
"task_types": [],
|
||||
"custom_path": "",
|
||||
"builtin_path": "{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio"
|
||||
}
|
||||
if first_filepath in (
|
||||
"{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio",
|
||||
"{BUILTIN_OCIO_ROOT}/nuke-default/config.ocio",
|
||||
):
|
||||
base_value["type"] = "builtin_path"
|
||||
base_value["builtin_path"] = first_filepath
|
||||
else:
|
||||
base_value["type"] = "custom_path"
|
||||
base_value["custom_path"] = first_filepath
|
||||
|
||||
ocio_config_profiles.append(base_value)
|
||||
|
|
|
|||
|
|
@ -54,9 +54,67 @@ class CoreImageIOFileRulesModel(BaseSettingsModel):
|
|||
return value
|
||||
|
||||
|
||||
class CoreImageIOConfigModel(BaseSettingsModel):
|
||||
filepath: list[str] = SettingsField(
|
||||
default_factory=list, title="Config path"
|
||||
def _ocio_config_profile_types():
|
||||
return [
|
||||
{"value": "builtin_path", "label": "AYON built-in OCIO config"},
|
||||
{"value": "custom_path", "label": "Path to OCIO config"},
|
||||
{"value": "product_name", "label": "Published product"},
|
||||
]
|
||||
|
||||
|
||||
def _ocio_built_in_paths():
|
||||
return [
|
||||
{
|
||||
"value": "{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio",
|
||||
"label": "ACES 1.2",
|
||||
"description": "Aces 1.2 OCIO config file."
|
||||
},
|
||||
{
|
||||
"value": "{BUILTIN_OCIO_ROOT}/nuke-default/config.ocio",
|
||||
"label": "Nuke default",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
class CoreImageIOConfigProfilesModel(BaseSettingsModel):
|
||||
_layout = "expanded"
|
||||
host_names: list[str] = SettingsField(
|
||||
default_factory=list,
|
||||
title="Host names"
|
||||
)
|
||||
task_types: list[str] = SettingsField(
|
||||
default_factory=list,
|
||||
title="Task types",
|
||||
enum_resolver=task_types_enum
|
||||
)
|
||||
task_names: list[str] = SettingsField(
|
||||
default_factory=list,
|
||||
title="Task names"
|
||||
)
|
||||
type: str = SettingsField(
|
||||
title="Profile type",
|
||||
enum_resolver=_ocio_config_profile_types,
|
||||
conditionalEnum=True,
|
||||
default="builtin_path",
|
||||
section="---",
|
||||
)
|
||||
builtin_path: str = SettingsField(
|
||||
"ACES 1.2",
|
||||
title="Built-in OCIO config",
|
||||
enum_resolver=_ocio_built_in_paths,
|
||||
)
|
||||
custom_path: str = SettingsField(
|
||||
"",
|
||||
title="OCIO config path",
|
||||
description="Path to OCIO config. Anatomy formatting is supported.",
|
||||
)
|
||||
product_name: str = SettingsField(
|
||||
"",
|
||||
title="Product name",
|
||||
description=(
|
||||
"Published product name to get OCIO config from. "
|
||||
"Partial match is supported."
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -65,9 +123,8 @@ class CoreImageIOBaseModel(BaseSettingsModel):
|
|||
False,
|
||||
title="Enable Color Management"
|
||||
)
|
||||
ocio_config: CoreImageIOConfigModel = SettingsField(
|
||||
default_factory=CoreImageIOConfigModel,
|
||||
title="OCIO config"
|
||||
ocio_config_profiles: list[CoreImageIOConfigProfilesModel] = SettingsField(
|
||||
default_factory=list, title="OCIO config profiles"
|
||||
)
|
||||
file_rules: CoreImageIOFileRulesModel = SettingsField(
|
||||
default_factory=CoreImageIOFileRulesModel,
|
||||
|
|
@ -186,12 +243,17 @@ class CoreSettings(BaseSettingsModel):
|
|||
DEFAULT_VALUES = {
|
||||
"imageio": {
|
||||
"activate_global_color_management": False,
|
||||
"ocio_config": {
|
||||
"filepath": [
|
||||
"{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio",
|
||||
"{BUILTIN_OCIO_ROOT}/nuke-default/config.ocio"
|
||||
]
|
||||
},
|
||||
"ocio_config_profiles": [
|
||||
{
|
||||
"host_names": [],
|
||||
"task_types": [],
|
||||
"task_names": [],
|
||||
"type": "builtin_path",
|
||||
"builtin_path": "{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio",
|
||||
"custom_path": "",
|
||||
"product_name": "",
|
||||
}
|
||||
],
|
||||
"file_rules": {
|
||||
"activate_global_file_rules": False,
|
||||
"rules": [
|
||||
|
|
@ -199,42 +261,57 @@ DEFAULT_VALUES = {
|
|||
"name": "example",
|
||||
"pattern": ".*(beauty).*",
|
||||
"colorspace": "ACES - ACEScg",
|
||||
"ext": "exr"
|
||||
"ext": "exr",
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
},
|
||||
},
|
||||
"studio_name": "",
|
||||
"studio_code": "",
|
||||
"environments": "{\n\"STUDIO_SW\": {\n \"darwin\": \"/mnt/REPO_SW\",\n \"linux\": \"/mnt/REPO_SW\",\n \"windows\": \"P:/REPO_SW\"\n }\n}",
|
||||
"environments": json.dumps(
|
||||
{
|
||||
"STUDIO_SW": {
|
||||
"darwin": "/mnt/REPO_SW",
|
||||
"linux": "/mnt/REPO_SW",
|
||||
"windows": "P:/REPO_SW"
|
||||
}
|
||||
},
|
||||
indent=4
|
||||
),
|
||||
"tools": DEFAULT_TOOLS_VALUES,
|
||||
"version_start_category": {
|
||||
"profiles": []
|
||||
},
|
||||
"publish": DEFAULT_PUBLISH_VALUES,
|
||||
"project_folder_structure": json.dumps({
|
||||
"__project_root__": {
|
||||
"prod": {},
|
||||
"resources": {
|
||||
"footage": {
|
||||
"plates": {},
|
||||
"offline": {}
|
||||
"project_folder_structure": json.dumps(
|
||||
{
|
||||
"__project_root__": {
|
||||
"prod": {},
|
||||
"resources": {
|
||||
"footage": {
|
||||
"plates": {},
|
||||
"offline": {}
|
||||
},
|
||||
"audio": {},
|
||||
"art_dept": {}
|
||||
},
|
||||
"audio": {},
|
||||
"art_dept": {}
|
||||
},
|
||||
"editorial": {},
|
||||
"assets": {
|
||||
"characters": {},
|
||||
"locations": {}
|
||||
},
|
||||
"shots": {}
|
||||
}
|
||||
}, indent=4),
|
||||
"editorial": {},
|
||||
"assets": {
|
||||
"characters": {},
|
||||
"locations": {}
|
||||
},
|
||||
"shots": {}
|
||||
}
|
||||
},
|
||||
indent=4
|
||||
),
|
||||
"project_plugins": {
|
||||
"windows": [],
|
||||
"darwin": [],
|
||||
"linux": []
|
||||
},
|
||||
"project_environments": "{}"
|
||||
"project_environments": json.dumps(
|
||||
{},
|
||||
indent=4
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,6 +59,33 @@ class CollectFramesFixDefModel(BaseSettingsModel):
|
|||
)
|
||||
|
||||
|
||||
class ValidateOutdatedContainersProfile(BaseSettingsModel):
|
||||
_layout = "expanded"
|
||||
# Filtering
|
||||
host_names: list[str] = SettingsField(
|
||||
default_factory=list,
|
||||
title="Host names"
|
||||
)
|
||||
# Profile values
|
||||
enabled: bool = SettingsField(True, title="Enabled")
|
||||
optional: bool = SettingsField(True, title="Optional")
|
||||
active: bool = SettingsField(True, title="Active")
|
||||
|
||||
|
||||
class ValidateOutdatedContainersModel(BaseSettingsModel):
|
||||
"""Validate if Publishing intent was selected.
|
||||
|
||||
It is possible to disable validation for specific publishing context
|
||||
with profiles.
|
||||
"""
|
||||
|
||||
_isGroup = True
|
||||
plugin_state_profiles: list[ValidateOutdatedContainersProfile] = SettingsField(
|
||||
default_factory=list,
|
||||
title="Plugin enable state profiles",
|
||||
)
|
||||
|
||||
|
||||
class ValidateIntentProfile(BaseSettingsModel):
|
||||
_layout = "expanded"
|
||||
hosts: list[str] = SettingsField(default_factory=list, title="Host names")
|
||||
|
|
@ -770,6 +797,10 @@ class PublishPuginsModel(BaseSettingsModel):
|
|||
default_factory=ValidateBaseModel,
|
||||
title="Validate Version"
|
||||
)
|
||||
ValidateOutdatedContainers: ValidateOutdatedContainersModel = SettingsField(
|
||||
default_factory=ValidateOutdatedContainersModel,
|
||||
title="Validate Containers"
|
||||
)
|
||||
ValidateIntent: ValidateIntentModel = SettingsField(
|
||||
default_factory=ValidateIntentModel,
|
||||
title="Validate Intent"
|
||||
|
|
@ -855,6 +886,25 @@ DEFAULT_PUBLISH_VALUES = {
|
|||
"optional": False,
|
||||
"active": True
|
||||
},
|
||||
"ValidateOutdatedContainers": {
|
||||
"plugin_state_profiles": [
|
||||
{
|
||||
# Default host names are based on original
|
||||
# filter of ValidateContainer pyblish plugin
|
||||
"host_names": [
|
||||
"maya",
|
||||
"houdini",
|
||||
"nuke",
|
||||
"harmony",
|
||||
"photoshop",
|
||||
"aftereffects"
|
||||
],
|
||||
"enabled": True,
|
||||
"optional": True,
|
||||
"active": True
|
||||
}
|
||||
]
|
||||
},
|
||||
"ValidateIntent": {
|
||||
"enabled": False,
|
||||
"profiles": []
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
name = "aftereffects"
|
||||
title = "AfterEffects"
|
||||
version = "0.1.3"
|
||||
version = "0.1.4"
|
||||
|
|
|
|||
|
|
@ -22,12 +22,6 @@ class ValidateSceneSettingsModel(BaseSettingsModel):
|
|||
)
|
||||
|
||||
|
||||
class ValidateContainersModel(BaseSettingsModel):
|
||||
enabled: bool = SettingsField(True, title="Enabled")
|
||||
optional: bool = SettingsField(True, title="Optional")
|
||||
active: bool = SettingsField(True, title="Active")
|
||||
|
||||
|
||||
class AfterEffectsPublishPlugins(BaseSettingsModel):
|
||||
CollectReview: CollectReviewPluginModel = SettingsField(
|
||||
default_factory=CollectReviewPluginModel,
|
||||
|
|
@ -37,10 +31,6 @@ class AfterEffectsPublishPlugins(BaseSettingsModel):
|
|||
default_factory=ValidateSceneSettingsModel,
|
||||
title="Validate Scene Settings",
|
||||
)
|
||||
ValidateContainers: ValidateContainersModel = SettingsField(
|
||||
default_factory=ValidateContainersModel,
|
||||
title="Validate Containers",
|
||||
)
|
||||
|
||||
|
||||
AE_PUBLISH_PLUGINS_DEFAULTS = {
|
||||
|
|
@ -58,9 +48,4 @@ AE_PUBLISH_PLUGINS_DEFAULTS = {
|
|||
".*"
|
||||
]
|
||||
},
|
||||
"ValidateContainers": {
|
||||
"enabled": True,
|
||||
"optional": True,
|
||||
"active": True,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ plugin_for = ["ayon_server"]
|
|||
"""
|
||||
|
||||
CLIENT_VERSION_CONTENT = '''# -*- coding: utf-8 -*-
|
||||
"""Package declaring AYON core addon version."""
|
||||
"""Package declaring AYON addon '{}' version."""
|
||||
__version__ = "{}"
|
||||
'''
|
||||
|
||||
|
|
@ -183,6 +183,7 @@ def create_addon_zip(
|
|||
|
||||
|
||||
def prepare_client_code(
|
||||
addon_name: str,
|
||||
addon_dir: Path,
|
||||
addon_output_dir: Path,
|
||||
addon_version: str
|
||||
|
|
@ -211,7 +212,9 @@ def prepare_client_code(
|
|||
version_path = subpath / "version.py"
|
||||
if version_path.exists():
|
||||
with open(version_path, "w") as stream:
|
||||
stream.write(CLIENT_VERSION_CONTENT.format(addon_version))
|
||||
stream.write(
|
||||
CLIENT_VERSION_CONTENT.format(addon_name, addon_version)
|
||||
)
|
||||
|
||||
zip_filepath = private_dir / "client.zip"
|
||||
with ZipFileLongPaths(zip_filepath, "w", zipfile.ZIP_DEFLATED) as zipf:
|
||||
|
|
@ -262,7 +265,9 @@ def create_addon_package(
|
|||
server_dir, addon_output_dir / "server", dirs_exist_ok=True
|
||||
)
|
||||
|
||||
prepare_client_code(addon_dir, addon_output_dir, addon_version)
|
||||
prepare_client_code(
|
||||
package.name, addon_dir, addon_output_dir, addon_version
|
||||
)
|
||||
|
||||
if create_zip:
|
||||
create_addon_zip(
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
name = "deadline"
|
||||
title = "Deadline"
|
||||
version = "0.1.11"
|
||||
version = "0.1.12"
|
||||
|
|
|
|||
|
|
@ -38,10 +38,9 @@ class ServerItemSubmodel(BaseSettingsModel):
|
|||
name: str = SettingsField(title="Name")
|
||||
value: str = SettingsField(title="Url")
|
||||
require_authentication: bool = SettingsField(
|
||||
False,
|
||||
title="Require authentication")
|
||||
ssl: bool = SettingsField(False,
|
||||
title="SSL")
|
||||
False, title="Require authentication")
|
||||
not_verify_ssl: bool = SettingsField(
|
||||
False, title="Don't verify SSL")
|
||||
|
||||
|
||||
class DeadlineSettings(BaseSettingsModel):
|
||||
|
|
@ -78,7 +77,7 @@ DEFAULT_VALUES = {
|
|||
"name": "default",
|
||||
"value": "http://127.0.0.1:8082",
|
||||
"require_authentication": False,
|
||||
"ssl": False
|
||||
"not_verify_ssl": False
|
||||
}
|
||||
],
|
||||
"deadline_server": "default",
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
name = "harmony"
|
||||
title = "Harmony"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
|
|
|
|||
|
|
@ -45,11 +45,6 @@ DEFAULT_HARMONY_SETTING = {
|
|||
"optional": True,
|
||||
"active": True
|
||||
},
|
||||
"ValidateContainers": {
|
||||
"enabled": True,
|
||||
"optional": True,
|
||||
"active": True
|
||||
},
|
||||
"ValidateSceneSettings": {
|
||||
"enabled": True,
|
||||
"optional": True,
|
||||
|
|
|
|||
|
|
@ -18,14 +18,6 @@ class ValidateAudioPlugin(BaseSettingsModel):
|
|||
active: bool = SettingsField(True, title="Active")
|
||||
|
||||
|
||||
class ValidateContainersPlugin(BaseSettingsModel):
|
||||
"""Check if loaded container is scene are latest versions."""
|
||||
_isGroup = True
|
||||
enabled: bool = True
|
||||
optional: bool = SettingsField(False, title="Optional")
|
||||
active: bool = SettingsField(True, title="Active")
|
||||
|
||||
|
||||
class ValidateSceneSettingsPlugin(BaseSettingsModel):
|
||||
"""Validate if FrameStart, FrameEnd and Resolution match shot data in DB.
|
||||
Use regular expressions to limit validations only on particular asset
|
||||
|
|
@ -63,11 +55,6 @@ class HarmonyPublishPlugins(BaseSettingsModel):
|
|||
default_factory=ValidateAudioPlugin,
|
||||
)
|
||||
|
||||
ValidateContainers: ValidateContainersPlugin = SettingsField(
|
||||
title="Validate Containers",
|
||||
default_factory=ValidateContainersPlugin,
|
||||
)
|
||||
|
||||
ValidateSceneSettings: ValidateSceneSettingsPlugin = SettingsField(
|
||||
title="Validate Scene Settings",
|
||||
default_factory=ValidateSceneSettingsPlugin,
|
||||
|
|
|
|||
|
|
@ -77,10 +77,6 @@ class PublishPluginsModel(BaseSettingsModel):
|
|||
default_factory=CollectLocalRenderInstancesModel,
|
||||
title="Collect Local Render Instances."
|
||||
)
|
||||
ValidateContainers: BasicValidateModel = SettingsField(
|
||||
default_factory=BasicValidateModel,
|
||||
title="Validate Latest Containers.",
|
||||
section="Validators")
|
||||
ValidateInstanceInContextHoudini: BasicValidateModel = SettingsField(
|
||||
default_factory=BasicValidateModel,
|
||||
title="Validate Instance is in same Context.")
|
||||
|
|
@ -119,11 +115,6 @@ DEFAULT_HOUDINI_PUBLISH_SETTINGS = {
|
|||
]
|
||||
}
|
||||
},
|
||||
"ValidateContainers": {
|
||||
"enabled": True,
|
||||
"optional": True,
|
||||
"active": True
|
||||
},
|
||||
"ValidateInstanceInContextHoudini": {
|
||||
"enabled": True,
|
||||
"optional": True,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
name = "maya"
|
||||
title = "Maya"
|
||||
version = "0.1.18"
|
||||
version = "0.1.20"
|
||||
|
|
|
|||
|
|
@ -634,10 +634,6 @@ class PublishersModel(BaseSettingsModel):
|
|||
title="Validate Instance In Context",
|
||||
section="Validators"
|
||||
)
|
||||
ValidateContainers: BasicValidateModel = SettingsField(
|
||||
default_factory=BasicValidateModel,
|
||||
title="Validate Containers"
|
||||
)
|
||||
ValidateFrameRange: ValidateFrameRangeModel = SettingsField(
|
||||
default_factory=ValidateFrameRangeModel,
|
||||
title="Validate Frame Range"
|
||||
|
|
@ -917,10 +913,6 @@ class PublishersModel(BaseSettingsModel):
|
|||
default_factory=BasicValidateModel,
|
||||
title="Validate Rig Controllers",
|
||||
)
|
||||
ValidateAnimatedReferenceRig: BasicValidateModel = SettingsField(
|
||||
default_factory=BasicValidateModel,
|
||||
title="Validate Animated Reference Rig",
|
||||
)
|
||||
ValidateAnimationContent: BasicValidateModel = SettingsField(
|
||||
default_factory=BasicValidateModel,
|
||||
title="Validate Animation Content",
|
||||
|
|
@ -1063,11 +1055,6 @@ DEFAULT_PUBLISH_SETTINGS = {
|
|||
"optional": True,
|
||||
"active": True
|
||||
},
|
||||
"ValidateContainers": {
|
||||
"enabled": True,
|
||||
"optional": True,
|
||||
"active": True
|
||||
},
|
||||
"ValidateFrameRange": {
|
||||
"enabled": True,
|
||||
"optional": True,
|
||||
|
|
@ -1447,11 +1434,6 @@ DEFAULT_PUBLISH_SETTINGS = {
|
|||
"optional": True,
|
||||
"active": True
|
||||
},
|
||||
"ValidateAnimatedReferenceRig": {
|
||||
"enabled": True,
|
||||
"optional": False,
|
||||
"active": True
|
||||
},
|
||||
"ValidateAnimationContent": {
|
||||
"enabled": True,
|
||||
"optional": False,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
name = "nuke"
|
||||
title = "Nuke"
|
||||
version = "0.1.11"
|
||||
version = "0.1.13"
|
||||
|
|
|
|||
|
|
@ -125,6 +125,7 @@ class ReformatNodesConfigModel(BaseSettingsModel):
|
|||
|
||||
class IntermediateOutputModel(BaseSettingsModel):
|
||||
name: str = SettingsField(title="Output name")
|
||||
publish: bool = SettingsField(title="Publish")
|
||||
filter: BakingStreamFilterModel = SettingsField(
|
||||
title="Filter", default_factory=BakingStreamFilterModel)
|
||||
read_raw: bool = SettingsField(
|
||||
|
|
@ -230,10 +231,6 @@ class PublishPluginsModel(BaseSettingsModel):
|
|||
default_factory=OptionalPluginModel,
|
||||
section="Validators"
|
||||
)
|
||||
ValidateContainers: OptionalPluginModel = SettingsField(
|
||||
title="Validate Containers",
|
||||
default_factory=OptionalPluginModel
|
||||
)
|
||||
ValidateKnobs: ValidateKnobsModel = SettingsField(
|
||||
title="Validate Knobs",
|
||||
default_factory=ValidateKnobsModel
|
||||
|
|
@ -299,11 +296,6 @@ DEFAULT_PUBLISH_PLUGIN_SETTINGS = {
|
|||
"optional": True,
|
||||
"active": True
|
||||
},
|
||||
"ValidateContainers": {
|
||||
"enabled": True,
|
||||
"optional": True,
|
||||
"active": True
|
||||
},
|
||||
"ValidateKnobs": {
|
||||
"enabled": False,
|
||||
"knobs": "\n".join([
|
||||
|
|
@ -346,6 +338,7 @@ DEFAULT_PUBLISH_PLUGIN_SETTINGS = {
|
|||
"outputs": [
|
||||
{
|
||||
"name": "baking",
|
||||
"publish": False,
|
||||
"filter": {
|
||||
"task_types": [],
|
||||
"product_types": [],
|
||||
|
|
@ -401,6 +394,7 @@ DEFAULT_PUBLISH_PLUGIN_SETTINGS = {
|
|||
"outputs": [
|
||||
{
|
||||
"name": "baking",
|
||||
"publish": False,
|
||||
"filter": {
|
||||
"task_types": [],
|
||||
"product_types": [],
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
name = "photoshop"
|
||||
title = "Photoshop"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
|
|
|
|||
|
|
@ -83,14 +83,6 @@ class CollectVersionPlugin(BaseSettingsModel):
|
|||
enabled: bool = SettingsField(True, title="Enabled")
|
||||
|
||||
|
||||
class ValidateContainersPlugin(BaseSettingsModel):
|
||||
"""Check that workfile contains latest version of loaded items""" # noqa
|
||||
_isGroup = True
|
||||
enabled: bool = True
|
||||
optional: bool = SettingsField(False, title="Optional")
|
||||
active: bool = SettingsField(True, title="Active")
|
||||
|
||||
|
||||
class ValidateNamingPlugin(BaseSettingsModel):
|
||||
"""Validate naming of products and layers""" # noqa
|
||||
invalid_chars: str = SettingsField(
|
||||
|
|
@ -154,11 +146,6 @@ class PhotoshopPublishPlugins(BaseSettingsModel):
|
|||
default_factory=CollectVersionPlugin,
|
||||
)
|
||||
|
||||
ValidateContainers: ValidateContainersPlugin = SettingsField(
|
||||
title="Validate Containers",
|
||||
default_factory=ValidateContainersPlugin,
|
||||
)
|
||||
|
||||
ValidateNaming: ValidateNamingPlugin = SettingsField(
|
||||
title="Validate naming of products and layers",
|
||||
default_factory=ValidateNamingPlugin,
|
||||
|
|
@ -187,11 +174,6 @@ DEFAULT_PUBLISH_SETTINGS = {
|
|||
"CollectVersion": {
|
||||
"enabled": False
|
||||
},
|
||||
"ValidateContainers": {
|
||||
"enabled": True,
|
||||
"optional": True,
|
||||
"active": True
|
||||
},
|
||||
"ValidateNaming": {
|
||||
"invalid_chars": "[ \\\\/+\\*\\?\\(\\)\\[\\]\\{\\}:,;]",
|
||||
"replace_char": "_"
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
name = "traypublisher"
|
||||
title = "TrayPublisher"
|
||||
version = "0.1.4"
|
||||
version = "0.1.5"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
from ayon_server.settings import BaseSettingsModel, SettingsField
|
||||
from ayon_server.settings import (
|
||||
BaseSettingsModel,
|
||||
SettingsField,
|
||||
)
|
||||
|
||||
|
||||
class ValidatePluginModel(BaseSettingsModel):
|
||||
|
|
@ -14,6 +17,45 @@ class ValidateFrameRangeModel(ValidatePluginModel):
|
|||
'my_asset_to_publish.mov')"""
|
||||
|
||||
|
||||
class ExtractEditorialPckgFFmpegModel(BaseSettingsModel):
|
||||
video_filters: list[str] = SettingsField(
|
||||
default_factory=list,
|
||||
title="Video filters"
|
||||
)
|
||||
audio_filters: list[str] = SettingsField(
|
||||
default_factory=list,
|
||||
title="Audio filters"
|
||||
)
|
||||
input: list[str] = SettingsField(
|
||||
default_factory=list,
|
||||
title="Input arguments"
|
||||
)
|
||||
output: list[str] = SettingsField(
|
||||
default_factory=list,
|
||||
title="Output arguments"
|
||||
)
|
||||
|
||||
|
||||
class ExtractEditorialPckgOutputDefModel(BaseSettingsModel):
|
||||
_layout = "expanded"
|
||||
ext: str = SettingsField("", title="Output extension")
|
||||
|
||||
ffmpeg_args: ExtractEditorialPckgFFmpegModel = SettingsField(
|
||||
default_factory=ExtractEditorialPckgFFmpegModel,
|
||||
title="FFmpeg arguments"
|
||||
)
|
||||
|
||||
|
||||
class ExtractEditorialPckgConversionModel(BaseSettingsModel):
|
||||
"""Set output definition if resource files should be converted."""
|
||||
conversion_enabled: bool = SettingsField(True,
|
||||
title="Conversion enabled")
|
||||
output: ExtractEditorialPckgOutputDefModel = SettingsField(
|
||||
default_factory=ExtractEditorialPckgOutputDefModel,
|
||||
title="Output Definitions",
|
||||
)
|
||||
|
||||
|
||||
class TrayPublisherPublishPlugins(BaseSettingsModel):
|
||||
CollectFrameDataFromAssetEntity: ValidatePluginModel = SettingsField(
|
||||
default_factory=ValidatePluginModel,
|
||||
|
|
@ -28,6 +70,13 @@ class TrayPublisherPublishPlugins(BaseSettingsModel):
|
|||
default_factory=ValidatePluginModel,
|
||||
)
|
||||
|
||||
ExtractEditorialPckgConversion: ExtractEditorialPckgConversionModel = (
|
||||
SettingsField(
|
||||
default_factory=ExtractEditorialPckgConversionModel,
|
||||
title="Extract Editorial Package Conversion"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
DEFAULT_PUBLISH_PLUGINS = {
|
||||
"CollectFrameDataFromAssetEntity": {
|
||||
|
|
@ -44,5 +93,24 @@ DEFAULT_PUBLISH_PLUGINS = {
|
|||
"enabled": True,
|
||||
"optional": True,
|
||||
"active": True
|
||||
},
|
||||
"ExtractEditorialPckgConversion": {
|
||||
"optional": False,
|
||||
"conversion_enabled": True,
|
||||
"output": {
|
||||
"ext": "",
|
||||
"ffmpeg_args": {
|
||||
"video_filters": [],
|
||||
"audio_filters": [],
|
||||
"input": [
|
||||
"-apply_trc gamma22"
|
||||
],
|
||||
"output": [
|
||||
"-pix_fmt yuv420p",
|
||||
"-crf 18",
|
||||
"-intra"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue