diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
index a35dbf1a17..7a39103859 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -35,6 +35,7 @@ body:
label: Version
description: What version are you running? Look to OpenPype Tray
options:
+ - 3.16.6-nightly.1
- 3.16.5
- 3.16.5-nightly.5
- 3.16.5-nightly.4
@@ -134,7 +135,6 @@ body:
- 3.14.9-nightly.4
- 3.14.9-nightly.3
- 3.14.9-nightly.2
- - 3.14.9-nightly.1
validations:
required: true
- type: dropdown
diff --git a/openpype/hosts/aftereffects/api/extension.zxp b/openpype/hosts/aftereffects/api/extension.zxp
index 358e9740d3..933dc7dc6c 100644
Binary files a/openpype/hosts/aftereffects/api/extension.zxp and b/openpype/hosts/aftereffects/api/extension.zxp differ
diff --git a/openpype/hosts/aftereffects/api/extension/CSXS/manifest.xml b/openpype/hosts/aftereffects/api/extension/CSXS/manifest.xml
index 0057758320..7329a9e723 100644
--- a/openpype/hosts/aftereffects/api/extension/CSXS/manifest.xml
+++ b/openpype/hosts/aftereffects/api/extension/CSXS/manifest.xml
@@ -1,5 +1,5 @@
-
@@ -10,22 +10,22 @@
-
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
@@ -63,7 +63,7 @@
550
400
-->
-
+
./icons/iconNormal.png
@@ -71,9 +71,9 @@
./icons/iconDisabled.png
./icons/iconDarkNormal.png
./icons/iconDarkRollover.png
-
+
-
\ No newline at end of file
+
diff --git a/openpype/hosts/aftereffects/api/extension/jsx/hostscript.jsx b/openpype/hosts/aftereffects/api/extension/jsx/hostscript.jsx
index bc443930df..c00844e637 100644
--- a/openpype/hosts/aftereffects/api/extension/jsx/hostscript.jsx
+++ b/openpype/hosts/aftereffects/api/extension/jsx/hostscript.jsx
@@ -215,6 +215,8 @@ function _getItem(item, comps, folders, footages){
* Refactor
*/
var item_type = '';
+ var path = '';
+ var containing_comps = [];
if (item instanceof FolderItem){
item_type = 'folder';
if (!folders){
@@ -222,10 +224,18 @@ function _getItem(item, comps, folders, footages){
}
}
if (item instanceof FootageItem){
- item_type = 'footage';
if (!footages){
return "{}";
}
+ item_type = 'footage';
+ if (item.file){
+ path = item.file.fsName;
+ }
+ if (item.usedIn){
+ for (j = 0; j < item.usedIn.length; ++j){
+ containing_comps.push(item.usedIn[j].id);
+ }
+ }
}
if (item instanceof CompItem){
item_type = 'comp';
@@ -236,7 +246,9 @@ function _getItem(item, comps, folders, footages){
var item = {"name": item.name,
"id": item.id,
- "type": item_type};
+ "type": item_type,
+ "path": path,
+ "containing_comps": containing_comps};
return JSON.stringify(item);
}
diff --git a/openpype/hosts/aftereffects/api/ws_stub.py b/openpype/hosts/aftereffects/api/ws_stub.py
index f5b96fa63a..18f530e272 100644
--- a/openpype/hosts/aftereffects/api/ws_stub.py
+++ b/openpype/hosts/aftereffects/api/ws_stub.py
@@ -37,6 +37,9 @@ class AEItem(object):
height = attr.ib(default=None)
is_placeholder = attr.ib(default=False)
uuid = attr.ib(default=False)
+ path = attr.ib(default=False) # path to FootageItem to validate
+ # list of composition Footage is in
+ containing_comps = attr.ib(factory=list)
class AfterEffectsServerStub():
@@ -704,7 +707,10 @@ class AfterEffectsServerStub():
d.get("instance_id"),
d.get("width"),
d.get("height"),
- d.get("is_placeholder"))
+ d.get("is_placeholder"),
+ d.get("uuid"),
+ d.get("path"),
+ d.get("containing_comps"),)
ret.append(item)
return ret
diff --git a/openpype/hosts/aftereffects/plugins/load/load_file.py b/openpype/hosts/aftereffects/plugins/load/load_file.py
index def7c927ab..8d52aac546 100644
--- a/openpype/hosts/aftereffects/plugins/load/load_file.py
+++ b/openpype/hosts/aftereffects/plugins/load/load_file.py
@@ -31,13 +31,8 @@ class FileLoader(api.AfterEffectsLoader):
path = self.filepath_from_context(context)
- repr_cont = context["representation"]["context"]
- if "#" not in path:
- frame = repr_cont.get("frame")
- if frame:
- padding = len(frame)
- path = path.replace(frame, "#" * padding)
- import_options['sequence'] = True
+ if len(context["representation"]["files"]) > 1:
+ import_options['sequence'] = True
if not path:
repr_id = context["representation"]["_id"]
diff --git a/openpype/hosts/aftereffects/plugins/publish/help/validate_footage_items.xml b/openpype/hosts/aftereffects/plugins/publish/help/validate_footage_items.xml
new file mode 100644
index 0000000000..01c8966015
--- /dev/null
+++ b/openpype/hosts/aftereffects/plugins/publish/help/validate_footage_items.xml
@@ -0,0 +1,14 @@
+
+
+
+Footage item missing
+
+## Footage item missing
+
+ FootageItem `{name}` contains missing `{path}`. Render will not produce any frames and AE will stop react to any integration
+### How to repair?
+
+Remove `{name}` or provide missing file.
+
+
+
diff --git a/openpype/hosts/aftereffects/plugins/publish/validate_footage_items.py b/openpype/hosts/aftereffects/plugins/publish/validate_footage_items.py
new file mode 100644
index 0000000000..40a08a2c3f
--- /dev/null
+++ b/openpype/hosts/aftereffects/plugins/publish/validate_footage_items.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+"""Validate presence of footage items in composition
+Requires:
+"""
+import os
+
+import pyblish.api
+
+from openpype.pipeline import (
+ PublishXmlValidationError
+)
+from openpype.hosts.aftereffects.api import get_stub
+
+
+class ValidateFootageItems(pyblish.api.InstancePlugin):
+ """
+ Validates if FootageItems contained in composition exist.
+
+ AE fails silently and doesn't render anything if footage item file is
+ missing. This will result in nonresponsiveness of AE UI as it expects
+ reaction from user, but it will not provide dialog.
+ This validator tries to check existence of the files.
+ It will not protect from missing frame in multiframes though
+ (as AE api doesn't provide this information and it cannot be told how many
+ frames should be there easily). Missing frame is replaced by placeholder.
+ """
+
+ order = pyblish.api.ValidatorOrder
+ label = "Validate Footage Items"
+ families = ["render.farm", "render.local", "render"]
+ hosts = ["aftereffects"]
+ optional = True
+
+ def process(self, instance):
+ """Plugin entry point."""
+
+ comp_id = instance.data["comp_id"]
+ for footage_item in get_stub().get_items(comps=False, folders=False,
+ footages=True):
+ self.log.info(footage_item)
+ if comp_id not in footage_item.containing_comps:
+ continue
+
+ path = footage_item.path
+ if path and not os.path.exists(path):
+ msg = f"File {path} not found."
+ formatting = {"name": footage_item.name, "path": path}
+ raise PublishXmlValidationError(self, msg,
+ formatting_data=formatting)
diff --git a/openpype/hosts/blender/plugins/publish/collect_review.py b/openpype/hosts/blender/plugins/publish/collect_review.py
index 6459927015..3bf2e39e24 100644
--- a/openpype/hosts/blender/plugins/publish/collect_review.py
+++ b/openpype/hosts/blender/plugins/publish/collect_review.py
@@ -39,15 +39,11 @@ class CollectReview(pyblish.api.InstancePlugin):
]
if not instance.data.get("remove"):
-
- task = instance.context.data["task"]
-
# Store focal length in `burninDataMembers`
burninData = instance.data.setdefault("burninDataMembers", {})
burninData["focalLength"] = focal_length
instance.data.update({
- "subset": f"{task}Review",
"review_camera": camera,
"frameStart": instance.context.data["frameStart"],
"frameEnd": instance.context.data["frameEnd"],
diff --git a/openpype/hosts/photoshop/plugins/create/create_flatten_image.py b/openpype/hosts/photoshop/plugins/create/create_flatten_image.py
index 9d4189a1a3..e4229788bd 100644
--- a/openpype/hosts/photoshop/plugins/create/create_flatten_image.py
+++ b/openpype/hosts/photoshop/plugins/create/create_flatten_image.py
@@ -4,6 +4,7 @@ from openpype.lib import BoolDef
import openpype.hosts.photoshop.api as api
from openpype.hosts.photoshop.lib import PSAutoCreator
from openpype.pipeline.create import get_subset_name
+from openpype.lib import prepare_template_data
from openpype.client import get_asset_by_name
@@ -37,19 +38,14 @@ class AutoImageCreator(PSAutoCreator):
asset_doc = get_asset_by_name(project_name, asset_name)
if existing_instance is None:
- subset_name = get_subset_name(
- self.family, self.default_variant, task_name, asset_doc,
+ subset_name = self.get_subset_name(
+ self.default_variant, task_name, asset_doc,
project_name, host_name
)
- publishable_ids = [layer.id for layer in api.stub().get_layers()
- if layer.visible]
data = {
"asset": asset_name,
"task": task_name,
- # ids are "virtual" layers, won't get grouped as 'members' do
- # same difference in color coded layers in WP
- "ids": publishable_ids
}
if not self.active_on_create:
@@ -69,8 +65,8 @@ class AutoImageCreator(PSAutoCreator):
existing_instance["asset"] != asset_name
or existing_instance["task"] != task_name
):
- subset_name = get_subset_name(
- self.family, self.default_variant, task_name, asset_doc,
+ subset_name = self.get_subset_name(
+ self.default_variant, task_name, asset_doc,
project_name, host_name
)
@@ -118,3 +114,29 @@ class AutoImageCreator(PSAutoCreator):
Artist might disable this instance from publishing or from creating
review for it though.
"""
+
+ def get_subset_name(
+ self,
+ variant,
+ task_name,
+ asset_doc,
+ project_name,
+ host_name=None,
+ instance=None
+ ):
+ dynamic_data = prepare_template_data({"layer": "{layer}"})
+ subset_name = get_subset_name(
+ self.family, variant, task_name, asset_doc,
+ project_name, host_name, dynamic_data=dynamic_data
+ )
+ return self._clean_subset_name(subset_name)
+
+ def _clean_subset_name(self, subset_name):
+ """Clean all variants leftover {layer} from subset name."""
+ dynamic_data = prepare_template_data({"layer": "{layer}"})
+ for value in dynamic_data.values():
+ if value in subset_name:
+ return (subset_name.replace(value, "")
+ .replace("__", "_")
+ .replace("..", "."))
+ return subset_name
diff --git a/openpype/hosts/photoshop/plugins/create/create_image.py b/openpype/hosts/photoshop/plugins/create/create_image.py
index 8d3ac9f459..af20d456e0 100644
--- a/openpype/hosts/photoshop/plugins/create/create_image.py
+++ b/openpype/hosts/photoshop/plugins/create/create_image.py
@@ -94,12 +94,17 @@ class ImageCreator(Creator):
name = self._clean_highlights(stub, directory)
layer_names_in_hierarchy.append(name)
- data.update({"subset": subset_name})
- data.update({"members": [str(group.id)]})
- data.update({"layer_name": layer_name})
- data.update({"long_name": "_".join(layer_names_in_hierarchy)})
+ data_update = {
+ "subset": subset_name,
+ "members": [str(group.id)],
+ "layer_name": layer_name,
+ "long_name": "_".join(layer_names_in_hierarchy)
+ }
+ data.update(data_update)
- creator_attributes = {"mark_for_review": self.mark_for_review}
+ mark_for_review = (pre_create_data.get("mark_for_review") or
+ self.mark_for_review)
+ creator_attributes = {"mark_for_review": mark_for_review}
data.update({"creator_attributes": creator_attributes})
if not self.active_on_create:
@@ -124,8 +129,6 @@ class ImageCreator(Creator):
if creator_id == self.identifier:
instance_data = self._handle_legacy(instance_data)
- layer = api.stub().get_layer(instance_data["members"][0])
- instance_data["layer"] = layer
instance = CreatedInstance.from_existing(
instance_data, self
)
diff --git a/openpype/hosts/photoshop/plugins/publish/collect_auto_image_refresh.py b/openpype/hosts/photoshop/plugins/publish/collect_auto_image_refresh.py
new file mode 100644
index 0000000000..741fb0e9cd
--- /dev/null
+++ b/openpype/hosts/photoshop/plugins/publish/collect_auto_image_refresh.py
@@ -0,0 +1,24 @@
+import pyblish.api
+
+from openpype.hosts.photoshop import api as photoshop
+
+
+class CollectAutoImageRefresh(pyblish.api.ContextPlugin):
+ """Refreshes auto_image instance with currently visible layers..
+ """
+
+ label = "Collect Auto Image Refresh"
+ order = pyblish.api.CollectorOrder
+ hosts = ["photoshop"]
+ order = pyblish.api.CollectorOrder + 0.2
+
+ def process(self, context):
+ for instance in context:
+ creator_identifier = instance.data.get("creator_identifier")
+ if creator_identifier and creator_identifier == "auto_image":
+ self.log.debug("Auto image instance found, won't create new")
+ # refresh existing auto image instance with current visible
+ publishable_ids = [layer.id for layer in photoshop.stub().get_layers() # noqa
+ if layer.visible]
+ instance.data["ids"] = publishable_ids
+ return
diff --git a/openpype/hosts/photoshop/plugins/publish/collect_image.py b/openpype/hosts/photoshop/plugins/publish/collect_image.py
new file mode 100644
index 0000000000..64727cef33
--- /dev/null
+++ b/openpype/hosts/photoshop/plugins/publish/collect_image.py
@@ -0,0 +1,20 @@
+import pyblish.api
+
+from openpype.hosts.photoshop import api
+
+
+class CollectImage(pyblish.api.InstancePlugin):
+ """Collect layer metadata into a instance.
+
+ Used later in validation
+ """
+ order = pyblish.api.CollectorOrder + 0.200
+ label = 'Collect Image'
+
+ hosts = ["photoshop"]
+ families = ["image"]
+
+ def process(self, instance):
+ if instance.data.get("members"):
+ layer = api.stub().get_layer(instance.data["members"][0])
+ instance.data["layer"] = layer
diff --git a/openpype/hosts/photoshop/plugins/publish/extract_image.py b/openpype/hosts/photoshop/plugins/publish/extract_image.py
index cdb28c742d..680f580cc0 100644
--- a/openpype/hosts/photoshop/plugins/publish/extract_image.py
+++ b/openpype/hosts/photoshop/plugins/publish/extract_image.py
@@ -45,9 +45,11 @@ class ExtractImage(pyblish.api.ContextPlugin):
# Perform extraction
files = {}
ids = set()
- layer = instance.data.get("layer")
- if layer:
- ids.add(layer.id)
+ # real layers and groups
+ members = instance.data("members")
+ if members:
+ ids.update(set([int(member) for member in members]))
+ # virtual groups collected by color coding or auto_image
add_ids = instance.data.pop("ids", None)
if add_ids:
ids.update(set(add_ids))
diff --git a/openpype/hosts/photoshop/plugins/publish/extract_review.py b/openpype/hosts/photoshop/plugins/publish/extract_review.py
index 4aa7a05bd1..afddbdba31 100644
--- a/openpype/hosts/photoshop/plugins/publish/extract_review.py
+++ b/openpype/hosts/photoshop/plugins/publish/extract_review.py
@@ -1,4 +1,5 @@
import os
+import shutil
from PIL import Image
from openpype.lib import (
@@ -55,6 +56,7 @@ class ExtractReview(publish.Extractor):
}
if instance.data["family"] != "review":
+ self.log.debug("Existing extracted file from image family used.")
# enable creation of review, without this jpg review would clash
# with jpg of the image family
output_name = repre_name
@@ -62,8 +64,15 @@ class ExtractReview(publish.Extractor):
repre_skeleton.update({"name": repre_name,
"outputName": output_name})
- if self.make_image_sequence and len(layers) > 1:
- self.log.info("Extract layers to image sequence.")
+ img_file = self.output_seq_filename % 0
+ self._prepare_file_for_image_family(img_file, instance,
+ staging_dir)
+ repre_skeleton.update({
+ "files": img_file,
+ })
+ processed_img_names = [img_file]
+ elif self.make_image_sequence and len(layers) > 1:
+ self.log.debug("Extract layers to image sequence.")
img_list = self._save_sequence_images(staging_dir, layers)
repre_skeleton.update({
@@ -72,17 +81,17 @@ class ExtractReview(publish.Extractor):
"fps": fps,
"files": img_list,
})
- instance.data["representations"].append(repre_skeleton)
processed_img_names = img_list
else:
- self.log.info("Extract layers to flatten image.")
- img_list = self._save_flatten_image(staging_dir, layers)
+ self.log.debug("Extract layers to flatten image.")
+ img_file = self._save_flatten_image(staging_dir, layers)
repre_skeleton.update({
- "files": img_list,
+ "files": img_file,
})
- instance.data["representations"].append(repre_skeleton)
- processed_img_names = [img_list]
+ processed_img_names = [img_file]
+
+ instance.data["representations"].append(repre_skeleton)
ffmpeg_args = get_ffmpeg_tool_args("ffmpeg")
@@ -111,6 +120,35 @@ class ExtractReview(publish.Extractor):
self.log.info(f"Extracted {instance} to {staging_dir}")
+ def _prepare_file_for_image_family(self, img_file, instance, staging_dir):
+ """Converts existing file for image family to .jpg
+
+ Image instance could have its own separate review (instance per layer
+ for example). This uses extracted file instead of extracting again.
+ Args:
+ img_file (str): name of output file (with 0000 value for ffmpeg
+ later)
+ instance:
+ staging_dir (str): temporary folder where extracted file is located
+ """
+ repre_file = instance.data["representations"][0]
+ source_file_path = os.path.join(repre_file["stagingDir"],
+ repre_file["files"])
+ if not os.path.exists(source_file_path):
+ raise RuntimeError(f"{source_file_path} doesn't exist for "
+ "review to create from")
+ _, ext = os.path.splitext(repre_file["files"])
+ if ext != ".jpg":
+ im = Image.open(source_file_path)
+ # without this it produces messy low quality jpg
+ rgb_im = Image.new("RGBA", (im.width, im.height), "#ffffff")
+ rgb_im.alpha_composite(im)
+ rgb_im.convert("RGB").save(os.path.join(staging_dir, img_file))
+ else:
+ # handles already .jpg
+ shutil.copy(source_file_path,
+ os.path.join(staging_dir, img_file))
+
def _generate_mov(self, ffmpeg_path, instance, fps, no_of_frames,
source_files_pattern, staging_dir):
"""Generates .mov to upload to Ftrack.
@@ -218,6 +256,11 @@ class ExtractReview(publish.Extractor):
(list) of PSItem
"""
layers = []
+ # creating review for existing 'image' instance
+ if instance.data["family"] == "image" and instance.data.get("layer"):
+ layers.append(instance.data["layer"])
+ return layers
+
for image_instance in instance.context:
if image_instance.data["family"] != "image":
continue
diff --git a/openpype/hosts/tvpaint/api/communication_server.py b/openpype/hosts/tvpaint/api/communication_server.py
index 6f76c25e0c..d67ef8f798 100644
--- a/openpype/hosts/tvpaint/api/communication_server.py
+++ b/openpype/hosts/tvpaint/api/communication_server.py
@@ -11,7 +11,7 @@ import filecmp
import tempfile
import threading
import shutil
-from queue import Queue
+
from contextlib import closing
from aiohttp import web
@@ -319,19 +319,19 @@ class QtTVPaintRpc(BaseTVPaintRpc):
async def workfiles_tool(self):
log.info("Triggering Workfile tool")
item = MainThreadItem(self.tools_helper.show_workfiles)
- self._execute_in_main_thread(item)
+ self._execute_in_main_thread(item, wait=False)
return
async def loader_tool(self):
log.info("Triggering Loader tool")
item = MainThreadItem(self.tools_helper.show_loader)
- self._execute_in_main_thread(item)
+ self._execute_in_main_thread(item, wait=False)
return
async def publish_tool(self):
log.info("Triggering Publish tool")
item = MainThreadItem(self.tools_helper.show_publisher_tool)
- self._execute_in_main_thread(item)
+ self._execute_in_main_thread(item, wait=False)
return
async def scene_inventory_tool(self):
@@ -350,13 +350,13 @@ class QtTVPaintRpc(BaseTVPaintRpc):
async def library_loader_tool(self):
log.info("Triggering Library loader tool")
item = MainThreadItem(self.tools_helper.show_library_loader)
- self._execute_in_main_thread(item)
+ self._execute_in_main_thread(item, wait=False)
return
async def experimental_tools(self):
log.info("Triggering Library loader tool")
item = MainThreadItem(self.tools_helper.show_experimental_tools_dialog)
- self._execute_in_main_thread(item)
+ self._execute_in_main_thread(item, wait=False)
return
async def _async_execute_in_main_thread(self, item, **kwargs):
@@ -867,7 +867,7 @@ class QtCommunicator(BaseCommunicator):
def __init__(self, qt_app):
super().__init__()
- self.callback_queue = Queue()
+ self.callback_queue = collections.deque()
self.qt_app = qt_app
def _create_routes(self):
@@ -880,14 +880,14 @@ class QtCommunicator(BaseCommunicator):
def execute_in_main_thread(self, main_thread_item, wait=True):
"""Add `MainThreadItem` to callback queue and wait for result."""
- self.callback_queue.put(main_thread_item)
+ self.callback_queue.append(main_thread_item)
if wait:
return main_thread_item.wait()
return
async def async_execute_in_main_thread(self, main_thread_item, wait=True):
"""Add `MainThreadItem` to callback queue and wait for result."""
- self.callback_queue.put(main_thread_item)
+ self.callback_queue.append(main_thread_item)
if wait:
return await main_thread_item.async_wait()
@@ -904,9 +904,9 @@ class QtCommunicator(BaseCommunicator):
self._exit()
return None
- if self.callback_queue.empty():
- return None
- return self.callback_queue.get()
+ if self.callback_queue:
+ return self.callback_queue.popleft()
+ return None
def _on_client_connect(self):
super()._on_client_connect()
diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json
index b736c462ff..7961e77113 100644
--- a/openpype/settings/defaults/project_settings/nuke.json
+++ b/openpype/settings/defaults/project_settings/nuke.json
@@ -28,11 +28,7 @@
"colorManagement": "Nuke",
"OCIO_config": "nuke-default",
"workingSpaceLUT": "linear",
- "monitorLut": "sRGB",
- "int8Lut": "sRGB",
- "int16Lut": "sRGB",
- "logLut": "Cineon",
- "floatLut": "linear"
+ "monitorLut": "sRGB"
},
"nodes": {
"requiredNodes": [
diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_imageio.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_imageio.json
index d4cd332ef8..af826fcf46 100644
--- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_imageio.json
+++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_imageio.json
@@ -106,26 +106,6 @@
"type": "text",
"key": "monitorLut",
"label": "monitor"
- },
- {
- "type": "text",
- "key": "int8Lut",
- "label": "8-bit files"
- },
- {
- "type": "text",
- "key": "int16Lut",
- "label": "16-bit files"
- },
- {
- "type": "text",
- "key": "logLut",
- "label": "log files"
- },
- {
- "type": "text",
- "key": "floatLut",
- "label": "float files"
}
]
}
diff --git a/server_addon/nuke/server/settings/common.py b/server_addon/nuke/server/settings/common.py
index 700f01f3dc..2bc3c9be81 100644
--- a/server_addon/nuke/server/settings/common.py
+++ b/server_addon/nuke/server/settings/common.py
@@ -89,12 +89,6 @@ knob_types_enum = [
class KnobModel(BaseSettingsModel):
- """# TODO: new data structure
- - v3 was having type, name, value but
- ayon is not able to make it the same. Current model is
- defining `type` as `text` and instead of `value` the key is `text`.
- So if `type` is `boolean` then key is `boolean` (value).
- """
_layout = "expanded"
type: str = Field(
diff --git a/server_addon/nuke/server/settings/create_plugins.py b/server_addon/nuke/server/settings/create_plugins.py
index 0bbae4ee77..80aec51ae0 100644
--- a/server_addon/nuke/server/settings/create_plugins.py
+++ b/server_addon/nuke/server/settings/create_plugins.py
@@ -16,13 +16,10 @@ def instance_attributes_enum():
class PrenodeModel(BaseSettingsModel):
- # TODO: missing in host api
- # - good for `dependency`
name: str = Field(
title="Node name"
)
- # TODO: `nodeclass` should be renamed to `nuke_node_class`
nodeclass: str = Field(
"",
title="Node class"
@@ -32,11 +29,8 @@ class PrenodeModel(BaseSettingsModel):
title="Incoming dependency"
)
- """# TODO: Changes in host api:
- - Need complete rework of knob types in nuke integration.
- - We could not support v3 style of settings.
- """
knobs: list[KnobModel] = Field(
+ default_factory=list,
title="Knobs",
)
@@ -61,11 +55,8 @@ class CreateWriteRenderModel(BaseSettingsModel):
title="Instance attributes"
)
- """# TODO: Changes in host api:
- - prenodes key was originally dict and now is list
- (we could not support v3 style of settings)
- """
prenodes: list[PrenodeModel] = Field(
+ default_factory=list,
title="Preceding nodes",
)
@@ -90,11 +81,8 @@ class CreateWritePrerenderModel(BaseSettingsModel):
title="Instance attributes"
)
- """# TODO: Changes in host api:
- - prenodes key was originally dict and now is list
- (we could not support v3 style of settings)
- """
prenodes: list[PrenodeModel] = Field(
+ default_factory=list,
title="Preceding nodes",
)
@@ -119,11 +107,8 @@ class CreateWriteImageModel(BaseSettingsModel):
title="Instance attributes"
)
- """# TODO: Changes in host api:
- - prenodes key was originally dict and now is list
- (we could not support v3 style of settings)
- """
prenodes: list[PrenodeModel] = Field(
+ default_factory=list,
title="Preceding nodes",
)
diff --git a/server_addon/nuke/server/settings/dirmap.py b/server_addon/nuke/server/settings/dirmap.py
index 2da6d7bf60..7e3c443957 100644
--- a/server_addon/nuke/server/settings/dirmap.py
+++ b/server_addon/nuke/server/settings/dirmap.py
@@ -25,19 +25,6 @@ class DirmapSettings(BaseSettingsModel):
)
-"""# TODO:
-nuke is having originally implemented
-following data inputs:
-
-"nuke-dirmap": {
- "enabled": false,
- "paths": {
- "source-path": [],
- "destination-path": []
- }
-}
-"""
-
DEFAULT_DIRMAP_SETTINGS = {
"enabled": False,
"paths": {
diff --git a/server_addon/nuke/server/settings/imageio.py b/server_addon/nuke/server/settings/imageio.py
index b43017ef8b..811b12104b 100644
--- a/server_addon/nuke/server/settings/imageio.py
+++ b/server_addon/nuke/server/settings/imageio.py
@@ -9,22 +9,17 @@ from .common import KnobModel
class NodesModel(BaseSettingsModel):
- """# TODO: This needs to be somehow labeled in settings panel
- or at least it could show gist of configuration
- """
_layout = "expanded"
plugins: list[str] = Field(
+ default_factory=list,
title="Used in plugins"
)
- # TODO: rename `nukeNodeClass` to `nuke_node_class`
nukeNodeClass: str = Field(
title="Nuke Node Class",
)
- """ # TODO: Need complete rework of knob types
- in nuke integration. We could not support v3 style of settings.
- """
knobs: list[KnobModel] = Field(
+ default_factory=list,
title="Knobs",
)
@@ -66,22 +61,6 @@ def ocio_configs_switcher_enum():
class WorkfileColorspaceSettings(BaseSettingsModel):
"""Nuke workfile colorspace preset. """
- """# TODO: enhance settings with host api:
- we need to add mapping to resolve properly keys.
- Nuke is excpecting camel case key names,
- but for better code consistency we need to
- be using snake_case:
-
- color_management = colorManagement
- ocio_config = OCIO_config
- working_space_name = workingSpaceLUT
- monitor_name = monitorLut
- monitor_out_name = monitorOutLut
- int_8_name = int8Lut
- int_16_name = int16Lut
- log_name = logLut
- float_name = floatLut
- """
colorManagement: Literal["Nuke", "OCIO"] = Field(
title="Color Management"
@@ -100,18 +79,6 @@ class WorkfileColorspaceSettings(BaseSettingsModel):
monitorLut: str = Field(
title="Monitor"
)
- int8Lut: str = Field(
- title="8-bit files"
- )
- int16Lut: str = Field(
- title="16-bit files"
- )
- logLut: str = Field(
- title="Log files"
- )
- floatLut: str = Field(
- title="Float files"
- )
class ReadColorspaceRulesItems(BaseSettingsModel):
@@ -170,7 +137,7 @@ class ImageIOSettings(BaseSettingsModel):
_isGroup: bool = True
"""# TODO: enhance settings with host api:
- to restruture settings for simplification.
+ to restructure settings for simplification.
now: nuke/imageio/viewer/viewerProcess
future: nuke/imageio/viewer
@@ -193,7 +160,7 @@ class ImageIOSettings(BaseSettingsModel):
)
"""# TODO: enhance settings with host api:
- to restruture settings for simplification.
+ to restructure settings for simplification.
now: nuke/imageio/baking/viewerProcess
future: nuke/imageio/baking
@@ -215,9 +182,9 @@ class ImageIOSettings(BaseSettingsModel):
title="Nodes"
)
"""# TODO: enhance settings with host api:
- - old settings are using `regexInputs` key but we
+ - [ ] old settings are using `regexInputs` key but we
need to rename to `regex_inputs`
- - no need for `inputs` middle part. It can stay
+ - [ ] no need for `inputs` middle part. It can stay
directly on `regex_inputs`
"""
regexInputs: RegexInputsModel = Field(
@@ -238,10 +205,6 @@ DEFAULT_IMAGEIO_SETTINGS = {
"OCIO_config": "nuke-default",
"workingSpaceLUT": "linear",
"monitorLut": "sRGB",
- "int8Lut": "sRGB",
- "int16Lut": "sRGB",
- "logLut": "Cineon",
- "floatLut": "linear"
},
"nodes": {
"requiredNodes": [
diff --git a/server_addon/nuke/server/settings/loader_plugins.py b/server_addon/nuke/server/settings/loader_plugins.py
index 6db381bffb..51e2c2149b 100644
--- a/server_addon/nuke/server/settings/loader_plugins.py
+++ b/server_addon/nuke/server/settings/loader_plugins.py
@@ -6,10 +6,6 @@ class LoadImageModel(BaseSettingsModel):
enabled: bool = Field(
title="Enabled"
)
- """# TODO: v3 api used `_representation`
- New api is hiding it so it had to be renamed
- to `representations_include`
- """
representations_include: list[str] = Field(
default_factory=list,
title="Include representations"
@@ -33,10 +29,6 @@ class LoadClipModel(BaseSettingsModel):
enabled: bool = Field(
title="Enabled"
)
- """# TODO: v3 api used `_representation`
- New api is hiding it so it had to be renamed
- to `representations_include`
- """
representations_include: list[str] = Field(
default_factory=list,
title="Include representations"
diff --git a/server_addon/nuke/server/settings/main.py b/server_addon/nuke/server/settings/main.py
index 4687d48ac9..cdaaa3a9e2 100644
--- a/server_addon/nuke/server/settings/main.py
+++ b/server_addon/nuke/server/settings/main.py
@@ -59,9 +59,7 @@ class NukeSettings(BaseSettingsModel):
default_factory=ImageIOSettings,
title="Color Management (imageio)",
)
- """# TODO: fix host api:
- - rename `nuke-dirmap` to `dirmap` was inevitable
- """
+
dirmap: DirmapSettings = Field(
default_factory=DirmapSettings,
title="Nuke Directory Mapping",
diff --git a/server_addon/nuke/server/settings/publish_plugins.py b/server_addon/nuke/server/settings/publish_plugins.py
index 7e898f8c9a..c78685534f 100644
--- a/server_addon/nuke/server/settings/publish_plugins.py
+++ b/server_addon/nuke/server/settings/publish_plugins.py
@@ -28,11 +28,9 @@ def nuke_product_types_enum():
class NodeModel(BaseSettingsModel):
- # TODO: missing in host api
name: str = Field(
title="Node name"
)
- # TODO: `nodeclass` rename to `nuke_node_class`
nodeclass: str = Field(
"",
title="Node class"
@@ -41,11 +39,8 @@ class NodeModel(BaseSettingsModel):
"",
title="Incoming dependency"
)
- """# TODO: Changes in host api:
- - Need complete rework of knob types in nuke integration.
- - We could not support v3 style of settings.
- """
knobs: list[KnobModel] = Field(
+ default_factory=list,
title="Knobs",
)
@@ -99,12 +94,9 @@ class ExtractThumbnailModel(BaseSettingsModel):
use_rendered: bool = Field(title="Use rendered images")
bake_viewer_process: bool = Field(title="Bake view process")
bake_viewer_input_process: bool = Field(title="Bake viewer input process")
- """# TODO: needs to rewrite from v3 to ayon
- - `nodes` in v3 was dict but now `prenodes` is list of dict
- - also later `nodes` should be `prenodes`
- """
nodes: list[NodeModel] = Field(
+ default_factory=list,
title="Nodes (deprecated)"
)
reposition_nodes: list[ThumbnailRepositionNodeModel] = Field(
@@ -177,6 +169,7 @@ class ExtractReviewDataMovModel(BaseSettingsModel):
enabled: bool = Field(title="Enabled")
viewer_lut_raw: bool = Field(title="Viewer lut raw")
outputs: list[BakingStreamModel] = Field(
+ default_factory=list,
title="Baking streams"
)
@@ -213,12 +206,6 @@ class ExctractSlateFrameParamModel(BaseSettingsModel):
class ExtractSlateFrameModel(BaseSettingsModel):
viewer_lut_raw: bool = Field(title="Viewer lut raw")
- """# TODO: v3 api different model:
- - not possible to replicate v3 model:
- {"name": [bool, str]}
- - not it is:
- {"name": {"enabled": bool, "template": str}}
- """
key_value_mapping: ExctractSlateFrameParamModel = Field(
title="Key value mapping",
default_factory=ExctractSlateFrameParamModel
@@ -287,7 +274,6 @@ class PublishPuginsModel(BaseSettingsModel):
title="Extract Slate Frame",
default_factory=ExtractSlateFrameModel
)
- # TODO: plugin should be renamed - `workfile` not `script`
IncrementScriptVersion: IncrementScriptVersionModel = Field(
title="Increment Workfile Version",
default_factory=IncrementScriptVersionModel,
diff --git a/server_addon/nuke/server/settings/scriptsmenu.py b/server_addon/nuke/server/settings/scriptsmenu.py
index 9d1c32ebac..0b2d660da5 100644
--- a/server_addon/nuke/server/settings/scriptsmenu.py
+++ b/server_addon/nuke/server/settings/scriptsmenu.py
@@ -17,7 +17,6 @@ class ScriptsmenuSettings(BaseSettingsModel):
"""Nuke script menu project settings."""
_isGroup = True
- # TODO: in api rename key `name` to `menu_name`
name: str = Field(title="Menu Name")
definition: list[ScriptsmenuSubmodel] = Field(
default_factory=list,
diff --git a/server_addon/nuke/server/settings/templated_workfile_build.py b/server_addon/nuke/server/settings/templated_workfile_build.py
index e0245c8d06..0899be841e 100644
--- a/server_addon/nuke/server/settings/templated_workfile_build.py
+++ b/server_addon/nuke/server/settings/templated_workfile_build.py
@@ -28,6 +28,7 @@ class TemplatedWorkfileProfileModel(BaseSettingsModel):
class TemplatedWorkfileBuildModel(BaseSettingsModel):
+ """Settings for templated workfile builder."""
profiles: list[TemplatedWorkfileProfileModel] = Field(
default_factory=list
)
diff --git a/server_addon/nuke/server/settings/workfile_builder.py b/server_addon/nuke/server/settings/workfile_builder.py
index ee67c7c16a..3ae3b08788 100644
--- a/server_addon/nuke/server/settings/workfile_builder.py
+++ b/server_addon/nuke/server/settings/workfile_builder.py
@@ -48,20 +48,32 @@ class BuilderProfileModel(BaseSettingsModel):
title="Task names"
)
current_context: list[BuilderProfileItemModel] = Field(
- title="Current context")
+ default_factory=list,
+ title="Current context"
+ )
linked_assets: list[BuilderProfileItemModel] = Field(
- title="Linked assets/shots")
+ default_factory=list,
+ title="Linked assets/shots"
+ )
class WorkfileBuilderModel(BaseSettingsModel):
+ """[deprecated] use Template Workfile Build Settings instead.
+ """
create_first_version: bool = Field(
title="Create first workfile")
custom_templates: list[CustomTemplateModel] = Field(
- title="Custom templates")
+ default_factory=list,
+ title="Custom templates"
+ )
builder_on_start: bool = Field(
- title="Run Builder at first workfile")
+ default=False,
+ title="Run Builder at first workfile"
+ )
profiles: list[BuilderProfileModel] = Field(
- title="Builder profiles")
+ default_factory=list,
+ title="Builder profiles"
+ )
DEFAULT_WORKFILE_BUILDER_SETTINGS = {
diff --git a/website/docs/admin_hosts_aftereffects.md b/website/docs/admin_hosts_aftereffects.md
index 974428fe06..72fdb32faf 100644
--- a/website/docs/admin_hosts_aftereffects.md
+++ b/website/docs/admin_hosts_aftereffects.md
@@ -18,6 +18,10 @@ Location: Settings > Project > AfterEffects
## Publish plugins
+### Collect Review
+
+Enable/disable creation of auto instance of review.
+
### Validate Scene Settings
#### Skip Resolution Check for Tasks
@@ -28,6 +32,10 @@ Set regex pattern(s) to look for in a Task name to skip resolution check against
Set regex pattern(s) to look for in a Task name to skip `frameStart`, `frameEnd` check against values from DB.
+### ValidateContainers
+
+By default this validator will look loaded items with lower version than latest. This validator is context wide so it must be disabled in Context button.
+
### AfterEffects Submit to Deadline
* `Use Published scene` - Set to True (green) when Deadline should take published scene as a source instead of uploaded local one.
diff --git a/website/docs/admin_hosts_photoshop.md b/website/docs/admin_hosts_photoshop.md
index de684f01d2..d79789760e 100644
--- a/website/docs/admin_hosts_photoshop.md
+++ b/website/docs/admin_hosts_photoshop.md
@@ -33,7 +33,6 @@ Provides list of [variants](artist_concepts.md#variant) that will be shown to an
Provides simplified publishing process. It will create single `image` instance for artist automatically. This instance will
produce flatten image from all visible layers in a workfile.
-- Subset template for flatten image - provide template for subset name for this instance (example `imageBeauty`)
- Review - should be separate review created for this instance
### Create Review
@@ -111,11 +110,11 @@ Set Byte limit for review file. Applicable if gigantic `image` instances are pro
#### Extract jpg Options
-Handles tags for produced `.jpg` representation. `Create review` and `Add review to Ftrack` are defaults.
+Handles tags for produced `.jpg` representation. `Create review` and `Add review to Ftrack` are defaults.
#### Extract mov Options
-Handles tags for produced `.mov` representation. `Create review` and `Add review to Ftrack` are defaults.
+Handles tags for produced `.mov` representation. `Create review` and `Add review to Ftrack` are defaults.
### Workfile Builder
@@ -124,4 +123,4 @@ Allows to open prepared workfile for an artist when no workfile exists. Useful t
Could be configured per `Task type`, eg. `composition` task type could use different `.psd` template file than `art` task.
Workfile template must be accessible for all artists.
-(Currently not handled by [SiteSync](module_site_sync.md))
\ No newline at end of file
+(Currently not handled by [SiteSync](module_site_sync.md))
diff --git a/website/docs/assets/admin_hosts_photoshop_settings.png b/website/docs/assets/admin_hosts_photoshop_settings.png
index aaa6ecbed7..9478fbedf7 100644
Binary files a/website/docs/assets/admin_hosts_photoshop_settings.png and b/website/docs/assets/admin_hosts_photoshop_settings.png differ