diff --git a/client/ayon_core/addon/base.py b/client/ayon_core/addon/base.py
index 21b1193b07..939fab68b8 100644
--- a/client/ayon_core/addon/base.py
+++ b/client/ayon_core/addon/base.py
@@ -51,8 +51,13 @@ IGNORED_MODULES_IN_AYON = set()
# - this is used to log the missing addon
MOVED_ADDON_MILESTONE_VERSIONS = {
"applications": VersionInfo(0, 2, 0),
+ "clockify": VersionInfo(0, 2, 0),
+ "traypublisher": VersionInfo(0, 2, 0),
+ "tvpaint": VersionInfo(0, 2, 0),
+ "nuke": VersionInfo(0, 2, 0),
}
+
# Inherit from `object` for Python 2 hosts
class _ModuleClass(object):
"""Fake module class for storing AYON addons.
diff --git a/client/ayon_core/hooks/pre_ocio_hook.py b/client/ayon_core/hooks/pre_ocio_hook.py
index 0817afec71..6c30b267bc 100644
--- a/client/ayon_core/hooks/pre_ocio_hook.py
+++ b/client/ayon_core/hooks/pre_ocio_hook.py
@@ -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
diff --git a/client/ayon_core/hosts/aftereffects/api/launch_logic.py b/client/ayon_core/hosts/aftereffects/api/launch_logic.py
index 5a23f2cb35..da6887668a 100644
--- a/client/ayon_core/hosts/aftereffects/api/launch_logic.py
+++ b/client/ayon_core/hosts/aftereffects/api/launch_logic.py
@@ -60,7 +60,7 @@ def main(*subprocess_args):
)
)
- elif os.environ.get("AVALON_PHOTOSHOP_WORKFILES_ON_LAUNCH", True):
+ elif os.environ.get("AVALON_AFTEREFFECTS_WORKFILES_ON_LAUNCH", True):
save = False
if os.getenv("WORKFILES_SAVE_AS"):
save = True
diff --git a/client/ayon_core/hosts/blender/api/lib.py b/client/ayon_core/hosts/blender/api/lib.py
index 32137f0fcd..ea37f17d05 100644
--- a/client/ayon_core/hosts/blender/api/lib.py
+++ b/client/ayon_core/hosts/blender/api/lib.py
@@ -365,3 +365,62 @@ def maintained_time():
yield
finally:
bpy.context.scene.frame_current = current_time
+
+
+def get_all_parents(obj):
+ """Get all recursive parents of object.
+
+ Arguments:
+ obj (bpy.types.Object): Object to get all parents for.
+
+ Returns:
+ List[bpy.types.Object]: All parents of object
+
+ """
+ result = []
+ while True:
+ obj = obj.parent
+ if not obj:
+ break
+ result.append(obj)
+ return result
+
+
+def get_highest_root(objects):
+ """Get the highest object (the least parents) among the objects.
+
+ If multiple objects have the same amount of parents (or no parents) the
+ first object found in the input iterable will be returned.
+
+ Note that this will *not* return objects outside of the input list, as
+ such it will not return the root of node from a child node. It is purely
+ intended to find the highest object among a list of objects. To instead
+ get the root from one object use, e.g. `get_all_parents(obj)[-1]`
+
+ Arguments:
+ objects (List[bpy.types.Object]): Objects to find the highest root in.
+
+ Returns:
+ Optional[bpy.types.Object]: First highest root found or None if no
+ `bpy.types.Object` found in input list.
+
+ """
+ included_objects = {obj.name_full for obj in objects}
+ num_parents_to_obj = {}
+ for obj in objects:
+ if isinstance(obj, bpy.types.Object):
+ parents = get_all_parents(obj)
+ # included parents
+ parents = [parent for parent in parents if
+ parent.name_full in included_objects]
+ if not parents:
+ # A node without parents must be a highest root
+ return obj
+
+ num_parents_to_obj.setdefault(len(parents), obj)
+
+ if not num_parents_to_obj:
+ return
+
+ minimum_parent = min(num_parents_to_obj)
+ return num_parents_to_obj[minimum_parent]
diff --git a/client/ayon_core/hosts/blender/api/plugin.py b/client/ayon_core/hosts/blender/api/plugin.py
index 4a13d16805..e00e127f70 100644
--- a/client/ayon_core/hosts/blender/api/plugin.py
+++ b/client/ayon_core/hosts/blender/api/plugin.py
@@ -26,7 +26,8 @@ from .ops import (
)
from .lib import imprint
-VALID_EXTENSIONS = [".blend", ".json", ".abc", ".fbx"]
+VALID_EXTENSIONS = [".blend", ".json", ".abc", ".fbx",
+ ".usd", ".usdc", ".usda"]
def prepare_scene_name(
diff --git a/client/ayon_core/hosts/blender/plugins/create/create_usd.py b/client/ayon_core/hosts/blender/plugins/create/create_usd.py
new file mode 100644
index 0000000000..2c2d0c46c6
--- /dev/null
+++ b/client/ayon_core/hosts/blender/plugins/create/create_usd.py
@@ -0,0 +1,30 @@
+"""Create a USD Export."""
+
+from ayon_core.hosts.blender.api import plugin, lib
+
+
+class CreateUSD(plugin.BaseCreator):
+ """Create USD Export"""
+
+ identifier = "io.openpype.creators.blender.usd"
+ name = "usdMain"
+ label = "USD"
+ product_type = "usd"
+ icon = "gears"
+
+ def create(
+ self, product_name: str, instance_data: dict, pre_create_data: dict
+ ):
+ # Run parent create method
+ collection = super().create(
+ product_name, instance_data, pre_create_data
+ )
+
+ if pre_create_data.get("use_selection"):
+ objects = lib.get_selection()
+ for obj in objects:
+ collection.objects.link(obj)
+ if obj.type == 'EMPTY':
+ objects.extend(obj.children)
+
+ return collection
diff --git a/client/ayon_core/hosts/blender/plugins/load/load_abc.py b/client/ayon_core/hosts/blender/plugins/load/load_cache.py
similarity index 94%
rename from client/ayon_core/hosts/blender/plugins/load/load_abc.py
rename to client/ayon_core/hosts/blender/plugins/load/load_cache.py
index c074b5ed13..30c847f89d 100644
--- a/client/ayon_core/hosts/blender/plugins/load/load_abc.py
+++ b/client/ayon_core/hosts/blender/plugins/load/load_cache.py
@@ -26,10 +26,10 @@ class CacheModelLoader(plugin.AssetLoader):
Note:
At least for now it only supports Alembic files.
"""
- product_types = {"model", "pointcache", "animation"}
- representations = {"abc"}
+ product_types = {"model", "pointcache", "animation", "usd"}
+ representations = {"abc", "usd"}
- label = "Load Alembic"
+ label = "Load Cache"
icon = "code-fork"
color = "orange"
@@ -53,10 +53,21 @@ class CacheModelLoader(plugin.AssetLoader):
plugin.deselect_all()
relative = bpy.context.preferences.filepaths.use_relative_paths
- bpy.ops.wm.alembic_import(
- filepath=libpath,
- relative_path=relative
- )
+
+ if any(libpath.lower().endswith(ext)
+ for ext in [".usd", ".usda", ".usdc"]):
+ # USD
+ bpy.ops.wm.usd_import(
+ filepath=libpath,
+ relative_path=relative
+ )
+
+ else:
+ # Alembic
+ bpy.ops.wm.alembic_import(
+ filepath=libpath,
+ relative_path=relative
+ )
imported = lib.get_selection()
diff --git a/client/ayon_core/hosts/blender/plugins/load/load_camera_abc.py b/client/ayon_core/hosts/blender/plugins/load/load_camera_abc.py
index 6178578081..a49bb40d9a 100644
--- a/client/ayon_core/hosts/blender/plugins/load/load_camera_abc.py
+++ b/client/ayon_core/hosts/blender/plugins/load/load_camera_abc.py
@@ -43,7 +43,10 @@ class AbcCameraLoader(plugin.AssetLoader):
def _process(self, libpath, asset_group, group_name):
plugin.deselect_all()
- bpy.ops.wm.alembic_import(filepath=libpath)
+ # Force the creation of the transform cache even if the camera
+ # doesn't have an animation. We use the cache to update the camera.
+ bpy.ops.wm.alembic_import(
+ filepath=libpath, always_add_cache_reader=True)
objects = lib.get_selection()
@@ -178,12 +181,33 @@ class AbcCameraLoader(plugin.AssetLoader):
self.log.info("Library already loaded, not updating...")
return
- mat = asset_group.matrix_basis.copy()
+ for obj in asset_group.children:
+ found = False
+ for constraint in obj.constraints:
+ if constraint.type == "TRANSFORM_CACHE":
+ constraint.cache_file.filepath = libpath.as_posix()
+ found = True
+ break
+ if not found:
+ # This is to keep compatibility with cameras loaded with
+ # the old loader
+ # Create a new constraint for the cache file
+ constraint = obj.constraints.new("TRANSFORM_CACHE")
+ bpy.ops.cachefile.open(filepath=libpath.as_posix())
+ constraint.cache_file = bpy.data.cache_files[-1]
+ constraint.cache_file.scale = 1.0
- self._remove(asset_group)
- self._process(str(libpath), asset_group, object_name)
+ # This is a workaround to set the object path. Blender doesn't
+ # load the list of object paths until the object is evaluated.
+ # This is a hack to force the object to be evaluated.
+ # The modifier doesn't need to be removed because camera
+ # objects don't have modifiers.
+ obj.modifiers.new(
+ name='MeshSequenceCache', type='MESH_SEQUENCE_CACHE')
+ bpy.context.evaluated_depsgraph_get()
- asset_group.matrix_basis = mat
+ constraint.object_path = (
+ constraint.cache_file.object_paths[0].path)
metadata["libpath"] = str(libpath)
metadata["representation"] = repre_entity["id"]
diff --git a/client/ayon_core/hosts/blender/plugins/publish/collect_instance.py b/client/ayon_core/hosts/blender/plugins/publish/collect_instance.py
index d47c69a270..314ffd368a 100644
--- a/client/ayon_core/hosts/blender/plugins/publish/collect_instance.py
+++ b/client/ayon_core/hosts/blender/plugins/publish/collect_instance.py
@@ -12,7 +12,7 @@ class CollectBlenderInstanceData(pyblish.api.InstancePlugin):
order = pyblish.api.CollectorOrder
hosts = ["blender"]
families = ["model", "pointcache", "animation", "rig", "camera", "layout",
- "blendScene"]
+ "blendScene", "usd"]
label = "Collect Instance"
def process(self, instance):
diff --git a/client/ayon_core/hosts/blender/plugins/publish/extract_usd.py b/client/ayon_core/hosts/blender/plugins/publish/extract_usd.py
new file mode 100644
index 0000000000..1d4fa3d7ac
--- /dev/null
+++ b/client/ayon_core/hosts/blender/plugins/publish/extract_usd.py
@@ -0,0 +1,90 @@
+import os
+
+import bpy
+
+from ayon_core.pipeline import publish
+from ayon_core.hosts.blender.api import plugin, lib
+
+
+class ExtractUSD(publish.Extractor):
+ """Extract as USD."""
+
+ label = "Extract USD"
+ hosts = ["blender"]
+ families = ["usd"]
+
+ def process(self, instance):
+
+ # Ignore runtime instances (e.g. USD layers)
+ # TODO: This is better done via more specific `families`
+ if not instance.data.get("transientData", {}).get("instance_node"):
+ return
+
+ # Define extract output file path
+ stagingdir = self.staging_dir(instance)
+ filename = f"{instance.name}.usd"
+ filepath = os.path.join(stagingdir, filename)
+
+ # Perform extraction
+ self.log.debug("Performing extraction..")
+
+ # Select all members to "export selected"
+ plugin.deselect_all()
+
+ selected = []
+ for obj in instance:
+ if isinstance(obj, bpy.types.Object):
+ obj.select_set(True)
+ selected.append(obj)
+
+ root = lib.get_highest_root(objects=instance[:])
+ if not root:
+ instance_node = instance.data["transientData"]["instance_node"]
+ raise publish.KnownPublishError(
+ f"No root object found in instance: {instance_node.name}"
+ )
+ self.log.debug(f"Exporting using active root: {root.name}")
+
+ context = plugin.create_blender_context(
+ active=root, selected=selected)
+
+ # Export USD
+ with bpy.context.temp_override(**context):
+ bpy.ops.wm.usd_export(
+ filepath=filepath,
+ selected_objects_only=True,
+ export_textures=False,
+ relative_paths=False,
+ export_animation=False,
+ export_hair=False,
+ export_uvmaps=True,
+ # TODO: add for new version of Blender (4+?)
+ # export_mesh_colors=True,
+ export_normals=True,
+ export_materials=True,
+ use_instancing=True
+ )
+
+ plugin.deselect_all()
+
+ # Add representation
+ representation = {
+ 'name': 'usd',
+ 'ext': 'usd',
+ 'files': filename,
+ "stagingDir": stagingdir,
+ }
+ instance.data.setdefault("representations", []).append(representation)
+ self.log.debug("Extracted instance '%s' to: %s",
+ instance.name, representation)
+
+
+class ExtractModelUSD(ExtractUSD):
+ """Extract model as USD."""
+
+ label = "Extract USD (Model)"
+ hosts = ["blender"]
+ families = ["model"]
+
+ # Driven by settings
+ optional = True
diff --git a/client/ayon_core/hosts/fusion/api/action.py b/client/ayon_core/hosts/fusion/api/action.py
index 1643f1ce03..a0c6aafcb5 100644
--- a/client/ayon_core/hosts/fusion/api/action.py
+++ b/client/ayon_core/hosts/fusion/api/action.py
@@ -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))
+ )
diff --git a/client/ayon_core/hosts/fusion/plugins/publish/collect_render.py b/client/ayon_core/hosts/fusion/plugins/publish/collect_render.py
index 95d029aad4..9c04e59717 100644
--- a/client/ayon_core/hosts/fusion/plugins/publish/collect_render.py
+++ b/client/ayon_core/hosts/fusion/plugins/publish/collect_render.py
@@ -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", [])
diff --git a/client/ayon_core/hosts/fusion/plugins/publish/validate_instance_in_context.py b/client/ayon_core/hosts/fusion/plugins/publish/validate_instance_in_context.py
new file mode 100644
index 0000000000..3aa6fb452f
--- /dev/null
+++ b/client/ayon_core/hosts/fusion/plugins/publish/validate_instance_in_context.py
@@ -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"]
diff --git a/client/ayon_core/hosts/hiero/api/lib.py b/client/ayon_core/hosts/hiero/api/lib.py
index aaf99546c7..456a68f125 100644
--- a/client/ayon_core/hosts/hiero/api/lib.py
+++ b/client/ayon_core/hosts/hiero/api/lib.py
@@ -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({
diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_arnold_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_arnold_rop.py
index f65b54a452..1208cfc1ea 100644
--- a/client/ayon_core/hosts/houdini/plugins/create/create_arnold_rop.py
+++ b/client/ayon_core/hosts/houdini/plugins/create/create_arnold_rop.py
@@ -13,11 +13,17 @@ class CreateArnoldRop(plugin.HoudiniCreator):
# Default extension
ext = "exr"
- # Default to split export and render jobs
- export_job = True
+ # Default render target
+ render_target = "farm_split"
def create(self, product_name, instance_data, pre_create_data):
import hou
+ # Transfer settings from pre create to instance
+ creator_attributes = instance_data.setdefault(
+ "creator_attributes", dict())
+ for key in ["render_target", "review"]:
+ if key in pre_create_data:
+ creator_attributes[key] = pre_create_data[key]
# Remove the active, we are checking the bypass flag of the nodes
instance_data.pop("active", None)
@@ -25,8 +31,6 @@ class CreateArnoldRop(plugin.HoudiniCreator):
# Add chunk size attribute
instance_data["chunkSize"] = 1
- # Submit for job publishing
- instance_data["farm"] = pre_create_data.get("farm")
instance = super(CreateArnoldRop, self).create(
product_name,
@@ -51,7 +55,7 @@ class CreateArnoldRop(plugin.HoudiniCreator):
"ar_exr_half_precision": 1 # half precision
}
- if pre_create_data.get("export_job"):
+ if pre_create_data.get("render_target") == "farm_split":
ass_filepath = \
"{export_dir}{product_name}/{product_name}.$F4.ass".format(
export_dir=hou.text.expandString("$HIP/pyblish/ass/"),
@@ -66,23 +70,41 @@ class CreateArnoldRop(plugin.HoudiniCreator):
to_lock = ["productType", "id"]
self.lock_parameters(instance_node, to_lock)
- def get_pre_create_attr_defs(self):
- attrs = super(CreateArnoldRop, self).get_pre_create_attr_defs()
+ def get_instance_attr_defs(self):
+ """get instance attribute definitions.
+ Attributes defined in this method are exposed in
+ publish tab in the publisher UI.
+ """
+
+ render_target_items = {
+ "local": "Local machine rendering",
+ "local_no_render": "Use existing frames (local)",
+ "farm": "Farm Rendering",
+ "farm_split": "Farm Rendering - Split export & render jobs",
+ }
+
+ return [
+ BoolDef("review",
+ label="Review",
+ tooltip="Mark as reviewable",
+ default=True),
+ EnumDef("render_target",
+ items=render_target_items,
+ label="Render target",
+ default=self.render_target),
+ ]
+
+ def get_pre_create_attr_defs(self):
image_format_enum = [
"bmp", "cin", "exr", "jpg", "pic", "pic.gz", "png",
"rad", "rat", "rta", "sgi", "tga", "tif",
]
- return attrs + [
- BoolDef("farm",
- label="Submitting to Farm",
- default=True),
- BoolDef("export_job",
- label="Split export and render jobs",
- default=self.export_job),
+ attrs = [
EnumDef("image_format",
image_format_enum,
default=self.ext,
- label="Image Format Options")
+ label="Image Format Options"),
]
+ return attrs + self.get_instance_attr_defs()
diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_karma_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_karma_rop.py
index e91ddbc0ac..48cf5057ab 100644
--- a/client/ayon_core/hosts/houdini/plugins/create/create_karma_rop.py
+++ b/client/ayon_core/hosts/houdini/plugins/create/create_karma_rop.py
@@ -11,15 +11,23 @@ class CreateKarmaROP(plugin.HoudiniCreator):
product_type = "karma_rop"
icon = "magic"
+ # Default render target
+ render_target = "farm"
+
def create(self, product_name, instance_data, pre_create_data):
import hou # noqa
+ # Transfer settings from pre create to instance
+ creator_attributes = instance_data.setdefault(
+ "creator_attributes", dict())
+
+ for key in ["render_target", "review"]:
+ if key in pre_create_data:
+ creator_attributes[key] = pre_create_data[key]
instance_data.pop("active", None)
instance_data.update({"node_type": "karma"})
# Add chunk size attribute
instance_data["chunkSize"] = 10
- # Submit for job publishing
- instance_data["farm"] = pre_create_data.get("farm")
instance = super(CreateKarmaROP, self).create(
product_name,
@@ -86,18 +94,40 @@ class CreateKarmaROP(plugin.HoudiniCreator):
to_lock = ["productType", "id"]
self.lock_parameters(instance_node, to_lock)
- def get_pre_create_attr_defs(self):
- attrs = super(CreateKarmaROP, self).get_pre_create_attr_defs()
+ def get_instance_attr_defs(self):
+ """get instance attribute definitions.
+ Attributes defined in this method are exposed in
+ publish tab in the publisher UI.
+ """
+
+ render_target_items = {
+ "local": "Local machine rendering",
+ "local_no_render": "Use existing frames (local)",
+ "farm": "Farm Rendering",
+ }
+
+ return [
+ BoolDef("review",
+ label="Review",
+ tooltip="Mark as reviewable",
+ default=True),
+ EnumDef("render_target",
+ items=render_target_items,
+ label="Render target",
+ default=self.render_target)
+ ]
+
+
+ def get_pre_create_attr_defs(self):
image_format_enum = [
"bmp", "cin", "exr", "jpg", "pic", "pic.gz", "png",
"rad", "rat", "rta", "sgi", "tga", "tif",
]
- return attrs + [
- BoolDef("farm",
- label="Submitting to Farm",
- default=True),
+ attrs = super(CreateKarmaROP, self).get_pre_create_attr_defs()
+
+ attrs += [
EnumDef("image_format",
image_format_enum,
default="exr",
@@ -112,5 +142,6 @@ class CreateKarmaROP(plugin.HoudiniCreator):
decimals=0),
BoolDef("cam_res",
label="Camera Resolution",
- default=False)
+ default=False),
]
+ return attrs + self.get_instance_attr_defs()
diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_mantra_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_mantra_rop.py
index 64ecf428e9..05b4431aba 100644
--- a/client/ayon_core/hosts/houdini/plugins/create/create_mantra_rop.py
+++ b/client/ayon_core/hosts/houdini/plugins/create/create_mantra_rop.py
@@ -11,18 +11,22 @@ class CreateMantraROP(plugin.HoudiniCreator):
product_type = "mantra_rop"
icon = "magic"
- # Default to split export and render jobs
- export_job = True
+ # Default render target
+ render_target = "farm_split"
def create(self, product_name, instance_data, pre_create_data):
import hou # noqa
+ # Transfer settings from pre create to instance
+ creator_attributes = instance_data.setdefault(
+ "creator_attributes", dict())
+ for key in ["render_target", "review"]:
+ if key in pre_create_data:
+ creator_attributes[key] = pre_create_data[key]
instance_data.pop("active", None)
instance_data.update({"node_type": "ifd"})
# Add chunk size attribute
instance_data["chunkSize"] = 10
- # Submit for job publishing
- instance_data["farm"] = pre_create_data.get("farm")
instance = super(CreateMantraROP, self).create(
product_name,
@@ -46,7 +50,7 @@ class CreateMantraROP(plugin.HoudiniCreator):
"vm_picture": filepath,
}
- if pre_create_data.get("export_job"):
+ if pre_create_data.get("render_target") == "farm_split":
ifd_filepath = \
"{export_dir}{product_name}/{product_name}.$F4.ifd".format(
export_dir=hou.text.expandString("$HIP/pyblish/ifd/"),
@@ -77,21 +81,40 @@ class CreateMantraROP(plugin.HoudiniCreator):
to_lock = ["productType", "id"]
self.lock_parameters(instance_node, to_lock)
- def get_pre_create_attr_defs(self):
- attrs = super(CreateMantraROP, self).get_pre_create_attr_defs()
+ def get_instance_attr_defs(self):
+ """get instance attribute definitions.
+ Attributes defined in this method are exposed in
+ publish tab in the publisher UI.
+ """
+
+ render_target_items = {
+ "local": "Local machine rendering",
+ "local_no_render": "Use existing frames (local)",
+ "farm": "Farm Rendering",
+ "farm_split": "Farm Rendering - Split export & render jobs",
+ }
+
+ return [
+ BoolDef("review",
+ label="Review",
+ tooltip="Mark as reviewable",
+ default=True),
+ EnumDef("render_target",
+ items=render_target_items,
+ label="Render target",
+ default=self.render_target)
+ ]
+
+ def get_pre_create_attr_defs(self):
image_format_enum = [
"bmp", "cin", "exr", "jpg", "pic", "pic.gz", "png",
"rad", "rat", "rta", "sgi", "tga", "tif",
]
- return attrs + [
- BoolDef("farm",
- label="Submitting to Farm",
- default=True),
- BoolDef("export_job",
- label="Split export and render jobs",
- default=self.export_job),
+ attrs = super(CreateMantraROP, self).get_pre_create_attr_defs()
+
+ attrs += [
EnumDef("image_format",
image_format_enum,
default="exr",
@@ -100,5 +123,6 @@ class CreateMantraROP(plugin.HoudiniCreator):
label="Override Camera Resolution",
tooltip="Override the current camera "
"resolution, recommended for IPR.",
- default=False)
+ default=False),
]
+ return attrs + self.get_instance_attr_defs()
diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_model.py b/client/ayon_core/hosts/houdini/plugins/create/create_model.py
new file mode 100644
index 0000000000..74d067b133
--- /dev/null
+++ b/client/ayon_core/hosts/houdini/plugins/create/create_model.py
@@ -0,0 +1,141 @@
+# -*- coding: utf-8 -*-
+"""Creator plugin for creating Model product type.
+
+Note:
+ Currently, This creator plugin is the same as 'create_pointcache.py'
+ But renaming the product type to 'model'.
+
+ It's purpose to support
+ Maya (load/publish model from maya to/from houdini).
+
+ It's considered to support multiple representations in the future.
+"""
+
+from ayon_core.hosts.houdini.api import plugin
+from ayon_core.lib import BoolDef
+
+import hou
+
+
+
+class CreateModel(plugin.HoudiniCreator):
+ """Create Model"""
+ identifier = "io.openpype.creators.houdini.model"
+ label = "Model"
+ product_type = "model"
+ icon = "cube"
+
+ def create(self, product_name, instance_data, pre_create_data):
+ instance_data.pop("active", None)
+ instance_data.update({"node_type": "alembic"})
+ creator_attributes = instance_data.setdefault(
+ "creator_attributes", dict())
+ creator_attributes["farm"] = pre_create_data["farm"]
+
+ instance = super(CreateModel, self).create(
+ product_name,
+ instance_data,
+ pre_create_data)
+
+ instance_node = hou.node(instance.get("instance_node"))
+ parms = {
+ "use_sop_path": True,
+ "build_from_path": True,
+ "path_attrib": "path",
+ "prim_to_detail_pattern": "cbId",
+ "format": 2,
+ "facesets": 0,
+ "filename": hou.text.expandString(
+ "$HIP/pyblish/{}.abc".format(product_name))
+ }
+
+ if self.selected_nodes:
+ selected_node = self.selected_nodes[0]
+
+ # Although Houdini allows ObjNode path on `sop_path` for the
+ # the ROP node we prefer it set to the SopNode path explicitly
+
+ # Allow sop level paths (e.g. /obj/geo1/box1)
+ if isinstance(selected_node, hou.SopNode):
+ parms["sop_path"] = selected_node.path()
+ self.log.debug(
+ "Valid SopNode selection, 'SOP Path' in ROP will be set to '%s'."
+ % selected_node.path()
+ )
+
+ # Allow object level paths to Geometry nodes (e.g. /obj/geo1)
+ # but do not allow other object level nodes types like cameras, etc.
+ elif isinstance(selected_node, hou.ObjNode) and \
+ selected_node.type().name() in ["geo"]:
+
+ # get the output node with the minimum
+ # 'outputidx' or the node with display flag
+ sop_path = self.get_obj_output(selected_node)
+
+ if sop_path:
+ parms["sop_path"] = sop_path.path()
+ self.log.debug(
+ "Valid ObjNode selection, 'SOP Path' in ROP will be set to "
+ "the child path '%s'."
+ % sop_path.path()
+ )
+
+ if not parms.get("sop_path", None):
+ self.log.debug(
+ "Selection isn't valid. 'SOP Path' in ROP will be empty."
+ )
+ else:
+ self.log.debug(
+ "No Selection. 'SOP Path' in ROP will be empty."
+ )
+
+ instance_node.setParms(parms)
+ instance_node.parm("trange").set(1)
+
+ # Explicitly set f1 and f2 to frame start.
+ # Which forces the rop node to export one frame.
+ instance_node.parmTuple('f').deleteAllKeyframes()
+ fstart = int(hou.hscriptExpression("$FSTART"))
+ instance_node.parmTuple('f').set((fstart, fstart, 1))
+
+ # Lock any parameters in this list
+ to_lock = ["prim_to_detail_pattern"]
+ self.lock_parameters(instance_node, to_lock)
+
+ def get_network_categories(self):
+ return [
+ hou.ropNodeTypeCategory(),
+ hou.sopNodeTypeCategory()
+ ]
+
+ def get_obj_output(self, obj_node):
+ """Find output node with the smallest 'outputidx'."""
+
+ outputs = obj_node.subnetOutputs()
+
+ # if obj_node is empty
+ if not outputs:
+ return
+
+ # if obj_node has one output child whether its
+ # sop output node or a node with the render flag
+ elif len(outputs) == 1:
+ return outputs[0]
+
+ # if there are more than one, then it have multiple output nodes
+ # return the one with the minimum 'outputidx'
+ else:
+ return min(outputs,
+ key=lambda node: node.evalParm('outputidx'))
+
+ def get_instance_attr_defs(self):
+ return [
+ BoolDef("farm",
+ label="Submitting to Farm",
+ default=False)
+ ]
+
+ def get_pre_create_attr_defs(self):
+ attrs = super().get_pre_create_attr_defs()
+ # Use same attributes as for instance attributes
+ return attrs + self.get_instance_attr_defs()
diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_redshift_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_redshift_rop.py
index 1cd239e929..3ecb09ee9b 100644
--- a/client/ayon_core/hosts/houdini/plugins/create/create_redshift_rop.py
+++ b/client/ayon_core/hosts/houdini/plugins/create/create_redshift_rop.py
@@ -17,17 +17,21 @@ class CreateRedshiftROP(plugin.HoudiniCreator):
ext = "exr"
multi_layered_mode = "No Multi-Layered EXR File"
- # Default to split export and render jobs
- split_render = True
+ # Default render target
+ render_target = "farm_split"
def create(self, product_name, instance_data, pre_create_data):
+ # Transfer settings from pre create to instance
+ creator_attributes = instance_data.setdefault(
+ "creator_attributes", dict())
+ for key in ["render_target", "review"]:
+ if key in pre_create_data:
+ creator_attributes[key] = pre_create_data[key]
instance_data.pop("active", None)
instance_data.update({"node_type": "Redshift_ROP"})
# Add chunk size attribute
instance_data["chunkSize"] = 10
- # Submit for job publishing
- instance_data["farm"] = pre_create_data.get("farm")
instance = super(CreateRedshiftROP, self).create(
product_name,
@@ -99,7 +103,7 @@ class CreateRedshiftROP(plugin.HoudiniCreator):
rs_filepath = f"{export_dir}{product_name}/{product_name}.$F4.rs"
parms["RS_archive_file"] = rs_filepath
- if pre_create_data.get("split_render", self.split_render):
+ if pre_create_data.get("render_target") == "farm_split":
parms["RS_archive_enable"] = 1
instance_node.setParms(parms)
@@ -118,24 +122,44 @@ class CreateRedshiftROP(plugin.HoudiniCreator):
return super(CreateRedshiftROP, self).remove_instances(instances)
+ def get_instance_attr_defs(self):
+ """get instance attribute definitions.
+
+ Attributes defined in this method are exposed in
+ publish tab in the publisher UI.
+ """
+
+ render_target_items = {
+ "local": "Local machine rendering",
+ "local_no_render": "Use existing frames (local)",
+ "farm": "Farm Rendering",
+ "farm_split": "Farm Rendering - Split export & render jobs",
+ }
+
+ return [
+ BoolDef("review",
+ label="Review",
+ tooltip="Mark as reviewable",
+ default=True),
+ EnumDef("render_target",
+ items=render_target_items,
+ label="Render target",
+ default=self.render_target)
+ ]
+
def get_pre_create_attr_defs(self):
- attrs = super(CreateRedshiftROP, self).get_pre_create_attr_defs()
+
image_format_enum = [
"exr", "tif", "jpg", "png",
]
+
multi_layered_mode = [
"No Multi-Layered EXR File",
"Full Multi-Layered EXR File"
]
-
- return attrs + [
- BoolDef("farm",
- label="Submitting to Farm",
- default=True),
- BoolDef("split_render",
- label="Split export and render jobs",
- default=self.split_render),
+ attrs = super(CreateRedshiftROP, self).get_pre_create_attr_defs()
+ attrs += [
EnumDef("image_format",
image_format_enum,
default=self.ext,
@@ -143,5 +167,6 @@ class CreateRedshiftROP(plugin.HoudiniCreator):
EnumDef("multi_layered_mode",
multi_layered_mode,
default=self.multi_layered_mode,
- label="Multi-Layered EXR")
+ label="Multi-Layered EXR"),
]
+ return attrs + self.get_instance_attr_defs()
diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_vray_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_vray_rop.py
index 5ed9e848a7..9e4633e745 100644
--- a/client/ayon_core/hosts/houdini/plugins/create/create_vray_rop.py
+++ b/client/ayon_core/hosts/houdini/plugins/create/create_vray_rop.py
@@ -16,17 +16,21 @@ class CreateVrayROP(plugin.HoudiniCreator):
icon = "magic"
ext = "exr"
- # Default to split export and render jobs
- export_job = True
+ # Default render target
+ render_target = "farm_split"
def create(self, product_name, instance_data, pre_create_data):
+ # Transfer settings from pre create to instance
+ creator_attributes = instance_data.setdefault(
+ "creator_attributes", dict())
+ for key in ["render_target", "review"]:
+ if key in pre_create_data:
+ creator_attributes[key] = pre_create_data[key]
instance_data.pop("active", None)
instance_data.update({"node_type": "vray_renderer"})
# Add chunk size attribute
instance_data["chunkSize"] = 10
- # Submit for job publishing
- instance_data["farm"] = pre_create_data.get("farm")
instance = super(CreateVrayROP, self).create(
product_name,
@@ -55,7 +59,7 @@ class CreateVrayROP(plugin.HoudiniCreator):
"SettingsEXR_bits_per_channel": "16" # half precision
}
- if pre_create_data.get("export_job"):
+ if pre_create_data.get("render_target") == "farm_split":
scene_filepath = \
"{export_dir}{product_name}/{product_name}.$F4.vrscene".format(
export_dir=hou.text.expandString("$HIP/pyblish/vrscene/"),
@@ -143,20 +147,41 @@ class CreateVrayROP(plugin.HoudiniCreator):
return super(CreateVrayROP, self).remove_instances(instances)
+ def get_instance_attr_defs(self):
+ """get instance attribute definitions.
+
+ Attributes defined in this method are exposed in
+ publish tab in the publisher UI.
+ """
+
+
+ render_target_items = {
+ "local": "Local machine rendering",
+ "local_no_render": "Use existing frames (local)",
+ "farm": "Farm Rendering",
+ "farm_split": "Farm Rendering - Split export & render jobs",
+ }
+
+ return [
+ BoolDef("review",
+ label="Review",
+ tooltip="Mark as reviewable",
+ default=True),
+ EnumDef("render_target",
+ items=render_target_items,
+ label="Render target",
+ default=self.render_target)
+ ]
+
def get_pre_create_attr_defs(self):
- attrs = super(CreateVrayROP, self).get_pre_create_attr_defs()
image_format_enum = [
"bmp", "cin", "exr", "jpg", "pic", "pic.gz", "png",
"rad", "rat", "rta", "sgi", "tga", "tif",
]
- return attrs + [
- BoolDef("farm",
- label="Submitting to Farm",
- default=True),
- BoolDef("export_job",
- label="Split export and render jobs",
- default=self.export_job),
+ attrs = super(CreateVrayROP, self).get_pre_create_attr_defs()
+
+ attrs += [
EnumDef("image_format",
image_format_enum,
default=self.ext,
@@ -172,3 +197,4 @@ class CreateVrayROP(plugin.HoudiniCreator):
"if enabled",
default=False)
]
+ return attrs + self.get_instance_attr_defs()
diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_workfile.py b/client/ayon_core/hosts/houdini/plugins/create/create_workfile.py
index a958509e25..40a607e81a 100644
--- a/client/ayon_core/hosts/houdini/plugins/create/create_workfile.py
+++ b/client/ayon_core/hosts/houdini/plugins/create/create_workfile.py
@@ -95,7 +95,7 @@ class CreateWorkfile(plugin.HoudiniCreatorBase, AutoCreator):
# write workfile information to context container.
op_ctx = hou.node(CONTEXT_CONTAINER)
if not op_ctx:
- op_ctx = self.create_context_node()
+ op_ctx = self.host.create_context_node()
workfile_data = {"workfile": current_instance.data_to_store()}
imprint(op_ctx, workfile_data)
diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_arnold_rop.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_arnold_rop.py
index 7fe38555a3..53a3e52717 100644
--- a/client/ayon_core/hosts/houdini/plugins/publish/collect_arnold_rop.py
+++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_arnold_rop.py
@@ -40,12 +40,9 @@ class CollectArnoldROPRenderProducts(pyblish.api.InstancePlugin):
default_prefix = evalParmNoFrame(rop, "ar_picture")
render_products = []
- # Store whether we are splitting the render job (export + render)
- split_render = bool(rop.parm("ar_ass_export_enable").eval())
- instance.data["splitRender"] = split_render
export_prefix = None
export_products = []
- if split_render:
+ if instance.data["splitRender"]:
export_prefix = evalParmNoFrame(
rop, "ar_ass_file", pad_character="0"
)
@@ -68,7 +65,12 @@ class CollectArnoldROPRenderProducts(pyblish.api.InstancePlugin):
"": self.generate_expected_files(instance, beauty_product)
}
+ # Assume it's a multipartExr Render.
+ multipartExr = True
+
num_aovs = rop.evalParm("ar_aovs")
+ # TODO: Check the following logic.
+ # as it always assumes that all AOV are not merged.
for index in range(1, num_aovs + 1):
# Skip disabled AOVs
if not rop.evalParm("ar_enable_aov{}".format(index)):
@@ -85,6 +87,14 @@ class CollectArnoldROPRenderProducts(pyblish.api.InstancePlugin):
files_by_aov[label] = self.generate_expected_files(instance,
aov_product)
+ # Set to False as soon as we have a separated aov.
+ multipartExr = False
+
+ # Review Logic expects this key to exist and be True
+ # if render is a multipart Exr.
+ # As long as we have one AOV then multipartExr should be True.
+ instance.data["multipartExr"] = multipartExr
+
for product in render_products:
self.log.debug("Found render product: {}".format(product))
diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_cache_farm.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_cache_farm.py
index e931c7bf1b..e1782b2bb3 100644
--- a/client/ayon_core/hosts/houdini/plugins/publish/collect_cache_farm.py
+++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_cache_farm.py
@@ -11,7 +11,7 @@ class CollectDataforCache(pyblish.api.InstancePlugin):
order = pyblish.api.CollectorOrder + 0.11
families = ["ass", "pointcache",
"mantraifd", "redshiftproxy",
- "vdbcache"]
+ "vdbcache", "model"]
hosts = ["houdini"]
targets = ["local", "remote"]
label = "Collect Data for Cache"
@@ -43,10 +43,7 @@ class CollectDataforCache(pyblish.api.InstancePlugin):
cache_files = {"_": instance.data["files"]}
# Convert instance family to pointcache if it is bgeo or abc
# because ???
- for family in instance.data["families"]:
- if family == "bgeo" or "abc":
- instance.data["productType"] = "pointcache"
- break
+ self.log.debug(instance.data["families"])
instance.data.update({
"plugin": "Houdini",
"publish": True
diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_chunk_size.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_chunk_size.py
index 3e2561dd6f..f0913f2f0a 100644
--- a/client/ayon_core/hosts/houdini/plugins/publish/collect_chunk_size.py
+++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_chunk_size.py
@@ -10,7 +10,7 @@ class CollectChunkSize(pyblish.api.InstancePlugin,
order = pyblish.api.CollectorOrder + 0.05
families = ["ass", "pointcache",
"vdbcache", "mantraifd",
- "redshiftproxy"]
+ "redshiftproxy", "model"]
hosts = ["houdini"]
targets = ["local", "remote"]
label = "Collect Chunk Size"
diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_farm_instances.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_farm_instances.py
new file mode 100644
index 0000000000..586aa2da57
--- /dev/null
+++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_farm_instances.py
@@ -0,0 +1,35 @@
+import pyblish.api
+
+
+class CollectFarmInstances(pyblish.api.InstancePlugin):
+ """Collect instances for farm render."""
+
+ order = pyblish.api.CollectorOrder
+ families = ["mantra_rop",
+ "karma_rop",
+ "redshift_rop",
+ "arnold_rop",
+ "vray_rop"]
+
+ hosts = ["houdini"]
+ targets = ["local", "remote"]
+ label = "Collect farm instances"
+
+ def process(self, instance):
+
+ creator_attribute = instance.data["creator_attributes"]
+
+ # Collect Render Target
+ if creator_attribute.get("render_target") not in {
+ "farm_split", "farm"
+ }:
+ instance.data["farm"] = False
+ instance.data["splitRender"] = False
+ self.log.debug("Render on farm is disabled. "
+ "Skipping farm collecting.")
+ return
+
+ instance.data["farm"] = True
+ instance.data["splitRender"] = (
+ creator_attribute.get("render_target") == "farm_split"
+ )
diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_pointcache_type.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_instances_type.py
similarity index 51%
rename from client/ayon_core/hosts/houdini/plugins/publish/collect_pointcache_type.py
rename to client/ayon_core/hosts/houdini/plugins/publish/collect_instances_type.py
index 3323e97c20..07851387fe 100644
--- a/client/ayon_core/hosts/houdini/plugins/publish/collect_pointcache_type.py
+++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_instances_type.py
@@ -1,21 +1,24 @@
-"""Collector for pointcache types.
+"""Collector for different types.
-This will add additional family to pointcache instance based on
+This will add additional families to different instance based on
the creator_identifier parameter.
"""
import pyblish.api
class CollectPointcacheType(pyblish.api.InstancePlugin):
- """Collect data type for pointcache instance."""
+ """Collect data type for different instances."""
order = pyblish.api.CollectorOrder
hosts = ["houdini"]
- families = ["pointcache"]
- label = "Collect type of pointcache"
+ families = ["pointcache", "model"]
+ label = "Collect instances types"
def process(self, instance):
if instance.data["creator_identifier"] == "io.openpype.creators.houdini.bgeo": # noqa: E501
instance.data["families"] += ["bgeo"]
- elif instance.data["creator_identifier"] == "io.openpype.creators.houdini.pointcache": # noqa: E501
+ elif instance.data["creator_identifier"] in {
+ "io.openpype.creators.houdini.pointcache",
+ "io.openpype.creators.houdini.model"
+ }:
instance.data["families"] += ["abc"]
diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_karma_rop.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_karma_rop.py
index 78651b0c69..662ed7ae30 100644
--- a/client/ayon_core/hosts/houdini/plugins/publish/collect_karma_rop.py
+++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_karma_rop.py
@@ -55,6 +55,12 @@ class CollectKarmaROPRenderProducts(pyblish.api.InstancePlugin):
beauty_product)
}
+ # Review Logic expects this key to exist and be True
+ # if render is a multipart Exr.
+ # As long as we have one AOV then multipartExr should be True.
+ # By default karma render is a multipart Exr.
+ instance.data["multipartExr"] = True
+
filenames = list(render_products)
instance.data["files"] = filenames
instance.data["renderProducts"] = colorspace.ARenderProduct()
diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py
new file mode 100644
index 0000000000..474002e1ee
--- /dev/null
+++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py
@@ -0,0 +1,137 @@
+import os
+import pyblish.api
+from ayon_core.pipeline.create import get_product_name
+from ayon_core.pipeline.farm.patterning import match_aov_pattern
+from ayon_core.pipeline.publish import (
+ get_plugin_settings,
+ apply_plugin_settings_automatically
+)
+
+
+class CollectLocalRenderInstances(pyblish.api.InstancePlugin):
+ """Collect instances for local render.
+
+ Agnostic Local Render Collector.
+ """
+
+ # this plugin runs after Collect Render Products
+ order = pyblish.api.CollectorOrder + 0.12
+ families = ["mantra_rop",
+ "karma_rop",
+ "redshift_rop",
+ "arnold_rop",
+ "vray_rop"]
+
+ hosts = ["houdini"]
+ label = "Collect local render instances"
+
+ use_deadline_aov_filter = False
+ aov_filter = {"host_name": "houdini",
+ "value": [".*([Bb]eauty).*"]}
+
+ @classmethod
+ def apply_settings(cls, project_settings):
+ # Preserve automatic settings applying logic
+ settings = get_plugin_settings(plugin=cls,
+ project_settings=project_settings,
+ log=cls.log,
+ category="houdini")
+ apply_plugin_settings_automatically(cls, settings, logger=cls.log)
+
+ if not cls.use_deadline_aov_filter:
+ # get aov_filter from collector settings
+ # and restructure it as match_aov_pattern requires.
+ cls.aov_filter = {
+ cls.aov_filter["host_name"]: cls.aov_filter["value"]
+ }
+ else:
+ # get aov_filter from deadline settings
+ cls.aov_filter = project_settings["deadline"]["publish"]["ProcessSubmittedJobOnFarm"]["aov_filter"]
+ cls.aov_filter = {
+ item["name"]: item["value"]
+ for item in cls.aov_filter
+ }
+
+ def process(self, instance):
+
+ if instance.data["farm"]:
+ self.log.debug("Render on farm is enabled. "
+ "Skipping local render collecting.")
+ return
+
+ # Create Instance for each AOV.
+ context = instance.context
+ expectedFiles = next(iter(instance.data["expectedFiles"]), {})
+
+ product_type = "render" # is always render
+ product_group = get_product_name(
+ context.data["projectName"],
+ context.data["taskEntity"]["name"],
+ context.data["taskEntity"]["taskType"],
+ context.data["hostName"],
+ product_type,
+ instance.data["productName"]
+ )
+
+ for aov_name, aov_filepaths in expectedFiles.items():
+ product_name = product_group
+
+ if aov_name:
+ product_name = "{}_{}".format(product_name, aov_name)
+
+ # Create instance for each AOV
+ aov_instance = context.create_instance(product_name)
+
+ # Prepare Representation for each AOV
+ aov_filenames = [os.path.basename(path) for path in aov_filepaths]
+ staging_dir = os.path.dirname(aov_filepaths[0])
+ ext = aov_filepaths[0].split(".")[-1]
+
+ # Decide if instance is reviewable
+ preview = False
+ if instance.data.get("multipartExr", False):
+ # Add preview tag because its multipartExr.
+ preview = True
+ else:
+ # Add Preview tag if the AOV matches the filter.
+ preview = match_aov_pattern(
+ "houdini", self.aov_filter, aov_filenames[0]
+ )
+
+ preview = preview and instance.data.get("review", False)
+
+ # Support Single frame.
+ # The integrator wants single files to be a single
+ # filename instead of a list.
+ # More info: https://github.com/ynput/ayon-core/issues/238
+ if len(aov_filenames) == 1:
+ aov_filenames = aov_filenames[0]
+
+ aov_instance.data.update({
+ # 'label': label,
+ "task": instance.data["task"],
+ "folderPath": instance.data["folderPath"],
+ "frameStart": instance.data["frameStartHandle"],
+ "frameEnd": instance.data["frameEndHandle"],
+ "productType": product_type,
+ "family": product_type,
+ "productName": product_name,
+ "productGroup": product_group,
+ "families": ["render.local.hou", "review"],
+ "instance_node": instance.data["instance_node"],
+ "representations": [
+ {
+ "stagingDir": staging_dir,
+ "ext": ext,
+ "name": ext,
+ "tags": ["review"] if preview else [],
+ "files": aov_filenames,
+ "frameStart": instance.data["frameStartHandle"],
+ "frameEnd": instance.data["frameEndHandle"]
+ }
+ ]
+ })
+
+ # Skip integrating original render instance.
+ # We are not removing it because it's used to trigger the render.
+ instance.data["integrate"] = False
diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_mantra_rop.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_mantra_rop.py
index df9acc4b61..7b247768fc 100644
--- a/client/ayon_core/hosts/houdini/plugins/publish/collect_mantra_rop.py
+++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_mantra_rop.py
@@ -44,12 +44,9 @@ class CollectMantraROPRenderProducts(pyblish.api.InstancePlugin):
default_prefix = evalParmNoFrame(rop, "vm_picture")
render_products = []
- # Store whether we are splitting the render job (export + render)
- split_render = bool(rop.parm("soho_outputmode").eval())
- instance.data["splitRender"] = split_render
export_prefix = None
export_products = []
- if split_render:
+ if instance.data["splitRender"]:
export_prefix = evalParmNoFrame(
rop, "soho_diskfile", pad_character="0"
)
@@ -74,6 +71,11 @@ class CollectMantraROPRenderProducts(pyblish.api.InstancePlugin):
beauty_product)
}
+ # Assume it's a multipartExr Render.
+ multipartExr = True
+
+ # TODO: This logic doesn't take into considerations
+ # cryptomatte defined in 'Images > Cryptomatte'
aov_numbers = rop.evalParm("vm_numaux")
if aov_numbers > 0:
# get the filenames of the AOVs
@@ -93,6 +95,14 @@ class CollectMantraROPRenderProducts(pyblish.api.InstancePlugin):
files_by_aov[var] = self.generate_expected_files(instance, aov_product) # noqa
+ # Set to False as soon as we have a separated aov.
+ multipartExr = False
+
+ # Review Logic expects this key to exist and be True
+ # if render is a multipart Exr.
+ # As long as we have one AOV then multipartExr should be True.
+ instance.data["multipartExr"] = multipartExr
+
for product in render_products:
self.log.debug("Found render product: %s" % product)
diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_output_node.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_output_node.py
index 26381e065e..289222f32b 100644
--- a/client/ayon_core/hosts/houdini/plugins/publish/collect_output_node.py
+++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_output_node.py
@@ -15,7 +15,8 @@ class CollectOutputSOPPath(pyblish.api.InstancePlugin):
"usd",
"usdrender",
"redshiftproxy",
- "staticMesh"
+ "staticMesh",
+ "model"
]
hosts = ["houdini"]
diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_redshift_rop.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_redshift_rop.py
index 55a55bb12a..ce90ae2413 100644
--- a/client/ayon_core/hosts/houdini/plugins/publish/collect_redshift_rop.py
+++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_redshift_rop.py
@@ -42,11 +42,9 @@ class CollectRedshiftROPRenderProducts(pyblish.api.InstancePlugin):
default_prefix = evalParmNoFrame(rop, "RS_outputFileNamePrefix")
beauty_suffix = rop.evalParm("RS_outputBeautyAOVSuffix")
- # Store whether we are splitting the render job (export + render)
- split_render = bool(rop.parm("RS_archive_enable").eval())
- instance.data["splitRender"] = split_render
+
export_products = []
- if split_render:
+ if instance.data["splitRender"]:
export_prefix = evalParmNoFrame(
rop, "RS_archive_file", pad_character="0"
)
@@ -63,9 +61,12 @@ class CollectRedshiftROPRenderProducts(pyblish.api.InstancePlugin):
full_exr_mode = (rop.evalParm("RS_outputMultilayerMode") == "2")
if full_exr_mode:
# Ignore beauty suffix if full mode is enabled
- # As this is what the rop does.
+ # As this is what the rop does.
beauty_suffix = ""
+ # Assume it's a multipartExr Render.
+ multipartExr = True
+
# Default beauty/main layer AOV
beauty_product = self.get_render_product_name(
prefix=default_prefix, suffix=beauty_suffix
@@ -75,7 +76,7 @@ class CollectRedshiftROPRenderProducts(pyblish.api.InstancePlugin):
beauty_suffix: self.generate_expected_files(instance,
beauty_product)
}
-
+
aovs_rop = rop.parm("RS_aovGetFromNode").evalAsNode()
if aovs_rop:
rop = aovs_rop
@@ -98,13 +99,21 @@ class CollectRedshiftROPRenderProducts(pyblish.api.InstancePlugin):
if rop.parm(f"RS_aovID_{i}").evalAsString() == "CRYPTOMATTE" or \
not full_exr_mode:
-
+
aov_product = self.get_render_product_name(aov_prefix, aov_suffix)
render_products.append(aov_product)
files_by_aov[aov_suffix] = self.generate_expected_files(instance,
aov_product) # noqa
+ # Set to False as soon as we have a separated aov.
+ multipartExr = False
+
+ # Review Logic expects this key to exist and be True
+ # if render is a multipart Exr.
+ # As long as we have one AOV then multipartExr should be True.
+ instance.data["multipartExr"] = multipartExr
+
for product in render_products:
self.log.debug("Found render product: %s" % product)
diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_review_data.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_review_data.py
index 9671945b9a..ed2de785a2 100644
--- a/client/ayon_core/hosts/houdini/plugins/publish/collect_review_data.py
+++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_review_data.py
@@ -8,7 +8,8 @@ class CollectHoudiniReviewData(pyblish.api.InstancePlugin):
label = "Collect Review Data"
# This specific order value is used so that
# this plugin runs after CollectRopFrameRange
- order = pyblish.api.CollectorOrder + 0.1
+ # Also after CollectLocalRenderInstances
+ order = pyblish.api.CollectorOrder + 0.13
hosts = ["houdini"]
families = ["review"]
@@ -28,7 +29,8 @@ class CollectHoudiniReviewData(pyblish.api.InstancePlugin):
ropnode_path = instance.data["instance_node"]
ropnode = hou.node(ropnode_path)
- camera_path = ropnode.parm("camera").eval()
+ # Get camera based on the instance_node type.
+ camera_path = self._get_camera_path(ropnode)
camera_node = hou.node(camera_path)
if not camera_node:
self.log.warning("No valid camera node found on review node: "
@@ -55,3 +57,29 @@ class CollectHoudiniReviewData(pyblish.api.InstancePlugin):
# Store focal length in `burninDataMembers`
burnin_members = instance.data.setdefault("burninDataMembers", {})
burnin_members["focalLength"] = focal_length
+
+ def _get_camera_path(self, ropnode):
+ """Get the camera path associated with the given rop node.
+
+ This function evaluates the camera parameter according to the
+ type of the given rop node.
+
+ Returns:
+ Union[str, None]: Camera path or None.
+
+ This function can return empty string if the camera
+ path is empty i.e. no camera path.
+ """
+
+ if ropnode.type().name() in {
+ "opengl", "karma", "ifd", "arnold"
+ }:
+ return ropnode.parm("camera").eval()
+
+ elif ropnode.type().name() == "Redshift_ROP":
+ return ropnode.parm("RS_renderCamera").eval()
+
+ elif ropnode.type().name() == "vray_renderer":
+ return ropnode.parm("render_camera").eval()
+
+ return None
diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_reviewable_instances.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_reviewable_instances.py
new file mode 100644
index 0000000000..78dc5fe11a
--- /dev/null
+++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_reviewable_instances.py
@@ -0,0 +1,22 @@
+import pyblish.api
+
+
+class CollectReviewableInstances(pyblish.api.InstancePlugin):
+ """Collect Reviewable Instances.
+
+ Basically, all instances of the specified families
+ with creator_attribure["review"]
+ """
+
+ order = pyblish.api.CollectorOrder
+ label = "Collect Reviewable Instances"
+ families = ["mantra_rop",
+ "karma_rop",
+ "redshift_rop",
+ "arnold_rop",
+ "vray_rop"]
+
+ def process(self, instance):
+ creator_attribute = instance.data["creator_attributes"]
+
+ instance.data["review"] = creator_attribute.get("review", False)
diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_vray_rop.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_vray_rop.py
index 62b7dcdd5d..c39b1db103 100644
--- a/client/ayon_core/hosts/houdini/plugins/publish/collect_vray_rop.py
+++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_vray_rop.py
@@ -45,12 +45,9 @@ class CollectVrayROPRenderProducts(pyblish.api.InstancePlugin):
render_products = []
# TODO: add render elements if render element
- # Store whether we are splitting the render job in an export + render
- split_render = rop.parm("render_export_mode").eval() == "2"
- instance.data["splitRender"] = split_render
export_prefix = None
export_products = []
- if split_render:
+ if instance.data["splitRender"]:
export_prefix = evalParmNoFrame(
rop, "render_export_filepath", pad_character="0"
)
@@ -70,6 +67,9 @@ class CollectVrayROPRenderProducts(pyblish.api.InstancePlugin):
"": self.generate_expected_files(instance,
beauty_product)}
+ # Assume it's a multipartExr Render.
+ multipartExr = True
+
if instance.data.get("RenderElement", True):
render_element = self.get_render_element_name(rop, default_prefix)
if render_element:
@@ -77,7 +77,13 @@ class CollectVrayROPRenderProducts(pyblish.api.InstancePlugin):
render_products.append(renderpass)
files_by_aov[aov] = self.generate_expected_files(
instance, renderpass)
+ # Set to False as soon as we have a separated aov.
+ multipartExr = False
+ # Review Logic expects this key to exist and be True
+ # if render is a multipart Exr.
+ # As long as we have one AOV then multipartExr should be True.
+ instance.data["multipartExr"] = multipartExr
for product in render_products:
self.log.debug("Found render product: %s" % product)
diff --git a/client/ayon_core/hosts/houdini/plugins/publish/extract_opengl.py b/client/ayon_core/hosts/houdini/plugins/publish/extract_opengl.py
index 57bb8b881a..26a216e335 100644
--- a/client/ayon_core/hosts/houdini/plugins/publish/extract_opengl.py
+++ b/client/ayon_core/hosts/houdini/plugins/publish/extract_opengl.py
@@ -19,6 +19,16 @@ class ExtractOpenGL(publish.Extractor,
def process(self, instance):
ropnode = hou.node(instance.data.get("instance_node"))
+ # This plugin is triggered when marking render as reviewable.
+ # Therefore, this plugin will run on over wrong instances.
+ # TODO: Don't run this plugin on wrong instances.
+ # This plugin should run only on review product type
+ # with instance node of opengl type.
+ if ropnode.type().name() != "opengl":
+ self.log.debug("Skipping OpenGl extraction. Rop node {} "
+ "is not an OpenGl node.".format(ropnode.path()))
+ return
+
output = ropnode.evalParm("picture")
staging_dir = os.path.normpath(os.path.dirname(output))
instance.data["stagingDir"] = staging_dir
diff --git a/client/ayon_core/hosts/houdini/plugins/publish/extract_render.py b/client/ayon_core/hosts/houdini/plugins/publish/extract_render.py
new file mode 100644
index 0000000000..7b4762a25f
--- /dev/null
+++ b/client/ayon_core/hosts/houdini/plugins/publish/extract_render.py
@@ -0,0 +1,74 @@
+import pyblish.api
+
+from ayon_core.pipeline import publish
+from ayon_core.hosts.houdini.api.lib import render_rop
+import hou
+import os
+
+
+class ExtractRender(publish.Extractor):
+
+ order = pyblish.api.ExtractorOrder
+ label = "Extract Render"
+ hosts = ["houdini"]
+ families = ["mantra_rop",
+ "karma_rop",
+ "redshift_rop",
+ "arnold_rop",
+ "vray_rop"]
+
+ def process(self, instance):
+ creator_attribute = instance.data["creator_attributes"]
+ product_type = instance.data["productType"]
+ rop_node = hou.node(instance.data.get("instance_node"))
+
+ # Align split parameter value on rop node to the render target.
+ if instance.data["splitRender"]:
+ if product_type == "arnold_rop":
+ rop_node.setParms({"ar_ass_export_enable": 1})
+ elif product_type == "mantra_rop":
+ rop_node.setParms({"soho_outputmode": 1})
+ elif product_type == "redshift_rop":
+ rop_node.setParms({"RS_archive_enable": 1})
+ elif product_type == "vray_rop":
+ rop_node.setParms({"render_export_mode": "2"})
+ else:
+ if product_type == "arnold_rop":
+ rop_node.setParms({"ar_ass_export_enable": 0})
+ elif product_type == "mantra_rop":
+ rop_node.setParms({"soho_outputmode": 0})
+ elif product_type == "redshift_rop":
+ rop_node.setParms({"RS_archive_enable": 0})
+ elif product_type == "vray_rop":
+ rop_node.setParms({"render_export_mode": "1"})
+
+ if instance.data.get("farm"):
+ self.log.debug("Render should be processed on farm, skipping local render.")
+ return
+
+ if creator_attribute.get("render_target") == "local":
+ ropnode = hou.node(instance.data.get("instance_node"))
+ render_rop(ropnode)
+
+ # `ExpectedFiles` is a list that includes one dict.
+ expected_files = instance.data["expectedFiles"][0]
+ # Each key in that dict is a list of files.
+ # Combine lists of files into one big list.
+ all_frames = []
+ for value in expected_files.values():
+ if isinstance(value, str):
+ all_frames.append(value)
+ elif isinstance(value, list):
+ all_frames.extend(value)
+ # Check missing frames.
+ # Frames won't exist if user cancels the render.
+ missing_frames = [
+ frame
+ for frame in all_frames
+ if not os.path.exists(frame)
+ ]
+ if missing_frames:
+ # TODO: Use user friendly error reporting.
+ raise RuntimeError("Failed to complete render extraction. "
+ "Missing output files: {}".format(
+ missing_frames))
diff --git a/client/ayon_core/hosts/houdini/plugins/publish/increment_current_file.py b/client/ayon_core/hosts/houdini/plugins/publish/increment_current_file.py
index fe8fa25f10..3e9291d5c2 100644
--- a/client/ayon_core/hosts/houdini/plugins/publish/increment_current_file.py
+++ b/client/ayon_core/hosts/houdini/plugins/publish/increment_current_file.py
@@ -17,11 +17,13 @@ class IncrementCurrentFile(pyblish.api.ContextPlugin):
order = pyblish.api.IntegratorOrder + 9.0
hosts = ["houdini"]
families = ["workfile",
- "redshift_rop",
- "arnold_rop",
+ "usdrender",
"mantra_rop",
"karma_rop",
- "usdrender",
+ "redshift_rop",
+ "arnold_rop",
+ "vray_rop",
+ "render.local.hou",
"publish.hou"]
optional = True
diff --git a/client/ayon_core/hosts/houdini/plugins/publish/validate_export_is_a_single_frame.py b/client/ayon_core/hosts/houdini/plugins/publish/validate_export_is_a_single_frame.py
new file mode 100644
index 0000000000..b188055bc7
--- /dev/null
+++ b/client/ayon_core/hosts/houdini/plugins/publish/validate_export_is_a_single_frame.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+"""Validator for checking that export is a single frame."""
+import pyblish.api
+from ayon_core.pipeline import (
+ PublishValidationError,
+ OptionalPyblishPluginMixin
+)
+from ayon_core.pipeline.publish import ValidateContentsOrder
+from ayon_core.hosts.houdini.api.action import SelectInvalidAction
+
+
+class ValidateSingleFrame(pyblish.api.InstancePlugin,
+ OptionalPyblishPluginMixin):
+ """Validate Export is a Single Frame.
+
+ It checks if rop node is exporting one frame.
+ This is mainly for Model product type.
+ """
+
+ families = ["model"]
+ hosts = ["houdini"]
+ label = "Validate Single Frame"
+ order = ValidateContentsOrder + 0.1
+ actions = [SelectInvalidAction]
+
+ def process(self, instance):
+
+ invalid = self.get_invalid(instance)
+ if invalid:
+ nodes = [n.path() for n in invalid]
+ raise PublishValidationError(
+ "See log for details. "
+ "Invalid nodes: {0}".format(nodes)
+ )
+
+ @classmethod
+ def get_invalid(cls, instance):
+
+ invalid = []
+
+ frame_start = instance.data.get("frameStartHandle")
+ frame_end = instance.data.get("frameEndHandle")
+
+ # This happens if instance node has no 'trange' parameter.
+ if frame_start is None or frame_end is None:
+ cls.log.debug(
+ "No frame data, skipping check.."
+ )
+ return
+
+ if frame_start != frame_end:
+ invalid.append(instance.data["instance_node"])
+ cls.log.error(
+ "Invalid frame range on '%s'."
+ "You should use the same frame number for 'f1' "
+ "and 'f2' parameters.",
+ instance.data["instance_node"].path()
+ )
+
+ return invalid
diff --git a/client/ayon_core/hosts/houdini/plugins/publish/validate_mesh_is_static.py b/client/ayon_core/hosts/houdini/plugins/publish/validate_mesh_is_static.py
index 289e00339b..9652367bfe 100644
--- a/client/ayon_core/hosts/houdini/plugins/publish/validate_mesh_is_static.py
+++ b/client/ayon_core/hosts/houdini/plugins/publish/validate_mesh_is_static.py
@@ -16,9 +16,13 @@ class ValidateMeshIsStatic(pyblish.api.InstancePlugin,
"""Validate mesh is static.
It checks if output node is time dependent.
+ this avoids getting different output from ROP node when extracted
+ from a different frame than the first frame.
+ (Might be overly restrictive though)
"""
- families = ["staticMesh"]
+ families = ["staticMesh",
+ "model"]
hosts = ["houdini"]
label = "Validate Mesh is Static"
order = ValidateContentsOrder + 0.1
diff --git a/client/ayon_core/hosts/houdini/plugins/publish/validate_mkpaths_toggled.py b/client/ayon_core/hosts/houdini/plugins/publish/validate_mkpaths_toggled.py
index 38f1c4e176..5e59eb505f 100644
--- a/client/ayon_core/hosts/houdini/plugins/publish/validate_mkpaths_toggled.py
+++ b/client/ayon_core/hosts/houdini/plugins/publish/validate_mkpaths_toggled.py
@@ -7,7 +7,7 @@ class ValidateIntermediateDirectoriesChecked(pyblish.api.InstancePlugin):
"""Validate Create Intermediate Directories is enabled on ROP node."""
order = pyblish.api.ValidatorOrder
- families = ["pointcache", "camera", "vdbcache"]
+ families = ["pointcache", "camera", "vdbcache", "model"]
hosts = ["houdini"]
label = "Create Intermediate Directories Checked"
diff --git a/client/ayon_core/hosts/houdini/plugins/publish/validate_review_colorspace.py b/client/ayon_core/hosts/houdini/plugins/publish/validate_review_colorspace.py
index e7f528ba57..fa532c5437 100644
--- a/client/ayon_core/hosts/houdini/plugins/publish/validate_review_colorspace.py
+++ b/client/ayon_core/hosts/houdini/plugins/publish/validate_review_colorspace.py
@@ -56,6 +56,18 @@ class ValidateReviewColorspace(pyblish.api.InstancePlugin,
def process(self, instance):
+ rop_node = hou.node(instance.data["instance_node"])
+
+ # This plugin is triggered when marking render as reviewable.
+ # Therefore, this plugin will run on over wrong instances.
+ # TODO: Don't run this plugin on wrong instances.
+ # This plugin should run only on review product type
+ # with instance node of opengl type.
+ if rop_node.type().name() != "opengl":
+ self.log.debug("Skipping Validation. Rop node {} "
+ "is not an OpenGl node.".format(rop_node.path()))
+ return
+
if not self.is_active(instance.data):
return
@@ -66,7 +78,6 @@ class ValidateReviewColorspace(pyblish.api.InstancePlugin,
)
return
- rop_node = hou.node(instance.data["instance_node"])
if rop_node.evalParm("colorcorrect") != 2:
# any colorspace settings other than default requires
# 'Color Correct' parm to be set to 'OpenColorIO'
diff --git a/client/ayon_core/hosts/houdini/plugins/publish/validate_scene_review.py b/client/ayon_core/hosts/houdini/plugins/publish/validate_scene_review.py
index b6007d3f0f..0b09306b0d 100644
--- a/client/ayon_core/hosts/houdini/plugins/publish/validate_scene_review.py
+++ b/client/ayon_core/hosts/houdini/plugins/publish/validate_scene_review.py
@@ -20,6 +20,16 @@ class ValidateSceneReview(pyblish.api.InstancePlugin):
report = []
instance_node = hou.node(instance.data.get("instance_node"))
+ # This plugin is triggered when marking render as reviewable.
+ # Therefore, this plugin will run on over wrong instances.
+ # TODO: Don't run this plugin on wrong instances.
+ # This plugin should run only on review product type
+ # with instance node of opengl type.
+ if instance_node.type().name() != "opengl":
+ self.log.debug("Skipping Validation. Rop node {} "
+ "is not an OpenGl node.".format(instance_node.path()))
+ return
+
invalid = self.get_invalid_scene_path(instance_node)
if invalid:
report.append(invalid)
diff --git a/client/ayon_core/hosts/houdini/plugins/publish/validate_sop_output_node.py b/client/ayon_core/hosts/houdini/plugins/publish/validate_sop_output_node.py
index 61cf7596ac..d67192d28e 100644
--- a/client/ayon_core/hosts/houdini/plugins/publish/validate_sop_output_node.py
+++ b/client/ayon_core/hosts/houdini/plugins/publish/validate_sop_output_node.py
@@ -22,7 +22,7 @@ class ValidateSopOutputNode(pyblish.api.InstancePlugin):
"""
order = pyblish.api.ValidatorOrder
- families = ["pointcache", "vdbcache"]
+ families = ["pointcache", "vdbcache", "model"]
hosts = ["houdini"]
label = "Validate Output Node (SOP)"
actions = [SelectROPAction, SelectInvalidAction]
diff --git a/client/ayon_core/hosts/houdini/startup/OPmenu.xml b/client/ayon_core/hosts/houdini/startup/OPmenu.xml
new file mode 100644
index 0000000000..0a7b265fa1
--- /dev/null
+++ b/client/ayon_core/hosts/houdini/startup/OPmenu.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
diff --git a/client/ayon_core/hosts/max/api/lib.py b/client/ayon_core/hosts/max/api/lib.py
index d9a3af3336..f20f754248 100644
--- a/client/ayon_core/hosts/max/api/lib.py
+++ b/client/ayon_core/hosts/max/api/lib.py
@@ -6,12 +6,9 @@ import json
from typing import Any, Dict, Union
import six
-import ayon_api
from ayon_core.pipeline import (
get_current_project_name,
- get_current_folder_path,
- get_current_task_name,
colorspace
)
from ayon_core.settings import get_project_settings
@@ -372,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
@@ -392,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
diff --git a/client/ayon_core/hosts/max/api/pipeline.py b/client/ayon_core/hosts/max/api/pipeline.py
index dc13f47795..d9cfc3407f 100644
--- a/client/ayon_core/hosts/max/api/pipeline.py
+++ b/client/ayon_core/hosts/max/api/pipeline.py
@@ -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 = {
diff --git a/client/ayon_core/hosts/maya/api/fbx.py b/client/ayon_core/hosts/maya/api/fbx.py
index 939da4011b..fd1bf2c901 100644
--- a/client/ayon_core/hosts/maya/api/fbx.py
+++ b/client/ayon_core/hosts/maya/api/fbx.py
@@ -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,
diff --git a/client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py b/client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py
index 08d50a1ab8..069762e4ae 100644
--- a/client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py
+++ b/client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py
@@ -6,7 +6,6 @@ from ayon_core.lib import (
BoolDef,
NumberDef,
)
-from ayon_core.pipeline import CreatedInstance
def _get_animation_attr_defs(cls):
diff --git a/client/ayon_core/hosts/maya/plugins/load/load_image.py b/client/ayon_core/hosts/maya/plugins/load/load_image.py
index ed2f3f7778..936db86f43 100644
--- a/client/ayon_core/hosts/maya/plugins/load/load_image.py
+++ b/client/ayon_core/hosts/maya/plugins/load/load_image.py
@@ -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
)
diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_fbx_animation.py b/client/ayon_core/hosts/maya/plugins/publish/extract_fbx_animation.py
index ee66ed2fb7..77b5b79b5f 100644
--- a/client/ayon_core/hosts/maya/plugins/publish/extract_fbx_animation.py
+++ b/client/ayon_core/hosts/maya/plugins/publish/extract_fbx_animation.py
@@ -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)
diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_animated_reference.py b/client/ayon_core/hosts/maya/plugins/publish/validate_animated_reference.py
deleted file mode 100644
index 2ba2bff6fc..0000000000
--- a/client/ayon_core/hosts/maya/plugins/publish/validate_animated_reference.py
+++ /dev/null
@@ -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
diff --git a/client/ayon_core/hosts/unreal/lib.py b/client/ayon_core/hosts/unreal/lib.py
index 37122b2096..185853a0aa 100644
--- a/client/ayon_core/hosts/unreal/lib.py
+++ b/client/ayon_core/hosts/unreal/lib.py
@@ -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
diff --git a/client/ayon_core/lib/__init__.py b/client/ayon_core/lib/__init__.py
index e436396c6c..e25d3479ee 100644
--- a/client/ayon_core/lib/__init__.py
+++ b/client/ayon_core/lib/__init__.py
@@ -139,6 +139,7 @@ from .path_tools import (
)
from .ayon_info import (
+ is_in_ayon_launcher_process,
is_running_from_build,
is_using_ayon_console,
is_staging_enabled,
@@ -248,6 +249,7 @@ __all__ = [
"Logger",
+ "is_in_ayon_launcher_process",
"is_running_from_build",
"is_using_ayon_console",
"is_staging_enabled",
diff --git a/client/ayon_core/lib/ayon_info.py b/client/ayon_core/lib/ayon_info.py
index fc09a7c90c..c4333fab95 100644
--- a/client/ayon_core/lib/ayon_info.py
+++ b/client/ayon_core/lib/ayon_info.py
@@ -1,4 +1,5 @@
import os
+import sys
import json
import datetime
import platform
@@ -25,6 +26,18 @@ def get_ayon_launcher_version():
return content["__version__"]
+def is_in_ayon_launcher_process():
+ """Determine if current process is running from AYON launcher.
+
+ Returns:
+ bool: True if running from AYON launcher.
+
+ """
+ ayon_executable_path = os.path.normpath(os.environ["AYON_EXECUTABLE"])
+ executable_path = os.path.normpath(sys.executable)
+ return ayon_executable_path == executable_path
+
+
def is_running_from_build():
"""Determine if current process is running from build or code.
diff --git a/client/ayon_core/modules/clockify/__init__.py b/client/ayon_core/modules/clockify/__init__.py
deleted file mode 100644
index 98834b516c..0000000000
--- a/client/ayon_core/modules/clockify/__init__.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from .clockify_module import ClockifyModule
-
-__all__ = (
- "ClockifyModule",
-)
diff --git a/client/ayon_core/modules/deadline/abstract_submit_deadline.py b/client/ayon_core/modules/deadline/abstract_submit_deadline.py
index 00e51100bc..564966b6a0 100644
--- a/client/ayon_core/modules/deadline/abstract_submit_deadline.py
+++ b/client/ayon_core/modules/deadline/abstract_submit_deadline.py
@@ -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)
diff --git a/client/ayon_core/modules/deadline/plugins/publish/collect_pools.py b/client/ayon_core/modules/deadline/plugins/publish/collect_pools.py
index 6923c2b16b..2592d358e5 100644
--- a/client/ayon_core/modules/deadline/plugins/publish/collect_pools.py
+++ b/client/ayon_core/modules/deadline/plugins/publish/collect_pools.py
@@ -26,27 +26,32 @@ class CollectDeadlinePools(pyblish.api.InstancePlugin,
order = pyblish.api.CollectorOrder + 0.420
label = "Collect Deadline Pools"
- hosts = ["aftereffects",
- "fusion",
- "harmony"
- "nuke",
- "maya",
- "max",
- "houdini"]
+ hosts = [
+ "aftereffects",
+ "fusion",
+ "harmony",
+ "maya",
+ "max",
+ "houdini",
+ "nuke",
+ ]
- families = ["render",
- "rendering",
- "render.farm",
- "renderFarm",
- "renderlayer",
- "maxrender",
- "usdrender",
- "redshift_rop",
- "arnold_rop",
- "mantra_rop",
- "karma_rop",
- "vray_rop",
- "publish.hou"]
+ families = [
+ "render",
+ "prerender",
+ "rendering",
+ "render.farm",
+ "renderFarm",
+ "renderlayer",
+ "maxrender",
+ "usdrender",
+ "redshift_rop",
+ "arnold_rop",
+ "mantra_rop",
+ "karma_rop",
+ "vray_rop",
+ "publish.hou",
+ ]
primary_pool = None
secondary_pool = None
diff --git a/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py b/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py
index 8a6d29875a..5eb99be9a3 100644
--- a/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py
+++ b/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py
@@ -82,7 +82,10 @@ class CollectDeadlineUserCredentials(pyblish.api.InstancePlugin):
)
instance.data["deadline"]["auth"] = None
- if not require_authentication:
+ 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
# in public 'ayon_api'
diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_blender_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_blender_deadline.py
index f5805beb5c..311dbcedd5 100644
--- a/client/ayon_core/modules/deadline/plugins/publish/submit_blender_deadline.py
+++ b/client/ayon_core/modules/deadline/plugins/publish/submit_blender_deadline.py
@@ -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):
"""
diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_celaction_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_celaction_deadline.py
index 2220442dac..a17bf0c3ef 100644
--- a/client/ayon_core/modules/deadline/plugins/publish/submit_celaction_deadline.py
+++ b/client/ayon_core/modules/deadline/plugins/publish/submit_celaction_deadline.py
@@ -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(
diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py
index e9b93a47cd..6c70119628 100644
--- a/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py
+++ b/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py
@@ -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)
diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py
index 597a3cfc55..590abc3f12 100644
--- a/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py
+++ b/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py
@@ -85,7 +85,7 @@ class HoudiniSubmitDeadline(
priority = 50
chunk_size = 1
group = ""
-
+
@classmethod
def get_attribute_defs(cls):
return [
@@ -188,7 +188,7 @@ class HoudiniSubmitDeadline(
job_info.Pool = instance.data.get("primaryPool")
job_info.SecondaryPool = instance.data.get("secondaryPool")
-
+
if split_render_job and is_export_job:
job_info.Priority = attribute_values.get(
"export_priority", self.export_priority
@@ -309,6 +309,11 @@ class HoudiniSubmitDeadline(
return attr.asdict(plugin_info)
def process(self, instance):
+ if not instance.data["farm"]:
+ self.log.debug("Render on farm is disabled. "
+ "Skipping deadline submission.")
+ return
+
super(HoudiniSubmitDeadline, self).process(instance)
# TODO: Avoid the need for this logic here, needed for submit publish
diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_max_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_max_deadline.py
index e9f6c382c5..ababb01285 100644
--- a/client/ayon_core/modules/deadline/plugins/publish/submit_max_deadline.py
+++ b/client/ayon_core/modules/deadline/plugins/publish/submit_max_deadline.py
@@ -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.
diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_maya_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_maya_deadline.py
index 250dc8b7ea..f1bc1cb2be 100644
--- a/client/ayon_core/modules/deadline/plugins/publish/submit_maya_deadline.py
+++ b/client/ayon_core/modules/deadline/plugins/publish/submit_maya_deadline.py
@@ -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
diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py
index ef744ae1e1..db35c2ae67 100644
--- a/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py
+++ b/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py
@@ -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)
diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py
index ce15eda9a0..103f1355da 100644
--- a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py
+++ b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py
@@ -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)
diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py
index 06dd62e18b..64313c5c4d 100644
--- a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py
+++ b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py
@@ -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)
@@ -467,8 +468,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin,
# Inject deadline url to instances to query DL for job id for overrides
for inst in instances:
- if not "deadline" in inst:
- inst["deadline"] = {}
inst["deadline"] = instance.data["deadline"]
# publish job file
diff --git a/client/ayon_core/modules/deadline/plugins/publish/validate_deadline_pools.py b/client/ayon_core/modules/deadline/plugins/publish/validate_deadline_pools.py
index 5094b3deaf..2fb511bf51 100644
--- a/client/ayon_core/modules/deadline/plugins/publish/validate_deadline_pools.py
+++ b/client/ayon_core/modules/deadline/plugins/publish/validate_deadline_pools.py
@@ -72,7 +72,7 @@ class ValidateDeadlinePools(OptionalPyblishPluginMixin,
auth=auth,
log=self.log)
# some DL return "none" as a pool name
- if not "none" in pools:
+ if "none" not in pools:
pools.append("none")
self.log.info("Available pools: {}".format(pools))
self.pools_per_url[deadline_url] = pools
diff --git a/client/ayon_core/modules/royalrender/api.py b/client/ayon_core/modules/royalrender/api.py
index a69f88c43c..ef715811c5 100644
--- a/client/ayon_core/modules/royalrender/api.py
+++ b/client/ayon_core/modules/royalrender/api.py
@@ -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:
diff --git a/client/ayon_core/pipeline/colorspace.py b/client/ayon_core/pipeline/colorspace.py
index efa3bbf968..099616ff4a 100644
--- a/client/ayon_core/pipeline/colorspace.py
+++ b/client/ayon_core/pipeline/colorspace.py
@@ -8,16 +8,20 @@ import tempfile
import warnings
from copy import deepcopy
+import ayon_api
+
from ayon_core import AYON_CORE_ROOT
from ayon_core.settings import get_project_settings
from ayon_core.lib import (
+ filter_profiles,
StringTemplate,
run_ayon_launcher_process,
- Logger
+ Logger,
)
-from ayon_core.pipeline import Anatomy
from ayon_core.lib.transcoding import VIDEO_EXTENSIONS, IMAGE_EXTENSIONS
-
+from ayon_core.pipeline import Anatomy
+from ayon_core.pipeline.template_data import get_template_data
+from ayon_core.pipeline.load import get_representation_path_with_anatomy
log = Logger.get_logger(__name__)
@@ -32,10 +36,6 @@ class CachedData:
}
-class DeprecatedWarning(DeprecationWarning):
- pass
-
-
def deprecated(new_destination):
"""Mark functions as deprecated.
@@ -60,13 +60,13 @@ def deprecated(new_destination):
@functools.wraps(decorated_func)
def wrapper(*args, **kwargs):
- warnings.simplefilter("always", DeprecatedWarning)
+ warnings.simplefilter("always", DeprecationWarning)
warnings.warn(
(
"Call to deprecated function '{}'"
"\nFunction was moved or removed.{}"
).format(decorated_func.__name__, warning_message),
- category=DeprecatedWarning,
+ category=DeprecationWarning,
stacklevel=4
)
return decorated_func(*args, **kwargs)
@@ -81,28 +81,54 @@ def deprecated(new_destination):
def _make_temp_json_file():
"""Wrapping function for json temp file
"""
+ temporary_json_file = None
try:
# Store dumped json to temporary file
- temporary_json_file = tempfile.NamedTemporaryFile(
+ with tempfile.NamedTemporaryFile(
mode="w", suffix=".json", delete=False
- )
- temporary_json_file.close()
- temporary_json_filepath = temporary_json_file.name.replace(
- "\\", "/"
- )
+ ) as tmpfile:
+ temporary_json_filepath = tmpfile.name.replace("\\", "/")
yield temporary_json_filepath
- except IOError as _error:
+ except IOError as exc:
raise IOError(
- "Unable to create temp json file: {}".format(
- _error
- )
+ "Unable to create temp json file: {}".format(exc)
)
finally:
# Remove the temporary json
- os.remove(temporary_json_filepath)
+ if temporary_json_file is not None:
+ os.remove(temporary_json_filepath)
+
+
+def has_compatible_ocio_package():
+ """Current process has available compatible 'PyOpenColorIO'.
+
+ Returns:
+ bool: True if compatible package is available.
+
+ """
+ if CachedData.has_compatible_ocio_package is not None:
+ return CachedData.has_compatible_ocio_package
+
+ is_compatible = False
+ try:
+ import PyOpenColorIO
+
+ # Check if PyOpenColorIO is compatible
+ # - version 2.0.0 or higher is required
+ # NOTE version 1 does not have '__version__' attribute
+ if hasattr(PyOpenColorIO, "__version__"):
+ version_parts = PyOpenColorIO.__version__.split(".")
+ major = int(version_parts[0])
+ is_compatible = (major, ) >= (2, )
+ except ImportError:
+ pass
+
+ CachedData.has_compatible_ocio_package = is_compatible
+ # compatible
+ return CachedData.has_compatible_ocio_package
def get_ocio_config_script_path():
@@ -110,53 +136,58 @@ def get_ocio_config_script_path():
Returns:
str: path string
+
"""
- return os.path.normpath(
- os.path.join(
- AYON_CORE_ROOT,
- "scripts",
- "ocio_wrapper.py"
- )
+ return os.path.join(
+ os.path.normpath(AYON_CORE_ROOT),
+ "scripts",
+ "ocio_wrapper.py"
)
def get_colorspace_name_from_filepath(
- filepath, host_name, project_name,
- config_data=None, file_rules=None,
+ filepath,
+ host_name,
+ project_name,
+ config_data,
+ file_rules=None,
project_settings=None,
validate=True
):
"""Get colorspace name from filepath
Args:
- filepath (str): path string, file rule pattern is tested on it
- host_name (str): host name
- project_name (str): project name
- config_data (Optional[dict]): config path and template in dict.
- Defaults to None.
- file_rules (Optional[dict]): file rule data from settings.
- Defaults to None.
- project_settings (Optional[dict]): project settings. Defaults to None.
+ filepath (str): Path string, file rule pattern is tested on it.
+ host_name (str): Host name.
+ project_name (str): Project name.
+ config_data (dict): Config path and template in dict.
+ file_rules (Optional[dict]): File rule data from settings.
+ project_settings (Optional[dict]): Project settings.
validate (Optional[bool]): should resulting colorspace be validated
- with config file? Defaults to True.
+ with config file? Defaults to True.
Returns:
- str: name of colorspace
- """
- project_settings, config_data, file_rules = _get_context_settings(
- host_name, project_name,
- config_data=config_data, file_rules=file_rules,
- project_settings=project_settings
- )
+ Union[str, None]: name of colorspace
+ """
if not config_data:
# in case global or host color management is not enabled
return None
+ if file_rules is None:
+ if project_settings is None:
+ project_settings = get_project_settings(project_name)
+ file_rules = get_imageio_file_rules(
+ project_name, host_name, project_settings
+ )
+
# use ImageIO file rules
colorspace_name = get_imageio_file_rules_colorspace_from_filepath(
- filepath, host_name, project_name,
- config_data=config_data, file_rules=file_rules,
+ filepath,
+ host_name,
+ project_name,
+ config_data=config_data,
+ file_rules=file_rules,
project_settings=project_settings
)
@@ -182,47 +213,18 @@ def get_colorspace_name_from_filepath(
# validate matching colorspace with config
if validate:
validate_imageio_colorspace_in_config(
- config_data["path"], colorspace_name)
+ config_data["path"], colorspace_name
+ )
return colorspace_name
-# TODO: remove this in future - backward compatibility
-@deprecated("get_imageio_file_rules_colorspace_from_filepath")
-def get_imageio_colorspace_from_filepath(*args, **kwargs):
- return get_imageio_file_rules_colorspace_from_filepath(*args, **kwargs)
-
-# TODO: remove this in future - backward compatibility
-@deprecated("get_imageio_file_rules_colorspace_from_filepath")
-def get_colorspace_from_filepath(*args, **kwargs):
- return get_imageio_file_rules_colorspace_from_filepath(*args, **kwargs)
-
-
-def _get_context_settings(
- host_name, project_name,
- config_data=None, file_rules=None,
- project_settings=None
-):
- project_settings = project_settings or get_project_settings(
- project_name
- )
-
- config_data = config_data or get_imageio_config(
- project_name, host_name, project_settings)
-
- # in case host color management is not enabled
- if not config_data:
- return (None, None, None)
-
- file_rules = file_rules or get_imageio_file_rules(
- project_name, host_name, project_settings)
-
- return project_settings, config_data, file_rules
-
-
def get_imageio_file_rules_colorspace_from_filepath(
- filepath, host_name, project_name,
- config_data=None, file_rules=None,
+ filepath,
+ host_name,
+ project_name,
+ config_data,
+ file_rules=None,
project_settings=None
):
"""Get colorspace name from filepath
@@ -230,28 +232,28 @@ def get_imageio_file_rules_colorspace_from_filepath(
ImageIO Settings file rules are tested for matching rule.
Args:
- filepath (str): path string, file rule pattern is tested on it
- host_name (str): host name
- project_name (str): project name
- config_data (Optional[dict]): config path and template in dict.
- Defaults to None.
- file_rules (Optional[dict]): file rule data from settings.
- Defaults to None.
- project_settings (Optional[dict]): project settings. Defaults to None.
+ filepath (str): Path string, file rule pattern is tested on it.
+ host_name (str): Host name.
+ project_name (str): Project name.
+ config_data (dict): Config path and template in dict.
+ file_rules (Optional[dict]): File rule data from settings.
+ project_settings (Optional[dict]): Project settings.
Returns:
- str: name of colorspace
- """
- project_settings, config_data, file_rules = _get_context_settings(
- host_name, project_name,
- config_data=config_data, file_rules=file_rules,
- project_settings=project_settings
- )
+ Union[str, None]: Name of colorspace.
+ """
if not config_data:
# in case global or host color management is not enabled
return None
+ if file_rules is None:
+ if project_settings is None:
+ project_settings = get_project_settings(project_name)
+ file_rules = get_imageio_file_rules(
+ project_name, host_name, project_settings
+ )
+
# match file rule from path
colorspace_name = None
for file_rule in file_rules:
@@ -282,26 +284,48 @@ def get_config_file_rules_colorspace_from_filepath(config_path, filepath):
Returns:
Union[str, None]: matching colorspace name
+
"""
- if not compatibility_check():
- # python environment is not compatible with PyOpenColorIO
- # needs to be run in subprocess
+ if has_compatible_ocio_package():
+ result_data = _get_config_file_rules_colorspace_from_filepath(
+ config_path, filepath
+ )
+ else:
result_data = _get_wrapped_with_subprocess(
- "colorspace", "get_config_file_rules_colorspace_from_filepath",
+ "get_config_file_rules_colorspace_from_filepath",
config_path=config_path,
filepath=filepath
)
- if result_data:
- return result_data[0]
-
- # TODO: refactor this so it is not imported but part of this file
- from ayon_core.scripts.ocio_wrapper import _get_config_file_rules_colorspace_from_filepath # noqa: E501
-
- result_data = _get_config_file_rules_colorspace_from_filepath(
- config_path, filepath)
if result_data:
return result_data[0]
+ return None
+
+
+def get_config_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
+
+ """
+ if config_path not in CachedData.config_version_data:
+ if has_compatible_ocio_package():
+ version_data = _get_config_version_data(config_path)
+ else:
+ version_data = _get_wrapped_with_subprocess(
+ "get_config_version_data",
+ config_path=config_path
+ )
+ CachedData.config_version_data[config_path] = version_data
+
+ return deepcopy(CachedData.config_version_data[config_path])
def parse_colorspace_from_filepath(
@@ -344,10 +368,10 @@ def parse_colorspace_from_filepath(
pattern = "|".join(
# Allow to match spaces also as underscores because the
# integrator replaces spaces with underscores in filenames
- re.escape(colorspace) for colorspace in
+ re.escape(colorspace)
# Sort by longest first so the regex matches longer matches
# over smaller matches, e.g. matching 'Output - sRGB' over 'sRGB'
- sorted(colorspaces, key=len, reverse=True)
+ for colorspace in sorted(colorspaces, key=len, reverse=True)
)
return re.compile(pattern)
@@ -395,6 +419,7 @@ def validate_imageio_colorspace_in_config(config_path, colorspace_name):
Returns:
bool: True if exists
+
"""
colorspaces = get_ocio_config_colorspaces(config_path)["colorspaces"]
if colorspace_name not in colorspaces:
@@ -405,28 +430,10 @@ def validate_imageio_colorspace_in_config(config_path, colorspace_name):
return True
-# TODO: remove this in future - backward compatibility
-@deprecated("_get_wrapped_with_subprocess")
-def get_data_subprocess(config_path, data_type):
- """[Deprecated] Get data via subprocess
-
- Wrapper for Python 2 hosts.
+def _get_wrapped_with_subprocess(command, **kwargs):
+ """Get data via subprocess.
Args:
- config_path (str): path leading to config.ocio file
- """
- return _get_wrapped_with_subprocess(
- "config", data_type, in_path=config_path,
- )
-
-
-def _get_wrapped_with_subprocess(command_group, command, **kwargs):
- """Get data via subprocess
-
- Wrapper for Python 2 hosts.
-
- Args:
- command_group (str): command group name
command (str): command name
**kwargs: command arguments
@@ -436,14 +443,15 @@ def _get_wrapped_with_subprocess(command_group, command, **kwargs):
with _make_temp_json_file() as tmp_json_path:
# Prepare subprocess arguments
args = [
- "run", get_ocio_config_script_path(),
- command_group, command
+ "run",
+ get_ocio_config_script_path(),
+ command
]
- for key_, value_ in kwargs.items():
- args.extend(("--{}".format(key_), value_))
+ for key, value in kwargs.items():
+ args.extend(("--{}".format(key), value))
- args.append("--out_path")
+ args.append("--output_path")
args.append(tmp_json_path)
log.info("Executing: {}".format(" ".join(args)))
@@ -451,55 +459,23 @@ def _get_wrapped_with_subprocess(command_group, command, **kwargs):
run_ayon_launcher_process(*args, logger=log)
# return all colorspaces
- with open(tmp_json_path, "r") as f_:
- return json.load(f_)
+ with open(tmp_json_path, "r") as stream:
+ return json.load(stream)
-# TODO: this should be part of ocio_wrapper.py
-def compatibility_check():
- """Making sure PyOpenColorIO is importable"""
- if CachedData.has_compatible_ocio_package is not None:
- return CachedData.has_compatible_ocio_package
-
- try:
- import PyOpenColorIO # noqa: F401
- CachedData.has_compatible_ocio_package = True
- except ImportError:
- CachedData.has_compatible_ocio_package = False
-
- # compatible
- return CachedData.has_compatible_ocio_package
-
-
-# TODO: this should be part of ocio_wrapper.py
def compatibility_check_config_version(config_path, major=1, minor=None):
"""Making sure PyOpenColorIO config version is compatible"""
- if not CachedData.config_version_data.get(config_path):
- if compatibility_check():
- # TODO: refactor this so it is not imported but part of this file
- from ayon_core.scripts.ocio_wrapper import _get_version_data
-
- CachedData.config_version_data[config_path] = \
- _get_version_data(config_path)
-
- else:
- # python environment is not compatible with PyOpenColorIO
- # needs to be run in subprocess
- CachedData.config_version_data[config_path] = \
- _get_wrapped_with_subprocess(
- "config", "get_version", config_path=config_path
- )
+ version_data = get_config_version_data(config_path)
# check major version
- if CachedData.config_version_data[config_path]["major"] != major:
+ if version_data["major"] != major:
return False
# check minor version
- if minor and CachedData.config_version_data[config_path]["minor"] != minor:
+ if minor is not None and version_data["minor"] != minor:
return False
- # compatible
return True
@@ -514,23 +490,19 @@ def get_ocio_config_colorspaces(config_path):
Returns:
dict: colorspace and family in couple
+
"""
- if not CachedData.ocio_config_colorspaces.get(config_path):
- if not compatibility_check():
- # python environment is not compatible with PyOpenColorIO
- # needs to be run in subprocess
- CachedData.ocio_config_colorspaces[config_path] = \
- _get_wrapped_with_subprocess(
- "config", "get_colorspace", in_path=config_path
- )
+ if config_path not in CachedData.ocio_config_colorspaces:
+ if has_compatible_ocio_package():
+ config_colorspaces = _get_ocio_config_colorspaces(config_path)
else:
- # TODO: refactor this so it is not imported but part of this file
- from ayon_core.scripts.ocio_wrapper import _get_colorspace_data
+ config_colorspaces = _get_wrapped_with_subprocess(
+ "get_ocio_config_colorspaces",
+ config_path=config_path
+ )
+ CachedData.ocio_config_colorspaces[config_path] = config_colorspaces
- CachedData.ocio_config_colorspaces[config_path] = \
- _get_colorspace_data(config_path)
-
- return CachedData.ocio_config_colorspaces[config_path]
+ return deepcopy(CachedData.ocio_config_colorspaces[config_path])
def convert_colorspace_enumerator_item(
@@ -540,11 +512,12 @@ def convert_colorspace_enumerator_item(
"""Convert colorspace enumerator item to dictionary
Args:
- colorspace_item (str): colorspace and family in couple
- config_items (dict[str,dict]): colorspace data
+ colorspace_enum_item (str): Colorspace and family in couple.
+ config_items (dict[str,dict]): Colorspace data.
Returns:
dict: colorspace data
+
"""
if "::" not in colorspace_enum_item:
return None
@@ -603,16 +576,18 @@ def get_colorspaces_enumerator_items(
Families can be used for building menu and submenus in gui.
Args:
- config_items (dict[str,dict]): colorspace data coming from
- `get_ocio_config_colorspaces` function
- include_aliases (bool): include aliases in result
- include_looks (bool): include looks in result
- include_roles (bool): include roles in result
+ config_items (dict[str,dict]): Colorspace data coming from
+ `get_ocio_config_colorspaces` function.
+ include_aliases (Optional[bool]): Include aliases in result.
+ include_looks (Optional[bool]): Include looks in result.
+ include_roles (Optional[bool]): Include roles in result.
+ include_display_views (Optional[bool]): Include display views
+ in result.
Returns:
- list[tuple[str,str]]: colorspace and family in couple
+ list[tuple[str, str]]: Colorspace and family in couples.
+
"""
- labeled_colorspaces = []
aliases = set()
colorspaces = set()
looks = set()
@@ -622,86 +597,86 @@ def get_colorspaces_enumerator_items(
if items_type == "colorspaces":
for color_name, color_data in colorspace_items.items():
if color_data.get("aliases"):
- aliases.update([
+ aliases.update({
(
"aliases::{}".format(alias_name),
"[alias] {} ({})".format(alias_name, color_name)
)
for alias_name in color_data["aliases"]
- ])
+ })
colorspaces.add((
"{}::{}".format(items_type, color_name),
"[colorspace] {}".format(color_name)
))
elif items_type == "looks":
- looks.update([
+ looks.update({
(
"{}::{}".format(items_type, name),
"[look] {} ({})".format(name, role_data["process_space"])
)
for name, role_data in colorspace_items.items()
- ])
+ })
elif items_type == "displays_views":
- display_views.update([
+ display_views.update({
(
"{}::{}".format(items_type, name),
"[view (display)] {}".format(name)
)
for name, _ in colorspace_items.items()
- ])
+ })
elif items_type == "roles":
- roles.update([
+ roles.update({
(
"{}::{}".format(items_type, name),
"[role] {} ({})".format(name, role_data["colorspace"])
)
for name, role_data in colorspace_items.items()
- ])
+ })
- if roles and include_roles:
- roles = sorted(roles, key=lambda x: x[0])
- labeled_colorspaces.extend(roles)
+ def _sort_key_getter(item):
+ """Use colorspace for sorting.
- # add colorspaces as second so it is not first in menu
- colorspaces = sorted(colorspaces, key=lambda x: x[0])
- labeled_colorspaces.extend(colorspaces)
+ Args:
+ item (tuple[str, str]): Item with colorspace and label.
- if aliases and include_aliases:
- aliases = sorted(aliases, key=lambda x: x[0])
- labeled_colorspaces.extend(aliases)
+ Returns:
+ str: Colorspace.
- if looks and include_looks:
- looks = sorted(looks, key=lambda x: x[0])
- labeled_colorspaces.extend(looks)
+ """
+ return item[0]
- if display_views and include_display_views:
- display_views = sorted(display_views, key=lambda x: x[0])
- labeled_colorspaces.extend(display_views)
+ labeled_colorspaces = []
+ if include_roles:
+ labeled_colorspaces.extend(
+ sorted(roles, key=_sort_key_getter)
+ )
+
+ # Add colorspaces after roles, so it is not first in menu
+ labeled_colorspaces.extend(
+ sorted(colorspaces, key=_sort_key_getter)
+ )
+
+ if include_aliases:
+ labeled_colorspaces.extend(
+ sorted(aliases, key=_sort_key_getter)
+ )
+
+ if include_looks:
+ labeled_colorspaces.extend(
+ sorted(looks, key=_sort_key_getter)
+ )
+
+ if include_display_views:
+ labeled_colorspaces.extend(
+ sorted(display_views, key=_sort_key_getter)
+ )
return labeled_colorspaces
-# TODO: remove this in future - backward compatibility
-@deprecated("_get_wrapped_with_subprocess")
-def get_colorspace_data_subprocess(config_path):
- """[Deprecated] Get colorspace data via subprocess
-
- Wrapper for Python 2 hosts.
-
- Args:
- config_path (str): path leading to config.ocio file
-
- Returns:
- dict: colorspace and family in couple
- """
- return _get_wrapped_with_subprocess(
- "config", "get_colorspace", in_path=config_path
- )
-
-
def get_ocio_config_views(config_path):
"""Get all viewer data
@@ -713,212 +688,346 @@ def get_ocio_config_views(config_path):
Returns:
dict: `display/viewer` and viewer data
+
"""
- if not compatibility_check():
- # python environment is not compatible with PyOpenColorIO
- # needs to be run in subprocess
- return _get_wrapped_with_subprocess(
- "config", "get_views", in_path=config_path
- )
+ if has_compatible_ocio_package():
+ return _get_ocio_config_views(config_path)
- # TODO: refactor this so it is not imported but part of this file
- from ayon_core.scripts.ocio_wrapper import _get_views_data
-
- return _get_views_data(config_path)
-
-
-# TODO: remove this in future - backward compatibility
-@deprecated("_get_wrapped_with_subprocess")
-def get_views_data_subprocess(config_path):
- """[Deprecated] Get viewers data via subprocess
-
- Wrapper for Python 2 hosts.
-
- Args:
- config_path (str): path leading to config.ocio file
-
- Returns:
- dict: `display/viewer` and viewer data
- """
return _get_wrapped_with_subprocess(
- "config", "get_views", in_path=config_path
+ "get_ocio_config_views",
+ config_path=config_path
)
-def get_imageio_config(
+def _get_global_config_data(
project_name,
host_name,
- project_settings=None,
- anatomy_data=None,
+ anatomy,
+ template_data,
+ imageio_global,
+ folder_id,
+ log,
+):
+ """Get global config data.
+
+ Global config from core settings is using profiles that are based on
+ host name, task name and task type. The filtered profile can define 3
+ types of config sources:
+ 1. AYON ocio addon configs.
+ 2. Custom path to ocio config.
+ 3. Path to 'ocioconfig' representation on product. Name of product can be
+ defined in settings. Product name can be regex but exact match is
+ always preferred.
+
+ None is returned when no profile is found, when path
+
+ Args:
+ project_name (str): Project name.
+ host_name (str): Host name.
+ anatomy (Anatomy): Project anatomy object.
+ template_data (dict[str, Any]): Template data.
+ imageio_global (dict[str, Any]): Core imagio settings.
+ folder_id (Union[dict[str, Any], None]): Folder id.
+ log (logging.Logger): Logger object.
+
+ Returns:
+ Union[dict[str, str], None]: Config data with path and template
+ or None.
+
+ """
+ task_name = task_type = None
+ task_data = template_data.get("task")
+ if task_data:
+ task_name = task_data["name"]
+ task_type = task_data["type"]
+
+ filter_values = {
+ "task_names": task_name,
+ "task_types": task_type,
+ "host_names": host_name,
+ }
+ profile = filter_profiles(
+ imageio_global["ocio_config_profiles"], filter_values
+ )
+ if profile is None:
+ log.info(f"No config profile matched filters {str(filter_values)}")
+ return None
+
+ profile_type = profile["type"]
+ if profile_type in ("builtin_path", "custom_path"):
+ template = profile[profile_type]
+ result = StringTemplate.format_strict_template(
+ template, template_data
+ )
+ normalized_path = str(result.normalized())
+ if not os.path.exists(normalized_path):
+ log.warning(f"Path was not found '{normalized_path}'.")
+ return None
+
+ return {
+ "path": normalized_path,
+ "template": template
+ }
+
+ # TODO decide if this is the right name for representation
+ repre_name = "ocioconfig"
+
+ folder_info = template_data.get("folder")
+ if not folder_info:
+ log.warning("Folder info is missing.")
+ return None
+ folder_path = folder_info["path"]
+
+ product_name = profile["product_name"]
+ if folder_id is None:
+ folder_entity = ayon_api.get_folder_by_path(
+ project_name, folder_path, fields={"id"}
+ )
+ if not folder_entity:
+ log.warning(f"Folder entity '{folder_path}' was not found..")
+ return None
+ folder_id = folder_entity["id"]
+
+ product_entities_by_name = {
+ product_entity["name"]: product_entity
+ for product_entity in ayon_api.get_products(
+ project_name,
+ folder_ids={folder_id},
+ product_name_regex=product_name,
+ fields={"id", "name"}
+ )
+ }
+ if not product_entities_by_name:
+ log.debug(
+ f"No product entities were found for folder '{folder_path}' with"
+ f" product name filter '{product_name}'."
+ )
+ return None
+
+ # Try to use exact match first, otherwise use first available product
+ product_entity = product_entities_by_name.get(product_name)
+ if product_entity is None:
+ product_entity = next(iter(product_entities_by_name.values()))
+
+ product_name = product_entity["name"]
+ # Find last product version
+ version_entity = ayon_api.get_last_version_by_product_id(
+ project_name,
+ product_id=product_entity["id"],
+ fields={"id"}
+ )
+ if not version_entity:
+ log.info(
+ f"Product '{product_name}' does not have available any versions."
+ )
+ return None
+
+ # Find 'ocioconfig' representation entity
+ repre_entity = ayon_api.get_representation_by_name(
+ project_name,
+ representation_name=repre_name,
+ version_id=version_entity["id"],
+ )
+ if not repre_entity:
+ log.debug(
+ f"Representation '{repre_name}'"
+ f" not found on product '{product_name}'."
+ )
+ return None
+
+ path = get_representation_path_with_anatomy(repre_entity, anatomy)
+ template = repre_entity["attrib"]["template"]
+ return {
+ "path": path,
+ "template": template,
+ }
+
+
+def get_imageio_config_preset(
+ project_name,
+ folder_path,
+ task_name,
+ host_name,
anatomy=None,
- env=None
+ project_settings=None,
+ template_data=None,
+ env=None,
+ folder_id=None,
):
"""Returns config data from settings
- Config path is formatted in `path` key
- and original settings input is saved into `template` key.
+ Output contains 'path' key and 'template' key holds its template.
+
+ Template data can be prepared with 'get_template_data'.
Args:
- project_name (str): project name
- host_name (str): host name
+ project_name (str): Project name.
+ folder_path (str): Folder path.
+ task_name (str): Task name.
+ host_name (str): Host name.
+ anatomy (Optional[Anatomy]): Project anatomy object.
project_settings (Optional[dict]): Project settings.
- anatomy_data (Optional[dict]): anatomy formatting data.
- anatomy (Optional[Anatomy]): Anatomy object.
- env (Optional[dict]): Environment variables.
+ template_data (Optional[dict]): Template data used for
+ template formatting.
+ env (Optional[dict]): Environment variables. Environments are used
+ for template formatting too. Values from 'os.environ' are used
+ when not provided.
+ folder_id (Optional[str]): Folder id. Is used only when config path
+ is received from published representation. Is autofilled when
+ not provided.
Returns:
dict: config path data or empty dict
+
"""
- project_settings = project_settings or get_project_settings(project_name)
- anatomy = anatomy or Anatomy(project_name)
-
- if not anatomy_data:
- from ayon_core.pipeline.context_tools import (
- get_current_context_template_data)
- anatomy_data = get_current_context_template_data()
-
- formatting_data = deepcopy(anatomy_data)
-
- # Add project roots to anatomy data
- formatting_data["root"] = anatomy.roots
- formatting_data["platform"] = platform.system().lower()
+ if not project_settings:
+ project_settings = get_project_settings(project_name)
# Get colorspace settings
imageio_global, imageio_host = _get_imageio_settings(
- project_settings, host_name)
+ project_settings, host_name
+ )
+ # Global color management must be enabled to be able to use host settings
+ if not imageio_global["activate_global_color_management"]:
+ log.info("Colorspace management is disabled globally.")
+ return {}
# Host 'ocio_config' is optional
host_ocio_config = imageio_host.get("ocio_config") or {}
-
- # Global color management must be enabled to be able to use host settings
- activate_color_management = imageio_global.get(
- "activate_global_color_management")
- # TODO: remove this in future - backward compatibility
- # For already saved overrides from previous version look for 'enabled'
- # on host settings.
- if activate_color_management is None:
- activate_color_management = host_ocio_config.get("enabled", False)
-
- if not activate_color_management:
- # if global settings are disabled return empty dict because
- # it is expected that no colorspace management is needed
- log.info("Colorspace management is disabled globally.")
- return {}
+ # TODO remove
+ # - backward compatibility when host settings had only 'enabled' flag
+ # the flag was split into 'activate_global_color_management'
+ # and 'override_global_config'
+ host_ocio_config_enabled = host_ocio_config.get("enabled", False)
# Check if host settings group is having 'activate_host_color_management'
# - if it does not have activation key then default it to True so it uses
# global settings
- # This is for backward compatibility.
- # TODO: in future rewrite this to be more explicit
activate_host_color_management = imageio_host.get(
- "activate_host_color_management")
-
- # TODO: remove this in future - backward compatibility
+ "activate_host_color_management"
+ )
if activate_host_color_management is None:
- activate_host_color_management = host_ocio_config.get("enabled", False)
+ activate_host_color_management = host_ocio_config_enabled
if not activate_host_color_management:
# if host settings are disabled return False because
# it is expected that no colorspace management is needed
log.info(
- "Colorspace management for host '{}' is disabled.".format(
- host_name)
+ f"Colorspace management for host '{host_name}' is disabled."
)
return {}
- # get config path from either global or host settings
- # depending on override flag
+ project_entity = None
+ if anatomy is None:
+ project_entity = ayon_api.get_project(project_name)
+ anatomy = Anatomy(project_name, project_entity=project_entity)
+
+ if env is None:
+ env = dict(os.environ.items())
+
+ if template_data:
+ template_data = deepcopy(template_data)
+ else:
+ if not project_entity:
+ project_entity = ayon_api.get_project(project_name)
+
+ folder_entity = task_entity = folder_id = None
+ if folder_path:
+ folder_entity = ayon_api.get_folder_by_path(
+ project_name, folder_path
+ )
+ folder_id = folder_entity["id"]
+
+ if folder_id and task_name:
+ task_entity = ayon_api.get_task_by_name(
+ project_name, folder_id, task_name
+ )
+ template_data = get_template_data(
+ project_entity,
+ folder_entity,
+ task_entity,
+ host_name,
+ project_settings,
+ )
+
+ # Add project roots to anatomy data
+ template_data["root"] = anatomy.roots
+ template_data["platform"] = platform.system().lower()
+
+ # Add environment variables to template data
+ template_data.update(env)
+
+ # Get config path from core or host settings
+ # - based on override flag in host settings
# TODO: in future rewrite this to be more explicit
override_global_config = host_ocio_config.get("override_global_config")
if override_global_config is None:
- # for already saved overrides from previous version
- # TODO: remove this in future - backward compatibility
- override_global_config = host_ocio_config.get("enabled")
+ override_global_config = host_ocio_config_enabled
- if override_global_config:
- config_data = _get_config_data(
- host_ocio_config["filepath"], formatting_data, env
+ if not override_global_config:
+ config_data = _get_global_config_data(
+ project_name,
+ host_name,
+ anatomy,
+ template_data,
+ imageio_global,
+ folder_id,
+ log,
)
else:
- # get config path from global
- config_global = imageio_global["ocio_config"]
- config_data = _get_config_data(
- config_global["filepath"], formatting_data, env
+ config_data = _get_host_config_data(
+ host_ocio_config["filepath"], template_data
)
if not config_data:
raise FileExistsError(
- "No OCIO config found in settings. It is "
- "either missing or there is typo in path inputs"
+ "No OCIO config found in settings. It is"
+ " either missing or there is typo in path inputs"
)
return config_data
-def _get_config_data(path_list, anatomy_data, env=None):
+def _get_host_config_data(templates, template_data):
"""Return first existing path in path list.
- If template is used in path inputs,
- then it is formatted by anatomy data
- and environment variables
+ Use template data to fill possible formatting in paths.
Args:
- path_list (list[str]): list of abs paths
- anatomy_data (dict): formatting data
- env (Optional[dict]): Environment variables.
+ templates (list[str]): List of templates to config paths.
+ template_data (dict): Template data used to format templates.
Returns:
- dict: config data
+ Union[dict, None]: Config data or 'None' if templates are empty
+ or any path exists.
+
"""
- formatting_data = deepcopy(anatomy_data)
-
- environment_vars = env or dict(**os.environ)
-
- # format the path for potential env vars
- formatting_data.update(environment_vars)
-
- # first try host config paths
- for path_ in path_list:
- formatted_path = _format_path(path_, formatting_data)
-
- if not os.path.exists(formatted_path):
+ for template in templates:
+ formatted_path = StringTemplate.format_template(
+ template, template_data
+ )
+ if not formatted_path.solved:
continue
- return {
- "path": os.path.normpath(formatted_path),
- "template": path_
- }
-
-
-def _format_path(template_path, formatting_data):
- """Single template path formatting.
-
- Args:
- template_path (str): template string
- formatting_data (dict): data to be used for
- template formatting
-
- Returns:
- str: absolute formatted path
- """
- # format path for anatomy keys
- formatted_path = StringTemplate(template_path).format(
- formatting_data)
-
- return os.path.abspath(formatted_path)
+ path = os.path.abspath(formatted_path)
+ if os.path.exists(path):
+ return {
+ "path": os.path.normpath(path),
+ "template": template
+ }
def get_imageio_file_rules(project_name, host_name, project_settings=None):
"""Get ImageIO File rules from project settings
Args:
- project_name (str): project name
- host_name (str): host name
- project_settings (dict, optional): project settings.
- Defaults to None.
+ project_name (str): Project name.
+ host_name (str): Host name.
+ project_settings (Optional[dict]): Project settings.
Returns:
list[dict[str, Any]]: file rules data
+
"""
project_settings = project_settings or get_project_settings(project_name)
@@ -960,7 +1069,7 @@ def get_remapped_colorspace_to_native(
"""Return native colorspace name.
Args:
- ocio_colorspace_name (str | None): ocio colorspace name
+ ocio_colorspace_name (str | None): OCIO colorspace name.
host_name (str): Host name.
imageio_host_settings (dict[str, Any]): ImageIO host settings.
@@ -968,16 +1077,15 @@ def get_remapped_colorspace_to_native(
Union[str, None]: native colorspace name defined in remapping or None
"""
- CachedData.remapping.setdefault(host_name, {})
- if CachedData.remapping[host_name].get("to_native") is None:
+ host_mapping = CachedData.remapping.setdefault(host_name, {})
+ if "to_native" not in host_mapping:
remapping_rules = imageio_host_settings["remapping"]["rules"]
- CachedData.remapping[host_name]["to_native"] = {
+ host_mapping["to_native"] = {
rule["ocio_name"]: rule["host_native_name"]
for rule in remapping_rules
}
- return CachedData.remapping[host_name]["to_native"].get(
- ocio_colorspace_name)
+ return host_mapping["to_native"].get(ocio_colorspace_name)
def get_remapped_colorspace_from_native(
@@ -992,30 +1100,29 @@ def get_remapped_colorspace_from_native(
Returns:
Union[str, None]: Ocio colorspace name defined in remapping or None.
- """
- CachedData.remapping.setdefault(host_name, {})
- if CachedData.remapping[host_name].get("from_native") is None:
+ """
+ host_mapping = CachedData.remapping.setdefault(host_name, {})
+ if "from_native" not in host_mapping:
remapping_rules = imageio_host_settings["remapping"]["rules"]
- CachedData.remapping[host_name]["from_native"] = {
+ host_mapping["from_native"] = {
rule["host_native_name"]: rule["ocio_name"]
for rule in remapping_rules
}
- return CachedData.remapping[host_name]["from_native"].get(
- host_native_colorspace_name)
+ return host_mapping["from_native"].get(host_native_colorspace_name)
def _get_imageio_settings(project_settings, host_name):
"""Get ImageIO settings for global and host
Args:
- project_settings (dict): project settings.
- Defaults to None.
- host_name (str): host name
+ project_settings (dict[str, Any]): Project settings.
+ host_name (str): Host name.
Returns:
- tuple[dict, dict]: image io settings for global and host
+ tuple[dict, dict]: Image io settings for global and host.
+
"""
# get image io from global and host_name
imageio_global = project_settings["core"]["imageio"]
@@ -1033,27 +1140,41 @@ def get_colorspace_settings_from_publish_context(context_data):
Returns:
tuple | bool: config, file rules or None
+
"""
if "imageioSettings" in context_data and context_data["imageioSettings"]:
return context_data["imageioSettings"]
project_name = context_data["projectName"]
+ folder_path = context_data["folderPath"]
+ task_name = context_data["task"]
host_name = context_data["hostName"]
- anatomy_data = context_data["anatomyData"]
- project_settings_ = context_data["project_settings"]
+ anatomy = context_data["anatomy"]
+ template_data = context_data["anatomyData"]
+ project_settings = context_data["project_settings"]
+ folder_id = None
+ folder_entity = context_data.get("folderEntity")
+ if folder_entity:
+ folder_id = folder_entity["id"]
- config_data = get_imageio_config(
- project_name, host_name,
- project_settings=project_settings_,
- anatomy_data=anatomy_data
+ config_data = get_imageio_config_preset(
+ project_name,
+ folder_path,
+ task_name,
+ host_name,
+ anatomy=anatomy,
+ project_settings=project_settings,
+ template_data=template_data,
+ folder_id=folder_id,
)
# caching invalid state, so it's not recalculated all the time
file_rules = None
if config_data:
file_rules = get_imageio_file_rules(
- project_name, host_name,
- project_settings=project_settings_
+ project_name,
+ host_name,
+ project_settings=project_settings
)
# caching settings for future instance processing
@@ -1063,18 +1184,13 @@ def get_colorspace_settings_from_publish_context(context_data):
def set_colorspace_data_to_representation(
- representation, context_data,
+ representation,
+ context_data,
colorspace=None,
log=None
):
"""Sets colorspace data to representation.
- Args:
- representation (dict): publishing representation
- context_data (publish.Context.data): publishing context data
- colorspace (str, optional): colorspace name. Defaults to None.
- log (logging.Logger, optional): logger instance. Defaults to None.
-
Example:
```
{
@@ -1089,6 +1205,12 @@ def set_colorspace_data_to_representation(
}
```
+ Args:
+ representation (dict): publishing representation
+ context_data (publish.Context.data): publishing context data
+ colorspace (Optional[str]): Colorspace name.
+ log (Optional[logging.Logger]): logger instance.
+
"""
log = log or Logger.get_logger(__name__)
@@ -1122,12 +1244,15 @@ def set_colorspace_data_to_representation(
filename = filename[0]
# get matching colorspace from rules
- colorspace = colorspace or get_imageio_colorspace_from_filepath(
- filename, host_name, project_name,
- config_data=config_data,
- file_rules=file_rules,
- project_settings=project_settings
- )
+ if colorspace is None:
+ colorspace = get_imageio_file_rules_colorspace_from_filepath(
+ filename,
+ host_name,
+ project_name,
+ config_data=config_data,
+ file_rules=file_rules,
+ project_settings=project_settings
+ )
# infuse data to representation
if colorspace:
@@ -1149,47 +1274,330 @@ def get_display_view_colorspace_name(config_path, display, view):
view (str): view name e.g. "sRGB"
Returns:
- view color space name (str) e.g. "Output - sRGB"
+ str: View color space name. e.g. "Output - sRGB"
+
"""
-
- if not compatibility_check():
- # python environment is not compatible with PyOpenColorIO
- # needs to be run in subprocess
- return get_display_view_colorspace_subprocess(config_path,
- display, view)
-
- from ayon_core.scripts.ocio_wrapper import _get_display_view_colorspace_name # noqa
-
- return _get_display_view_colorspace_name(config_path, display, view)
+ if has_compatible_ocio_package():
+ return _get_display_view_colorspace_name(
+ config_path, display, view
+ )
+ return _get_wrapped_with_subprocess(
+ "get_display_view_colorspace_name",
+ config_path=config_path,
+ display=display,
+ view=view
+ )
-def get_display_view_colorspace_subprocess(config_path, display, view):
- """Returns the colorspace attribute of the (display, view) pair
- via subprocess.
+# --- Implementation of logic using 'PyOpenColorIO' ---
+def _get_ocio_config(config_path):
+ """Helper function to create OCIO config object.
+
+ Args:
+ config_path (str): Path to config.
+
+ Returns:
+ PyOpenColorIO.Config: OCIO config for the confing path.
+
+ """
+ import PyOpenColorIO
+
+ config_path = os.path.abspath(config_path)
+
+ if not os.path.isfile(config_path):
+ raise IOError("Input path should be `config.ocio` file")
+
+ return PyOpenColorIO.Config.CreateFromFile(config_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 = _get_ocio_config(config_path)
+
+ # TODO: use `parseColorSpaceFromString` instead if ocio v1
+ return config.getColorSpaceFromFilepath(str(filepath))
+
+
+def _get_config_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 = _get_ocio_config(config_path)
+
+ return {
+ "major": config.getMajorVersion(),
+ "minor": config.getMinorVersion()
+ }
+
+
+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"
+ str: view color space name e.g. "Output - sRGB"
+
+ """
+ config = _get_ocio_config(config_path)
+ return config.getDisplayViewColorSpaceName(display, view)
+
+
+def _get_ocio_config_colorspaces(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 = _get_ocio_config(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
+
+
+def _get_ocio_config_views(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 = _get_ocio_config(config_path)
+
+ output = {}
+ 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 == "":
+ colorspace = display
+
+ output[f"{display}/{view}"] = {
+ "display": display,
+ "view": view,
+ "colorspace": colorspace
+ }
+
+ return output
+
+
+# --- Current context functions ---
+def get_current_context_imageio_config_preset(
+ anatomy=None,
+ project_settings=None,
+ template_data=None,
+ env=None,
+):
+ """Get ImageIO config preset for current context.
+
+ Args:
+ anatomy (Optional[Anatomy]): Current project anatomy.
+ project_settings (Optional[dict[str, Any]]): Current project settings.
+ template_data (Optional[dict[str, Any]]): Prepared template data
+ for current context.
+ env (Optional[dict[str, str]]): Custom environment variable values.
+
+ Returns:
+ dict: ImageIO config preset.
+
+ """
+ from .context_tools import get_current_context, get_current_host_name
+
+ context = get_current_context()
+ host_name = get_current_host_name()
+ return get_imageio_config_preset(
+ context["project_name"],
+ context["folder_path"],
+ context["task_name"],
+ host_name,
+ anatomy=anatomy,
+ project_settings=project_settings,
+ template_data=template_data,
+ env=env,
+ )
+
+
+# --- Deprecated functions ---
+@deprecated("has_compatible_ocio_package")
+def compatibility_check():
+ """Making sure PyOpenColorIO is importable
+
+ Deprecated:
+ Deprecated since '0.3.2'. Use `has_compatible_ocio_package` instead.
"""
- with _make_temp_json_file() as tmp_json_path:
- # Prepare subprocess arguments
- args = [
- "run", get_ocio_config_script_path(),
- "config", "get_display_view_colorspace_name",
- "--in_path", config_path,
- "--out_path", tmp_json_path,
- "--display", display,
- "--view", view
- ]
- log.debug("Executing: {}".format(" ".join(args)))
+ return has_compatible_ocio_package()
- run_ayon_launcher_process(*args, logger=log)
- # return default view colorspace name
- with open(tmp_json_path, "r") as f:
- return json.load(f)
+@deprecated("get_imageio_file_rules_colorspace_from_filepath")
+def get_imageio_colorspace_from_filepath(*args, **kwargs):
+ return get_imageio_file_rules_colorspace_from_filepath(*args, **kwargs)
+
+
+@deprecated("get_imageio_file_rules_colorspace_from_filepath")
+def get_colorspace_from_filepath(*args, **kwargs):
+ return get_imageio_file_rules_colorspace_from_filepath(*args, **kwargs)
+
+
+@deprecated("_get_wrapped_with_subprocess")
+def get_colorspace_data_subprocess(config_path):
+ """[Deprecated] Get colorspace data via subprocess
+
+ Deprecated:
+ Deprecated since OpenPype. Use `_get_wrapped_with_subprocess` instead.
+
+ Args:
+ config_path (str): path leading to config.ocio file
+
+ Returns:
+ dict: colorspace and family in couple
+ """
+ return _get_wrapped_with_subprocess(
+ "get_ocio_config_colorspaces",
+ config_path=config_path
+ )
+
+
+@deprecated("_get_wrapped_with_subprocess")
+def get_views_data_subprocess(config_path):
+ """[Deprecated] Get viewers data via subprocess
+
+ Deprecated:
+ Deprecated since OpenPype. Use `_get_wrapped_with_subprocess` instead.
+
+ Args:
+ config_path (str): path leading to config.ocio file
+
+ Returns:
+ dict: `display/viewer` and viewer data
+
+ """
+ return _get_wrapped_with_subprocess(
+ "get_ocio_config_views",
+ config_path=config_path
+ )
+
+
+@deprecated("get_imageio_config_preset")
+def get_imageio_config(
+ project_name,
+ host_name,
+ project_settings=None,
+ anatomy_data=None,
+ anatomy=None,
+ env=None
+):
+ """Returns config data from settings
+
+ Config path is formatted in `path` key
+ and original settings input is saved into `template` key.
+
+ Deprecated:
+ Deprecated since '0.3.1' . Use `get_imageio_config_preset` instead.
+
+ Args:
+ project_name (str): project name
+ host_name (str): host name
+ project_settings (Optional[dict]): Project settings.
+ anatomy_data (Optional[dict]): anatomy formatting data.
+ anatomy (Optional[Anatomy]): Anatomy object.
+ env (Optional[dict]): Environment variables.
+
+ Returns:
+ dict: config path data or empty dict
+
+ """
+ if not anatomy_data:
+ from .context_tools import get_current_context_template_data
+ anatomy_data = get_current_context_template_data()
+
+ task_name = anatomy_data.get("task", {}).get("name")
+ folder_path = anatomy_data.get("folder", {}).get("path")
+ return get_imageio_config_preset(
+ project_name,
+ folder_path,
+ task_name,
+ host_name,
+ anatomy=anatomy,
+ project_settings=project_settings,
+ template_data=anatomy_data,
+ env=env,
+ )
diff --git a/client/ayon_core/pipeline/context_tools.py b/client/ayon_core/pipeline/context_tools.py
index 33567d7280..c32d04c44c 100644
--- a/client/ayon_core/pipeline/context_tools.py
+++ b/client/ayon_core/pipeline/context_tools.py
@@ -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.
diff --git a/client/ayon_core/pipeline/template_data.py b/client/ayon_core/pipeline/template_data.py
index 526c7d35c5..d5f06d6a59 100644
--- a/client/ayon_core/pipeline/template_data.py
+++ b/client/ayon_core/pipeline/template_data.py
@@ -73,8 +73,8 @@ def get_folder_template_data(folder_entity, project_name):
- 'parent' - direct parent name, project name used if is under
project
- Required document fields:
- Folder: 'path' -> Plan to require: 'folderType'
+ Required entity fields:
+ Folder: 'path', 'folderType'
Args:
folder_entity (Dict[str, Any]): Folder entity.
@@ -101,6 +101,8 @@ def get_folder_template_data(folder_entity, project_name):
return {
"folder": {
"name": folder_name,
+ "type": folder_entity["folderType"],
+ "path": path,
},
"asset": folder_name,
"hierarchy": hierarchy,
diff --git a/client/ayon_core/plugins/publish/collect_anatomy_instance_data.py b/client/ayon_core/plugins/publish/collect_anatomy_instance_data.py
index f8cc81e718..ad5a5d43fc 100644
--- a/client/ayon_core/plugins/publish/collect_anatomy_instance_data.py
+++ b/client/ayon_core/plugins/publish/collect_anatomy_instance_data.py
@@ -33,6 +33,7 @@ import collections
import pyblish.api
import ayon_api
+from ayon_core.pipeline.template_data import get_folder_template_data
from ayon_core.pipeline.version_start import get_versioning_start
@@ -383,24 +384,11 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin):
# - 'folder', 'hierarchy', 'parent', 'folder'
folder_entity = instance.data.get("folderEntity")
if folder_entity:
- folder_name = folder_entity["name"]
- folder_path = folder_entity["path"]
- hierarchy_parts = folder_path.split("/")
- hierarchy_parts.pop(0)
- hierarchy_parts.pop(-1)
- parent_name = project_entity["name"]
- if hierarchy_parts:
- parent_name = hierarchy_parts[-1]
-
- hierarchy = "/".join(hierarchy_parts)
- anatomy_data.update({
- "asset": folder_name,
- "hierarchy": hierarchy,
- "parent": parent_name,
- "folder": {
- "name": folder_name,
- },
- })
+ folder_data = get_folder_template_data(
+ folder_entity,
+ project_entity["name"]
+ )
+ anatomy_data.update(folder_data)
return
if instance.data.get("newAssetPublishing"):
@@ -418,6 +406,11 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin):
"parent": parent_name,
"folder": {
"name": folder_name,
+ "path": instance.data["folderPath"],
+ # TODO get folder type from hierarchy
+ # Using 'Shot' is current default behavior of editorial
+ # (or 'newAssetPublishing') publishing.
+ "type": "Shot",
},
})
diff --git a/client/ayon_core/plugins/publish/integrate.py b/client/ayon_core/plugins/publish/integrate.py
index 9ae96e1a20..865b566e6e 100644
--- a/client/ayon_core/plugins/publish/integrate.py
+++ b/client/ayon_core/plugins/publish/integrate.py
@@ -108,69 +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",
- ]
default_template_name = "publish"
@@ -360,7 +297,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin):
# Compute the resource file infos once (files belonging to the
# version instance instead of an individual representation) so
- # we can re-use those file infos per representation
+ # we can reuse those file infos per representation
resource_file_infos = self.get_files_info(
resource_destinations, anatomy
)
diff --git a/client/ayon_core/plugins/publish/validate_containers.py b/client/ayon_core/plugins/publish/validate_containers.py
index bd21ec9693..520e7a7ce9 100644
--- a/client/ayon_core/plugins/publish/validate_containers.py
+++ b/client/ayon_core/plugins/publish/validate_containers.py
@@ -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
diff --git a/client/ayon_core/plugins/publish/validate_version.py b/client/ayon_core/plugins/publish/validate_version.py
index 9031194e8c..25a5757330 100644
--- a/client/ayon_core/plugins/publish/validate_version.py
+++ b/client/ayon_core/plugins/publish/validate_version.py
@@ -1,8 +1,10 @@
import pyblish.api
-from ayon_core.pipeline.publish import PublishValidationError
+from ayon_core.pipeline.publish import (
+ PublishValidationError, OptionalPyblishPluginMixin
+)
-class ValidateVersion(pyblish.api.InstancePlugin):
+class ValidateVersion(pyblish.api.InstancePlugin, OptionalPyblishPluginMixin):
"""Validate instance version.
AYON does not allow overwriting previously published versions.
@@ -18,6 +20,9 @@ class ValidateVersion(pyblish.api.InstancePlugin):
active = True
def process(self, instance):
+ if not self.is_active(instance.data):
+ return
+
version = instance.data.get("version")
latest_version = instance.data.get("latestVersion")
diff --git a/client/ayon_core/scripts/ocio_wrapper.py b/client/ayon_core/scripts/ocio_wrapper.py
index 0a78e33c1f..0414fc59ce 100644
--- a/client/ayon_core/scripts/ocio_wrapper.py
+++ b/client/ayon_core/scripts/ocio_wrapper.py
@@ -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 *args
- """
- pass # noqa: WPS100
-
-
-@main.group()
-def colorspace():
- """Colorspace related commands group
-
- Example of use:
- > pyton.exe ./ocio_wrapper.py config *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= --out_path=
+ --config_path --output_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= --out_path=
+ --config_path --output
"""
- 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 == "":
- 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= --out_path=
+ --config_path --output_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= --filepath= --out_path=
+ --config_path --filepath --output_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= \
- --out_path= --display= --view=
+ get_display_view_colorspace_name --config_path \
+ --output_path --display --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()
diff --git a/client/ayon_core/tools/common_models/projects.py b/client/ayon_core/tools/common_models/projects.py
index 19a38bee21..89dd881a10 100644
--- a/client/ayon_core/tools/common_models/projects.py
+++ b/client/ayon_core/tools/common_models/projects.py
@@ -5,7 +5,7 @@ import ayon_api
import six
from ayon_core.style import get_default_entity_icon_color
-from ayon_core.lib import CacheItem
+from ayon_core.lib import CacheItem, NestedCacheItem
PROJECTS_MODEL_SENDER = "projects.model"
@@ -17,6 +17,49 @@ class AbstractHierarchyController:
pass
+class StatusItem:
+ """Item representing status of project.
+
+ Args:
+ name (str): Status name ("Not ready").
+ color (str): Status color in hex ("#434a56").
+ short (str): Short status name ("NRD").
+ icon (str): Icon name in MaterialIcons ("fiber_new").
+ state (Literal["not_started", "in_progress", "done", "blocked"]):
+ Status state.
+
+ """
+ def __init__(self, name, color, short, icon, state):
+ self.name = name
+ self.color = color
+ self.short = short
+ self.icon = icon
+ self.state = state
+
+ def to_data(self):
+ return {
+ "name": self.name,
+ "color": self.color,
+ "short": self.short,
+ "icon": self.icon,
+ "state": self.state,
+ }
+
+ @classmethod
+ def from_data(cls, data):
+ return cls(**data)
+
+ @classmethod
+ def from_project_item(cls, status_data):
+ return cls(
+ name=status_data["name"],
+ color=status_data["color"],
+ short=status_data["shortName"],
+ icon=status_data["icon"],
+ state=status_data["state"],
+ )
+
+
class ProjectItem:
"""Item representing folder entity on a server.
@@ -40,6 +83,23 @@ class ProjectItem:
}
self.icon = icon
+ @classmethod
+ def from_entity(cls, project_entity):
+ """Creates folder item from entity.
+
+ Args:
+ project_entity (dict[str, Any]): Project entity.
+
+ Returns:
+ ProjectItem: Project item.
+
+ """
+ return cls(
+ project_entity["name"],
+ project_entity["active"],
+ project_entity["library"],
+ )
+
def to_data(self):
"""Converts folder item to data.
@@ -79,7 +139,7 @@ def _get_project_items_from_entitiy(projects):
"""
return [
- ProjectItem(project["name"], project["active"], project["library"])
+ ProjectItem.from_entity(project)
for project in projects
]
@@ -87,18 +147,29 @@ def _get_project_items_from_entitiy(projects):
class ProjectsModel(object):
def __init__(self, controller):
self._projects_cache = CacheItem(default_factory=list)
- self._project_items_by_name = {}
- self._projects_by_name = {}
+ self._project_statuses_cache = NestedCacheItem(
+ levels=1, default_factory=list
+ )
+ self._projects_by_name = NestedCacheItem(
+ levels=1, default_factory=list
+ )
self._is_refreshing = False
self._controller = controller
def reset(self):
self._projects_cache.reset()
- self._project_items_by_name = {}
- self._projects_by_name = {}
+ self._project_statuses_cache.reset()
+ self._projects_by_name.reset()
def refresh(self):
+ """Refresh project items.
+
+ This method will requery list of ProjectItem returned by
+ 'get_project_items'.
+
+ To reset all cached items use 'reset' method.
+ """
self._refresh_projects_cache()
def get_project_items(self, sender):
@@ -117,12 +188,51 @@ class ProjectsModel(object):
return self._projects_cache.get_data()
def get_project_entity(self, project_name):
- if project_name not in self._projects_by_name:
+ """Get project entity.
+
+ Args:
+ project_name (str): Project name.
+
+ Returns:
+ Union[dict[str, Any], None]: Project entity or None if project
+ was not found by name.
+
+ """
+ project_cache = self._projects_by_name[project_name]
+ if not project_cache.is_valid:
entity = None
if project_name:
entity = ayon_api.get_project(project_name)
- self._projects_by_name[project_name] = entity
- return self._projects_by_name[project_name]
+ project_cache.update_data(entity)
+ return project_cache.get_data()
+
+ def get_project_status_items(self, project_name, sender):
+ """Get project status items.
+
+ Args:
+ project_name (str): Project name.
+ sender (Union[str, None]): Name of sender who asked for items.
+
+ Returns:
+ list[StatusItem]: Status items for project.
+
+ """
+ statuses_cache = self._project_statuses_cache[project_name]
+ if not statuses_cache.is_valid:
+ with self._project_statuses_refresh_event_manager(
+ sender, project_name
+ ):
+ project_entity = None
+ if project_name:
+ project_entity = self.get_project_entity(project_name)
+ statuses = []
+ if project_entity:
+ statuses = [
+ StatusItem.from_project_item(status)
+ for status in project_entity["statuses"]
+ ]
+ statuses_cache.update_data(statuses)
+ return statuses_cache.get_data()
@contextlib.contextmanager
def _project_refresh_event_manager(self, sender):
@@ -143,6 +253,23 @@ class ProjectsModel(object):
)
self._is_refreshing = False
+ @contextlib.contextmanager
+ def _project_statuses_refresh_event_manager(self, sender, project_name):
+ self._controller.emit_event(
+ "projects.statuses.refresh.started",
+ {"sender": sender, "project_name": project_name},
+ PROJECTS_MODEL_SENDER
+ )
+ try:
+ yield
+
+ finally:
+ self._controller.emit_event(
+ "projects.statuses.refresh.finished",
+ {"sender": sender, "project_name": project_name},
+ PROJECTS_MODEL_SENDER
+ )
+
def _refresh_projects_cache(self, sender=None):
if self._is_refreshing:
return None
diff --git a/client/ayon_core/tools/launcher/ui/actions_widget.py b/client/ayon_core/tools/launcher/ui/actions_widget.py
index a225827418..2ffce13292 100644
--- a/client/ayon_core/tools/launcher/ui/actions_widget.py
+++ b/client/ayon_core/tools/launcher/ui/actions_widget.py
@@ -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()
diff --git a/client/ayon_core/tools/loader/abstract.py b/client/ayon_core/tools/loader/abstract.py
index 7a7d335092..509db4d037 100644
--- a/client/ayon_core/tools/loader/abstract.py
+++ b/client/ayon_core/tools/loader/abstract.py
@@ -114,6 +114,7 @@ class VersionItem:
thumbnail_id (Union[str, None]): Thumbnail id.
published_time (Union[str, None]): Published time in format
'%Y%m%dT%H%M%SZ'.
+ status (Union[str, None]): Status name.
author (Union[str, None]): Author.
frame_range (Union[str, None]): Frame range.
duration (Union[int, None]): Duration.
@@ -132,6 +133,7 @@ class VersionItem:
thumbnail_id,
published_time,
author,
+ status,
frame_range,
duration,
handles,
@@ -146,6 +148,7 @@ class VersionItem:
self.is_hero = is_hero
self.published_time = published_time
self.author = author
+ self.status = status
self.frame_range = frame_range
self.duration = duration
self.handles = handles
@@ -185,6 +188,7 @@ class VersionItem:
"is_hero": self.is_hero,
"published_time": self.published_time,
"author": self.author,
+ "status": self.status,
"frame_range": self.frame_range,
"duration": self.duration,
"handles": self.handles,
@@ -488,6 +492,27 @@ class FrontendLoaderController(_BaseLoaderController):
pass
+ @abstractmethod
+ def get_project_status_items(self, project_name, sender=None):
+ """Items for all projects available on server.
+
+ Triggers event topics "projects.statuses.refresh.started" and
+ "projects.statuses.refresh.finished" with data:
+ {
+ "sender": sender,
+ "project_name": project_name
+ }
+
+ Args:
+ project_name (Union[str, None]): Project name.
+ sender (Optional[str]): Sender who requested the items.
+
+ Returns:
+ list[StatusItem]: List of status items.
+ """
+
+ pass
+
@abstractmethod
def get_product_items(self, project_name, folder_ids, sender=None):
"""Product items for folder ids.
diff --git a/client/ayon_core/tools/loader/control.py b/client/ayon_core/tools/loader/control.py
index 0c9bb369c7..35188369c2 100644
--- a/client/ayon_core/tools/loader/control.py
+++ b/client/ayon_core/tools/loader/control.py
@@ -180,6 +180,11 @@ class LoaderController(BackendLoaderController, FrontendLoaderController):
def get_project_items(self, sender=None):
return self._projects_model.get_project_items(sender)
+ def get_project_status_items(self, project_name, sender=None):
+ return self._projects_model.get_project_status_items(
+ project_name, sender
+ )
+
def get_folder_items(self, project_name, sender=None):
return self._hierarchy_model.get_folder_items(project_name, sender)
diff --git a/client/ayon_core/tools/loader/models/products.py b/client/ayon_core/tools/loader/models/products.py
index a3bbc30a09..c9325c4480 100644
--- a/client/ayon_core/tools/loader/models/products.py
+++ b/client/ayon_core/tools/loader/models/products.py
@@ -58,6 +58,7 @@ def version_item_from_entity(version):
thumbnail_id=version["thumbnailId"],
published_time=published_time,
author=author,
+ status=version["status"],
frame_range=frame_range,
duration=duration,
handles=handles,
@@ -526,8 +527,11 @@ class ProductsModel:
products = list(ayon_api.get_products(project_name, **kwargs))
product_ids = {product["id"] for product in products}
+ # Add 'status' to fields -> fixed in ayon-python-api 1.0.4
+ fields = ayon_api.get_default_fields_for_type("version")
+ fields.add("status")
versions = ayon_api.get_versions(
- project_name, product_ids=product_ids
+ project_name, product_ids=product_ids, fields=fields
)
return self._create_product_items(
diff --git a/client/ayon_core/tools/loader/ui/products_delegates.py b/client/ayon_core/tools/loader/ui/products_delegates.py
index 12ed1165ae..1ac19b53eb 100644
--- a/client/ayon_core/tools/loader/ui/products_delegates.py
+++ b/client/ayon_core/tools/loader/ui/products_delegates.py
@@ -6,6 +6,9 @@ from ayon_core.tools.utils.lib import format_version
from .products_model import (
PRODUCT_ID_ROLE,
VERSION_NAME_EDIT_ROLE,
+ VERSION_STATUS_NAME_ROLE,
+ VERSION_STATUS_SHORT_ROLE,
+ VERSION_STATUS_COLOR_ROLE,
VERSION_ID_ROLE,
PRODUCT_IN_SCENE_ROLE,
ACTIVE_SITE_ICON_ROLE,
@@ -104,7 +107,10 @@ class VersionDelegate(QtWidgets.QStyledItemDelegate):
style = QtWidgets.QApplication.style()
style.drawControl(
- style.CE_ItemViewItem, option, painter, option.widget
+ QtWidgets.QCommonStyle.CE_ItemViewItem,
+ option,
+ painter,
+ option.widget
)
painter.save()
@@ -116,9 +122,14 @@ class VersionDelegate(QtWidgets.QStyledItemDelegate):
pen.setColor(fg_color)
painter.setPen(pen)
- text_rect = style.subElementRect(style.SE_ItemViewItemText, option)
+ text_rect = style.subElementRect(
+ QtWidgets.QCommonStyle.SE_ItemViewItemText,
+ option
+ )
text_margin = style.proxy().pixelMetric(
- style.PM_FocusFrameHMargin, option, option.widget
+ QtWidgets.QCommonStyle.PM_FocusFrameHMargin,
+ option,
+ option.widget
) + 1
painter.drawText(
@@ -194,6 +205,57 @@ class LoadedInSceneDelegate(QtWidgets.QStyledItemDelegate):
option.palette.setBrush(QtGui.QPalette.Text, color)
+class StatusDelegate(QtWidgets.QStyledItemDelegate):
+ """Delegate showing status name and short name."""
+
+ def paint(self, painter, option, index):
+ if option.widget:
+ style = option.widget.style()
+ else:
+ style = QtWidgets.QApplication.style()
+
+ style.drawControl(
+ QtWidgets.QCommonStyle.CE_ItemViewItem,
+ option,
+ painter,
+ option.widget
+ )
+
+ painter.save()
+
+ text_rect = style.subElementRect(
+ QtWidgets.QCommonStyle.SE_ItemViewItemText,
+ option
+ )
+ text_margin = style.proxy().pixelMetric(
+ QtWidgets.QCommonStyle.PM_FocusFrameHMargin,
+ option,
+ option.widget
+ ) + 1
+ padded_text_rect = text_rect.adjusted(
+ text_margin, 0, - text_margin, 0
+ )
+
+ fm = QtGui.QFontMetrics(option.font)
+ text = index.data(VERSION_STATUS_NAME_ROLE)
+ if padded_text_rect.width() < fm.width(text):
+ text = index.data(VERSION_STATUS_SHORT_ROLE)
+
+ status_color = index.data(VERSION_STATUS_COLOR_ROLE)
+ fg_color = QtGui.QColor(status_color)
+ pen = painter.pen()
+ pen.setColor(fg_color)
+ painter.setPen(pen)
+
+ painter.drawText(
+ padded_text_rect,
+ option.displayAlignment,
+ text
+ )
+
+ painter.restore()
+
+
class SiteSyncDelegate(QtWidgets.QStyledItemDelegate):
"""Paints icons and downloaded representation ration for both sites."""
diff --git a/client/ayon_core/tools/loader/ui/products_model.py b/client/ayon_core/tools/loader/ui/products_model.py
index b465679c3b..f309473d10 100644
--- a/client/ayon_core/tools/loader/ui/products_model.py
+++ b/client/ayon_core/tools/loader/ui/products_model.py
@@ -22,18 +22,21 @@ VERSION_HERO_ROLE = QtCore.Qt.UserRole + 11
VERSION_NAME_ROLE = QtCore.Qt.UserRole + 12
VERSION_NAME_EDIT_ROLE = QtCore.Qt.UserRole + 13
VERSION_PUBLISH_TIME_ROLE = QtCore.Qt.UserRole + 14
-VERSION_AUTHOR_ROLE = QtCore.Qt.UserRole + 15
-VERSION_FRAME_RANGE_ROLE = QtCore.Qt.UserRole + 16
-VERSION_DURATION_ROLE = QtCore.Qt.UserRole + 17
-VERSION_HANDLES_ROLE = QtCore.Qt.UserRole + 18
-VERSION_STEP_ROLE = QtCore.Qt.UserRole + 19
-VERSION_AVAILABLE_ROLE = QtCore.Qt.UserRole + 20
-VERSION_THUMBNAIL_ID_ROLE = QtCore.Qt.UserRole + 21
-ACTIVE_SITE_ICON_ROLE = QtCore.Qt.UserRole + 22
-REMOTE_SITE_ICON_ROLE = QtCore.Qt.UserRole + 23
-REPRESENTATIONS_COUNT_ROLE = QtCore.Qt.UserRole + 24
-SYNC_ACTIVE_SITE_AVAILABILITY = QtCore.Qt.UserRole + 25
-SYNC_REMOTE_SITE_AVAILABILITY = QtCore.Qt.UserRole + 26
+VERSION_STATUS_NAME_ROLE = QtCore.Qt.UserRole + 15
+VERSION_STATUS_SHORT_ROLE = QtCore.Qt.UserRole + 16
+VERSION_STATUS_COLOR_ROLE = QtCore.Qt.UserRole + 17
+VERSION_AUTHOR_ROLE = QtCore.Qt.UserRole + 18
+VERSION_FRAME_RANGE_ROLE = QtCore.Qt.UserRole + 19
+VERSION_DURATION_ROLE = QtCore.Qt.UserRole + 20
+VERSION_HANDLES_ROLE = QtCore.Qt.UserRole + 21
+VERSION_STEP_ROLE = QtCore.Qt.UserRole + 22
+VERSION_AVAILABLE_ROLE = QtCore.Qt.UserRole + 23
+VERSION_THUMBNAIL_ID_ROLE = QtCore.Qt.UserRole + 24
+ACTIVE_SITE_ICON_ROLE = QtCore.Qt.UserRole + 25
+REMOTE_SITE_ICON_ROLE = QtCore.Qt.UserRole + 26
+REPRESENTATIONS_COUNT_ROLE = QtCore.Qt.UserRole + 27
+SYNC_ACTIVE_SITE_AVAILABILITY = QtCore.Qt.UserRole + 28
+SYNC_REMOTE_SITE_AVAILABILITY = QtCore.Qt.UserRole + 29
class ProductsModel(QtGui.QStandardItemModel):
@@ -44,6 +47,7 @@ class ProductsModel(QtGui.QStandardItemModel):
"Product type",
"Folder",
"Version",
+ "Status",
"Time",
"Author",
"Frames",
@@ -69,11 +73,35 @@ class ProductsModel(QtGui.QStandardItemModel):
]
]
- version_col = column_labels.index("Version")
- published_time_col = column_labels.index("Time")
+ product_name_col = column_labels.index("Product name")
+ product_type_col = column_labels.index("Product type")
folders_label_col = column_labels.index("Folder")
+ version_col = column_labels.index("Version")
+ status_col = column_labels.index("Status")
+ published_time_col = column_labels.index("Time")
+ author_col = column_labels.index("Author")
+ frame_range_col = column_labels.index("Frames")
+ duration_col = column_labels.index("Duration")
+ handles_col = column_labels.index("Handles")
+ step_col = column_labels.index("Step")
in_scene_col = column_labels.index("In scene")
sitesync_avail_col = column_labels.index("Availability")
+ _display_role_mapping = {
+ product_name_col: QtCore.Qt.DisplayRole,
+ product_type_col: PRODUCT_TYPE_ROLE,
+ folders_label_col: FOLDER_LABEL_ROLE,
+ version_col: VERSION_NAME_ROLE,
+ status_col: VERSION_STATUS_NAME_ROLE,
+ published_time_col: VERSION_PUBLISH_TIME_ROLE,
+ author_col: VERSION_AUTHOR_ROLE,
+ frame_range_col: VERSION_FRAME_RANGE_ROLE,
+ duration_col: VERSION_DURATION_ROLE,
+ handles_col: VERSION_HANDLES_ROLE,
+ step_col: VERSION_STEP_ROLE,
+ in_scene_col: PRODUCT_IN_SCENE_ROLE,
+ sitesync_avail_col: VERSION_AVAILABLE_ROLE,
+
+ }
def __init__(self, controller):
super(ProductsModel, self).__init__()
@@ -96,6 +124,7 @@ class ProductsModel(QtGui.QStandardItemModel):
self._last_project_name = None
self._last_folder_ids = []
+ self._last_project_statuses = {}
def get_product_item_indexes(self):
return [
@@ -141,6 +170,15 @@ class ProductsModel(QtGui.QStandardItemModel):
if not index.isValid():
return None
+ if role in (VERSION_STATUS_SHORT_ROLE, VERSION_STATUS_COLOR_ROLE):
+ status_name = self.data(index, VERSION_STATUS_NAME_ROLE)
+ status_item = self._last_project_statuses.get(status_name)
+ if status_item is None:
+ return ""
+ if role == VERSION_STATUS_SHORT_ROLE:
+ return status_item.short
+ return status_item.color
+
col = index.column()
if col == 0:
return super(ProductsModel, self).data(index, role)
@@ -168,29 +206,8 @@ class ProductsModel(QtGui.QStandardItemModel):
if role == QtCore.Qt.DisplayRole:
if not index.data(PRODUCT_ID_ROLE):
return None
- if col == self.version_col:
- role = VERSION_NAME_ROLE
- elif col == 1:
- role = PRODUCT_TYPE_ROLE
- elif col == 2:
- role = FOLDER_LABEL_ROLE
- elif col == 4:
- role = VERSION_PUBLISH_TIME_ROLE
- elif col == 5:
- role = VERSION_AUTHOR_ROLE
- elif col == 6:
- role = VERSION_FRAME_RANGE_ROLE
- elif col == 7:
- role = VERSION_DURATION_ROLE
- elif col == 8:
- role = VERSION_HANDLES_ROLE
- elif col == 9:
- role = VERSION_STEP_ROLE
- elif col == 10:
- role = PRODUCT_IN_SCENE_ROLE
- elif col == 11:
- role = VERSION_AVAILABLE_ROLE
- else:
+ role = self._display_role_mapping.get(col)
+ if role is None:
return None
index = self.index(index.row(), 0, index.parent())
@@ -312,6 +329,7 @@ class ProductsModel(QtGui.QStandardItemModel):
version_item.published_time, VERSION_PUBLISH_TIME_ROLE
)
model_item.setData(version_item.author, VERSION_AUTHOR_ROLE)
+ model_item.setData(version_item.status, VERSION_STATUS_NAME_ROLE)
model_item.setData(version_item.frame_range, VERSION_FRAME_RANGE_ROLE)
model_item.setData(version_item.duration, VERSION_DURATION_ROLE)
model_item.setData(version_item.handles, VERSION_HANDLES_ROLE)
@@ -393,6 +411,11 @@ class ProductsModel(QtGui.QStandardItemModel):
self._last_project_name = project_name
self._last_folder_ids = folder_ids
+ status_items = self._controller.get_project_status_items(project_name)
+ self._last_project_statuses = {
+ status_item.name: status_item
+ for status_item in status_items
+ }
active_site_icon_def = self._controller.get_active_site_icon_def(
project_name
diff --git a/client/ayon_core/tools/loader/ui/products_widget.py b/client/ayon_core/tools/loader/ui/products_widget.py
index d9f027153e..3a30d83d52 100644
--- a/client/ayon_core/tools/loader/ui/products_widget.py
+++ b/client/ayon_core/tools/loader/ui/products_widget.py
@@ -22,7 +22,8 @@ from .products_model import (
from .products_delegates import (
VersionDelegate,
LoadedInSceneDelegate,
- SiteSyncDelegate
+ StatusDelegate,
+ SiteSyncDelegate,
)
from .actions_utils import show_actions_menu
@@ -89,6 +90,7 @@ class ProductsWidget(QtWidgets.QWidget):
90, # Product type
130, # Folder label
60, # Version
+ 100, # Status
125, # Time
75, # Author
75, # Frames
@@ -128,20 +130,19 @@ class ProductsWidget(QtWidgets.QWidget):
products_view.setColumnWidth(idx, width)
version_delegate = VersionDelegate()
- products_view.setItemDelegateForColumn(
- products_model.version_col, version_delegate)
-
time_delegate = PrettyTimeDelegate()
- products_view.setItemDelegateForColumn(
- products_model.published_time_col, time_delegate)
-
+ status_delegate = StatusDelegate()
in_scene_delegate = LoadedInSceneDelegate()
- products_view.setItemDelegateForColumn(
- products_model.in_scene_col, in_scene_delegate)
-
sitesync_delegate = SiteSyncDelegate()
- products_view.setItemDelegateForColumn(
- products_model.sitesync_avail_col, sitesync_delegate)
+
+ for col, delegate in (
+ (products_model.version_col, version_delegate),
+ (products_model.published_time_col, time_delegate),
+ (products_model.status_col, status_delegate),
+ (products_model.in_scene_col, in_scene_delegate),
+ (products_model.sitesync_avail_col, sitesync_delegate),
+ ):
+ products_view.setItemDelegateForColumn(col, delegate)
main_layout = QtWidgets.QHBoxLayout(self)
main_layout.setContentsMargins(0, 0, 0, 0)
@@ -175,6 +176,7 @@ class ProductsWidget(QtWidgets.QWidget):
self._version_delegate = version_delegate
self._time_delegate = time_delegate
+ self._status_delegate = status_delegate
self._in_scene_delegate = in_scene_delegate
self._sitesync_delegate = sitesync_delegate
diff --git a/client/ayon_core/tools/loader/ui/window.py b/client/ayon_core/tools/loader/ui/window.py
index 3a6f4679fa..8529a53b06 100644
--- a/client/ayon_core/tools/loader/ui/window.py
+++ b/client/ayon_core/tools/loader/ui/window.py
@@ -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):
diff --git a/client/ayon_core/tools/push_to_project/models/integrate.py b/client/ayon_core/tools/push_to_project/models/integrate.py
index 6e43050c05..5937ffa4da 100644
--- a/client/ayon_core/tools/push_to_project/models/integrate.py
+++ b/client/ayon_core/tools/push_to_project/models/integrate.py
@@ -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}\""
diff --git a/client/ayon_core/version.py b/client/ayon_core/version.py
index a60de0493a..e4297e2000 100644
--- a/client/ayon_core/version.py
+++ b/client/ayon_core/version.py
@@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
"""Package declaring AYON core addon version."""
-__version__ = "0.3.1-dev.1"
+__version__ = "0.3.3-dev.1"
diff --git a/client/pyproject.toml b/client/pyproject.toml
index 1a0ad7e5f2..5e811321f8 100644
--- a/client/pyproject.toml
+++ b/client/pyproject.toml
@@ -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
diff --git a/package.py b/package.py
index 79450d029f..73f7174b6f 100644
--- a/package.py
+++ b/package.py
@@ -1,11 +1,12 @@
name = "core"
title = "Core"
-version = "0.3.1-dev.1"
+version = "0.3.3-dev.1"
client_dir = "ayon_core"
plugin_for = ["ayon_server"]
-requires = [
- "~ayon_server-1.0.3+<2.0.0",
-]
+ayon_server_version = ">=1.0.3,<2.0.0"
+ayon_launcher_version = ">=1.0.2"
+ayon_required_addons = {}
+ayon_compatible_addons = {}
diff --git a/server/__init__.py b/server/__init__.py
index 152cc77218..79f505ccd5 100644
--- a/server/__init__.py
+++ b/server/__init__.py
@@ -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)
diff --git a/server/settings/main.py b/server/settings/main.py
index 28a69e182d..40e16e7e91 100644
--- a/server/settings/main.py
+++ b/server/settings/main.py
@@ -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
+ )
}
diff --git a/server/settings/publish_plugins.py b/server/settings/publish_plugins.py
index e61bf6986b..61e73ce912 100644
--- a/server/settings/publish_plugins.py
+++ b/server/settings/publish_plugins.py
@@ -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": []
diff --git a/server_addon/aftereffects/package.py b/server_addon/aftereffects/package.py
index a680b37602..7a2f9bc7af 100644
--- a/server_addon/aftereffects/package.py
+++ b/server_addon/aftereffects/package.py
@@ -1,3 +1,3 @@
name = "aftereffects"
title = "AfterEffects"
-version = "0.1.3"
+version = "0.1.4"
diff --git a/server_addon/aftereffects/server/settings/publish_plugins.py b/server_addon/aftereffects/server/settings/publish_plugins.py
index 61d67f26d3..a9f30c6686 100644
--- a/server_addon/aftereffects/server/settings/publish_plugins.py
+++ b/server_addon/aftereffects/server/settings/publish_plugins.py
@@ -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,
- }
}
diff --git a/server_addon/blender/package.py b/server_addon/blender/package.py
index 667076e533..d2c02a4909 100644
--- a/server_addon/blender/package.py
+++ b/server_addon/blender/package.py
@@ -1,3 +1,3 @@
name = "blender"
title = "Blender"
-version = "0.1.8"
+version = "0.1.9"
diff --git a/server_addon/blender/server/settings/publish_plugins.py b/server_addon/blender/server/settings/publish_plugins.py
index e998d7b057..8db8c5be46 100644
--- a/server_addon/blender/server/settings/publish_plugins.py
+++ b/server_addon/blender/server/settings/publish_plugins.py
@@ -151,6 +151,10 @@ class PublishPluginsModel(BaseSettingsModel):
default_factory=ExtractPlayblastModel,
title="Extract Playblast"
)
+ ExtractModelUSD: ValidatePluginModel = SettingsField(
+ default_factory=ValidatePluginModel,
+ title="Extract Model USD"
+ )
DEFAULT_BLENDER_PUBLISH_SETTINGS = {
@@ -348,5 +352,10 @@ DEFAULT_BLENDER_PUBLISH_SETTINGS = {
},
indent=4
)
+ },
+ "ExtractModelUSD": {
+ "enabled": True,
+ "optional": True,
+ "active": True
}
}
diff --git a/server_addon/clockify/client/ayon_clockify/__init__.py b/server_addon/clockify/client/ayon_clockify/__init__.py
new file mode 100644
index 0000000000..75fb87494e
--- /dev/null
+++ b/server_addon/clockify/client/ayon_clockify/__init__.py
@@ -0,0 +1,5 @@
+from .addon import ClockifyAddon
+
+__all__ = (
+ "ClockifyAddon",
+)
diff --git a/client/ayon_core/modules/clockify/clockify_module.py b/server_addon/clockify/client/ayon_clockify/addon.py
similarity index 93%
rename from client/ayon_core/modules/clockify/clockify_module.py
rename to server_addon/clockify/client/ayon_clockify/addon.py
index d2ee4f1e1e..ce91b2be70 100644
--- a/client/ayon_core/modules/clockify/clockify_module.py
+++ b/server_addon/clockify/client/ayon_clockify/addon.py
@@ -2,12 +2,12 @@ import os
import threading
import time
-from ayon_core.modules import AYONAddon, ITrayModule, IPluginPaths
+from ayon_core.addon import AYONAddon, ITrayAddon, IPluginPaths
from .constants import CLOCKIFY_FTRACK_USER_PATH, CLOCKIFY_FTRACK_SERVER_PATH
-class ClockifyModule(AYONAddon, ITrayModule, IPluginPaths):
+class ClockifyAddon(AYONAddon, ITrayAddon, IPluginPaths):
name = "clockify"
def initialize(self, studio_settings):
@@ -31,7 +31,7 @@ class ClockifyModule(AYONAddon, ITrayModule, IPluginPaths):
# TimersManager attributes
# - set `timers_manager_connector` only in `tray_init`
self.timers_manager_connector = None
- self._timers_manager_module = None
+ self._timer_manager_addon = None
@property
def clockify_api(self):
@@ -87,7 +87,7 @@ class ClockifyModule(AYONAddon, ITrayModule, IPluginPaths):
return {"actions": [actions_path]}
def get_ftrack_event_handler_paths(self):
- """Function for Ftrack module to add ftrack event handler paths."""
+ """Function for ftrack addon to add ftrack event handler paths."""
return {
"user": [CLOCKIFY_FTRACK_USER_PATH],
"server": [CLOCKIFY_FTRACK_SERVER_PATH],
@@ -206,19 +206,19 @@ class ClockifyModule(AYONAddon, ITrayModule, IPluginPaths):
self.action_stop_timer.setVisible(self.bool_timer_run)
# --- TimersManager connection methods ---
- def register_timers_manager(self, timer_manager_module):
+ def register_timers_manager(self, timer_manager_addon):
"""Store TimersManager for future use."""
- self._timers_manager_module = timer_manager_module
+ self._timer_manager_addon = timer_manager_addon
def timer_started(self, data):
"""Tell TimersManager that timer started."""
- if self._timers_manager_module is not None:
- self._timers_manager_module.timer_started(self.id, data)
+ if self._timer_manager_addon is not None:
+ self._timer_manager_addon.timer_started(self.id, data)
def timer_stopped(self):
"""Tell TimersManager that timer stopped."""
- if self._timers_manager_module is not None:
- self._timers_manager_module.timer_stopped(self.id)
+ if self._timer_manager_addon is not None:
+ self._timer_manager_addon.timer_stopped(self.id)
def stop_timer(self):
"""Called from TimersManager to stop timer."""
diff --git a/client/ayon_core/modules/clockify/clockify_api.py b/server_addon/clockify/client/ayon_clockify/clockify_api.py
similarity index 99%
rename from client/ayon_core/modules/clockify/clockify_api.py
rename to server_addon/clockify/client/ayon_clockify/clockify_api.py
index 2e1d8f008f..38ca6cdb66 100644
--- a/client/ayon_core/modules/clockify/clockify_api.py
+++ b/server_addon/clockify/client/ayon_clockify/clockify_api.py
@@ -1,15 +1,17 @@
import os
import json
import datetime
+
import requests
+
+from ayon_core.lib.local_settings import AYONSecureRegistry
+from ayon_core.lib import Logger
+
from .constants import (
CLOCKIFY_ENDPOINT,
ADMIN_PERMISSION_NAMES,
)
-from ayon_core.lib.local_settings import AYONSecureRegistry
-from ayon_core.lib import Logger
-
class ClockifyAPI:
log = Logger.get_logger(__name__)
diff --git a/client/ayon_core/modules/clockify/constants.py b/server_addon/clockify/client/ayon_clockify/constants.py
similarity index 100%
rename from client/ayon_core/modules/clockify/constants.py
rename to server_addon/clockify/client/ayon_clockify/constants.py
diff --git a/client/ayon_core/modules/clockify/ftrack/server/action_clockify_sync_server.py b/server_addon/clockify/client/ayon_clockify/ftrack/server/action_clockify_sync_server.py
similarity index 97%
rename from client/ayon_core/modules/clockify/ftrack/server/action_clockify_sync_server.py
rename to server_addon/clockify/client/ayon_clockify/ftrack/server/action_clockify_sync_server.py
index 7854f0ceba..ed83fed287 100644
--- a/client/ayon_core/modules/clockify/ftrack/server/action_clockify_sync_server.py
+++ b/server_addon/clockify/client/ayon_clockify/ftrack/server/action_clockify_sync_server.py
@@ -1,7 +1,9 @@
import os
import json
-from openpype_modules.ftrack.lib import ServerAction
-from openpype_modules.clockify.clockify_api import ClockifyAPI
+
+from ayon_clockify.clockify_api import ClockifyAPI
+
+from ayon_ftrack.lib import ServerAction
class SyncClockifyServer(ServerAction):
diff --git a/client/ayon_core/modules/clockify/ftrack/user/action_clockify_sync_local.py b/server_addon/clockify/client/ayon_clockify/ftrack/user/action_clockify_sync_local.py
similarity index 87%
rename from client/ayon_core/modules/clockify/ftrack/user/action_clockify_sync_local.py
rename to server_addon/clockify/client/ayon_clockify/ftrack/user/action_clockify_sync_local.py
index 4701653a0b..05a94e56fd 100644
--- a/client/ayon_core/modules/clockify/ftrack/user/action_clockify_sync_local.py
+++ b/server_addon/clockify/client/ayon_clockify/ftrack/user/action_clockify_sync_local.py
@@ -1,25 +1,20 @@
import json
-from openpype_modules.ftrack.lib import BaseAction, statics_icon
-from openpype_modules.clockify.clockify_api import ClockifyAPI
+from ayon_clockify.clockify_api import ClockifyAPI
+from ayon_ftrack.lib import BaseAction, statics_icon
class SyncClockifyLocal(BaseAction):
- '''Synchronise project names and task types.'''
+ """Synchronise project names and task types."""
- #: Action identifier.
- identifier = 'clockify.sync.local'
- #: Action label.
- label = 'Sync To Clockify (local)'
- #: Action description.
- description = 'Synchronise data to Clockify workspace'
- #: roles that are allowed to register this action
+ identifier = "clockify.sync.local"
+ label = "Sync To Clockify"
+ description = "Synchronise data to Clockify workspace"
role_list = ["Administrator", "project Manager"]
- #: icon
icon = statics_icon("app_icons", "clockify-white.png")
def __init__(self, *args, **kwargs):
super(SyncClockifyLocal, self).__init__(*args, **kwargs)
- #: CLockifyApi
+
self.clockify_api = ClockifyAPI()
def discover(self, session, entities, event):
@@ -56,7 +51,7 @@ class SyncClockifyLocal(BaseAction):
'user': user,
'status': 'running',
'data': json.dumps({
- 'description': 'Sync Ftrack to Clockify'
+ 'description': 'Sync ftrack to Clockify'
})
})
session.commit()
diff --git a/client/ayon_core/modules/clockify/launcher_actions/ClockifyStart.py b/server_addon/clockify/client/ayon_clockify/launcher_actions/ClockifyStart.py
similarity index 96%
rename from client/ayon_core/modules/clockify/launcher_actions/ClockifyStart.py
rename to server_addon/clockify/client/ayon_clockify/launcher_actions/ClockifyStart.py
index 8381c7d73e..d69d0371c0 100644
--- a/client/ayon_core/modules/clockify/launcher_actions/ClockifyStart.py
+++ b/server_addon/clockify/client/ayon_clockify/launcher_actions/ClockifyStart.py
@@ -1,7 +1,8 @@
import ayon_api
+from ayon_clockify.clockify_api import ClockifyAPI
+
from ayon_core.pipeline import LauncherAction
-from openpype_modules.clockify.clockify_api import ClockifyAPI
class ClockifyStart(LauncherAction):
diff --git a/client/ayon_core/modules/clockify/launcher_actions/ClockifySync.py b/server_addon/clockify/client/ayon_clockify/launcher_actions/ClockifySync.py
similarity index 97%
rename from client/ayon_core/modules/clockify/launcher_actions/ClockifySync.py
rename to server_addon/clockify/client/ayon_clockify/launcher_actions/ClockifySync.py
index 5388f47c98..a32f2a8082 100644
--- a/client/ayon_core/modules/clockify/launcher_actions/ClockifySync.py
+++ b/server_addon/clockify/client/ayon_clockify/launcher_actions/ClockifySync.py
@@ -1,6 +1,6 @@
import ayon_api
-from openpype_modules.clockify.clockify_api import ClockifyAPI
+from ayon_clockify.clockify_api import ClockifyAPI
from ayon_core.pipeline import LauncherAction
diff --git a/server_addon/clockify/client/ayon_clockify/version.py b/server_addon/clockify/client/ayon_clockify/version.py
new file mode 100644
index 0000000000..0e6e40cb7d
--- /dev/null
+++ b/server_addon/clockify/client/ayon_clockify/version.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+"""Package declaring AYON addon 'clockify' version."""
+__version__ = "0.2.0"
diff --git a/client/ayon_core/modules/clockify/widgets.py b/server_addon/clockify/client/ayon_clockify/widgets.py
similarity index 100%
rename from client/ayon_core/modules/clockify/widgets.py
rename to server_addon/clockify/client/ayon_clockify/widgets.py
diff --git a/server_addon/clockify/package.py b/server_addon/clockify/package.py
index bcf9425b3f..61e0685191 100644
--- a/server_addon/clockify/package.py
+++ b/server_addon/clockify/package.py
@@ -1,3 +1,9 @@
name = "clockify"
title = "Clockify"
-version = "0.1.1"
+version = "0.2.0"
+client_dir = "ayon_clockify"
+
+ayon_required_addons = {
+ "core": ">0.3.2",
+}
+ayon_compatible_addons = {}
diff --git a/server_addon/create_ayon_addons.py b/server_addon/create_ayon_addons.py
index f0a36d4740..749077d2a8 100644
--- a/server_addon/create_ayon_addons.py
+++ b/server_addon/create_ayon_addons.py
@@ -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(
diff --git a/server_addon/deadline/package.py b/server_addon/deadline/package.py
index 25ba1c1166..e26734c813 100644
--- a/server_addon/deadline/package.py
+++ b/server_addon/deadline/package.py
@@ -1,3 +1,3 @@
name = "deadline"
title = "Deadline"
-version = "0.1.11"
+version = "0.1.12"
diff --git a/server_addon/deadline/server/settings/main.py b/server_addon/deadline/server/settings/main.py
index 5d42b9b1ef..47ad72a86f 100644
--- a/server_addon/deadline/server/settings/main.py
+++ b/server_addon/deadline/server/settings/main.py
@@ -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",
diff --git a/server_addon/harmony/package.py b/server_addon/harmony/package.py
index 83e88e7d57..00824cedef 100644
--- a/server_addon/harmony/package.py
+++ b/server_addon/harmony/package.py
@@ -1,3 +1,3 @@
name = "harmony"
title = "Harmony"
-version = "0.1.2"
+version = "0.1.3"
diff --git a/server_addon/harmony/server/settings/main.py b/server_addon/harmony/server/settings/main.py
index 9c780b63c2..8a72c966d8 100644
--- a/server_addon/harmony/server/settings/main.py
+++ b/server_addon/harmony/server/settings/main.py
@@ -45,11 +45,6 @@ DEFAULT_HARMONY_SETTING = {
"optional": True,
"active": True
},
- "ValidateContainers": {
- "enabled": True,
- "optional": True,
- "active": True
- },
"ValidateSceneSettings": {
"enabled": True,
"optional": True,
diff --git a/server_addon/harmony/server/settings/publish_plugins.py b/server_addon/harmony/server/settings/publish_plugins.py
index c9e7c515e4..2d976389f6 100644
--- a/server_addon/harmony/server/settings/publish_plugins.py
+++ b/server_addon/harmony/server/settings/publish_plugins.py
@@ -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,
diff --git a/server_addon/houdini/package.py b/server_addon/houdini/package.py
index 4e441c76ae..06b034da38 100644
--- a/server_addon/houdini/package.py
+++ b/server_addon/houdini/package.py
@@ -1,3 +1,3 @@
name = "houdini"
title = "Houdini"
-version = "0.2.13"
+version = "0.2.15"
diff --git a/server_addon/houdini/server/settings/create.py b/server_addon/houdini/server/settings/create.py
index 203ca4f9d6..cd1e110c23 100644
--- a/server_addon/houdini/server/settings/create.py
+++ b/server_addon/houdini/server/settings/create.py
@@ -57,6 +57,9 @@ class CreatePluginsModel(BaseSettingsModel):
CreateMantraROP: CreatorModel = SettingsField(
default_factory=CreatorModel,
title="Create Mantra ROP")
+ CreateModel: CreatorModel = SettingsField(
+ default_factory=CreatorModel,
+ title="Create Model")
CreatePointCache: CreatorModel = SettingsField(
default_factory=CreatorModel,
title="Create PointCache (Abc)")
@@ -124,6 +127,10 @@ DEFAULT_HOUDINI_CREATE_SETTINGS = {
"enabled": True,
"default_variants": ["Main"]
},
+ "CreateModel": {
+ "enabled": True,
+ "default_variants": ["Main"]
+ },
"CreatePointCache": {
"enabled": True,
"default_variants": ["Main"]
diff --git a/server_addon/houdini/server/settings/publish.py b/server_addon/houdini/server/settings/publish.py
index 8e0e7f7795..4a0c022f23 100644
--- a/server_addon/houdini/server/settings/publish.py
+++ b/server_addon/houdini/server/settings/publish.py
@@ -1,4 +1,7 @@
-from ayon_server.settings import BaseSettingsModel, SettingsField
+from ayon_server.settings import (
+ BaseSettingsModel,
+ SettingsField
+)
# Publish Plugins
@@ -20,6 +23,27 @@ class CollectChunkSizeModel(BaseSettingsModel):
title="Frames Per Task")
+class AOVFilterSubmodel(BaseSettingsModel):
+ """You should use the same host name you are using for Houdini."""
+ host_name: str = SettingsField("", title="Houdini Host name")
+ value: list[str] = SettingsField(
+ default_factory=list,
+ title="AOV regex"
+ )
+
+class CollectLocalRenderInstancesModel(BaseSettingsModel):
+
+ use_deadline_aov_filter: bool = SettingsField(
+ False,
+ title="Use Deadline AOV Filter"
+ )
+
+ aov_filter: AOVFilterSubmodel = SettingsField(
+ default_factory=AOVFilterSubmodel,
+ title="Reviewable products filter"
+ )
+
+
class ValidateWorkfilePathsModel(BaseSettingsModel):
enabled: bool = SettingsField(title="Enabled")
optional: bool = SettingsField(title="Optional")
@@ -49,10 +73,10 @@ class PublishPluginsModel(BaseSettingsModel):
default_factory=CollectChunkSizeModel,
title="Collect Chunk Size."
)
- ValidateContainers: BasicValidateModel = SettingsField(
- default_factory=BasicValidateModel,
- title="Validate Latest Containers.",
- section="Validators")
+ CollectLocalRenderInstances: CollectLocalRenderInstancesModel = SettingsField(
+ default_factory=CollectLocalRenderInstancesModel,
+ title="Collect Local Render Instances."
+ )
ValidateInstanceInContextHoudini: BasicValidateModel = SettingsField(
default_factory=BasicValidateModel,
title="Validate Instance is in same Context.")
@@ -82,10 +106,14 @@ DEFAULT_HOUDINI_PUBLISH_SETTINGS = {
"optional": True,
"chunk_size": 999999
},
- "ValidateContainers": {
- "enabled": True,
- "optional": True,
- "active": True
+ "CollectLocalRenderInstances": {
+ "use_deadline_aov_filter": False,
+ "aov_filter" : {
+ "host_name": "houdini",
+ "value": [
+ ".*([Bb]eauty).*"
+ ]
+ }
},
"ValidateInstanceInContextHoudini": {
"enabled": True,
diff --git a/server_addon/maya/package.py b/server_addon/maya/package.py
index fe3e3039f5..4537c23eaa 100644
--- a/server_addon/maya/package.py
+++ b/server_addon/maya/package.py
@@ -1,3 +1,3 @@
name = "maya"
title = "Maya"
-version = "0.1.18"
+version = "0.1.20"
diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py
index 20523b2ca9..9c552e17fa 100644
--- a/server_addon/maya/server/settings/publishers.py
+++ b/server_addon/maya/server/settings/publishers.py
@@ -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,
diff --git a/client/ayon_core/hosts/nuke/__init__.py b/server_addon/nuke/client/ayon_nuke/__init__.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/__init__.py
rename to server_addon/nuke/client/ayon_nuke/__init__.py
diff --git a/client/ayon_core/hosts/nuke/addon.py b/server_addon/nuke/client/ayon_nuke/addon.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/addon.py
rename to server_addon/nuke/client/ayon_nuke/addon.py
diff --git a/client/ayon_core/hosts/nuke/api/__init__.py b/server_addon/nuke/client/ayon_nuke/api/__init__.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/api/__init__.py
rename to server_addon/nuke/client/ayon_nuke/api/__init__.py
diff --git a/client/ayon_core/hosts/nuke/api/actions.py b/server_addon/nuke/client/ayon_nuke/api/actions.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/api/actions.py
rename to server_addon/nuke/client/ayon_nuke/api/actions.py
diff --git a/client/ayon_core/hosts/nuke/api/command.py b/server_addon/nuke/client/ayon_nuke/api/command.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/api/command.py
rename to server_addon/nuke/client/ayon_nuke/api/command.py
diff --git a/client/ayon_core/hosts/nuke/api/constants.py b/server_addon/nuke/client/ayon_nuke/api/constants.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/api/constants.py
rename to server_addon/nuke/client/ayon_nuke/api/constants.py
diff --git a/client/ayon_core/hosts/nuke/api/gizmo_menu.py b/server_addon/nuke/client/ayon_nuke/api/gizmo_menu.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/api/gizmo_menu.py
rename to server_addon/nuke/client/ayon_nuke/api/gizmo_menu.py
diff --git a/client/ayon_core/hosts/nuke/api/lib.py b/server_addon/nuke/client/ayon_nuke/api/lib.py
similarity index 98%
rename from client/ayon_core/hosts/nuke/api/lib.py
rename to server_addon/nuke/client/ayon_nuke/api/lib.py
index e3505a16f2..09dab4687a 100644
--- a/client/ayon_core/hosts/nuke/api/lib.py
+++ b/server_addon/nuke/client/ayon_nuke/api/lib.py
@@ -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
@@ -352,7 +354,7 @@ def imprint(node, data, tab=None):
Examples:
```
import nuke
- from ayon_core.hosts.nuke.api import lib
+ from ayon_nuke.api import lib
node = nuke.createNode("NoOp")
data = {
@@ -417,7 +419,7 @@ def add_publish_knob(node):
return node
-@deprecated("ayon_core.hosts.nuke.api.lib.set_node_data")
+@deprecated("ayon_nuke.api.lib.set_node_data")
def set_avalon_knob_data(node, data=None, prefix="avalon:"):
"""[DEPRECATED] Sets data into nodes's avalon knob
@@ -483,7 +485,7 @@ def set_avalon_knob_data(node, data=None, prefix="avalon:"):
return node
-@deprecated("ayon_core.hosts.nuke.api.lib.get_node_data")
+@deprecated("ayon_nuke.api.lib.get_node_data")
def get_avalon_knob_data(node, prefix="avalon:", create=True):
"""[DEPRECATED] Gets a data from nodes's avalon knob
@@ -1022,6 +1024,18 @@ def script_name():
return nuke.root().knob("name").value()
+def add_button_render_on_farm(node):
+ name = "renderOnFarm"
+ label = "Render On Farm"
+ value = (
+ "from ayon_nuke.api.utils import submit_render_on_farm;"
+ "submit_render_on_farm(nuke.thisNode())"
+ )
+ knob = nuke.PyScript_Knob(name, label, value)
+ knob.clearFlag(nuke.STARTLINE)
+ node.addKnob(knob)
+
+
def add_button_write_to_read(node):
name = "createReadNode"
label = "Read From Rendered"
@@ -1144,6 +1158,17 @@ def create_write_node(
Return:
node (obj): group node with avalon data as Knobs
'''
+ # Ensure name does not contain any invalid characters.
+ special_chars = re.escape("!@#$%^&*()=[]{}|\\;',.<>/?~+-")
+ special_chars_regex = re.compile(f"[{special_chars}]")
+ found_special_characters = list(special_chars_regex.findall(name))
+
+ msg = (
+ f"Special characters found in name \"{name}\": "
+ f"{' '.join(found_special_characters)}"
+ )
+ assert not found_special_characters, msg
+
prenodes = prenodes or []
# filtering variables
@@ -1268,6 +1293,10 @@ def create_write_node(
link.setFlag(0x1000)
GN.addKnob(link)
+ # Adding render farm submission button.
+ if data.get("render_on_farm", False):
+ add_button_render_on_farm(GN)
+
# adding write to read button
add_button_write_to_read(GN)
@@ -1552,10 +1581,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"]
@@ -2443,7 +2469,7 @@ def _launch_workfile_app():
host_tools.show_workfiles(parent=None, on_top=True)
-@deprecated("ayon_core.hosts.nuke.api.lib.start_workfile_template_builder")
+@deprecated("ayon_nuke.api.lib.start_workfile_template_builder")
def process_workfile_builder():
""" [DEPRECATED] Process workfile builder on nuke start
diff --git a/client/ayon_core/hosts/nuke/api/pipeline.py b/server_addon/nuke/client/ayon_nuke/api/pipeline.py
similarity index 98%
rename from client/ayon_core/hosts/nuke/api/pipeline.py
rename to server_addon/nuke/client/ayon_nuke/api/pipeline.py
index d35a2e89e0..ad8e17b1f6 100644
--- a/client/ayon_core/hosts/nuke/api/pipeline.py
+++ b/server_addon/nuke/client/ayon_nuke/api/pipeline.py
@@ -28,7 +28,7 @@ from ayon_core.pipeline import (
)
from ayon_core.pipeline.workfile import BuildWorkfile
from ayon_core.tools.utils import host_tools
-from ayon_core.hosts.nuke import NUKE_ROOT_DIR
+from ayon_nuke import NUKE_ROOT_DIR
from ayon_core.tools.workfile_template_build import open_template_ui
from .lib import (
@@ -188,10 +188,10 @@ def reload_config():
"""
for module in (
- "ayon_core.hosts.nuke.api.actions",
- "ayon_core.hosts.nuke.api.menu",
- "ayon_core.hosts.nuke.api.plugin",
- "ayon_core.hosts.nuke.api.lib",
+ "ayon_nuke.api.actions",
+ "ayon_nuke.api.menu",
+ "ayon_nuke.api.plugin",
+ "ayon_nuke.api.lib",
):
log.info("Reloading module: {}...".format(module))
diff --git a/client/ayon_core/hosts/nuke/api/plugin.py b/server_addon/nuke/client/ayon_nuke/api/plugin.py
similarity index 97%
rename from client/ayon_core/hosts/nuke/api/plugin.py
rename to server_addon/nuke/client/ayon_nuke/api/plugin.py
index fb56dec833..4f05cd41b9 100644
--- a/client/ayon_core/hosts/nuke/api/plugin.py
+++ b/server_addon/nuke/client/ayon_nuke/api/plugin.py
@@ -572,8 +572,11 @@ class ExporterReview(object):
self.fhead = self.fhead.replace("#", "")[:-1]
def get_representation_data(
- self, tags=None, range=False,
- custom_tags=None, colorspace=None
+ self,
+ tags=None,
+ range=False,
+ custom_tags=None,
+ colorspace=None,
):
""" Add representation data to self.data
@@ -584,6 +587,8 @@ class ExporterReview(object):
Defaults to False.
custom_tags (list[str], optional): user inputted custom tags.
Defaults to None.
+ colorspace (str, optional): colorspace name.
+ Defaults to None.
"""
add_tags = tags or []
repre = {
@@ -591,7 +596,13 @@ class ExporterReview(object):
"ext": self.ext,
"files": self.file,
"stagingDir": self.staging_dir,
- "tags": [self.name.replace("_", "-")] + add_tags
+ "tags": [self.name.replace("_", "-")] + add_tags,
+ "data": {
+ # making sure that once intermediate file is published
+ # as representation, we will be able to then identify it
+ # from representation.data.isIntermediate
+ "isIntermediate": True
+ },
}
if custom_tags:
@@ -778,6 +789,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,9 +846,10 @@ 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
+ colorspace = self.write_colorspace
+
# get colorspace settings
# get colorspace data from context
config_data, _ = get_colorspace_settings_from_publish_context(
@@ -947,6 +960,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,11 +1002,16 @@ 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
+ colorspace=colorspace,
)
self.log.debug("Representation... `{}`".format(self.data))
@@ -1030,7 +1050,7 @@ def convert_to_valid_instaces():
}
return mapping[product_type]
- from ayon_core.hosts.nuke.api import workio
+ from ayon_nuke.api import workio
task_name = get_current_task_name()
diff --git a/client/ayon_core/hosts/nuke/api/utils.py b/server_addon/nuke/client/ayon_nuke/api/utils.py
similarity index 63%
rename from client/ayon_core/hosts/nuke/api/utils.py
rename to server_addon/nuke/client/ayon_nuke/api/utils.py
index 1bfc1919fa..646bb0ece1 100644
--- a/client/ayon_core/hosts/nuke/api/utils.py
+++ b/server_addon/nuke/client/ayon_nuke/api/utils.py
@@ -3,9 +3,15 @@ import re
import nuke
-from ayon_core import resources
+import pyblish.util
+import pyblish.api
from qtpy import QtWidgets
+from ayon_core import resources
+from ayon_core.pipeline import registered_host
+from ayon_core.tools.utils import show_message_dialog
+from ayon_core.pipeline.create import CreateContext
+
def set_context_favorites(favorites=None):
""" Adding favorite folders to nuke's browser
@@ -142,3 +148,77 @@ def is_headless():
bool: headless
"""
return QtWidgets.QApplication.instance() is None
+
+
+def submit_render_on_farm(node):
+ # Ensure code is executed in root context.
+ if nuke.root() == nuke.thisNode():
+ _submit_render_on_farm(node)
+ else:
+ # If not in root context, move to the root context and then execute the
+ # code.
+ with nuke.root():
+ _submit_render_on_farm(node)
+
+
+def _submit_render_on_farm(node):
+ """Render on farm submission
+
+ This function prepares the context for farm submission, validates it,
+ extracts relevant data, copies the current workfile to a timestamped copy,
+ and submits the job to the farm.
+
+ Args:
+ node (Node): The node for which the farm submission is being made.
+ """
+
+ host = registered_host()
+ create_context = CreateContext(host)
+
+ # Ensure CreateInstance is enabled.
+ for instance in create_context.instances:
+ if node.name() != instance.transient_data["node"].name():
+ continue
+
+ instance.data["active"] = True
+
+ context = pyblish.api.Context()
+ context.data["create_context"] = create_context
+ # Used in pyblish plugin to determine which instance to publish.
+ context.data["node_name"] = node.name()
+ # Used in pyblish plugins to determine whether to run or not.
+ context.data["render_on_farm"] = True
+
+ # Since we need to bypass version validation and incrementing, we need to
+ # remove the plugins from the list that are responsible for these tasks.
+ plugins = pyblish.api.discover()
+ blacklist = ["IncrementScriptVersion", "ValidateVersion"]
+ plugins = [
+ plugin
+ for plugin in plugins
+ if plugin.__name__ not in blacklist
+ ]
+
+ context = pyblish.util.publish(context, plugins=plugins)
+
+ error_message = ""
+ success = True
+ for result in context.data["results"]:
+ if result["success"]:
+ continue
+
+ success = False
+
+ err = result["error"]
+ error_message += "\n"
+ error_message += err.formatted_traceback
+
+ if not success:
+ show_message_dialog(
+ "Publish Errors", error_message, level="critical"
+ )
+ return
+
+ show_message_dialog(
+ "Submission Successful", "Submission to the farm was successful."
+ )
diff --git a/client/ayon_core/hosts/nuke/api/workfile_template_builder.py b/server_addon/nuke/client/ayon_nuke/api/workfile_template_builder.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/api/workfile_template_builder.py
rename to server_addon/nuke/client/ayon_nuke/api/workfile_template_builder.py
diff --git a/client/ayon_core/hosts/nuke/api/workio.py b/server_addon/nuke/client/ayon_nuke/api/workio.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/api/workio.py
rename to server_addon/nuke/client/ayon_nuke/api/workio.py
diff --git a/client/ayon_core/hosts/nuke/hooks/pre_nukeassist_setup.py b/server_addon/nuke/client/ayon_nuke/hooks/pre_nukeassist_setup.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/hooks/pre_nukeassist_setup.py
rename to server_addon/nuke/client/ayon_nuke/hooks/pre_nukeassist_setup.py
diff --git a/client/ayon_core/hosts/nuke/plugins/__init__.py b/server_addon/nuke/client/ayon_nuke/plugins/__init__.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/plugins/__init__.py
rename to server_addon/nuke/client/ayon_nuke/plugins/__init__.py
diff --git a/client/ayon_core/hosts/nuke/plugins/create/__init__.py b/server_addon/nuke/client/ayon_nuke/plugins/create/__init__.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/plugins/create/__init__.py
rename to server_addon/nuke/client/ayon_nuke/plugins/create/__init__.py
diff --git a/client/ayon_core/hosts/nuke/plugins/create/convert_legacy.py b/server_addon/nuke/client/ayon_nuke/plugins/create/convert_legacy.py
similarity index 93%
rename from client/ayon_core/hosts/nuke/plugins/create/convert_legacy.py
rename to server_addon/nuke/client/ayon_nuke/plugins/create/convert_legacy.py
index 8fb8abfbbf..65e719d15b 100644
--- a/client/ayon_core/hosts/nuke/plugins/create/convert_legacy.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/create/convert_legacy.py
@@ -1,12 +1,12 @@
from ayon_core.pipeline import AYON_INSTANCE_ID, AVALON_INSTANCE_ID
from ayon_core.pipeline.create.creator_plugins import ProductConvertorPlugin
-from ayon_core.hosts.nuke.api.lib import (
+from ayon_nuke.api.lib import (
INSTANCE_DATA_KNOB,
get_node_data,
get_avalon_knob_data,
NODE_TAB_NAME,
)
-from ayon_core.hosts.nuke.api.plugin import convert_to_valid_instaces
+from ayon_nuke.api.plugin import convert_to_valid_instaces
import nuke
diff --git a/client/ayon_core/hosts/nuke/plugins/create/create_backdrop.py b/server_addon/nuke/client/ayon_nuke/plugins/create/create_backdrop.py
similarity index 95%
rename from client/ayon_core/hosts/nuke/plugins/create/create_backdrop.py
rename to server_addon/nuke/client/ayon_nuke/plugins/create/create_backdrop.py
index cefd9501ec..f97b9efeb6 100644
--- a/client/ayon_core/hosts/nuke/plugins/create/create_backdrop.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/create/create_backdrop.py
@@ -1,6 +1,6 @@
from nukescripts import autoBackdrop
-from ayon_core.hosts.nuke.api import (
+from ayon_nuke.api import (
NukeCreator,
maintained_selection,
select_nodes
@@ -10,6 +10,8 @@ from ayon_core.hosts.nuke.api import (
class CreateBackdrop(NukeCreator):
"""Add Publishable Backdrop"""
+ settings_category = "nuke"
+
identifier = "create_backdrop"
label = "Nukenodes (backdrop)"
product_type = "nukenodes"
diff --git a/client/ayon_core/hosts/nuke/plugins/create/create_camera.py b/server_addon/nuke/client/ayon_nuke/plugins/create/create_camera.py
similarity index 95%
rename from client/ayon_core/hosts/nuke/plugins/create/create_camera.py
rename to server_addon/nuke/client/ayon_nuke/plugins/create/create_camera.py
index 764de84dcf..69e5b9c676 100644
--- a/client/ayon_core/hosts/nuke/plugins/create/create_camera.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/create/create_camera.py
@@ -1,10 +1,10 @@
import nuke
-from ayon_core.hosts.nuke.api import (
+from ayon_nuke.api import (
NukeCreator,
NukeCreatorError,
maintained_selection
)
-from ayon_core.hosts.nuke.api.lib import (
+from ayon_nuke.api.lib import (
create_camera_node_by_version
)
@@ -12,6 +12,8 @@ from ayon_core.hosts.nuke.api.lib import (
class CreateCamera(NukeCreator):
"""Add Publishable Camera"""
+ settings_category = "nuke"
+
identifier = "create_camera"
label = "Camera (3d)"
product_type = "camera"
diff --git a/client/ayon_core/hosts/nuke/plugins/create/create_gizmo.py b/server_addon/nuke/client/ayon_nuke/plugins/create/create_gizmo.py
similarity index 96%
rename from client/ayon_core/hosts/nuke/plugins/create/create_gizmo.py
rename to server_addon/nuke/client/ayon_nuke/plugins/create/create_gizmo.py
index ccc6aa13bd..6be7cd58db 100644
--- a/client/ayon_core/hosts/nuke/plugins/create/create_gizmo.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/create/create_gizmo.py
@@ -1,5 +1,5 @@
import nuke
-from ayon_core.hosts.nuke.api import (
+from ayon_nuke.api import (
NukeCreator,
NukeCreatorError,
maintained_selection
@@ -9,6 +9,8 @@ from ayon_core.hosts.nuke.api import (
class CreateGizmo(NukeCreator):
"""Add Publishable Group as gizmo"""
+ settings_category = "nuke"
+
identifier = "create_gizmo"
label = "Gizmo (group)"
product_type = "gizmo"
diff --git a/client/ayon_core/hosts/nuke/plugins/create/create_model.py b/server_addon/nuke/client/ayon_nuke/plugins/create/create_model.py
similarity index 96%
rename from client/ayon_core/hosts/nuke/plugins/create/create_model.py
rename to server_addon/nuke/client/ayon_nuke/plugins/create/create_model.py
index 507b7a1b57..b7d7b740c2 100644
--- a/client/ayon_core/hosts/nuke/plugins/create/create_model.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/create/create_model.py
@@ -1,5 +1,5 @@
import nuke
-from ayon_core.hosts.nuke.api import (
+from ayon_nuke.api import (
NukeCreator,
NukeCreatorError,
maintained_selection
@@ -9,6 +9,8 @@ from ayon_core.hosts.nuke.api import (
class CreateModel(NukeCreator):
"""Add Publishable Camera"""
+ settings_category = "nuke"
+
identifier = "create_model"
label = "Model (3d)"
product_type = "model"
diff --git a/client/ayon_core/hosts/nuke/plugins/create/create_source.py b/server_addon/nuke/client/ayon_nuke/plugins/create/create_source.py
similarity index 97%
rename from client/ayon_core/hosts/nuke/plugins/create/create_source.py
rename to server_addon/nuke/client/ayon_nuke/plugins/create/create_source.py
index ac6b8f694b..1579cebb1d 100644
--- a/client/ayon_core/hosts/nuke/plugins/create/create_source.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/create/create_source.py
@@ -1,7 +1,7 @@
import nuke
import six
import sys
-from ayon_core.hosts.nuke.api import (
+from ayon_nuke.api import (
INSTANCE_DATA_KNOB,
NukeCreator,
NukeCreatorError,
@@ -15,6 +15,8 @@ from ayon_core.pipeline import (
class CreateSource(NukeCreator):
"""Add Publishable Read with source"""
+ settings_category = "nuke"
+
identifier = "create_source"
label = "Source (read)"
product_type = "source"
diff --git a/client/ayon_core/hosts/nuke/plugins/create/create_write_image.py b/server_addon/nuke/client/ayon_nuke/plugins/create/create_write_image.py
similarity index 91%
rename from client/ayon_core/hosts/nuke/plugins/create/create_write_image.py
rename to server_addon/nuke/client/ayon_nuke/plugins/create/create_write_image.py
index 770726e34f..2268817e76 100644
--- a/client/ayon_core/hosts/nuke/plugins/create/create_write_image.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/create/create_write_image.py
@@ -11,11 +11,14 @@ from ayon_core.lib import (
UISeparatorDef,
EnumDef
)
-from ayon_core.hosts.nuke import api as napi
-from ayon_core.hosts.nuke.api.plugin import exposed_write_knobs
+from ayon_nuke import api as napi
+from ayon_nuke.api.plugin import exposed_write_knobs
class CreateWriteImage(napi.NukeWriteCreator):
+
+ settings_category = "nuke"
+
identifier = "create_write_image"
label = "Image (write)"
product_type = "image"
@@ -65,12 +68,16 @@ class CreateWriteImage(napi.NukeWriteCreator):
)
def create_instance_node(self, product_name, instance_data):
+ settings = self.project_settings["nuke"]["create"]["CreateWriteImage"]
# add fpath_template
write_data = {
"creator": self.__class__.__name__,
"productName": product_name,
- "fpath_template": self.temp_rendering_path_template
+ "fpath_template": self.temp_rendering_path_template,
+ "render_on_farm": (
+ "render_on_farm" in settings["instance_attributes"]
+ )
}
write_data.update(instance_data)
diff --git a/client/ayon_core/hosts/nuke/plugins/create/create_write_prerender.py b/server_addon/nuke/client/ayon_nuke/plugins/create/create_write_prerender.py
similarity index 90%
rename from client/ayon_core/hosts/nuke/plugins/create/create_write_prerender.py
rename to server_addon/nuke/client/ayon_nuke/plugins/create/create_write_prerender.py
index 96ac2fac9c..014e91e81c 100644
--- a/client/ayon_core/hosts/nuke/plugins/create/create_write_prerender.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/create/create_write_prerender.py
@@ -8,11 +8,14 @@ from ayon_core.pipeline import (
from ayon_core.lib import (
BoolDef
)
-from ayon_core.hosts.nuke import api as napi
-from ayon_core.hosts.nuke.api.plugin import exposed_write_knobs
+from ayon_nuke import api as napi
+from ayon_nuke.api.plugin import exposed_write_knobs
class CreateWritePrerender(napi.NukeWriteCreator):
+
+ settings_category = "nuke"
+
identifier = "create_write_prerender"
label = "Prerender (write)"
product_type = "prerender"
@@ -46,11 +49,17 @@ class CreateWritePrerender(napi.NukeWriteCreator):
return attr_defs
def create_instance_node(self, product_name, instance_data):
+ settings = self.project_settings["nuke"]["create"]
+ settings = settings["CreateWritePrerender"]
+
# add fpath_template
write_data = {
"creator": self.__class__.__name__,
"productName": product_name,
- "fpath_template": self.temp_rendering_path_template
+ "fpath_template": self.temp_rendering_path_template,
+ "render_on_farm": (
+ "render_on_farm" in settings["instance_attributes"]
+ )
}
write_data.update(instance_data)
diff --git a/client/ayon_core/hosts/nuke/plugins/create/create_write_render.py b/server_addon/nuke/client/ayon_nuke/plugins/create/create_write_render.py
similarity index 89%
rename from client/ayon_core/hosts/nuke/plugins/create/create_write_render.py
rename to server_addon/nuke/client/ayon_nuke/plugins/create/create_write_render.py
index 24bddb3d26..bed081c882 100644
--- a/client/ayon_core/hosts/nuke/plugins/create/create_write_render.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/create/create_write_render.py
@@ -8,11 +8,14 @@ from ayon_core.pipeline import (
from ayon_core.lib import (
BoolDef
)
-from ayon_core.hosts.nuke import api as napi
-from ayon_core.hosts.nuke.api.plugin import exposed_write_knobs
+from ayon_nuke import api as napi
+from ayon_nuke.api.plugin import exposed_write_knobs
class CreateWriteRender(napi.NukeWriteCreator):
+
+ settings_category = "nuke"
+
identifier = "create_write_render"
label = "Render (write)"
product_type = "render"
@@ -40,11 +43,16 @@ class CreateWriteRender(napi.NukeWriteCreator):
return attr_defs
def create_instance_node(self, product_name, instance_data):
+ settings = self.project_settings["nuke"]["create"]["CreateWriteRender"]
+
# add fpath_template
write_data = {
"creator": self.__class__.__name__,
"productName": product_name,
- "fpath_template": self.temp_rendering_path_template
+ "fpath_template": self.temp_rendering_path_template,
+ "render_on_farm": (
+ "render_on_farm" in settings["instance_attributes"]
+ )
}
write_data.update(instance_data)
diff --git a/client/ayon_core/hosts/nuke/plugins/create/workfile_creator.py b/server_addon/nuke/client/ayon_nuke/plugins/create/workfile_creator.py
similarity index 96%
rename from client/ayon_core/hosts/nuke/plugins/create/workfile_creator.py
rename to server_addon/nuke/client/ayon_nuke/plugins/create/workfile_creator.py
index b9d83a2b48..463d898224 100644
--- a/client/ayon_core/hosts/nuke/plugins/create/workfile_creator.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/create/workfile_creator.py
@@ -1,11 +1,11 @@
import ayon_api
-import ayon_core.hosts.nuke.api as api
+import ayon_nuke.api as api
from ayon_core.pipeline import (
AutoCreator,
CreatedInstance,
)
-from ayon_core.hosts.nuke.api import (
+from ayon_nuke.api import (
INSTANCE_DATA_KNOB,
set_node_data
)
@@ -13,6 +13,9 @@ import nuke
class WorkfileCreator(AutoCreator):
+
+ settings_category = "nuke"
+
identifier = "workfile"
product_type = "workfile"
diff --git a/client/ayon_core/hosts/nuke/plugins/inventory/repair_old_loaders.py b/server_addon/nuke/client/ayon_nuke/plugins/inventory/repair_old_loaders.py
similarity index 94%
rename from client/ayon_core/hosts/nuke/plugins/inventory/repair_old_loaders.py
rename to server_addon/nuke/client/ayon_nuke/plugins/inventory/repair_old_loaders.py
index 7bb5c8ef20..11d65d4b8c 100644
--- a/client/ayon_core/hosts/nuke/plugins/inventory/repair_old_loaders.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/inventory/repair_old_loaders.py
@@ -1,6 +1,6 @@
from ayon_core.lib import Logger
from ayon_core.pipeline import InventoryAction
-from ayon_core.hosts.nuke.api.lib import set_avalon_knob_data
+from ayon_nuke.api.lib import set_avalon_knob_data
class RepairOldLoaders(InventoryAction):
diff --git a/client/ayon_core/hosts/nuke/plugins/inventory/select_containers.py b/server_addon/nuke/client/ayon_nuke/plugins/inventory/select_containers.py
similarity index 88%
rename from client/ayon_core/hosts/nuke/plugins/inventory/select_containers.py
rename to server_addon/nuke/client/ayon_nuke/plugins/inventory/select_containers.py
index 2fa9c06984..f67c8c16e9 100644
--- a/client/ayon_core/hosts/nuke/plugins/inventory/select_containers.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/inventory/select_containers.py
@@ -1,5 +1,5 @@
from ayon_core.pipeline import InventoryAction
-from ayon_core.hosts.nuke.api.command import viewer_update_and_undo_stop
+from ayon_nuke.api.command import viewer_update_and_undo_stop
class SelectContainers(InventoryAction):
diff --git a/client/ayon_core/hosts/nuke/plugins/load/actions.py b/server_addon/nuke/client/ayon_nuke/plugins/load/actions.py
similarity index 98%
rename from client/ayon_core/hosts/nuke/plugins/load/actions.py
rename to server_addon/nuke/client/ayon_nuke/plugins/load/actions.py
index 53cb03087b..a4e2b156a3 100644
--- a/client/ayon_core/hosts/nuke/plugins/load/actions.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/load/actions.py
@@ -4,7 +4,7 @@
from ayon_core.lib import Logger
from ayon_core.pipeline import load
-from ayon_core.hosts.nuke.api import lib
+from ayon_nuke.api import lib
log = Logger.get_logger(__name__)
diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_backdrop.py b/server_addon/nuke/client/ayon_nuke/plugins/load/load_backdrop.py
similarity index 96%
rename from client/ayon_core/hosts/nuke/plugins/load/load_backdrop.py
rename to server_addon/nuke/client/ayon_nuke/plugins/load/load_backdrop.py
index 7d823919dc..054a56d041 100644
--- a/client/ayon_core/hosts/nuke/plugins/load/load_backdrop.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/load/load_backdrop.py
@@ -6,7 +6,7 @@ from ayon_core.pipeline import (
load,
get_representation_path,
)
-from ayon_core.hosts.nuke.api.lib import (
+from ayon_nuke.api.lib import (
find_free_space_to_paste_nodes,
maintained_selection,
reset_selection,
@@ -14,8 +14,8 @@ from ayon_core.hosts.nuke.api.lib import (
get_avalon_knob_data,
set_avalon_knob_data
)
-from ayon_core.hosts.nuke.api.command import viewer_update_and_undo_stop
-from ayon_core.hosts.nuke.api import containerise, update_container
+from ayon_nuke.api.command import viewer_update_and_undo_stop
+from ayon_nuke.api import containerise, update_container
class LoadBackdropNodes(load.LoaderPlugin):
@@ -25,6 +25,8 @@ class LoadBackdropNodes(load.LoaderPlugin):
representations = {"*"}
extensions = {"nk"}
+ settings_category = "nuke"
+
label = "Import Nuke Nodes"
order = 0
icon = "eye"
@@ -62,7 +64,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 +208,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
diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_camera_abc.py b/server_addon/nuke/client/ayon_nuke/plugins/load/load_camera_abc.py
similarity index 97%
rename from client/ayon_core/hosts/nuke/plugins/load/load_camera_abc.py
rename to server_addon/nuke/client/ayon_nuke/plugins/load/load_camera_abc.py
index 14c54c3adc..3930cf52fa 100644
--- a/client/ayon_core/hosts/nuke/plugins/load/load_camera_abc.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/load/load_camera_abc.py
@@ -5,12 +5,12 @@ from ayon_core.pipeline import (
load,
get_representation_path,
)
-from ayon_core.hosts.nuke.api import (
+from ayon_nuke.api import (
containerise,
update_container,
viewer_update_and_undo_stop
)
-from ayon_core.hosts.nuke.api.lib import (
+from ayon_nuke.api.lib import (
maintained_selection
)
@@ -24,6 +24,8 @@ class AlembicCameraLoader(load.LoaderPlugin):
representations = {"*"}
extensions = {"abc"}
+ settings_category = "nuke"
+
label = "Load Alembic Camera"
icon = "camera"
color = "orange"
@@ -48,7 +50,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 +125,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
diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_clip.py b/server_addon/nuke/client/ayon_nuke/plugins/load/load_clip.py
similarity index 90%
rename from client/ayon_core/hosts/nuke/plugins/load/load_clip.py
rename to server_addon/nuke/client/ayon_nuke/plugins/load/load_clip.py
index df8f2ab018..d1e38eea6b 100644
--- a/client/ayon_core/hosts/nuke/plugins/load/load_clip.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/load/load_clip.py
@@ -9,13 +9,14 @@ 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 (
+from ayon_nuke.api.lib import (
get_imageio_input_colorspace,
maintained_selection
)
-from ayon_core.hosts.nuke.api import (
+from ayon_nuke.api import (
containerise,
update_container,
viewer_update_and_undo_stop,
@@ -25,7 +26,7 @@ from ayon_core.lib.transcoding import (
VIDEO_EXTENSIONS,
IMAGE_EXTENSIONS
)
-from ayon_core.hosts.nuke.api import plugin
+from ayon_nuke.api import plugin
class LoadClip(plugin.NukeLoader):
@@ -47,6 +48,8 @@ class LoadClip(plugin.NukeLoader):
ext.lstrip(".") for ext in IMAGE_EXTENSIONS.union(VIDEO_EXTENSIONS)
)
+ settings_category = "nuke"
+
label = "Load Clip"
order = -20
icon = "file-video-o"
@@ -60,7 +63,8 @@ class LoadClip(plugin.NukeLoader):
# option gui
options_defaults = {
"start_at_workfile": True,
- "add_retime": True
+ "add_retime": True,
+ "deep_exr": False
}
node_name_template = "{class_name}_{ext}"
@@ -77,6 +81,11 @@ class LoadClip(plugin.NukeLoader):
"add_retime",
help="Load with retime",
default=cls.options_defaults["add_retime"]
+ ),
+ qargparse.Boolean(
+ "deep_exr",
+ help="Read with deep exr",
+ default=cls.options_defaults["deep_exr"]
)
]
@@ -112,6 +121,9 @@ class LoadClip(plugin.NukeLoader):
add_retime = options.get(
"add_retime", self.options_defaults["add_retime"])
+ deep_exr = options.get(
+ "deep_exr", self.options_defaults["deep_exr"])
+
repre_id = repre_entity["id"]
self.log.debug(
@@ -152,13 +164,21 @@ class LoadClip(plugin.NukeLoader):
return
read_name = self._get_node_name(context)
-
- # Create the Loader with the filename path set
- read_node = nuke.createNode(
- "Read",
- "name {}".format(read_name),
- inpanel=False
- )
+ read_node = None
+ if deep_exr:
+ # Create the Loader with the filename path set
+ read_node = nuke.createNode(
+ "DeepRead",
+ "name {}".format(read_name),
+ inpanel=False
+ )
+ else:
+ # Create the Loader with the filename path set
+ read_node = nuke.createNode(
+ "Read",
+ "name {}".format(read_name),
+ inpanel=False
+ )
# get colorspace
colorspace = (
@@ -170,14 +190,14 @@ class LoadClip(plugin.NukeLoader):
# we will switch off undo-ing
with viewer_update_and_undo_stop():
read_node["file"].setValue(filepath)
-
- self.set_colorspace_to_node(
- read_node,
- filepath,
- project_name,
- version_entity,
- repre_entity
- )
+ if read_node.Class() == "Read":
+ self.set_colorspace_to_node(
+ read_node,
+ filepath,
+ project_name,
+ version_entity,
+ repre_entity
+ )
self._set_range_to_node(
read_node, first, last, start_at_workfile, slate_frames
@@ -197,7 +217,6 @@ class LoadClip(plugin.NukeLoader):
"frameStart",
"frameEnd",
"source",
- "author",
"fps",
"handleStart",
"handleEnd",
@@ -328,13 +347,14 @@ class LoadClip(plugin.NukeLoader):
# to avoid multiple undo steps for rest of process
# we will switch off undo-ing
with viewer_update_and_undo_stop():
- self.set_colorspace_to_node(
- read_node,
- filepath,
- project_name,
- version_entity,
- repre_entity
- )
+ if read_node.Class() == "Read":
+ self.set_colorspace_to_node(
+ read_node,
+ filepath,
+ project_name,
+ version_entity,
+ repre_entity
+ )
self._set_range_to_node(read_node, first, last, start_at_workfile)
@@ -347,8 +367,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 +566,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}")
diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_effects.py b/server_addon/nuke/client/ayon_nuke/plugins/load/load_effects.py
similarity index 99%
rename from client/ayon_core/hosts/nuke/plugins/load/load_effects.py
rename to server_addon/nuke/client/ayon_nuke/plugins/load/load_effects.py
index a87c81295a..e923a02424 100644
--- a/client/ayon_core/hosts/nuke/plugins/load/load_effects.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/load/load_effects.py
@@ -8,7 +8,7 @@ from ayon_core.pipeline import (
load,
get_representation_path,
)
-from ayon_core.hosts.nuke.api import (
+from ayon_nuke.api import (
containerise,
update_container,
viewer_update_and_undo_stop
@@ -22,13 +22,14 @@ class LoadEffects(load.LoaderPlugin):
representations = {"*"}
extensions = {"json"}
+ settings_category = "nuke"
+
label = "Load Effects - nodes"
order = 0
icon = "cc"
color = "white"
ignore_attr = ["useLifetime"]
-
def load(self, context, name, namespace, data):
"""
Loading function to get the soft effects to particular read node
@@ -69,7 +70,6 @@ class LoadEffects(load.LoaderPlugin):
"handleStart",
"handleEnd",
"source",
- "author",
"fps"
]:
data_imprint[k] = version_attributes[k]
@@ -189,7 +189,6 @@ class LoadEffects(load.LoaderPlugin):
"handleStart",
"handleEnd",
"source",
- "author",
"fps",
]:
data_imprint[k] = version_attributes[k]
diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_effects_ip.py b/server_addon/nuke/client/ayon_nuke/plugins/load/load_effects_ip.py
similarity index 98%
rename from client/ayon_core/hosts/nuke/plugins/load/load_effects_ip.py
rename to server_addon/nuke/client/ayon_nuke/plugins/load/load_effects_ip.py
index 8fa1347598..ce7e7debeb 100644
--- a/client/ayon_core/hosts/nuke/plugins/load/load_effects_ip.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/load/load_effects_ip.py
@@ -8,8 +8,8 @@ from ayon_core.pipeline import (
load,
get_representation_path,
)
-from ayon_core.hosts.nuke.api import lib
-from ayon_core.hosts.nuke.api import (
+from ayon_nuke.api import lib
+from ayon_nuke.api import (
containerise,
update_container,
viewer_update_and_undo_stop
@@ -23,6 +23,8 @@ class LoadEffectsInputProcess(load.LoaderPlugin):
representations = {"*"}
extensions = {"json"}
+ settings_category = "nuke"
+
label = "Load Effects - Input Process"
order = 0
icon = "eye"
@@ -69,7 +71,6 @@ class LoadEffectsInputProcess(load.LoaderPlugin):
"handleStart",
"handleEnd",
"source",
- "author",
"fps"
]:
data_imprint[k] = version_attributes[k]
@@ -192,7 +193,6 @@ class LoadEffectsInputProcess(load.LoaderPlugin):
"handleStart",
"handleEnd",
"source",
- "author",
"fps"
]:
data_imprint[k] = version_attributes[k]
diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_gizmo.py b/server_addon/nuke/client/ayon_nuke/plugins/load/load_gizmo.py
similarity index 97%
rename from client/ayon_core/hosts/nuke/plugins/load/load_gizmo.py
rename to server_addon/nuke/client/ayon_nuke/plugins/load/load_gizmo.py
index 95f85bacfc..1c91af0c1c 100644
--- a/client/ayon_core/hosts/nuke/plugins/load/load_gizmo.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/load/load_gizmo.py
@@ -5,13 +5,13 @@ from ayon_core.pipeline import (
load,
get_representation_path,
)
-from ayon_core.hosts.nuke.api.lib import (
+from ayon_nuke.api.lib import (
maintained_selection,
get_avalon_knob_data,
set_avalon_knob_data,
swap_node_with_dependency,
)
-from ayon_core.hosts.nuke.api import (
+from ayon_nuke.api import (
containerise,
update_container,
viewer_update_and_undo_stop
@@ -25,6 +25,8 @@ class LoadGizmo(load.LoaderPlugin):
representations = {"*"}
extensions = {"nk"}
+ settings_category = "nuke"
+
label = "Load Gizmo"
order = 0
icon = "dropbox"
@@ -71,7 +73,6 @@ class LoadGizmo(load.LoaderPlugin):
"handleStart",
"handleEnd",
"source",
- "author",
"fps"
]:
data_imprint[k] = version_attributes[k]
@@ -139,7 +140,6 @@ class LoadGizmo(load.LoaderPlugin):
"handleStart",
"handleEnd",
"source",
- "author",
"fps"
]:
data_imprint[k] = version_attributes[k]
diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_gizmo_ip.py b/server_addon/nuke/client/ayon_nuke/plugins/load/load_gizmo_ip.py
similarity index 98%
rename from client/ayon_core/hosts/nuke/plugins/load/load_gizmo_ip.py
rename to server_addon/nuke/client/ayon_nuke/plugins/load/load_gizmo_ip.py
index 3112e27811..36e878fdf1 100644
--- a/client/ayon_core/hosts/nuke/plugins/load/load_gizmo_ip.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/load/load_gizmo_ip.py
@@ -6,14 +6,14 @@ from ayon_core.pipeline import (
load,
get_representation_path,
)
-from ayon_core.hosts.nuke.api.lib import (
+from ayon_nuke.api.lib import (
maintained_selection,
create_backdrop,
get_avalon_knob_data,
set_avalon_knob_data,
swap_node_with_dependency,
)
-from ayon_core.hosts.nuke.api import (
+from ayon_nuke.api import (
containerise,
update_container,
viewer_update_and_undo_stop
@@ -27,6 +27,8 @@ class LoadGizmoInputProcess(load.LoaderPlugin):
representations = {"*"}
extensions = {"nk"}
+ settings_category = "nuke"
+
label = "Load Gizmo - Input Process"
order = 0
icon = "eye"
@@ -73,7 +75,6 @@ class LoadGizmoInputProcess(load.LoaderPlugin):
"handleStart",
"handleEnd",
"source",
- "author",
"fps"
]:
data_imprint[k] = version_attributes[k]
@@ -145,7 +146,6 @@ class LoadGizmoInputProcess(load.LoaderPlugin):
"handleStart",
"handleEnd",
"source",
- "author",
"fps"
]:
data_imprint[k] = version_attributes[k]
diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_image.py b/server_addon/nuke/client/ayon_nuke/plugins/load/load_image.py
similarity index 96%
rename from client/ayon_core/hosts/nuke/plugins/load/load_image.py
rename to server_addon/nuke/client/ayon_nuke/plugins/load/load_image.py
index d825b621fc..0c43f5a5ca 100644
--- a/client/ayon_core/hosts/nuke/plugins/load/load_image.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/load/load_image.py
@@ -7,10 +7,10 @@ from ayon_core.pipeline import (
load,
get_representation_path,
)
-from ayon_core.hosts.nuke.api.lib import (
+from ayon_nuke.api.lib import (
get_imageio_input_colorspace
)
-from ayon_core.hosts.nuke.api import (
+from ayon_nuke.api import (
containerise,
update_container,
viewer_update_and_undo_stop
@@ -33,9 +33,9 @@ class LoadImage(load.LoaderPlugin):
"image",
}
representations = {"*"}
- extensions = set(
- ext.lstrip(".") for ext in IMAGE_EXTENSIONS
- )
+ extensions = set(ext.lstrip(".") for ext in IMAGE_EXTENSIONS)
+
+ settings_category = "nuke"
label = "Load Image"
order = -10
@@ -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
diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_matchmove.py b/server_addon/nuke/client/ayon_nuke/plugins/load/load_matchmove.py
similarity index 95%
rename from client/ayon_core/hosts/nuke/plugins/load/load_matchmove.py
rename to server_addon/nuke/client/ayon_nuke/plugins/load/load_matchmove.py
index beebd0458f..c1b5a24504 100644
--- a/client/ayon_core/hosts/nuke/plugins/load/load_matchmove.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/load/load_matchmove.py
@@ -11,6 +11,8 @@ class MatchmoveLoader(load.LoaderPlugin):
representations = {"*"}
extensions = {"py"}
+ settings_category = "nuke"
+
defaults = ["Camera", "Object"]
label = "Run matchmove script"
diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_model.py b/server_addon/nuke/client/ayon_nuke/plugins/load/load_model.py
similarity index 97%
rename from client/ayon_core/hosts/nuke/plugins/load/load_model.py
rename to server_addon/nuke/client/ayon_nuke/plugins/load/load_model.py
index 0326e0a4fc..551147be96 100644
--- a/client/ayon_core/hosts/nuke/plugins/load/load_model.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/load/load_model.py
@@ -5,8 +5,8 @@ from ayon_core.pipeline import (
load,
get_representation_path,
)
-from ayon_core.hosts.nuke.api.lib import maintained_selection
-from ayon_core.hosts.nuke.api import (
+from ayon_nuke.api.lib import maintained_selection
+from ayon_nuke.api import (
containerise,
update_container,
viewer_update_and_undo_stop
@@ -22,6 +22,8 @@ class AlembicModelLoader(load.LoaderPlugin):
representations = {"*"}
extensions = {"abc"}
+ settings_category = "nuke"
+
label = "Load Alembic"
icon = "cube"
color = "orange"
@@ -47,7 +49,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 +132,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
diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_ociolook.py b/server_addon/nuke/client/ayon_nuke/plugins/load/load_ociolook.py
similarity index 99%
rename from client/ayon_core/hosts/nuke/plugins/load/load_ociolook.py
rename to server_addon/nuke/client/ayon_nuke/plugins/load/load_ociolook.py
index c369030b65..bdff8d7e28 100644
--- a/client/ayon_core/hosts/nuke/plugins/load/load_ociolook.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/load/load_ociolook.py
@@ -10,7 +10,7 @@ from ayon_core.pipeline import (
load,
get_representation_path,
)
-from ayon_core.hosts.nuke.api import (
+from ayon_nuke.api import (
containerise,
viewer_update_and_undo_stop,
update_container,
@@ -24,6 +24,8 @@ class LoadOcioLookNodes(load.LoaderPlugin):
representations = {"*"}
extensions = {"json"}
+ settings_category = "nuke"
+
label = "Load OcioLook [nodes]"
order = 0
icon = "cc"
diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_script_precomp.py b/server_addon/nuke/client/ayon_nuke/plugins/load/load_script_precomp.py
similarity index 96%
rename from client/ayon_core/hosts/nuke/plugins/load/load_script_precomp.py
rename to server_addon/nuke/client/ayon_nuke/plugins/load/load_script_precomp.py
index 3e554f9d3b..cf543dabfd 100644
--- a/client/ayon_core/hosts/nuke/plugins/load/load_script_precomp.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/load/load_script_precomp.py
@@ -5,8 +5,8 @@ from ayon_core.pipeline import (
load,
get_representation_path,
)
-from ayon_core.hosts.nuke.api.lib import get_avalon_knob_data
-from ayon_core.hosts.nuke.api import (
+from ayon_nuke.api.lib import get_avalon_knob_data
+from ayon_nuke.api import (
containerise,
update_container,
viewer_update_and_undo_stop
@@ -20,6 +20,8 @@ class LinkAsGroup(load.LoaderPlugin):
representations = {"*"}
extensions = {"nk"}
+ settings_category = "nuke"
+
label = "Load Precomp"
order = 0
icon = "file"
@@ -55,7 +57,6 @@ class LinkAsGroup(load.LoaderPlugin):
"handleStart",
"handleEnd",
"source",
- "author",
"fps"
]:
data_imprint[k] = version_attributes[k]
@@ -131,7 +132,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
diff --git a/client/ayon_core/hosts/nuke/plugins/publish/collect_backdrop.py b/server_addon/nuke/client/ayon_nuke/plugins/publish/collect_backdrop.py
similarity index 96%
rename from client/ayon_core/hosts/nuke/plugins/publish/collect_backdrop.py
rename to server_addon/nuke/client/ayon_nuke/plugins/publish/collect_backdrop.py
index fc17de95b4..1471159380 100644
--- a/client/ayon_core/hosts/nuke/plugins/publish/collect_backdrop.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/publish/collect_backdrop.py
@@ -1,6 +1,6 @@
from pprint import pformat
import pyblish.api
-from ayon_core.hosts.nuke.api import lib as pnlib
+from ayon_nuke.api import lib as pnlib
import nuke
@@ -13,6 +13,8 @@ class CollectBackdrops(pyblish.api.InstancePlugin):
hosts = ["nuke"]
families = ["nukenodes"]
+ settings_category = "nuke"
+
def process(self, instance):
self.log.debug(pformat(instance.data))
diff --git a/client/ayon_core/hosts/nuke/plugins/publish/collect_context_data.py b/server_addon/nuke/client/ayon_nuke/plugins/publish/collect_context_data.py
similarity index 97%
rename from client/ayon_core/hosts/nuke/plugins/publish/collect_context_data.py
rename to server_addon/nuke/client/ayon_nuke/plugins/publish/collect_context_data.py
index 0a032e5a2d..33c8e63e82 100644
--- a/client/ayon_core/hosts/nuke/plugins/publish/collect_context_data.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/publish/collect_context_data.py
@@ -2,7 +2,7 @@ import os
import nuke
import pyblish.api
from ayon_core.lib import get_version_from_path
-import ayon_core.hosts.nuke.api as napi
+import ayon_nuke.api as napi
from ayon_core.pipeline import KnownPublishError
@@ -13,6 +13,8 @@ class CollectContextData(pyblish.api.ContextPlugin):
label = "Collect context data"
hosts = ['nuke']
+ settings_category = "nuke"
+
def process(self, context): # sourcery skip: avoid-builtin-shadow
root_node = nuke.root()
diff --git a/client/ayon_core/hosts/nuke/plugins/publish/collect_framerate.py b/server_addon/nuke/client/ayon_nuke/plugins/publish/collect_framerate.py
similarity index 91%
rename from client/ayon_core/hosts/nuke/plugins/publish/collect_framerate.py
rename to server_addon/nuke/client/ayon_nuke/plugins/publish/collect_framerate.py
index 88a449e745..cd77eab0f1 100644
--- a/client/ayon_core/hosts/nuke/plugins/publish/collect_framerate.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/publish/collect_framerate.py
@@ -13,5 +13,7 @@ class CollectFramerate(pyblish.api.ContextPlugin):
"nukeassist"
]
+ settings_category = "nuke"
+
def process(self, context):
context.data["fps"] = nuke.root()["fps"].getValue()
diff --git a/client/ayon_core/hosts/nuke/plugins/publish/collect_gizmo.py b/server_addon/nuke/client/ayon_nuke/plugins/publish/collect_gizmo.py
similarity index 98%
rename from client/ayon_core/hosts/nuke/plugins/publish/collect_gizmo.py
rename to server_addon/nuke/client/ayon_nuke/plugins/publish/collect_gizmo.py
index fda1c7ac31..ece9823b37 100644
--- a/client/ayon_core/hosts/nuke/plugins/publish/collect_gizmo.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/publish/collect_gizmo.py
@@ -11,6 +11,8 @@ class CollectGizmo(pyblish.api.InstancePlugin):
hosts = ["nuke"]
families = ["gizmo"]
+ settings_category = "nuke"
+
def process(self, instance):
gizmo_node = instance.data["transientData"]["node"]
diff --git a/server_addon/nuke/client/ayon_nuke/plugins/publish/collect_headless_farm.py b/server_addon/nuke/client/ayon_nuke/plugins/publish/collect_headless_farm.py
new file mode 100644
index 0000000000..c00b9a8f5d
--- /dev/null
+++ b/server_addon/nuke/client/ayon_nuke/plugins/publish/collect_headless_farm.py
@@ -0,0 +1,58 @@
+import pyblish.api
+
+from ayon_core.pipeline.publish import (
+ AYONPyblishPluginMixin
+)
+
+
+class CollectRenderOnFarm(pyblish.api.ContextPlugin):
+ """Setup instances for render on farm submission."""
+
+ # Needs to be after CollectFromCreateContext
+ order = pyblish.api.CollectorOrder - 0.49
+ label = "Collect Render On Farm"
+ hosts = ["nuke"]
+
+ settings_category = "nuke"
+
+ def process(self, context):
+ if not context.data.get("render_on_farm", False):
+ return
+
+ for instance in context:
+ if instance.data["family"] == "workfile":
+ instance.data["active"] = False
+ continue
+
+ # Filter out all other instances.
+ node = instance.data["transientData"]["node"]
+ if node.name() != instance.context.data["node_name"]:
+ instance.data["active"] = False
+ continue
+
+ instance.data["families"].append("render_on_farm")
+
+ # Enable for farm publishing.
+ instance.data["farm"] = True
+
+ # Skip workfile version incremental save.
+ instance.context.data["increment_script_version"] = False
+
+
+class SetupRenderOnFarm(pyblish.api.InstancePlugin, AYONPyblishPluginMixin):
+ """Setup instance for render on farm submission."""
+
+ order = pyblish.api.CollectorOrder + 0.4999
+ label = "Setup Render On Farm"
+ hosts = ["nuke"]
+ families = ["render_on_farm"]
+
+ def process(self, instance):
+ # Clear the families as we only want the main family, ei. no review
+ # etc.
+ instance.data["families"] = ["render_on_farm"]
+
+ # Use the workfile instead of published.
+ publish_attributes = instance.data["publish_attributes"]
+ plugin_attributes = publish_attributes["NukeSubmitDeadline"]
+ plugin_attributes["use_published_workfile"] = False
diff --git a/client/ayon_core/hosts/nuke/plugins/publish/collect_model.py b/server_addon/nuke/client/ayon_nuke/plugins/publish/collect_model.py
similarity index 98%
rename from client/ayon_core/hosts/nuke/plugins/publish/collect_model.py
rename to server_addon/nuke/client/ayon_nuke/plugins/publish/collect_model.py
index 1a2bc9c019..f4266bbbcb 100644
--- a/client/ayon_core/hosts/nuke/plugins/publish/collect_model.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/publish/collect_model.py
@@ -11,6 +11,8 @@ class CollectModel(pyblish.api.InstancePlugin):
hosts = ["nuke"]
families = ["model"]
+ settings_category = "nuke"
+
def process(self, instance):
geo_node = instance.data["transientData"]["node"]
diff --git a/client/ayon_core/hosts/nuke/plugins/publish/collect_nuke_instance_data.py b/server_addon/nuke/client/ayon_nuke/plugins/publish/collect_nuke_instance_data.py
similarity index 98%
rename from client/ayon_core/hosts/nuke/plugins/publish/collect_nuke_instance_data.py
rename to server_addon/nuke/client/ayon_nuke/plugins/publish/collect_nuke_instance_data.py
index 951072ff3f..d1392a8460 100644
--- a/client/ayon_core/hosts/nuke/plugins/publish/collect_nuke_instance_data.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/publish/collect_nuke_instance_data.py
@@ -11,6 +11,8 @@ class CollectInstanceData(pyblish.api.InstancePlugin):
label = "Collect Nuke Instance Data"
hosts = ["nuke", "nukeassist"]
+ settings_category = "nuke"
+
# presets
sync_workfile_version_on_families = []
diff --git a/client/ayon_core/hosts/nuke/plugins/publish/collect_reads.py b/server_addon/nuke/client/ayon_nuke/plugins/publish/collect_reads.py
similarity index 99%
rename from client/ayon_core/hosts/nuke/plugins/publish/collect_reads.py
rename to server_addon/nuke/client/ayon_nuke/plugins/publish/collect_reads.py
index af17933eb1..439374e825 100644
--- a/client/ayon_core/hosts/nuke/plugins/publish/collect_reads.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/publish/collect_reads.py
@@ -12,6 +12,8 @@ class CollectNukeReads(pyblish.api.InstancePlugin):
hosts = ["nuke", "nukeassist"]
families = ["source"]
+ settings_category = "nuke"
+
def process(self, instance):
self.log.debug("checking instance: {}".format(instance))
diff --git a/client/ayon_core/hosts/nuke/plugins/publish/collect_slate_node.py b/server_addon/nuke/client/ayon_nuke/plugins/publish/collect_slate_node.py
similarity index 97%
rename from client/ayon_core/hosts/nuke/plugins/publish/collect_slate_node.py
rename to server_addon/nuke/client/ayon_nuke/plugins/publish/collect_slate_node.py
index ac30bd6051..bb3b0083ab 100644
--- a/client/ayon_core/hosts/nuke/plugins/publish/collect_slate_node.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/publish/collect_slate_node.py
@@ -10,6 +10,8 @@ class CollectSlate(pyblish.api.InstancePlugin):
hosts = ["nuke"]
families = ["render"]
+ settings_category = "nuke"
+
def process(self, instance):
node = instance.data["transientData"]["node"]
diff --git a/client/ayon_core/hosts/nuke/plugins/publish/collect_workfile.py b/server_addon/nuke/client/ayon_nuke/plugins/publish/collect_workfile.py
similarity index 97%
rename from client/ayon_core/hosts/nuke/plugins/publish/collect_workfile.py
rename to server_addon/nuke/client/ayon_nuke/plugins/publish/collect_workfile.py
index 0f03572f8b..e4bd5ed129 100644
--- a/client/ayon_core/hosts/nuke/plugins/publish/collect_workfile.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/publish/collect_workfile.py
@@ -11,6 +11,8 @@ class CollectWorkfile(pyblish.api.InstancePlugin):
hosts = ['nuke']
families = ["workfile"]
+ settings_category = "nuke"
+
def process(self, instance): # sourcery skip: avoid-builtin-shadow
script_data = instance.context.data["scriptData"]
diff --git a/client/ayon_core/hosts/nuke/plugins/publish/collect_writes.py b/server_addon/nuke/client/ayon_nuke/plugins/publish/collect_writes.py
similarity index 97%
rename from client/ayon_core/hosts/nuke/plugins/publish/collect_writes.py
rename to server_addon/nuke/client/ayon_nuke/plugins/publish/collect_writes.py
index 745351dc49..816f493d72 100644
--- a/client/ayon_core/hosts/nuke/plugins/publish/collect_writes.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/publish/collect_writes.py
@@ -1,7 +1,7 @@
import os
import nuke
import pyblish.api
-from ayon_core.hosts.nuke import api as napi
+from ayon_nuke import api as napi
from ayon_core.pipeline import publish
@@ -14,6 +14,8 @@ class CollectNukeWrites(pyblish.api.InstancePlugin,
hosts = ["nuke", "nukeassist"]
families = ["render", "prerender", "image"]
+ settings_category = "nuke"
+
# cache
_write_nodes = {}
_frame_ranges = {}
@@ -153,6 +155,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 +177,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":
diff --git a/client/ayon_core/hosts/nuke/plugins/publish/extract_backdrop.py b/server_addon/nuke/client/ayon_nuke/plugins/publish/extract_backdrop.py
similarity index 98%
rename from client/ayon_core/hosts/nuke/plugins/publish/extract_backdrop.py
rename to server_addon/nuke/client/ayon_nuke/plugins/publish/extract_backdrop.py
index e53ce9015a..8c42920979 100644
--- a/client/ayon_core/hosts/nuke/plugins/publish/extract_backdrop.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/publish/extract_backdrop.py
@@ -5,7 +5,7 @@ import nuke
import pyblish.api
from ayon_core.pipeline import publish
-from ayon_core.hosts.nuke.api.lib import (
+from ayon_nuke.api.lib import (
maintained_selection,
reset_selection,
select_nodes
@@ -25,6 +25,8 @@ class ExtractBackdropNode(publish.Extractor):
hosts = ["nuke"]
families = ["nukenodes"]
+ settings_category = "nuke"
+
def process(self, instance):
tmp_nodes = []
child_nodes = instance.data["transientData"]["childNodes"]
diff --git a/client/ayon_core/hosts/nuke/plugins/publish/extract_camera.py b/server_addon/nuke/client/ayon_nuke/plugins/publish/extract_camera.py
similarity index 98%
rename from client/ayon_core/hosts/nuke/plugins/publish/extract_camera.py
rename to server_addon/nuke/client/ayon_nuke/plugins/publish/extract_camera.py
index a1a5acb63b..83914087e3 100644
--- a/client/ayon_core/hosts/nuke/plugins/publish/extract_camera.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/publish/extract_camera.py
@@ -6,7 +6,7 @@ import nuke
import pyblish.api
from ayon_core.pipeline import publish
-from ayon_core.hosts.nuke.api.lib import maintained_selection
+from ayon_nuke.api.lib import maintained_selection
class ExtractCamera(publish.Extractor):
@@ -17,6 +17,8 @@ class ExtractCamera(publish.Extractor):
families = ["camera"]
hosts = ["nuke"]
+ settings_category = "nuke"
+
# presets
write_geo_knobs = [
("file_type", "abc"),
diff --git a/client/ayon_core/hosts/nuke/plugins/publish/extract_gizmo.py b/server_addon/nuke/client/ayon_nuke/plugins/publish/extract_gizmo.py
similarity index 95%
rename from client/ayon_core/hosts/nuke/plugins/publish/extract_gizmo.py
rename to server_addon/nuke/client/ayon_nuke/plugins/publish/extract_gizmo.py
index 2a2e2255fd..05e3164163 100644
--- a/client/ayon_core/hosts/nuke/plugins/publish/extract_gizmo.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/publish/extract_gizmo.py
@@ -4,8 +4,8 @@ import nuke
import pyblish.api
from ayon_core.pipeline import publish
-from ayon_core.hosts.nuke.api import utils as pnutils
-from ayon_core.hosts.nuke.api.lib import (
+from ayon_nuke.api import utils as pnutils
+from ayon_nuke.api.lib import (
maintained_selection,
reset_selection,
select_nodes
@@ -23,6 +23,8 @@ class ExtractGizmo(publish.Extractor):
hosts = ["nuke"]
families = ["gizmo"]
+ settings_category = "nuke"
+
def process(self, instance):
tmp_nodes = []
orig_grpn = instance.data["transientData"]["node"]
diff --git a/server_addon/nuke/client/ayon_nuke/plugins/publish/extract_headless_farm.py b/server_addon/nuke/client/ayon_nuke/plugins/publish/extract_headless_farm.py
new file mode 100644
index 0000000000..4721fe4462
--- /dev/null
+++ b/server_addon/nuke/client/ayon_nuke/plugins/publish/extract_headless_farm.py
@@ -0,0 +1,38 @@
+import os
+from datetime import datetime
+import shutil
+
+import pyblish.api
+
+from ayon_core.pipeline import registered_host
+
+
+class ExtractRenderOnFarm(pyblish.api.InstancePlugin):
+ """Copy the workfile to a timestamped copy."""
+
+ order = pyblish.api.ExtractorOrder + 0.499
+ label = "Extract Render On Farm"
+ hosts = ["nuke"]
+ families = ["render_on_farm"]
+
+ settings_category = "nuke"
+
+ def process(self, instance):
+ if not instance.context.data.get("render_on_farm", False):
+ return
+
+ host = registered_host()
+ current_datetime = datetime.now()
+ formatted_timestamp = current_datetime.strftime("%Y%m%d%H%M%S")
+ base, ext = os.path.splitext(host.current_file())
+
+ directory = os.path.join(os.path.dirname(base), "farm_submissions")
+ if not os.path.exists(directory):
+ os.makedirs(directory)
+
+ filename = "{}_{}{}".format(
+ os.path.basename(base), formatted_timestamp, ext
+ )
+ path = os.path.join(directory, filename).replace("\\", "/")
+ instance.context.data["currentFile"] = path
+ shutil.copy(host.current_file(), path)
diff --git a/client/ayon_core/hosts/nuke/plugins/publish/extract_model.py b/server_addon/nuke/client/ayon_nuke/plugins/publish/extract_model.py
similarity index 98%
rename from client/ayon_core/hosts/nuke/plugins/publish/extract_model.py
rename to server_addon/nuke/client/ayon_nuke/plugins/publish/extract_model.py
index 36896fe595..58b9d4179b 100644
--- a/client/ayon_core/hosts/nuke/plugins/publish/extract_model.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/publish/extract_model.py
@@ -4,7 +4,7 @@ import nuke
import pyblish.api
from ayon_core.pipeline import publish
-from ayon_core.hosts.nuke.api.lib import (
+from ayon_nuke.api.lib import (
maintained_selection,
select_nodes
)
@@ -18,6 +18,8 @@ class ExtractModel(publish.Extractor):
families = ["model"]
hosts = ["nuke"]
+ settings_category = "nuke"
+
# presets
write_geo_knobs = [
("file_type", "abc"),
diff --git a/client/ayon_core/hosts/nuke/plugins/publish/extract_ouput_node.py b/server_addon/nuke/client/ayon_nuke/plugins/publish/extract_ouput_node.py
similarity index 92%
rename from client/ayon_core/hosts/nuke/plugins/publish/extract_ouput_node.py
rename to server_addon/nuke/client/ayon_nuke/plugins/publish/extract_ouput_node.py
index b8e038a4f5..52072cddc5 100644
--- a/client/ayon_core/hosts/nuke/plugins/publish/extract_ouput_node.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/publish/extract_ouput_node.py
@@ -1,6 +1,6 @@
import nuke
import pyblish.api
-from ayon_core.hosts.nuke.api.lib import maintained_selection
+from ayon_nuke.api.lib import maintained_selection
class CreateOutputNode(pyblish.api.ContextPlugin):
@@ -11,7 +11,9 @@ class CreateOutputNode(pyblish.api.ContextPlugin):
label = 'Output Node Create'
order = pyblish.api.ExtractorOrder + 0.4
families = ["workfile"]
- hosts = ['nuke']
+ hosts = ["nuke"]
+
+ settings_category = "nuke"
def process(self, context):
# capture selection state
diff --git a/client/ayon_core/hosts/nuke/plugins/publish/extract_output_directory.py b/server_addon/nuke/client/ayon_nuke/plugins/publish/extract_output_directory.py
similarity index 94%
rename from client/ayon_core/hosts/nuke/plugins/publish/extract_output_directory.py
rename to server_addon/nuke/client/ayon_nuke/plugins/publish/extract_output_directory.py
index d999d200de..45156ca9ae 100644
--- a/client/ayon_core/hosts/nuke/plugins/publish/extract_output_directory.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/publish/extract_output_directory.py
@@ -10,7 +10,7 @@ class ExtractOutputDirectory(pyblish.api.InstancePlugin):
label = "Output Directory"
optional = True
- # targets = ["process"]
+ settings_category = "nuke"
def process(self, instance):
diff --git a/client/ayon_core/hosts/nuke/plugins/publish/extract_render_local.py b/server_addon/nuke/client/ayon_nuke/plugins/publish/extract_render_local.py
similarity index 99%
rename from client/ayon_core/hosts/nuke/plugins/publish/extract_render_local.py
rename to server_addon/nuke/client/ayon_nuke/plugins/publish/extract_render_local.py
index c8be2a5564..c865684e7a 100644
--- a/client/ayon_core/hosts/nuke/plugins/publish/extract_render_local.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/publish/extract_render_local.py
@@ -4,7 +4,7 @@ import shutil
import pyblish.api
import clique
import nuke
-from ayon_core.hosts.nuke import api as napi
+from ayon_nuke import api as napi
from ayon_core.pipeline import publish
from ayon_core.lib import collect_frames
@@ -25,6 +25,8 @@ class NukeRenderLocal(publish.Extractor,
hosts = ["nuke"]
families = ["render.local", "prerender.local", "image.local"]
+ settings_category = "nuke"
+
def process(self, instance):
child_nodes = (
instance.data.get("transientData", {}).get("childNodes")
diff --git a/client/ayon_core/hosts/nuke/plugins/publish/extract_review_data.py b/server_addon/nuke/client/ayon_nuke/plugins/publish/extract_review_data.py
similarity index 97%
rename from client/ayon_core/hosts/nuke/plugins/publish/extract_review_data.py
rename to server_addon/nuke/client/ayon_nuke/plugins/publish/extract_review_data.py
index 258a019319..856616898b 100644
--- a/client/ayon_core/hosts/nuke/plugins/publish/extract_review_data.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/publish/extract_review_data.py
@@ -16,6 +16,8 @@ class ExtractReviewData(publish.Extractor):
families = ["review"]
hosts = ["nuke"]
+ settings_category = "nuke"
+
def process(self, instance):
fpath = instance.data["path"]
ext = os.path.splitext(fpath)[-1][1:]
diff --git a/client/ayon_core/hosts/nuke/plugins/publish/extract_review_data_lut.py b/server_addon/nuke/client/ayon_nuke/plugins/publish/extract_review_data_lut.py
similarity index 94%
rename from client/ayon_core/hosts/nuke/plugins/publish/extract_review_data_lut.py
rename to server_addon/nuke/client/ayon_nuke/plugins/publish/extract_review_data_lut.py
index 0674a2dd55..d3377807ea 100644
--- a/client/ayon_core/hosts/nuke/plugins/publish/extract_review_data_lut.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/publish/extract_review_data_lut.py
@@ -2,8 +2,8 @@ import os
import pyblish.api
from ayon_core.pipeline import publish
-from ayon_core.hosts.nuke.api import plugin
-from ayon_core.hosts.nuke.api.lib import maintained_selection
+from ayon_nuke.api import plugin
+from ayon_nuke.api.lib import maintained_selection
class ExtractReviewDataLut(publish.Extractor):
@@ -19,6 +19,8 @@ class ExtractReviewDataLut(publish.Extractor):
families = ["review"]
hosts = ["nuke"]
+ settings_category = "nuke"
+
def process(self, instance):
self.log.debug("Creating staging dir...")
if "representations" in instance.data:
diff --git a/client/ayon_core/hosts/nuke/plugins/publish/extract_review_intermediates.py b/server_addon/nuke/client/ayon_nuke/plugins/publish/extract_review_intermediates.py
similarity index 94%
rename from client/ayon_core/hosts/nuke/plugins/publish/extract_review_intermediates.py
rename to server_addon/nuke/client/ayon_nuke/plugins/publish/extract_review_intermediates.py
index 8d7a3ec311..b7bb911347 100644
--- a/client/ayon_core/hosts/nuke/plugins/publish/extract_review_intermediates.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/publish/extract_review_intermediates.py
@@ -4,8 +4,8 @@ from pprint import pformat
import pyblish.api
from ayon_core.pipeline import publish
-from ayon_core.hosts.nuke.api import plugin
-from ayon_core.hosts.nuke.api.lib import maintained_selection
+from ayon_nuke.api import plugin
+from ayon_nuke.api.lib import maintained_selection
class ExtractReviewIntermediates(publish.Extractor):
@@ -22,6 +22,8 @@ class ExtractReviewIntermediates(publish.Extractor):
families = ["review"]
hosts = ["nuke"]
+ settings_category = "nuke"
+
# presets
viewer_lut_raw = None
outputs = {}
@@ -136,11 +138,15 @@ class ExtractReviewIntermediates(publish.Extractor):
self, instance, o_name, o_data["extension"],
multiple_presets)
+ 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 +160,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"])
diff --git a/client/ayon_core/hosts/nuke/plugins/publish/extract_script_save.py b/server_addon/nuke/client/ayon_nuke/plugins/publish/extract_script_save.py
similarity index 85%
rename from client/ayon_core/hosts/nuke/plugins/publish/extract_script_save.py
rename to server_addon/nuke/client/ayon_nuke/plugins/publish/extract_script_save.py
index d325684a7c..ea584b6529 100644
--- a/client/ayon_core/hosts/nuke/plugins/publish/extract_script_save.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/publish/extract_script_save.py
@@ -6,7 +6,9 @@ class ExtractScriptSave(pyblish.api.InstancePlugin):
"""Save current Nuke workfile script"""
label = 'Script Save'
order = pyblish.api.ExtractorOrder - 0.1
- hosts = ['nuke']
+ hosts = ["nuke"]
+
+ settings_category = "nuke"
def process(self, instance):
diff --git a/client/ayon_core/hosts/nuke/plugins/publish/extract_slate_frame.py b/server_addon/nuke/client/ayon_nuke/plugins/publish/extract_slate_frame.py
similarity index 99%
rename from client/ayon_core/hosts/nuke/plugins/publish/extract_slate_frame.py
rename to server_addon/nuke/client/ayon_nuke/plugins/publish/extract_slate_frame.py
index 627888ac92..47750ea637 100644
--- a/client/ayon_core/hosts/nuke/plugins/publish/extract_slate_frame.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/publish/extract_slate_frame.py
@@ -7,7 +7,7 @@ import pyblish.api
import six
from ayon_core.pipeline import publish
-from ayon_core.hosts.nuke.api import (
+from ayon_nuke.api import (
maintained_selection,
duplicate_node,
get_view_process_node
@@ -27,6 +27,8 @@ class ExtractSlateFrame(publish.Extractor):
families = ["slate"]
hosts = ["nuke"]
+ settings_category = "nuke"
+
# Settings values
key_value_mapping = {
"f_submission_note": {
diff --git a/client/ayon_core/hosts/nuke/plugins/publish/help/validate_asset_context.xml b/server_addon/nuke/client/ayon_nuke/plugins/publish/help/validate_asset_context.xml
similarity index 100%
rename from client/ayon_core/hosts/nuke/plugins/publish/help/validate_asset_context.xml
rename to server_addon/nuke/client/ayon_nuke/plugins/publish/help/validate_asset_context.xml
diff --git a/client/ayon_core/hosts/nuke/plugins/publish/help/validate_backdrop.xml b/server_addon/nuke/client/ayon_nuke/plugins/publish/help/validate_backdrop.xml
similarity index 100%
rename from client/ayon_core/hosts/nuke/plugins/publish/help/validate_backdrop.xml
rename to server_addon/nuke/client/ayon_nuke/plugins/publish/help/validate_backdrop.xml
diff --git a/client/ayon_core/hosts/nuke/plugins/publish/help/validate_gizmo.xml b/server_addon/nuke/client/ayon_nuke/plugins/publish/help/validate_gizmo.xml
similarity index 100%
rename from client/ayon_core/hosts/nuke/plugins/publish/help/validate_gizmo.xml
rename to server_addon/nuke/client/ayon_nuke/plugins/publish/help/validate_gizmo.xml
diff --git a/client/ayon_core/hosts/nuke/plugins/publish/help/validate_knobs.xml b/server_addon/nuke/client/ayon_nuke/plugins/publish/help/validate_knobs.xml
similarity index 100%
rename from client/ayon_core/hosts/nuke/plugins/publish/help/validate_knobs.xml
rename to server_addon/nuke/client/ayon_nuke/plugins/publish/help/validate_knobs.xml
diff --git a/client/ayon_core/hosts/nuke/plugins/publish/help/validate_output_resolution.xml b/server_addon/nuke/client/ayon_nuke/plugins/publish/help/validate_output_resolution.xml
similarity index 100%
rename from client/ayon_core/hosts/nuke/plugins/publish/help/validate_output_resolution.xml
rename to server_addon/nuke/client/ayon_nuke/plugins/publish/help/validate_output_resolution.xml
diff --git a/client/ayon_core/hosts/nuke/plugins/publish/help/validate_proxy_mode.xml b/server_addon/nuke/client/ayon_nuke/plugins/publish/help/validate_proxy_mode.xml
similarity index 100%
rename from client/ayon_core/hosts/nuke/plugins/publish/help/validate_proxy_mode.xml
rename to server_addon/nuke/client/ayon_nuke/plugins/publish/help/validate_proxy_mode.xml
diff --git a/client/ayon_core/hosts/nuke/plugins/publish/help/validate_rendered_frames.xml b/server_addon/nuke/client/ayon_nuke/plugins/publish/help/validate_rendered_frames.xml
similarity index 100%
rename from client/ayon_core/hosts/nuke/plugins/publish/help/validate_rendered_frames.xml
rename to server_addon/nuke/client/ayon_nuke/plugins/publish/help/validate_rendered_frames.xml
diff --git a/client/ayon_core/hosts/nuke/plugins/publish/help/validate_script_attributes.xml b/server_addon/nuke/client/ayon_nuke/plugins/publish/help/validate_script_attributes.xml
similarity index 100%
rename from client/ayon_core/hosts/nuke/plugins/publish/help/validate_script_attributes.xml
rename to server_addon/nuke/client/ayon_nuke/plugins/publish/help/validate_script_attributes.xml
diff --git a/client/ayon_core/hosts/nuke/plugins/publish/help/validate_write_nodes.xml b/server_addon/nuke/client/ayon_nuke/plugins/publish/help/validate_write_nodes.xml
similarity index 100%
rename from client/ayon_core/hosts/nuke/plugins/publish/help/validate_write_nodes.xml
rename to server_addon/nuke/client/ayon_nuke/plugins/publish/help/validate_write_nodes.xml
diff --git a/client/ayon_core/hosts/nuke/plugins/publish/increment_script_version.py b/server_addon/nuke/client/ayon_nuke/plugins/publish/increment_script_version.py
similarity index 82%
rename from client/ayon_core/hosts/nuke/plugins/publish/increment_script_version.py
rename to server_addon/nuke/client/ayon_nuke/plugins/publish/increment_script_version.py
index 6b0be42ba1..36659aa2d2 100644
--- a/client/ayon_core/hosts/nuke/plugins/publish/increment_script_version.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/publish/increment_script_version.py
@@ -1,4 +1,3 @@
-
import nuke
import pyblish.api
@@ -10,9 +9,13 @@ class IncrementScriptVersion(pyblish.api.ContextPlugin):
label = "Increment Script Version"
optional = True
families = ["workfile"]
- hosts = ['nuke']
+ hosts = ["nuke"]
+
+ settings_category = "nuke"
def process(self, context):
+ if not context.data.get("increment_script_version", True):
+ return
assert all(result["success"] for result in context.data["results"]), (
"Publishing not successful so version is not increased.")
diff --git a/client/ayon_core/hosts/nuke/plugins/publish/remove_ouput_node.py b/server_addon/nuke/client/ayon_nuke/plugins/publish/remove_ouput_node.py
similarity index 91%
rename from client/ayon_core/hosts/nuke/plugins/publish/remove_ouput_node.py
rename to server_addon/nuke/client/ayon_nuke/plugins/publish/remove_ouput_node.py
index fb77e8638c..4c17cb5f56 100644
--- a/client/ayon_core/hosts/nuke/plugins/publish/remove_ouput_node.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/publish/remove_ouput_node.py
@@ -9,7 +9,9 @@ class RemoveOutputNode(pyblish.api.ContextPlugin):
label = 'Output Node Remove'
order = pyblish.api.IntegratorOrder + 0.4
families = ["workfile"]
- hosts = ['nuke']
+ hosts = ["nuke"]
+
+ settings_category = "nuke"
def process(self, context):
try:
diff --git a/client/ayon_core/hosts/nuke/plugins/publish/validate_asset_context.py b/server_addon/nuke/client/ayon_nuke/plugins/publish/validate_asset_context.py
similarity index 97%
rename from client/ayon_core/hosts/nuke/plugins/publish/validate_asset_context.py
rename to server_addon/nuke/client/ayon_nuke/plugins/publish/validate_asset_context.py
index 93a30aa438..903648fd1b 100644
--- a/client/ayon_core/hosts/nuke/plugins/publish/validate_asset_context.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/publish/validate_asset_context.py
@@ -10,7 +10,7 @@ from ayon_core.pipeline.publish import (
PublishXmlValidationError,
OptionalPyblishPluginMixin
)
-from ayon_core.hosts.nuke.api import SelectInstanceNodeAction
+from ayon_nuke.api import SelectInstanceNodeAction
class ValidateCorrectAssetContext(
@@ -34,6 +34,8 @@ class ValidateCorrectAssetContext(
]
optional = True
+ settings_category = "nuke"
+
@classmethod
def apply_settings(cls, project_settings):
"""Apply deprecated settings from project settings.
diff --git a/client/ayon_core/hosts/nuke/plugins/publish/validate_backdrop.py b/server_addon/nuke/client/ayon_nuke/plugins/publish/validate_backdrop.py
similarity index 97%
rename from client/ayon_core/hosts/nuke/plugins/publish/validate_backdrop.py
rename to server_addon/nuke/client/ayon_nuke/plugins/publish/validate_backdrop.py
index 22344c661e..f7b94e0c82 100644
--- a/client/ayon_core/hosts/nuke/plugins/publish/validate_backdrop.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/publish/validate_backdrop.py
@@ -1,6 +1,6 @@
import nuke
import pyblish
-from ayon_core.hosts.nuke import api as napi
+from ayon_nuke import api as napi
from ayon_core.pipeline.publish import (
ValidateContentsOrder,
@@ -65,6 +65,8 @@ class ValidateBackdrop(
hosts = ["nuke"]
actions = [SelectCenterInNodeGraph]
+ settings_category = "nuke"
+
def process(self, instance):
if not self.is_active(instance.data):
return
diff --git a/client/ayon_core/hosts/nuke/plugins/publish/validate_exposed_knobs.py b/server_addon/nuke/client/ayon_nuke/plugins/publish/validate_exposed_knobs.py
similarity index 97%
rename from client/ayon_core/hosts/nuke/plugins/publish/validate_exposed_knobs.py
rename to server_addon/nuke/client/ayon_nuke/plugins/publish/validate_exposed_knobs.py
index 217fe6fb85..d1b7c146fb 100644
--- a/client/ayon_core/hosts/nuke/plugins/publish/validate_exposed_knobs.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/publish/validate_exposed_knobs.py
@@ -1,7 +1,7 @@
import pyblish.api
from ayon_core.pipeline.publish import get_errored_instances_from_context
-from ayon_core.hosts.nuke.api.lib import link_knobs
+from ayon_nuke.api.lib import link_knobs
from ayon_core.pipeline.publish import (
OptionalPyblishPluginMixin,
PublishValidationError
@@ -52,6 +52,9 @@ class ValidateExposedKnobs(
label = "Validate Exposed Knobs"
actions = [RepairExposedKnobs]
hosts = ["nuke"]
+
+ settings_category = "nuke"
+
product_types_mapping = {
"render": "CreateWriteRender",
"prerender": "CreateWritePrerender",
diff --git a/client/ayon_core/hosts/nuke/plugins/publish/validate_gizmo.py b/server_addon/nuke/client/ayon_nuke/plugins/publish/validate_gizmo.py
similarity index 97%
rename from client/ayon_core/hosts/nuke/plugins/publish/validate_gizmo.py
rename to server_addon/nuke/client/ayon_nuke/plugins/publish/validate_gizmo.py
index 2cdcb90d70..55249ae931 100644
--- a/client/ayon_core/hosts/nuke/plugins/publish/validate_gizmo.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/publish/validate_gizmo.py
@@ -1,6 +1,6 @@
import pyblish
from ayon_core.pipeline import PublishXmlValidationError
-from ayon_core.hosts.nuke import api as napi
+from ayon_nuke import api as napi
import nuke
@@ -43,6 +43,8 @@ class ValidateGizmo(pyblish.api.InstancePlugin):
hosts = ["nuke"]
actions = [OpenFailedGroupNode]
+ settings_category = "nuke"
+
def process(self, instance):
grpn = instance.data["transientData"]["node"]
diff --git a/client/ayon_core/hosts/nuke/plugins/publish/validate_knobs.py b/server_addon/nuke/client/ayon_nuke/plugins/publish/validate_knobs.py
similarity index 99%
rename from client/ayon_core/hosts/nuke/plugins/publish/validate_knobs.py
rename to server_addon/nuke/client/ayon_nuke/plugins/publish/validate_knobs.py
index 8bcde9609d..ea03bd94b2 100644
--- a/client/ayon_core/hosts/nuke/plugins/publish/validate_knobs.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/publish/validate_knobs.py
@@ -32,6 +32,8 @@ class ValidateKnobs(pyblish.api.ContextPlugin):
actions = [RepairContextAction]
optional = True
+ settings_category = "nuke"
+
knobs = "{}"
def process(self, context):
diff --git a/client/ayon_core/hosts/nuke/plugins/publish/validate_output_resolution.py b/server_addon/nuke/client/ayon_nuke/plugins/publish/validate_output_resolution.py
similarity index 98%
rename from client/ayon_core/hosts/nuke/plugins/publish/validate_output_resolution.py
rename to server_addon/nuke/client/ayon_nuke/plugins/publish/validate_output_resolution.py
index e8a00d2294..440cb8b758 100644
--- a/client/ayon_core/hosts/nuke/plugins/publish/validate_output_resolution.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/publish/validate_output_resolution.py
@@ -1,6 +1,6 @@
import pyblish.api
-from ayon_core.hosts.nuke import api as napi
+from ayon_nuke import api as napi
from ayon_core.pipeline.publish import RepairAction
from ayon_core.pipeline import (
PublishXmlValidationError,
@@ -27,6 +27,8 @@ class ValidateOutputResolution(
hosts = ["nuke"]
actions = [RepairAction]
+ settings_category = "nuke"
+
missing_msg = "Missing Reformat node in render group node"
resolution_msg = "Reformat is set to wrong format"
diff --git a/client/ayon_core/hosts/nuke/plugins/publish/validate_proxy_mode.py b/server_addon/nuke/client/ayon_nuke/plugins/publish/validate_proxy_mode.py
similarity index 96%
rename from client/ayon_core/hosts/nuke/plugins/publish/validate_proxy_mode.py
rename to server_addon/nuke/client/ayon_nuke/plugins/publish/validate_proxy_mode.py
index 26e54295c9..1eb858b17e 100644
--- a/client/ayon_core/hosts/nuke/plugins/publish/validate_proxy_mode.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/publish/validate_proxy_mode.py
@@ -25,6 +25,8 @@ class ValidateProxyMode(pyblish.api.ContextPlugin):
hosts = ["nuke"]
actions = [FixProxyMode]
+ settings_category = "nuke"
+
def process(self, context):
rootNode = nuke.root()
diff --git a/client/ayon_core/hosts/nuke/plugins/publish/validate_rendered_frames.py b/server_addon/nuke/client/ayon_nuke/plugins/publish/validate_rendered_frames.py
similarity index 99%
rename from client/ayon_core/hosts/nuke/plugins/publish/validate_rendered_frames.py
rename to server_addon/nuke/client/ayon_nuke/plugins/publish/validate_rendered_frames.py
index 76ac7e97ad..20b7f6a6ac 100644
--- a/client/ayon_core/hosts/nuke/plugins/publish/validate_rendered_frames.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/publish/validate_rendered_frames.py
@@ -54,6 +54,8 @@ class ValidateRenderedFrames(pyblish.api.InstancePlugin):
hosts = ["nuke", "nukestudio"]
actions = [RepairCollectionActionToLocal, RepairCollectionActionToFarm]
+ settings_category = "nuke"
+
def process(self, instance):
node = instance.data["transientData"]["node"]
diff --git a/client/ayon_core/hosts/nuke/plugins/publish/validate_script_attributes.py b/server_addon/nuke/client/ayon_nuke/plugins/publish/validate_script_attributes.py
similarity index 98%
rename from client/ayon_core/hosts/nuke/plugins/publish/validate_script_attributes.py
rename to server_addon/nuke/client/ayon_nuke/plugins/publish/validate_script_attributes.py
index 2bd2034079..617d8d835b 100644
--- a/client/ayon_core/hosts/nuke/plugins/publish/validate_script_attributes.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/publish/validate_script_attributes.py
@@ -5,7 +5,7 @@ from ayon_core.pipeline import (
OptionalPyblishPluginMixin
)
from ayon_core.pipeline.publish import RepairAction
-from ayon_core.hosts.nuke.api.lib import (
+from ayon_nuke.api.lib import (
WorkfileSettings
)
@@ -23,6 +23,8 @@ class ValidateScriptAttributes(
optional = True
actions = [RepairAction]
+ settings_category = "nuke"
+
def process(self, instance):
if not self.is_active(instance.data):
return
diff --git a/client/ayon_core/hosts/nuke/plugins/publish/validate_write_nodes.py b/server_addon/nuke/client/ayon_nuke/plugins/publish/validate_write_nodes.py
similarity index 98%
rename from client/ayon_core/hosts/nuke/plugins/publish/validate_write_nodes.py
rename to server_addon/nuke/client/ayon_nuke/plugins/publish/validate_write_nodes.py
index 0244c1d504..d642a4314c 100644
--- a/client/ayon_core/hosts/nuke/plugins/publish/validate_write_nodes.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/publish/validate_write_nodes.py
@@ -2,7 +2,7 @@ from collections import defaultdict
import pyblish.api
from ayon_core.pipeline.publish import get_errored_instances_from_context
-from ayon_core.hosts.nuke.api.lib import (
+from ayon_nuke.api.lib import (
get_write_node_template_attr,
set_node_knobs_from_settings,
color_gui_to_int
@@ -59,6 +59,8 @@ class ValidateNukeWriteNode(
actions = [RepairNukeWriteNodeAction]
hosts = ["nuke"]
+ settings_category = "nuke"
+
def process(self, instance):
if not self.is_active(instance.data):
return
diff --git a/client/ayon_core/hosts/nuke/plugins/workfile_build/create_placeholder.py b/server_addon/nuke/client/ayon_nuke/plugins/workfile_build/create_placeholder.py
similarity index 99%
rename from client/ayon_core/hosts/nuke/plugins/workfile_build/create_placeholder.py
rename to server_addon/nuke/client/ayon_nuke/plugins/workfile_build/create_placeholder.py
index a5490021e4..4d43d59bad 100644
--- a/client/ayon_core/hosts/nuke/plugins/workfile_build/create_placeholder.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/workfile_build/create_placeholder.py
@@ -4,7 +4,7 @@ from ayon_core.pipeline.workfile.workfile_template_builder import (
CreatePlaceholderItem,
PlaceholderCreateMixin,
)
-from ayon_core.hosts.nuke.api.lib import (
+from ayon_nuke.api.lib import (
find_free_space_to_paste_nodes,
get_extreme_positions,
get_group_io_nodes,
@@ -18,7 +18,7 @@ from ayon_core.hosts.nuke.api.lib import (
duplicate_node,
node_tempfile,
)
-from ayon_core.hosts.nuke.api.workfile_template_builder import (
+from ayon_nuke.api.workfile_template_builder import (
NukePlaceholderPlugin
)
diff --git a/client/ayon_core/hosts/nuke/plugins/workfile_build/load_placeholder.py b/server_addon/nuke/client/ayon_nuke/plugins/workfile_build/load_placeholder.py
similarity index 99%
rename from client/ayon_core/hosts/nuke/plugins/workfile_build/load_placeholder.py
rename to server_addon/nuke/client/ayon_nuke/plugins/workfile_build/load_placeholder.py
index 258f48c9d3..68bc10e41b 100644
--- a/client/ayon_core/hosts/nuke/plugins/workfile_build/load_placeholder.py
+++ b/server_addon/nuke/client/ayon_nuke/plugins/workfile_build/load_placeholder.py
@@ -4,7 +4,7 @@ from ayon_core.pipeline.workfile.workfile_template_builder import (
LoadPlaceholderItem,
PlaceholderLoadMixin,
)
-from ayon_core.hosts.nuke.api.lib import (
+from ayon_nuke.api.lib import (
find_free_space_to_paste_nodes,
get_extreme_positions,
get_group_io_nodes,
@@ -18,7 +18,7 @@ from ayon_core.hosts.nuke.api.lib import (
duplicate_node,
node_tempfile,
)
-from ayon_core.hosts.nuke.api.workfile_template_builder import (
+from ayon_nuke.api.workfile_template_builder import (
NukePlaceholderPlugin
)
diff --git a/client/ayon_core/hosts/nuke/startup/__init__.py b/server_addon/nuke/client/ayon_nuke/startup/__init__.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/startup/__init__.py
rename to server_addon/nuke/client/ayon_nuke/startup/__init__.py
diff --git a/client/ayon_core/hosts/nuke/startup/clear_rendered.py b/server_addon/nuke/client/ayon_nuke/startup/clear_rendered.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/startup/clear_rendered.py
rename to server_addon/nuke/client/ayon_nuke/startup/clear_rendered.py
diff --git a/client/ayon_core/hosts/nuke/startup/custom_write_node.py b/server_addon/nuke/client/ayon_nuke/startup/custom_write_node.py
similarity index 99%
rename from client/ayon_core/hosts/nuke/startup/custom_write_node.py
rename to server_addon/nuke/client/ayon_nuke/startup/custom_write_node.py
index f119e69919..5b0f240a49 100644
--- a/client/ayon_core/hosts/nuke/startup/custom_write_node.py
+++ b/server_addon/nuke/client/ayon_nuke/startup/custom_write_node.py
@@ -3,7 +3,7 @@ import os
import nuke
import nukescripts
from ayon_core.pipeline import Anatomy, get_current_project_name
-from ayon_core.hosts.nuke.api.lib import (
+from ayon_nuke.api.lib import (
set_node_knobs_from_settings,
get_nuke_imageio_settings
)
diff --git a/client/ayon_core/hosts/nuke/startup/frame_setting_for_read_nodes.py b/server_addon/nuke/client/ayon_nuke/startup/frame_setting_for_read_nodes.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/startup/frame_setting_for_read_nodes.py
rename to server_addon/nuke/client/ayon_nuke/startup/frame_setting_for_read_nodes.py
diff --git a/client/ayon_core/hosts/nuke/startup/menu.py b/server_addon/nuke/client/ayon_nuke/startup/menu.py
similarity index 64%
rename from client/ayon_core/hosts/nuke/startup/menu.py
rename to server_addon/nuke/client/ayon_nuke/startup/menu.py
index 2559e2142a..c3dd8cda8f 100644
--- a/client/ayon_core/hosts/nuke/startup/menu.py
+++ b/server_addon/nuke/client/ayon_nuke/startup/menu.py
@@ -1,5 +1,5 @@
from ayon_core.pipeline import install_host
-from ayon_core.hosts.nuke.api import NukeHost
+from ayon_nuke.api import NukeHost
host = NukeHost()
install_host(host)
diff --git a/client/ayon_core/hosts/nuke/startup/write_to_read.py b/server_addon/nuke/client/ayon_nuke/startup/write_to_read.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/startup/write_to_read.py
rename to server_addon/nuke/client/ayon_nuke/startup/write_to_read.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/__init__.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/__init__.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/__init__.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/__init__.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/any_pb2.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/any_pb2.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/any_pb2.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/any_pb2.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/api_pb2.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/api_pb2.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/api_pb2.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/api_pb2.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/compiler/__init__.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/compiler/__init__.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/compiler/__init__.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/compiler/__init__.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/compiler/plugin_pb2.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/compiler/plugin_pb2.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/compiler/plugin_pb2.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/compiler/plugin_pb2.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/descriptor.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/descriptor.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/descriptor.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/descriptor.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/descriptor_database.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/descriptor_database.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/descriptor_database.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/descriptor_database.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/descriptor_pb2.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/descriptor_pb2.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/descriptor_pb2.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/descriptor_pb2.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/descriptor_pool.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/descriptor_pool.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/descriptor_pool.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/descriptor_pool.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/duration_pb2.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/duration_pb2.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/duration_pb2.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/duration_pb2.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/empty_pb2.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/empty_pb2.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/empty_pb2.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/empty_pb2.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/field_mask_pb2.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/field_mask_pb2.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/field_mask_pb2.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/field_mask_pb2.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/internal/__init__.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/internal/__init__.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/internal/__init__.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/internal/__init__.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/internal/_parameterized.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/internal/_parameterized.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/internal/_parameterized.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/internal/_parameterized.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/internal/api_implementation.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/internal/api_implementation.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/internal/api_implementation.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/internal/api_implementation.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/internal/builder.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/internal/builder.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/internal/builder.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/internal/builder.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/internal/containers.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/internal/containers.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/internal/containers.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/internal/containers.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/internal/decoder.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/internal/decoder.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/internal/decoder.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/internal/decoder.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/internal/encoder.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/internal/encoder.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/internal/encoder.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/internal/encoder.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/internal/enum_type_wrapper.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/internal/enum_type_wrapper.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/internal/enum_type_wrapper.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/internal/enum_type_wrapper.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/internal/extension_dict.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/internal/extension_dict.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/internal/extension_dict.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/internal/extension_dict.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/internal/message_listener.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/internal/message_listener.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/internal/message_listener.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/internal/message_listener.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/internal/message_set_extensions_pb2.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/internal/message_set_extensions_pb2.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/internal/message_set_extensions_pb2.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/internal/message_set_extensions_pb2.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/internal/missing_enum_values_pb2.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/internal/missing_enum_values_pb2.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/internal/missing_enum_values_pb2.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/internal/missing_enum_values_pb2.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/internal/more_extensions_dynamic_pb2.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/internal/more_extensions_dynamic_pb2.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/internal/more_extensions_dynamic_pb2.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/internal/more_extensions_dynamic_pb2.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/internal/more_extensions_pb2.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/internal/more_extensions_pb2.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/internal/more_extensions_pb2.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/internal/more_extensions_pb2.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/internal/more_messages_pb2.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/internal/more_messages_pb2.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/internal/more_messages_pb2.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/internal/more_messages_pb2.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/internal/no_package_pb2.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/internal/no_package_pb2.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/internal/no_package_pb2.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/internal/no_package_pb2.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/internal/python_message.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/internal/python_message.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/internal/python_message.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/internal/python_message.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/internal/type_checkers.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/internal/type_checkers.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/internal/type_checkers.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/internal/type_checkers.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/internal/well_known_types.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/internal/well_known_types.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/internal/well_known_types.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/internal/well_known_types.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/internal/wire_format.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/internal/wire_format.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/internal/wire_format.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/internal/wire_format.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/json_format.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/json_format.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/json_format.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/json_format.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/message.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/message.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/message.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/message.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/message_factory.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/message_factory.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/message_factory.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/message_factory.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/proto_builder.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/proto_builder.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/proto_builder.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/proto_builder.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/pyext/__init__.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/pyext/__init__.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/pyext/__init__.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/pyext/__init__.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/pyext/cpp_message.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/pyext/cpp_message.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/pyext/cpp_message.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/pyext/cpp_message.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/pyext/python_pb2.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/pyext/python_pb2.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/pyext/python_pb2.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/pyext/python_pb2.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/reflection.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/reflection.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/reflection.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/reflection.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/service.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/service.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/service.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/service.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/service_reflection.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/service_reflection.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/service_reflection.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/service_reflection.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/source_context_pb2.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/source_context_pb2.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/source_context_pb2.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/source_context_pb2.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/struct_pb2.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/struct_pb2.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/struct_pb2.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/struct_pb2.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/symbol_database.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/symbol_database.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/symbol_database.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/symbol_database.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/text_encoding.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/text_encoding.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/text_encoding.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/text_encoding.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/text_format.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/text_format.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/text_format.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/text_format.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/timestamp_pb2.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/timestamp_pb2.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/timestamp_pb2.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/timestamp_pb2.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/type_pb2.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/type_pb2.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/type_pb2.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/type_pb2.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/util/__init__.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/util/__init__.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/util/__init__.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/util/__init__.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/util/json_format_pb2.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/util/json_format_pb2.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/util/json_format_pb2.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/util/json_format_pb2.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/util/json_format_proto3_pb2.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/util/json_format_proto3_pb2.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/util/json_format_proto3_pb2.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/util/json_format_proto3_pb2.py
diff --git a/client/ayon_core/hosts/nuke/vendor/google/protobuf/wrappers_pb2.py b/server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/wrappers_pb2.py
similarity index 100%
rename from client/ayon_core/hosts/nuke/vendor/google/protobuf/wrappers_pb2.py
rename to server_addon/nuke/client/ayon_nuke/vendor/google/protobuf/wrappers_pb2.py
diff --git a/server_addon/nuke/package.py b/server_addon/nuke/package.py
index bf03c4e7e7..9e1f9362cb 100644
--- a/server_addon/nuke/package.py
+++ b/server_addon/nuke/package.py
@@ -1,3 +1,10 @@
name = "nuke"
title = "Nuke"
-version = "0.1.11"
+version = "0.2.1"
+
+client_dir = "ayon_nuke"
+
+ayon_required_addons = {
+ "core": ">0.3.2",
+}
+ayon_compatible_addons = {}
diff --git a/server_addon/nuke/server/settings/create_plugins.py b/server_addon/nuke/server/settings/create_plugins.py
index 6bdc5ee5ad..e4a0f9c938 100644
--- a/server_addon/nuke/server/settings/create_plugins.py
+++ b/server_addon/nuke/server/settings/create_plugins.py
@@ -12,7 +12,11 @@ def instance_attributes_enum():
return [
{"value": "reviewable", "label": "Reviewable"},
{"value": "farm_rendering", "label": "Farm rendering"},
- {"value": "use_range_limit", "label": "Use range limit"}
+ {"value": "use_range_limit", "label": "Use range limit"},
+ {
+ "value": "render_on_farm",
+ "label": "Render On Farm"
+ }
]
diff --git a/server_addon/nuke/server/settings/loader_plugins.py b/server_addon/nuke/server/settings/loader_plugins.py
index 531ea8d986..22cb469e8d 100644
--- a/server_addon/nuke/server/settings/loader_plugins.py
+++ b/server_addon/nuke/server/settings/loader_plugins.py
@@ -22,7 +22,9 @@ class LoadClipOptionsModel(BaseSettingsModel):
add_retime: bool = SettingsField(
title="Add retime"
)
-
+ deep_exr: bool = SettingsField(
+ title="Deep Exr Read Node"
+ )
class LoadClipModel(BaseSettingsModel):
enabled: bool = SettingsField(
@@ -65,7 +67,8 @@ DEFAULT_LOADER_PLUGINS_SETTINGS = {
"node_name_template": "{class_name}_{ext}",
"options_defaults": {
"start_at_workfile": True,
- "add_retime": True
+ "add_retime": True,
+ "deep_exr": False
}
}
}
diff --git a/server_addon/nuke/server/settings/publish_plugins.py b/server_addon/nuke/server/settings/publish_plugins.py
index d5b05d8715..6c37ecd37a 100644
--- a/server_addon/nuke/server/settings/publish_plugins.py
+++ b/server_addon/nuke/server/settings/publish_plugins.py
@@ -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": [],
diff --git a/server_addon/photoshop/package.py b/server_addon/photoshop/package.py
index 25615529d1..22043f951c 100644
--- a/server_addon/photoshop/package.py
+++ b/server_addon/photoshop/package.py
@@ -1,3 +1,3 @@
name = "photoshop"
title = "Photoshop"
-version = "0.1.2"
+version = "0.1.3"
diff --git a/server_addon/photoshop/server/settings/publish_plugins.py b/server_addon/photoshop/server/settings/publish_plugins.py
index d04faaf53a..149b08beb4 100644
--- a/server_addon/photoshop/server/settings/publish_plugins.py
+++ b/server_addon/photoshop/server/settings/publish_plugins.py
@@ -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": "_"
diff --git a/client/ayon_core/hosts/traypublisher/__init__.py b/server_addon/traypublisher/client/ayon_traypublisher/__init__.py
similarity index 100%
rename from client/ayon_core/hosts/traypublisher/__init__.py
rename to server_addon/traypublisher/client/ayon_traypublisher/__init__.py
diff --git a/client/ayon_core/hosts/traypublisher/addon.py b/server_addon/traypublisher/client/ayon_traypublisher/addon.py
similarity index 93%
rename from client/ayon_core/hosts/traypublisher/addon.py
rename to server_addon/traypublisher/client/ayon_traypublisher/addon.py
index 3dd275f223..5432cb1a92 100644
--- a/client/ayon_core/hosts/traypublisher/addon.py
+++ b/server_addon/traypublisher/client/ayon_traypublisher/addon.py
@@ -29,8 +29,8 @@ class TrayPublishAddon(AYONAddon, IHostAddon, ITrayAction):
def on_action_trigger(self):
self.run_traypublisher()
- def connect_with_addons(self, enabled_modules):
- """Collect publish paths from other modules."""
+ def connect_with_addons(self, enabled_addons):
+ """Collect publish paths from other addons."""
publish_paths = self.manager.collect_plugin_paths()["publish"]
self.publish_paths.extend(publish_paths)
@@ -55,9 +55,9 @@ def cli_main():
def launch():
"""Launch TrayPublish tool UI."""
- from ayon_core.tools import traypublisher
+ from ayon_traypublisher import ui
- traypublisher.main()
+ ui.main()
@cli_main.command()
diff --git a/client/ayon_core/hosts/traypublisher/api/__init__.py b/server_addon/traypublisher/client/ayon_traypublisher/api/__init__.py
similarity index 100%
rename from client/ayon_core/hosts/traypublisher/api/__init__.py
rename to server_addon/traypublisher/client/ayon_traypublisher/api/__init__.py
diff --git a/client/ayon_core/hosts/traypublisher/api/editorial.py b/server_addon/traypublisher/client/ayon_traypublisher/api/editorial.py
similarity index 100%
rename from client/ayon_core/hosts/traypublisher/api/editorial.py
rename to server_addon/traypublisher/client/ayon_traypublisher/api/editorial.py
diff --git a/client/ayon_core/hosts/traypublisher/api/pipeline.py b/server_addon/traypublisher/client/ayon_traypublisher/api/pipeline.py
similarity index 100%
rename from client/ayon_core/hosts/traypublisher/api/pipeline.py
rename to server_addon/traypublisher/client/ayon_traypublisher/api/pipeline.py
diff --git a/client/ayon_core/hosts/traypublisher/api/plugin.py b/server_addon/traypublisher/client/ayon_traypublisher/api/plugin.py
similarity index 99%
rename from client/ayon_core/hosts/traypublisher/api/plugin.py
rename to server_addon/traypublisher/client/ayon_traypublisher/api/plugin.py
index 257d01eb50..973eb65b11 100644
--- a/client/ayon_core/hosts/traypublisher/api/plugin.py
+++ b/server_addon/traypublisher/client/ayon_traypublisher/api/plugin.py
@@ -22,7 +22,7 @@ from .pipeline import (
)
REVIEW_EXTENSIONS = set(IMAGE_EXTENSIONS) | set(VIDEO_EXTENSIONS)
-SHARED_DATA_KEY = "openpype.traypublisher.instances"
+SHARED_DATA_KEY = "ayon.traypublisher.instances"
class HiddenTrayPublishCreator(HiddenCreator):
diff --git a/client/ayon_core/hosts/traypublisher/batch_parsing.py b/server_addon/traypublisher/client/ayon_traypublisher/batch_parsing.py
similarity index 100%
rename from client/ayon_core/hosts/traypublisher/batch_parsing.py
rename to server_addon/traypublisher/client/ayon_traypublisher/batch_parsing.py
diff --git a/client/ayon_core/hosts/traypublisher/csv_publish.py b/server_addon/traypublisher/client/ayon_traypublisher/csv_publish.py
similarity index 96%
rename from client/ayon_core/hosts/traypublisher/csv_publish.py
rename to server_addon/traypublisher/client/ayon_traypublisher/csv_publish.py
index b43792a357..b7906c5706 100644
--- a/client/ayon_core/hosts/traypublisher/csv_publish.py
+++ b/server_addon/traypublisher/client/ayon_traypublisher/csv_publish.py
@@ -1,5 +1,3 @@
-import os
-
import pyblish.api
import pyblish.util
@@ -8,7 +6,7 @@ from ayon_core.lib.attribute_definitions import FileDefItem
from ayon_core.pipeline import install_host
from ayon_core.pipeline.create import CreateContext
-from ayon_core.hosts.traypublisher.api import TrayPublisherHost
+from ayon_traypublisher.api import TrayPublisherHost
def csvpublish(
diff --git a/client/ayon_core/hosts/traypublisher/plugins/create/create_colorspace_look.py b/server_addon/traypublisher/client/ayon_traypublisher/plugins/create/create_colorspace_look.py
similarity index 93%
rename from client/ayon_core/hosts/traypublisher/plugins/create/create_colorspace_look.py
rename to server_addon/traypublisher/client/ayon_traypublisher/plugins/create/create_colorspace_look.py
index 4d865c1c5c..901bd758ba 100644
--- a/client/ayon_core/hosts/traypublisher/plugins/create/create_colorspace_look.py
+++ b/server_addon/traypublisher/client/ayon_traypublisher/plugins/create/create_colorspace_look.py
@@ -15,13 +15,13 @@ from ayon_core.pipeline import (
CreatorError
)
from ayon_core.pipeline import colorspace
-from ayon_core.hosts.traypublisher.api.plugin import TrayPublishCreator
+from ayon_traypublisher.api.plugin import TrayPublishCreator
class CreateColorspaceLook(TrayPublishCreator):
"""Creates colorspace look files."""
- identifier = "io.openpype.creators.traypublisher.colorspace_look"
+ identifier = "io.ayon.creators.traypublisher.colorspace_look"
label = "Colorspace Look"
product_type = "ociolook"
description = "Publishes color space look file."
@@ -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
diff --git a/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py b/server_addon/traypublisher/client/ayon_traypublisher/plugins/create/create_csv_ingest.py
similarity index 99%
rename from client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py
rename to server_addon/traypublisher/client/ayon_traypublisher/plugins/create/create_csv_ingest.py
index 8143e8b45b..5a5deeada8 100644
--- a/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py
+++ b/server_addon/traypublisher/client/ayon_traypublisher/plugins/create/create_csv_ingest.py
@@ -13,9 +13,7 @@ from ayon_core.lib.transcoding import (
VIDEO_EXTENSIONS, IMAGE_EXTENSIONS
)
from ayon_core.pipeline.create import CreatorError
-from ayon_core.hosts.traypublisher.api.plugin import (
- TrayPublishCreator
-)
+from ayon_traypublisher.api.plugin import TrayPublishCreator
class IngestCSV(TrayPublishCreator):
diff --git a/client/ayon_core/hosts/traypublisher/plugins/create/create_editorial.py b/server_addon/traypublisher/client/ayon_traypublisher/plugins/create/create_editorial.py
similarity index 99%
rename from client/ayon_core/hosts/traypublisher/plugins/create/create_editorial.py
rename to server_addon/traypublisher/client/ayon_traypublisher/plugins/create/create_editorial.py
index 4057aee9a6..a2f6f211f5 100644
--- a/client/ayon_core/hosts/traypublisher/plugins/create/create_editorial.py
+++ b/server_addon/traypublisher/client/ayon_traypublisher/plugins/create/create_editorial.py
@@ -4,11 +4,11 @@ from copy import deepcopy
import ayon_api
import opentimelineio as otio
-from ayon_core.hosts.traypublisher.api.plugin import (
+from ayon_traypublisher.api.plugin import (
TrayPublishCreator,
HiddenTrayPublishCreator
)
-from ayon_core.hosts.traypublisher.api.editorial import (
+from ayon_traypublisher.api.editorial import (
ShotMetadataSolver
)
from ayon_core.pipeline import CreatedInstance
diff --git a/server_addon/traypublisher/client/ayon_traypublisher/plugins/create/create_editorial_package.py b/server_addon/traypublisher/client/ayon_traypublisher/plugins/create/create_editorial_package.py
new file mode 100644
index 0000000000..5f0a84be4a
--- /dev/null
+++ b/server_addon/traypublisher/client/ayon_traypublisher/plugins/create/create_editorial_package.py
@@ -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_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.
+ """
diff --git a/client/ayon_core/hosts/traypublisher/plugins/create/create_from_settings.py b/server_addon/traypublisher/client/ayon_traypublisher/plugins/create/create_from_settings.py
similarity index 88%
rename from client/ayon_core/hosts/traypublisher/plugins/create/create_from_settings.py
rename to server_addon/traypublisher/client/ayon_traypublisher/plugins/create/create_from_settings.py
index fe7ba4c4a4..13cf92ab10 100644
--- a/client/ayon_core/hosts/traypublisher/plugins/create/create_from_settings.py
+++ b/server_addon/traypublisher/client/ayon_traypublisher/plugins/create/create_from_settings.py
@@ -6,7 +6,7 @@ log = Logger.get_logger(__name__)
def initialize():
- from ayon_core.hosts.traypublisher.api.plugin import SettingsCreator
+ from ayon_traypublisher.api.plugin import SettingsCreator
project_name = os.environ["AYON_PROJECT_NAME"]
project_settings = get_project_settings(project_name)
diff --git a/client/ayon_core/hosts/traypublisher/plugins/create/create_movie_batch.py b/server_addon/traypublisher/client/ayon_traypublisher/plugins/create/create_movie_batch.py
similarity index 97%
rename from client/ayon_core/hosts/traypublisher/plugins/create/create_movie_batch.py
rename to server_addon/traypublisher/client/ayon_traypublisher/plugins/create/create_movie_batch.py
index 546408b4d6..77b9b0df0a 100644
--- a/client/ayon_core/hosts/traypublisher/plugins/create/create_movie_batch.py
+++ b/server_addon/traypublisher/client/ayon_traypublisher/plugins/create/create_movie_batch.py
@@ -17,8 +17,8 @@ from ayon_core.pipeline.create import (
TaskNotSetError,
)
-from ayon_core.hosts.traypublisher.api.plugin import TrayPublishCreator
-from ayon_core.hosts.traypublisher.batch_parsing import (
+from ayon_traypublisher.api.plugin import TrayPublishCreator
+from ayon_traypublisher.batch_parsing import (
get_folder_entity_from_filename
)
diff --git a/client/ayon_core/hosts/traypublisher/plugins/create/create_online.py b/server_addon/traypublisher/client/ayon_traypublisher/plugins/create/create_online.py
similarity index 96%
rename from client/ayon_core/hosts/traypublisher/plugins/create/create_online.py
rename to server_addon/traypublisher/client/ayon_traypublisher/plugins/create/create_online.py
index f48037701e..135a11c0c6 100644
--- a/client/ayon_core/hosts/traypublisher/plugins/create/create_online.py
+++ b/server_addon/traypublisher/client/ayon_traypublisher/plugins/create/create_online.py
@@ -14,13 +14,13 @@ from ayon_core.pipeline import (
CreatedInstance,
CreatorError
)
-from ayon_core.hosts.traypublisher.api.plugin import TrayPublishCreator
+from ayon_traypublisher.api.plugin import TrayPublishCreator
class OnlineCreator(TrayPublishCreator):
"""Creates instance from file and retains its original name."""
- identifier = "io.openpype.creators.traypublisher.online"
+ identifier = "io.ayon.creators.traypublisher.online"
label = "Online"
product_type = "online"
description = "Publish file retaining its original file name"
diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/collect_app_name.py b/server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/collect_app_name.py
similarity index 100%
rename from client/ayon_core/hosts/traypublisher/plugins/publish/collect_app_name.py
rename to server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/collect_app_name.py
diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/collect_clip_instances.py b/server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/collect_clip_instances.py
similarity index 100%
rename from client/ayon_core/hosts/traypublisher/plugins/publish/collect_clip_instances.py
rename to server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/collect_clip_instances.py
diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/collect_colorspace_look.py b/server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/collect_colorspace_look.py
similarity index 100%
rename from client/ayon_core/hosts/traypublisher/plugins/publish/collect_colorspace_look.py
rename to server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/collect_colorspace_look.py
diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/collect_csv_ingest_instance_data.py b/server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/collect_csv_ingest_instance_data.py
similarity index 100%
rename from client/ayon_core/hosts/traypublisher/plugins/publish/collect_csv_ingest_instance_data.py
rename to server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/collect_csv_ingest_instance_data.py
diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/collect_editorial_instances.py b/server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/collect_editorial_instances.py
similarity index 100%
rename from client/ayon_core/hosts/traypublisher/plugins/publish/collect_editorial_instances.py
rename to server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/collect_editorial_instances.py
diff --git a/server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/collect_editorial_package.py b/server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/collect_editorial_package.py
new file mode 100644
index 0000000000..cb1277546c
--- /dev/null
+++ b/server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/collect_editorial_package.py
@@ -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
diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/collect_editorial_reviewable.py b/server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/collect_editorial_reviewable.py
similarity index 100%
rename from client/ayon_core/hosts/traypublisher/plugins/publish/collect_editorial_reviewable.py
rename to server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/collect_editorial_reviewable.py
diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/collect_explicit_colorspace.py b/server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/collect_explicit_colorspace.py
similarity index 85%
rename from client/ayon_core/hosts/traypublisher/plugins/publish/collect_explicit_colorspace.py
rename to server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/collect_explicit_colorspace.py
index 8e29a0048d..5fbb9a6f4c 100644
--- a/client/ayon_core/hosts/traypublisher/plugins/publish/collect_explicit_colorspace.py
+++ b/server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/collect_explicit_colorspace.py
@@ -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):
diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/collect_frame_data_from_asset_entity.py b/server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/collect_frame_data_from_folder_entity.py
similarity index 64%
rename from client/ayon_core/hosts/traypublisher/plugins/publish/collect_frame_data_from_asset_entity.py
rename to server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/collect_frame_data_from_folder_entity.py
index 4d203649c7..2e564a2e4e 100644
--- a/client/ayon_core/hosts/traypublisher/plugins/publish/collect_frame_data_from_asset_entity.py
+++ b/server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/collect_frame_data_from_folder_entity.py
@@ -10,9 +10,13 @@ class CollectFrameDataFromAssetEntity(pyblish.api.InstancePlugin):
order = pyblish.api.CollectorOrder + 0.491
label = "Collect Missing Frame Data From Folder"
- families = ["plate", "pointcache",
- "vdbcache", "online",
- "render"]
+ families = [
+ "plate",
+ "pointcache",
+ "vdbcache",
+ "online",
+ "render",
+ ]
hosts = ["traypublisher"]
def process(self, instance):
@@ -22,16 +26,26 @@ class CollectFrameDataFromAssetEntity(pyblish.api.InstancePlugin):
"frameStart",
"frameEnd",
"handleStart",
- "handleEnd"
+ "handleEnd",
):
if key not in instance.data:
missing_keys.append(key)
+
+ # Skip the logic if all keys are already collected.
+ # NOTE: In editorial is not 'folderEntity' filled, so it would crash
+ # even if we don't need it.
+ if not missing_keys:
+ return
+
keys_set = []
folder_attributes = instance.data["folderEntity"]["attrib"]
for key in missing_keys:
if key in folder_attributes:
instance.data[key] = folder_attributes[key]
keys_set.append(key)
+
if keys_set:
- self.log.debug(f"Frame range data {keys_set} "
- "has been collected from folder entity.")
+ self.log.debug(
+ f"Frame range data {keys_set} "
+ "has been collected from folder entity."
+ )
diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/collect_movie_batch.py b/server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/collect_movie_batch.py
similarity index 100%
rename from client/ayon_core/hosts/traypublisher/plugins/publish/collect_movie_batch.py
rename to server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/collect_movie_batch.py
diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/collect_online_file.py b/server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/collect_online_file.py
similarity index 100%
rename from client/ayon_core/hosts/traypublisher/plugins/publish/collect_online_file.py
rename to server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/collect_online_file.py
diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/collect_review_frames.py b/server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/collect_review_frames.py
similarity index 100%
rename from client/ayon_core/hosts/traypublisher/plugins/publish/collect_review_frames.py
rename to server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/collect_review_frames.py
diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/collect_sequence_frame_data.py b/server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/collect_sequence_frame_data.py
similarity index 100%
rename from client/ayon_core/hosts/traypublisher/plugins/publish/collect_sequence_frame_data.py
rename to server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/collect_sequence_frame_data.py
diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/collect_shot_instances.py b/server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/collect_shot_instances.py
similarity index 100%
rename from client/ayon_core/hosts/traypublisher/plugins/publish/collect_shot_instances.py
rename to server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/collect_shot_instances.py
diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/collect_simple_instances.py b/server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/collect_simple_instances.py
similarity index 100%
rename from client/ayon_core/hosts/traypublisher/plugins/publish/collect_simple_instances.py
rename to server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/collect_simple_instances.py
diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/collect_source.py b/server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/collect_source.py
similarity index 100%
rename from client/ayon_core/hosts/traypublisher/plugins/publish/collect_source.py
rename to server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/collect_source.py
diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/extract_colorspace_look.py b/server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/extract_colorspace_look.py
similarity index 100%
rename from client/ayon_core/hosts/traypublisher/plugins/publish/extract_colorspace_look.py
rename to server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/extract_colorspace_look.py
diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/extract_csv_file.py b/server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/extract_csv_file.py
similarity index 100%
rename from client/ayon_core/hosts/traypublisher/plugins/publish/extract_csv_file.py
rename to server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/extract_csv_file.py
diff --git a/server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/extract_editorial_pckg.py b/server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/extract_editorial_pckg.py
new file mode 100644
index 0000000000..6dd4e84704
--- /dev/null
+++ b/server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/extract_editorial_pckg.py
@@ -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
diff --git a/client/ayon_core/plugins/publish/extract_trim_video_audio.py b/server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/extract_trim_video_audio.py
similarity index 100%
rename from client/ayon_core/plugins/publish/extract_trim_video_audio.py
rename to server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/extract_trim_video_audio.py
diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/help/validate_existing_version.xml b/server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/help/validate_existing_version.xml
similarity index 100%
rename from client/ayon_core/hosts/traypublisher/plugins/publish/help/validate_existing_version.xml
rename to server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/help/validate_existing_version.xml
diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/help/validate_frame_ranges.xml b/server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/help/validate_frame_ranges.xml
similarity index 100%
rename from client/ayon_core/hosts/traypublisher/plugins/publish/help/validate_frame_ranges.xml
rename to server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/help/validate_frame_ranges.xml
diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/validate_colorspace.py b/server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/validate_colorspace.py
similarity index 100%
rename from client/ayon_core/hosts/traypublisher/plugins/publish/validate_colorspace.py
rename to server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/validate_colorspace.py
diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/validate_colorspace_look.py b/server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/validate_colorspace_look.py
similarity index 100%
rename from client/ayon_core/hosts/traypublisher/plugins/publish/validate_colorspace_look.py
rename to server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/validate_colorspace_look.py
diff --git a/server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/validate_editorial_package.py b/server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/validate_editorial_package.py
new file mode 100644
index 0000000000..c63c4a6a73
--- /dev/null
+++ b/server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/validate_editorial_package.py
@@ -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
diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/validate_existing_version.py b/server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/validate_existing_version.py
similarity index 100%
rename from client/ayon_core/hosts/traypublisher/plugins/publish/validate_existing_version.py
rename to server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/validate_existing_version.py
diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/validate_filepaths.py b/server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/validate_filepaths.py
similarity index 100%
rename from client/ayon_core/hosts/traypublisher/plugins/publish/validate_filepaths.py
rename to server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/validate_filepaths.py
diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/validate_frame_ranges.py b/server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/validate_frame_ranges.py
similarity index 100%
rename from client/ayon_core/hosts/traypublisher/plugins/publish/validate_frame_ranges.py
rename to server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/validate_frame_ranges.py
diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/validate_online_file.py b/server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/validate_online_file.py
similarity index 100%
rename from client/ayon_core/hosts/traypublisher/plugins/publish/validate_online_file.py
rename to server_addon/traypublisher/client/ayon_traypublisher/plugins/publish/validate_online_file.py
diff --git a/client/ayon_core/tools/traypublisher/__init__.py b/server_addon/traypublisher/client/ayon_traypublisher/ui/__init__.py
similarity index 100%
rename from client/ayon_core/tools/traypublisher/__init__.py
rename to server_addon/traypublisher/client/ayon_traypublisher/ui/__init__.py
diff --git a/client/ayon_core/tools/traypublisher/window.py b/server_addon/traypublisher/client/ayon_traypublisher/ui/window.py
similarity index 99%
rename from client/ayon_core/tools/traypublisher/window.py
rename to server_addon/traypublisher/client/ayon_traypublisher/ui/window.py
index 4700e20531..288dac8529 100644
--- a/client/ayon_core/tools/traypublisher/window.py
+++ b/server_addon/traypublisher/client/ayon_traypublisher/ui/window.py
@@ -13,7 +13,6 @@ import qtawesome
from ayon_core.lib import AYONSettingsRegistry, is_running_from_build
from ayon_core.pipeline import install_host
-from ayon_core.hosts.traypublisher.api import TrayPublisherHost
from ayon_core.tools.publisher.control_qt import QtPublisherController
from ayon_core.tools.publisher.window import PublisherWindow
from ayon_core.tools.common_models import ProjectsModel
@@ -24,6 +23,7 @@ from ayon_core.tools.utils import (
ProjectSortFilterProxy,
PROJECT_NAME_ROLE,
)
+from ayon_traypublisher.api import TrayPublisherHost
class TrayPublisherRegistry(AYONSettingsRegistry):
diff --git a/server_addon/traypublisher/package.py b/server_addon/traypublisher/package.py
index 4ca8ae9fd3..ea04835b45 100644
--- a/server_addon/traypublisher/package.py
+++ b/server_addon/traypublisher/package.py
@@ -1,3 +1,10 @@
name = "traypublisher"
title = "TrayPublisher"
-version = "0.1.4"
+version = "0.2.0"
+
+client_dir = "ayon_traypublisher"
+
+ayon_required_addons = {
+ "core": ">0.3.2",
+}
+ayon_compatible_addons = {}
diff --git a/server_addon/traypublisher/server/settings/publish_plugins.py b/server_addon/traypublisher/server/settings/publish_plugins.py
index f413c86227..99a0bbf107 100644
--- a/server_addon/traypublisher/server/settings/publish_plugins.py
+++ b/server_addon/traypublisher/server/settings/publish_plugins.py
@@ -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"
+ ]
+ }
+ }
}
}
diff --git a/client/ayon_core/hosts/tvpaint/__init__.py b/server_addon/tvpaint/client/ayon_tvpaint/__init__.py
similarity index 100%
rename from client/ayon_core/hosts/tvpaint/__init__.py
rename to server_addon/tvpaint/client/ayon_tvpaint/__init__.py
diff --git a/client/ayon_core/hosts/tvpaint/addon.py b/server_addon/tvpaint/client/ayon_tvpaint/addon.py
similarity index 100%
rename from client/ayon_core/hosts/tvpaint/addon.py
rename to server_addon/tvpaint/client/ayon_tvpaint/addon.py
diff --git a/client/ayon_core/hosts/tvpaint/api/__init__.py b/server_addon/tvpaint/client/ayon_tvpaint/api/__init__.py
similarity index 100%
rename from client/ayon_core/hosts/tvpaint/api/__init__.py
rename to server_addon/tvpaint/client/ayon_tvpaint/api/__init__.py
diff --git a/client/ayon_core/hosts/tvpaint/api/communication_server.py b/server_addon/tvpaint/client/ayon_tvpaint/api/communication_server.py
similarity index 99%
rename from client/ayon_core/hosts/tvpaint/api/communication_server.py
rename to server_addon/tvpaint/client/ayon_tvpaint/api/communication_server.py
index d185bdf685..7ccb49f07e 100644
--- a/client/ayon_core/hosts/tvpaint/api/communication_server.py
+++ b/server_addon/tvpaint/client/ayon_tvpaint/api/communication_server.py
@@ -22,7 +22,7 @@ from aiohttp_json_rpc.protocol import (
from aiohttp_json_rpc.exceptions import RpcError
from ayon_core.lib import emit_event
-from ayon_core.hosts.tvpaint.tvpaint_plugin import get_plugin_files_path
+from ayon_tvpaint.tvpaint_plugin import get_plugin_files_path
log = logging.getLogger(__name__)
log.setLevel(logging.DEBUG)
diff --git a/client/ayon_core/hosts/tvpaint/api/launch_script.py b/server_addon/tvpaint/client/ayon_tvpaint/api/launch_script.py
similarity index 98%
rename from client/ayon_core/hosts/tvpaint/api/launch_script.py
rename to server_addon/tvpaint/client/ayon_tvpaint/api/launch_script.py
index bcc92d8b6d..1e23e95572 100644
--- a/client/ayon_core/hosts/tvpaint/api/launch_script.py
+++ b/server_addon/tvpaint/client/ayon_tvpaint/api/launch_script.py
@@ -10,7 +10,7 @@ from qtpy import QtWidgets, QtCore, QtGui
from ayon_core import style
from ayon_core.pipeline import install_host
-from ayon_core.hosts.tvpaint.api import (
+from ayon_tvpaint.api import (
TVPaintHost,
CommunicationWrapper,
)
diff --git a/client/ayon_core/hosts/tvpaint/api/lib.py b/server_addon/tvpaint/client/ayon_tvpaint/api/lib.py
similarity index 100%
rename from client/ayon_core/hosts/tvpaint/api/lib.py
rename to server_addon/tvpaint/client/ayon_tvpaint/api/lib.py
diff --git a/client/ayon_core/hosts/tvpaint/api/pipeline.py b/server_addon/tvpaint/client/ayon_tvpaint/api/pipeline.py
similarity index 99%
rename from client/ayon_core/hosts/tvpaint/api/pipeline.py
rename to server_addon/tvpaint/client/ayon_tvpaint/api/pipeline.py
index 6f5c4d49d4..5ec6355138 100644
--- a/client/ayon_core/hosts/tvpaint/api/pipeline.py
+++ b/server_addon/tvpaint/client/ayon_tvpaint/api/pipeline.py
@@ -7,8 +7,9 @@ import requests
import ayon_api
import pyblish.api
+from ayon_tvpaint import TVPAINT_ROOT_DIR
+
from ayon_core.host import HostBase, IWorkfileHost, ILoadHost, IPublishHost
-from ayon_core.hosts.tvpaint import TVPAINT_ROOT_DIR
from ayon_core.settings import get_current_project_settings
from ayon_core.lib import register_event_callback
from ayon_core.pipeline import (
diff --git a/client/ayon_core/hosts/tvpaint/api/plugin.py b/server_addon/tvpaint/client/ayon_tvpaint/api/plugin.py
similarity index 97%
rename from client/ayon_core/hosts/tvpaint/api/plugin.py
rename to server_addon/tvpaint/client/ayon_tvpaint/api/plugin.py
index e715b959f4..9dd6ae530a 100644
--- a/client/ayon_core/hosts/tvpaint/api/plugin.py
+++ b/server_addon/tvpaint/client/ayon_tvpaint/api/plugin.py
@@ -12,7 +12,7 @@ from ayon_core.pipeline.create.creator_plugins import cache_and_get_instances
from .lib import get_layers_data
-SHARED_DATA_KEY = "openpype.tvpaint.instances"
+SHARED_DATA_KEY = "ayon.tvpaint.instances"
class TVPaintCreatorCommon:
@@ -89,6 +89,8 @@ class TVPaintCreatorCommon:
class TVPaintCreator(Creator, TVPaintCreatorCommon):
+ settings_category = "tvpaint"
+
def collect_instances(self):
self._collect_create_instances()
@@ -140,6 +142,8 @@ class TVPaintCreator(Creator, TVPaintCreatorCommon):
class TVPaintAutoCreator(AutoCreator, TVPaintCreatorCommon):
+ settings_category = "tvpaint"
+
def collect_instances(self):
self._collect_create_instances()
@@ -152,6 +156,7 @@ class TVPaintAutoCreator(AutoCreator, TVPaintCreatorCommon):
class Loader(LoaderPlugin):
hosts = ["tvpaint"]
+ settings_category = "tvpaint"
@staticmethod
def get_members_from_container(container):
diff --git a/client/ayon_core/hosts/tvpaint/hooks/pre_launch_args.py b/server_addon/tvpaint/client/ayon_tvpaint/hooks/pre_launch_args.py
similarity index 95%
rename from client/ayon_core/hosts/tvpaint/hooks/pre_launch_args.py
rename to server_addon/tvpaint/client/ayon_tvpaint/hooks/pre_launch_args.py
index 691b81e089..8ee91aa0e7 100644
--- a/client/ayon_core/hosts/tvpaint/hooks/pre_launch_args.py
+++ b/server_addon/tvpaint/client/ayon_tvpaint/hooks/pre_launch_args.py
@@ -37,6 +37,6 @@ class TvpaintPrelaunchHook(PreLaunchHook):
self.launch_context.launch_args.extend(remainders)
def launch_script_path(self):
- from ayon_core.hosts.tvpaint import get_launch_script_path
+ from ayon_tvpaint import get_launch_script_path
return get_launch_script_path()
diff --git a/client/ayon_core/hosts/tvpaint/lib.py b/server_addon/tvpaint/client/ayon_tvpaint/lib.py
similarity index 100%
rename from client/ayon_core/hosts/tvpaint/lib.py
rename to server_addon/tvpaint/client/ayon_tvpaint/lib.py
diff --git a/client/ayon_core/hosts/tvpaint/plugins/create/convert_legacy.py b/server_addon/tvpaint/client/ayon_tvpaint/plugins/create/convert_legacy.py
similarity index 97%
rename from client/ayon_core/hosts/tvpaint/plugins/create/convert_legacy.py
rename to server_addon/tvpaint/client/ayon_tvpaint/plugins/create/convert_legacy.py
index 34fe0ce8f4..e79a6565e8 100644
--- a/client/ayon_core/hosts/tvpaint/plugins/create/convert_legacy.py
+++ b/server_addon/tvpaint/client/ayon_tvpaint/plugins/create/convert_legacy.py
@@ -4,8 +4,8 @@ from ayon_core.pipeline.create.creator_plugins import (
ProductConvertorPlugin,
cache_and_get_instances,
)
-from ayon_core.hosts.tvpaint.api.plugin import SHARED_DATA_KEY
-from ayon_core.hosts.tvpaint.api.lib import get_groups_data
+from ayon_tvpaint.api.plugin import SHARED_DATA_KEY
+from ayon_tvpaint.api.lib import get_groups_data
class TVPaintLegacyConverted(ProductConvertorPlugin):
diff --git a/client/ayon_core/hosts/tvpaint/plugins/create/create_render.py b/server_addon/tvpaint/client/ayon_tvpaint/plugins/create/create_render.py
similarity index 99%
rename from client/ayon_core/hosts/tvpaint/plugins/create/create_render.py
rename to server_addon/tvpaint/client/ayon_tvpaint/plugins/create/create_render.py
index dc9c2466e0..2286a4417a 100644
--- a/client/ayon_core/hosts/tvpaint/plugins/create/create_render.py
+++ b/server_addon/tvpaint/client/ayon_tvpaint/plugins/create/create_render.py
@@ -52,11 +52,11 @@ from ayon_core.pipeline.create import (
CreatedInstance,
CreatorError,
)
-from ayon_core.hosts.tvpaint.api.plugin import (
+from ayon_tvpaint.api.plugin import (
TVPaintCreator,
TVPaintAutoCreator,
)
-from ayon_core.hosts.tvpaint.api.lib import (
+from ayon_tvpaint.api.lib import (
get_layers_data,
get_groups_data,
execute_george_through_file,
diff --git a/client/ayon_core/hosts/tvpaint/plugins/create/create_review.py b/server_addon/tvpaint/client/ayon_tvpaint/plugins/create/create_review.py
similarity index 97%
rename from client/ayon_core/hosts/tvpaint/plugins/create/create_review.py
rename to server_addon/tvpaint/client/ayon_tvpaint/plugins/create/create_review.py
index acb4f0f8d6..6068ffa1d8 100644
--- a/client/ayon_core/hosts/tvpaint/plugins/create/create_review.py
+++ b/server_addon/tvpaint/client/ayon_tvpaint/plugins/create/create_review.py
@@ -1,7 +1,7 @@
import ayon_api
from ayon_core.pipeline import CreatedInstance
-from ayon_core.hosts.tvpaint.api.plugin import TVPaintAutoCreator
+from ayon_tvpaint.api.plugin import TVPaintAutoCreator
class TVPaintReviewCreator(TVPaintAutoCreator):
diff --git a/client/ayon_core/hosts/tvpaint/plugins/create/create_workfile.py b/server_addon/tvpaint/client/ayon_tvpaint/plugins/create/create_workfile.py
similarity index 97%
rename from client/ayon_core/hosts/tvpaint/plugins/create/create_workfile.py
rename to server_addon/tvpaint/client/ayon_tvpaint/plugins/create/create_workfile.py
index f21f41439e..b08f731869 100644
--- a/client/ayon_core/hosts/tvpaint/plugins/create/create_workfile.py
+++ b/server_addon/tvpaint/client/ayon_tvpaint/plugins/create/create_workfile.py
@@ -1,7 +1,7 @@
import ayon_api
from ayon_core.pipeline import CreatedInstance
-from ayon_core.hosts.tvpaint.api.plugin import TVPaintAutoCreator
+from ayon_tvpaint.api.plugin import TVPaintAutoCreator
class TVPaintWorkfileCreator(TVPaintAutoCreator):
diff --git a/client/ayon_core/hosts/tvpaint/plugins/load/load_image.py b/server_addon/tvpaint/client/ayon_tvpaint/plugins/load/load_image.py
similarity index 95%
rename from client/ayon_core/hosts/tvpaint/plugins/load/load_image.py
rename to server_addon/tvpaint/client/ayon_tvpaint/plugins/load/load_image.py
index aad8f92d26..de61992d3f 100644
--- a/client/ayon_core/hosts/tvpaint/plugins/load/load_image.py
+++ b/server_addon/tvpaint/client/ayon_tvpaint/plugins/load/load_image.py
@@ -1,6 +1,6 @@
from ayon_core.lib.attribute_definitions import BoolDef
-from ayon_core.hosts.tvpaint.api import plugin
-from ayon_core.hosts.tvpaint.api.lib import execute_george_through_file
+from ayon_tvpaint.api import plugin
+from ayon_tvpaint.api.lib import execute_george_through_file
class ImportImage(plugin.Loader):
diff --git a/client/ayon_core/hosts/tvpaint/plugins/load/load_reference_image.py b/server_addon/tvpaint/client/ayon_tvpaint/plugins/load/load_reference_image.py
similarity index 98%
rename from client/ayon_core/hosts/tvpaint/plugins/load/load_reference_image.py
rename to server_addon/tvpaint/client/ayon_tvpaint/plugins/load/load_reference_image.py
index a7fcb9f4a4..ce08aa9cd9 100644
--- a/client/ayon_core/hosts/tvpaint/plugins/load/load_reference_image.py
+++ b/server_addon/tvpaint/client/ayon_tvpaint/plugins/load/load_reference_image.py
@@ -2,12 +2,12 @@ import collections
from ayon_core.lib.attribute_definitions import BoolDef
from ayon_core.pipeline import registered_host
-from ayon_core.hosts.tvpaint.api import plugin
-from ayon_core.hosts.tvpaint.api.lib import (
+from ayon_tvpaint.api import plugin
+from ayon_tvpaint.api.lib import (
get_layers_data,
execute_george_through_file,
)
-from ayon_core.hosts.tvpaint.api.pipeline import (
+from ayon_tvpaint.api.pipeline import (
write_workfile_metadata,
SECTION_NAME_CONTAINERS,
containerise,
diff --git a/client/ayon_core/hosts/tvpaint/plugins/load/load_sound.py b/server_addon/tvpaint/client/ayon_tvpaint/plugins/load/load_sound.py
similarity index 97%
rename from client/ayon_core/hosts/tvpaint/plugins/load/load_sound.py
rename to server_addon/tvpaint/client/ayon_tvpaint/plugins/load/load_sound.py
index 7e8c8022d6..086afba079 100644
--- a/client/ayon_core/hosts/tvpaint/plugins/load/load_sound.py
+++ b/server_addon/tvpaint/client/ayon_tvpaint/plugins/load/load_sound.py
@@ -1,7 +1,7 @@
import os
import tempfile
-from ayon_core.hosts.tvpaint.api import plugin
-from ayon_core.hosts.tvpaint.api.lib import (
+from ayon_tvpaint.api import plugin
+from ayon_tvpaint.api.lib import (
execute_george_through_file,
)
diff --git a/client/ayon_core/hosts/tvpaint/plugins/load/load_workfile.py b/server_addon/tvpaint/client/ayon_tvpaint/plugins/load/load_workfile.py
similarity index 96%
rename from client/ayon_core/hosts/tvpaint/plugins/load/load_workfile.py
rename to server_addon/tvpaint/client/ayon_tvpaint/plugins/load/load_workfile.py
index 07c2d91533..045e22f188 100644
--- a/client/ayon_core/hosts/tvpaint/plugins/load/load_workfile.py
+++ b/server_addon/tvpaint/client/ayon_tvpaint/plugins/load/load_workfile.py
@@ -10,11 +10,11 @@ from ayon_core.pipeline.workfile import (
get_last_workfile_with_version,
)
from ayon_core.pipeline.template_data import get_template_data_with_names
-from ayon_core.hosts.tvpaint.api import plugin
-from ayon_core.hosts.tvpaint.api.lib import (
+from ayon_tvpaint.api import plugin
+from ayon_tvpaint.api.lib import (
execute_george_through_file,
)
-from ayon_core.hosts.tvpaint.api.pipeline import (
+from ayon_tvpaint.api.pipeline import (
get_current_workfile_context,
)
from ayon_core.pipeline.version_start import get_versioning_start
diff --git a/client/ayon_core/hosts/tvpaint/plugins/publish/collect_instance_frames.py b/server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/collect_instance_frames.py
similarity index 97%
rename from client/ayon_core/hosts/tvpaint/plugins/publish/collect_instance_frames.py
rename to server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/collect_instance_frames.py
index 5f134a0cd0..a9e69166d7 100644
--- a/client/ayon_core/hosts/tvpaint/plugins/publish/collect_instance_frames.py
+++ b/server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/collect_instance_frames.py
@@ -14,6 +14,8 @@ class CollectOutputFrameRange(pyblish.api.InstancePlugin):
hosts = ["tvpaint"]
families = ["review", "render"]
+ settings_category = "tvpaint"
+
def process(self, instance):
folder_entity = instance.data.get("folderEntity")
if not folder_entity:
diff --git a/client/ayon_core/hosts/tvpaint/plugins/publish/collect_render_instances.py b/server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/collect_render_instances.py
similarity index 99%
rename from client/ayon_core/hosts/tvpaint/plugins/publish/collect_render_instances.py
rename to server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/collect_render_instances.py
index 596d257f22..00af624700 100644
--- a/client/ayon_core/hosts/tvpaint/plugins/publish/collect_render_instances.py
+++ b/server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/collect_render_instances.py
@@ -9,6 +9,7 @@ class CollectRenderInstances(pyblish.api.InstancePlugin):
hosts = ["tvpaint"]
families = ["render", "review"]
+ settings_category = "tvpaint"
ignore_render_pass_transparency = False
def process(self, instance):
diff --git a/client/ayon_core/hosts/tvpaint/plugins/publish/collect_workfile.py b/server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/collect_workfile.py
similarity index 96%
rename from client/ayon_core/hosts/tvpaint/plugins/publish/collect_workfile.py
rename to server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/collect_workfile.py
index a9e9db3872..27de086a46 100644
--- a/client/ayon_core/hosts/tvpaint/plugins/publish/collect_workfile.py
+++ b/server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/collect_workfile.py
@@ -9,6 +9,8 @@ class CollectWorkfile(pyblish.api.InstancePlugin):
hosts = ["tvpaint"]
families = ["workfile"]
+ settings_category = "tvpaint"
+
def process(self, instance):
context = instance.context
current_file = context.data["currentFile"]
diff --git a/client/ayon_core/hosts/tvpaint/plugins/publish/collect_workfile_data.py b/server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/collect_workfile_data.py
similarity index 98%
rename from client/ayon_core/hosts/tvpaint/plugins/publish/collect_workfile_data.py
rename to server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/collect_workfile_data.py
index 3155773bda..a34a718ff5 100644
--- a/client/ayon_core/hosts/tvpaint/plugins/publish/collect_workfile_data.py
+++ b/server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/collect_workfile_data.py
@@ -4,13 +4,13 @@ import tempfile
import pyblish.api
-from ayon_core.hosts.tvpaint.api.lib import (
+from ayon_tvpaint.api.lib import (
execute_george,
execute_george_through_file,
get_layers_data,
get_groups_data,
)
-from ayon_core.hosts.tvpaint.api.pipeline import (
+from ayon_tvpaint.api.pipeline import (
SECTION_NAME_CONTEXT,
SECTION_NAME_INSTANCES,
SECTION_NAME_CONTAINERS,
@@ -58,6 +58,8 @@ class CollectWorkfileData(pyblish.api.ContextPlugin):
hosts = ["tvpaint"]
actions = [ResetTVPaintWorkfileMetadata]
+ settings_category = "tvpaint"
+
def process(self, context):
current_project_id = execute_george("tv_projectcurrentid")
execute_george("tv_projectselect {}".format(current_project_id))
diff --git a/client/ayon_core/hosts/tvpaint/plugins/publish/extract_convert_to_exr.py b/server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/extract_convert_to_exr.py
similarity index 98%
rename from client/ayon_core/hosts/tvpaint/plugins/publish/extract_convert_to_exr.py
rename to server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/extract_convert_to_exr.py
index d1bc68ef35..020ebc1a89 100644
--- a/client/ayon_core/hosts/tvpaint/plugins/publish/extract_convert_to_exr.py
+++ b/server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/extract_convert_to_exr.py
@@ -23,6 +23,8 @@ class ExtractConvertToEXR(pyblish.api.InstancePlugin):
hosts = ["tvpaint"]
families = ["render"]
+ settings_category = "tvpaint"
+
enabled = False
# Replace source PNG files or just add
diff --git a/client/ayon_core/hosts/tvpaint/plugins/publish/extract_sequence.py b/server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/extract_sequence.py
similarity index 99%
rename from client/ayon_core/hosts/tvpaint/plugins/publish/extract_sequence.py
rename to server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/extract_sequence.py
index fe5e148b7b..86c20c6528 100644
--- a/client/ayon_core/hosts/tvpaint/plugins/publish/extract_sequence.py
+++ b/server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/extract_sequence.py
@@ -10,13 +10,13 @@ from ayon_core.pipeline.publish import (
KnownPublishError,
get_publish_instance_families,
)
-from ayon_core.hosts.tvpaint.api.lib import (
+from ayon_tvpaint.api.lib import (
execute_george,
execute_george_through_file,
get_layers_pre_post_behavior,
get_layers_exposure_frames,
)
-from ayon_core.hosts.tvpaint.lib import (
+from ayon_tvpaint.lib import (
calculate_layers_extraction_data,
get_frame_filename_template,
fill_reference_frames,
@@ -31,6 +31,8 @@ class ExtractSequence(pyblish.api.InstancePlugin):
hosts = ["tvpaint"]
families = ["review", "render"]
+ settings_category = "tvpaint"
+
# Modifiable with settings
review_bg = [255, 255, 255, 1.0]
diff --git a/client/ayon_core/hosts/tvpaint/plugins/publish/help/validate_asset_name.xml b/server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/help/validate_asset_name.xml
similarity index 100%
rename from client/ayon_core/hosts/tvpaint/plugins/publish/help/validate_asset_name.xml
rename to server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/help/validate_asset_name.xml
diff --git a/client/ayon_core/hosts/tvpaint/plugins/publish/help/validate_duplicated_layer_names.xml b/server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/help/validate_duplicated_layer_names.xml
similarity index 100%
rename from client/ayon_core/hosts/tvpaint/plugins/publish/help/validate_duplicated_layer_names.xml
rename to server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/help/validate_duplicated_layer_names.xml
diff --git a/client/ayon_core/hosts/tvpaint/plugins/publish/help/validate_layers_visibility.xml b/server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/help/validate_layers_visibility.xml
similarity index 100%
rename from client/ayon_core/hosts/tvpaint/plugins/publish/help/validate_layers_visibility.xml
rename to server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/help/validate_layers_visibility.xml
diff --git a/client/ayon_core/hosts/tvpaint/plugins/publish/help/validate_marks.xml b/server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/help/validate_marks.xml
similarity index 100%
rename from client/ayon_core/hosts/tvpaint/plugins/publish/help/validate_marks.xml
rename to server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/help/validate_marks.xml
diff --git a/client/ayon_core/hosts/tvpaint/plugins/publish/help/validate_missing_layer_names.xml b/server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/help/validate_missing_layer_names.xml
similarity index 100%
rename from client/ayon_core/hosts/tvpaint/plugins/publish/help/validate_missing_layer_names.xml
rename to server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/help/validate_missing_layer_names.xml
diff --git a/client/ayon_core/hosts/tvpaint/plugins/publish/help/validate_render_layer_group.xml b/server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/help/validate_render_layer_group.xml
similarity index 100%
rename from client/ayon_core/hosts/tvpaint/plugins/publish/help/validate_render_layer_group.xml
rename to server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/help/validate_render_layer_group.xml
diff --git a/client/ayon_core/hosts/tvpaint/plugins/publish/help/validate_render_pass_group.xml b/server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/help/validate_render_pass_group.xml
similarity index 100%
rename from client/ayon_core/hosts/tvpaint/plugins/publish/help/validate_render_pass_group.xml
rename to server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/help/validate_render_pass_group.xml
diff --git a/client/ayon_core/hosts/tvpaint/plugins/publish/help/validate_scene_settings.xml b/server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/help/validate_scene_settings.xml
similarity index 100%
rename from client/ayon_core/hosts/tvpaint/plugins/publish/help/validate_scene_settings.xml
rename to server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/help/validate_scene_settings.xml
diff --git a/client/ayon_core/hosts/tvpaint/plugins/publish/help/validate_start_frame.xml b/server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/help/validate_start_frame.xml
similarity index 100%
rename from client/ayon_core/hosts/tvpaint/plugins/publish/help/validate_start_frame.xml
rename to server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/help/validate_start_frame.xml
diff --git a/client/ayon_core/hosts/tvpaint/plugins/publish/help/validate_workfile_metadata.xml b/server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/help/validate_workfile_metadata.xml
similarity index 100%
rename from client/ayon_core/hosts/tvpaint/plugins/publish/help/validate_workfile_metadata.xml
rename to server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/help/validate_workfile_metadata.xml
diff --git a/client/ayon_core/hosts/tvpaint/plugins/publish/help/validate_workfile_project_name.xml b/server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/help/validate_workfile_project_name.xml
similarity index 100%
rename from client/ayon_core/hosts/tvpaint/plugins/publish/help/validate_workfile_project_name.xml
rename to server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/help/validate_workfile_project_name.xml
diff --git a/client/ayon_core/hosts/tvpaint/plugins/publish/increment_workfile_version.py b/server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/increment_workfile_version.py
similarity index 95%
rename from client/ayon_core/hosts/tvpaint/plugins/publish/increment_workfile_version.py
rename to server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/increment_workfile_version.py
index 5dd6110bc7..601d276b97 100644
--- a/client/ayon_core/hosts/tvpaint/plugins/publish/increment_workfile_version.py
+++ b/server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/increment_workfile_version.py
@@ -12,6 +12,8 @@ class IncrementWorkfileVersion(pyblish.api.ContextPlugin):
optional = True
hosts = ["tvpaint"]
+ settings_category = "tvpaint"
+
def process(self, context):
assert all(result["success"] for result in context.data["results"]), (
diff --git a/client/ayon_core/hosts/tvpaint/plugins/publish/validate_asset_name.py b/server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/validate_asset_name.py
similarity index 96%
rename from client/ayon_core/hosts/tvpaint/plugins/publish/validate_asset_name.py
rename to server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/validate_asset_name.py
index 764c090720..8763c005dc 100644
--- a/client/ayon_core/hosts/tvpaint/plugins/publish/validate_asset_name.py
+++ b/server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/validate_asset_name.py
@@ -3,7 +3,7 @@ from ayon_core.pipeline import (
PublishXmlValidationError,
OptionalPyblishPluginMixin,
)
-from ayon_core.hosts.tvpaint.api.pipeline import (
+from ayon_tvpaint.api.pipeline import (
list_instances,
write_instances,
)
@@ -48,6 +48,8 @@ class ValidateAssetName(
hosts = ["tvpaint"]
actions = [FixFolderPaths]
+ settings_category = "tvpaint"
+
def process(self, context):
if not self.is_active(context.data):
return
diff --git a/client/ayon_core/hosts/tvpaint/plugins/publish/validate_duplicated_layer_names.py b/server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/validate_duplicated_layer_names.py
similarity index 98%
rename from client/ayon_core/hosts/tvpaint/plugins/publish/validate_duplicated_layer_names.py
rename to server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/validate_duplicated_layer_names.py
index aab0557bdd..be4dc0f123 100644
--- a/client/ayon_core/hosts/tvpaint/plugins/publish/validate_duplicated_layer_names.py
+++ b/server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/validate_duplicated_layer_names.py
@@ -9,6 +9,8 @@ class ValidateLayersGroup(pyblish.api.InstancePlugin):
order = pyblish.api.ValidatorOrder
families = ["renderPass"]
+ settings_category = "tvpaint"
+
def process(self, instance):
# Prepare layers
layers_by_name = instance.context.data["layersByName"]
diff --git a/client/ayon_core/hosts/tvpaint/plugins/publish/validate_layers_visibility.py b/server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/validate_layers_visibility.py
similarity index 97%
rename from client/ayon_core/hosts/tvpaint/plugins/publish/validate_layers_visibility.py
rename to server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/validate_layers_visibility.py
index 1bcdf7baa1..f58b8a6973 100644
--- a/client/ayon_core/hosts/tvpaint/plugins/publish/validate_layers_visibility.py
+++ b/server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/validate_layers_visibility.py
@@ -10,6 +10,8 @@ class ValidateLayersVisiblity(pyblish.api.InstancePlugin):
order = pyblish.api.ValidatorOrder
families = ["review", "render"]
+ settings_category = "tvpaint"
+
def process(self, instance):
layers = instance.data.get("layers")
# Instance have empty layers
diff --git a/client/ayon_core/hosts/tvpaint/plugins/publish/validate_marks.py b/server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/validate_marks.py
similarity index 97%
rename from client/ayon_core/hosts/tvpaint/plugins/publish/validate_marks.py
rename to server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/validate_marks.py
index 6bfbe840bb..0911beb4e8 100644
--- a/client/ayon_core/hosts/tvpaint/plugins/publish/validate_marks.py
+++ b/server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/validate_marks.py
@@ -5,7 +5,7 @@ from ayon_core.pipeline import (
PublishXmlValidationError,
OptionalPyblishPluginMixin,
)
-from ayon_core.hosts.tvpaint.api.lib import execute_george
+from ayon_tvpaint.api.lib import execute_george
class ValidateMarksRepair(pyblish.api.Action):
@@ -41,6 +41,8 @@ class ValidateMarks(
optional = True
actions = [ValidateMarksRepair]
+ settings_category = "tvpaint"
+
@staticmethod
def get_expected_data(context):
scene_mark_in = context.data["sceneMarkIn"]
diff --git a/client/ayon_core/hosts/tvpaint/plugins/publish/validate_missing_layer_names.py b/server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/validate_missing_layer_names.py
similarity index 98%
rename from client/ayon_core/hosts/tvpaint/plugins/publish/validate_missing_layer_names.py
rename to server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/validate_missing_layer_names.py
index 3fc80f6e78..f340d3c10d 100644
--- a/client/ayon_core/hosts/tvpaint/plugins/publish/validate_missing_layer_names.py
+++ b/server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/validate_missing_layer_names.py
@@ -9,6 +9,8 @@ class ValidateMissingLayers(pyblish.api.InstancePlugin):
order = pyblish.api.ValidatorOrder
families = ["renderPass"]
+ settings_category = "tvpaint"
+
def process(self, instance):
# Prepare layers
layers_by_name = instance.context.data["layersByName"]
diff --git a/client/ayon_core/hosts/tvpaint/plugins/publish/validate_render_layer_group.py b/server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/validate_render_layer_group.py
similarity index 98%
rename from client/ayon_core/hosts/tvpaint/plugins/publish/validate_render_layer_group.py
rename to server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/validate_render_layer_group.py
index 0e97a01de2..b20ea3cac6 100644
--- a/client/ayon_core/hosts/tvpaint/plugins/publish/validate_render_layer_group.py
+++ b/server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/validate_render_layer_group.py
@@ -12,6 +12,8 @@ class ValidateRenderLayerGroups(pyblish.api.ContextPlugin):
label = "Validate Render Layers Group"
order = pyblish.api.ValidatorOrder + 0.1
+ settings_category = "tvpaint"
+
def process(self, context):
# Prepare layers
render_layers_by_group_id = collections.defaultdict(list)
diff --git a/client/ayon_core/hosts/tvpaint/plugins/publish/validate_render_pass_group.py b/server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/validate_render_pass_group.py
similarity index 98%
rename from client/ayon_core/hosts/tvpaint/plugins/publish/validate_render_pass_group.py
rename to server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/validate_render_pass_group.py
index 874af38dd4..3d00fd031f 100644
--- a/client/ayon_core/hosts/tvpaint/plugins/publish/validate_render_pass_group.py
+++ b/server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/validate_render_pass_group.py
@@ -13,6 +13,8 @@ class ValidateLayersGroup(pyblish.api.InstancePlugin):
order = pyblish.api.ValidatorOrder + 0.1
families = ["renderPass"]
+ settings_category = "tvpaint"
+
def process(self, instance):
# Prepare layers
layers_data = instance.context.data["layersData"]
diff --git a/client/ayon_core/hosts/tvpaint/plugins/publish/validate_scene_settings.py b/server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/validate_scene_settings.py
similarity index 98%
rename from client/ayon_core/hosts/tvpaint/plugins/publish/validate_scene_settings.py
rename to server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/validate_scene_settings.py
index 5e42b5ab2f..8bad5c43c8 100644
--- a/client/ayon_core/hosts/tvpaint/plugins/publish/validate_scene_settings.py
+++ b/server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/validate_scene_settings.py
@@ -16,6 +16,8 @@ class ValidateProjectSettings(
label = "Validate Scene Settings"
order = pyblish.api.ValidatorOrder
+
+ settings_category = "tvpaint"
optional = True
def process(self, context):
diff --git a/client/ayon_core/hosts/tvpaint/plugins/publish/validate_start_frame.py b/server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/validate_start_frame.py
similarity index 92%
rename from client/ayon_core/hosts/tvpaint/plugins/publish/validate_start_frame.py
rename to server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/validate_start_frame.py
index fea64bd6a8..9669acf1b5 100644
--- a/client/ayon_core/hosts/tvpaint/plugins/publish/validate_start_frame.py
+++ b/server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/validate_start_frame.py
@@ -3,7 +3,7 @@ from ayon_core.pipeline import (
PublishXmlValidationError,
OptionalPyblishPluginMixin,
)
-from ayon_core.hosts.tvpaint.api.lib import execute_george
+from ayon_tvpaint.api.lib import execute_george
class RepairStartFrame(pyblish.api.Action):
@@ -27,6 +27,8 @@ class ValidateStartFrame(
order = pyblish.api.ValidatorOrder
hosts = ["tvpaint"]
actions = [RepairStartFrame]
+
+ settings_category = "tvpaint"
optional = True
def process(self, context):
diff --git a/client/ayon_core/hosts/tvpaint/plugins/publish/validate_workfile_metadata.py b/server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/validate_workfile_metadata.py
similarity index 98%
rename from client/ayon_core/hosts/tvpaint/plugins/publish/validate_workfile_metadata.py
rename to server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/validate_workfile_metadata.py
index 1d9954d051..34c02c78ed 100644
--- a/client/ayon_core/hosts/tvpaint/plugins/publish/validate_workfile_metadata.py
+++ b/server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/validate_workfile_metadata.py
@@ -31,6 +31,8 @@ class ValidateWorkfileMetadata(pyblish.api.ContextPlugin):
actions = [ValidateWorkfileMetadataRepair]
+ settings_category = "tvpaint"
+
required_keys = {"project_name", "folder_path", "task_name"}
def process(self, context):
diff --git a/client/ayon_core/hosts/tvpaint/plugins/publish/validate_workfile_project_name.py b/server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/validate_workfile_project_name.py
similarity index 98%
rename from client/ayon_core/hosts/tvpaint/plugins/publish/validate_workfile_project_name.py
rename to server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/validate_workfile_project_name.py
index 5b42842717..868c7d44fc 100644
--- a/client/ayon_core/hosts/tvpaint/plugins/publish/validate_workfile_project_name.py
+++ b/server_addon/tvpaint/client/ayon_tvpaint/plugins/publish/validate_workfile_project_name.py
@@ -12,6 +12,8 @@ class ValidateWorkfileProjectName(pyblish.api.ContextPlugin):
label = "Validate Workfile Project Name"
order = pyblish.api.ValidatorOrder
+ settings_category = "tvpaint"
+
def process(self, context):
workfile_context = context.data.get("workfile_context")
# If workfile context is missing than project is matching to
diff --git a/client/ayon_core/hosts/tvpaint/resources/template.tvpp b/server_addon/tvpaint/client/ayon_tvpaint/resources/template.tvpp
similarity index 100%
rename from client/ayon_core/hosts/tvpaint/resources/template.tvpp
rename to server_addon/tvpaint/client/ayon_tvpaint/resources/template.tvpp
diff --git a/client/ayon_core/hosts/tvpaint/tvpaint_plugin/__init__.py b/server_addon/tvpaint/client/ayon_tvpaint/tvpaint_plugin/__init__.py
similarity index 100%
rename from client/ayon_core/hosts/tvpaint/tvpaint_plugin/__init__.py
rename to server_addon/tvpaint/client/ayon_tvpaint/tvpaint_plugin/__init__.py
diff --git a/client/ayon_core/hosts/tvpaint/tvpaint_plugin/plugin_code/CMakeLists.txt b/server_addon/tvpaint/client/ayon_tvpaint/tvpaint_plugin/plugin_code/CMakeLists.txt
similarity index 100%
rename from client/ayon_core/hosts/tvpaint/tvpaint_plugin/plugin_code/CMakeLists.txt
rename to server_addon/tvpaint/client/ayon_tvpaint/tvpaint_plugin/plugin_code/CMakeLists.txt
diff --git a/client/ayon_core/hosts/tvpaint/tvpaint_plugin/plugin_code/README.md b/server_addon/tvpaint/client/ayon_tvpaint/tvpaint_plugin/plugin_code/README.md
similarity index 100%
rename from client/ayon_core/hosts/tvpaint/tvpaint_plugin/plugin_code/README.md
rename to server_addon/tvpaint/client/ayon_tvpaint/tvpaint_plugin/plugin_code/README.md
diff --git a/client/ayon_core/hosts/tvpaint/tvpaint_plugin/plugin_code/library.cpp b/server_addon/tvpaint/client/ayon_tvpaint/tvpaint_plugin/plugin_code/library.cpp
similarity index 100%
rename from client/ayon_core/hosts/tvpaint/tvpaint_plugin/plugin_code/library.cpp
rename to server_addon/tvpaint/client/ayon_tvpaint/tvpaint_plugin/plugin_code/library.cpp
diff --git a/client/ayon_core/hosts/tvpaint/tvpaint_plugin/plugin_code/library.def b/server_addon/tvpaint/client/ayon_tvpaint/tvpaint_plugin/plugin_code/library.def
similarity index 100%
rename from client/ayon_core/hosts/tvpaint/tvpaint_plugin/plugin_code/library.def
rename to server_addon/tvpaint/client/ayon_tvpaint/tvpaint_plugin/plugin_code/library.def
diff --git a/client/ayon_core/hosts/tvpaint/tvpaint_plugin/plugin_files/windows_x64/plugin/OpenPypePlugin.dll b/server_addon/tvpaint/client/ayon_tvpaint/tvpaint_plugin/plugin_files/windows_x64/plugin/OpenPypePlugin.dll
similarity index 100%
rename from client/ayon_core/hosts/tvpaint/tvpaint_plugin/plugin_files/windows_x64/plugin/OpenPypePlugin.dll
rename to server_addon/tvpaint/client/ayon_tvpaint/tvpaint_plugin/plugin_files/windows_x64/plugin/OpenPypePlugin.dll
diff --git a/client/ayon_core/hosts/tvpaint/tvpaint_plugin/plugin_files/windows_x86/plugin/OpenPypePlugin.dll b/server_addon/tvpaint/client/ayon_tvpaint/tvpaint_plugin/plugin_files/windows_x86/plugin/OpenPypePlugin.dll
similarity index 100%
rename from client/ayon_core/hosts/tvpaint/tvpaint_plugin/plugin_files/windows_x86/plugin/OpenPypePlugin.dll
rename to server_addon/tvpaint/client/ayon_tvpaint/tvpaint_plugin/plugin_files/windows_x86/plugin/OpenPypePlugin.dll
diff --git a/client/ayon_core/hosts/tvpaint/worker/__init__.py b/server_addon/tvpaint/client/ayon_tvpaint/worker/__init__.py
similarity index 100%
rename from client/ayon_core/hosts/tvpaint/worker/__init__.py
rename to server_addon/tvpaint/client/ayon_tvpaint/worker/__init__.py
diff --git a/client/ayon_core/hosts/tvpaint/worker/init_file.tvpp b/server_addon/tvpaint/client/ayon_tvpaint/worker/init_file.tvpp
similarity index 100%
rename from client/ayon_core/hosts/tvpaint/worker/init_file.tvpp
rename to server_addon/tvpaint/client/ayon_tvpaint/worker/init_file.tvpp
diff --git a/client/ayon_core/hosts/tvpaint/worker/worker.py b/server_addon/tvpaint/client/ayon_tvpaint/worker/worker.py
similarity index 97%
rename from client/ayon_core/hosts/tvpaint/worker/worker.py
rename to server_addon/tvpaint/client/ayon_tvpaint/worker/worker.py
index 3d9b1ef2b8..aa79fd442c 100644
--- a/client/ayon_core/hosts/tvpaint/worker/worker.py
+++ b/server_addon/tvpaint/client/ayon_tvpaint/worker/worker.py
@@ -5,11 +5,11 @@ import tempfile
import shutil
import asyncio
-from ayon_core.hosts.tvpaint.api.communication_server import (
+from ayon_tvpaint.api.communication_server import (
BaseCommunicator,
CommunicationWrapper
)
-from openpype_modules.job_queue.job_workers import WorkerJobsConnection
+from ayon_core.modules.job_queue.job_workers import WorkerJobsConnection
from .worker_job import ProcessTVPaintCommands
diff --git a/client/ayon_core/hosts/tvpaint/worker/worker_job.py b/server_addon/tvpaint/client/ayon_tvpaint/worker/worker_job.py
similarity index 99%
rename from client/ayon_core/hosts/tvpaint/worker/worker_job.py
rename to server_addon/tvpaint/client/ayon_tvpaint/worker/worker_job.py
index f111ed369a..db91010c47 100644
--- a/client/ayon_core/hosts/tvpaint/worker/worker_job.py
+++ b/server_addon/tvpaint/client/ayon_tvpaint/worker/worker_job.py
@@ -256,7 +256,7 @@ class CollectSceneData(BaseCommand):
name = "collect_scene_data"
def execute(self):
- from ayon_core.hosts.tvpaint.api.lib import (
+ from ayon_tvpaint.api.lib import (
get_layers_data,
get_groups_data,
get_layers_pre_post_behavior,
diff --git a/server_addon/tvpaint/package.py b/server_addon/tvpaint/package.py
index 2be3164f4a..3ab35f727e 100644
--- a/server_addon/tvpaint/package.py
+++ b/server_addon/tvpaint/package.py
@@ -1,3 +1,9 @@
name = "tvpaint"
title = "TVPaint"
-version = "0.1.2"
+version = "0.2.0"
+client_dir = "ayon_tvpaint"
+
+ayon_required_addons = {
+ "core": ">0.3.2",
+}
+ayon_compatible_addons = {}