From 851b573a81798a85b61ac6e1bfb20b83e91e8d02 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 18 Aug 2022 21:21:56 +0800 Subject: [PATCH 001/201] add write_color_sets in create_rig and enable options of swtiching on/off for write_color_sets in create_model/rig --- .../hosts/maya/plugins/create/create_model.py | 4 +- .../hosts/maya/plugins/create/create_rig.py | 7 ++- .../defaults/project_settings/maya.json | 4 +- .../schemas/schema_maya_create.json | 60 ++++++++++++++++--- 4 files changed, 61 insertions(+), 14 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_model.py b/openpype/hosts/maya/plugins/create/create_model.py index 37faad23a0..041d3a77e2 100644 --- a/openpype/hosts/maya/plugins/create/create_model.py +++ b/openpype/hosts/maya/plugins/create/create_model.py @@ -9,12 +9,12 @@ class CreateModel(plugin.Creator): family = "model" icon = "cube" defaults = ["Main", "Proxy", "_MD", "_HD", "_LD"] - + write_color_sets = False def __init__(self, *args, **kwargs): super(CreateModel, self).__init__(*args, **kwargs) # Vertex colors with the geometry - self.data["writeColorSets"] = False + self.data["writeColorSets"] = self.write_color_sets self.data["writeFaceSets"] = False # Include attributes by attribute name or prefix diff --git a/openpype/hosts/maya/plugins/create/create_rig.py b/openpype/hosts/maya/plugins/create/create_rig.py index 8032e5fbbd..37fadbe3e1 100644 --- a/openpype/hosts/maya/plugins/create/create_rig.py +++ b/openpype/hosts/maya/plugins/create/create_rig.py @@ -13,13 +13,16 @@ class CreateRig(plugin.Creator): label = "Rig" family = "rig" icon = "wheelchair" + write_color_sets = False + def __init__(self, *args, **kwargs): + super(CreateRig, self).__init__(*args, **kwargs) + self.data["writeColorSets"] = self.write_color_sets def process(self): with lib.undo_chunk(): instance = super(CreateRig, self).process() - self.log.info("Creating Rig instance set up ...") controls = cmds.sets(name="controls_SET", empty=True) pointcache = cmds.sets(name="out_SET", empty=True) - cmds.sets([controls, pointcache], forceElement=instance) + cmds.sets([controls, pointcache], forceElement=instance) \ No newline at end of file diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index ac0f161cf2..4e950aa8b5 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -33,7 +33,7 @@ }, "RenderSettings": { "apply_render_settings": true, - "default_render_image_folder": "", + "default_render_image_folder": "renders", "aov_separator": "underscore", "reset_current_frame": false, "arnold_renderer": { @@ -163,6 +163,7 @@ }, "CreateModel": { "enabled": true, + "write_color_sets": false, "defaults": [ "Main", "Proxy", @@ -183,6 +184,7 @@ }, "CreateRig": { "enabled": true, + "write_color_sets": false, "defaults": [ "Main", "Sim", diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json index 431add28df..b9ef6cb80c 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json @@ -135,6 +135,56 @@ } ] }, + { + "type": "dict", + "collapsible": true, + "key": "CreateModel", + "label": "Create Model", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "write_color_sets", + "label": "Write Color Sets" + }, + { + "type": "list", + "key": "defaults", + "label": "Default Subsets", + "object_type": "text" + } + ] + }, + { + "type": "dict", + "collapsible": true, + "key": "CreateRig", + "label": "Create Rig", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "write_color_sets", + "label": "Write Color Sets" + }, + { + "type": "list", + "key": "defaults", + "label": "Default Subsets", + "object_type": "text" + } + ] + }, { "type": "dict", "collapsible": true, @@ -160,7 +210,7 @@ } ] }, - + { "type": "schema_template", "name": "template_create_plugin", @@ -197,10 +247,6 @@ "key": "CreateMayaScene", "label": "Create Maya Scene" }, - { - "key": "CreateModel", - "label": "Create Model" - }, { "key": "CreateRenderSetup", "label": "Create Render Setup" @@ -209,10 +255,6 @@ "key": "CreateReview", "label": "Create Review" }, - { - "key": "CreateRig", - "label": "Create Rig" - }, { "key": "CreateSetDress", "label": "Create Set Dress" From d90fa8b8fba57992894272827576b9bfd354fd4b Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 18 Aug 2022 21:32:56 +0800 Subject: [PATCH 002/201] add write_color_sets in create_rig and enable options of swtiching on/off for write_color_sets in create_model/rig --- openpype/hosts/maya/plugins/create/create_rig.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/create/create_rig.py b/openpype/hosts/maya/plugins/create/create_rig.py index 37fadbe3e1..9484605076 100644 --- a/openpype/hosts/maya/plugins/create/create_rig.py +++ b/openpype/hosts/maya/plugins/create/create_rig.py @@ -14,6 +14,7 @@ class CreateRig(plugin.Creator): family = "rig" icon = "wheelchair" write_color_sets = False + def __init__(self, *args, **kwargs): super(CreateRig, self).__init__(*args, **kwargs) self.data["writeColorSets"] = self.write_color_sets @@ -25,4 +26,4 @@ class CreateRig(plugin.Creator): self.log.info("Creating Rig instance set up ...") controls = cmds.sets(name="controls_SET", empty=True) pointcache = cmds.sets(name="out_SET", empty=True) - cmds.sets([controls, pointcache], forceElement=instance) \ No newline at end of file + cmds.sets([controls, pointcache], forceElement=instance) From 745386decba851f07fcc72b8e9cac7d758ca4ef9 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 18 Aug 2022 22:12:05 +0800 Subject: [PATCH 003/201] add write_color_sets in create_rig and enable options of swtiching on/off for write_color_sets in create_model/rig --- openpype/hosts/maya/plugins/create/create_rig.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/maya/plugins/create/create_rig.py b/openpype/hosts/maya/plugins/create/create_rig.py index 9484605076..8eb1fab5e0 100644 --- a/openpype/hosts/maya/plugins/create/create_rig.py +++ b/openpype/hosts/maya/plugins/create/create_rig.py @@ -18,6 +18,7 @@ class CreateRig(plugin.Creator): def __init__(self, *args, **kwargs): super(CreateRig, self).__init__(*args, **kwargs) self.data["writeColorSets"] = self.write_color_sets + self.data["writeFaceSets"] = False def process(self): From 31cc50534439315117e7bd68626a5fe807df3f2a Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 19 Aug 2022 20:23:32 +0800 Subject: [PATCH 004/201] add write_color_sets in create_rig and enable options of swtiching on/off for write_color_sets in create_model/rig --- .../maya/plugins/create/create_animation.py | 3 ++- .../hosts/maya/plugins/create/create_model.py | 3 ++- .../maya/plugins/create/create_pointcache.py | 4 +++- .../hosts/maya/plugins/create/create_rig.py | 3 ++- .../defaults/project_settings/maya.json | 4 ++++ .../schemas/schema_maya_create.json | 20 +++++++++++++++++++ 6 files changed, 33 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_animation.py b/openpype/hosts/maya/plugins/create/create_animation.py index e47d4e5b5a..5ef5f61ab1 100644 --- a/openpype/hosts/maya/plugins/create/create_animation.py +++ b/openpype/hosts/maya/plugins/create/create_animation.py @@ -12,6 +12,7 @@ class CreateAnimation(plugin.Creator): family = "animation" icon = "male" write_color_sets = False + write_face_sets = False def __init__(self, *args, **kwargs): super(CreateAnimation, self).__init__(*args, **kwargs) @@ -24,7 +25,7 @@ class CreateAnimation(plugin.Creator): # Write vertex colors with the geometry. self.data["writeColorSets"] = self.write_color_sets - self.data["writeFaceSets"] = False + self.data["writeFaceSets"] = self.write_face_sets # Include only renderable visible shapes. # Skips locators and empty transforms diff --git a/openpype/hosts/maya/plugins/create/create_model.py b/openpype/hosts/maya/plugins/create/create_model.py index 041d3a77e2..520e962f74 100644 --- a/openpype/hosts/maya/plugins/create/create_model.py +++ b/openpype/hosts/maya/plugins/create/create_model.py @@ -10,12 +10,13 @@ class CreateModel(plugin.Creator): icon = "cube" defaults = ["Main", "Proxy", "_MD", "_HD", "_LD"] write_color_sets = False + write_face_sets = False def __init__(self, *args, **kwargs): super(CreateModel, self).__init__(*args, **kwargs) # Vertex colors with the geometry self.data["writeColorSets"] = self.write_color_sets - self.data["writeFaceSets"] = False + self.data["writeFaceSets"] = self.write_face_sets # Include attributes by attribute name or prefix self.data["attr"] = "" diff --git a/openpype/hosts/maya/plugins/create/create_pointcache.py b/openpype/hosts/maya/plugins/create/create_pointcache.py index 5516445de8..ab8fe12079 100644 --- a/openpype/hosts/maya/plugins/create/create_pointcache.py +++ b/openpype/hosts/maya/plugins/create/create_pointcache.py @@ -12,6 +12,7 @@ class CreatePointCache(plugin.Creator): family = "pointcache" icon = "gears" write_color_sets = False + write_face_sets = False def __init__(self, *args, **kwargs): super(CreatePointCache, self).__init__(*args, **kwargs) @@ -21,7 +22,8 @@ class CreatePointCache(plugin.Creator): # Vertex colors with the geometry. self.data["writeColorSets"] = self.write_color_sets - self.data["writeFaceSets"] = False # Vertex colors with the geometry. + # Vertex colors with the geometry. + self.data["writeFaceSets"] = self.write_face_sets self.data["renderableOnly"] = False # Only renderable visible shapes self.data["visibleOnly"] = False # only nodes that are visible self.data["includeParentHierarchy"] = False # Include parent groups diff --git a/openpype/hosts/maya/plugins/create/create_rig.py b/openpype/hosts/maya/plugins/create/create_rig.py index 8eb1fab5e0..3b0ee1e22a 100644 --- a/openpype/hosts/maya/plugins/create/create_rig.py +++ b/openpype/hosts/maya/plugins/create/create_rig.py @@ -14,11 +14,12 @@ class CreateRig(plugin.Creator): family = "rig" icon = "wheelchair" write_color_sets = False + write_face_sets = False def __init__(self, *args, **kwargs): super(CreateRig, self).__init__(*args, **kwargs) self.data["writeColorSets"] = self.write_color_sets - self.data["writeFaceSets"] = False + self.data["writeFaceSets"] = self.write_face_sets def process(self): diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 4e950aa8b5..b4164c63f0 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -102,6 +102,7 @@ "CreateAnimation": { "enabled": true, "write_color_sets": false, + "write_face_sets": false, "defaults": [ "Main" ] @@ -109,6 +110,7 @@ "CreatePointCache": { "enabled": true, "write_color_sets": false, + "write_face_sets": false, "defaults": [ "Main" ] @@ -164,6 +166,7 @@ "CreateModel": { "enabled": true, "write_color_sets": false, + "write_face_sets": false, "defaults": [ "Main", "Proxy", @@ -185,6 +188,7 @@ "CreateRig": { "enabled": true, "write_color_sets": false, + "write_face_sets": false, "defaults": [ "Main", "Sim", diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json index b9ef6cb80c..7e12897336 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json @@ -127,6 +127,11 @@ "key": "write_color_sets", "label": "Write Color Sets" }, + { + "type": "boolean", + "key": "write_face_sets", + "label": "Write Face Sets" + }, { "type": "list", "key": "defaults", @@ -152,6 +157,11 @@ "key": "write_color_sets", "label": "Write Color Sets" }, + { + "type": "boolean", + "key": "write_face_sets", + "label": "Write Face Sets" + }, { "type": "list", "key": "defaults", @@ -177,6 +187,11 @@ "key": "write_color_sets", "label": "Write Color Sets" }, + { + "type": "boolean", + "key": "write_face_sets", + "label": "Write Face Sets" + }, { "type": "list", "key": "defaults", @@ -202,6 +217,11 @@ "key": "write_color_sets", "label": "Write Color Sets" }, + { + "type": "boolean", + "key": "write_face_sets", + "label": "Write Face Sets" + }, { "type": "list", "key": "defaults", From c69736a597cf5b20d6090a5c20f0d05494679852 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 23 Aug 2022 10:52:07 +0200 Subject: [PATCH 005/201] tvpaint is installed as object ingeriting HostBase --- openpype/hosts/tvpaint/api/__init__.py | 12 +- openpype/hosts/tvpaint/api/launch_script.py | 7 +- openpype/hosts/tvpaint/api/pipeline.py | 214 ++++++++++++-------- 3 files changed, 131 insertions(+), 102 deletions(-) diff --git a/openpype/hosts/tvpaint/api/__init__.py b/openpype/hosts/tvpaint/api/__init__.py index 43d411d8f9..b07658c583 100644 --- a/openpype/hosts/tvpaint/api/__init__.py +++ b/openpype/hosts/tvpaint/api/__init__.py @@ -5,11 +5,7 @@ from . import workio from . import pipeline from . import plugin from .pipeline import ( - install, - maintained_selection, - remove_instance, - list_instances, - ls + TVPaintHost, ) from .workio import ( @@ -31,11 +27,7 @@ __all__ = ( "pipeline", "plugin", - "install", - "maintained_selection", - "remove_instance", - "list_instances", - "ls", + "TVPaintHost", # Workfiles API "open_file", diff --git a/openpype/hosts/tvpaint/api/launch_script.py b/openpype/hosts/tvpaint/api/launch_script.py index 0b25027fc6..c474a10529 100644 --- a/openpype/hosts/tvpaint/api/launch_script.py +++ b/openpype/hosts/tvpaint/api/launch_script.py @@ -10,10 +10,10 @@ from Qt import QtWidgets, QtCore, QtGui from openpype import style from openpype.pipeline import install_host -from openpype.hosts.tvpaint.api.communication_server import ( - CommunicationWrapper +from openpype.hosts.tvpaint.api import ( + TVPaintHost, + CommunicationWrapper, ) -from openpype.hosts.tvpaint import api as tvpaint_host log = logging.getLogger(__name__) @@ -30,6 +30,7 @@ def main(launch_args): # - QApplicaiton is also main thread/event loop of the server qt_app = QtWidgets.QApplication([]) + tvpaint_host = TVPaintHost() # Execute pipeline installation install_host(tvpaint_host) diff --git a/openpype/hosts/tvpaint/api/pipeline.py b/openpype/hosts/tvpaint/api/pipeline.py index 427c927264..6c90de2aa9 100644 --- a/openpype/hosts/tvpaint/api/pipeline.py +++ b/openpype/hosts/tvpaint/api/pipeline.py @@ -1,6 +1,5 @@ import os import json -import contextlib import tempfile import logging @@ -9,7 +8,8 @@ import requests import pyblish.api from openpype.client import get_project, get_asset_by_name -from openpype.hosts import tvpaint +from openpype.host import HostBase, IWorkfileHost, ILoadHost +from openpype.hosts.tvpaint import TVPAINT_ROOT_DIR from openpype.api import get_current_project_settings from openpype.lib import register_event_callback from openpype.pipeline import ( @@ -26,11 +26,6 @@ from .lib import ( log = logging.getLogger(__name__) -HOST_DIR = os.path.dirname(os.path.abspath(tvpaint.__file__)) -PLUGINS_DIR = os.path.join(HOST_DIR, "plugins") -PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") -LOAD_PATH = os.path.join(PLUGINS_DIR, "load") -CREATE_PATH = os.path.join(PLUGINS_DIR, "create") METADATA_SECTION = "avalon" SECTION_NAME_CONTEXT = "context" @@ -63,30 +58,132 @@ instances=2 """ -def install(): - """Install TVPaint-specific functionality.""" +class TVPaintHost(HostBase, IWorkfileHost, ILoadHost): + name = "tvpaint" - log.info("OpenPype - Installing TVPaint integration") - legacy_io.install() + def install(self): + """Install TVPaint-specific functionality.""" - # Create workdir folder if does not exist yet - workdir = legacy_io.Session["AVALON_WORKDIR"] - if not os.path.exists(workdir): - os.makedirs(workdir) + log.info("OpenPype - Installing TVPaint integration") + legacy_io.install() - pyblish.api.register_host("tvpaint") - pyblish.api.register_plugin_path(PUBLISH_PATH) - register_loader_plugin_path(LOAD_PATH) - register_creator_plugin_path(CREATE_PATH) + # Create workdir folder if does not exist yet + workdir = legacy_io.Session["AVALON_WORKDIR"] + if not os.path.exists(workdir): + os.makedirs(workdir) - registered_callbacks = ( - pyblish.api.registered_callbacks().get("instanceToggled") or [] - ) - if on_instance_toggle not in registered_callbacks: - pyblish.api.register_callback("instanceToggled", on_instance_toggle) + plugins_dir = os.path.join(TVPAINT_ROOT_DIR, "plugins") + publish_dir = os.path.join(plugins_dir, "publish") + load_dir = os.path.join(plugins_dir, "load") + create_dir = os.path.join(plugins_dir, "create") - register_event_callback("application.launched", initial_launch) - register_event_callback("application.exit", application_exit) + pyblish.api.register_host("tvpaint") + pyblish.api.register_plugin_path(publish_dir) + register_loader_plugin_path(load_dir) + register_creator_plugin_path(create_dir) + + registered_callbacks = ( + pyblish.api.registered_callbacks().get("instanceToggled") or [] + ) + if self.on_instance_toggle not in registered_callbacks: + pyblish.api.register_callback( + "instanceToggled", self.on_instance_toggle + ) + + register_event_callback("application.launched", self.initial_launch) + register_event_callback("application.exit", self.application_exit) + + def open_workfile(self, filepath): + george_script = "tv_LoadProject '\"'\"{}\"'\"'".format( + filepath.replace("\\", "/") + ) + return execute_george_through_file(george_script) + + def save_workfile(self, filepath=None): + if not filepath: + filepath = self.get_current_workfile() + context = { + "project": legacy_io.Session["AVALON_PROJECT"], + "asset": legacy_io.Session["AVALON_ASSET"], + "task": legacy_io.Session["AVALON_TASK"] + } + save_current_workfile_context(context) + + # Execute george script to save workfile. + george_script = "tv_SaveProject {}".format(filepath.replace("\\", "/")) + return execute_george(george_script) + + def work_root(self, session): + return session["AVALON_WORKDIR"] + + def get_current_workfile(self): + return execute_george("tv_GetProjectName") + + def workfile_has_unsaved_changes(self): + return None + + def get_workfile_extensions(self): + return [".tvpp"] + + def get_containers(self): + return get_containers() + + def initial_launch(self): + # Setup project settings if its the template that's launched. + # TODO also check for template creation when it's possible to define + # templates + last_workfile = os.environ.get("AVALON_LAST_WORKFILE") + if not last_workfile or os.path.exists(last_workfile): + return + + log.info("Setting up project...") + set_context_settings() + + def application_exit(self): + """Logic related to TimerManager. + + Todo: + This should be handled out of TVPaint integration logic. + """ + + data = get_current_project_settings() + stop_timer = data["tvpaint"]["stop_timer_on_application_exit"] + + if not stop_timer: + return + + # Stop application timer. + webserver_url = os.environ.get("OPENPYPE_WEBSERVER_URL") + rest_api_url = "{}/timers_manager/stop_timer".format(webserver_url) + requests.post(rest_api_url) + + def on_instance_toggle(self, instance, old_value, new_value): + """Update instance data in workfile on publish toggle.""" + # Review may not have real instance in wokrfile metadata + if not instance.data.get("uuid"): + return + + instance_id = instance.data["uuid"] + found_idx = None + current_instances = list_instances() + for idx, workfile_instance in enumerate(current_instances): + if workfile_instance["uuid"] == instance_id: + found_idx = idx + break + + if found_idx is None: + return + + if "active" in current_instances[found_idx]: + current_instances[found_idx]["active"] = new_value + self.write_instances(current_instances) + + def list_instances(self): + """List all created instances from current workfile.""" + return list_instances() + + def write_instances(self, data): + return write_instances(data) def containerise( @@ -116,7 +213,7 @@ def containerise( "representation": str(context["representation"]["_id"]) } if current_containers is None: - current_containers = ls() + current_containers = get_containers() # Add container to containers list current_containers.append(container_data) @@ -127,15 +224,6 @@ def containerise( return container_data -@contextlib.contextmanager -def maintained_selection(): - # TODO implement logic - try: - yield - finally: - pass - - def split_metadata_string(text, chunk_length=None): """Split string by length. @@ -359,12 +447,7 @@ def write_instances(data): return write_workfile_metadata(SECTION_NAME_INSTANCES, data) -# Backwards compatibility -def _write_instances(*args, **kwargs): - return write_instances(*args, **kwargs) - - -def ls(): +def get_containers(): output = get_workfile_metadata(SECTION_NAME_CONTAINERS) if output: for item in output: @@ -376,53 +459,6 @@ def ls(): return output -def on_instance_toggle(instance, old_value, new_value): - """Update instance data in workfile on publish toggle.""" - # Review may not have real instance in wokrfile metadata - if not instance.data.get("uuid"): - return - - instance_id = instance.data["uuid"] - found_idx = None - current_instances = list_instances() - for idx, workfile_instance in enumerate(current_instances): - if workfile_instance["uuid"] == instance_id: - found_idx = idx - break - - if found_idx is None: - return - - if "active" in current_instances[found_idx]: - current_instances[found_idx]["active"] = new_value - write_instances(current_instances) - - -def initial_launch(): - # Setup project settings if its the template that's launched. - # TODO also check for template creation when it's possible to define - # templates - last_workfile = os.environ.get("AVALON_LAST_WORKFILE") - if not last_workfile or os.path.exists(last_workfile): - return - - log.info("Setting up project...") - set_context_settings() - - -def application_exit(): - data = get_current_project_settings() - stop_timer = data["tvpaint"]["stop_timer_on_application_exit"] - - if not stop_timer: - return - - # Stop application timer. - webserver_url = os.environ.get("OPENPYPE_WEBSERVER_URL") - rest_api_url = "{}/timers_manager/stop_timer".format(webserver_url) - requests.post(rest_api_url) - - def set_context_settings(asset_doc=None): """Set workfile settings by asset document data. From 0b473de76bbe3548a2d43cba7c083a415b5d7f78 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 23 Aug 2022 10:53:27 +0200 Subject: [PATCH 006/201] changed imports in plugin logic --- openpype/hosts/tvpaint/api/plugin.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/tvpaint/api/plugin.py b/openpype/hosts/tvpaint/api/plugin.py index 15ad8905e0..da456e7067 100644 --- a/openpype/hosts/tvpaint/api/plugin.py +++ b/openpype/hosts/tvpaint/api/plugin.py @@ -4,11 +4,11 @@ import uuid from openpype.pipeline import ( LegacyCreator, LoaderPlugin, + registered_host, ) -from openpype.hosts.tvpaint.api import ( - pipeline, - lib -) + +from .lib import get_layers_data +from .pipeline import get_current_workfile_context class Creator(LegacyCreator): @@ -22,7 +22,7 @@ class Creator(LegacyCreator): dynamic_data = super(Creator, cls).get_dynamic_data(*args, **kwargs) # Change asset and name by current workfile context - workfile_context = pipeline.get_current_workfile_context() + workfile_context = get_current_workfile_context() asset_name = workfile_context.get("asset") task_name = workfile_context.get("task") if "asset" not in dynamic_data and asset_name: @@ -67,10 +67,12 @@ class Creator(LegacyCreator): self.log.debug( "Storing instance data to workfile. {}".format(str(data)) ) - return pipeline.write_instances(data) + host = registered_host() + return host.write_instances(data) def process(self): - data = pipeline.list_instances() + host = registered_host() + data = host.list_instances() data.append(self.data) self.write_instances(data) @@ -108,7 +110,7 @@ class Loader(LoaderPlugin): counter_regex = re.compile(r"_(\d{3})$") higher_counter = 0 - for layer in lib.get_layers_data(): + for layer in get_layers_data(): layer_name = layer["name"] if not layer_name.startswith(layer_name_base): continue From bcad1ab7fd120938593d59d35fc39cae2677cbc8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 23 Aug 2022 10:53:43 +0200 Subject: [PATCH 007/201] changed import of CommunicationsWrapper --- openpype/hosts/tvpaint/api/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/tvpaint/api/lib.py b/openpype/hosts/tvpaint/api/lib.py index a341f48859..5e64773b8e 100644 --- a/openpype/hosts/tvpaint/api/lib.py +++ b/openpype/hosts/tvpaint/api/lib.py @@ -2,7 +2,7 @@ import os import logging import tempfile -from . import CommunicationWrapper +from .communication_server import CommunicationWrapper log = logging.getLogger(__name__) From 88b900bda06e7b13bee7e62a2185cb4eefab3c65 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 23 Aug 2022 10:54:58 +0200 Subject: [PATCH 008/201] use explicit imports --- .../plugins/create/create_render_layer.py | 22 +++++---- .../plugins/create/create_render_pass.py | 10 ++-- .../hosts/tvpaint/plugins/load/load_image.py | 5 +- .../plugins/load/load_reference_image.py | 41 +++++++++++----- .../hosts/tvpaint/plugins/load/load_sound.py | 7 ++- .../tvpaint/plugins/load/load_workfile.py | 21 +++++--- .../plugins/publish/collect_workfile_data.py | 49 ++++++++++++------- .../plugins/publish/extract_sequence.py | 20 +++++--- .../publish/increment_workfile_version.py | 7 +-- .../plugins/publish/validate_asset_name.py | 9 ++-- .../tvpaint/plugins/publish/validate_marks.py | 6 +-- .../plugins/publish/validate_start_frame.py | 6 +-- .../publish/validate_workfile_metadata.py | 6 +-- 13 files changed, 132 insertions(+), 77 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/create/create_render_layer.py b/openpype/hosts/tvpaint/plugins/create/create_render_layer.py index 3b5bd47189..a085830e96 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render_layer.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render_layer.py @@ -1,11 +1,15 @@ -from openpype.pipeline import CreatorError from openpype.lib import prepare_template_data +from openpype.pipeline import CreatorError from openpype.hosts.tvpaint.api import ( plugin, - pipeline, - lib, CommunicationWrapper ) +from openpype.hosts.tvpaint.api.lib import ( + get_layers_data, + get_groups_data, + execute_george_through_file, +) +from openpype.hosts.tvpaint.api.pipeline import list_instances class CreateRenderlayer(plugin.Creator): @@ -63,7 +67,7 @@ class CreateRenderlayer(plugin.Creator): # Validate that communication is initialized if CommunicationWrapper.communicator: # Get currently selected layers - layers_data = lib.get_layers_data() + layers_data = get_layers_data() selected_layers = [ layer @@ -81,8 +85,8 @@ class CreateRenderlayer(plugin.Creator): def process(self): self.log.debug("Query data from workfile.") - instances = pipeline.list_instances() - layers_data = lib.get_layers_data() + instances = list_instances() + layers_data = get_layers_data() self.log.debug("Checking for selection groups.") # Collect group ids from selection @@ -109,7 +113,7 @@ class CreateRenderlayer(plugin.Creator): self.log.debug(f"Selected group id is \"{group_id}\".") self.data["group_id"] = group_id - group_data = lib.get_groups_data() + group_data = get_groups_data() group_name = None for group in group_data: if group["group_id"] == group_id: @@ -176,7 +180,7 @@ class CreateRenderlayer(plugin.Creator): return self.log.debug("Querying groups data from workfile.") - groups_data = lib.get_groups_data() + groups_data = get_groups_data() self.log.debug("Changing name of the group.") selected_group = None @@ -195,7 +199,7 @@ class CreateRenderlayer(plugin.Creator): b=selected_group["blue"], name=new_group_name ) - lib.execute_george_through_file(rename_script) + execute_george_through_file(rename_script) self.log.info( f"Name of group with index {group_id}" diff --git a/openpype/hosts/tvpaint/plugins/create/create_render_pass.py b/openpype/hosts/tvpaint/plugins/create/create_render_pass.py index 26fa8ac51a..a44cb29f20 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render_pass.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render_pass.py @@ -2,10 +2,10 @@ from openpype.pipeline import CreatorError from openpype.lib import prepare_template_data from openpype.hosts.tvpaint.api import ( plugin, - pipeline, - lib, CommunicationWrapper ) +from openpype.hosts.tvpaint.api.lib import get_layers_data +from openpype.hosts.tvpaint.api.pipeline import list_instances class CreateRenderPass(plugin.Creator): @@ -54,7 +54,7 @@ class CreateRenderPass(plugin.Creator): # Validate that communication is initialized if CommunicationWrapper.communicator: # Get currently selected layers - layers_data = lib.layers_data() + layers_data = get_layers_data() selected_layers = [ layer @@ -72,8 +72,8 @@ class CreateRenderPass(plugin.Creator): def process(self): self.log.debug("Query data from workfile.") - instances = pipeline.list_instances() - layers_data = lib.layers_data() + instances = list_instances() + layers_data = get_layers_data() self.log.debug("Checking selection.") # Get all selected layers and their group ids diff --git a/openpype/hosts/tvpaint/plugins/load/load_image.py b/openpype/hosts/tvpaint/plugins/load/load_image.py index f861d0119e..151db94135 100644 --- a/openpype/hosts/tvpaint/plugins/load/load_image.py +++ b/openpype/hosts/tvpaint/plugins/load/load_image.py @@ -1,5 +1,6 @@ import qargparse -from openpype.hosts.tvpaint.api import lib, plugin +from openpype.hosts.tvpaint.api import plugin +from openpype.hosts.tvpaint.api.lib import execute_george_through_file class ImportImage(plugin.Loader): @@ -79,4 +80,4 @@ class ImportImage(plugin.Loader): layer_name, load_options_str ) - return lib.execute_george_through_file(george_script) + return execute_george_through_file(george_script) diff --git a/openpype/hosts/tvpaint/plugins/load/load_reference_image.py b/openpype/hosts/tvpaint/plugins/load/load_reference_image.py index af1a4a9b6b..393236fba6 100644 --- a/openpype/hosts/tvpaint/plugins/load/load_reference_image.py +++ b/openpype/hosts/tvpaint/plugins/load/load_reference_image.py @@ -1,7 +1,21 @@ import collections + import qargparse -from openpype.pipeline import get_representation_context -from openpype.hosts.tvpaint.api import lib, pipeline, plugin + +from openpype.pipeline import ( + get_representation_context, + register_host, +) +from openpype.hosts.tvpaint.api import plugin +from openpype.hosts.tvpaint.api.lib import ( + get_layers_data, + execute_george_through_file, +) +from openpype.hosts.tvpaint.api.pipeline import ( + write_workfile_metadata, + SECTION_NAME_CONTAINERS, + containerise, +) class LoadImage(plugin.Loader): @@ -79,10 +93,10 @@ class LoadImage(plugin.Loader): load_options_str ) - lib.execute_george_through_file(george_script) + execute_george_through_file(george_script) loaded_layer = None - layers = lib.layers_data() + layers = get_layers_data() for layer in layers: if layer["name"] == layer_name: loaded_layer = layer @@ -95,7 +109,7 @@ class LoadImage(plugin.Loader): layer_names = [loaded_layer["name"]] namespace = namespace or layer_name - return pipeline.containerise( + return containerise( name=name, namespace=namespace, members=layer_names, @@ -109,7 +123,7 @@ class LoadImage(plugin.Loader): return if layers is None: - layers = lib.layers_data() + layers = get_layers_data() available_ids = set(layer["layer_id"] for layer in layers) @@ -152,14 +166,15 @@ class LoadImage(plugin.Loader): line = "tv_layerkill {}".format(layer_id) george_script_lines.append(line) george_script = "\n".join(george_script_lines) - lib.execute_george_through_file(george_script) + execute_george_through_file(george_script) def _remove_container(self, container, members=None): if not container: return representation = container["representation"] members = self.get_members_from_container(container) - current_containers = pipeline.ls() + host = register_host() + current_containers = host.get_containers() pop_idx = None for idx, cur_con in enumerate(current_containers): cur_members = self.get_members_from_container(cur_con) @@ -179,8 +194,8 @@ class LoadImage(plugin.Loader): return current_containers.pop(pop_idx) - pipeline.write_workfile_metadata( - pipeline.SECTION_NAME_CONTAINERS, current_containers + write_workfile_metadata( + SECTION_NAME_CONTAINERS, current_containers ) def remove(self, container): @@ -214,7 +229,7 @@ class LoadImage(plugin.Loader): break old_layers = [] - layers = lib.layers_data() + layers = get_layers_data() previous_layer_ids = set(layer["layer_id"] for layer in layers) if old_layers_are_ids: for layer in layers: @@ -263,7 +278,7 @@ class LoadImage(plugin.Loader): new_container = self.load(context, name, namespace, {}) new_layer_names = self.get_members_from_container(new_container) - layers = lib.layers_data() + layers = get_layers_data() new_layers = [] for layer in layers: @@ -304,4 +319,4 @@ class LoadImage(plugin.Loader): # Execute george scripts if there are any if george_script_lines: george_script = "\n".join(george_script_lines) - lib.execute_george_through_file(george_script) + execute_george_through_file(george_script) diff --git a/openpype/hosts/tvpaint/plugins/load/load_sound.py b/openpype/hosts/tvpaint/plugins/load/load_sound.py index 3f42370f5c..f312db262a 100644 --- a/openpype/hosts/tvpaint/plugins/load/load_sound.py +++ b/openpype/hosts/tvpaint/plugins/load/load_sound.py @@ -1,6 +1,9 @@ import os import tempfile -from openpype.hosts.tvpaint.api import lib, plugin +from openpype.hosts.tvpaint.api import plugin +from openpype.hosts.tvpaint.api.lib import ( + execute_george_through_file, +) class ImportSound(plugin.Loader): @@ -64,7 +67,7 @@ class ImportSound(plugin.Loader): ) self.log.info("*** George script:\n{}\n***".format(george_script)) # Execute geoge script - lib.execute_george_through_file(george_script) + execute_george_through_file(george_script) # Read output file lines = [] diff --git a/openpype/hosts/tvpaint/plugins/load/load_workfile.py b/openpype/hosts/tvpaint/plugins/load/load_workfile.py index a99b300730..fc7588f56e 100644 --- a/openpype/hosts/tvpaint/plugins/load/load_workfile.py +++ b/openpype/hosts/tvpaint/plugins/load/load_workfile.py @@ -11,7 +11,13 @@ from openpype.pipeline.workfile import ( get_last_workfile_with_version, ) from openpype.pipeline.template_data import get_template_data_with_names -from openpype.hosts.tvpaint.api import lib, pipeline, plugin +from openpype.hosts.tvpaint.api import plugin +from openpype.hosts.tvpaint.api.lib import ( + execute_george_through_file, +) +from openpype.hosts.tvpaint.api.pipeline import ( + get_current_workfile_context, +) class LoadWorkfile(plugin.Loader): @@ -26,9 +32,9 @@ class LoadWorkfile(plugin.Loader): # Load context of current workfile as first thing # - which context and extension has host = registered_host() - current_file = host.current_file() + current_file = host.get_current_workfile() - context = pipeline.get_current_workfile_context() + context = get_current_workfile_context() filepath = self.fname.replace("\\", "/") @@ -40,7 +46,7 @@ class LoadWorkfile(plugin.Loader): george_script = "tv_LoadProject '\"'\"{}\"'\"'".format( filepath ) - lib.execute_george_through_file(george_script) + execute_george_through_file(george_script) # Save workfile. host_name = "tvpaint" @@ -69,12 +75,13 @@ class LoadWorkfile(plugin.Loader): file_template = anatomy.templates[template_key]["file"] # Define saving file extension + extensions = host.get_workfile_extensions() if current_file: # Match the extension of current file _, extension = os.path.splitext(current_file) else: # Fall back to the first extension supported for this host. - extension = host.file_extensions()[0] + extension = extensions[0] data["ext"] = extension @@ -83,7 +90,7 @@ class LoadWorkfile(plugin.Loader): folder_template, data ) version = get_last_workfile_with_version( - work_root, file_template, data, host.file_extensions() + work_root, file_template, data, extensions )[1] if version is None: @@ -97,4 +104,4 @@ class LoadWorkfile(plugin.Loader): file_template, data ) path = os.path.join(work_root, filename) - host.save_file(path) + host.save_workfile(path) diff --git a/openpype/hosts/tvpaint/plugins/publish/collect_workfile_data.py b/openpype/hosts/tvpaint/plugins/publish/collect_workfile_data.py index c59ef82f85..8fe71a4a46 100644 --- a/openpype/hosts/tvpaint/plugins/publish/collect_workfile_data.py +++ b/openpype/hosts/tvpaint/plugins/publish/collect_workfile_data.py @@ -5,7 +5,22 @@ import tempfile import pyblish.api from openpype.pipeline import legacy_io -from openpype.hosts.tvpaint.api import pipeline, lib +from openpype.hosts.tvpaint.api.lib import ( + execute_george, + execute_george_through_file, + get_layers_data, + get_groups_data, +) +from openpype.hosts.tvpaint.api.pipeline import ( + SECTION_NAME_CONTEXT, + SECTION_NAME_INSTANCES, + SECTION_NAME_CONTAINERS, + + get_workfile_metadata_string, + write_workfile_metadata, + get_current_workfile_context, + list_instances, +) class ResetTVPaintWorkfileMetadata(pyblish.api.Action): @@ -15,12 +30,12 @@ class ResetTVPaintWorkfileMetadata(pyblish.api.Action): def process(self, context, plugin): metadata_keys = { - pipeline.SECTION_NAME_CONTEXT: {}, - pipeline.SECTION_NAME_INSTANCES: [], - pipeline.SECTION_NAME_CONTAINERS: [] + SECTION_NAME_CONTEXT: {}, + SECTION_NAME_INSTANCES: [], + SECTION_NAME_CONTAINERS: [] } for metadata_key, default in metadata_keys.items(): - json_string = pipeline.get_workfile_metadata_string(metadata_key) + json_string = get_workfile_metadata_string(metadata_key) if not json_string: continue @@ -35,7 +50,7 @@ class ResetTVPaintWorkfileMetadata(pyblish.api.Action): ).format(metadata_key, default, json_string), exc_info=True ) - pipeline.write_workfile_metadata(metadata_key, default) + write_workfile_metadata(metadata_key, default) class CollectWorkfileData(pyblish.api.ContextPlugin): @@ -45,8 +60,8 @@ class CollectWorkfileData(pyblish.api.ContextPlugin): actions = [ResetTVPaintWorkfileMetadata] def process(self, context): - current_project_id = lib.execute_george("tv_projectcurrentid") - lib.execute_george("tv_projectselect {}".format(current_project_id)) + current_project_id = execute_george("tv_projectcurrentid") + execute_george("tv_projectselect {}".format(current_project_id)) # Collect and store current context to have reference current_context = { @@ -60,7 +75,7 @@ class CollectWorkfileData(pyblish.api.ContextPlugin): # Collect context from workfile metadata self.log.info("Collecting workfile context") - workfile_context = pipeline.get_current_workfile_context() + workfile_context = get_current_workfile_context() # Store workfile context to pyblish context context.data["workfile_context"] = workfile_context if workfile_context: @@ -96,7 +111,7 @@ class CollectWorkfileData(pyblish.api.ContextPlugin): # Collect instances self.log.info("Collecting instance data from workfile") - instance_data = pipeline.list_instances() + instance_data = list_instances() context.data["workfileInstances"] = instance_data self.log.debug( "Instance data:\"{}".format(json.dumps(instance_data, indent=4)) @@ -104,7 +119,7 @@ class CollectWorkfileData(pyblish.api.ContextPlugin): # Collect information about layers self.log.info("Collecting layers data from workfile") - layers_data = lib.layers_data() + layers_data = get_layers_data() layers_by_name = {} for layer in layers_data: layer_name = layer["name"] @@ -120,14 +135,14 @@ class CollectWorkfileData(pyblish.api.ContextPlugin): # Collect information about groups self.log.info("Collecting groups data from workfile") - group_data = lib.groups_data() + group_data = get_groups_data() context.data["groupsData"] = group_data self.log.debug( "Group data:\"{}".format(json.dumps(group_data, indent=4)) ) self.log.info("Collecting scene data from workfile") - workfile_info_parts = lib.execute_george("tv_projectinfo").split(" ") + workfile_info_parts = execute_george("tv_projectinfo").split(" ") # Project frame start - not used workfile_info_parts.pop(-1) @@ -139,10 +154,10 @@ class CollectWorkfileData(pyblish.api.ContextPlugin): workfile_path = " ".join(workfile_info_parts).replace("\"", "") # Marks return as "{frame - 1} {state} ", example "0 set". - result = lib.execute_george("tv_markin") + result = execute_george("tv_markin") mark_in_frame, mark_in_state, _ = result.split(" ") - result = lib.execute_george("tv_markout") + result = execute_george("tv_markout") mark_out_frame, mark_out_state, _ = result.split(" ") scene_data = { @@ -156,7 +171,7 @@ class CollectWorkfileData(pyblish.api.ContextPlugin): "sceneMarkInState": mark_in_state == "set", "sceneMarkOut": int(mark_out_frame), "sceneMarkOutState": mark_out_state == "set", - "sceneStartFrame": int(lib.execute_george("tv_startframe")), + "sceneStartFrame": int(execute_george("tv_startframe")), "sceneBgColor": self._get_bg_color() } self.log.debug( @@ -188,7 +203,7 @@ class CollectWorkfileData(pyblish.api.ContextPlugin): ] george_script = "\n".join(george_script_lines) - lib.execute_george_through_file(george_script) + execute_george_through_file(george_script) with open(output_filepath, "r") as stream: data = stream.read() diff --git a/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py b/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py index 77712347bd..1ebaf1da64 100644 --- a/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py +++ b/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py @@ -5,7 +5,13 @@ import tempfile from PIL import Image import pyblish.api -from openpype.hosts.tvpaint.api import lib + +from openpype.hosts.tvpaint.api.lib import ( + execute_george, + execute_george_through_file, + get_layers_pre_post_behavior, + get_layers_exposure_frames, +) from openpype.hosts.tvpaint.lib import ( calculate_layers_extraction_data, get_frame_filename_template, @@ -61,7 +67,7 @@ class ExtractSequence(pyblish.api.Extractor): # different way when Start Frame is not `0` # NOTE It will be set back after rendering scene_start_frame = instance.context.data["sceneStartFrame"] - lib.execute_george("tv_startframe 0") + execute_george("tv_startframe 0") # Frame start/end may be stored as float frame_start = int(instance.data["frameStart"]) @@ -113,7 +119,7 @@ class ExtractSequence(pyblish.api.Extractor): output_filepaths_by_frame_idx, thumbnail_fullpath = result # Change scene frame Start back to previous value - lib.execute_george("tv_startframe {}".format(scene_start_frame)) + execute_george("tv_startframe {}".format(scene_start_frame)) # Sequence of one frame if not output_filepaths_by_frame_idx: @@ -241,7 +247,7 @@ class ExtractSequence(pyblish.api.Extractor): george_script_lines.append(" ".join(orig_color_command)) - lib.execute_george_through_file("\n".join(george_script_lines)) + execute_george_through_file("\n".join(george_script_lines)) first_frame_filepath = None output_filepaths_by_frame_idx = {} @@ -304,8 +310,8 @@ class ExtractSequence(pyblish.api.Extractor): return [], None self.log.debug("Collecting pre/post behavior of individual layers.") - behavior_by_layer_id = lib.get_layers_pre_post_behavior(layer_ids) - exposure_frames_by_layer_id = lib.get_layers_exposure_frames( + behavior_by_layer_id = get_layers_pre_post_behavior(layer_ids) + exposure_frames_by_layer_id = get_layers_exposure_frames( layer_ids, layers ) extraction_data_by_layer_id = calculate_layers_extraction_data( @@ -410,7 +416,7 @@ class ExtractSequence(pyblish.api.Extractor): ",".join(frames_to_render), layer_id, layer["name"] )) # Let TVPaint render layer's image - lib.execute_george_through_file("\n".join(george_script_lines)) + execute_george_through_file("\n".join(george_script_lines)) # Fill frames between `frame_start_index` and `frame_end_index` self.log.debug("Filling frames not rendered frames.") diff --git a/openpype/hosts/tvpaint/plugins/publish/increment_workfile_version.py b/openpype/hosts/tvpaint/plugins/publish/increment_workfile_version.py index 24d6558168..a85caf2557 100644 --- a/openpype/hosts/tvpaint/plugins/publish/increment_workfile_version.py +++ b/openpype/hosts/tvpaint/plugins/publish/increment_workfile_version.py @@ -1,7 +1,7 @@ import pyblish.api -from openpype.api import version_up -from openpype.hosts.tvpaint.api import workio +from openpype.lib import version_up +from openpype.pipeline import registered_host class IncrementWorkfileVersion(pyblish.api.ContextPlugin): @@ -17,6 +17,7 @@ class IncrementWorkfileVersion(pyblish.api.ContextPlugin): assert all(result["success"] for result in context.data["results"]), ( "Publishing not successful so version is not increased.") + host = registered_host() path = context.data["currentFile"] - workio.save_file(version_up(path)) + host.save_workfile(version_up(path)) self.log.info('Incrementing workfile version') diff --git a/openpype/hosts/tvpaint/plugins/publish/validate_asset_name.py b/openpype/hosts/tvpaint/plugins/publish/validate_asset_name.py index 70816f9f18..7e35726030 100644 --- a/openpype/hosts/tvpaint/plugins/publish/validate_asset_name.py +++ b/openpype/hosts/tvpaint/plugins/publish/validate_asset_name.py @@ -1,6 +1,9 @@ import pyblish.api from openpype.pipeline import PublishXmlValidationError -from openpype.hosts.tvpaint.api import pipeline +from openpype.hosts.tvpaint.api.pipeline import ( + list_instances, + write_instances, +) class FixAssetNames(pyblish.api.Action): @@ -15,7 +18,7 @@ class FixAssetNames(pyblish.api.Action): def process(self, context, plugin): context_asset_name = context.data["asset"] - old_instance_items = pipeline.list_instances() + old_instance_items = list_instances() new_instance_items = [] for instance_item in old_instance_items: instance_asset_name = instance_item.get("asset") @@ -25,7 +28,7 @@ class FixAssetNames(pyblish.api.Action): ): instance_item["asset"] = context_asset_name new_instance_items.append(instance_item) - pipeline._write_instances(new_instance_items) + write_instances(new_instance_items) class ValidateAssetNames(pyblish.api.ContextPlugin): diff --git a/openpype/hosts/tvpaint/plugins/publish/validate_marks.py b/openpype/hosts/tvpaint/plugins/publish/validate_marks.py index d1f299e006..12d50e17ff 100644 --- a/openpype/hosts/tvpaint/plugins/publish/validate_marks.py +++ b/openpype/hosts/tvpaint/plugins/publish/validate_marks.py @@ -2,7 +2,7 @@ import json import pyblish.api from openpype.pipeline import PublishXmlValidationError -from openpype.hosts.tvpaint.api import lib +from openpype.hosts.tvpaint.api.lib import execute_george class ValidateMarksRepair(pyblish.api.Action): @@ -15,10 +15,10 @@ class ValidateMarksRepair(pyblish.api.Action): def process(self, context, plugin): expected_data = ValidateMarks.get_expected_data(context) - lib.execute_george( + execute_george( "tv_markin {} set".format(expected_data["markIn"]) ) - lib.execute_george( + execute_george( "tv_markout {} set".format(expected_data["markOut"]) ) diff --git a/openpype/hosts/tvpaint/plugins/publish/validate_start_frame.py b/openpype/hosts/tvpaint/plugins/publish/validate_start_frame.py index ddc738c6ed..066e54c670 100644 --- a/openpype/hosts/tvpaint/plugins/publish/validate_start_frame.py +++ b/openpype/hosts/tvpaint/plugins/publish/validate_start_frame.py @@ -1,6 +1,6 @@ import pyblish.api from openpype.pipeline import PublishXmlValidationError -from openpype.hosts.tvpaint.api import lib +from openpype.hosts.tvpaint.api.lib import execute_george class RepairStartFrame(pyblish.api.Action): @@ -11,7 +11,7 @@ class RepairStartFrame(pyblish.api.Action): on = "failed" def process(self, context, plugin): - lib.execute_george("tv_startframe 0") + execute_george("tv_startframe 0") class ValidateStartFrame(pyblish.api.ContextPlugin): @@ -24,7 +24,7 @@ class ValidateStartFrame(pyblish.api.ContextPlugin): optional = True def process(self, context): - start_frame = lib.execute_george("tv_startframe") + start_frame = execute_george("tv_startframe") if start_frame == 0: return diff --git a/openpype/hosts/tvpaint/plugins/publish/validate_workfile_metadata.py b/openpype/hosts/tvpaint/plugins/publish/validate_workfile_metadata.py index eac345f395..d66ae50c60 100644 --- a/openpype/hosts/tvpaint/plugins/publish/validate_workfile_metadata.py +++ b/openpype/hosts/tvpaint/plugins/publish/validate_workfile_metadata.py @@ -1,6 +1,5 @@ import pyblish.api -from openpype.pipeline import PublishXmlValidationError -from openpype.hosts.tvpaint.api import save_file +from openpype.pipeline import PublishXmlValidationError, registered_host class ValidateWorkfileMetadataRepair(pyblish.api.Action): @@ -13,8 +12,9 @@ class ValidateWorkfileMetadataRepair(pyblish.api.Action): def process(self, context, _plugin): """Save current workfile which should trigger storing of metadata.""" current_file = context.data["currentFile"] + host = registered_host() # Save file should trigger - save_file(current_file) + host.save_workfile(current_file) class ValidateWorkfileMetadata(pyblish.api.ContextPlugin): From 326508888727c4626bf60e7469c7efab5ce6483a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 23 Aug 2022 10:55:13 +0200 Subject: [PATCH 009/201] removed unused workio --- openpype/hosts/tvpaint/api/__init__.py | 17 -------- openpype/hosts/tvpaint/api/workio.py | 58 -------------------------- 2 files changed, 75 deletions(-) delete mode 100644 openpype/hosts/tvpaint/api/workio.py diff --git a/openpype/hosts/tvpaint/api/__init__.py b/openpype/hosts/tvpaint/api/__init__.py index b07658c583..5d42a8cc02 100644 --- a/openpype/hosts/tvpaint/api/__init__.py +++ b/openpype/hosts/tvpaint/api/__init__.py @@ -8,15 +8,6 @@ from .pipeline import ( TVPaintHost, ) -from .workio import ( - open_file, - save_file, - current_file, - has_unsaved_changes, - file_extensions, - work_root, -) - __all__ = ( "CommunicationWrapper", @@ -28,12 +19,4 @@ __all__ = ( "plugin", "TVPaintHost", - - # Workfiles API - "open_file", - "save_file", - "current_file", - "has_unsaved_changes", - "file_extensions", - "work_root" ) diff --git a/openpype/hosts/tvpaint/api/workio.py b/openpype/hosts/tvpaint/api/workio.py deleted file mode 100644 index 1a5ad00ca8..0000000000 --- a/openpype/hosts/tvpaint/api/workio.py +++ /dev/null @@ -1,58 +0,0 @@ -"""Host API required for Work Files. -# TODO @iLLiCiT implement functions: - has_unsaved_changes -""" - -from openpype.pipeline import ( - HOST_WORKFILE_EXTENSIONS, - legacy_io, -) -from .lib import ( - execute_george, - execute_george_through_file -) -from .pipeline import save_current_workfile_context - - -def open_file(filepath): - """Open the scene file in Blender.""" - george_script = "tv_LoadProject '\"'\"{}\"'\"'".format( - filepath.replace("\\", "/") - ) - return execute_george_through_file(george_script) - - -def save_file(filepath): - """Save the open scene file.""" - # Store context to workfile before save - context = { - "project": legacy_io.Session["AVALON_PROJECT"], - "asset": legacy_io.Session["AVALON_ASSET"], - "task": legacy_io.Session["AVALON_TASK"] - } - save_current_workfile_context(context) - - # Execute george script to save workfile. - george_script = "tv_SaveProject {}".format(filepath.replace("\\", "/")) - return execute_george(george_script) - - -def current_file(): - """Return the path of the open scene file.""" - george_script = "tv_GetProjectName" - return execute_george(george_script) - - -def has_unsaved_changes(): - """Does the open scene file have unsaved changes?""" - return False - - -def file_extensions(): - """Return the supported file extensions for Blender scene files.""" - return HOST_WORKFILE_EXTENSIONS["tvpaint"] - - -def work_root(session): - """Return the default root to browse for work files.""" - return session["AVALON_WORKDIR"] From dbd983f8c8c35c7d747c247e9593d451b42f6d5d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 23 Aug 2022 10:55:23 +0200 Subject: [PATCH 010/201] cleanup imports in api init file --- openpype/hosts/tvpaint/api/__init__.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/openpype/hosts/tvpaint/api/__init__.py b/openpype/hosts/tvpaint/api/__init__.py index 5d42a8cc02..7b53aad9a4 100644 --- a/openpype/hosts/tvpaint/api/__init__.py +++ b/openpype/hosts/tvpaint/api/__init__.py @@ -1,9 +1,4 @@ from .communication_server import CommunicationWrapper -from . import lib -from . import launch_script -from . import workio -from . import pipeline -from . import plugin from .pipeline import ( TVPaintHost, ) @@ -12,11 +7,5 @@ from .pipeline import ( __all__ = ( "CommunicationWrapper", - "lib", - "launch_script", - "workio", - "pipeline", - "plugin", - "TVPaintHost", ) From c238a9cbc15d516b1d2b662eb6b67aec5d341afa Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 24 Aug 2022 10:51:12 +0100 Subject: [PATCH 011/201] Fix maya extractor for instance_name --- openpype/hosts/maya/plugins/publish/extract_layout.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_layout.py b/openpype/hosts/maya/plugins/publish/extract_layout.py index 991217684a..92ca6c883f 100644 --- a/openpype/hosts/maya/plugins/publish/extract_layout.py +++ b/openpype/hosts/maya/plugins/publish/extract_layout.py @@ -56,7 +56,7 @@ class ExtractLayout(openpype.api.Extractor): json_element = { "family": family, - "instance_name": cmds.getAttr(f"{container}.name"), + "instance_name": cmds.getAttr(f"{container}.namespace"), "representation": str(representation_id), "version": str(version_id) } From e3de88e4fe54f0e483d16841730883a6762a7f85 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 24 Aug 2022 10:52:22 +0100 Subject: [PATCH 012/201] Implemented loader for layouts for existing scenes --- .../plugins/load/load_layout_existing.py | 403 ++++++++++++++++++ 1 file changed, 403 insertions(+) create mode 100644 openpype/hosts/unreal/plugins/load/load_layout_existing.py diff --git a/openpype/hosts/unreal/plugins/load/load_layout_existing.py b/openpype/hosts/unreal/plugins/load/load_layout_existing.py new file mode 100644 index 0000000000..297e8d1a4c --- /dev/null +++ b/openpype/hosts/unreal/plugins/load/load_layout_existing.py @@ -0,0 +1,403 @@ +import json +from pathlib import Path + +import unreal +from unreal import EditorLevelLibrary + +from bson.objectid import ObjectId + +from openpype import pipeline +from openpype.pipeline import ( + discover_loader_plugins, + loaders_from_representation, + load_container, + AVALON_CONTAINER_ID, + legacy_io, +) +from openpype.hosts.unreal.api import plugin +from openpype.hosts.unreal.api import pipeline as upipeline + + +class ExistingLayoutLoader(plugin.Loader): + """ + Load Layout for an existing scene, and match the existing assets. + """ + + families = ["layout"] + representations = ["json"] + + label = "Load Layout on Existing Scene" + icon = "code-fork" + color = "orange" + ASSET_ROOT = "/Game/OpenPype" + + @staticmethod + def _create_container( + asset_name, asset_dir, asset, representation, parent, family + ): + container_name = f"{asset_name}_CON" + + container = None + if not unreal.EditorAssetLibrary.does_asset_exist( + f"{asset_dir}/{container_name}" + ): + container = upipeline.create_container(container_name, asset_dir) + else: + ar = unreal.AssetRegistryHelpers.get_asset_registry() + obj = ar.get_asset_by_object_path( + f"{asset_dir}/{container_name}.{container_name}") + container = obj.get_asset() + + data = { + "schema": "openpype:container-2.0", + "id": AVALON_CONTAINER_ID, + "asset": asset, + "namespace": asset_dir, + "container_name": container_name, + "asset_name": asset_name, + # "loader": str(self.__class__.__name__), + "representation": representation, + "parent": parent, + "family": family + } + + upipeline.imprint( + "{}/{}".format(asset_dir, container_name), data) + + return container.get_path_name() + + @staticmethod + def _get_current_level(): + ue_version = unreal.SystemLibrary.get_engine_version().split('.') + ue_major = ue_version[0] + + if ue_major == '4': + return EditorLevelLibrary.get_editor_world() + elif ue_major == '5': + return unreal.LevelEditorSubsystem().get_current_level() + + raise NotImplementedError( + f"Unreal version {ue_major} not supported") + + @staticmethod + def _transform_from_basis(transform, basis, conversion): + """Transform a transform from a basis to a new basis.""" + # Get the basis matrix + basis_matrix = unreal.Matrix( + basis[0], + basis[1], + basis[2], + basis[3] + ) + transform_matrix = unreal.Matrix( + transform[0], + transform[1], + transform[2], + transform[3] + ) + + new_transform = ( + basis_matrix.get_inverse() * transform_matrix * basis_matrix) + + return conversion.inverse() * new_transform.transform() + + def _get_transform(self, ext, import_data, lasset): + conversion = unreal.Matrix.IDENTITY.transform() + + # Check for the conversion settings. We cannot access + # the alembic conversion settings, so we assume that + # the maya ones have been applied. + if ext == '.fbx': + loc = import_data.import_translation + rot = import_data.import_rotation.to_vector() + scale = import_data.import_scale + conversion = unreal.Transform( + location=[loc.x, loc.y, loc.z], + rotation=[rot.x, rot.y, rot.z], + scale=[scale, scale, scale] + ) + elif ext == '.abc': + # This is the standard conversion settings for + # alembic files from Maya. + conversion = unreal.Transform( + location=[0.0, 0.0, 0.0], + rotation=[0.0, 0.0, 0.0], + scale=[1.0, -1.0, 1.0] + ) + + transform = self._transform_from_basis( + lasset.get('transform_matrix'), + lasset.get('basis'), + conversion + ) + return transform + + @staticmethod + def _get_fbx_loader(loaders, family): + name = "" + if family == 'rig': + name = "SkeletalMeshFBXLoader" + elif family == 'model': + name = "StaticMeshFBXLoader" + elif family == 'camera': + name = "CameraLoader" + + if name == "": + return None + + for loader in loaders: + if loader.__name__ == name: + return loader + + return None + + @staticmethod + def _get_abc_loader(loaders, family): + name = "" + if family == 'rig': + name = "SkeletalMeshAlembicLoader" + elif family == 'model': + name = "StaticMeshAlembicLoader" + + if name == "": + return None + + for loader in loaders: + if loader.__name__ == name: + return loader + + return None + + def _load_asset(self, representation, version, instance_name, family): + valid_formats = ['fbx', 'abc'] + + repr_data = legacy_io.find_one({ + "type": "representation", + "parent": ObjectId(version), + "name": {"$in": valid_formats} + }) + repr_format = repr_data.get('name') + + all_loaders = discover_loader_plugins() + loaders = loaders_from_representation( + all_loaders, representation) + + loader = None + + if repr_format == 'fbx': + loader = self._get_fbx_loader(loaders, family) + elif repr_format == 'abc': + loader = self._get_abc_loader(loaders, family) + + if not loader: + raise AssertionError(f"No valid loader found for {representation}") + + assets = load_container( + loader, + representation, + namespace=instance_name + ) + + return assets + + def load(self, context, name, namespace, options): + print("Loading Layout and Match Assets") + + ar = unreal.AssetRegistryHelpers.get_asset_registry() + + asset = context.get('asset').get('name') + container_name = f"{asset}_{name}_CON" + + actors = EditorLevelLibrary.get_all_level_actors() + + with open(self.fname, "r") as fp: + data = json.load(fp) + + layout_data = [] + + # Get all the representations in the JSON from the database. + for element in data: + if element.get('representation'): + layout_data.append(( + pipeline.legacy_io.find_one({ + "_id": ObjectId(element.get('representation')) + }), + element + )) + + containers = [] + actors_matched = [] + + for (repr_data, lasset) in layout_data: + if not repr_data: + raise AssertionError("Representation not found") + if not (repr_data.get('data') or repr_data.get('data').get('path')): + raise AssertionError("Representation does not have path") + if not repr_data.get('context'): + raise AssertionError("Representation does not have context") + + # For every actor in the scene, check if it has a representation in + # those we got from the JSON. If so, create a container for it. + # Otherwise, remove it from the scene. + found = False + + for actor in actors: + if not actor.get_class().get_name() == 'StaticMeshActor': + continue + if actor in actors_matched: + continue + + # Get the original path of the file from which the asset has + # been imported. + actor.set_actor_label(lasset.get('instance_name')) + smc = actor.get_editor_property('static_mesh_component') + mesh = smc.get_editor_property('static_mesh') + import_data = mesh.get_editor_property('asset_import_data') + filename = import_data.get_first_filename() + path = Path(filename) + + if not path.name in repr_data.get('data').get('path'): + continue + + asset_name = path.with_suffix('').name + mesh_path = Path(mesh.get_path_name()).parent.as_posix() + + # Create the container for the asset. + asset = repr_data.get('context').get('asset') + subset = repr_data.get('context').get('subset') + container = self._create_container( + f"{asset}_{subset}", mesh_path, asset, + repr_data.get('_id'), repr_data.get('parent'), + repr_data.get('context').get('family') + ) + containers.append(container) + + # Set the transform for the actor. + transform = self._get_transform( + path.suffix, import_data, lasset) + actor.set_actor_transform(transform, False, True) + + actors_matched.append(actor) + found = True + break + + # If an actor has not been found for this representation, + # we check if it has been loaded already by checking all the + # loaded containers. If so, we add it to the scene. Otherwise, + # we load it. + if found: + continue + + all_containers = upipeline.ls() + + loaded = False + + for container in all_containers: + repr = container.get('representation') + + if not repr == str(repr_data.get('_id')): + continue + + asset_dir = container.get('namespace') + + filter = unreal.ARFilter( + class_names=["StaticMesh"], + package_paths=[asset_dir], + recursive_paths=False) + assets = ar.get_assets(filter) + + for asset in assets: + obj = asset.get_asset() + actor = EditorLevelLibrary.spawn_actor_from_object( + obj, unreal.Vector(0.0, 0.0, 0.0) + ) + + actor.set_actor_label(lasset.get('instance_name')) + smc = actor.get_editor_property( + 'static_mesh_component') + mesh = smc.get_editor_property('static_mesh') + import_data = mesh.get_editor_property( + 'asset_import_data') + filename = import_data.get_first_filename() + path = Path(filename) + + transform = self._get_transform( + path.suffix, import_data, lasset) + + actor.set_actor_transform(transform, False, True) + + loaded = True + break + + # If the asset has not been loaded yet, we load it. + if loaded: + continue + + assets = self._load_asset( + lasset.get('representation'), + lasset.get('version'), + lasset.get('instance_name'), + lasset.get('family') + ) + + for asset in assets: + obj = ar.get_asset_by_object_path(asset).get_asset() + if not obj.get_class().get_name() == 'StaticMesh': + continue + actor = EditorLevelLibrary.spawn_actor_from_object( + obj, unreal.Vector(0.0, 0.0, 0.0) + ) + + actor.set_actor_label(lasset.get('instance_name')) + smc = actor.get_editor_property('static_mesh_component') + mesh = smc.get_editor_property('static_mesh') + import_data = mesh.get_editor_property('asset_import_data') + filename = import_data.get_first_filename() + path = Path(filename) + + transform = self._transform_from_basis( + lasset.get('transform_matrix'), + lasset.get('basis'), + unreal.Matrix.IDENTITY.transform() + ) + + actor.set_actor_transform(transform, False, True) + + break + + # Check if an actor was not matched to a representation. + # If so, remove it from the scene. + for actor in actors: + if not actor.get_class().get_name() == 'StaticMeshActor': + continue + if actor not in actors_matched: + EditorLevelLibrary.destroy_actor(actor) + + curr_level = self._get_current_level() + + if not curr_level: + return + + curr_level_path = Path( + curr_level.get_outer().get_path_name()).parent.as_posix() + + if not unreal.EditorAssetLibrary.does_asset_exist( + f"{curr_level_path}/{container_name}" + ): + upipeline.create_container( + container=container_name, path=curr_level_path) + + data = { + "schema": "openpype:container-2.0", + "id": AVALON_CONTAINER_ID, + "asset": asset, + "namespace": curr_level_path, + "container_name": container_name, + "asset_name": asset_name, + "loader": str(self.__class__.__name__), + "representation": context["representation"]["_id"], + "parent": context["representation"]["parent"], + "family": context["representation"]["context"]["family"], + "loaded_assets": containers + } + upipeline.imprint(f"{curr_level_path}/{container_name}", data) From b3cd5e1ea060a533d983d7cfb4b231f240430226 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 24 Aug 2022 11:46:18 +0100 Subject: [PATCH 013/201] Hound fixes --- openpype/hosts/unreal/plugins/load/load_layout_existing.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/unreal/plugins/load/load_layout_existing.py b/openpype/hosts/unreal/plugins/load/load_layout_existing.py index 297e8d1a4c..c20af950d9 100644 --- a/openpype/hosts/unreal/plugins/load/load_layout_existing.py +++ b/openpype/hosts/unreal/plugins/load/load_layout_existing.py @@ -231,7 +231,8 @@ class ExistingLayoutLoader(plugin.Loader): for (repr_data, lasset) in layout_data: if not repr_data: raise AssertionError("Representation not found") - if not (repr_data.get('data') or repr_data.get('data').get('path')): + if not (repr_data.get('data') or + repr_data.get('data').get('path')): raise AssertionError("Representation does not have path") if not repr_data.get('context'): raise AssertionError("Representation does not have context") @@ -256,7 +257,7 @@ class ExistingLayoutLoader(plugin.Loader): filename = import_data.get_first_filename() path = Path(filename) - if not path.name in repr_data.get('data').get('path'): + if path.name not in repr_data.get('data').get('path'): continue asset_name = path.with_suffix('').name From 2f0f9508d4d36a157d4a4a55ed63c7408ae3c7f8 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Fri, 26 Aug 2022 12:05:18 +0100 Subject: [PATCH 014/201] Implemented update --- .../plugins/load/load_alembic_staticmesh.py | 26 +++-- .../plugins/load/load_layout_existing.py | 103 ++++++++++-------- 2 files changed, 75 insertions(+), 54 deletions(-) diff --git a/openpype/hosts/unreal/plugins/load/load_alembic_staticmesh.py b/openpype/hosts/unreal/plugins/load/load_alembic_staticmesh.py index 50e498dbb0..a5b9cbd1fc 100644 --- a/openpype/hosts/unreal/plugins/load/load_alembic_staticmesh.py +++ b/openpype/hosts/unreal/plugins/load/load_alembic_staticmesh.py @@ -20,15 +20,11 @@ class StaticMeshAlembicLoader(plugin.Loader): icon = "cube" color = "orange" - def get_task(self, filename, asset_dir, asset_name, replace): + @staticmethod + def get_task(filename, asset_dir, asset_name, replace, default_conversion): task = unreal.AssetImportTask() options = unreal.AbcImportSettings() sm_settings = unreal.AbcStaticMeshSettings() - conversion_settings = unreal.AbcConversionSettings( - preset=unreal.AbcConversionPreset.CUSTOM, - flip_u=False, flip_v=False, - rotation=[0.0, 0.0, 0.0], - scale=[1.0, 1.0, 1.0]) task.set_editor_property('filename', filename) task.set_editor_property('destination_path', asset_dir) @@ -44,13 +40,20 @@ class StaticMeshAlembicLoader(plugin.Loader): sm_settings.set_editor_property('merge_meshes', True) + if not default_conversion: + conversion_settings = unreal.AbcConversionSettings( + preset=unreal.AbcConversionPreset.CUSTOM, + flip_u=False, flip_v=False, + rotation=[0.0, 0.0, 0.0], + scale=[1.0, 1.0, 1.0]) + options.conversion_settings = conversion_settings + options.static_mesh_settings = sm_settings - options.conversion_settings = conversion_settings task.options = options return task - def load(self, context, name, namespace, data): + def load(self, context, name, namespace, options): """Load and containerise representation into Content Browser. This is two step process. First, import FBX to temporary path and @@ -82,6 +85,10 @@ class StaticMeshAlembicLoader(plugin.Loader): asset_name = "{}".format(name) version = context.get('version').get('name') + default_conversion = False + if options.get("default_conversion"): + default_conversion = options.get("default_conversion") + tools = unreal.AssetToolsHelpers().get_asset_tools() asset_dir, container_name = tools.create_unique_asset_name( f"{root}/{asset}/{name}_v{version:03d}", suffix="") @@ -91,7 +98,8 @@ class StaticMeshAlembicLoader(plugin.Loader): if not unreal.EditorAssetLibrary.does_directory_exist(asset_dir): unreal.EditorAssetLibrary.make_directory(asset_dir) - task = self.get_task(self.fname, asset_dir, asset_name, False) + task = self.get_task( + self.fname, asset_dir, asset_name, False, default_conversion) unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task]) # noqa: E501 diff --git a/openpype/hosts/unreal/plugins/load/load_layout_existing.py b/openpype/hosts/unreal/plugins/load/load_layout_existing.py index c20af950d9..8cd1950f7e 100644 --- a/openpype/hosts/unreal/plugins/load/load_layout_existing.py +++ b/openpype/hosts/unreal/plugins/load/load_layout_existing.py @@ -11,6 +11,7 @@ from openpype.pipeline import ( discover_loader_plugins, loaders_from_representation, load_container, + get_representation_path, AVALON_CONTAINER_ID, legacy_io, ) @@ -132,6 +133,23 @@ class ExistingLayoutLoader(plugin.Loader): ) return transform + def _spawn_actor(self, obj, lasset): + actor = EditorLevelLibrary.spawn_actor_from_object( + obj, unreal.Vector(0.0, 0.0, 0.0) + ) + + actor.set_actor_label(lasset.get('instance_name')) + smc = actor.get_editor_property('static_mesh_component') + mesh = smc.get_editor_property('static_mesh') + import_data = mesh.get_editor_property('asset_import_data') + filename = import_data.get_first_filename() + path = Path(filename) + + transform = self._get_transform( + path.suffix, import_data, lasset) + + actor.set_actor_transform(transform, False, True) + @staticmethod def _get_fbx_loader(loaders, family): name = "" @@ -192,25 +210,29 @@ class ExistingLayoutLoader(plugin.Loader): if not loader: raise AssertionError(f"No valid loader found for {representation}") + # This option is necessary to avoid importing the assets with a + # different conversion compared to the other assets. For ABC files, + # it is in fact impossible to access the conversion settings. So, + # we must assume that the Maya conversion settings have been applied. + options = { + "default_conversion": True + } + assets = load_container( loader, representation, - namespace=instance_name + namespace=instance_name, + options=options ) return assets - def load(self, context, name, namespace, options): - print("Loading Layout and Match Assets") - + def _process(self, lib_path): ar = unreal.AssetRegistryHelpers.get_asset_registry() - asset = context.get('asset').get('name') - container_name = f"{asset}_{name}_CON" - actors = EditorLevelLibrary.get_all_level_actors() - with open(self.fname, "r") as fp: + with open(lib_path, "r") as fp: data = json.load(fp) layout_data = [] @@ -260,7 +282,6 @@ class ExistingLayoutLoader(plugin.Loader): if path.name not in repr_data.get('data').get('path'): continue - asset_name = path.with_suffix('').name mesh_path = Path(mesh.get_path_name()).parent.as_posix() # Create the container for the asset. @@ -309,23 +330,7 @@ class ExistingLayoutLoader(plugin.Loader): for asset in assets: obj = asset.get_asset() - actor = EditorLevelLibrary.spawn_actor_from_object( - obj, unreal.Vector(0.0, 0.0, 0.0) - ) - - actor.set_actor_label(lasset.get('instance_name')) - smc = actor.get_editor_property( - 'static_mesh_component') - mesh = smc.get_editor_property('static_mesh') - import_data = mesh.get_editor_property( - 'asset_import_data') - filename = import_data.get_first_filename() - path = Path(filename) - - transform = self._get_transform( - path.suffix, import_data, lasset) - - actor.set_actor_transform(transform, False, True) + self._spawn_actor(obj, lasset) loaded = True break @@ -345,24 +350,7 @@ class ExistingLayoutLoader(plugin.Loader): obj = ar.get_asset_by_object_path(asset).get_asset() if not obj.get_class().get_name() == 'StaticMesh': continue - actor = EditorLevelLibrary.spawn_actor_from_object( - obj, unreal.Vector(0.0, 0.0, 0.0) - ) - - actor.set_actor_label(lasset.get('instance_name')) - smc = actor.get_editor_property('static_mesh_component') - mesh = smc.get_editor_property('static_mesh') - import_data = mesh.get_editor_property('asset_import_data') - filename = import_data.get_first_filename() - path = Path(filename) - - transform = self._transform_from_basis( - lasset.get('transform_matrix'), - lasset.get('basis'), - unreal.Matrix.IDENTITY.transform() - ) - - actor.set_actor_transform(transform, False, True) + self._spawn_actor(obj, lasset) break @@ -374,10 +362,21 @@ class ExistingLayoutLoader(plugin.Loader): if actor not in actors_matched: EditorLevelLibrary.destroy_actor(actor) + return containers + + def load(self, context, name, namespace, options): + print("Loading Layout and Match Assets") + + asset = context.get('asset').get('name') + asset_name = f"{asset}_{name}" if asset else name + container_name = f"{asset}_{name}_CON" + curr_level = self._get_current_level() if not curr_level: - return + raise AssertionError("Current level not saved") + + containers = self._process(self.fname) curr_level_path = Path( curr_level.get_outer().get_path_name()).parent.as_posix() @@ -402,3 +401,17 @@ class ExistingLayoutLoader(plugin.Loader): "loaded_assets": containers } upipeline.imprint(f"{curr_level_path}/{container_name}", data) + + def update(self, container, representation): + asset_dir = container.get('namespace') + + source_path = get_representation_path(representation) + containers = self._process(source_path) + + data = { + "representation": str(representation["_id"]), + "parent": str(representation["parent"]), + "loaded_assets": containers + } + upipeline.imprint( + "{}/{}".format(asset_dir, container.get('container_name')), data) From 41e673c3ee45c85a7c71e5b0a0bb465ae95a9b83 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 9 Sep 2022 00:20:47 +0200 Subject: [PATCH 015/201] Cleanup comment --- openpype/hosts/maya/api/lib.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 58e160cb2f..5e449b324e 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -2633,8 +2633,6 @@ def load_capture_preset(data=None): scene = capture.parse_active_scene() options['sound'] = scene['sound'] - # options['display_options'] = temp_options - return options From ed11baf0fdaa8c35764bdee8b04215e58aca1e23 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 9 Sep 2022 00:29:16 +0200 Subject: [PATCH 016/201] Include camera options --- openpype/hosts/maya/api/lib.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 5e449b324e..c8369bac13 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -2629,6 +2629,13 @@ def load_capture_preset(data=None): options['viewport_options'] = temp_options options['viewport2_options'] = temp_options2 + # CAMERA OPTIONS + id = 'Camera Options' + camera_options = {} + for key, value in preset[id].items(): + camera_options[key] = value + options['camera_options'] = camera_options + # use active sound track scene = capture.parse_active_scene() options['sound'] = scene['sound'] From 5558c1eb46c8e2045c26c19ae4afcf8a47520eba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Albert?= Date: Fri, 9 Sep 2022 12:07:56 +0200 Subject: [PATCH 017/201] Kitsu : add launcher action --- .../kitsu/actions/launcher_show_in_kitsu.py | 131 ++++++++++++++++++ openpype/modules/kitsu/kitsu_module.py | 5 +- 2 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 openpype/modules/kitsu/actions/launcher_show_in_kitsu.py diff --git a/openpype/modules/kitsu/actions/launcher_show_in_kitsu.py b/openpype/modules/kitsu/actions/launcher_show_in_kitsu.py new file mode 100644 index 0000000000..0ac9c6e9b7 --- /dev/null +++ b/openpype/modules/kitsu/actions/launcher_show_in_kitsu.py @@ -0,0 +1,131 @@ +import webbrowser + +from openpype.pipeline import LauncherAction +from openpype.modules import ModulesManager +from openpype.client import get_project, get_asset_by_name + + +class ShowInKitsu(LauncherAction): + name = "showinkitsu" + label = "Show in Kitsu" + icon = "external-link-square" + color = "#e0e1e1" + order = 10 + + @staticmethod + def get_kitsu_module(): + return ModulesManager().modules_by_name.get("kitsu") + + def is_compatible(self, session): + if not session.get("AVALON_PROJECT"): + return False + + return True + + def process(self, session, **kwargs): + + # Context inputs + project_name = session["AVALON_PROJECT"] + asset_name = session.get("AVALON_ASSET", None) + task_name = session.get("AVALON_TASK", None) + + project = get_project(project_name=project_name, + fields=["data.zou_id"]) + if not project: + raise RuntimeError(f"Project {project_name} not found.") + + project_zou_id = project["data"].get("zou_id") + if not project_zou_id: + raise RuntimeError(f"Project {project_name} has no " + f"connected ftrack id.") + + asset_zou_data = None + task_zou_id = None + asset_zou_name = None + asset_zou_id = None + asset_zou_type = 'Assets' + zou_sub_type = ['AssetType','Sequence'] + if asset_name: + asset_zou_name = asset_name + asset_fields = ["data.zou.id", "data.zou.type"] + if task_name: + asset_fields.append(f"data.tasks.{task_name}.zou.id") + + asset = get_asset_by_name(project_name, + asset_name=asset_name, + fields=asset_fields) + + asset_zou_data = asset["data"].get("zou") + + if asset_zou_data: + asset_zou_type = asset_zou_data["type"] + if not asset_zou_type in zou_sub_type: + asset_zou_id = asset_zou_data["id"] + else: + asset_zou_type = asset_name + + + if task_name: + task_data = asset["data"]["tasks"][task_name] + task_zou_data = task_data.get("zou", {}) + if not task_zou_data: + self.log.debug(f"No zou task data for task: {task_name}") + task_zou_id = task_zou_data["id"] + + # Define URL + url = self.get_url(project_id=project_zou_id, + asset_name=asset_zou_name, + asset_id=asset_zou_id, + asset_type=asset_zou_type, + task_id=task_zou_id) + + # Open URL in webbrowser + self.log.info(f"Opening URL: {url}") + webbrowser.open(url, + # Try in new tab + new=2) + + def get_url(self, + project_id, + asset_name=None, + asset_id=None, + asset_type=None, + task_id=None): + + shots_url = ['Shots','Sequence','Shot'] + sub_type = ['AssetType','Sequence'] + kitsu_module = self.get_kitsu_module() + + # Get kitsu url with /api stripped + kitsu_url = kitsu_module.server_url + if kitsu_url.endswith("/api"): + kitsu_url = kitsu_url[:-len("/api")] + + sub_url = f"/productions/{project_id}" + asset_type_url = "Assets" + + # Add redirection url for shots_url list + if asset_type in shots_url: + asset_type_url = 'Shots' + + if task_id: + # Go to task page + # /productions/{project-id}/{asset_type}/tasks/{task_id} + sub_url += f"/{asset_type_url}/tasks/{task_id}" + + elif asset_id: + # Go to asset or shot page + # /productions/{project-id}/assets/{entity_id} + # /productions/{project-id}/shots/{entity_id} + sub_url += f"/{asset_type_url}/{asset_id}" + + else: + # Go to project page + # Project page must end with a view + # /productions/{project-id}/assets/ + # Add search method if is a sub_type + sub_url += f"/{asset_type_url}" + if asset_type in sub_type: + sub_url += f'?search={asset_name}' + + return f"{kitsu_url}{sub_url}" diff --git a/openpype/modules/kitsu/kitsu_module.py b/openpype/modules/kitsu/kitsu_module.py index d19d14dda7..23c032715b 100644 --- a/openpype/modules/kitsu/kitsu_module.py +++ b/openpype/modules/kitsu/kitsu_module.py @@ -89,7 +89,10 @@ class KitsuModule(OpenPypeModule, IPluginPaths, ITrayAction): """Implementation of abstract method for `IPluginPaths`.""" current_dir = os.path.dirname(os.path.abspath(__file__)) - return {"publish": [os.path.join(current_dir, "plugins", "publish")]} + return { + "publish": [os.path.join(current_dir, "plugins", "publish")], + "actions": [os.path.join(current_dir, "actions")] + } def cli(self, click_group): click_group.add_command(cli_main) From d0d80b0b90648b1633a11c5980c0c66e3a3cff7f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 13 Sep 2022 14:57:05 +0200 Subject: [PATCH 018/201] Fix typo `camera_option` -> `camera_options` - Also use `setdefault` to ensure its added into the preset when key wasn't there originally --- openpype/hosts/maya/plugins/publish/extract_playblast.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_playblast.py b/openpype/hosts/maya/plugins/publish/extract_playblast.py index 871adda0c3..6010319f40 100644 --- a/openpype/hosts/maya/plugins/publish/extract_playblast.py +++ b/openpype/hosts/maya/plugins/publish/extract_playblast.py @@ -79,8 +79,10 @@ class ExtractPlayblast(openpype.api.Extractor): preset['height'] = asset_height preset['start_frame'] = start preset['end_frame'] = end - camera_option = preset.get("camera_option", {}) - camera_option["depthOfField"] = cmds.getAttr( + + # Enforce persisting camera depth of field + camera_options = preset.setdefault("camera_options", {}) + camera_options["depthOfField"] = cmds.getAttr( "{0}.depthOfField".format(camera)) stagingdir = self.staging_dir(instance) From 4b7ecac2bbc5fc0f33839c3b0efa0cf2ff304e4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Albert?= Date: Wed, 14 Sep 2022 12:31:13 +0200 Subject: [PATCH 019/201] Fix Format Document --- .../modules/kitsu/actions/launcher_show_in_kitsu.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/openpype/modules/kitsu/actions/launcher_show_in_kitsu.py b/openpype/modules/kitsu/actions/launcher_show_in_kitsu.py index 0ac9c6e9b7..bca57ce4c6 100644 --- a/openpype/modules/kitsu/actions/launcher_show_in_kitsu.py +++ b/openpype/modules/kitsu/actions/launcher_show_in_kitsu.py @@ -44,7 +44,7 @@ class ShowInKitsu(LauncherAction): asset_zou_name = None asset_zou_id = None asset_zou_type = 'Assets' - zou_sub_type = ['AssetType','Sequence'] + zou_sub_type = ['AssetType', 'Sequence'] if asset_name: asset_zou_name = asset_name asset_fields = ["data.zou.id", "data.zou.type"] @@ -59,11 +59,10 @@ class ShowInKitsu(LauncherAction): if asset_zou_data: asset_zou_type = asset_zou_data["type"] - if not asset_zou_type in zou_sub_type: + if asset_zou_type not in zou_sub_type: asset_zou_id = asset_zou_data["id"] else: asset_zou_type = asset_name - if task_name: task_data = asset["data"]["tasks"][task_name] @@ -71,7 +70,7 @@ class ShowInKitsu(LauncherAction): if not task_zou_data: self.log.debug(f"No zou task data for task: {task_name}") task_zou_id = task_zou_data["id"] - + # Define URL url = self.get_url(project_id=project_zou_id, asset_name=asset_zou_name, @@ -92,8 +91,8 @@ class ShowInKitsu(LauncherAction): asset_type=None, task_id=None): - shots_url = ['Shots','Sequence','Shot'] - sub_type = ['AssetType','Sequence'] + shots_url = ['Shots', 'Sequence', 'Shot'] + sub_type = ['AssetType', 'Sequence'] kitsu_module = self.get_kitsu_module() # Get kitsu url with /api stripped @@ -103,7 +102,7 @@ class ShowInKitsu(LauncherAction): sub_url = f"/productions/{project_id}" asset_type_url = "Assets" - + # Add redirection url for shots_url list if asset_type in shots_url: asset_type_url = 'Shots' From 2fb40a9db7c3fde1c1842958c597c07693bb17e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Albert?= <89812691+sebasti1a@users.noreply.github.com> Date: Wed, 14 Sep 2022 14:49:44 +0200 Subject: [PATCH 020/201] Update openpype/modules/kitsu/actions/launcher_show_in_kitsu.py Co-authored-by: Roy Nieterau --- openpype/modules/kitsu/actions/launcher_show_in_kitsu.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/openpype/modules/kitsu/actions/launcher_show_in_kitsu.py b/openpype/modules/kitsu/actions/launcher_show_in_kitsu.py index bca57ce4c6..0ee95e773d 100644 --- a/openpype/modules/kitsu/actions/launcher_show_in_kitsu.py +++ b/openpype/modules/kitsu/actions/launcher_show_in_kitsu.py @@ -101,11 +101,7 @@ class ShowInKitsu(LauncherAction): kitsu_url = kitsu_url[:-len("/api")] sub_url = f"/productions/{project_id}" - asset_type_url = "Assets" - - # Add redirection url for shots_url list - if asset_type in shots_url: - asset_type_url = 'Shots' + asset_type_url = "Shots" if asset_type in shots_url else "Assets" if task_id: # Go to task page From 9b34573361ef292fcb68e692efc74268ac165ca5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Albert?= <89812691+sebasti1a@users.noreply.github.com> Date: Wed, 14 Sep 2022 14:50:29 +0200 Subject: [PATCH 021/201] Update openpype/modules/kitsu/actions/launcher_show_in_kitsu.py Co-authored-by: Roy Nieterau --- openpype/modules/kitsu/actions/launcher_show_in_kitsu.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/modules/kitsu/actions/launcher_show_in_kitsu.py b/openpype/modules/kitsu/actions/launcher_show_in_kitsu.py index 0ee95e773d..68da3e3a0e 100644 --- a/openpype/modules/kitsu/actions/launcher_show_in_kitsu.py +++ b/openpype/modules/kitsu/actions/launcher_show_in_kitsu.py @@ -39,7 +39,6 @@ class ShowInKitsu(LauncherAction): raise RuntimeError(f"Project {project_name} has no " f"connected ftrack id.") - asset_zou_data = None task_zou_id = None asset_zou_name = None asset_zou_id = None From 40e3dbb38fe91cdc07814707b56f2550138c7eac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Albert?= <89812691+sebasti1a@users.noreply.github.com> Date: Wed, 14 Sep 2022 14:50:42 +0200 Subject: [PATCH 022/201] Update openpype/modules/kitsu/actions/launcher_show_in_kitsu.py Co-authored-by: Roy Nieterau --- openpype/modules/kitsu/actions/launcher_show_in_kitsu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/kitsu/actions/launcher_show_in_kitsu.py b/openpype/modules/kitsu/actions/launcher_show_in_kitsu.py index 68da3e3a0e..575d1eecd0 100644 --- a/openpype/modules/kitsu/actions/launcher_show_in_kitsu.py +++ b/openpype/modules/kitsu/actions/launcher_show_in_kitsu.py @@ -39,10 +39,10 @@ class ShowInKitsu(LauncherAction): raise RuntimeError(f"Project {project_name} has no " f"connected ftrack id.") - task_zou_id = None asset_zou_name = None asset_zou_id = None asset_zou_type = 'Assets' + task_zou_id = None zou_sub_type = ['AssetType', 'Sequence'] if asset_name: asset_zou_name = asset_name From ab03df702a0f14c8d0dd94ed3c917c78fde9cb4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Albert?= <89812691+sebasti1a@users.noreply.github.com> Date: Wed, 14 Sep 2022 14:50:50 +0200 Subject: [PATCH 023/201] Update openpype/modules/kitsu/actions/launcher_show_in_kitsu.py Co-authored-by: Roy Nieterau --- openpype/modules/kitsu/actions/launcher_show_in_kitsu.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/modules/kitsu/actions/launcher_show_in_kitsu.py b/openpype/modules/kitsu/actions/launcher_show_in_kitsu.py index 575d1eecd0..ab523876ed 100644 --- a/openpype/modules/kitsu/actions/launcher_show_in_kitsu.py +++ b/openpype/modules/kitsu/actions/launcher_show_in_kitsu.py @@ -90,8 +90,8 @@ class ShowInKitsu(LauncherAction): asset_type=None, task_id=None): - shots_url = ['Shots', 'Sequence', 'Shot'] - sub_type = ['AssetType', 'Sequence'] + shots_url = {'Shots', 'Sequence', 'Shot'} + sub_type = {'AssetType', 'Sequence'} kitsu_module = self.get_kitsu_module() # Get kitsu url with /api stripped From 3fff6647389d28b47fe4048a5a048d3fb420da91 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 15 Sep 2022 16:13:01 +0200 Subject: [PATCH 024/201] Refactor `load_capture_preset` --- openpype/hosts/maya/api/lib.py | 204 +++++++++++---------------------- 1 file changed, 64 insertions(+), 140 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 97035ad3f2..3561c3f53f 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -2462,28 +2462,16 @@ def load_capture_preset(data=None): import capture preset = data - options = dict() - # CODEC - id = 'Codec' - for key in preset[id]: - options[str(key)] = preset[id][key] - - # GENERIC - id = 'Generic' - for key in preset[id]: - options[str(key)] = preset[id][key] - - # RESOLUTION - id = 'Resolution' - options['height'] = preset[id]['height'] - options['width'] = preset[id]['width'] + # Straight key-value match from settings to capture arguments + for settings_key in ["Codec", "Generic", "Resolution"]: + for key, value in preset[settings_key].items(): + options[key] = value # DISPLAY OPTIONS - id = 'Display Options' disp_options = {} - for key in preset[id]: + for key in preset['Display Options']: if key.startswith('background'): disp_options[key] = preset['Display Options'][key] if len(disp_options[key]) == 4: @@ -2497,142 +2485,78 @@ def load_capture_preset(data=None): options['display_options'] = disp_options # VIEWPORT OPTIONS - temp_options = {} - id = 'Renderer' - for key in preset[id]: - temp_options[str(key)] = preset[id][key] + viewport_options = {} + viewport2_options = {} - temp_options2 = {} - id = 'Viewport Options' - for key in preset[id]: + for key, value in preset['Renderer'].items(): + viewport_options[key] = value + + # Viewport Options has a mixture of Viewport2 Options and Viewport Options + # to pass along to capture. So we'll need to differentiate between the two + VIEWPORT2_OPTIONS = { + "textureMaxResolution", + "renderDepthOfField", + "ssaoEnable", + "ssaoSamples", + "ssaoAmount", + "ssaoRadius", + "ssaoFilterRadius", + "hwFogStart", + "hwFogEnd", + "hwFogAlpha", + "hwFogFalloff", + "hwFogColorR", + "hwFogColorG", + "hwFogColorB", + "hwFogDensity", + "motionBlurEnable", + "motionBlurSampleCount", + "motionBlurShutterOpenFraction", + "lineAAEnable" + } + for key, value in preset['Viewport Options'].items(): + + # There are some keys we want to ignore + if key in {"override_viewport_options", "high_quality"}: + continue + + # First handle special cases where we do value conversion to + # separate option values if key == 'textureMaxResolution': - if preset[id][key] > 0: - temp_options2['textureMaxResolution'] = preset[id][key] - temp_options2['enableTextureMaxRes'] = True - temp_options2['textureMaxResMode'] = 1 + viewport2_options['textureMaxResolution'] = value + if value > 0: + viewport2_options['enableTextureMaxRes'] = True + viewport2_options['textureMaxResMode'] = 1 else: - temp_options2['textureMaxResolution'] = preset[id][key] - temp_options2['enableTextureMaxRes'] = False - temp_options2['textureMaxResMode'] = 0 + viewport2_options['enableTextureMaxRes'] = False + viewport2_options['textureMaxResMode'] = 0 - if key == 'multiSample': - if preset[id][key] > 0: - temp_options2['multiSampleEnable'] = True - temp_options2['multiSampleCount'] = preset[id][key] - else: - temp_options2['multiSampleEnable'] = False - temp_options2['multiSampleCount'] = preset[id][key] + elif key == 'multiSample': + viewport2_options['multiSampleEnable'] = value > 0 + viewport2_options['multiSampleCount'] = value - if key == 'renderDepthOfField': - temp_options2['renderDepthOfField'] = preset[id][key] + elif key == 'alphaCut': + viewport2_options['transparencyAlgorithm'] = 5 + viewport2_options['transparencyQuality'] = 1 - if key == 'ssaoEnable': - if preset[id][key] is True: - temp_options2['ssaoEnable'] = True - else: - temp_options2['ssaoEnable'] = False + elif key == 'hwFogFalloff': + # Settings enum value string to integer + viewport2_options['hwFogFalloff'] = int(value) - if key == 'ssaoSamples': - temp_options2['ssaoSamples'] = preset[id][key] - - if key == 'ssaoAmount': - temp_options2['ssaoAmount'] = preset[id][key] - - if key == 'ssaoRadius': - temp_options2['ssaoRadius'] = preset[id][key] - - if key == 'hwFogDensity': - temp_options2['hwFogDensity'] = preset[id][key] - - if key == 'ssaoFilterRadius': - temp_options2['ssaoFilterRadius'] = preset[id][key] - - if key == 'alphaCut': - temp_options2['transparencyAlgorithm'] = 5 - temp_options2['transparencyQuality'] = 1 - - if key == 'headsUpDisplay': - temp_options['headsUpDisplay'] = True - - if key == 'fogging': - temp_options['fogging'] = preset[id][key] or False - - if key == 'hwFogStart': - temp_options2['hwFogStart'] = preset[id][key] - - if key == 'hwFogEnd': - temp_options2['hwFogEnd'] = preset[id][key] - - if key == 'hwFogAlpha': - temp_options2['hwFogAlpha'] = preset[id][key] - - if key == 'hwFogFalloff': - temp_options2['hwFogFalloff'] = int(preset[id][key]) - - if key == 'hwFogColorR': - temp_options2['hwFogColorR'] = preset[id][key] - - if key == 'hwFogColorG': - temp_options2['hwFogColorG'] = preset[id][key] - - if key == 'hwFogColorB': - temp_options2['hwFogColorB'] = preset[id][key] - - if key == 'motionBlurEnable': - if preset[id][key] is True: - temp_options2['motionBlurEnable'] = True - else: - temp_options2['motionBlurEnable'] = False - - if key == 'motionBlurSampleCount': - temp_options2['motionBlurSampleCount'] = preset[id][key] - - if key == 'motionBlurShutterOpenFraction': - temp_options2['motionBlurShutterOpenFraction'] = preset[id][key] - - if key == 'lineAAEnable': - if preset[id][key] is True: - temp_options2['lineAAEnable'] = True - else: - temp_options2['lineAAEnable'] = False + # Then handle Viewport 2.0 Options + elif key in VIEWPORT2_OPTIONS: + viewport2_options[key] = value + # Then assume remainder is Viewport Options else: - temp_options[str(key)] = preset[id][key] + viewport_options[key] = value - for key in ['override_viewport_options', - 'high_quality', - 'alphaCut', - 'gpuCacheDisplayFilter', - 'multiSample', - 'ssaoEnable', - 'ssaoSamples', - 'ssaoAmount', - 'ssaoFilterRadius', - 'ssaoRadius', - 'hwFogStart', - 'hwFogEnd', - 'hwFogAlpha', - 'hwFogFalloff', - 'hwFogColorR', - 'hwFogColorG', - 'hwFogColorB', - 'hwFogDensity', - 'textureMaxResolution', - 'motionBlurEnable', - 'motionBlurSampleCount', - 'motionBlurShutterOpenFraction', - 'lineAAEnable', - 'renderDepthOfField' - ]: - temp_options.pop(key, None) - - options['viewport_options'] = temp_options - options['viewport2_options'] = temp_options2 + options['viewport_options'] = viewport_options + options['viewport2_options'] = viewport2_options # CAMERA OPTIONS - id = 'Camera Options' camera_options = {} - for key, value in preset[id].items(): + for key, value in preset['Camera Options'].items(): camera_options[key] = value options['camera_options'] = camera_options From 2560fc0081292b7862367101e895a7d3526045da Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 15 Sep 2022 16:29:16 +0200 Subject: [PATCH 025/201] Simplify logic where we're taking values directly --- openpype/hosts/maya/api/lib.py | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 3561c3f53f..4d6f599d2b 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -2460,14 +2460,20 @@ def bake_to_world_space(nodes, def load_capture_preset(data=None): import capture - preset = data + options = dict() + viewport_options = dict() + viewport2_options = dict() + camera_options = dict() # Straight key-value match from settings to capture arguments - for settings_key in ["Codec", "Generic", "Resolution"]: - for key, value in preset[settings_key].items(): - options[key] = value + options.update(preset["Codec"]) + options.update(preset["Generic"]) + options.update(preset["Resolution"]) + + camera_options.update(preset['Camera Options']) + viewport_options.update(preset["Renderer"]) # DISPLAY OPTIONS disp_options = {} @@ -2484,13 +2490,6 @@ def load_capture_preset(data=None): options['display_options'] = disp_options - # VIEWPORT OPTIONS - viewport_options = {} - viewport2_options = {} - - for key, value in preset['Renderer'].items(): - viewport_options[key] = value - # Viewport Options has a mixture of Viewport2 Options and Viewport Options # to pass along to capture. So we'll need to differentiate between the two VIEWPORT2_OPTIONS = { @@ -2553,11 +2552,6 @@ def load_capture_preset(data=None): options['viewport_options'] = viewport_options options['viewport2_options'] = viewport2_options - - # CAMERA OPTIONS - camera_options = {} - for key, value in preset['Camera Options'].items(): - camera_options[key] = value options['camera_options'] = camera_options # use active sound track From f99b31d13a7bafbfe3763978db9a10a1024f8d85 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 15 Sep 2022 16:30:32 +0200 Subject: [PATCH 026/201] Don't remap input argument `preset` isn't necessarily more explicit than `data` and actually adds to confusion because it makes it feel like it's an actual capture preset but instead it converts OpenPype preset data into capture preset --- openpype/hosts/maya/api/lib.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 4d6f599d2b..7220f53e66 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -2460,7 +2460,6 @@ def bake_to_world_space(nodes, def load_capture_preset(data=None): import capture - preset = data options = dict() viewport_options = dict() @@ -2468,18 +2467,18 @@ def load_capture_preset(data=None): camera_options = dict() # Straight key-value match from settings to capture arguments - options.update(preset["Codec"]) - options.update(preset["Generic"]) - options.update(preset["Resolution"]) + options.update(data["Codec"]) + options.update(data["Generic"]) + options.update(data["Resolution"]) - camera_options.update(preset['Camera Options']) - viewport_options.update(preset["Renderer"]) + camera_options.update(data['Camera Options']) + viewport_options.update(data["Renderer"]) # DISPLAY OPTIONS disp_options = {} - for key in preset['Display Options']: + for key in data['Display Options']: if key.startswith('background'): - disp_options[key] = preset['Display Options'][key] + disp_options[key] = data['Display Options'][key] if len(disp_options[key]) == 4: disp_options[key][0] = (float(disp_options[key][0])/255) disp_options[key][1] = (float(disp_options[key][1])/255) @@ -2513,7 +2512,7 @@ def load_capture_preset(data=None): "motionBlurShutterOpenFraction", "lineAAEnable" } - for key, value in preset['Viewport Options'].items(): + for key, value in data['Viewport Options'].items(): # There are some keys we want to ignore if key in {"override_viewport_options", "high_quality"}: From 1132d8d4f8ffa4b224308b5099424e9984d0822c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 15 Sep 2022 16:34:11 +0200 Subject: [PATCH 027/201] Add docstring --- openpype/hosts/maya/api/lib.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 7220f53e66..dec698062a 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -2459,6 +2459,19 @@ def bake_to_world_space(nodes, def load_capture_preset(data=None): + """Convert OpenPype Extract Playblast settings to `capture` arguments + + Input data is the settings from: + `project_settings/maya/publish/ExtractPlayblast/capture_preset` + + Args: + data (dict): Capture preset settings from OpenPype settings + + Returns: + dict: `capture.capture` compatible keyword arguments + + """ + import capture options = dict() From 598ec6e2dcb5664407945d081ae15adad6b4d8c6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 15 Sep 2022 16:57:42 +0200 Subject: [PATCH 028/201] Improve readability of color conversion logic --- openpype/hosts/maya/api/lib.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index dec698062a..5b436a018d 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -2489,14 +2489,17 @@ def load_capture_preset(data=None): # DISPLAY OPTIONS disp_options = {} - for key in data['Display Options']: + for key, value in data['Display Options'].items(): if key.startswith('background'): - disp_options[key] = data['Display Options'][key] - if len(disp_options[key]) == 4: - disp_options[key][0] = (float(disp_options[key][0])/255) - disp_options[key][1] = (float(disp_options[key][1])/255) - disp_options[key][2] = (float(disp_options[key][2])/255) - disp_options[key].pop() + # Convert background, backgroundTop, backgroundBottom colors + if len(value) == 4: + # Ignore alpha + convert RGB to float + value = [ + float(value[0]) / 255, + float(value[1]) / 255, + float(value[2]) / 255 + ] + disp_options[key] = value else: disp_options['displayGradient'] = True From ccab10b0d3fecc4e32711e5f1a783de433b54ce8 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 16 Sep 2022 14:43:13 +0200 Subject: [PATCH 029/201] Do not enforce maya/ folder --- .../publish/validate_rendersettings.py | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py index 08ecc0d149..4a67cb73e4 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py +++ b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py @@ -16,10 +16,10 @@ from openpype.hosts.maya.api import lib class ValidateRenderSettings(pyblish.api.InstancePlugin): """Validates the global render settings - * File Name Prefix must start with: `maya/` + * File Name Prefix must start with: `` all other token are customizable but sane values for Arnold are: - `maya///_` + `//_` token is supported also, useful for multiple renderable cameras per render layer. @@ -58,12 +58,12 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): } ImagePrefixTokens = { - 'mentalray': 'maya///{aov_separator}', # noqa: E501 - 'arnold': 'maya///{aov_separator}', # noqa: E501 - 'redshift': 'maya///', - 'vray': 'maya///', + 'mentalray': '//{aov_separator}', # noqa: E501 + 'arnold': '//{aov_separator}', # noqa: E501 + 'redshift': '//', + 'vray': '//', 'renderman': '{aov_separator}..', - 'mayahardware2': 'maya///', + 'mayahardware2': '//', } _aov_chars = { @@ -74,7 +74,7 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): redshift_AOV_prefix = "/{aov_separator}" # noqa: E501 - renderman_dir_prefix = "maya//" + renderman_dir_prefix = "/" R_AOV_TOKEN = re.compile( r'%a||', re.IGNORECASE) @@ -84,8 +84,8 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): R_SCENE_TOKEN = re.compile(r'%s|', re.IGNORECASE) DEFAULT_PADDING = 4 - VRAY_PREFIX = "maya///" - DEFAULT_PREFIX = "maya///_" + VRAY_PREFIX = "//" + DEFAULT_PREFIX = "//_" def process(self, instance): @@ -116,7 +116,7 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): prefix = prefix.replace( "{aov_separator}", instance.data.get("aovSeparator", "_")) - required_prefix = "maya/" + required_prefix = "" default_prefix = cls.ImagePrefixTokens[renderer] if not anim_override: From 59cfca7508f493bb7afec85ded74bd8abd44e2e0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 16 Sep 2022 14:44:19 +0200 Subject: [PATCH 030/201] Do not enforce maya/ folder --- openpype/hosts/maya/api/lib_rendersettings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/api/lib_rendersettings.py b/openpype/hosts/maya/api/lib_rendersettings.py index 7cd2193086..21dd7f00c8 100644 --- a/openpype/hosts/maya/api/lib_rendersettings.py +++ b/openpype/hosts/maya/api/lib_rendersettings.py @@ -28,7 +28,7 @@ class RenderSettings(object): _image_prefixes = { 'vray': get_current_project_settings()["maya"]["RenderSettings"]["vray_renderer"]["image_prefix"], # noqa 'arnold': get_current_project_settings()["maya"]["RenderSettings"]["arnold_renderer"]["image_prefix"], # noqa - 'renderman': 'maya///{aov_separator}', + 'renderman': '//{aov_separator}', 'redshift': get_current_project_settings()["maya"]["RenderSettings"]["redshift_renderer"]["image_prefix"] # noqa } From 47d7b3044bff4827356ddde4f4c257ac87a9287c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 16 Sep 2022 14:46:45 +0200 Subject: [PATCH 031/201] Remove maya/ from file prefixes in setting defaults --- openpype/settings/defaults/project_settings/maya.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 8643297f02..716e45a6e2 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -34,12 +34,12 @@ }, "RenderSettings": { "apply_render_settings": true, - "default_render_image_folder": "renders", + "default_render_image_folder": "renders/maya", "enable_all_lights": false, "aov_separator": "underscore", "reset_current_frame": false, "arnold_renderer": { - "image_prefix": "maya///_", + "image_prefix": "//_", "image_format": "exr", "multilayer_exr": true, "tiled": true, @@ -47,14 +47,14 @@ "additional_options": [] }, "vray_renderer": { - "image_prefix": "maya///", + "image_prefix": "//", "engine": "1", "image_format": "png", "aov_list": [], "additional_options": [] }, "redshift_renderer": { - "image_prefix": "maya///", + "image_prefix": "//", "primary_gi_engine": "0", "secondary_gi_engine": "0", "image_format": "iff", From ea5ae50982cc867f24c253e8c5dcb1df1612d30f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 16 Sep 2022 14:47:28 +0200 Subject: [PATCH 032/201] Add maya/ folder by default into "images" file rule in workspace --- openpype/settings/defaults/project_settings/maya.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 716e45a6e2..a62d356162 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -1,5 +1,5 @@ { - "mel_workspace": "workspace -fr \"shaders\" \"renderData/shaders\";\nworkspace -fr \"images\" \"renders\";\nworkspace -fr \"particles\" \"particles\";\nworkspace -fr \"mayaAscii\" \"\";\nworkspace -fr \"mayaBinary\" \"\";\nworkspace -fr \"scene\" \"\";\nworkspace -fr \"alembicCache\" \"cache/alembic\";\nworkspace -fr \"renderData\" \"renderData\";\nworkspace -fr \"sourceImages\" \"sourceimages\";\nworkspace -fr \"fileCache\" \"cache/nCache\";\n", + "mel_workspace": "workspace -fr \"shaders\" \"renderData/shaders\";\nworkspace -fr \"images\" \"renders/maya\";\nworkspace -fr \"particles\" \"particles\";\nworkspace -fr \"mayaAscii\" \"\";\nworkspace -fr \"mayaBinary\" \"\";\nworkspace -fr \"scene\" \"\";\nworkspace -fr \"alembicCache\" \"cache/alembic\";\nworkspace -fr \"renderData\" \"renderData\";\nworkspace -fr \"sourceImages\" \"sourceimages\";\nworkspace -fr \"fileCache\" \"cache/nCache\";\n", "ext_mapping": { "model": "ma", "mayaAscii": "ma", From b4b62ce7ba793dcd812ea6cc40adcdb79b687aac Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 16 Sep 2022 14:48:56 +0200 Subject: [PATCH 033/201] Fix remainder of hardcoded maya/ folders in file prefixes --- openpype/hosts/maya/api/lib_renderproducts.py | 2 +- openpype/hosts/maya/plugins/publish/submit_maya_muster.py | 2 +- .../modules/deadline/plugins/publish/submit_maya_deadline.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/api/lib_renderproducts.py b/openpype/hosts/maya/api/lib_renderproducts.py index 1e883ea43f..1ab771cfe6 100644 --- a/openpype/hosts/maya/api/lib_renderproducts.py +++ b/openpype/hosts/maya/api/lib_renderproducts.py @@ -80,7 +80,7 @@ IMAGE_PREFIXES = { "mayahardware2": "defaultRenderGlobals.imageFilePrefix" } -RENDERMAN_IMAGE_DIR = "maya//" +RENDERMAN_IMAGE_DIR = "/" def has_tokens(string, tokens): diff --git a/openpype/hosts/maya/plugins/publish/submit_maya_muster.py b/openpype/hosts/maya/plugins/publish/submit_maya_muster.py index c4250a20bd..01008b7756 100644 --- a/openpype/hosts/maya/plugins/publish/submit_maya_muster.py +++ b/openpype/hosts/maya/plugins/publish/submit_maya_muster.py @@ -118,7 +118,7 @@ def preview_fname(folder, scene, layer, padding, ext): """ # Following hardcoded "/_/" - output = "maya/{scene}/{layer}/{layer}.{number}.{ext}".format( + output = "{scene}/{layer}/{layer}.{number}.{ext}".format( scene=scene, layer=layer, number="#" * padding, diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 44f2b5b2b4..3e3e5c5b16 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -729,10 +729,10 @@ def _format_tiles( Example:: Image prefix is: - `maya///_` + `//_` Result for tile 0 for 4x4 will be: - `maya///_tile_1x1_4x4__` + `//_tile_1x1_4x4__` Calculating coordinates is tricky as in Job they are defined as top, left, bottom, right with zero being in top-left corner. But Assembler From 8742acaef94ab0a2695e4637d66fccd783742bea Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 21 Sep 2022 23:56:10 +0200 Subject: [PATCH 034/201] Match logic of global thumbnail extractor to avoid overwriting source files --- .../maya/plugins/publish/extract_thumbnail.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py index 854301ea48..712159c2be 100644 --- a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py @@ -1,5 +1,6 @@ import os import glob +import tempfile import capture @@ -81,9 +82,17 @@ class ExtractThumbnail(publish.Extractor): elif asset_width and asset_height: preset['width'] = asset_width preset['height'] = asset_height - stagingDir = self.staging_dir(instance) + + # Create temp directory for thumbnail + # - this is to avoid "override" of source file + dst_staging = tempfile.mkdtemp(prefix="pyblish_tmp_") + self.log.debug( + "Create temp directory {} for thumbnail".format(dst_staging) + ) + # Store new staging to cleanup paths + instance.context.data["cleanupFullPaths"].append(dst_staging) filename = "{0}".format(instance.name) - path = os.path.join(stagingDir, filename) + path = os.path.join(dst_staging, filename) self.log.info("Outputting images to %s" % path) @@ -137,7 +146,7 @@ class ExtractThumbnail(publish.Extractor): 'name': 'thumbnail', 'ext': 'jpg', 'files': thumbnail, - "stagingDir": stagingDir, + "stagingDir": dst_staging, "thumbnail": True } instance.data["representations"].append(representation) From e97b6ce01f511b1cf240cb8640b871de3d79dc4e Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Fri, 23 Sep 2022 16:18:08 +0100 Subject: [PATCH 035/201] Fixed path resolving not finding the workfile in certain conditions In case the workfile only contains the project name, the workfile is not found because while the regex matches, the match doesn't have any group, and so it throws an exception. --- openpype/pipeline/workfile/path_resolving.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/pipeline/workfile/path_resolving.py b/openpype/pipeline/workfile/path_resolving.py index ed1d1d793e..4e4d3ca1c0 100644 --- a/openpype/pipeline/workfile/path_resolving.py +++ b/openpype/pipeline/workfile/path_resolving.py @@ -265,6 +265,10 @@ def get_last_workfile_with_version( if not match: continue + if not match.groups(): + output_filenames.append(filename) + continue + file_version = int(match.group(1)) if version is None or file_version > version: output_filenames[:] = [] From 548a37e4e1baf7f8cd9c4af61d0172caeec0f3ba Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Fri, 23 Sep 2022 16:18:52 +0100 Subject: [PATCH 036/201] Added setting to remove unmatched assets --- openpype/settings/defaults/project_settings/unreal.json | 1 + .../schemas/projects_schema/schema_project_unreal.json | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/openpype/settings/defaults/project_settings/unreal.json b/openpype/settings/defaults/project_settings/unreal.json index c5f5cdf719..391e2415a5 100644 --- a/openpype/settings/defaults/project_settings/unreal.json +++ b/openpype/settings/defaults/project_settings/unreal.json @@ -1,5 +1,6 @@ { "level_sequences_for_layouts": false, + "delete_unmatched_assets": false, "project_setup": { "dev_mode": true } diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json b/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json index d26b5c1ccf..09e5791ac4 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json @@ -10,6 +10,11 @@ "key": "level_sequences_for_layouts", "label": "Generate level sequences when loading layouts" }, + { + "type": "boolean", + "key": "delete_unmatched_assets", + "label": "Delete assets that are not matched" + }, { "type": "dict", "collapsible": true, From b42fb6aedb10934f5836d89e1734e32669671350 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Fri, 23 Sep 2022 16:24:21 +0100 Subject: [PATCH 037/201] Fixed problem with transformations from FBX files --- .../plugins/load/load_layout_existing.py | 52 ++++++++----------- 1 file changed, 22 insertions(+), 30 deletions(-) diff --git a/openpype/hosts/unreal/plugins/load/load_layout_existing.py b/openpype/hosts/unreal/plugins/load/load_layout_existing.py index 8cd1950f7e..9ab27d0cef 100644 --- a/openpype/hosts/unreal/plugins/load/load_layout_existing.py +++ b/openpype/hosts/unreal/plugins/load/load_layout_existing.py @@ -80,30 +80,22 @@ class ExistingLayoutLoader(plugin.Loader): raise NotImplementedError( f"Unreal version {ue_major} not supported") - @staticmethod - def _transform_from_basis(transform, basis, conversion): - """Transform a transform from a basis to a new basis.""" - # Get the basis matrix - basis_matrix = unreal.Matrix( - basis[0], - basis[1], - basis[2], - basis[3] - ) - transform_matrix = unreal.Matrix( - transform[0], - transform[1], - transform[2], - transform[3] - ) - - new_transform = ( - basis_matrix.get_inverse() * transform_matrix * basis_matrix) - - return conversion.inverse() * new_transform.transform() - def _get_transform(self, ext, import_data, lasset): conversion = unreal.Matrix.IDENTITY.transform() + fbx_tuning = unreal.Matrix.IDENTITY.transform() + + basis = unreal.Matrix( + lasset.get('basis')[0], + lasset.get('basis')[1], + lasset.get('basis')[2], + lasset.get('basis')[3] + ).transform() + transform = unreal.Matrix( + lasset.get('transform_matrix')[0], + lasset.get('transform_matrix')[1], + lasset.get('transform_matrix')[2], + lasset.get('transform_matrix')[3] + ).transform() # Check for the conversion settings. We cannot access # the alembic conversion settings, so we assume that @@ -111,11 +103,15 @@ class ExistingLayoutLoader(plugin.Loader): if ext == '.fbx': loc = import_data.import_translation rot = import_data.import_rotation.to_vector() - scale = import_data.import_scale + scale = import_data.import_uniform_scale conversion = unreal.Transform( location=[loc.x, loc.y, loc.z], rotation=[rot.x, rot.y, rot.z], - scale=[scale, scale, scale] + scale=[-scale, scale, scale] + ) + fbx_tuning = unreal.Transform( + rotation=[180.0, 0.0, 90.0], + scale=[1.0, 1.0, 1.0] ) elif ext == '.abc': # This is the standard conversion settings for @@ -126,12 +122,8 @@ class ExistingLayoutLoader(plugin.Loader): scale=[1.0, -1.0, 1.0] ) - transform = self._transform_from_basis( - lasset.get('transform_matrix'), - lasset.get('basis'), - conversion - ) - return transform + new_transform = (basis.inverse() * transform * basis) + return fbx_tuning * conversion.inverse() * new_transform def _spawn_actor(self, obj, lasset): actor = EditorLevelLibrary.spawn_actor_from_object( From a3eb15387108938f8536ed4b336e66c893e595aa Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Fri, 23 Sep 2022 16:34:05 +0100 Subject: [PATCH 038/201] Checks settings to determine if deleting or not unmatched assets --- .../plugins/load/load_layout_existing.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/unreal/plugins/load/load_layout_existing.py b/openpype/hosts/unreal/plugins/load/load_layout_existing.py index 9ab27d0cef..3ce99f8ef6 100644 --- a/openpype/hosts/unreal/plugins/load/load_layout_existing.py +++ b/openpype/hosts/unreal/plugins/load/load_layout_existing.py @@ -15,6 +15,7 @@ from openpype.pipeline import ( AVALON_CONTAINER_ID, legacy_io, ) +from openpype.api import get_current_project_settings from openpype.hosts.unreal.api import plugin from openpype.hosts.unreal.api import pipeline as upipeline @@ -147,7 +148,7 @@ class ExistingLayoutLoader(plugin.Loader): name = "" if family == 'rig': name = "SkeletalMeshFBXLoader" - elif family == 'model': + elif family == 'model' or family == 'staticMesh': name = "StaticMeshFBXLoader" elif family == 'camera': name = "CameraLoader" @@ -200,7 +201,8 @@ class ExistingLayoutLoader(plugin.Loader): loader = self._get_abc_loader(loaders, family) if not loader: - raise AssertionError(f"No valid loader found for {representation}") + self.log.error(f"No valid loader found for {representation}") + return [] # This option is necessary to avoid importing the assets with a # different conversion compared to the other assets. For ABC files, @@ -220,6 +222,9 @@ class ExistingLayoutLoader(plugin.Loader): return assets def _process(self, lib_path): + data = get_current_project_settings() + delete_unmatched = data["unreal"]["delete_unmatched_assets"] + ar = unreal.AssetRegistryHelpers.get_asset_registry() actors = EditorLevelLibrary.get_all_level_actors() @@ -264,16 +269,18 @@ class ExistingLayoutLoader(plugin.Loader): # Get the original path of the file from which the asset has # been imported. - actor.set_actor_label(lasset.get('instance_name')) smc = actor.get_editor_property('static_mesh_component') mesh = smc.get_editor_property('static_mesh') import_data = mesh.get_editor_property('asset_import_data') filename = import_data.get_first_filename() path = Path(filename) - if path.name not in repr_data.get('data').get('path'): + if (not path.name or + path.name not in repr_data.get('data').get('path')): continue + actor.set_actor_label(lasset.get('instance_name')) + mesh_path = Path(mesh.get_path_name()).parent.as_posix() # Create the container for the asset. @@ -352,7 +359,9 @@ class ExistingLayoutLoader(plugin.Loader): if not actor.get_class().get_name() == 'StaticMeshActor': continue if actor not in actors_matched: - EditorLevelLibrary.destroy_actor(actor) + self.log.warning(f"Actor {actor.get_name()} not matched.") + if delete_unmatched: + EditorLevelLibrary.destroy_actor(actor) return containers From 38387fc8997e579c1d4c8281f3810abc48059ce8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 23 Sep 2022 18:46:12 +0200 Subject: [PATCH 039/201] removed 'abstract_collect_render' --- openpype/lib/abstract_collect_render.py | 33 ------------------------- 1 file changed, 33 deletions(-) delete mode 100644 openpype/lib/abstract_collect_render.py diff --git a/openpype/lib/abstract_collect_render.py b/openpype/lib/abstract_collect_render.py deleted file mode 100644 index e4ff87aa0f..0000000000 --- a/openpype/lib/abstract_collect_render.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -*- -"""Content was moved to 'openpype.pipeline.publish.abstract_collect_render'. - -Please change your imports as soon as possible. - -File will be probably removed in OpenPype 3.14.* -""" - -import warnings -from openpype.pipeline.publish import AbstractCollectRender, RenderInstance - - -class CollectRenderDeprecated(DeprecationWarning): - pass - - -warnings.simplefilter("always", CollectRenderDeprecated) -warnings.warn( - ( - "Content of 'abstract_collect_render' was moved." - "\nUsing deprecated source of 'abstract_collect_render'. Content was" - " move to 'openpype.pipeline.publish.abstract_collect_render'." - " Please change your imports as soon as possible." - ), - category=CollectRenderDeprecated, - stacklevel=4 -) - - -__all__ = ( - "AbstractCollectRender", - "RenderInstance" -) From 97f368c3a5377e0972ba56bba99b90dc092be2bf Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 23 Sep 2022 18:46:23 +0200 Subject: [PATCH 040/201] removed 'abstract_expected_files' --- openpype/lib/abstract_expected_files.py | 32 ------------------------- 1 file changed, 32 deletions(-) delete mode 100644 openpype/lib/abstract_expected_files.py diff --git a/openpype/lib/abstract_expected_files.py b/openpype/lib/abstract_expected_files.py deleted file mode 100644 index f24d844fe5..0000000000 --- a/openpype/lib/abstract_expected_files.py +++ /dev/null @@ -1,32 +0,0 @@ -# -*- coding: utf-8 -*- -"""Content was moved to 'openpype.pipeline.publish.abstract_expected_files'. - -Please change your imports as soon as possible. - -File will be probably removed in OpenPype 3.14.* -""" - -import warnings -from openpype.pipeline.publish import ExpectedFiles - - -class ExpectedFilesDeprecated(DeprecationWarning): - pass - - -warnings.simplefilter("always", ExpectedFilesDeprecated) -warnings.warn( - ( - "Content of 'abstract_expected_files' was moved." - "\nUsing deprecated source of 'abstract_expected_files'. Content was" - " move to 'openpype.pipeline.publish.abstract_expected_files'." - " Please change your imports as soon as possible." - ), - category=ExpectedFilesDeprecated, - stacklevel=4 -) - - -__all__ = ( - "ExpectedFiles", -) From e21334463398df7eb2e199dc61c4f5eef85325d0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 23 Sep 2022 18:46:33 +0200 Subject: [PATCH 041/201] removed 'abstract_metaplugins' --- openpype/lib/abstract_metaplugins.py | 35 ---------------------------- 1 file changed, 35 deletions(-) delete mode 100644 openpype/lib/abstract_metaplugins.py diff --git a/openpype/lib/abstract_metaplugins.py b/openpype/lib/abstract_metaplugins.py deleted file mode 100644 index 346b5d86b3..0000000000 --- a/openpype/lib/abstract_metaplugins.py +++ /dev/null @@ -1,35 +0,0 @@ -"""Content was moved to 'openpype.pipeline.publish.publish_plugins'. - -Please change your imports as soon as possible. - -File will be probably removed in OpenPype 3.14.* -""" - -import warnings -from openpype.pipeline.publish import ( - AbstractMetaInstancePlugin, - AbstractMetaContextPlugin -) - - -class MetaPluginsDeprecated(DeprecationWarning): - pass - - -warnings.simplefilter("always", MetaPluginsDeprecated) -warnings.warn( - ( - "Content of 'abstract_metaplugins' was moved." - "\nUsing deprecated source of 'abstract_metaplugins'. Content was" - " moved to 'openpype.pipeline.publish.publish_plugins'." - " Please change your imports as soon as possible." - ), - category=MetaPluginsDeprecated, - stacklevel=4 -) - - -__all__ = ( - "AbstractMetaInstancePlugin", - "AbstractMetaContextPlugin", -) From 5f32cdc7a53e7ec3aa5dcf4df1fe0ddfc1d8ab43 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 23 Sep 2022 18:49:34 +0200 Subject: [PATCH 042/201] removed config --- openpype/api.py | 2 -- openpype/lib/config.py | 41 ----------------------------------------- 2 files changed, 43 deletions(-) delete mode 100644 openpype/lib/config.py diff --git a/openpype/api.py b/openpype/api.py index 0466eb7f78..b60cd21d2b 100644 --- a/openpype/api.py +++ b/openpype/api.py @@ -11,7 +11,6 @@ from .lib import ( PypeLogger, Logger, Anatomy, - config, execute, run_subprocess, version_up, @@ -72,7 +71,6 @@ __all__ = [ "PypeLogger", "Logger", "Anatomy", - "config", "execute", "get_default_components", "ApplicationManager", diff --git a/openpype/lib/config.py b/openpype/lib/config.py deleted file mode 100644 index 26822649e4..0000000000 --- a/openpype/lib/config.py +++ /dev/null @@ -1,41 +0,0 @@ -import warnings -import functools - - -class ConfigDeprecatedWarning(DeprecationWarning): - pass - - -def deprecated(func): - """Mark functions as deprecated. - - It will result in a warning being emitted when the function is used. - """ - - @functools.wraps(func) - def new_func(*args, **kwargs): - warnings.simplefilter("always", ConfigDeprecatedWarning) - warnings.warn( - ( - "Deprecated import of function '{}'." - " Class was moved to 'openpype.lib.dateutils.{}'." - " Please change your imports." - ).format(func.__name__), - category=ConfigDeprecatedWarning - ) - return func(*args, **kwargs) - return new_func - - -@deprecated -def get_datetime_data(datetime_obj=None): - from .dateutils import get_datetime_data - - return get_datetime_data(datetime_obj) - - -@deprecated -def get_formatted_current_time(): - from .dateutils import get_formatted_current_time - - return get_formatted_current_time() From c06ce65885f819475aa36b625f0f0d05f3de0b21 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 23 Sep 2022 18:54:23 +0200 Subject: [PATCH 043/201] removed 'editorial' --- openpype/lib/__init__.py | 23 --------- openpype/lib/editorial.py | 102 -------------------------------------- 2 files changed, 125 deletions(-) delete mode 100644 openpype/lib/editorial.py diff --git a/openpype/lib/__init__.py b/openpype/lib/__init__.py index 17aafc3e8b..a64b7c2911 100644 --- a/openpype/lib/__init__.py +++ b/openpype/lib/__init__.py @@ -203,19 +203,6 @@ from .path_tools import ( get_project_basic_paths, ) -from .editorial import ( - is_overlapping_otio_ranges, - otio_range_to_frame_range, - otio_range_with_handles, - get_media_range_with_retimes, - convert_to_padded_path, - trim_media_range, - range_from_frames, - frames_to_secons, - frames_to_timecode, - make_sequence_collection -) - from .openpype_version import ( op_version_control_available, get_openpype_version, @@ -383,16 +370,6 @@ __all__ = [ "validate_mongo_connection", "OpenPypeMongoConnection", - "is_overlapping_otio_ranges", - "otio_range_with_handles", - "convert_to_padded_path", - "otio_range_to_frame_range", - "get_media_range_with_retimes", - "trim_media_range", - "range_from_frames", - "frames_to_secons", - "frames_to_timecode", - "make_sequence_collection", "create_project_folders", "create_workdir_extra_folders", "get_project_basic_paths", diff --git a/openpype/lib/editorial.py b/openpype/lib/editorial.py deleted file mode 100644 index 49220b4f15..0000000000 --- a/openpype/lib/editorial.py +++ /dev/null @@ -1,102 +0,0 @@ -"""Code related to editorial utility functions was moved -to 'openpype.pipeline.editorial' please change your imports as soon as -possible. File will be probably removed in OpenPype 3.14.* -""" - -import warnings -import functools - - -class EditorialDeprecatedWarning(DeprecationWarning): - pass - - -def editorial_deprecated(func): - """Mark functions as deprecated. - - It will result in a warning being emitted when the function is used. - """ - - @functools.wraps(func) - def new_func(*args, **kwargs): - warnings.simplefilter("always", EditorialDeprecatedWarning) - warnings.warn( - ( - "Call to deprecated function '{}'." - " Function was moved to 'openpype.pipeline.editorial'." - ).format(func.__name__), - category=EditorialDeprecatedWarning, - stacklevel=2 - ) - return func(*args, **kwargs) - return new_func - - -@editorial_deprecated -def otio_range_to_frame_range(*args, **kwargs): - from openpype.pipeline.editorial import otio_range_to_frame_range - - return otio_range_to_frame_range(*args, **kwargs) - - -@editorial_deprecated -def otio_range_with_handles(*args, **kwargs): - from openpype.pipeline.editorial import otio_range_with_handles - - return otio_range_with_handles(*args, **kwargs) - - -@editorial_deprecated -def is_overlapping_otio_ranges(*args, **kwargs): - from openpype.pipeline.editorial import is_overlapping_otio_ranges - - return is_overlapping_otio_ranges(*args, **kwargs) - - -@editorial_deprecated -def convert_to_padded_path(*args, **kwargs): - from openpype.pipeline.editorial import convert_to_padded_path - - return convert_to_padded_path(*args, **kwargs) - - -@editorial_deprecated -def trim_media_range(*args, **kwargs): - from openpype.pipeline.editorial import trim_media_range - - return trim_media_range(*args, **kwargs) - - -@editorial_deprecated -def range_from_frames(*args, **kwargs): - from openpype.pipeline.editorial import range_from_frames - - return range_from_frames(*args, **kwargs) - - -@editorial_deprecated -def frames_to_secons(*args, **kwargs): - from openpype.pipeline.editorial import frames_to_seconds - - return frames_to_seconds(*args, **kwargs) - - -@editorial_deprecated -def frames_to_timecode(*args, **kwargs): - from openpype.pipeline.editorial import frames_to_timecode - - return frames_to_timecode(*args, **kwargs) - - -@editorial_deprecated -def make_sequence_collection(*args, **kwargs): - from openpype.pipeline.editorial import make_sequence_collection - - return make_sequence_collection(*args, **kwargs) - - -@editorial_deprecated -def get_media_range_with_retimes(*args, **kwargs): - from openpype.pipeline.editorial import get_media_range_with_retimes - - return get_media_range_with_retimes(*args, **kwargs) From affd54bd1ae6603fb0e59fffc3c39c2b08c85e41 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 28 Sep 2022 18:45:47 +0800 Subject: [PATCH 044/201] write color sets --- openpype/hosts/maya/plugins/create/create_rig.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_rig.py b/openpype/hosts/maya/plugins/create/create_rig.py index 3b0ee1e22a..8032e5fbbd 100644 --- a/openpype/hosts/maya/plugins/create/create_rig.py +++ b/openpype/hosts/maya/plugins/create/create_rig.py @@ -13,18 +13,12 @@ class CreateRig(plugin.Creator): label = "Rig" family = "rig" icon = "wheelchair" - write_color_sets = False - write_face_sets = False - - def __init__(self, *args, **kwargs): - super(CreateRig, self).__init__(*args, **kwargs) - self.data["writeColorSets"] = self.write_color_sets - self.data["writeFaceSets"] = self.write_face_sets def process(self): with lib.undo_chunk(): instance = super(CreateRig, self).process() + self.log.info("Creating Rig instance set up ...") controls = cmds.sets(name="controls_SET", empty=True) pointcache = cmds.sets(name="out_SET", empty=True) From 001922edd6da1ba8937e5e02b45cc3ff266b9996 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 29 Sep 2022 17:51:25 +0200 Subject: [PATCH 045/201] Do not enforce a required prefix - allow to be defined by settings. --- .../maya/plugins/publish/validate_rendersettings.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py index 2f98a456a1..88a49be00c 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py +++ b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py @@ -123,7 +123,6 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): prefix = prefix.replace( "{aov_separator}", instance.data.get("aovSeparator", "_")) - required_prefix = "" default_prefix = cls.ImagePrefixTokens[renderer] if not anim_override: @@ -131,15 +130,6 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): cls.log.error("Animation needs to be enabled. Use the same " "frame for start and end to render single frame") - if renderer != "renderman" and not prefix.lower().startswith( - required_prefix): - invalid = True - cls.log.error( - ("Wrong image prefix [ {} ] " - " - doesn't start with: '{}'").format( - prefix, required_prefix) - ) - if not re.search(cls.R_LAYER_TOKEN, prefix): invalid = True cls.log.error("Wrong image prefix [ {} ] - " From 8f04c3351b51508245ad50780e511ac20aff2b90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Wed, 5 Oct 2022 12:12:53 +0200 Subject: [PATCH 046/201] Fix: 2 fixes, nb_frames and Shot type error --- .../modules/kitsu/utils/update_op_with_zou.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index 10e80b3c89..4cdc53497b 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -115,7 +115,8 @@ def update_op_assets( item_data["frameStart"] = frame_in # Frames duration, fallback on 0 try: - frames_duration = int(item_data.pop("nb_frames", 0)) + # NOTE nb_frames is stored directly in item because of zou's legacy design + frames_duration = int(item.get("nb_frames", 0)) except (TypeError, ValueError): frames_duration = 0 # Frame out, fallback on frame_in + duration or project's value or 1001 @@ -170,7 +171,7 @@ def update_op_assets( # Substitute item type for general classification (assets or shots) if item_type in ["Asset", "AssetType"]: entity_root_asset_name = "Assets" - elif item_type in ["Episode", "Sequence"]: + elif item_type in ["Episode", "Sequence", "Shot"]: entity_root_asset_name = "Shots" # Root parent folder if exist @@ -276,11 +277,13 @@ def write_project_to_op(project: dict, dbcon: AvalonMongoDB) -> UpdateOne: match_res = re.match(r"(\d+)x(\d+)", project["resolution"]) if match_res: - project_data['resolutionWidth'] = int(match_res.group(1)) - project_data['resolutionHeight'] = int(match_res.group(2)) + project_data["resolutionWidth"] = int(match_res.group(1)) + project_data["resolutionHeight"] = int(match_res.group(2)) else: - log.warning(f"\'{project['resolution']}\' does not match the expected" - " format for the resolution, for example: 1920x1080") + log.warning( + f"'{project['resolution']}' does not match the expected" + " format for the resolution, for example: 1920x1080" + ) return UpdateOne( {"_id": project_doc["_id"]}, From 0337403182459fc7babc7386242d61fc3ac261f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Wed, 5 Oct 2022 12:17:19 +0200 Subject: [PATCH 047/201] line length --- openpype/modules/kitsu/utils/update_op_with_zou.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index 4cdc53497b..a0cacc11cb 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -115,7 +115,8 @@ def update_op_assets( item_data["frameStart"] = frame_in # Frames duration, fallback on 0 try: - # NOTE nb_frames is stored directly in item because of zou's legacy design + # NOTE nb_frames is stored directly in item because + # of zou's legacy design frames_duration = int(item.get("nb_frames", 0)) except (TypeError, ValueError): frames_duration = 0 From 4aec32af91334021d3f943f6886343b5d3490ef9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Wed, 5 Oct 2022 12:30:20 +0200 Subject: [PATCH 048/201] line length --- openpype/modules/kitsu/utils/update_op_with_zou.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index a0cacc11cb..2d14b38bc4 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -115,8 +115,8 @@ def update_op_assets( item_data["frameStart"] = frame_in # Frames duration, fallback on 0 try: - # NOTE nb_frames is stored directly in item because - # of zou's legacy design + # NOTE nb_frames is stored directly in item + # because of zou's legacy design frames_duration = int(item.get("nb_frames", 0)) except (TypeError, ValueError): frames_duration = 0 From ff2453c70d04bf38762736d9f7168d159e91379c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 5 Oct 2022 17:22:04 +0200 Subject: [PATCH 049/201] PublisherController can be imported without import of Qt --- openpype/tools/publisher/__init__.py | 7 -- openpype/tools/publisher/control.py | 75 +++------------------- openpype/tools/publisher/control_qt.py | 88 ++++++++++++++++++++++++++ openpype/tools/publisher/window.py | 7 +- 4 files changed, 102 insertions(+), 75 deletions(-) create mode 100644 openpype/tools/publisher/control_qt.py diff --git a/openpype/tools/publisher/__init__.py b/openpype/tools/publisher/__init__.py index a7b597eece..e69de29bb2 100644 --- a/openpype/tools/publisher/__init__.py +++ b/openpype/tools/publisher/__init__.py @@ -1,7 +0,0 @@ -from .app import show -from .window import PublisherWindow - -__all__ = ( - "show", - "PublisherWindow" -) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 481fb5981b..af0556afc5 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -14,14 +14,13 @@ from openpype.pipeline import ( ) from openpype.pipeline.create import CreateContext -from Qt import QtCore - # Define constant for plugin orders offset PLUGIN_ORDER_OFFSET = 0.5 class MainThreadItem: """Callback with args and kwargs.""" + def __init__(self, callback, *args, **kwargs): self.callback = callback self.args = args @@ -31,64 +30,9 @@ class MainThreadItem: self.callback(*self.args, **self.kwargs) -class MainThreadProcess(QtCore.QObject): - """Qt based main thread process executor. - - Has timer which controls each 50ms if there is new item to process. - - This approach gives ability to update UI meanwhile plugin is in progress. - """ - - count_timeout = 2 - - def __init__(self): - super(MainThreadProcess, self).__init__() - self._items_to_process = collections.deque() - - timer = QtCore.QTimer() - timer.setInterval(0) - - timer.timeout.connect(self._execute) - - self._timer = timer - self._switch_counter = self.count_timeout - - def process(self, func, *args, **kwargs): - item = MainThreadItem(func, *args, **kwargs) - self.add_item(item) - - def add_item(self, item): - self._items_to_process.append(item) - - def _execute(self): - if not self._items_to_process: - return - - if self._switch_counter > 0: - self._switch_counter -= 1 - return - - self._switch_counter = self.count_timeout - - item = self._items_to_process.popleft() - item.process() - - def start(self): - if not self._timer.isActive(): - self._timer.start() - - def stop(self): - if self._timer.isActive(): - self._timer.stop() - - def clear(self): - if self._timer.isActive(): - self._timer.stop() - self._items_to_process = collections.deque() - - class AssetDocsCache: """Cache asset documents for creation part.""" + projection = { "_id": True, "name": True, @@ -133,6 +77,7 @@ class PublishReport: Report keeps current state of publishing and currently processed plugin. """ + def __init__(self, controller): self.controller = controller self._publish_discover_result = None @@ -341,7 +286,7 @@ class PublishReport: return output -class PublisherController: +class PublisherController(object): """Middleware between UI, CreateContext and publish Context. Handle both creation and publishing parts. @@ -394,8 +339,6 @@ class PublisherController: pyblish.api.ValidatorOrder + PLUGIN_ORDER_OFFSET ) - # Qt based main thread processor - self._main_thread_processor = MainThreadProcess() # Plugin iterator self._main_thread_iter = None @@ -744,7 +687,7 @@ class PublisherController: self._publish_up_validation = False self._publish_finished = False self._publish_comment_is_set = False - self._main_thread_processor.clear() + self._main_thread_iter = self._publish_iterator() self._publish_context = pyblish.api.Context() # Make sure "comment" is set on publish context @@ -792,13 +735,12 @@ class PublisherController: self._publish_is_running = True self._emit_event("publish.process.started") - self._main_thread_processor.start() + self._publish_next_process() def _stop_publish(self): """Stop or pause publishing.""" self._publish_is_running = False - self._main_thread_processor.stop() self._emit_event("publish.process.stopped") @@ -837,7 +779,10 @@ class PublisherController: else: item = next(self._main_thread_iter) - self._main_thread_processor.add_item(item) + self._process_main_thread_item(item) + + def _process_main_thread_item(self, item): + item() def _publish_iterator(self): """Main logic center of publishing. diff --git a/openpype/tools/publisher/control_qt.py b/openpype/tools/publisher/control_qt.py new file mode 100644 index 0000000000..add7c4c7e4 --- /dev/null +++ b/openpype/tools/publisher/control_qt.py @@ -0,0 +1,88 @@ +import collections + +from Qt import QtCore + +from .control import MainThreadItem, PublisherController + + +class MainThreadProcess(QtCore.QObject): + """Qt based main thread process executor. + + Has timer which controls each 50ms if there is new item to process. + + This approach gives ability to update UI meanwhile plugin is in progress. + """ + + count_timeout = 2 + + def __init__(self): + super(MainThreadProcess, self).__init__() + self._items_to_process = collections.deque() + + timer = QtCore.QTimer() + timer.setInterval(0) + + timer.timeout.connect(self._execute) + + self._timer = timer + self._switch_counter = self.count_timeout + + def process(self, func, *args, **kwargs): + item = MainThreadItem(func, *args, **kwargs) + self.add_item(item) + + def add_item(self, item): + self._items_to_process.append(item) + + def _execute(self): + if not self._items_to_process: + return + + if self._switch_counter > 0: + self._switch_counter -= 1 + return + + self._switch_counter = self.count_timeout + + item = self._items_to_process.popleft() + item.process() + + def start(self): + if not self._timer.isActive(): + self._timer.start() + + def stop(self): + if self._timer.isActive(): + self._timer.stop() + + def clear(self): + if self._timer.isActive(): + self._timer.stop() + self._items_to_process = collections.deque() + + +class QtPublisherController(PublisherController): + def __init__(self, *args, **kwargs): + self._main_thread_processor = MainThreadProcess() + + super(QtPublisherController, self).__init__(*args, **kwargs) + + self._event_system.add_callback( + "publish.process.started", self._qt_on_publish_start + ) + self._event_system.add_callback( + "publish.process.stopped", self._qt_on_publish_stop + ) + + def _reset_publish(self): + super(QtPublisherController, self)._reset_publish() + self._main_thread_processor.clear() + + def _process_main_thread_item(self, item): + self._main_thread_processor.add_item(item) + + def _qt_on_publish_start(self): + self._main_thread_processor.start() + + def _qt_on_publish_stop(self): + self._main_thread_processor.stop() diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index aa5f08eed4..699cf6f1f9 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -11,7 +11,7 @@ from openpype.tools.utils import ( ) from .publish_report_viewer import PublishReportViewerWidget -from .control import PublisherController +from .control_qt import QtPublisherController from .widgets import ( OverviewWidget, ValidationsWidget, @@ -34,7 +34,7 @@ class PublisherWindow(QtWidgets.QDialog): default_width = 1300 default_height = 800 - def __init__(self, parent=None, reset_on_show=None): + def __init__(self, parent=None, controller=None, reset_on_show=None): super(PublisherWindow, self).__init__(parent) self.setWindowTitle("OpenPype publisher") @@ -59,7 +59,8 @@ class PublisherWindow(QtWidgets.QDialog): | on_top_flag ) - controller = PublisherController() + if controller is None: + controller = QtPublisherController() help_dialog = HelpDialog(controller, self) From a00dafb4b6cfa9fa5c1035ac320fbb4c429a45e3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 5 Oct 2022 17:30:47 +0200 Subject: [PATCH 050/201] change few attributes to private --- openpype/tools/publisher/control.py | 63 ++++++++++++++++------------- 1 file changed, 35 insertions(+), 28 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index af0556afc5..9abcc620a8 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -296,16 +296,17 @@ class PublisherController(object): headless (bool): Headless publishing. ATM not implemented or used. """ + _log = None + def __init__(self, dbcon=None, headless=False): - self.log = logging.getLogger("PublisherController") - self.host = registered_host() - self.headless = headless + self._host = registered_host() + self._headless = headless # Inner event system of controller self._event_system = EventSystem() - self.create_context = CreateContext( - self.host, dbcon, headless=headless, reset=False + self._create_context = CreateContext( + self._host, dbcon, headless=headless, reset=False ) # pyblish.api.Context @@ -349,6 +350,12 @@ class PublisherController(object): # Cacher of avalon documents self._asset_docs_cache = AssetDocsCache(self) + @property + def log(self): + if self._log is None: + self._log = logging.getLogger("PublisherController") + return self._log + @property def project_name(self): """Current project context defined by host. @@ -357,7 +364,7 @@ class PublisherController(object): str: Project name. """ - return self.host.get_current_context()["project_name"] + return self._host.get_current_context()["project_name"] @property def current_asset_name(self): @@ -367,7 +374,7 @@ class PublisherController(object): Union[str, None]: Asset name or None if asset is not set. """ - return self.host.get_current_context()["asset_name"] + return self._host.get_current_context()["asset_name"] @property def current_task_name(self): @@ -377,37 +384,37 @@ class PublisherController(object): Union[str, None]: Task name or None if task is not set. """ - return self.host.get_current_context()["task_name"] + return self._host.get_current_context()["task_name"] @property def instances(self): """Current instances in create context.""" - return self.create_context.instances + return self._create_context.instances @property def creators(self): """All creators loaded in create context.""" - return self.create_context.creators + return self._create_context.creators @property def manual_creators(self): """Creators that can be shown in create dialog.""" - return self.create_context.manual_creators + return self._create_context.manual_creators @property def host_is_valid(self): """Host is valid for creation.""" - return self.create_context.host_is_valid + return self._create_context.host_is_valid @property def publish_plugins(self): """Publish plugins.""" - return self.create_context.publish_plugins + return self._create_context.publish_plugins @property def plugins_with_defs(self): """Publish plugins with possible attribute definitions.""" - return self.create_context.plugins_with_defs + return self._create_context.plugins_with_defs @property def event_system(self): @@ -445,8 +452,8 @@ class PublisherController(object): def get_context_title(self): """Get context title for artist shown at the top of main window.""" context_title = None - if hasattr(self.host, "get_context_title"): - context_title = self.host.get_context_title() + if hasattr(self._host, "get_context_title"): + context_title = self._host.get_context_title() if context_title is None: context_title = os.environ.get("AVALON_APP_NAME") @@ -486,7 +493,7 @@ class PublisherController(object): self.save_changes() # Reset avalon context - self.create_context.reset_avalon_context() + self._create_context.reset_avalon_context() self._reset_plugins() # Publish part must be reset after plugins @@ -502,7 +509,7 @@ class PublisherController(object): self._resetting_plugins = True - self.create_context.reset_plugins() + self._create_context.reset_plugins() self._resetting_plugins = False @@ -515,10 +522,10 @@ class PublisherController(object): self._resetting_instances = True - self.create_context.reset_context_data() - with self.create_context.bulk_instances_collection(): - self.create_context.reset_instances() - self.create_context.execute_autocreators() + self._create_context.reset_context_data() + with self._create_context.bulk_instances_collection(): + self._create_context.reset_instances() + self._create_context.execute_autocreators() self._resetting_instances = False @@ -567,7 +574,7 @@ class PublisherController(object): """ _tmp_items = [] if include_context: - _tmp_items.append(self.create_context) + _tmp_items.append(self._create_context) for instance in instances: _tmp_items.append(instance) @@ -626,8 +633,8 @@ class PublisherController(object): def save_changes(self): """Save changes happened during creation.""" - if self.create_context.host_is_valid: - self.create_context.save_changes() + if self._create_context.host_is_valid: + self._create_context.save_changes() def remove_instances(self, instances): """""" @@ -635,7 +642,7 @@ class PublisherController(object): # reset is not required and save changes too. self.save_changes() - self.create_context.remove_instances(instances) + self._create_context.remove_instances(instances) self._emit_event("instances.refresh.finished") @@ -696,9 +703,9 @@ class PublisherController(object): # - must not be used for changing CreatedInstances during publishing! # QUESTION # - pop the key after first collector using it would be safest option? - self._publish_context.data["create_context"] = self.create_context + self._publish_context.data["create_context"] = self._create_context - self._publish_report.reset(self._publish_context, self.create_context) + self._publish_report.reset(self._publish_context, self._create_context) self._publish_validation_errors = [] self._publish_current_plugin_validation_errors = None self._publish_error = None From 92cd6b60dfb750e562a1aba61020b4b5c077d083 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 6 Oct 2022 13:47:22 +0200 Subject: [PATCH 051/201] added abstract controller for UI --- openpype/tools/publisher/control.py | 236 +++++++++++++++++++++++++++- 1 file changed, 235 insertions(+), 1 deletion(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 9abcc620a8..09f6555d69 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -3,7 +3,9 @@ import copy import logging import traceback import collections +from abc import ABCMeta, abstractmethod, abstractproperty +import six import pyblish.api from openpype.client import get_assets @@ -286,7 +288,239 @@ class PublishReport: return output -class PublisherController(object): + + +@six.add_metaclass(ABCMeta) +class AbstractPublisherController(object): + """Publisher tool controller. + + Define what must be implemented to be able use Publisher functionality. + + Goal is to have "data driven" controller that can be used to control UI + running in different process. That lead to some "" + """ + + _log = None + _event_system = None + + @property + def log(self): + """Controller's logger object. + + Returns: + logging.Logger: Logger object that can be used for logging. + """ + + if self._log is None: + self._log = logging.getLogget(self.__class__.__name__) + return self._log + + @property + def event_system(self): + """Inner event system for publisher controller. + + Event system is autocreated. + + Known topics: + "show.detailed.help" - Detailed help requested (UI related). + "show.card.message" - Show card message request (UI related). + "instances.refresh.finished" - Instances are refreshed. + "plugins.refresh.finished" - Plugins refreshed. + "publish.reset.finished" - Controller reset finished. + "publish.process.started" - Publishing started. Can be started from + paused state. + "publish.process.validated" - Publishing passed validation. + "publish.process.stopped" - Publishing stopped/paused process. + "publish.process.plugin.changed" - Plugin state has changed. + "publish.process.instance.changed" - Instance state has changed. + + Returns: + EventSystem: Event system which can trigger callbacks for topics. + """ + + if self._event_system is None: + self._event_system = EventSystem() + return self._event_system + + @abstractproperty + def project_name(self): + """Current context project name. + + Returns: + str: Name of project. + """ + + pass + + @abstractproperty + def current_asset_name(self): + """Current context asset name. + + Returns: + Union[str, None]: Name of asset. + """ + + pass + + @abstractproperty + def current_task_name(self): + """Current context task name. + + Returns: + Union[str, None]: Name of task. + """ + + pass + + @abstractproperty + def instances(self): + """Collected/created instances. + + Returns: + List[CreatedInstance]: List of created instances. + """ + + pass + + @abstractmethod + def get_manual_creators_base_info(self): + """Creators that can be selected and triggered by artist. + + Returns: + List[CreatorBaseInfo]: Base information about creator plugin. + """ + + pass + + @abstractmethod + def get_context_title(self): + """Get context title for artist shown at the top of main window. + + Returns: + Union[str, None]: Context title for window or None. In case of None + a warning is displayed (not nice for artists). + """ + + pass + + @abstractmethod + def get_asset_docs(self): + pass + + @abstractmethod + def get_asset_hierarchy(self): + pass + + @abstractmethod + def get_task_names_by_asset_names(self, asset_names): + pass + + @abstractmethod + def reset(self): + pass + + @abstractmethod + def emit_card_message(self, message): + pass + + @abstractmethod + def get_creator_attribute_definitions(self, instances): + pass + + @abstractmethod + def get_publish_attribute_definitions(self, instances, include_context): + pass + + @abstractmethod + def get_icon_for_family(self, family): + pass + + @abstractmethod + def create( + self, creator_identifier, subset_name, instance_data, options + ): + pass + + def save_changes(self): + """Save changes happened during creation.""" + + pass + + def remove_instances(self, instances): + """Remove list of instances.""" + + pass + + @abstractproperty + def publish_has_finished(self): + pass + + @abstractproperty + def publish_is_running(self): + pass + + @abstractproperty + def publish_has_validated(self): + pass + + @abstractproperty + def publish_has_crashed(self): + pass + + @abstractproperty + def publish_has_validation_errors(self): + pass + + @abstractproperty + def publish_max_progress(self): + pass + + @abstractproperty + def publish_progress(self): + pass + + @abstractproperty + def publish_comment_is_set(self): + pass + + @abstractmethod + def get_publish_crash_error(self): + pass + + @abstractmethod + def get_publish_report(self): + pass + + @abstractmethod + def get_validation_errors(self): + pass + + @abstractmethod + def set_comment(self, comment): + pass + + @abstractmethod + def publish(self): + pass + + @abstractmethod + def validate(self): + pass + + @abstractmethod + def stop_publish(self): + pass + + @abstractmethod + def run_action(self, plugin, action): + pass + + @abstractmethod + def reset_project_data_cache(self): + pass + + +class PublisherController(AbstractPublisherController): """Middleware between UI, CreateContext and publish Context. Handle both creation and publishing parts. From 6397db6e7956703de0776a90cb090d6f70bcabd7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 6 Oct 2022 15:14:11 +0200 Subject: [PATCH 052/201] removed 'plugins_with_defs' attribute --- openpype/tools/publisher/control.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 09f6555d69..a5a7539369 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -645,11 +645,6 @@ class PublisherController(AbstractPublisherController): """Publish plugins.""" return self._create_context.publish_plugins - @property - def plugins_with_defs(self): - """Publish plugins with possible attribute definitions.""" - return self._create_context.plugins_with_defs - @property def event_system(self): """Inner event system for publisher controller. @@ -838,7 +833,7 @@ class PublisherController(AbstractPublisherController): attr_values.append((item, value)) output = [] - for plugin in self.plugins_with_defs: + for plugin in self._create_context.plugins_with_defs: plugin_name = plugin.__name__ if plugin_name not in all_defs_by_plugin_name: continue From 80103e60e8ffb431ab0696ce5e396096f5d0faeb Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 6 Oct 2022 15:14:59 +0200 Subject: [PATCH 053/201] changed 'creators' attribute to '_creators' --- openpype/tools/publisher/control.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index a5a7539369..c2816757d4 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -626,8 +626,9 @@ class PublisherController(AbstractPublisherController): return self._create_context.instances @property - def creators(self): + def _creators(self): """All creators loaded in create context.""" + return self._create_context.creators @property @@ -846,7 +847,7 @@ class PublisherController(AbstractPublisherController): def get_icon_for_family(self, family): """TODO rename to get creator icon.""" - creator = self.creators.get(family) + creator = self._creators.get(family) if creator is not None: return creator.get_icon() return None @@ -855,7 +856,7 @@ class PublisherController(AbstractPublisherController): self, creator_identifier, subset_name, instance_data, options ): """Trigger creation and refresh of instances in UI.""" - creator = self.creators[creator_identifier] + creator = self._creators[creator_identifier] creator.create(subset_name, instance_data, options) self._emit_event("instances.refresh.finished") From 71cca8e74288284135484183ae24647acdfa5dea Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 6 Oct 2022 15:15:46 +0200 Subject: [PATCH 054/201] changed 'publish_plugins' attribute to '_publish_plugins' --- openpype/tools/publisher/control.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index c2816757d4..6a73989ae8 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -642,7 +642,7 @@ class PublisherController(AbstractPublisherController): return self._create_context.host_is_valid @property - def publish_plugins(self): + def _publish_plugins(self): """Publish plugins.""" return self._create_context.publish_plugins @@ -681,6 +681,7 @@ class PublisherController(AbstractPublisherController): def get_context_title(self): """Get context title for artist shown at the top of main window.""" + context_title = None if hasattr(self._host, "get_context_title"): context_title = self._host.get_context_title() @@ -913,7 +914,7 @@ class PublisherController(AbstractPublisherController): return self._publish_error def get_publish_report(self): - return self._publish_report.get_report(self.publish_plugins) + return self._publish_report.get_report(self._publish_plugins) def get_validation_errors(self): return self._publish_validation_errors @@ -940,7 +941,7 @@ class PublisherController(AbstractPublisherController): self._publish_current_plugin_validation_errors = None self._publish_error = None - self._publish_max_progress = len(self.publish_plugins) + self._publish_max_progress = len(self._publish_plugins) self._publish_progress = 0 self._emit_event("publish.reset.finished") @@ -1034,7 +1035,7 @@ class PublisherController(AbstractPublisherController): QUESTION: Does validate button still make sense? """ - for idx, plugin in enumerate(self.publish_plugins): + for idx, plugin in enumerate(self._publish_plugins): self._publish_progress = idx # Reset current plugin validations error From c232e812396cd00681b6badd9a49f63931d96b44 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 6 Oct 2022 15:17:11 +0200 Subject: [PATCH 055/201] removed doubled event system --- openpype/tools/publisher/control.py | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 6a73989ae8..57098f8734 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -536,9 +536,6 @@ class PublisherController(AbstractPublisherController): self._host = registered_host() self._headless = headless - # Inner event system of controller - self._event_system = EventSystem() - self._create_context = CreateContext( self._host, dbcon, headless=headless, reset=False ) @@ -646,29 +643,6 @@ class PublisherController(AbstractPublisherController): """Publish plugins.""" return self._create_context.publish_plugins - @property - def event_system(self): - """Inner event system for publisher controller. - - Known topics: - "show.detailed.help" - Detailed help requested (UI related). - "show.card.message" - Show card message request (UI related). - "instances.refresh.finished" - Instances are refreshed. - "plugins.refresh.finished" - Plugins refreshed. - "publish.reset.finished" - Controller reset finished. - "publish.process.started" - Publishing started. Can be started from - paused state. - "publish.process.validated" - Publishing passed validation. - "publish.process.stopped" - Publishing stopped/paused process. - "publish.process.plugin.changed" - Plugin state has changed. - "publish.process.instance.changed" - Instance state has changed. - - Returns: - EventSystem: Event system which can trigger callbacks for topics. - """ - - return self._event_system - def _emit_event(self, topic, data=None): if data is None: data = {} From 5b75511a6064c14c7532edddf50266d638616526 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 6 Oct 2022 18:53:24 +0200 Subject: [PATCH 056/201] traypublisher has it's controller --- openpype/tools/traypublisher/window.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/openpype/tools/traypublisher/window.py b/openpype/tools/traypublisher/window.py index b1ff3c7383..be9f12e269 100644 --- a/openpype/tools/traypublisher/window.py +++ b/openpype/tools/traypublisher/window.py @@ -15,6 +15,7 @@ import appdirs from openpype.lib import JSONSettingRegistry from openpype.pipeline import install_host from openpype.hosts.traypublisher.api import TrayPublisherHost +from openpype.tools.publisher.control_qt import QtPublisherController from openpype.tools.publisher.window import PublisherWindow from openpype.tools.utils import PlaceholderLineEdit from openpype.tools.utils.constants import PROJECT_NAME_ROLE @@ -24,6 +25,12 @@ from openpype.tools.utils.models import ( ) +class TrayPublisherController(QtPublisherController): + @property + def host(self): + return self._host + + class TrayPublisherRegistry(JSONSettingRegistry): """Class handling OpenPype general settings registry. @@ -179,7 +186,10 @@ class StandaloneOverlayWidget(QtWidgets.QFrame): class TrayPublishWindow(PublisherWindow): def __init__(self, *args, **kwargs): - super(TrayPublishWindow, self).__init__(reset_on_show=False) + controller = TrayPublisherController() + super(TrayPublishWindow, self).__init__( + controller=controller, reset_on_show=False + ) flags = self.windowFlags() # Disable always on top hint From 054b87bd687d41f2bc9e7fa7387c389c10da3112 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 7 Oct 2022 11:20:10 +0200 Subject: [PATCH 057/201] fix event system access in qt controller --- openpype/tools/publisher/control_qt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/publisher/control_qt.py b/openpype/tools/publisher/control_qt.py index add7c4c7e4..8515a7a843 100644 --- a/openpype/tools/publisher/control_qt.py +++ b/openpype/tools/publisher/control_qt.py @@ -67,10 +67,10 @@ class QtPublisherController(PublisherController): super(QtPublisherController, self).__init__(*args, **kwargs) - self._event_system.add_callback( + self.event_system.add_callback( "publish.process.started", self._qt_on_publish_start ) - self._event_system.add_callback( + self.event_system.add_callback( "publish.process.stopped", self._qt_on_publish_stop ) From f13d2bc9653726dce4c3db4375b9c288d7e79bb6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 7 Oct 2022 11:20:46 +0200 Subject: [PATCH 058/201] implemented helper publish plugins proxy to handle actions for plugins --- openpype/tools/publisher/control.py | 89 +++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 57098f8734..c084cba381 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -288,6 +288,95 @@ class PublishReport: return output +class PublishPluginsProxy: + """Wrapper around publish plugin. + + Prepare mapping for publish plugins and actions. Also can create + serializable data for plugin actions so UI don't have to have access to + them. + + This object is created in process where publishing is actually running. + + Notes: + Actions have id but single action can be used on multiple plugins so + to run an action is needed combination of plugin and action. + + Args: + plugins [List[pyblish.api.Plugin]]: Discovered plugins that will be + processed. + """ + + def __init__(self, plugins): + plugins_by_id = {} + actions_by_id = {} + action_ids_by_plugin_id = {} + for plugin in plugins: + plugin_id = plugin.id + plugins_by_id[plugin_id] = plugin + + action_ids = set() + action_ids_by_plugin_id[plugin_id] = action_ids + + actions = getattr(plugin, "actions", None) or [] + for action in actions: + action_id = action.id + action_ids.add(action_id) + actions_by_id[action_id] = action + + self._plugins_by_id = plugins_by_id + self._actions_by_id = actions_by_id + self._action_ids_by_plugin_id = action_ids_by_plugin_id + + def get_action(self, action_id): + return self._actions_by_id[action_id] + + def get_plugin(self, plugin_id): + return self._plugins_by_id[plugin_id] + + def get_plugin_id(self, plugin): + """Get id of plugin based on plugin object. + + It's used for validation errors report. + + Args: + plugin (pyblish.api.Plugin): Publish plugin for which id should be + returned. + + Returns: + str: Plugin id. + """ + + return plugin.id + + def get_plugin_action_items(self, plugin_id): + """Get plugin action items for plugin by it's id. + + Args: + plugin_id (str): Publish plugin id. + + Returns: + List[PublishPluginActionItem]: Items with information about publish + plugin actions. + """ + + return [ + self._create_action_item(self._actions_by_id[action_id], plugin_id) + for action_id in self._action_ids_by_plugin_id[plugin_id] + ] + + def _create_action_item(self, action, plugin_id): + label = action.label or action.__name__ + icon = getattr(action, "icon", None) + return PublishPluginActionItem( + action.id, + plugin_id, + action.active, + action.on, + label, + icon + ) + + @six.add_metaclass(ABCMeta) From a3d16def9b42328cfe89a03dbed9dc8ea1a51f9b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 7 Oct 2022 11:23:01 +0200 Subject: [PATCH 059/201] created objects for controller <-> ui communiction related to plugin actions and validation errors --- openpype/tools/publisher/control.py | 236 ++++++++++++++++++++++++++++ 1 file changed, 236 insertions(+) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index c084cba381..484d90fc16 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -377,6 +377,242 @@ class PublishPluginsProxy: ) +class PublishPluginActionItem: + """Representation of publish plugin action. + + Data driven object which is used as proxy for controller and UI. + + Args: + action_id (str): Action id. + plugin_id (str): Plugin id. + active (bool): Action is active. + on_filter (str): Actions have 'on' attribte which define when can be + action triggered (e.g. 'all', 'failed', ...). + label (str): Action's label. + icon (Union[str, None]) Action's icon. + """ + + def __init__(self, action_id, plugin_id, active, on_filter, label, icon): + self.action_id = action_id + self.plugin_id = plugin_id + self.active = active + self.on_filter = on_filter + self.label = label + self.icon = icon + + def to_data(self): + """Serialize object to dictionary. + + Returns: + Dict[str, Union[str,bool,None]]: Serialized object. + """ + + return { + "action_id": self.action_id, + "plugin_id": self.plugin_id, + "active": self.active, + "on_filter": self.on_filter, + "label": self.label, + "icon": self.icon + } + + @classmethod + def from_data(cls, data): + """Create object from data. + + Args: + data (Dict[str, Union[str,bool,None]]): Data used to recreate + object. + + Returns: + PublishPluginActionItem: Object created using data. + """ + + return cls(**data) + + +class ValidationErrorItem: + """Data driven validation error item. + + Prepared data container with information about validation error and it's + source plugin. + + Can be converted to raw data and recreated should be used for controller + and UI connection. + + Args: + instance_id (str): Id of pyblish instance to which is validation error + connected. + instance_label (str): Prepared instance label. + plugin_id (str): Id of pyblish Plugin which triggered the validation + error. Id is generated using 'PublishPluginsProxy'. + """ + + def __init__( + self, + instance_id, + instance_label, + plugin_id, + context_validation, + title, + description, + detail, + ): + self.instance_id = instance_id + self.instance_label = instance_label + self.plugin_id = plugin_id + self.context_validation = context_validation + self.title = title + self.description = description + self.detail = detail + + def to_data(self): + """Serialize object to dictionary. + + Returns: + Dict[str, Union[str, bool, None]]: Serialized object data. + """ + + return { + "instance_id": self.instance_id, + "instance_label": self.instance_label, + "plugin_id": self.plugin_id, + "context_validation": self.context_validation, + "title": self.title, + "description": self.description, + "detail": self.detail, + } + + @classmethod + def from_result(cls, plugin_id, error, instance): + """Create new object based on resukt from controller. + + Returns: + ValidationErrorItem: New object with filled data. + """ + + instance_label = None + instance_id = None + if instance is not None: + instance_label = ( + instance.data.get("label") or instance.data.get("name") + ) + instance_id = instance.id + + return cls( + instance_id, + instance_label, + plugin_id, + instance is None, + error.title, + error.description, + error.detail, + ) + + @classmethod + def from_data(cls, data): + return cls(**data) + + +class PublishValidationErrorsReport: + """Publish validation errors report that can be parsed to raw data. + + Args: + error_items (List[ValidationErrorItem]): List of validation errors. + plugin_action_items (Dict[str, PublishPluginActionItem]): Action items + by plugin id. + """ + + def __init__(self, error_items, plugin_action_items): + self._error_items = error_items + self._plugin_action_items = plugin_action_items + + def __iter__(self): + for item in self._error_items: + yield item + + def group_items_by_title(self): + """Group errors by plugin and their titles. + + Items are grouped by plugin and title -> same title from different + plugin is different item. Items are ordered by plugin order. + + Returns: + List[Dict[str, Any]]: List where each item title, instance + information related to title and possible plugin actions. + """ + + ordered_plugin_ids = [] + error_items_by_plugin_id = collections.defaultdict(list) + for error_item in self._error_items: + plugin_id = error_item.plugin_id + if plugin_id not in ordered_plugin_ids: + ordered_plugin_ids.append(plugin_id) + error_items_by_plugin_id[plugin_id].append(error_item) + + grouped_error_items = [] + for plugin_id in ordered_plugin_ids: + plugin_action_items = self._plugin_action_items[plugin_id] + error_items = error_items_by_plugin_id[plugin_id] + + titles = [] + error_items_by_title = collections.defaultdict(list) + for error_item in error_items: + title = error_item.title + if title not in titles: + titles.append(error_item.title) + error_items_by_title[title].append(error_item) + + for title in titles: + grouped_error_items.append({ + "plugin_action_items": list(plugin_action_items), + "error_items": error_items_by_title[title], + "title": title + }) + return grouped_error_items + + def to_data(self): + """Serialize object to dictionary. + + Returns: + Dict[str, Any]: Serialized data. + """ + + return { + "error_items": [ + item.to_data() + for item in self._error_items + ], + "plugin_action_items": { + plugin_id: [ + action_item.to_data() + for action_item in action_items + ] + for plugin_id, action_items in self._plugin_action_items.items() + } + } + + @classmethod + def from_data(cls, data): + """Recreate object from data. + + Args: + data (dict[str, Any]): Data to recreate object. Can be created + using 'to_data' method. + + Returns: + PublishValidationErrorsReport: New object based on data. + """ + + error_items = [ + ValidationErrorItem.from_data(error_item) + for error_item in data["error_items"] + ] + plugin_action_items = [ + PublishPluginActionItem.from_data(action_item) + for action_item in data["plugin_action_items"] + ] + return cls(error_items, plugin_action_items) @six.add_metaclass(ABCMeta) From a63854f2656a04e4f1643112e86e2d7a48dcc657 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 7 Oct 2022 11:24:54 +0200 Subject: [PATCH 060/201] Created object to gather validation errors during publish processing --- openpype/tools/publisher/control.py | 65 +++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 484d90fc16..c28d7ab3c9 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -615,6 +615,71 @@ class PublishValidationErrorsReport: return cls(error_items, plugin_action_items) +class PublishValidationErrors: + """Object to keep track about validation errors by plugin.""" + + def __init__(self): + self._plugins_proxy = None + self._error_items = [] + self._plugin_action_items = {} + + def __bool__(self): + return self.has_errors + + @property + def has_errors(self): + """At least one error was added.""" + + return bool(self._error_items) + + def reset(self, plugins_proxy): + """Reset object to default state. + + Args: + plugins_proxy (PublishPluginsProxy): Proxy which store plugins, + actions by ids and create mapping of action ids by plugin ids. + """ + + self._plugins_proxy = plugins_proxy + self._error_items = [] + self._plugin_action_items = {} + + def create_report(self): + """Create report based on currently existing errors. + + Returns: + PublishValidationErrorsReport: Validation error report with all + error information and publish plugin action items. + """ + + return PublishValidationErrorsReport( + self._error_items, self._plugin_action_items + ) + + def add_error(self, plugin, error, instance): + """Add error from pyblish result. + + Args: + plugin (pyblish.api.Plugin): Plugin which triggered error. + error (ValidationException): Validation error. + instance (Union[pyblish.api.Instance, None]): Instance on which was + error raised or None if was raised on context. + """ + + # Make sure the cached report is cleared + plugin_id = self._plugins_proxy.get_plugin_id(plugin) + self._error_items.append( + ValidationErrorItem.from_result(plugin_id, error, instance) + ) + if plugin_id in self._plugin_action_items: + return + + plugin_actions = self._plugins_proxy.get_plugin_action_items( + plugin_id + ) + self._plugin_action_items[plugin_id] = plugin_actions + + @six.add_metaclass(ABCMeta) class AbstractPublisherController(object): """Publisher tool controller. From d90838b630a47b6055600b2b4175e14b6cba62f5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 7 Oct 2022 11:25:41 +0200 Subject: [PATCH 061/201] removed unused 'get_manual_creators_base_info' --- openpype/tools/publisher/control.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index c28d7ab3c9..bd4b6a738e 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -772,16 +772,6 @@ class AbstractPublisherController(object): pass - @abstractmethod - def get_manual_creators_base_info(self): - """Creators that can be selected and triggered by artist. - - Returns: - List[CreatorBaseInfo]: Base information about creator plugin. - """ - - pass - @abstractmethod def get_context_title(self): """Get context title for artist shown at the top of main window. From e53efc7aba85bbbe12d46a3c35351a2f16bf6d59 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 7 Oct 2022 11:26:38 +0200 Subject: [PATCH 062/201] create plugins proxy in controller --- openpype/tools/publisher/control.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index bd4b6a738e..4fbf20492d 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -920,6 +920,8 @@ class PublisherController(AbstractPublisherController): self._host, dbcon, headless=headless, reset=False ) + self._publish_plugins_proxy = None + # pyblish.api.Context self._publish_context = None # Pyblish report @@ -1290,6 +1292,10 @@ class PublisherController(AbstractPublisherController): # - pop the key after first collector using it would be safest option? self._publish_context.data["create_context"] = self._create_context + self._publish_plugins_proxy = PublishPluginsProxy( + self._publish_plugins + ) + self._publish_report.reset(self._publish_context, self._create_context) self._publish_validation_errors = [] self._publish_current_plugin_validation_errors = None From 8996e27df1a4fbf92973c58973cc9881cc81e655 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 7 Oct 2022 11:27:51 +0200 Subject: [PATCH 063/201] changed how validation errors are collected and worked with in UI --- openpype/tools/publisher/control.py | 27 +-- .../publisher/widgets/validations_widget.py | 192 ++++++++++-------- 2 files changed, 119 insertions(+), 100 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 4fbf20492d..3df8da62cb 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -927,9 +927,7 @@ class PublisherController(AbstractPublisherController): # Pyblish report self._publish_report = PublishReport(self) # Store exceptions of validation error - self._publish_validation_errors = [] - # Currently processing plugin errors - self._publish_current_plugin_validation_errors = None + self._publish_validation_errors = PublishValidationErrors() # Any other exception that happened during publishing self._publish_error = None # Publishing is in progress @@ -1273,7 +1271,7 @@ class PublisherController(AbstractPublisherController): return self._publish_report.get_report(self._publish_plugins) def get_validation_errors(self): - return self._publish_validation_errors + return self._publish_validation_errors.create_report() def _reset_publish(self): self._publish_is_running = False @@ -1297,8 +1295,7 @@ class PublisherController(AbstractPublisherController): ) self._publish_report.reset(self._publish_context, self._create_context) - self._publish_validation_errors = [] - self._publish_current_plugin_validation_errors = None + self._publish_validation_errors.reset(self._publish_plugins_proxy) self._publish_error = None self._publish_max_progress = len(self._publish_plugins) @@ -1488,19 +1485,11 @@ class PublisherController(AbstractPublisherController): yield MainThreadItem(self.stop_publish) def _add_validation_error(self, result): - if self._publish_current_plugin_validation_errors is None: - self._publish_current_plugin_validation_errors = { - "plugin": result["plugin"], - "errors": [] - } - self._publish_validation_errors.append( - self._publish_current_plugin_validation_errors - ) - - self._publish_current_plugin_validation_errors["errors"].append({ - "exception": result["error"], - "instance": result["instance"] - }) + self._publish_validation_errors.add_error( + result["plugin"], + result["error"], + result["instance"] + ) def _process_and_continue(self, plugin, instance): result = pyblish.plugin.process( diff --git a/openpype/tools/publisher/widgets/validations_widget.py b/openpype/tools/publisher/widgets/validations_widget.py index fd9410df98..48b7370eee 100644 --- a/openpype/tools/publisher/widgets/validations_widget.py +++ b/openpype/tools/publisher/widgets/validations_widget.py @@ -50,6 +50,7 @@ class ValidationErrorTitleWidget(QtWidgets.QWidget): Has toggle button to show/hide instances on which validation error happened if there is a list (Valdation error may happen on context). """ + selected = QtCore.Signal(int) instance_changed = QtCore.Signal(int) @@ -75,33 +76,33 @@ class ValidationErrorTitleWidget(QtWidgets.QWidget): title_frame_layout.addWidget(toggle_instance_btn, 0) instances_model = QtGui.QStandardItemModel() - error_info = error_info["error_info"] help_text_by_instance_id = {} - context_validation = False - if ( - not error_info - or (len(error_info) == 1 and error_info[0][0] is None) - ): - context_validation = True - toggle_instance_btn.setArrowType(QtCore.Qt.NoArrow) - description = self._prepare_description(error_info[0][1]) - help_text_by_instance_id[None] = description - else: - items = [] - for instance, exception in error_info: - label = instance.data.get("label") or instance.data.get("name") - item = QtGui.QStandardItem(label) - item.setFlags( - QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable - ) - item.setData(label, QtCore.Qt.ToolTipRole) - item.setData(instance.id, INSTANCE_ID_ROLE) - items.append(item) - description = self._prepare_description(exception) - help_text_by_instance_id[instance.id] = description - instances_model.invisibleRootItem().appendRows(items) + items = [] + context_validation = False + for error_item in error_info["error_items"]: + context_validation = error_item.context_validation + if context_validation: + toggle_instance_btn.setArrowType(QtCore.Qt.NoArrow) + description = self._prepare_description(error_item) + help_text_by_instance_id[None] = description + continue + + label = error_item.instance_label + item = QtGui.QStandardItem(label) + item.setFlags( + QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable + ) + item.setData(label, QtCore.Qt.ToolTipRole) + item.setData(error_item.instance_id, INSTANCE_ID_ROLE) + items.append(item) + description = self._prepare_description(error_item) + help_text_by_instance_id[error_item.instance_id] = description + + if items: + root_item = instances_model.invisibleRootItem() + root_item.appendRows(items) instances_view = ValidationErrorInstanceList(self) instances_view.setModel(instances_model) @@ -162,9 +163,19 @@ class ValidationErrorTitleWidget(QtWidgets.QWidget): def minimumSizeHint(self): return self.sizeHint() - def _prepare_description(self, exception): - dsc = exception.description - detail = exception.detail + def _prepare_description(self, error_item): + """Prepare description text for detail intput. + + Args: + error_item (ValidationErrorItem): Item which hold information about + validation error. + + Returns: + str: Prepared detailed description. + """ + + dsc = error_item.description + detail = error_item.detail if detail: dsc += "

{}".format(detail) @@ -192,32 +203,51 @@ class ValidationErrorTitleWidget(QtWidgets.QWidget): @property def is_selected(self): - """Is widget marked a selected""" + """Is widget marked a selected. + + Returns: + bool: Item is selected or not. + """ + return self._selected @property def index(self): - """Widget's index set by parent.""" + """Widget's index set by parent. + + Returns: + int: Index of widget. + """ + return self._index def set_index(self, index): - """Set index of widget (called by parent).""" + """Set index of widget (called by parent). + + Args: + int: New index of widget. + """ + self._index = index def _change_style_property(self, selected): """Change style of widget based on selection.""" + value = "1" if selected else "" self._title_frame.setProperty("selected", value) self._title_frame.style().polish(self._title_frame) def set_selected(self, selected=None): """Change selected state of widget.""" + if selected is None: selected = not self._selected + # Clear instance view selection on deselect if not selected: self._instances_view.clearSelection() + # Skip if has same value if selected == self._selected: return @@ -255,18 +285,23 @@ class ActionButton(BaseClickableFrame): """Plugin's action callback button. Action may have label or icon or both. - """ - action_clicked = QtCore.Signal(str) - def __init__(self, action, parent): + Args: + plugin_action_item (PublishPluginActionItem): Action item that can be + triggered by it's id. + """ + + action_clicked = QtCore.Signal(str, str) + + def __init__(self, plugin_action_item, parent): super(ActionButton, self).__init__(parent) self.setObjectName("ValidationActionButton") - self.action = action + self.plugin_action_item = plugin_action_item - action_label = action.label or action.__name__ - action_icon = getattr(action, "icon", None) + action_label = plugin_action_item.label + action_icon = plugin_action_item.icon label_widget = QtWidgets.QLabel(action_label, self) icon_label = None if action_icon: @@ -284,7 +319,10 @@ class ActionButton(BaseClickableFrame): ) def _mouse_release_callback(self): - self.action_clicked.emit(self.action.id) + self.action_clicked.emit( + self.plugin_action_item.plugin_id, + self.plugin_action_item.action_id + ) class ValidateActionsWidget(QtWidgets.QFrame): @@ -292,6 +330,7 @@ class ValidateActionsWidget(QtWidgets.QFrame): Change actions based on selected validation error. """ + def __init__(self, controller, parent): super(ValidateActionsWidget, self).__init__(parent) @@ -304,10 +343,9 @@ class ValidateActionsWidget(QtWidgets.QFrame): layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(content_widget) - self.controller = controller + self._controller = controller self._content_widget = content_widget self._content_layout = content_layout - self._plugin = None self._actions_mapping = {} def clear(self): @@ -320,28 +358,34 @@ class ValidateActionsWidget(QtWidgets.QFrame): widget.deleteLater() self._actions_mapping = {} - def set_plugin(self, plugin): + def set_error_item(self, error_item): """Set selected plugin and show it's actions. Clears current actions from widget and recreate them from the plugin. + + Args: + Dict[str, Any]: Object holding error items, title and possible + actions to run. """ + self.clear() - self._plugin = plugin - if not plugin: + + if not error_item: self.setVisible(False) return - actions = getattr(plugin, "actions", []) - for action in actions: - if not action.active: + plugin_action_items = error_item["plugin_action_items"] + for plugin_action_item in plugin_action_items: + if not plugin_action_item.active: continue - if action.on not in ("failed", "all"): + if plugin_action_item.on_filter not in ("failed", "all"): continue - self._actions_mapping[action.id] = action + action_id = plugin_action_item.action_id + self._actions_mapping[action_id] = plugin_action_item - action_btn = ActionButton(action, self._content_widget) + action_btn = ActionButton(plugin_action_item, self._content_widget) action_btn.action_clicked.connect(self._on_action_click) self._content_layout.addWidget(action_btn) @@ -351,9 +395,8 @@ class ValidateActionsWidget(QtWidgets.QFrame): else: self.setVisible(False) - def _on_action_click(self, action_id): - action = self._actions_mapping[action_id] - self.controller.run_action(self._plugin, action) + def _on_action_click(self, plugin_id, action_id): + self._controller.run_action(plugin_id, action_id) class VerticallScrollArea(QtWidgets.QScrollArea): @@ -365,6 +408,7 @@ class VerticallScrollArea(QtWidgets.QScrollArea): Resize if deferred by 100ms because at the moment of resize are not yet propagated sizes and visibility of scroll bars. """ + def __init__(self, *args, **kwargs): super(VerticallScrollArea, self).__init__(*args, **kwargs) @@ -576,45 +620,31 @@ class ValidationsWidget(QtWidgets.QFrame): self._errors_widget.setVisible(False) self._actions_widget.setVisible(False) - def set_errors(self, errors): - """Set errors into context and created titles.""" + def _set_errors(self, validation_error_report): + """Set errors into context and created titles. + + Args: + validation_error_report (PublishValidationErrorsReport): Report + with information about validation errors and publish plugin + actions. + """ + self.clear() - if not errors: + if not validation_error_report: return self._top_label.setVisible(True) self._error_details_frame.setVisible(True) self._errors_widget.setVisible(True) - errors_by_title = [] - for plugin_info in errors: - titles = [] - error_info_by_title = {} - - for error_info in plugin_info["errors"]: - exception = error_info["exception"] - title = exception.title - if title not in titles: - titles.append(title) - error_info_by_title[title] = [] - error_info_by_title[title].append( - (error_info["instance"], exception) - ) - - for title in titles: - errors_by_title.append({ - "plugin": plugin_info["plugin"], - "error_info": error_info_by_title[title], - "title": title - }) - - for idx, item in enumerate(errors_by_title): - widget = ValidationErrorTitleWidget(idx, item, self) + grouped_error_items = validation_error_report.group_items_by_title() + for idx, error_info in enumerate(grouped_error_items): + widget = ValidationErrorTitleWidget(idx, error_info, self) widget.selected.connect(self._on_select) widget.instance_changed.connect(self._on_instance_change) self._errors_layout.addWidget(widget) self._title_widgets[idx] = widget - self._error_info[idx] = item + self._error_info[idx] = error_info self._errors_layout.addStretch(1) @@ -640,7 +670,7 @@ class ValidationsWidget(QtWidgets.QFrame): if self._controller.publish_has_validation_errors: validation_errors = self._controller.get_validation_errors() self._set_current_widget(self._validations_widget) - self.set_errors(validation_errors) + self._set_errors(validation_errors) return if self._contoller.publish_has_finished: @@ -659,7 +689,7 @@ class ValidationsWidget(QtWidgets.QFrame): error_item = self._error_info[index] - self._actions_widget.set_plugin(error_item["plugin"]) + self._actions_widget.set_error_item(error_item) self._update_description() From 2a34a5f9780e9423a409230dc1751b057c53a2fa Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 7 Oct 2022 11:28:51 +0200 Subject: [PATCH 064/201] renamed 'get_icon_for_family' to 'get_creator_icon' --- openpype/tools/publisher/control.py | 15 ++++++++++++--- .../tools/publisher/widgets/card_view_widgets.py | 2 +- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 3df8da62cb..f870f5d9e3 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -812,7 +812,16 @@ class AbstractPublisherController(object): pass @abstractmethod - def get_icon_for_family(self, family): + def get_creator_icon(self, identifier): + """Receive creator's icon by identifier. + + Args: + identifier (str): Creator's identifier. + + Returns: + Union[str, None]: Creator's icon string. + """ + pass @abstractmethod @@ -1200,9 +1209,9 @@ class PublisherController(AbstractPublisherController): )) return output - def get_icon_for_family(self, family): + def get_creator_icon(self, identifier): """TODO rename to get creator icon.""" - creator = self._creators.get(family) + creator = self._creators.get(identifier) if creator is not None: return creator.get_icon() return None diff --git a/openpype/tools/publisher/widgets/card_view_widgets.py b/openpype/tools/publisher/widgets/card_view_widgets.py index fa391f4ba0..4bd2cf25ae 100644 --- a/openpype/tools/publisher/widgets/card_view_widgets.py +++ b/openpype/tools/publisher/widgets/card_view_widgets.py @@ -469,7 +469,7 @@ class InstanceCardView(AbstractInstanceView): group_widget = self._widgets_by_group[group_name] else: group_icons = { - idenfier: self.controller.get_icon_for_family(idenfier) + idenfier: self.controller.get_creator_icon(idenfier) for idenfier in identifiers_by_group[group_name] } From 562852875ed60a310a4f29052cbbe66356bda466 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 7 Oct 2022 11:29:08 +0200 Subject: [PATCH 065/201] fix action trigger --- openpype/tools/publisher/control.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index f870f5d9e3..643efa8645 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -1354,8 +1354,11 @@ class PublisherController(AbstractPublisherController): if self._publish_is_running: self._stop_publish() - def run_action(self, plugin, action): + def run_action(self, plugin_id, action_id): # TODO handle result in UI + plugin = self._publish_plugins_proxy.get_plugin(plugin_id) + action = self._publish_plugins_proxy.get_action(action_id) + result = pyblish.plugin.process( plugin, self._publish_context, None, action.id ) From 35562e4abb2c41962e701012bcb13ff7ad1e0067 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 7 Oct 2022 11:29:36 +0200 Subject: [PATCH 066/201] remove unused variable reset --- openpype/tools/publisher/control.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 643efa8645..7aaaccd8d8 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -1407,9 +1407,6 @@ class PublisherController(AbstractPublisherController): for idx, plugin in enumerate(self._publish_plugins): self._publish_progress = idx - # Reset current plugin validations error - self._publish_current_plugin_validation_errors = None - # Check if plugin is over validation order if not self._publish_validated: self._publish_validated = ( From e6042d9889cb52675f642a9deee1664ed0f7057b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 7 Oct 2022 11:29:54 +0200 Subject: [PATCH 067/201] fix event system access --- openpype/tools/publisher/control.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 7aaaccd8d8..a42657dd9a 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -1025,6 +1025,7 @@ class PublisherController(AbstractPublisherController): @property def host_is_valid(self): """Host is valid for creation.""" + return self._create_context.host_is_valid @property @@ -1035,7 +1036,7 @@ class PublisherController(AbstractPublisherController): def _emit_event(self, topic, data=None): if data is None: data = {} - self._event_system.emit(topic, data, "controller") + self.event_system.emit(topic, data, "controller") # --- Publish specific callbacks --- def get_asset_docs(self): From a06f629a08b8cfaa50fa35745d7805fd8737eff9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 7 Oct 2022 11:30:32 +0200 Subject: [PATCH 068/201] added some docstrings --- openpype/tools/publisher/control.py | 123 +++++++++++++++++++++++++--- 1 file changed, 112 insertions(+), 11 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index a42657dd9a..d4b624e959 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -797,10 +797,12 @@ class AbstractPublisherController(object): @abstractmethod def reset(self): - pass + """Reset whole controller. + + This should reset create context, publish context and all variables + that are related to it. + """ - @abstractmethod - def emit_card_message(self, message): pass @abstractmethod @@ -828,52 +830,113 @@ class AbstractPublisherController(object): def create( self, creator_identifier, subset_name, instance_data, options ): - pass + """Trigger creation by creator identifier. + + Should also trigger refresh of instanes. + + Args: + creator_identifier (str): Identifier of Creator plugin. + subset_name (str): Calculated subset name. + instance_data (Dict[str, Any]): Base instance data with variant, + asset name and task name. + options (Dict[str, Any]): Data from pre-create attributes. + """ def save_changes(self): - """Save changes happened during creation.""" + """Save changes in create context.""" pass def remove_instances(self, instances): - """Remove list of instances.""" + """Remove list of instances from create context.""" pass @abstractproperty def publish_has_finished(self): + """Has publishing finished. + + Returns: + bool: If publishing finished and all plugins were iterated. + """ + pass @abstractproperty def publish_is_running(self): + """Publishing is running right now. + + Returns: + bool: If publishing is in progress. + """ + pass @abstractproperty def publish_has_validated(self): + """Publish validation passed. + + Returns: + bool: If publishing passed last possible validation order. + """ + pass @abstractproperty def publish_has_crashed(self): + """Publishing crashed for any reason. + + Returns: + bool: Publishing crashed. + """ + pass @abstractproperty def publish_has_validation_errors(self): + """During validation happened at least one validation error. + + Returns: + bool: Validation error was raised during validation. + """ + pass @abstractproperty def publish_max_progress(self): + """Get maximum possible progress number. + + Returns: + int: Number that can be used as 100% of publish progress bar. + """ + pass @abstractproperty def publish_progress(self): + """Current progress number. + + Returns: + int: Current progress value which is between 0 and + 'publish_max_progress'. + """ + pass @abstractproperty def publish_comment_is_set(self): + """Publish comment was at least once set. + + Publish comment can be set only once when publish is started for a + first time. This helpt to idetify if 'set_comment' should be called or + not. + """ + pass @abstractmethod def get_publish_crash_error(self): + pass @abstractmethod @@ -884,30 +947,68 @@ class AbstractPublisherController(object): def get_validation_errors(self): pass - @abstractmethod - def set_comment(self, comment): - pass - @abstractmethod def publish(self): + """Trigger publishing without any order limitations.""" + pass @abstractmethod def validate(self): + """Trigger publishing which will stop after validation order.""" + pass @abstractmethod def stop_publish(self): + """Stop publishing can be also used to pause publishing. + + Pause of publishing is possible only if all plugins successfully + finished. + """ + pass @abstractmethod - def run_action(self, plugin, action): + def run_action(self, plugin_id, action_id): + """Trigger pyblish action on a plugin. + + Args: + plugin_id (str): Id of publish plugin. + action_id (str): Id of publish action. + """ + pass @abstractmethod def reset_project_data_cache(self): pass + @abstractmethod + def set_comment(self, comment): + """Set comment on pyblish context. + + Set "comment" key on current pyblish.api.Context data. + + Args: + comment (str): Artist's comment. + """ + + pass + + @abstractmethod + def emit_card_message(self, message): + """Emit a card message which can have a lifetime. + + This is for UI purposes. Method can be extended to more arguments + in future e.g. different message timeout or type (color). + + Args: + message (str): Message that will be showed. + """ + + pass + class PublisherController(AbstractPublisherController): """Middleware between UI, CreateContext and publish Context. From 200107245a79c75bb5ce3329a04b31e3690241f0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 7 Oct 2022 11:33:51 +0200 Subject: [PATCH 069/201] controller is private for all widgets --- .../publisher/widgets/card_view_widgets.py | 7 ++-- .../publisher/widgets/list_view_widgets.py | 6 +-- .../tools/publisher/widgets/publish_frame.py | 42 +++++++++---------- openpype/tools/publisher/widgets/widgets.py | 16 +++---- 4 files changed, 36 insertions(+), 35 deletions(-) diff --git a/openpype/tools/publisher/widgets/card_view_widgets.py b/openpype/tools/publisher/widgets/card_view_widgets.py index 4bd2cf25ae..06fa49320e 100644 --- a/openpype/tools/publisher/widgets/card_view_widgets.py +++ b/openpype/tools/publisher/widgets/card_view_widgets.py @@ -351,10 +351,11 @@ class InstanceCardView(AbstractInstanceView): Wrapper of all widgets in card view. """ + def __init__(self, controller, parent): super(InstanceCardView, self).__init__(parent) - self.controller = controller + self._controller = controller scroll_area = QtWidgets.QScrollArea(self) scroll_area.setWidgetResizable(True) @@ -440,7 +441,7 @@ class InstanceCardView(AbstractInstanceView): # Prepare instances by group and identifiers by group instances_by_group = collections.defaultdict(list) identifiers_by_group = collections.defaultdict(set) - for instance in self.controller.instances: + for instance in self._controller.instances: group_name = instance.group_label instances_by_group[group_name].append(instance) identifiers_by_group[group_name].add( @@ -469,7 +470,7 @@ class InstanceCardView(AbstractInstanceView): group_widget = self._widgets_by_group[group_name] else: group_icons = { - idenfier: self.controller.get_creator_icon(idenfier) + idenfier: self._controller.get_creator_icon(idenfier) for idenfier in identifiers_by_group[group_name] } diff --git a/openpype/tools/publisher/widgets/list_view_widgets.py b/openpype/tools/publisher/widgets/list_view_widgets.py index a701181e5b..8438e17167 100644 --- a/openpype/tools/publisher/widgets/list_view_widgets.py +++ b/openpype/tools/publisher/widgets/list_view_widgets.py @@ -409,7 +409,7 @@ class InstanceListView(AbstractInstanceView): def __init__(self, controller, parent): super(InstanceListView, self).__init__(parent) - self.controller = controller + self._controller = controller instance_view = InstanceTreeView(self) instance_delegate = ListItemDelegate(instance_view) @@ -520,7 +520,7 @@ class InstanceListView(AbstractInstanceView): # Prepare instances by their groups instances_by_group_name = collections.defaultdict(list) group_names = set() - for instance in self.controller.instances: + for instance in self._controller.instances: group_label = instance.group_label group_names.add(group_label) instances_by_group_name[group_label].append(instance) @@ -771,7 +771,7 @@ class InstanceListView(AbstractInstanceView): context_selected = False instances_by_id = { instance.id: instance - for instance in self.controller.instances + for instance in self._controller.instances } for index in self._instance_view.selectionModel().selectedIndexes(): diff --git a/openpype/tools/publisher/widgets/publish_frame.py b/openpype/tools/publisher/widgets/publish_frame.py index 4e5f02f2da..b49f005640 100644 --- a/openpype/tools/publisher/widgets/publish_frame.py +++ b/openpype/tools/publisher/widgets/publish_frame.py @@ -185,7 +185,7 @@ class PublishFrame(QtWidgets.QWidget): self._shrunk_anim = shrunk_anim - self.controller = controller + self._controller = controller self._content_frame = content_frame self._content_layout = content_layout @@ -309,8 +309,8 @@ class PublishFrame(QtWidgets.QWidget): self._validate_btn.setEnabled(True) self._publish_btn.setEnabled(True) - self._progress_bar.setValue(self.controller.publish_progress) - self._progress_bar.setMaximum(self.controller.publish_max_progress) + self._progress_bar.setValue(self._controller.publish_progress) + self._progress_bar.setMaximum(self._controller.publish_max_progress) def _on_publish_start(self): self._set_success_property(-1) @@ -334,34 +334,34 @@ class PublishFrame(QtWidgets.QWidget): def _on_plugin_change(self, event): """Change plugin label when instance is going to be processed.""" - self._progress_bar.setValue(self.controller.publish_progress) + self._progress_bar.setValue(self._controller.publish_progress) self._plugin_label.setText(event["plugin_label"]) QtWidgets.QApplication.processEvents() def _on_publish_stop(self): - self._progress_bar.setValue(self.controller.publish_progress) + self._progress_bar.setValue(self._controller.publish_progress) self._reset_btn.setEnabled(True) self._stop_btn.setEnabled(False) - validate_enabled = not self.controller.publish_has_crashed - publish_enabled = not self.controller.publish_has_crashed + validate_enabled = not self._controller.publish_has_crashed + publish_enabled = not self._controller.publish_has_crashed if validate_enabled: - validate_enabled = not self.controller.publish_has_validated + validate_enabled = not self._controller.publish_has_validated if publish_enabled: if ( - self.controller.publish_has_validated - and self.controller.publish_has_validation_errors + self._controller.publish_has_validated + and self._controller.publish_has_validation_errors ): publish_enabled = False else: - publish_enabled = not self.controller.publish_has_finished + publish_enabled = not self._controller.publish_has_finished self._validate_btn.setEnabled(validate_enabled) self._publish_btn.setEnabled(publish_enabled) - error = self.controller.get_publish_crash_error() - validation_errors = self.controller.get_validation_errors() + error = self._controller.get_publish_crash_error() + validation_errors = self._controller.get_validation_errors() if error: self._set_error(error) @@ -369,7 +369,7 @@ class PublishFrame(QtWidgets.QWidget): self._set_progress_visibility(False) self._set_validation_errors() - elif self.controller.publish_has_finished: + elif self._controller.publish_has_finished: self._set_finished() else: @@ -377,7 +377,7 @@ class PublishFrame(QtWidgets.QWidget): def _set_stopped(self): main_label = "Publish paused" - if self.controller.publish_has_validated: + if self._controller.publish_has_validated: main_label += " - Validation passed" self._set_main_label(main_label) @@ -440,7 +440,7 @@ class PublishFrame(QtWidgets.QWidget): widget.style().polish(widget) def _copy_report(self): - logs = self.controller.get_publish_report() + logs = self._controller.get_publish_report() logs_string = json.dumps(logs, indent=4) mime_data = QtCore.QMimeData() @@ -463,7 +463,7 @@ class PublishFrame(QtWidgets.QWidget): if not ext or not new_filepath: return - logs = self.controller.get_publish_report() + logs = self._controller.get_publish_report() full_path = new_filepath + ext dir_path = os.path.dirname(full_path) if not os.path.exists(dir_path): @@ -483,13 +483,13 @@ class PublishFrame(QtWidgets.QWidget): self.details_page_requested.emit() def _on_reset_clicked(self): - self.controller.reset() + self._controller.reset() def _on_stop_clicked(self): - self.controller.stop_publish() + self._controller.stop_publish() def _on_validate_clicked(self): - self.controller.validate() + self._controller.validate() def _on_publish_clicked(self): - self.controller.publish() + self._controller.publish() diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index d5e55b88f9..903ce70f01 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -994,7 +994,7 @@ class GlobalAttrsWidget(QtWidgets.QWidget): def __init__(self, controller, parent): super(GlobalAttrsWidget, self).__init__(parent) - self.controller = controller + self._controller = controller self._current_instances = [] variant_input = VariantInputWidget(self) @@ -1068,7 +1068,7 @@ class GlobalAttrsWidget(QtWidgets.QWidget): else: asset_names.add(asset_name) - for asset_doc in self.controller.get_asset_docs(): + for asset_doc in self._controller.get_asset_docs(): _asset_name = asset_doc["name"] if _asset_name in asset_names: asset_names.remove(_asset_name) @@ -1077,7 +1077,7 @@ class GlobalAttrsWidget(QtWidgets.QWidget): if not asset_names: break - project_name = self.controller.project_name + project_name = self._controller.project_name subset_names = set() invalid_tasks = False for instance in self._current_instances: @@ -1245,7 +1245,7 @@ class CreatorAttrsWidget(QtWidgets.QWidget): self._main_layout = main_layout - self.controller = controller + self._controller = controller self._scroll_area = scroll_area self._attr_def_id_to_instances = {} @@ -1274,7 +1274,7 @@ class CreatorAttrsWidget(QtWidgets.QWidget): self._attr_def_id_to_instances = {} self._attr_def_id_to_attr_def = {} - result = self.controller.get_creator_attribute_definitions( + result = self._controller.get_creator_attribute_definitions( instances ) @@ -1366,7 +1366,7 @@ class PublishPluginAttrsWidget(QtWidgets.QWidget): self._main_layout = main_layout - self.controller = controller + self._controller = controller self._scroll_area = scroll_area self._attr_def_id_to_instances = {} @@ -1398,7 +1398,7 @@ class PublishPluginAttrsWidget(QtWidgets.QWidget): self._attr_def_id_to_attr_def = {} self._attr_def_id_to_plugin_name = {} - result = self.controller.get_publish_attribute_definitions( + result = self._controller.get_publish_attribute_definitions( instances, context_selected ) @@ -1513,7 +1513,7 @@ class SubsetAttributesWidget(QtWidgets.QWidget): self._on_instance_context_changed ) - self.controller = controller + self._controller = controller self.global_attrs_widget = global_attrs_widget From 5cfd5db5d7d323133fb1d79a6a1da0e2effc4c49 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 7 Oct 2022 11:53:10 +0200 Subject: [PATCH 070/201] added missing abstract property 'host_is_valid' --- openpype/tools/publisher/control.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index d4b624e959..1725961aac 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -687,7 +687,8 @@ class AbstractPublisherController(object): Define what must be implemented to be able use Publisher functionality. Goal is to have "data driven" controller that can be used to control UI - running in different process. That lead to some "" + running in different process. That lead to some disadvantages like UI can't + access objects directly but by using wrappers that can be serialized. """ _log = None @@ -762,6 +763,19 @@ class AbstractPublisherController(object): pass + @abstractproperty + def host_is_valid(self): + """Host is valid for creation part. + + Host must have implemented certain functionality to be able create + in Publisher tool. + + Returns: + bool: Host can handle creation of instances. + """ + + pass + @abstractproperty def instances(self): """Collected/created instances. From 56449218344d23d6f5bb4e23c849dbd5ba1ac93a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 7 Oct 2022 12:47:10 +0200 Subject: [PATCH 071/201] store asset documents by name --- openpype/tools/publisher/control.py | 39 ++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 1725961aac..2da26622eb 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -45,25 +45,34 @@ class AssetDocsCache: def __init__(self, controller): self._controller = controller self._asset_docs = None + # TODO use asset ids instead self._task_names_by_asset_name = {} + self._asset_docs_by_name = {} def reset(self): self._asset_docs = None self._task_names_by_asset_name = {} + self._asset_docs_by_name = {} def _query(self): - if self._asset_docs is None: - project_name = self._controller.project_name - asset_docs = get_assets( - project_name, fields=self.projection.keys() - ) - task_names_by_asset_name = {} - for asset_doc in asset_docs: - asset_name = asset_doc["name"] - asset_tasks = asset_doc.get("data", {}).get("tasks") or {} - task_names_by_asset_name[asset_name] = list(asset_tasks.keys()) - self._asset_docs = asset_docs - self._task_names_by_asset_name = task_names_by_asset_name + if self._asset_docs is not None: + return + + project_name = self._controller.project_name + asset_docs = get_assets( + project_name, fields=self.projection.keys() + ) + asset_docs_by_name = {} + task_names_by_asset_name = {} + for asset_doc in asset_docs: + asset_name = asset_doc["name"] + asset_tasks = asset_doc.get("data", {}).get("tasks") or {} + task_names_by_asset_name[asset_name] = list(asset_tasks.keys()) + asset_docs_by_name[asset_name] = asset_doc + + self._asset_docs = asset_docs + self._asset_docs_by_name = asset_docs_by_name + self._task_names_by_asset_name = task_names_by_asset_name def get_asset_docs(self): self._query() @@ -73,6 +82,12 @@ class AssetDocsCache: self._query() return copy.deepcopy(self._task_names_by_asset_name) + def get_asset_by_name(self, asset_name): + asset_doc = self._asset_docs_by_name.get(asset_name) + if asset_doc is None: + return None + return copy.deepcopy(asset_doc) + class PublishReport: """Report for single publishing process. From 2ab0ad9d4466c8d518726266115e0910fd53a0bf Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 7 Oct 2022 12:47:44 +0200 Subject: [PATCH 072/201] added ability to get and query full asset document --- openpype/tools/publisher/control.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 2da26622eb..8abe62e4b1 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -8,7 +8,10 @@ from abc import ABCMeta, abstractmethod, abstractproperty import six import pyblish.api -from openpype.client import get_assets +from openpype.client import ( + get_assets, + get_asset_by_id, +) from openpype.lib.events import EventSystem from openpype.pipeline import ( PublishValidationError, @@ -48,6 +51,7 @@ class AssetDocsCache: # TODO use asset ids instead self._task_names_by_asset_name = {} self._asset_docs_by_name = {} + self._full_asset_docs_by_name = {} def reset(self): self._asset_docs = None @@ -88,6 +92,15 @@ class AssetDocsCache: return None return copy.deepcopy(asset_doc) + def get_full_asset_by_name(self, asset_name): + self._query() + if asset_name not in self._full_asset_docs_by_name: + asset_doc = self._asset_docs_by_name.get(asset_name) + project_name = self._controller.project_name + full_asset_doc = get_asset_by_id(project_name, asset_doc["_id"]) + self._full_asset_docs_by_name[asset_name] = full_asset_doc + return copy.deepcopy(self._full_asset_docs_by_name[asset_name]) + class PublishReport: """Report for single publishing process. From 626cb387934956d2c1eea08a535dd83223a876cd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 7 Oct 2022 12:48:34 +0200 Subject: [PATCH 073/201] added ability to get existing subsets for passet asset name via controller --- openpype/tools/publisher/control.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 8abe62e4b1..89619f70f7 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -11,6 +11,7 @@ import pyblish.api from openpype.client import ( get_assets, get_asset_by_id, + get_subsets, ) from openpype.lib.events import EventSystem from openpype.pipeline import ( @@ -837,6 +838,10 @@ class AbstractPublisherController(object): def get_task_names_by_asset_names(self, asset_names): pass + @abstractmethod + def get_existing_subset_names(self, asset_name): + pass + @abstractmethod def reset(self): """Reset whole controller. @@ -1223,6 +1228,21 @@ class PublisherController(AbstractPublisherController): ) return result + def get_existing_subset_names(self, asset_name): + project_name = self.project_name + asset_doc = self._asset_docs_cache.get_asset_by_name(asset_name) + if not asset_doc: + return None + + asset_id = asset_doc["_id"] + subset_docs = get_subsets( + project_name, asset_ids=[asset_id], fields=["name"] + ) + return { + subset_doc["name"] + for subset_doc in subset_docs + } + def reset(self): """Reset everything related to creation and publishing.""" # Stop publishing From ac61407a4fe0d517b78cad30f2abffe51c378546 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 7 Oct 2022 12:49:25 +0200 Subject: [PATCH 074/201] controller can handle get subset name based on creator identifier --- openpype/tools/publisher/control.py | 52 +++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 89619f70f7..444cdbc914 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -873,6 +873,29 @@ class AbstractPublisherController(object): pass + @abstractmethod + def get_subset_name( + self, + creator_identifier, + variant, + task_name, + asset_name, + instance_id=None + ): + """Get subset name based on passed data. + + Args: + creator_identifier (str): Identifier of creator which should be + responsible for subset name creation. + variant (str): Variant value from user's input. + task_name (str): Name of task for which is instance created. + asset_name (str): Name of asset for which is instance created. + instance_id (Union[str, None]): Existing instance id when subset + name is updated. + """ + + pass + @abstractmethod def create( self, creator_identifier, subset_name, instance_data, options @@ -1380,6 +1403,35 @@ class PublisherController(AbstractPublisherController): return creator.get_icon() return None + def get_subset_name( + self, + creator_identifier, + variant, + task_name, + asset_name, + instance_id=None + ): + """Get subset name based on passed data. + + Args: + creator_identifier (str): Identifier of creator which should be + responsible for subset name creation. + variant (str): Variant value from user's input. + task_name (str): Name of task for which is instance created. + asset_name (str): Name of asset for which is instance created. + instance_id (Union[str, None]): Existing instance id when subset + name is updated. + """ + + creator = self._creators[creator_identifier] + project_name = self.project_name + print(asset_name) + asset_doc = self._asset_docs_cache.get_full_asset_by_name(asset_name) + + return creator.get_subset_name( + variant, task_name, asset_doc, project_name + ) + def create( self, creator_identifier, subset_name, instance_data, options ): From 72dccf24a2fd887a86221bfc12ae815447bde7f6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 7 Oct 2022 12:50:03 +0200 Subject: [PATCH 075/201] create widget does not call 'get_subset_name' on creator but via controller --- .../tools/publisher/widgets/create_widget.py | 38 +++++++------------ 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/openpype/tools/publisher/widgets/create_widget.py b/openpype/tools/publisher/widgets/create_widget.py index 4c9fa63d24..39fdeae30f 100644 --- a/openpype/tools/publisher/widgets/create_widget.py +++ b/openpype/tools/publisher/widgets/create_widget.py @@ -174,7 +174,7 @@ class CreateWidget(QtWidgets.QWidget): self._controller = controller - self._asset_doc = None + self._asset_name = None self._subset_names = None self._selected_creator = None @@ -380,7 +380,7 @@ class CreateWidget(QtWidgets.QWidget): if asset_name is None: asset_name = self.current_asset_name - return asset_name + return asset_name or None def _get_task_name(self): task_name = None @@ -444,7 +444,7 @@ class CreateWidget(QtWidgets.QWidget): prereq_available = False creator_btn_tooltips.append("Creator is not selected") - if self._context_change_is_enabled() and self._asset_doc is None: + if self._context_change_is_enabled() and self._asset_name is None: # QUESTION how to handle invalid asset? prereq_available = False creator_btn_tooltips.append("Context is not selected") @@ -468,30 +468,19 @@ class CreateWidget(QtWidgets.QWidget): asset_name = self._get_asset_name() # Skip if asset did not change - if self._asset_doc and self._asset_doc["name"] == asset_name: + if self._asset_name and self._asset_name == asset_name: return - # Make sure `_asset_doc` and `_subset_names` variables are reset - self._asset_doc = None + # Make sure `_asset_name` and `_subset_names` variables are reset + self._asset_name = asset_name self._subset_names = None if asset_name is None: return - project_name = self._controller.project_name - asset_doc = get_asset_by_name(project_name, asset_name) - self._asset_doc = asset_doc + subset_names = self._controller.get_existing_subset_names(asset_name) - if asset_doc: - asset_id = asset_doc["_id"] - subset_docs = get_subsets( - project_name, asset_ids=[asset_id], fields=["name"] - ) - self._subset_names = { - subset_doc["name"] - for subset_doc in subset_docs - } - - if not asset_doc: + self._subset_names = subset_names + if subset_names is None: self.subset_name_input.setText("< Asset is not set >") def _refresh_creators(self): @@ -670,14 +659,13 @@ class CreateWidget(QtWidgets.QWidget): self.subset_name_input.setText("< Valid variant >") return - project_name = self._controller.project_name + asset_name = self._get_asset_name() task_name = self._get_task_name() - - asset_doc = copy.deepcopy(self._asset_doc) + creator_idenfier = self._selected_creator.identifier # Calculate subset name with Creator plugin try: - subset_name = self._selected_creator.get_subset_name( - variant_value, task_name, asset_doc, project_name + subset_name = self._controller.get_subset_name( + creator_idenfier, variant_value, task_name, asset_name ) except TaskNotSetError: self._create_btn.setEnabled(False) From 9b92caa2549cb442dc0fed0810be7456538a4c34 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 7 Oct 2022 15:08:58 +0200 Subject: [PATCH 076/201] created new thumbnail extractor which is looking for 'thumbnailSource' on instance --- .../plugins/publish/extract_thumbnail.py | 173 ++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 openpype/hosts/traypublisher/plugins/publish/extract_thumbnail.py diff --git a/openpype/hosts/traypublisher/plugins/publish/extract_thumbnail.py b/openpype/hosts/traypublisher/plugins/publish/extract_thumbnail.py new file mode 100644 index 0000000000..eda3c8c191 --- /dev/null +++ b/openpype/hosts/traypublisher/plugins/publish/extract_thumbnail.py @@ -0,0 +1,173 @@ +"""Create instance thumbnail from "thumbnailSource" on 'instance.data'. + +Output is new representation with "thumbnail" name on instance. If instance +already have such representation the process is skipped. + +This way a collector can point to a file from which should be thumbnail +generated. This is different approach then what global plugin for thumbnails +does. The global plugin has specific logic which does not support + +Todos: + No size handling. Size of input is used for output thumbnail which can + cause issues. +""" + +import os +import tempfile + +import pyblish.api +from openpype.lib import ( + get_ffmpeg_tool_path, + get_oiio_tools_path, + is_oiio_supported, + + run_subprocess, +) + + +class ExtractThumbnailFromSource(pyblish.api.InstancePlugin): + """Create jpg thumbnail for instance based on 'thumbnailSource'. + + Thumbnail source must be a single image or video filepath. + """ + + label = "Extract Thumbnail (from source)" + # Before 'ExtractThumbnail' in global plugins + order = pyblish.api.ExtractorOrder - 0.00001 + hosts = ["traypublisher"] + + def process(self, instance): + subset_name = instance.data["subset"] + self.log.info( + "Processing instance with subset name {}".format(subset_name) + ) + + thumbnail_source = instance.data.get("thumbnailSource") + if not thumbnail_source: + self.log.debug("Thumbnail source not filled. Skipping.") + return + + elif not os.path.exists(thumbnail_source): + self.log.debug( + "Thumbnail source file was not found {}. Skipping.".format( + thumbnail_source)) + return + + # Check if already has thumbnail created + if self._already_has_thumbnail(instance): + self.log.info("Thumbnail representation already present.") + return + + # Create temp directory for thumbnail + # - this is to avoid "override" of source file + dst_staging = tempfile.mkdtemp(prefix="pyblish_tmp_") + self.log.debug( + "Create temp directory {} for thumbnail".format(dst_staging) + ) + # Store new staging to cleanup paths + instance.context.data["cleanupFullPaths"].append(dst_staging) + + thumbnail_created = False + oiio_supported = is_oiio_supported() + + self.log.info("Thumbnail source: {}".format(thumbnail_source)) + filename = os.path.splitext(thumbnail_source)[0] + jpeg_file = filename + ".jpg" + full_output_path = os.path.join(dst_staging, jpeg_file) + + if oiio_supported: + self.log.info("Trying to convert with OIIO") + # If the input can read by OIIO then use OIIO method for + # conversion otherwise use ffmpeg + thumbnail_created = self.create_thumbnail_oiio( + thumbnail_source, full_output_path + ) + + # Try to use FFMPEG if OIIO is not supported or for cases when + # oiiotool isn't available + if not thumbnail_created: + if oiio_supported: + self.log.info(( + "Converting with FFMPEG because input" + " can't be read by OIIO." + )) + + thumbnail_created = self.create_thumbnail_ffmpeg( + thumbnail_source, full_output_path + ) + + # Skip representation and try next one if wasn't created + if not thumbnail_created: + self.log.warning("Thumbanil has not been created.") + return + + new_repre = { + "name": "thumbnail", + "ext": "jpg", + "files": jpeg_file, + "stagingDir": dst_staging, + "thumbnail": True, + "tags": ["thumbnail"] + } + + # adding representation + self.log.debug( + "Adding thumbnail representation: {}".format(new_repre) + ) + instance.data["representations"].append(new_repre) + + def _already_has_thumbnail(self, instance): + if "representations" not in instance.data: + self.log.warning( + "Instance does not have 'representations' key filled" + ) + instance.data["representations"] = [] + + for repre in instance.data["representations"]: + if repre["name"] == "thumbnail": + return True + return False + + def create_thumbnail_oiio(self, src_path, dst_path): + self.log.info("outputting {}".format(dst_path)) + oiio_tool_path = get_oiio_tools_path() + oiio_cmd = [ + oiio_tool_path, + "-a", src_path, + "-o", dst_path + ] + self.log.info("Running: {}".format(" ".join(oiio_cmd))) + try: + run_subprocess(oiio_cmd, logger=self.log) + return True + except Exception: + self.log.warning( + "Failed to create thubmnail using oiiotool", + exc_info=True + ) + return False + + def create_thumbnail_ffmpeg(self, src_path, dst_path): + ffmpeg_path = get_ffmpeg_tool_path("ffmpeg") + + max_int = str(2147483647) + ffmpeg_cmd = [ + ffmpeg_path, + "-y", + "-analyzeduration", max_int, + "-probesize", max_int, + "-i", src_path, + "-vframes", "1", + dst_path + ] + + self.log.info("Running: {}".format(" ".join(ffmpeg_cmd))) + try: + run_subprocess(ffmpeg_cmd, logger=self.log) + return True + except Exception: + self.log.warning( + "Failed to create thubmnail using ffmpeg", + exc_info=True + ) + return False From e3988d9afeaff53708eef857970ac1f2273e298a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 7 Oct 2022 15:10:02 +0200 Subject: [PATCH 077/201] simple creators and batch mov are adding 'thumbnailSource' to 'instance.data' --- .../traypublisher/plugins/publish/collect_movie_batch.py | 4 ++-- .../plugins/publish/collect_simple_instances.py | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_movie_batch.py b/openpype/hosts/traypublisher/plugins/publish/collect_movie_batch.py index f37e04d1c9..3d93e2c927 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_movie_batch.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_movie_batch.py @@ -35,12 +35,12 @@ class CollectMovieBatch( "stagingDir": os.path.dirname(file_url), "tags": [] } + instance.data["representations"].append(repre) if creator_attributes["add_review_family"]: repre["tags"].append("review") instance.data["families"].append("review") - - instance.data["representations"].append(repre) + instance.data["thumbnailSource"] = file_url instance.data["source"] = file_url diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py b/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py index c0ae694c3c..3f07f4db00 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py @@ -148,8 +148,11 @@ class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin): )) return + item_dir = review_file_item["directory"] + first_filepath = os.path.join(item_dir, filenames[0]) + filepaths = { - os.path.join(review_file_item["directory"], filename) + os.path.join(item_dir, filename) for filename in filenames } source_filepaths.extend(filepaths) @@ -176,6 +179,8 @@ class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin): if "review" not in instance.data["families"]: instance.data["families"].append("review") + instance.data["thumbnailSource"] = first_filepath + review_representation["tags"].append("review") self.log.debug("Representation {} was marked for review. {}".format( review_representation["name"], review_path From 1666c16f81deef47a977042974a1c1c8ab60e0f5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 7 Oct 2022 15:14:28 +0200 Subject: [PATCH 078/201] fix representation filename --- .../traypublisher/plugins/publish/extract_thumbnail.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/publish/extract_thumbnail.py b/openpype/hosts/traypublisher/plugins/publish/extract_thumbnail.py index eda3c8c191..7781bb7b3e 100644 --- a/openpype/hosts/traypublisher/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/traypublisher/plugins/publish/extract_thumbnail.py @@ -71,9 +71,9 @@ class ExtractThumbnailFromSource(pyblish.api.InstancePlugin): oiio_supported = is_oiio_supported() self.log.info("Thumbnail source: {}".format(thumbnail_source)) - filename = os.path.splitext(thumbnail_source)[0] - jpeg_file = filename + ".jpg" - full_output_path = os.path.join(dst_staging, jpeg_file) + src_basename = os.path.basename(thumbnail_source) + dst_filename = os.path.splitext(src_basename)[0] + ".jpg" + full_output_path = os.path.join(dst_staging, dst_filename) if oiio_supported: self.log.info("Trying to convert with OIIO") @@ -104,7 +104,7 @@ class ExtractThumbnailFromSource(pyblish.api.InstancePlugin): new_repre = { "name": "thumbnail", "ext": "jpg", - "files": jpeg_file, + "files": dst_filename, "stagingDir": dst_staging, "thumbnail": True, "tags": ["thumbnail"] From 9ce236a9de4747102ccab076fd52f763cf5051d2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 7 Oct 2022 15:52:57 +0200 Subject: [PATCH 079/201] Added creator item for warpping creator plugins --- openpype/tools/publisher/control.py | 103 +++++++++++++++++++++++++++- 1 file changed, 102 insertions(+), 1 deletion(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 444cdbc914..047b34d550 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -18,7 +18,12 @@ from openpype.pipeline import ( PublishValidationError, registered_host, ) -from openpype.pipeline.create import CreateContext +from openpype.pipeline.create import ( + CreateContext, + AutoCreator, + HiddenCreator, + Creator, +) # Define constant for plugin orders offset PLUGIN_ORDER_OFFSET = 0.5 @@ -709,6 +714,102 @@ class PublishValidationErrors: self._plugin_action_items[plugin_id] = plugin_actions +class CreatorType: + def __init__(self, name): + self.name = name + + def __str__(self): + return self.name + + def __eq__(self, other): + return self.name == str(other) + + +class CreatorTypes: + base = CreatorType("base") + auto = CreatorType("auto") + hidden = CreatorType("hidden") + artist = CreatorType("artist") + + +class CreatorItem: + """Wrapper around Creator plugin. + + Object can be serialized and recreated. + """ + + def __init__( + self, + identifier, + creator_type, + family, + label, + group_label, + icon, + instance_attributes_defs, + description, + detailed_description, + default_variant, + default_variants, + create_allow_context_change, + pre_create_attributes_defs + ): + self.identifier = identifier + self.creator_type = creator_type + self.family = family + self.label = label + self.icon = icon + self.description = description + self.detailed_description = detailed_description + self.default_variant = default_variant + self.default_variants = default_variants + self.create_allow_context_change = create_allow_context_change + self.instance_attributes_defs = instance_attributes_defs + self.pre_create_attributes_defs = pre_create_attributes_defs + + @classmethod + def from_creator(cls, creator): + if isinstance(creator, AutoCreator): + creator_type = CreatorTypes.auto + elif isinstance(creator, HiddenCreator): + creator_type = CreatorTypes.hidden + elif isinstance(creator, Creator): + creator_type = CreatorTypes.artist + else: + creator_type = CreatorTypes.base + + description = None + detail_description = None + default_variant = None + default_variants = None + pre_create_attr_defs = None + create_allow_context_change = None + if creator_type is CreatorTypes.artist: + description = creator.get_description() + detail_description = creator.get_detail_description() + default_variant = creator.get_default_variant() + default_variants = creator.get_default_variants() + pre_create_attr_defs = creator.get_pre_create_attr_defs() + create_allow_context_change = creator.create_allow_context_change + + identifier = creator.identifier + return cls( + identifier, + creator_type, + creator.family, + creator.label or identifier, + creator.get_group_label(), + creator.get_icon(), + creator.get_instance_attr_defs(), + description, + detail_description, + default_variant, + default_variants, + create_allow_context_change, + pre_create_attr_defs + ) + + @six.add_metaclass(ABCMeta) class AbstractPublisherController(object): """Publisher tool controller. From 447d15694a6eaa08c6470b0cc8329c6d94951803 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 7 Oct 2022 15:53:26 +0200 Subject: [PATCH 080/201] use creator items instead of creators directly --- openpype/tools/publisher/control.py | 22 ++++--- .../tools/publisher/widgets/create_widget.py | 59 +++++++++++-------- .../publisher/widgets/precreate_widget.py | 6 +- 3 files changed, 50 insertions(+), 37 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 047b34d550..a8b9290811 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -1107,7 +1107,6 @@ class AbstractPublisherController(object): @abstractmethod def get_publish_crash_error(self): - pass @abstractmethod @@ -1201,6 +1200,8 @@ class PublisherController(AbstractPublisherController): self._host, dbcon, headless=headless, reset=False ) + self._creator_items = {} + self._publish_plugins_proxy = None # pyblish.api.Context @@ -1290,9 +1291,10 @@ class PublisherController(AbstractPublisherController): return self._create_context.creators @property - def manual_creators(self): + def creator_items(self): """Creators that can be shown in create dialog.""" - return self._create_context.manual_creators + + return self._creator_items @property def host_is_valid(self): @@ -1393,6 +1395,12 @@ class PublisherController(AbstractPublisherController): self._create_context.reset_plugins() + creator_items = { + identifier: CreatorItem.from_creator(creator) + for identifier, creator in self._create_context.creators.items() + } + self._creator_items = creator_items + self._resetting_plugins = False self._emit_event("plugins.refresh.finished") @@ -1498,10 +1506,9 @@ class PublisherController(AbstractPublisherController): return output def get_creator_icon(self, identifier): - """TODO rename to get creator icon.""" - creator = self._creators.get(identifier) - if creator is not None: - return creator.get_icon() + creator_item = self._creator_items.get(identifier) + if creator_item is not None: + return creator_item.icon return None def get_subset_name( @@ -1526,7 +1533,6 @@ class PublisherController(AbstractPublisherController): creator = self._creators[creator_identifier] project_name = self.project_name - print(asset_name) asset_doc = self._asset_docs_cache.get_full_asset_by_name(asset_name) return creator.get_subset_name( diff --git a/openpype/tools/publisher/widgets/create_widget.py b/openpype/tools/publisher/widgets/create_widget.py index 39fdeae30f..10cf39675e 100644 --- a/openpype/tools/publisher/widgets/create_widget.py +++ b/openpype/tools/publisher/widgets/create_widget.py @@ -1,11 +1,9 @@ import sys import re import traceback -import copy from Qt import QtWidgets, QtCore, QtGui -from openpype.client import get_asset_by_name, get_subsets from openpype.pipeline.create import ( CreatorError, SUBSET_NAME_ALLOWED_SYMBOLS, @@ -150,18 +148,18 @@ class CreatorShortDescWidget(QtWidgets.QWidget): self._family_label = family_label self._description_label = description_label - def set_plugin(self, plugin=None): - if not plugin: + def set_creator_item(self, creator_item=None): + if not creator_item: self._icon_widget.set_icon_def(None) self._family_label.setText("") self._description_label.setText("") return - plugin_icon = plugin.get_icon() - description = plugin.get_description() or "" + plugin_icon = creator_item.icon + description = creator_item.description or "" self._icon_widget.set_icon_def(plugin_icon) - self._family_label.setText("{}".format(plugin.family)) + self._family_label.setText("{}".format(creator_item.family)) self._family_label.setTextInteractionFlags(QtCore.Qt.NoTextInteraction) self._description_label.setText(description) @@ -495,7 +493,10 @@ class CreateWidget(QtWidgets.QWidget): # Add new families new_creators = set() - for identifier, creator in self._controller.manual_creators.items(): + for identifier, creator_item in self._controller.creator_items.items(): + if creator_item.creator_type != "artist": + continue + # TODO add details about creator new_creators.add(identifier) if identifier in existing_items: @@ -507,10 +508,9 @@ class CreateWidget(QtWidgets.QWidget): ) self._creators_model.appendRow(item) - label = creator.label or identifier - item.setData(label, QtCore.Qt.DisplayRole) + item.setData(creator_item.label, QtCore.Qt.DisplayRole) item.setData(identifier, CREATOR_IDENTIFIER_ROLE) - item.setData(creator.family, FAMILY_ROLE) + item.setData(creator_item.family, FAMILY_ROLE) # Remove families that are no more available for identifier in (old_creators - new_creators): @@ -561,11 +561,11 @@ class CreateWidget(QtWidgets.QWidget): identifier = new_index.data(CREATOR_IDENTIFIER_ROLE) self._set_creator_by_identifier(identifier) - def _set_creator_detailed_text(self, creator): + def _set_creator_detailed_text(self, creator_item): # TODO implement description = "" - if creator is not None: - description = creator.get_detail_description() or description + if creator_item is not None: + description = creator_item.detailed_description or description self._controller.event_system.emit( "show.detailed.help", { @@ -575,32 +575,39 @@ class CreateWidget(QtWidgets.QWidget): ) def _set_creator_by_identifier(self, identifier): - creator = self._controller.manual_creators.get(identifier) - self._set_creator(creator) + creator_item = self._controller.creator_items.get(identifier) + self._set_creator(creator_item) - def _set_creator(self, creator): - self._creator_short_desc_widget.set_plugin(creator) - self._set_creator_detailed_text(creator) - self._pre_create_widget.set_plugin(creator) + def _set_creator(self, creator_item): + """Set current creator item. - self._selected_creator = creator + Args: + creator_item (CreatorItem): Item representing creator that can be + triggered by artist. + """ - if not creator: + self._creator_short_desc_widget.set_creator_item(creator_item) + self._set_creator_detailed_text(creator_item) + self._pre_create_widget.set_creator_item(creator_item) + + self._selected_creator = creator_item + + if not creator_item: self._set_context_enabled(False) return if ( - creator.create_allow_context_change + creator_item.create_allow_context_change != self._context_change_is_enabled() ): - self._set_context_enabled(creator.create_allow_context_change) + self._set_context_enabled(creator_item.create_allow_context_change) self._refresh_asset() - default_variants = creator.get_default_variants() + default_variants = creator_item.default_variants if not default_variants: default_variants = ["Main"] - default_variant = creator.get_default_variant() + default_variant = creator_item.default_variant if not default_variant: default_variant = default_variants[0] diff --git a/openpype/tools/publisher/widgets/precreate_widget.py b/openpype/tools/publisher/widgets/precreate_widget.py index eaadfe890b..ef34c9bcb5 100644 --- a/openpype/tools/publisher/widgets/precreate_widget.py +++ b/openpype/tools/publisher/widgets/precreate_widget.py @@ -58,12 +58,12 @@ class PreCreateWidget(QtWidgets.QWidget): def current_value(self): return self._attributes_widget.current_value() - def set_plugin(self, creator): + def set_creator_item(self, creator_item): attr_defs = [] creator_selected = False - if creator is not None: + if creator_item is not None: creator_selected = True - attr_defs = creator.get_pre_create_attr_defs() + attr_defs = creator_item.pre_create_attributes_defs self._attributes_widget.set_attr_defs(attr_defs) From 06e1cf0b0ffd5f74da6bea47e1bc82c83623d844 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 7 Oct 2022 16:34:27 +0200 Subject: [PATCH 081/201] attribute definitions now have types --- openpype/lib/attribute_definitions.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/openpype/lib/attribute_definitions.py b/openpype/lib/attribute_definitions.py index 37446f01f8..0ce4c7866f 100644 --- a/openpype/lib/attribute_definitions.py +++ b/openpype/lib/attribute_definitions.py @@ -3,7 +3,7 @@ import re import collections import uuid import json -from abc import ABCMeta, abstractmethod +from abc import ABCMeta, abstractmethod, abstractproperty import six import clique @@ -115,6 +115,16 @@ class AbtractAttrDef: return False return self.key == other.key + @abstractproperty + def type(self): + """Attribute definition type also used as identifier of class. + + Returns: + str: Type of attribute definition. + """ + + pass + @abstractmethod def convert_value(self, value): """Convert value to a valid one. @@ -141,10 +151,12 @@ class UIDef(AbtractAttrDef): class UISeparatorDef(UIDef): - pass + type = "separator" class UILabelDef(UIDef): + type = "label" + def __init__(self, label): super(UILabelDef, self).__init__(label=label) @@ -160,6 +172,8 @@ class UnknownDef(AbtractAttrDef): have known definition of type. """ + type = "unknown" + def __init__(self, key, default=None, **kwargs): kwargs["default"] = default super(UnknownDef, self).__init__(key, **kwargs) @@ -181,6 +195,7 @@ class NumberDef(AbtractAttrDef): default(int, float): Default value for conversion. """ + type = "number" def __init__( self, key, minimum=None, maximum=None, decimals=None, default=None, **kwargs @@ -301,6 +316,8 @@ class EnumDef(AbtractAttrDef): default: Default value. Must be one key(value) from passed items. """ + type = "enum" + def __init__(self, key, items, default=None, **kwargs): if not items: raise ValueError(( @@ -343,6 +360,8 @@ class BoolDef(AbtractAttrDef): default(bool): Default value. Set to `False` if not defined. """ + type = "bool" + def __init__(self, key, default=None, **kwargs): if default is None: default = False @@ -585,6 +604,7 @@ class FileDef(AbtractAttrDef): default(str, List[str]): Default value. """ + type = "path" def __init__( self, key, single_item=True, folders=None, extensions=None, allow_sequences=True, extensions_label=None, default=None, **kwargs From bc39b992709ee75da2bf6dbc6f679b0a84b8f5f0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 7 Oct 2022 16:34:50 +0200 Subject: [PATCH 082/201] attribute definitions can be serialized and deserialized --- openpype/lib/attribute_definitions.py | 61 +++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/openpype/lib/attribute_definitions.py b/openpype/lib/attribute_definitions.py index 0ce4c7866f..a721aa09b8 100644 --- a/openpype/lib/attribute_definitions.py +++ b/openpype/lib/attribute_definitions.py @@ -90,6 +90,8 @@ class AbtractAttrDef: next to value input or ahead. """ + type_attributes = [] + is_value_def = True def __init__( @@ -135,6 +137,35 @@ class AbtractAttrDef: pass + def serialize(self): + """Serialize object to data so it's possible to recreate it. + + Returns: + Dict[str, Any]: Serialized object that can be passed to + 'deserialize' method. + """ + + data = { + "type": self.type, + "key": self.key, + "label": self.label, + "tooltip": self.tooltip, + "default": self.default, + "is_label_horizontal": self.is_label_horizontal + } + for attr in self.type_attributes: + data[attr] = getattr(self, attr) + return data + + @classmethod + def deserialize(cls, data): + """Recreate object from data. + + Data can be received using 'serialize' method. + """ + + return cls(**data) + # ----------------------------------------- # UI attribute definitoins won't hold value @@ -196,6 +227,12 @@ class NumberDef(AbtractAttrDef): """ type = "number" + type_attributes = [ + "minimum", + "maximum", + "decimals" + ] + def __init__( self, key, minimum=None, maximum=None, decimals=None, default=None, **kwargs @@ -267,6 +304,12 @@ class TextDef(AbtractAttrDef): default(str, None): Default value. Empty string used when not defined. """ + type = "text" + type_attributes = [ + "multiline", + "placeholder", + ] + def __init__( self, key, multiline=None, regex=None, placeholder=None, default=None, **kwargs @@ -305,6 +348,11 @@ class TextDef(AbtractAttrDef): return value return self.default + def serialize(self): + data = super(TextDef, self).serialize() + data["regex"] = self.regex.pattern + return data + class EnumDef(AbtractAttrDef): """Enumeration of single item from items. @@ -352,6 +400,11 @@ class EnumDef(AbtractAttrDef): return value return self.default + def serialize(self): + data = super(TextDef, self).serialize() + data["items"] = list(self.items) + return data + class BoolDef(AbtractAttrDef): """Boolean representation. @@ -605,6 +658,14 @@ class FileDef(AbtractAttrDef): """ type = "path" + type_attributes = [ + "single_item", + "folders", + "extensions", + "allow_sequences", + "extensions_label", + ] + def __init__( self, key, single_item=True, folders=None, extensions=None, allow_sequences=True, extensions_label=None, default=None, **kwargs From ac406106308bbff34b2e6a94d9d133159d23a853 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 7 Oct 2022 16:35:07 +0200 Subject: [PATCH 083/201] added helper functions to serialize and deserialize attribute definitions --- openpype/lib/attribute_definitions.py | 90 +++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/openpype/lib/attribute_definitions.py b/openpype/lib/attribute_definitions.py index a721aa09b8..bb0b07948f 100644 --- a/openpype/lib/attribute_definitions.py +++ b/openpype/lib/attribute_definitions.py @@ -8,6 +8,28 @@ from abc import ABCMeta, abstractmethod, abstractproperty import six import clique +# Global variable which store attribude definitions by type +# - default types are registered on import +_attr_defs_by_type = {} + + +def register_attr_def_class(cls): + """Register attribute definition. + + Currently are registered definitions used to deserialize data to objects. + + Attrs: + cls (AbtractAttrDef): Non-abstract class to be registered with unique + 'type' attribute. + + Raises: + KeyError: When type was already registered. + """ + + if cls.type in _attr_defs_by_type: + raise KeyError("Type \"{}\" was already registered".format(cls.type)) + _attr_defs_by_type[cls.type] = cls + def get_attributes_keys(attribute_definitions): """Collect keys from list of attribute definitions. @@ -756,3 +778,71 @@ class FileDef(AbtractAttrDef): if self.single_item: return FileDefItem.create_empty_item().to_dict() return [] + + +def serialize_attr_def(attr_def): + """Serialize attribute definition to data. + + Args: + attr_def (AbtractAttrDef): Attribute definition to serialize. + + Returns: + Dict[str, Any]: Serialized data. + """ + + return attr_def.serialize() + + +def serialize_attr_defs(attr_defs): + """Serialize attribute definitions to data. + + Args: + attr_defs (List[AbtractAttrDef]): Attribute definitions to serialize. + + Returns: + List[Dict[str, Any]]: Serialized data. + """ + + return [ + serialize_attr_def(attr_def) + for attr_def in attr_defs + ] + + +def deserialize_attr_def(attr_def_data): + """Deserialize attribute definition from data. + + Args: + attr_def (Dict[str, Any]): Attribute definition data to deserialize. + """ + + attr_type = attr_def_data.pop("type") + cls = _attr_defs_by_type[attr_type] + return cls.deserialize(attr_def_data) + + +def deserialize_attr_defs(attr_defs_data): + """Deserialize attribute definitions. + + Args: + List[Dict[str, Any]]: List of attribute definitions. + """ + + return [ + deserialize_attr_def(attr_def_data) + for attr_def_data in attr_defs_data + ] + + +# Register attribute definitions +for _attr_class in ( + UISeparatorDef, + UILabelDef, + UnknownDef, + NumberDef, + TextDef, + EnumDef, + BoolDef, + FileDef +): + register_attr_def_class(_attr_class) From 409ec104055779bab17c78da7d344c012dbf517f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 7 Oct 2022 16:47:13 +0200 Subject: [PATCH 084/201] added serialization and deserialization of CreatorItem --- openpype/tools/publisher/control.py | 66 ++++++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 2 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index a8b9290811..f96782b08d 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -14,6 +14,10 @@ from openpype.client import ( get_subsets, ) from openpype.lib.events import EventSystem +from openpype.lib.attribute_definitions import ( + serialize_attr_defs, + deserialize_attr_defs, +) from openpype.pipeline import ( PublishValidationError, registered_host, @@ -731,6 +735,18 @@ class CreatorTypes: hidden = CreatorType("hidden") artist = CreatorType("artist") + @classmethod + def from_str(cls, value): + for creator_type in ( + cls.base, + cls.auto, + cls.hidden, + cls.artist + ): + if value == creator_type: + return creator_type + raise ValueError("Unknown type \"{}\"".format(str(value))) + class CreatorItem: """Wrapper around Creator plugin. @@ -758,6 +774,7 @@ class CreatorItem: self.creator_type = creator_type self.family = family self.label = label + self.group_label = group_label self.icon = icon self.description = description self.detailed_description = detailed_description @@ -809,6 +826,52 @@ class CreatorItem: pre_create_attr_defs ) + def to_data(self): + instance_attributes_defs = None + if self.instance_attributes_defs is not None: + instance_attributes_defs = serialize_attr_defs( + self.instance_attributes_defs + ) + + pre_create_attributes_defs = None + if self.pre_create_attributes_defs is not None: + instance_attributes_defs = serialize_attr_defs( + self.pre_create_attributes_defs + ) + + return { + "identifier": self.identifier, + "creator_type": str(self.creator_type), + "family": self.family, + "label": self.label, + "group_label": self.group_label, + "icon": self.icon, + "description": self.description, + "detailed_description": self.detailed_description, + "default_variant": self.default_variant, + "default_variants": self.default_variants, + "create_allow_context_change": self.create_allow_context_change, + "instance_attributes_defs": instance_attributes_defs, + "pre_create_attributes_defs": pre_create_attributes_defs, + } + + @classmethod + def from_data(cls, data): + instance_attributes_defs = data["instance_attributes_defs"] + if instance_attributes_defs is not None: + data["instance_attributes_defs"] = deserialize_attr_defs( + instance_attributes_defs + ) + + pre_create_attributes_defs = data["pre_create_attributes_defs"] + if pre_create_attributes_defs is not None: + data["pre_create_attributes_defs"] = deserialize_attr_defs( + pre_create_attributes_defs + ) + + data["creator_type"] = CreatorTypes.from_str(data["creator_type"]) + return cls(**data) + @six.add_metaclass(ABCMeta) class AbstractPublisherController(object): @@ -1395,11 +1458,10 @@ class PublisherController(AbstractPublisherController): self._create_context.reset_plugins() - creator_items = { + self._creator_items = { identifier: CreatorItem.from_creator(creator) for identifier, creator in self._create_context.creators.items() } - self._creator_items = creator_items self._resetting_plugins = False From 12fee4ec4ff0985d28c74b40070e40aa13f25238 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 7 Oct 2022 18:40:23 +0200 Subject: [PATCH 085/201] create context provides instances by id --- openpype/pipeline/create/context.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index a7e43cb2f2..87768606e6 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -780,6 +780,10 @@ class CreateContext: def instances(self): return self._instances_by_id.values() + @property + def instances_by_id(self): + return self._instances_by_id + @property def publish_attributes(self): """Access to global publish attributes.""" From 8f83ff878f45a01a3689da4e31ca63db5c97a67d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 7 Oct 2022 18:40:41 +0200 Subject: [PATCH 086/201] prepared some methods for instance remote processing --- openpype/pipeline/create/context.py | 116 ++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 87768606e6..804e3955e5 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -197,6 +197,16 @@ class AttributeValues: def changes(self): return self.calculate_changes(self._data, self._origin_data) + def apply_changes(self, changes): + for key, item in changes.items(): + old_value, new_value = item + if new_value is None: + if key in self: + self.pop(key) + + elif self.get(key) != new_value: + self[key] = new_value + class CreatorAttributeValues(AttributeValues): """Creator specific attribute values of an instance. @@ -327,6 +337,21 @@ class PublishAttributes: changes[key] = (value, None) return changes + def apply_changes(self, changes): + for key, item in changes.items(): + if isinstance(item, dict): + self._data[key].apply_changes(item) + continue + + old_value, new_value = item + if new_value is not None: + raise ValueError( + "Unexpected type \"{}\" expected None".format( + str(type(new_value)) + ) + ) + self.pop(key) + def set_publish_plugins(self, attr_plugins): """Set publish plugins attribute definitions.""" @@ -693,6 +718,97 @@ class CreatedInstance: if member not in self._members: self._members.append(member) + def serialize_for_remote(self): + return { + "data": self.data_to_store(), + "orig_data": copy.deepcopy(self._orig_data) + } + + @classmethod + def deserialize_on_remote(cls, serialized_data, creator_items): + """Convert instance data to CreatedInstance. + + This is fake instance in remote process e.g. in UI process. The creator + is not a full creator and should not be used for calling methods when + instance is created from this method (matters on implementation). + + Args: + serialized_data (Dict[str, Any]): Serialized data for remote + recreating. Should contain 'data' and 'orig_data'. + creator_items (Dict[str, Any]): Mapping of creator identifier and + objects that behave like a creator for most of attribute + access. + """ + + instance_data = copy.deepcopy(serialized_data["data"]) + creator_identifier = instance_data["creator_identifier"] + creator_item = creator_items[creator_identifier] + + family = instance_data.get("family", None) + if family is None: + family = creator_item.family + subset_name = instance_data.get("subset", None) + + obj = cls( + family, subset_name, instance_data, creator_item, new=False + ) + obj._orig_data = serialized_data["orig_data"] + + return obj + + def remote_changes(self): + """Prepare serializable changes on remote side. + + Returns: + Dict[str, Any]: Prepared changes that can be send to client side. + """ + + return { + "changes": self.changes(), + "asset_is_valid": self._asset_is_valid, + "task_is_valid": self._task_is_valid, + } + + def update_from_remote(self, remote_changes): + """Apply changes from remote side on client side. + + Args: + remote_changes (Dict[str, Any]): Changes created on remote side. + """ + + self._asset_is_valid = remote_changes["asset_is_valid"] + self._task_is_valid = remote_changes["task_is_valid"] + + changes = remote_changes["changes"] + creator_attributes = changes.pop("creator_attributes", None) or {} + publish_attributes = changes.pop("publish_attributes", None) or {} + if changes: + self.apply_changes(changes) + + if creator_attributes: + self.creator_attributes.apply_changes(creator_attributes) + + if publish_attributes: + self.publish_attributes.apply_changes(publish_attributes) + + def apply_changes(self, changes): + """Apply changes created via 'changes'. + + Args: + Dict[str, Tuple[Any, Any]]: Instance changes to apply. Same values + are kept untouched. + """ + + for key, item in changes.items(): + old_value, new_value = item + if new_value is None: + if key in self: + self.pop(key) + else: + current_value = self.get(key) + if current_value != new_value: + self[key] = new_value + class CreateContext: """Context of instance creation. From b5a4420f0a8fa78b26988ab1e7e18d7150a04799 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 7 Oct 2022 18:41:31 +0200 Subject: [PATCH 087/201] instances returns instances by id --- openpype/tools/publisher/control.py | 2 +- openpype/tools/publisher/widgets/card_view_widgets.py | 2 +- openpype/tools/publisher/widgets/list_view_widgets.py | 7 ++----- openpype/tools/publisher/window.py | 2 +- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index f96782b08d..6765c75992 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -1345,7 +1345,7 @@ class PublisherController(AbstractPublisherController): @property def instances(self): """Current instances in create context.""" - return self._create_context.instances + return self._create_context.instances_by_id @property def _creators(self): diff --git a/openpype/tools/publisher/widgets/card_view_widgets.py b/openpype/tools/publisher/widgets/card_view_widgets.py index 06fa49320e..2be37ea44c 100644 --- a/openpype/tools/publisher/widgets/card_view_widgets.py +++ b/openpype/tools/publisher/widgets/card_view_widgets.py @@ -441,7 +441,7 @@ class InstanceCardView(AbstractInstanceView): # Prepare instances by group and identifiers by group instances_by_group = collections.defaultdict(list) identifiers_by_group = collections.defaultdict(set) - for instance in self._controller.instances: + for instance in self._controller.instances.values(): group_name = instance.group_label instances_by_group[group_name].append(instance) identifiers_by_group[group_name].add( diff --git a/openpype/tools/publisher/widgets/list_view_widgets.py b/openpype/tools/publisher/widgets/list_view_widgets.py index 8438e17167..17b50b764a 100644 --- a/openpype/tools/publisher/widgets/list_view_widgets.py +++ b/openpype/tools/publisher/widgets/list_view_widgets.py @@ -520,7 +520,7 @@ class InstanceListView(AbstractInstanceView): # Prepare instances by their groups instances_by_group_name = collections.defaultdict(list) group_names = set() - for instance in self._controller.instances: + for instance in self._controller.instances.values(): group_label = instance.group_label group_names.add(group_label) instances_by_group_name[group_label].append(instance) @@ -769,10 +769,7 @@ class InstanceListView(AbstractInstanceView): """ instances = [] context_selected = False - instances_by_id = { - instance.id: instance - for instance in self._controller.instances - } + instances_by_id = self._controller.instances for index in self._instance_view.selectionModel().selectedIndexes(): instance_id = index.data(INSTANCE_ID_ROLE) diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index 699cf6f1f9..bc2e42f051 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -523,7 +523,7 @@ class PublisherWindow(QtWidgets.QDialog): return all_valid = None - for instance in self._controller.instances: + for instance in self._controller.instances.values(): if not instance["active"]: continue From 56cea034aba692180f59a68814b900d5e127d8da Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 7 Oct 2022 18:41:46 +0200 Subject: [PATCH 088/201] don't call same property more then once --- openpype/tools/publisher/window.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index bc2e42f051..3b3e27660d 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -496,8 +496,9 @@ class PublisherWindow(QtWidgets.QDialog): self._set_publish_overlay_visibility(False) self._reset_btn.setEnabled(True) self._stop_btn.setEnabled(False) - validate_enabled = not self._controller.publish_has_crashed - publish_enabled = not self._controller.publish_has_crashed + publish_has_crashed = self._controller.publish_has_crashed + validate_enabled = not publish_has_crashed + publish_enabled = not publish_has_crashed if validate_enabled: validate_enabled = not self._controller.publish_has_validated if publish_enabled: From d71f201f65d453a8dacb98330e0f1fab39276d8a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 7 Oct 2022 18:42:26 +0200 Subject: [PATCH 089/201] removed 'reset_project_data_cache' used in traypublisher --- openpype/tools/publisher/control.py | 7 ------- openpype/tools/traypublisher/window.py | 3 +++ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 6765c75992..4482aea5ec 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -1213,10 +1213,6 @@ class AbstractPublisherController(object): pass - @abstractmethod - def reset_project_data_cache(self): - pass - @abstractmethod def set_comment(self, comment): """Set comment on pyblish context. @@ -1905,9 +1901,6 @@ class PublisherController(AbstractPublisherController): self._publish_next_process() - def reset_project_data_cache(self): - self._asset_docs_cache.reset() - def collect_families_from_instances(instances, only_active=False): """Collect all families for passed publish instances. diff --git a/openpype/tools/traypublisher/window.py b/openpype/tools/traypublisher/window.py index be9f12e269..dfe06d149d 100644 --- a/openpype/tools/traypublisher/window.py +++ b/openpype/tools/traypublisher/window.py @@ -30,6 +30,9 @@ class TrayPublisherController(QtPublisherController): def host(self): return self._host + def reset_project_data_cache(self): + self._asset_docs_cache.reset() + class TrayPublisherRegistry(JSONSettingRegistry): """Class handling OpenPype general settings registry. From 05344514d320c2cacba1a4a826f86b9910372839 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 7 Oct 2022 18:42:45 +0200 Subject: [PATCH 090/201] reset assets cache on controller reset --- openpype/tools/publisher/control.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 4482aea5ec..a2dd88e4fb 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -1438,6 +1438,8 @@ class PublisherController(AbstractPublisherController): # Reset avalon context self._create_context.reset_avalon_context() + self._asset_docs_cache.reset() + self._reset_plugins() # Publish part must be reset after plugins self._reset_publish() From 92f28271c5ba7d3769b453b11bf60a9b14d49e0c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 7 Oct 2022 18:43:15 +0200 Subject: [PATCH 091/201] mimic creator methods --- openpype/tools/publisher/control.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index a2dd88e4fb..9f62eed54a 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -784,6 +784,12 @@ class CreatorItem: self.instance_attributes_defs = instance_attributes_defs self.pre_create_attributes_defs = pre_create_attributes_defs + def get_instance_attr_defs(self): + return self.instance_attributes_defs + + def get_group_label(self): + return self.group_label + @classmethod def from_creator(cls, creator): if isinstance(creator, AutoCreator): From ae717d4151a34f09f0cf6b7a641bca37d22757da Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 7 Oct 2022 18:43:45 +0200 Subject: [PATCH 092/201] use creator item to get attribute definitions instead of instance --- openpype/tools/publisher/control.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 9f62eed54a..389382b96e 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -1500,7 +1500,9 @@ class PublisherController(AbstractPublisherController): output = [] _attr_defs = {} for instance in instances: - for attr_def in instance.creator_attribute_defs: + creator_identifier = instance.creator_identifier + creator_item = self._creator_items[creator_identifier] + for attr_def in creator_item.instance_attributes_defs: found_idx = None for idx, _attr_def in _attr_defs.items(): if attr_def == _attr_def: From 098bcce75193e5e46adbe29ba1d9771ab0ab2f59 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 7 Oct 2022 18:44:31 +0200 Subject: [PATCH 093/201] added some helper functions for easy overriding to avoid duplicity --- openpype/tools/publisher/control.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 389382b96e..b08486654c 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -1089,6 +1089,7 @@ class AbstractPublisherController(object): def remove_instances(self, instances): """Remove list of instances from create context.""" + # TODO expect instance ids pass @@ -1485,7 +1486,7 @@ class PublisherController(AbstractPublisherController): self._resetting_instances = False - self._emit_event("instances.refresh.finished") + self._on_create_instance_change() def emit_card_message(self, message): self._emit_event("show.card.message", {"message": message}) @@ -1494,9 +1495,10 @@ class PublisherController(AbstractPublisherController): """Collect creator attribute definitions for multuple instances. Args: - instances(list): List of created instances for + instances(List[CreatedInstance]): List of created instances for which should be attribute definitions returned. """ + output = [] _attr_defs = {} for instance in instances: @@ -1530,6 +1532,7 @@ class PublisherController(AbstractPublisherController): which should be attribute definitions returned. include_context(bool): Add context specific attribute definitions. """ + _tmp_items = [] if include_context: _tmp_items.append(self._create_context) @@ -1614,7 +1617,7 @@ class PublisherController(AbstractPublisherController): creator = self._creators[creator_identifier] creator.create(subset_name, instance_data, options) - self._emit_event("instances.refresh.finished") + self._on_create_instance_change() def save_changes(self): """Save changes happened during creation.""" @@ -1623,12 +1626,19 @@ class PublisherController(AbstractPublisherController): def remove_instances(self, instances): """""" + # TODO expect instance ids instead of instances # QUESTION Expect that instances are really removed? In that case save # reset is not required and save changes too. self.save_changes() + self._remove_instances_from_context(instances) + + self._on_create_instance_change() + + def _remove_instances_from_context(self, instances): self._create_context.remove_instances(instances) + def _on_create_instance_change(self): self._emit_event("instances.refresh.finished") # --- Publish specific implementations --- From 7e53b0354a37de52ef46d7011275b99e832e4e18 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 7 Oct 2022 18:44:51 +0200 Subject: [PATCH 094/201] prepared base class of remote qt controller --- openpype/tools/publisher/control_qt.py | 311 +++++++++++++++++++++++++ 1 file changed, 311 insertions(+) diff --git a/openpype/tools/publisher/control_qt.py b/openpype/tools/publisher/control_qt.py index 8515a7a843..c7099caf98 100644 --- a/openpype/tools/publisher/control_qt.py +++ b/openpype/tools/publisher/control_qt.py @@ -2,6 +2,8 @@ import collections from Qt import QtCore +from openpype.pipeline.create import CreatedInstance + from .control import MainThreadItem, PublisherController @@ -86,3 +88,312 @@ class QtPublisherController(PublisherController): def _qt_on_publish_stop(self): self._main_thread_processor.stop() + + +class QtRemotePublishController(QtPublisherController): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self._created_instances = {} + + def _on_create_instance_change(self): + # TODO somehow get serialized instances from client + serialized_instances = [] + + created_instances = {} + for serialized_data in serialized_instances: + item = CreatedInstance.deserialize_on_remote( + serialized_data, + self._creator_items + ) + created_instances[item.id] = item + + self._created_instances = created_instances + self._emit_event("instances.refresh.finished") + + @property + def project_name(self): + """Current context project name. + + Returns: + str: Name of project. + """ + + pass + + @property + def current_asset_name(self): + """Current context asset name. + + Returns: + Union[str, None]: Name of asset. + """ + + pass + + @property + def current_task_name(self): + """Current context task name. + + Returns: + Union[str, None]: Name of task. + """ + + pass + + @property + def host_is_valid(self): + """Host is valid for creation part. + + Host must have implemented certain functionality to be able create + in Publisher tool. + + Returns: + bool: Host can handle creation of instances. + """ + + pass + + @property + def instances(self): + """Collected/created instances. + + Returns: + List[CreatedInstance]: List of created instances. + """ + + return self._created_instances + + def get_context_title(self): + """Get context title for artist shown at the top of main window. + + Returns: + Union[str, None]: Context title for window or None. In case of None + a warning is displayed (not nice for artists). + """ + + pass + + def get_asset_docs(self): + pass + + def get_asset_hierarchy(self): + pass + + def get_task_names_by_asset_names(self, asset_names): + pass + + def get_existing_subset_names(self, asset_name): + pass + + def reset(self): + """Reset whole controller. + + This should reset create context, publish context and all variables + that are related to it. + """ + + pass + + def get_publish_attribute_definitions(self, instances, include_context): + pass + + def get_subset_name( + self, + creator_identifier, + variant, + task_name, + asset_name, + instance_id=None + ): + """Get subset name based on passed data. + + Args: + creator_identifier (str): Identifier of creator which should be + responsible for subset name creation. + variant (str): Variant value from user's input. + task_name (str): Name of task for which is instance created. + asset_name (str): Name of asset for which is instance created. + instance_id (Union[str, None]): Existing instance id when subset + name is updated. + """ + + pass + + def create( + self, creator_identifier, subset_name, instance_data, options + ): + """Trigger creation by creator identifier. + + Should also trigger refresh of instanes. + + Args: + creator_identifier (str): Identifier of Creator plugin. + subset_name (str): Calculated subset name. + instance_data (Dict[str, Any]): Base instance data with variant, + asset name and task name. + options (Dict[str, Any]): Data from pre-create attributes. + """ + + pass + + def save_changes(self): + """Save changes happened during creation.""" + + created_instance_changes = {} + for instance_id, instance in self._created_instances.items(): + created_instance_changes[instance_id] = ( + instance.remote_changes() + ) + + # TODO trigger save changes + self._trigger("save_changes", created_instance_changes) + + def remove_instances(self, instances): + """Remove list of instances from create context.""" + # TODO add Args: + + pass + + @property + def publish_has_finished(self): + """Has publishing finished. + + Returns: + bool: If publishing finished and all plugins were iterated. + """ + + pass + + @property + def publish_is_running(self): + """Publishing is running right now. + + Returns: + bool: If publishing is in progress. + """ + + pass + + @property + def publish_has_validated(self): + """Publish validation passed. + + Returns: + bool: If publishing passed last possible validation order. + """ + + pass + + @property + def publish_has_crashed(self): + """Publishing crashed for any reason. + + Returns: + bool: Publishing crashed. + """ + + pass + + @property + def publish_has_validation_errors(self): + """During validation happened at least one validation error. + + Returns: + bool: Validation error was raised during validation. + """ + + pass + + @property + def publish_max_progress(self): + """Get maximum possible progress number. + + Returns: + int: Number that can be used as 100% of publish progress bar. + """ + + pass + + @property + def publish_progress(self): + """Current progress number. + + Returns: + int: Current progress value which is between 0 and + 'publish_max_progress'. + """ + + pass + + @property + def publish_comment_is_set(self): + """Publish comment was at least once set. + + Publish comment can be set only once when publish is started for a + first time. This helpt to idetify if 'set_comment' should be called or + not. + """ + + pass + + def get_publish_crash_error(self): + pass + + def get_publish_report(self): + pass + + def get_validation_errors(self): + pass + + def publish(self): + """Trigger publishing without any order limitations.""" + + pass + + def validate(self): + """Trigger publishing which will stop after validation order.""" + + pass + + def stop_publish(self): + """Stop publishing can be also used to pause publishing. + + Pause of publishing is possible only if all plugins successfully + finished. + """ + + pass + + def run_action(self, plugin_id, action_id): + """Trigger pyblish action on a plugin. + + Args: + plugin_id (str): Id of publish plugin. + action_id (str): Id of publish action. + """ + + pass + + def set_comment(self, comment): + """Set comment on pyblish context. + + Set "comment" key on current pyblish.api.Context data. + + Args: + comment (str): Artist's comment. + """ + + pass + + def emit_card_message(self, message): + """Emit a card message which can have a lifetime. + + This is for UI purposes. Method can be extended to more arguments + in future e.g. different message timeout or type (color). + + Args: + message (str): Message that will be showed. + """ + + pass From ea4ede05a8983118987f75a9f6a169a5d84770be Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 7 Oct 2022 20:31:46 +0200 Subject: [PATCH 095/201] Hotfix for Maya + Deadline + Redshift renders without merge AOVs Fix #3953 --- .../deadline/plugins/publish/submit_maya_deadline.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 4d6068f3c0..75a3921237 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -32,6 +32,9 @@ from maya import cmds from openpype.pipeline import legacy_io +from openpype.hosts.maya.api.lib_rendersettings import RenderSettings +from openpype.hosts.maya.api.lib import get_attr_in_layer + from openpype_modules.deadline import abstract_submit_deadline from openpype_modules.deadline.abstract_submit_deadline import DeadlineJobInfo @@ -471,9 +474,10 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): job_info.AssetDependency += self.scene_path # Get layer prefix - render_products = self._instance.data["renderProducts"] - layer_metadata = render_products.layer_data - layer_prefix = layer_metadata.filePrefix + renderlayer = self._instance.data["setMembers"] + renderer = self._instance.data["renderer"] + layer_prefix_attr = RenderSettings.get_image_prefix_attr(renderer) + layer_prefix = get_attr_in_layer(layer_prefix_attr, layer=renderlayer) plugin_info = copy.deepcopy(self.plugin_info) plugin_info.update({ From 811e7853e5c031d23fa51dabbc07cc0caf3bc2f1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 10 Oct 2022 14:16:53 +0200 Subject: [PATCH 096/201] added ability to serailize and deserialize event to data --- openpype/lib/events.py | 49 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/openpype/lib/events.py b/openpype/lib/events.py index 301d62e2a6..747761fb3e 100644 --- a/openpype/lib/events.py +++ b/openpype/lib/events.py @@ -1,6 +1,7 @@ """Events holding data about specific event.""" import os import re +import copy import inspect import logging import weakref @@ -207,6 +208,12 @@ class Event(object): @property def source(self): + """Event's source used for triggering callbacks. + + Returns: + Union[str, None]: Source string or None. Source is optional. + """ + return self._source @property @@ -215,6 +222,12 @@ class Event(object): @property def topic(self): + """Event's topic used for triggering callbacks. + + Returns: + str: Topic string. + """ + return self._topic def emit(self): @@ -227,6 +240,42 @@ class Event(object): ) self._event_system.emit_event(self) + def to_data(self): + """Convert Event object to data. + + Returns: + Dict[str, Any]: Event data. + """ + + return { + "id": self.id, + "topic": self.topic, + "source": self.source, + "data": copy.deepcopy(self.data) + } + + @classmethod + def from_data(cls, event_data, event_system=None): + """Create event from data. + + Args: + event_data (Dict[str, Any]): Event data with defined keys. Can be + created using 'to_data' method. + event_system (EventSystem): System to which the event belongs. + + Returns: + Event: Event with attributes from passed data. + """ + + obj = cls( + event_data["topic"], + event_data["data"], + event_data["source"], + event_system + ) + obj._id = event_data["id"] + return obj + class EventSystem(object): """Encapsulate event handling into an object. From d1f3c8e18e7fe4c87a07007918715e1b368937a2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 10 Oct 2022 14:33:11 +0200 Subject: [PATCH 097/201] added properties with getters and setters --- openpype/tools/publisher/control.py | 128 +++++++++++++++++++++------- 1 file changed, 95 insertions(+), 33 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index b08486654c..9ca9924f39 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -1278,12 +1278,15 @@ class PublisherController(AbstractPublisherController): self._publish_validation_errors = PublishValidationErrors() # Any other exception that happened during publishing self._publish_error = None + self._publish_error_msg = None # Publishing is in progress self._publish_is_running = False # Publishing is over validation order - self._publish_validated = False + self._publish_has_validated = False # Publishing should stop at validation stage self._publish_up_validation = False + self._publish_has_validation_errors = False + self._publish_has_crashed = False # All publish plugins are processed self._publish_finished = False self._publish_max_progress = 0 @@ -1642,41 +1645,100 @@ class PublisherController(AbstractPublisherController): self._emit_event("instances.refresh.finished") # --- Publish specific implementations --- - @property - def publish_has_finished(self): - return self._publish_finished - - @property - def publish_is_running(self): - return self._publish_is_running - - @property - def publish_has_validated(self): - return self._publish_validated - - @property - def publish_has_crashed(self): - return bool(self._publish_error) - - @property - def publish_has_validation_errors(self): - return bool(self._publish_validation_errors) - - @property - def publish_max_progress(self): - return self._publish_max_progress - - @property - def publish_progress(self): - return self._publish_progress - - @property - def publish_comment_is_set(self): - return self._publish_comment_is_set - def get_publish_crash_error(self): return self._publish_error + def _get_publish_has_finished(self): + return self._publish_finished + + def _set_publish_has_finished(self, value): + if self._publish_finished != value: + self._publish_finished = value + + def _get_publish_is_running(self): + return self._publish_is_running + + def _set_publish_is_running(self, value): + if self._publish_is_running != value: + self._publish_is_running = value + self._emit_event("publish.is_running.changed", {"value": value}) + + def _get_publish_has_validated(self): + return self._publish_has_validated + + def _set_publish_has_validated(self, value): + if self._publish_has_validated != value: + self._publish_has_validated = value + self._emit_event("publish.has_validated.changed", {"value": value}) + + def _get_publish_has_crashed(self): + return self._publish_has_crashed + + def _set_publish_has_crashed(self, value): + if self._publish_has_crashed != value: + self._publish_has_crashed = value + self._emit_event("publish.has_crashed.changed", {"value": value}) + + def _get_publish_has_validation_errors(self): + return self._publish_has_validation_errors + + def _set_publish_has_validation_errors(self, value): + if self._publish_has_validation_errors != value: + self._publish_has_validation_errors = value + self._emit_event( + "publish.has_validation_errors.changed", + {"value": value} + ) + + def _get_publish_max_progress(self): + return self._publish_max_progress + + def _set_publish_max_progress(self, value): + if self._publish_max_progress != value: + self._publish_max_progress = value + self._emit_event("publish.max_progress.changed", {"value": value}) + + def _get_publish_progress(self): + return self._publish_progress + + def _set_publish_progress(self, value): + if self._publish_progress != value: + self._publish_progress = value + self._emit_event("publish.progress.changed", {"value": value}) + + def _get_publish_error_msg(self): + return self._publish_error_msg + + def _set_publish_error_msg(self, value): + if self._publish_error_msg != value: + self._publish_error_msg = value + self._emit_event("publish.publish_error.changed", {"value": value}) + + publish_has_finished = property( + _get_publish_has_finished, _set_publish_has_finished + ) + publish_is_running = property( + _get_publish_is_running, _set_publish_is_running + ) + publish_has_validated = property( + _get_publish_has_validated, _set_publish_has_validated + ) + publish_has_crashed = property( + _get_publish_has_crashed, _set_publish_has_crashed + ) + publish_has_validation_errors = property( + _get_publish_has_validation_errors, _set_publish_has_validation_errors + ) + publish_max_progress = property( + _get_publish_max_progress, _set_publish_max_progress + ) + publish_progress = property( + _get_publish_progress, _set_publish_progress + ) + publish_error_msg = property( + _get_publish_error_msg, _set_publish_error_msg + ) + def get_publish_report(self): return self._publish_report.get_report(self._publish_plugins) From c907383f88f3db6fd7eaef76321bcab11069a958 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 10 Oct 2022 14:34:23 +0200 Subject: [PATCH 098/201] use events to handle controller changes --- openpype/tools/publisher/control.py | 110 +++++++++++------- .../tools/publisher/widgets/publish_frame.py | 33 +++--- openpype/tools/publisher/window.py | 13 +-- 3 files changed, 86 insertions(+), 70 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 9ca9924f39..b4fc7cb91a 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -20,6 +20,7 @@ from openpype.lib.attribute_definitions import ( ) from openpype.pipeline import ( PublishValidationError, + KnownPublishError, registered_host, ) from openpype.pipeline.create import ( @@ -909,7 +910,7 @@ class AbstractPublisherController(object): def event_system(self): """Inner event system for publisher controller. - Event system is autocreated. + Is used for communication with UI. Event system is autocreated. Known topics: "show.detailed.help" - Detailed help requested (UI related). @@ -919,10 +920,20 @@ class AbstractPublisherController(object): "publish.reset.finished" - Controller reset finished. "publish.process.started" - Publishing started. Can be started from paused state. - "publish.process.validated" - Publishing passed validation. "publish.process.stopped" - Publishing stopped/paused process. "publish.process.plugin.changed" - Plugin state has changed. "publish.process.instance.changed" - Instance state has changed. + "publish.has_validated.changed" - Attr 'publish_has_validated' + changed. + "publish.is_running.changed" - Attr 'publish_is_running' changed. + "publish.has_validated.changed" - Attr 'has_validated' changed. + "publish.has_crashed.changed" - Attr 'publish_has_crashed' changed. + "publish.publish_error.changed" - Attr 'publish_error' + "publish.has_validation_errors.changed" - Attr + 'has_validation_errors' changed. + "publish.max_progress.changed" - Attr 'publish_max_progress' + changed. + "publish.progress.changed" - Attr 'publish_progress' changed. Returns: EventSystem: Event system which can trigger callbacks for topics. @@ -1158,27 +1169,22 @@ class AbstractPublisherController(object): """Current progress number. Returns: - int: Current progress value which is between 0 and - 'publish_max_progress'. + int: Current progress value from 0 to 'publish_max_progress'. """ pass @abstractproperty - def publish_comment_is_set(self): - """Publish comment was at least once set. + def publish_error_msg(self): + """Current error message which cause fail of publishing. - Publish comment can be set only once when publish is started for a - first time. This helpt to idetify if 'set_comment' should be called or - not. + Returns: + Union[str, None]: Message which will be showed to artist or + None. """ pass - @abstractmethod - def get_publish_crash_error(self): - pass - @abstractmethod def get_publish_report(self): pass @@ -1277,7 +1283,6 @@ class PublisherController(AbstractPublisherController): # Store exceptions of validation error self._publish_validation_errors = PublishValidationErrors() # Any other exception that happened during publishing - self._publish_error = None self._publish_error_msg = None # Publishing is in progress self._publish_is_running = False @@ -1645,9 +1650,6 @@ class PublisherController(AbstractPublisherController): self._emit_event("instances.refresh.finished") # --- Publish specific implementations --- - def get_publish_crash_error(self): - return self._publish_error - def _get_publish_has_finished(self): return self._publish_finished @@ -1746,10 +1748,13 @@ class PublisherController(AbstractPublisherController): return self._publish_validation_errors.create_report() def _reset_publish(self): - self._publish_is_running = False - self._publish_validated = False + self.publish_is_running = False + self.publish_has_validated = False + self.publish_has_crashed = False + self.publish_has_validation_errors = False + self.publish_finished = False + self._publish_up_validation = False - self._publish_finished = False self._publish_comment_is_set = False self._main_thread_iter = self._publish_iterator() @@ -1768,16 +1773,25 @@ class PublisherController(AbstractPublisherController): self._publish_report.reset(self._publish_context, self._create_context) self._publish_validation_errors.reset(self._publish_plugins_proxy) - self._publish_error = None - self._publish_max_progress = len(self._publish_plugins) - self._publish_progress = 0 + self.publish_error_msg = None + + self.publish_max_progress = len(self._publish_plugins) + self.publish_progress = 0 self._emit_event("publish.reset.finished") def set_comment(self, comment): - self._publish_context.data["comment"] = comment - self._publish_comment_is_set = True + """Set comment from ui to pyblish context. + + This should be called always before publishing is started but should + happen only once on first publish start thus variable + '_publish_comment_is_set' is used to keep track about the information. + """ + + if not self._publish_comment_is_set: + self._publish_context.data["comment"] = comment + self._publish_comment_is_set = True def publish(self): """Run publishing.""" @@ -1786,20 +1800,20 @@ class PublisherController(AbstractPublisherController): def validate(self): """Run publishing and stop after Validation.""" - if self._publish_validated: + if self.publish_has_validated: return self._publish_up_validation = True self._start_publish() def _start_publish(self): """Start or continue in publishing.""" - if self._publish_is_running: + if self.publish_is_running: return # Make sure changes are saved self.save_changes() - self._publish_is_running = True + self.publish_is_running = True self._emit_event("publish.process.started") @@ -1807,14 +1821,14 @@ class PublisherController(AbstractPublisherController): def _stop_publish(self): """Stop or pause publishing.""" - self._publish_is_running = False + self.publish_is_running = False self._emit_event("publish.process.stopped") def stop_publish(self): """Stop publishing process (any reason).""" - if self._publish_is_running: + if self.publish_is_running: self._stop_publish() def run_action(self, plugin_id, action_id): @@ -1835,14 +1849,14 @@ class PublisherController(AbstractPublisherController): # There are validation errors and validation is passed # - can't do any progree if ( - self._publish_validated - and self._publish_validation_errors + self.publish_has_validated + and self.publish_has_validation_errors ): item = MainThreadItem(self.stop_publish) # Any unexpected error happened # - everything should stop - elif self._publish_error: + elif self.publish_has_crashed: item = MainThreadItem(self.stop_publish) # Everything is ok so try to get new processing item @@ -1871,23 +1885,20 @@ class PublisherController(AbstractPublisherController): self._publish_progress = idx # Check if plugin is over validation order - if not self._publish_validated: - self._publish_validated = ( + if not self.publish_has_validated: + self.publish_has_validated = ( plugin.order >= self._validation_order ) - # Trigger callbacks when validation stage is passed - if self._publish_validated: - self._emit_event("publish.process.validated") # Stop if plugin is over validation order and process # should process up to validation. - if self._publish_up_validation and self._publish_validated: + if self._publish_up_validation and self.publish_has_validated: yield MainThreadItem(self.stop_publish) # Stop if validation is over and validation errors happened if ( - self._publish_validated - and self._publish_validation_errors + self.publish_has_validated + and self.publish_has_validation_errors ): yield MainThreadItem(self.stop_publish) @@ -1952,11 +1963,12 @@ class PublisherController(AbstractPublisherController): self._publish_report.set_plugin_skipped() # Cleanup of publishing process - self._publish_finished = True - self._publish_progress = self._publish_max_progress + self.publish_finished = True + self.publish_progress = self._publish_max_progress yield MainThreadItem(self.stop_publish) def _add_validation_error(self, result): + self.publish_has_validation_errors = False self._publish_validation_errors.add_error( result["plugin"], result["error"], @@ -1974,12 +1986,20 @@ class PublisherController(AbstractPublisherController): if exception: if ( isinstance(exception, PublishValidationError) - and not self._publish_validated + and not self.publish_has_validated ): self._add_validation_error(result) else: - self._publish_error = exception + if isinstance(exception, KnownPublishError): + msg = str(exception) + else: + msg = ( + "Something went wrong. Send report" + " to your supervisor or OpenPype." + ) + self.publish_error_msg = msg + self.publish_has_crashed = False self._publish_next_process() diff --git a/openpype/tools/publisher/widgets/publish_frame.py b/openpype/tools/publisher/widgets/publish_frame.py index b49f005640..8fd783a3c4 100644 --- a/openpype/tools/publisher/widgets/publish_frame.py +++ b/openpype/tools/publisher/widgets/publish_frame.py @@ -4,8 +4,6 @@ import time from Qt import QtWidgets, QtCore -from openpype.pipeline import KnownPublishError - from .widgets import ( StopBtn, ResetBtn, @@ -170,7 +168,7 @@ class PublishFrame(QtWidgets.QWidget): "publish.process.started", self._on_publish_start ) controller.event_system.add_callback( - "publish.process.validated", self._on_publish_validated + "publish.has_validated.changed", self._on_publish_validated_change ) controller.event_system.add_callback( "publish.process.stopped", self._on_publish_stop @@ -322,8 +320,9 @@ class PublishFrame(QtWidgets.QWidget): self._validate_btn.setEnabled(False) self._publish_btn.setEnabled(False) - def _on_publish_validated(self): - self._validate_btn.setEnabled(False) + def _on_publish_validated_change(self, event): + if event["value"]: + self._validate_btn.setEnabled(False) def _on_instance_change(self, event): """Change instance label when instance is going to be processed.""" @@ -360,10 +359,10 @@ class PublishFrame(QtWidgets.QWidget): self._validate_btn.setEnabled(validate_enabled) self._publish_btn.setEnabled(publish_enabled) - error = self._controller.get_publish_crash_error() + error_msg = self._controller.publish_error_msg validation_errors = self._controller.get_validation_errors() - if error: - self._set_error(error) + if error_msg: + self._set_error_msg(error_msg) elif validation_errors: self._set_progress_visibility(False) @@ -387,16 +386,16 @@ class PublishFrame(QtWidgets.QWidget): self._set_success_property(-1) - def _set_error(self, error): + def _set_error_msg(self, error_msg): + """Show error message to artist. + + Args: + error_msg (str): Message which is showed to artist. + """ + self._set_main_label("Error happened") - if isinstance(error, KnownPublishError): - msg = str(error) - else: - msg = ( - "Something went wrong. Send report" - " to your supervisor or OpenPype." - ) - self._message_label_top.setText(msg) + + self._message_label_top.setText(error_msg) self._set_success_property(0) diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index 3b3e27660d..e2beb480bd 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -248,7 +248,7 @@ class PublisherWindow(QtWidgets.QDialog): "publish.process.started", self._on_publish_start ) controller.event_system.add_callback( - "publish.process.validated", self._on_publish_validated + "publish.has_validated.changed", self._on_publish_validated_change ) controller.event_system.add_callback( "publish.process.stopped", self._on_publish_stop @@ -439,11 +439,7 @@ class PublisherWindow(QtWidgets.QDialog): self._controller.stop_publish() def _set_publish_comment(self): - if self._controller.publish_comment_is_set: - return - - comment = self._comment_input.text() - self._controller.set_comment(comment) + self._controller.set_comment(self._comment_input.text()) def _on_validate_clicked(self): self._set_publish_comment() @@ -489,8 +485,9 @@ class PublisherWindow(QtWidgets.QDialog): if self._tabs_widget.is_current_tab(self._create_tab): self._tabs_widget.set_current_tab("publish") - def _on_publish_validated(self): - self._validate_btn.setEnabled(False) + def _on_publish_validated_change(self, event): + if event["value"]: + self._validate_btn.setEnabled(False) def _on_publish_stop(self): self._set_publish_overlay_visibility(False) From 0f514aa5528efc44938a4eec671692d41329daed Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 10 Oct 2022 14:38:52 +0200 Subject: [PATCH 099/201] mark methods that should be abstract in remote controller --- openpype/tools/publisher/control_qt.py | 112 ++++--------------------- 1 file changed, 18 insertions(+), 94 deletions(-) diff --git a/openpype/tools/publisher/control_qt.py b/openpype/tools/publisher/control_qt.py index c7099caf98..8f0f304f9a 100644 --- a/openpype/tools/publisher/control_qt.py +++ b/openpype/tools/publisher/control_qt.py @@ -111,7 +111,7 @@ class QtRemotePublishController(QtPublisherController): self._created_instances = created_instances self._emit_event("instances.refresh.finished") - @property + @abstractproperty def project_name(self): """Current context project name. @@ -121,7 +121,7 @@ class QtRemotePublishController(QtPublisherController): pass - @property + @abstractproperty def current_asset_name(self): """Current context asset name. @@ -131,7 +131,7 @@ class QtRemotePublishController(QtPublisherController): pass - @property + @abstractproperty def current_task_name(self): """Current context task name. @@ -141,7 +141,7 @@ class QtRemotePublishController(QtPublisherController): pass - @property + @abstractproperty def host_is_valid(self): """Host is valid for creation part. @@ -186,6 +186,7 @@ class QtRemotePublishController(QtPublisherController): def get_existing_subset_names(self, asset_name): pass + @abstractmethod def reset(self): """Reset whole controller. @@ -195,9 +196,7 @@ class QtRemotePublishController(QtPublisherController): pass - def get_publish_attribute_definitions(self, instances, include_context): - pass - + @abstractmethod def get_subset_name( self, creator_identifier, @@ -220,6 +219,7 @@ class QtRemotePublishController(QtPublisherController): pass + @abstractmethod def create( self, creator_identifier, subset_name, instance_data, options ): @@ -237,6 +237,7 @@ class QtRemotePublishController(QtPublisherController): pass + @abstractmethod def save_changes(self): """Save changes happened during creation.""" @@ -246,116 +247,36 @@ class QtRemotePublishController(QtPublisherController): instance.remote_changes() ) - # TODO trigger save changes - self._trigger("save_changes", created_instance_changes) + # Send 'created_instance_changes' value to client + @abstractmethod def remove_instances(self, instances): """Remove list of instances from create context.""" # TODO add Args: pass - @property - def publish_has_finished(self): - """Has publishing finished. - - Returns: - bool: If publishing finished and all plugins were iterated. - """ - - pass - - @property - def publish_is_running(self): - """Publishing is running right now. - - Returns: - bool: If publishing is in progress. - """ - - pass - - @property - def publish_has_validated(self): - """Publish validation passed. - - Returns: - bool: If publishing passed last possible validation order. - """ - - pass - - @property - def publish_has_crashed(self): - """Publishing crashed for any reason. - - Returns: - bool: Publishing crashed. - """ - - pass - - @property - def publish_has_validation_errors(self): - """During validation happened at least one validation error. - - Returns: - bool: Validation error was raised during validation. - """ - - pass - - @property - def publish_max_progress(self): - """Get maximum possible progress number. - - Returns: - int: Number that can be used as 100% of publish progress bar. - """ - - pass - - @property - def publish_progress(self): - """Current progress number. - - Returns: - int: Current progress value which is between 0 and - 'publish_max_progress'. - """ - - pass - - @property - def publish_comment_is_set(self): - """Publish comment was at least once set. - - Publish comment can be set only once when publish is started for a - first time. This helpt to idetify if 'set_comment' should be called or - not. - """ - - pass - - def get_publish_crash_error(self): - pass - + @abstractmethod def get_publish_report(self): pass + @abstractmethod def get_validation_errors(self): pass + @abstractmethod def publish(self): """Trigger publishing without any order limitations.""" pass + @abstractmethod def validate(self): """Trigger publishing which will stop after validation order.""" pass + @abstractmethod def stop_publish(self): """Stop publishing can be also used to pause publishing. @@ -365,6 +286,7 @@ class QtRemotePublishController(QtPublisherController): pass + @abstractmethod def run_action(self, plugin_id, action_id): """Trigger pyblish action on a plugin. @@ -375,6 +297,7 @@ class QtRemotePublishController(QtPublisherController): pass + @abstractmethod def set_comment(self, comment): """Set comment on pyblish context. @@ -386,6 +309,7 @@ class QtRemotePublishController(QtPublisherController): pass + @abstractmethod def emit_card_message(self, message): """Emit a card message which can have a lifetime. From ebb6a17d9793b7aee94e01cb4ebe572bab26ecea Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 10 Oct 2022 14:43:57 +0200 Subject: [PATCH 100/201] trigger event on finished attribute change --- openpype/tools/publisher/control.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index b4fc7cb91a..dd7e90ea5f 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -1656,6 +1656,7 @@ class PublisherController(AbstractPublisherController): def _set_publish_has_finished(self, value): if self._publish_finished != value: self._publish_finished = value + self._emit_event("publish.finished.changed", {"value": value}) def _get_publish_is_running(self): return self._publish_is_running From 8ffdbf0dcfc70d0bf2741cdce7464864e82f0051 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 10 Oct 2022 14:44:51 +0200 Subject: [PATCH 101/201] instances are removed by ids --- openpype/tools/publisher/control.py | 13 +++++++++---- openpype/tools/publisher/control_qt.py | 2 +- openpype/tools/publisher/widgets/overview_widget.py | 6 +++++- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index dd7e90ea5f..0981f48dbe 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -1098,7 +1098,7 @@ class AbstractPublisherController(object): pass - def remove_instances(self, instances): + def remove_instances(self, instance_ids): """Remove list of instances from create context.""" # TODO expect instance ids @@ -1632,18 +1632,23 @@ class PublisherController(AbstractPublisherController): if self._create_context.host_is_valid: self._create_context.save_changes() - def remove_instances(self, instances): + def remove_instances(self, instance_ids): """""" # TODO expect instance ids instead of instances # QUESTION Expect that instances are really removed? In that case save # reset is not required and save changes too. self.save_changes() - self._remove_instances_from_context(instances) + self._remove_instances_from_context(instance_ids) self._on_create_instance_change() - def _remove_instances_from_context(self, instances): + def _remove_instances_from_context(self, instance_ids): + instances_by_id = self._create_context.instances_by_id + instances = [ + instances_by_id[instance_id] + for instance_id in instance_ids + ] self._create_context.remove_instances(instances) def _on_create_instance_change(self): diff --git a/openpype/tools/publisher/control_qt.py b/openpype/tools/publisher/control_qt.py index 8f0f304f9a..69809bcfe8 100644 --- a/openpype/tools/publisher/control_qt.py +++ b/openpype/tools/publisher/control_qt.py @@ -250,7 +250,7 @@ class QtRemotePublishController(QtPublisherController): # Send 'created_instance_changes' value to client @abstractmethod - def remove_instances(self, instances): + def remove_instances(self, instance_ids): """Remove list of instances from create context.""" # TODO add Args: diff --git a/openpype/tools/publisher/widgets/overview_widget.py b/openpype/tools/publisher/widgets/overview_widget.py index 08c2ce0513..3c67e6298e 100644 --- a/openpype/tools/publisher/widgets/overview_widget.py +++ b/openpype/tools/publisher/widgets/overview_widget.py @@ -224,7 +224,11 @@ class OverviewWidget(QtWidgets.QFrame): dialog.exec_() # Skip if OK was not clicked if dialog.result() == QtWidgets.QMessageBox.Ok: - self._controller.remove_instances(instances) + instance_ids = { + instance.id + for instance in instances + } + self._controller.remove_instances(instance_ids) def _on_change_view_clicked(self): self._change_view_type() From 91b66812dbb9adc00a42b634608291d934a7e30b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 10 Oct 2022 14:45:16 +0200 Subject: [PATCH 102/201] added some basic implementation of client event handling --- openpype/tools/publisher/control_qt.py | 71 ++++++++++++++++++++++++-- 1 file changed, 68 insertions(+), 3 deletions(-) diff --git a/openpype/tools/publisher/control_qt.py b/openpype/tools/publisher/control_qt.py index 69809bcfe8..5638ea554a 100644 --- a/openpype/tools/publisher/control_qt.py +++ b/openpype/tools/publisher/control_qt.py @@ -1,7 +1,9 @@ import collections +from ABC import abstractmethod, abstractproperty from Qt import QtCore +from openpype.lib.events import Event from openpype.pipeline.create import CreatedInstance from .control import MainThreadItem, PublisherController @@ -90,15 +92,29 @@ class QtPublisherController(PublisherController): self._main_thread_processor.stop() -class QtRemotePublishController(QtPublisherController): +class QtRemotePublishController(PublisherController): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._created_instances = {} + self._main_thread_processor = MainThreadProcess() + self._main_thread_processor.start() + + @abstractmethod + def _get_serialized_instances(self): + """Receive serialized instances from client process. + + Returns: + List[Dict[str, Any]]: Serialized instances. + """ + + pass + + def _process_main_thread_item(self, item): + self._main_thread_processor.add_item(item) def _on_create_instance_change(self): - # TODO somehow get serialized instances from client - serialized_instances = [] + serialized_instances = self._get_serialized_instances() created_instances = {} for serialized_data in serialized_instances: @@ -111,6 +127,55 @@ class QtRemotePublishController(QtPublisherController): self._created_instances = created_instances self._emit_event("instances.refresh.finished") + def remote_events_handler(self, event_data): + event = Event.from_data(event_data) + + # Topics that cause "replication" of controller changes + if event.topic == "publish.max_progress.changed": + self.publish_max_progress = event["value"] + return + + if event.topic == "publish.progress.changed": + self.publish_progress = event["value"] + return + + if event.topic == "publish.has_validated.changed": + self.publish_has_validated = event["value"] + return + + if event.topic == "publish.is_running.changed": + self.publish_is_running = event["value"] + return + + if event.topic == "publish.publish_error.changed": + self.publish_error_msg = event["value"] + return + + if event.topic == "publish.has_crashed.changed": + self.publish_has_crashed = event["value"] + return + + if event.topic == "publish.has_validation_errors.changed": + self.publish_has_validation_errors = event["value"] + return + + if event.topic == "publish.finished.changed": + self.publish_finished = event["value"] + return + + # Topics that can be just passed by because are not affecting + # controller itself + # - "show.card.message" + # - "show.detailed.help" + # - "publish.reset.finished" + # - "instances.refresh.finished" + # - "plugins.refresh.finished" + # - "publish.process.started" + # - "publish.process.stopped" + # - "publish.process.plugin.changed" + # - "publish.process.instance.changed" + self.event_system.emit_event(event) + @abstractproperty def project_name(self): """Current context project name. From ac3326d29690183206eed23520a6fad48e0982de Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 10 Oct 2022 15:08:43 +0200 Subject: [PATCH 103/201] fix import --- openpype/tools/publisher/control_qt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/publisher/control_qt.py b/openpype/tools/publisher/control_qt.py index 5638ea554a..10f576a3f3 100644 --- a/openpype/tools/publisher/control_qt.py +++ b/openpype/tools/publisher/control_qt.py @@ -1,5 +1,5 @@ import collections -from ABC import abstractmethod, abstractproperty +from abc import abstractmethod, abstractproperty from Qt import QtCore From 187411ef8bab8241662add0c57f81d538f3b008c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 10 Oct 2022 15:10:18 +0200 Subject: [PATCH 104/201] added BaseController to handle base attributes --- openpype/tools/publisher/control.py | 319 +++++++++++++++------------- 1 file changed, 172 insertions(+), 147 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 0981f48dbe..f2f6d07cd6 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -917,7 +917,8 @@ class AbstractPublisherController(object): "show.card.message" - Show card message request (UI related). "instances.refresh.finished" - Instances are refreshed. "plugins.refresh.finished" - Plugins refreshed. - "publish.reset.finished" - Controller reset finished. + "publish.reset.finished" - Publish context reset finished. + "controller.reset.finished" - Controller reset finished. "publish.process.started" - Publishing started. Can be started from paused state. "publish.process.stopped" - Publishing stopped/paused process. @@ -934,6 +935,8 @@ class AbstractPublisherController(object): "publish.max_progress.changed" - Attr 'publish_max_progress' changed. "publish.progress.changed" - Attr 'publish_progress' changed. + "publish.host_is_valid.changed" - Attr 'host_is_valid' changed. + "publish.finished.changed" - Attr 'publish_finished' changed. Returns: EventSystem: Event system which can trigger callbacks for topics. @@ -943,6 +946,11 @@ class AbstractPublisherController(object): self._event_system = EventSystem() return self._event_system + def _emit_event(self, topic, data=None): + if data is None: + data = {} + self.event_system.emit(topic, data, "controller") + @abstractproperty def project_name(self): """Current context project name. @@ -1252,7 +1260,156 @@ class AbstractPublisherController(object): pass -class PublisherController(AbstractPublisherController): +class BasePublishController(AbstractPublisherController): + def __init__(self): + # Controller must implement it's update + self._creator_items = {} + + self._host_is_valid = False + + # Any other exception that happened during publishing + self._publish_error_msg = None + # Publishing is in progress + self._publish_is_running = False + # Publishing is over validation order + self._publish_has_validated = False + + self._publish_has_validation_errors = False + self._publish_has_crashed = False + # All publish plugins are processed + self._publish_finished = False + self._publish_max_progress = 0 + self._publish_progress = 0 + + @property + def creator_items(self): + """Creators that can be shown in create dialog.""" + + return self._creator_items + + def get_creator_icon(self, identifier): + creator_item = self._creator_items.get(identifier) + if creator_item is not None: + return creator_item.icon + return None + + def _get_host_is_valid(self): + return self._host_is_valid + + def _set_host_is_valid(self, value): + if self._host_is_valid != value: + self._host_is_valid = value + self._emit_event("publish.host_is_valid.changed", {"value": value}) + + def _get_publish_has_finished(self): + return self._publish_finished + + def _set_publish_has_finished(self, value): + if self._publish_finished != value: + self._publish_finished = value + self._emit_event("publish.finished.changed", {"value": value}) + + def _get_publish_is_running(self): + return self._publish_is_running + + def _set_publish_is_running(self, value): + if self._publish_is_running != value: + self._publish_is_running = value + self._emit_event("publish.is_running.changed", {"value": value}) + + def _get_publish_has_validated(self): + return self._publish_has_validated + + def _set_publish_has_validated(self, value): + if self._publish_has_validated != value: + self._publish_has_validated = value + self._emit_event("publish.has_validated.changed", {"value": value}) + + def _get_publish_has_crashed(self): + return self._publish_has_crashed + + def _set_publish_has_crashed(self, value): + if self._publish_has_crashed != value: + self._publish_has_crashed = value + self._emit_event("publish.has_crashed.changed", {"value": value}) + + def _get_publish_has_validation_errors(self): + return self._publish_has_validation_errors + + def _set_publish_has_validation_errors(self, value): + if self._publish_has_validation_errors != value: + self._publish_has_validation_errors = value + self._emit_event( + "publish.has_validation_errors.changed", + {"value": value} + ) + + def _get_publish_max_progress(self): + return self._publish_max_progress + + def _set_publish_max_progress(self, value): + if self._publish_max_progress != value: + self._publish_max_progress = value + self._emit_event("publish.max_progress.changed", {"value": value}) + + def _get_publish_progress(self): + return self._publish_progress + + def _set_publish_progress(self, value): + if self._publish_progress != value: + self._publish_progress = value + self._emit_event("publish.progress.changed", {"value": value}) + + def _get_publish_error_msg(self): + return self._publish_error_msg + + def _set_publish_error_msg(self, value): + if self._publish_error_msg != value: + self._publish_error_msg = value + self._emit_event("publish.publish_error.changed", {"value": value}) + + host_is_valid = property( + _get_host_is_valid, _set_host_is_valid + ) + publish_has_finished = property( + _get_publish_has_finished, _set_publish_has_finished + ) + publish_is_running = property( + _get_publish_is_running, _set_publish_is_running + ) + publish_has_validated = property( + _get_publish_has_validated, _set_publish_has_validated + ) + publish_has_crashed = property( + _get_publish_has_crashed, _set_publish_has_crashed + ) + publish_has_validation_errors = property( + _get_publish_has_validation_errors, _set_publish_has_validation_errors + ) + publish_max_progress = property( + _get_publish_max_progress, _set_publish_max_progress + ) + publish_progress = property( + _get_publish_progress, _set_publish_progress + ) + publish_error_msg = property( + _get_publish_error_msg, _set_publish_error_msg + ) + + def _reset_attributes(self): + """Reset most of attributes that can be reset.""" + + self.publish_is_running = False + self.publish_has_validated = False + self.publish_has_crashed = False + self.publish_has_validation_errors = False + self.publish_finished = False + + self.publish_error_msg = None + self.publish_progress = 0 + + +class PublisherController(BasePublishController): """Middleware between UI, CreateContext and publish Context. Handle both creation and publishing parts. @@ -1265,6 +1422,8 @@ class PublisherController(AbstractPublisherController): _log = None def __init__(self, dbcon=None, headless=False): + super(PublisherController, self).__init__() + self._host = registered_host() self._headless = headless @@ -1272,8 +1431,6 @@ class PublisherController(AbstractPublisherController): self._host, dbcon, headless=headless, reset=False ) - self._creator_items = {} - self._publish_plugins_proxy = None # pyblish.api.Context @@ -1282,20 +1439,9 @@ class PublisherController(AbstractPublisherController): self._publish_report = PublishReport(self) # Store exceptions of validation error self._publish_validation_errors = PublishValidationErrors() - # Any other exception that happened during publishing - self._publish_error_msg = None - # Publishing is in progress - self._publish_is_running = False - # Publishing is over validation order - self._publish_has_validated = False + # Publishing should stop at validation stage self._publish_up_validation = False - self._publish_has_validation_errors = False - self._publish_has_crashed = False - # All publish plugins are processed - self._publish_finished = False - self._publish_max_progress = 0 - self._publish_progress = 0 # This information is not much important for controller but for widget # which can change (and set) the comment. self._publish_comment_is_set = False @@ -1317,12 +1463,6 @@ class PublisherController(AbstractPublisherController): # Cacher of avalon documents self._asset_docs_cache = AssetDocsCache(self) - @property - def log(self): - if self._log is None: - self._log = logging.getLogger("PublisherController") - return self._log - @property def project_name(self): """Current project context defined by host. @@ -1364,28 +1504,11 @@ class PublisherController(AbstractPublisherController): return self._create_context.creators - @property - def creator_items(self): - """Creators that can be shown in create dialog.""" - - return self._creator_items - - @property - def host_is_valid(self): - """Host is valid for creation.""" - - return self._create_context.host_is_valid - @property def _publish_plugins(self): """Publish plugins.""" return self._create_context.publish_plugins - def _emit_event(self, topic, data=None): - if data is None: - data = {} - self.event_system.emit(topic, data, "controller") - # --- Publish specific callbacks --- def get_asset_docs(self): """Get asset documents from cache for whole project.""" @@ -1450,6 +1573,8 @@ class PublisherController(AbstractPublisherController): self.save_changes() + self.host_is_valid = self._create_context.host_is_valid + # Reset avalon context self._create_context.reset_avalon_context() @@ -1460,6 +1585,8 @@ class PublisherController(AbstractPublisherController): self._reset_publish() self._reset_instances() + self._emit_event("controller.reset.finished") + self.emit_card_message("Refreshed..") def _reset_plugins(self): @@ -1584,12 +1711,6 @@ class PublisherController(AbstractPublisherController): )) return output - def get_creator_icon(self, identifier): - creator_item = self._creator_items.get(identifier) - if creator_item is not None: - return creator_item.icon - return None - def get_subset_name( self, creator_identifier, @@ -1633,7 +1754,11 @@ class PublisherController(AbstractPublisherController): self._create_context.save_changes() def remove_instances(self, instance_ids): - """""" + """Remove instances based on instance ids. + + Args: + instance_ids (List[str]): List of instance ids to remove. + """ # TODO expect instance ids instead of instances # QUESTION Expect that instances are really removed? In that case save # reset is not required and save changes too. @@ -1654,99 +1779,6 @@ class PublisherController(AbstractPublisherController): def _on_create_instance_change(self): self._emit_event("instances.refresh.finished") - # --- Publish specific implementations --- - def _get_publish_has_finished(self): - return self._publish_finished - - def _set_publish_has_finished(self, value): - if self._publish_finished != value: - self._publish_finished = value - self._emit_event("publish.finished.changed", {"value": value}) - - def _get_publish_is_running(self): - return self._publish_is_running - - def _set_publish_is_running(self, value): - if self._publish_is_running != value: - self._publish_is_running = value - self._emit_event("publish.is_running.changed", {"value": value}) - - def _get_publish_has_validated(self): - return self._publish_has_validated - - def _set_publish_has_validated(self, value): - if self._publish_has_validated != value: - self._publish_has_validated = value - self._emit_event("publish.has_validated.changed", {"value": value}) - - def _get_publish_has_crashed(self): - return self._publish_has_crashed - - def _set_publish_has_crashed(self, value): - if self._publish_has_crashed != value: - self._publish_has_crashed = value - self._emit_event("publish.has_crashed.changed", {"value": value}) - - def _get_publish_has_validation_errors(self): - return self._publish_has_validation_errors - - def _set_publish_has_validation_errors(self, value): - if self._publish_has_validation_errors != value: - self._publish_has_validation_errors = value - self._emit_event( - "publish.has_validation_errors.changed", - {"value": value} - ) - - def _get_publish_max_progress(self): - return self._publish_max_progress - - def _set_publish_max_progress(self, value): - if self._publish_max_progress != value: - self._publish_max_progress = value - self._emit_event("publish.max_progress.changed", {"value": value}) - - def _get_publish_progress(self): - return self._publish_progress - - def _set_publish_progress(self, value): - if self._publish_progress != value: - self._publish_progress = value - self._emit_event("publish.progress.changed", {"value": value}) - - def _get_publish_error_msg(self): - return self._publish_error_msg - - def _set_publish_error_msg(self, value): - if self._publish_error_msg != value: - self._publish_error_msg = value - self._emit_event("publish.publish_error.changed", {"value": value}) - - publish_has_finished = property( - _get_publish_has_finished, _set_publish_has_finished - ) - publish_is_running = property( - _get_publish_is_running, _set_publish_is_running - ) - publish_has_validated = property( - _get_publish_has_validated, _set_publish_has_validated - ) - publish_has_crashed = property( - _get_publish_has_crashed, _set_publish_has_crashed - ) - publish_has_validation_errors = property( - _get_publish_has_validation_errors, _set_publish_has_validation_errors - ) - publish_max_progress = property( - _get_publish_max_progress, _set_publish_max_progress - ) - publish_progress = property( - _get_publish_progress, _set_publish_progress - ) - publish_error_msg = property( - _get_publish_error_msg, _set_publish_error_msg - ) - def get_publish_report(self): return self._publish_report.get_report(self._publish_plugins) @@ -1754,11 +1786,7 @@ class PublisherController(AbstractPublisherController): return self._publish_validation_errors.create_report() def _reset_publish(self): - self.publish_is_running = False - self.publish_has_validated = False - self.publish_has_crashed = False - self.publish_has_validation_errors = False - self.publish_finished = False + self._reset_attributes() self._publish_up_validation = False self._publish_comment_is_set = False @@ -1780,10 +1808,7 @@ class PublisherController(AbstractPublisherController): self._publish_report.reset(self._publish_context, self._create_context) self._publish_validation_errors.reset(self._publish_plugins_proxy) - self.publish_error_msg = None - self.publish_max_progress = len(self._publish_plugins) - self.publish_progress = 0 self._emit_event("publish.reset.finished") From 16aff5224fd86552e84744dc6201d30c2e14863e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 10 Oct 2022 15:24:40 +0200 Subject: [PATCH 105/201] fix attribute changes --- openpype/tools/publisher/control.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index f2f6d07cd6..014efd5c01 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -1995,11 +1995,11 @@ class PublisherController(BasePublishController): # Cleanup of publishing process self.publish_finished = True - self.publish_progress = self._publish_max_progress + self.publish_progress = self.publish_max_progress yield MainThreadItem(self.stop_publish) def _add_validation_error(self, result): - self.publish_has_validation_errors = False + self.publish_has_validation_errors = True self._publish_validation_errors.add_error( result["plugin"], result["error"], @@ -2030,7 +2030,7 @@ class PublisherController(BasePublishController): " to your supervisor or OpenPype." ) self.publish_error_msg = msg - self.publish_has_crashed = False + self.publish_has_crashed = True self._publish_next_process() From f9155bd6429933e8df407f9673d603bae5e71e6f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 10 Oct 2022 15:28:45 +0200 Subject: [PATCH 106/201] implemented base controller --- openpype/tools/publisher/control.py | 181 ++++++++++++++++++---------- 1 file changed, 115 insertions(+), 66 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 014efd5c01..32a5d62fb5 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -891,10 +891,7 @@ class AbstractPublisherController(object): access objects directly but by using wrappers that can be serialized. """ - _log = None - _event_system = None - - @property + @abstractproperty def log(self): """Controller's logger object. @@ -902,54 +899,13 @@ class AbstractPublisherController(object): logging.Logger: Logger object that can be used for logging. """ - if self._log is None: - self._log = logging.getLogget(self.__class__.__name__) - return self._log + pass - @property + @abstractproperty def event_system(self): - """Inner event system for publisher controller. + """Inner event system for publisher controller.""" - Is used for communication with UI. Event system is autocreated. - - Known topics: - "show.detailed.help" - Detailed help requested (UI related). - "show.card.message" - Show card message request (UI related). - "instances.refresh.finished" - Instances are refreshed. - "plugins.refresh.finished" - Plugins refreshed. - "publish.reset.finished" - Publish context reset finished. - "controller.reset.finished" - Controller reset finished. - "publish.process.started" - Publishing started. Can be started from - paused state. - "publish.process.stopped" - Publishing stopped/paused process. - "publish.process.plugin.changed" - Plugin state has changed. - "publish.process.instance.changed" - Instance state has changed. - "publish.has_validated.changed" - Attr 'publish_has_validated' - changed. - "publish.is_running.changed" - Attr 'publish_is_running' changed. - "publish.has_validated.changed" - Attr 'has_validated' changed. - "publish.has_crashed.changed" - Attr 'publish_has_crashed' changed. - "publish.publish_error.changed" - Attr 'publish_error' - "publish.has_validation_errors.changed" - Attr - 'has_validation_errors' changed. - "publish.max_progress.changed" - Attr 'publish_max_progress' - changed. - "publish.progress.changed" - Attr 'publish_progress' changed. - "publish.host_is_valid.changed" - Attr 'host_is_valid' changed. - "publish.finished.changed" - Attr 'publish_finished' changed. - - Returns: - EventSystem: Event system which can trigger callbacks for topics. - """ - - if self._event_system is None: - self._event_system = EventSystem() - return self._event_system - - def _emit_event(self, topic, data=None): - if data is None: - data = {} - self.event_system.emit(topic, data, "controller") + pass @abstractproperty def project_name(self): @@ -1261,10 +1217,22 @@ class AbstractPublisherController(object): class BasePublishController(AbstractPublisherController): - def __init__(self): - # Controller must implement it's update - self._creator_items = {} + """Implement common logic for controllers. + Implement event system, logger and common attributes. Attributes are + triggering value changes so anyone can listen to their topics. + + Prepare implementation for creator items. Controller must implement just + their filling by '_collect_creator_items'. + + All prepared implementation is based on calling super '__init__'. + """ + + def __init__(self): + self._log = None + self._event_system = None + + # Host is valid for creation self._host_is_valid = False # Any other exception that happened during publishing @@ -1281,17 +1249,65 @@ class BasePublishController(AbstractPublisherController): self._publish_max_progress = 0 self._publish_progress = 0 + # Controller must '_collect_creator_items' to fill the value + self._creator_items = None + @property - def creator_items(self): - """Creators that can be shown in create dialog.""" + def log(self): + """Controller's logger object. - return self._creator_items + Returns: + logging.Logger: Logger object that can be used for logging. + """ - def get_creator_icon(self, identifier): - creator_item = self._creator_items.get(identifier) - if creator_item is not None: - return creator_item.icon - return None + if self._log is None: + self._log = logging.getLogget(self.__class__.__name__) + return self._log + + @property + def event_system(self): + """Inner event system for publisher controller. + + Is used for communication with UI. Event system is autocreated. + + Known topics: + "show.detailed.help" - Detailed help requested (UI related). + "show.card.message" - Show card message request (UI related). + "instances.refresh.finished" - Instances are refreshed. + "plugins.refresh.finished" - Plugins refreshed. + "publish.reset.finished" - Publish context reset finished. + "controller.reset.finished" - Controller reset finished. + "publish.process.started" - Publishing started. Can be started from + paused state. + "publish.process.stopped" - Publishing stopped/paused process. + "publish.process.plugin.changed" - Plugin state has changed. + "publish.process.instance.changed" - Instance state has changed. + "publish.has_validated.changed" - Attr 'publish_has_validated' + changed. + "publish.is_running.changed" - Attr 'publish_is_running' changed. + "publish.has_validated.changed" - Attr 'has_validated' changed. + "publish.has_crashed.changed" - Attr 'publish_has_crashed' changed. + "publish.publish_error.changed" - Attr 'publish_error' + "publish.has_validation_errors.changed" - Attr + 'has_validation_errors' changed. + "publish.max_progress.changed" - Attr 'publish_max_progress' + changed. + "publish.progress.changed" - Attr 'publish_progress' changed. + "publish.host_is_valid.changed" - Attr 'host_is_valid' changed. + "publish.finished.changed" - Attr 'publish_finished' changed. + + Returns: + EventSystem: Event system which can trigger callbacks for topics. + """ + + if self._event_system is None: + self._event_system = EventSystem() + return self._event_system + + def _emit_event(self, topic, data=None): + if data is None: + data = {} + self.event_system.emit(topic, data, "controller") def _get_host_is_valid(self): return self._host_is_valid @@ -1399,6 +1415,9 @@ class BasePublishController(AbstractPublisherController): def _reset_attributes(self): """Reset most of attributes that can be reset.""" + # Reset creator items + self._creator_items = None + self.publish_is_running = False self.publish_has_validated = False self.publish_has_crashed = False @@ -1408,6 +1427,35 @@ class BasePublishController(AbstractPublisherController): self.publish_error_msg = None self.publish_progress = 0 + @property + def creator_items(self): + """Creators that can be shown in create dialog.""" + if self._creator_items is None: + self._creator_items = self._collect_creator_items() + return self._creator_items + + @abstractmethod + def _collect_creator_items(self): + """Receive CreatorItems to work with. + + Returns: + Dict[str, CreatorItem]: Creator items by their identifier. + """ + + pass + + def get_creator_icon(self, identifier): + """Function to receive icon for creator identifier. + + Args: + str: Creator's identifier for which should be icon returned. + """ + + creator_item = self.creator_items.get(identifier) + if creator_item is not None: + return creator_item.icon + return None + class PublisherController(BasePublishController): """Middleware between UI, CreateContext and publish Context. @@ -1598,15 +1646,16 @@ class PublisherController(BasePublishController): self._create_context.reset_plugins() - self._creator_items = { - identifier: CreatorItem.from_creator(creator) - for identifier, creator in self._create_context.creators.items() - } - self._resetting_plugins = False self._emit_event("plugins.refresh.finished") + def _collect_creator_items(self): + return { + identifier: CreatorItem.from_creator(creator) + for identifier, creator in self._create_context.creators.items() + } + def _reset_instances(self): """Reset create instances.""" if self._resetting_instances: @@ -1638,7 +1687,7 @@ class PublisherController(BasePublishController): _attr_defs = {} for instance in instances: creator_identifier = instance.creator_identifier - creator_item = self._creator_items[creator_identifier] + creator_item = self.creator_items[creator_identifier] for attr_def in creator_item.instance_attributes_defs: found_idx = None for idx, _attr_def in _attr_defs.items(): From 2baa3a5b5449ece1972a4471ccad8396fd826af0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 10 Oct 2022 17:08:40 +0200 Subject: [PATCH 107/201] fix typo --- openpype/tools/publisher/widgets/validations_widget.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/publisher/widgets/validations_widget.py b/openpype/tools/publisher/widgets/validations_widget.py index 74be672f3b..06ac0bad8a 100644 --- a/openpype/tools/publisher/widgets/validations_widget.py +++ b/openpype/tools/publisher/widgets/validations_widget.py @@ -678,7 +678,7 @@ class ValidationsWidget(QtWidgets.QFrame): self._set_errors(validation_errors) return - if self._contoller.publish_has_finished: + if self._controller.publish_has_finished: self._set_current_widget(self._publish_stop_ok_widget) return From 4d40024bdbaf4fbee276e2957d0de534675bb3b2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 10 Oct 2022 18:35:32 +0200 Subject: [PATCH 108/201] added double click for asset dialog --- .../tools/publisher/widgets/assets_widget.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/openpype/tools/publisher/widgets/assets_widget.py b/openpype/tools/publisher/widgets/assets_widget.py index 39bf3886ea..996c9029d4 100644 --- a/openpype/tools/publisher/widgets/assets_widget.py +++ b/openpype/tools/publisher/widgets/assets_widget.py @@ -1,6 +1,7 @@ import collections from Qt import QtWidgets, QtCore, QtGui + from openpype.tools.utils import ( PlaceholderLineEdit, RecursiveSortFilterProxyModel, @@ -163,6 +164,16 @@ class AssetsHierarchyModel(QtGui.QStandardItemModel): return item_name in self._items_by_name +class AssetDialogView(QtWidgets.QTreeView): + double_clicked = QtCore.Signal(QtCore.QModelIndex) + + def mouseDoubleClickEvent(self, event): + index = self.indexAt(event.pos()) + if index.isValid(): + self.double_clicked.emit(index) + event.accept() + + class AssetsDialog(QtWidgets.QDialog): """Dialog to select asset for a context of instance.""" @@ -178,7 +189,7 @@ class AssetsDialog(QtWidgets.QDialog): filter_input = PlaceholderLineEdit(self) filter_input.setPlaceholderText("Filter assets..") - asset_view = QtWidgets.QTreeView(self) + asset_view = AssetDialogView(self) asset_view.setModel(proxy_model) asset_view.setHeaderHidden(True) asset_view.setFrameShape(QtWidgets.QFrame.NoFrame) @@ -200,6 +211,7 @@ class AssetsDialog(QtWidgets.QDialog): layout.addWidget(asset_view, 1) layout.addLayout(btns_layout, 0) + asset_view.double_clicked.connect(self._on_ok_clicked) filter_input.textChanged.connect(self._on_filter_change) ok_btn.clicked.connect(self._on_ok_clicked) cancel_btn.clicked.connect(self._on_cancel_clicked) @@ -274,7 +286,7 @@ class AssetsDialog(QtWidgets.QDialog): index = self._asset_view.currentIndex() asset_name = None if index.isValid(): - asset_name = index.data(QtCore.Qt.DisplayRole) + asset_name = index.data(ASSET_NAME_ROLE) self._selected_asset = asset_name self.done(1) From d46ca7ed50b314fa3ae61e106cfd8297b96c630e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 10 Oct 2022 19:23:29 +0200 Subject: [PATCH 109/201] cache assets hierarchy and stringify object ids --- openpype/tools/publisher/control.py | 46 +++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 43721b9229..c0ffa942a4 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -60,15 +60,17 @@ class AssetDocsCache: def __init__(self, controller): self._controller = controller self._asset_docs = None - # TODO use asset ids instead + self._asset_docs_hierarchy = None self._task_names_by_asset_name = {} self._asset_docs_by_name = {} self._full_asset_docs_by_name = {} def reset(self): self._asset_docs = None + self._asset_docs_hierarchy = None self._task_names_by_asset_name = {} self._asset_docs_by_name = {} + self._full_asset_docs_by_name = {} def _query(self): if self._asset_docs is not None: @@ -81,8 +83,13 @@ class AssetDocsCache: asset_docs_by_name = {} task_names_by_asset_name = {} for asset_doc in asset_docs: + if "data" not in asset_doc: + asset_doc["data"] = {"tasks": {}, "visualParent": None} + elif "tasks" not in asset_doc["data"]: + asset_doc["data"]["tasks"] = {} + asset_name = asset_doc["name"] - asset_tasks = asset_doc.get("data", {}).get("tasks") or {} + asset_tasks = asset_doc["data"]["tasks"] task_names_by_asset_name[asset_name] = list(asset_tasks.keys()) asset_docs_by_name[asset_name] = asset_doc @@ -94,11 +101,38 @@ class AssetDocsCache: self._query() return copy.deepcopy(self._asset_docs) + def get_asset_hierarchy(self): + """Prepare asset documents into hierarchy. + + Convert ObjectId to string. Asset id is not used during whole + process of publisher but asset name is used rather. + + Returns: + Dict[Union[str, None]: Any]: Mapping of parent id to it's children. + Top level assets have parent id 'None'. + """ + + if self._asset_docs_hierarchy is None: + _queue = collections.deque(self.get_asset_docs()) + + output = collections.defaultdict(list) + while _queue: + asset_doc = _queue.popleft() + asset_doc["_id"] = str(asset_doc["_id"]) + parent_id = asset_doc["data"]["visualParent"] + if parent_id is not None: + parent_id = str(parent_id) + asset_doc["data"]["visualParent"] = parent_id + output[parent_id].append(asset_doc) + self._asset_docs_hierarchy = output + return copy.deepcopy(self._asset_docs_hierarchy) + def get_task_names_by_asset_name(self): self._query() return copy.deepcopy(self._task_names_by_asset_name) def get_asset_by_name(self, asset_name): + self._query() asset_doc = self._asset_docs_by_name.get(asset_name) if asset_doc is None: return None @@ -1588,14 +1622,8 @@ class PublisherController(BasePublishController): def get_asset_hierarchy(self): """Prepare asset documents into hierarchy.""" - _queue = collections.deque(self.get_asset_docs()) - output = collections.defaultdict(list) - while _queue: - asset_doc = _queue.popleft() - parent_id = asset_doc["data"]["visualParent"] - output[parent_id].append(asset_doc) - return output + return self._asset_docs_cache.get_asset_hierarchy() def get_task_names_by_asset_names(self, asset_names): """Prepare task names by asset name.""" From 5f1bfe2790e1864fca60be2895caa54333a7ca09 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 10 Oct 2022 19:24:01 +0200 Subject: [PATCH 110/201] use 'get_subset_name' on controller instead of calling directly creator --- openpype/tools/publisher/widgets/widgets.py | 28 +++++---------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index 903ce70f01..c6c8ed3c7d 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -1060,24 +1060,6 @@ class GlobalAttrsWidget(QtWidgets.QWidget): if self.task_value_widget.has_value_changed(): task_name = self.task_value_widget.get_selected_items()[0] - asset_docs_by_name = {} - asset_names = set() - if asset_name is None: - for instance in self._current_instances: - asset_names.add(instance.get("asset")) - else: - asset_names.add(asset_name) - - for asset_doc in self._controller.get_asset_docs(): - _asset_name = asset_doc["name"] - if _asset_name in asset_names: - asset_names.remove(_asset_name) - asset_docs_by_name[_asset_name] = asset_doc - - if not asset_names: - break - - project_name = self._controller.project_name subset_names = set() invalid_tasks = False for instance in self._current_instances: @@ -1093,11 +1075,13 @@ class GlobalAttrsWidget(QtWidgets.QWidget): if task_name is not None: new_task_name = task_name - asset_doc = asset_docs_by_name[new_asset_name] - try: - new_subset_name = instance.creator.get_subset_name( - new_variant_value, new_task_name, asset_doc, project_name + new_subset_name = self._controller.get_subset_name( + instance.creator_identifier, + new_variant_value, + new_task_name, + new_asset_name, + instance.id ) except TaskNotSetError: invalid_tasks = True From 65c2638f3dd047d5168b256dd848a9a0c89c5ec4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 11 Oct 2022 09:43:18 +0200 Subject: [PATCH 111/201] Fusion: Implement backwards compatibility (pre Fusion 17.4) --- openpype/hosts/fusion/api/pipeline.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/fusion/api/pipeline.py b/openpype/hosts/fusion/api/pipeline.py index b22ee5328f..b6092f7c1b 100644 --- a/openpype/hosts/fusion/api/pipeline.py +++ b/openpype/hosts/fusion/api/pipeline.py @@ -44,11 +44,26 @@ INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory") class FusionLogHandler(logging.Handler): # Keep a reference to fusion's Print function (Remote Object) - _print = getattr(sys.modules["__main__"], "fusion").Print + _print = None + + @property + def print(self): + if self._print is not None: + # Use cached + return self._print + + _print = getattr(sys.modules["__main__"], "fusion").Print + if _print is None: + # Backwards compatibility: Print method on Fusion instance was + # added around Fusion 17.4 and wasn't available on PyRemote Object + # before + _print = get_current_comp().Print + self._print = _print + return _print def emit(self, record): entry = self.format(record) - self._print(entry) + self.print(entry) def install(): From 2e86d0329357a938d540808db54c3831357fed1c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 11 Oct 2022 10:55:09 +0200 Subject: [PATCH 112/201] fix import of PublisherWindow and add ability to pass controller --- openpype/tools/utils/host_tools.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/openpype/tools/utils/host_tools.py b/openpype/tools/utils/host_tools.py index 552ce0d432..eababfee32 100644 --- a/openpype/tools/utils/host_tools.py +++ b/openpype/tools/utils/host_tools.py @@ -269,25 +269,25 @@ class HostToolsHelper: dialog.activateWindow() dialog.showNormal() - def get_publisher_tool(self, parent): + def get_publisher_tool(self, parent=None, controller=None): """Create, cache and return publisher window.""" if self._publisher_tool is None: - from openpype.tools.publisher import PublisherWindow + from openpype.tools.publisher.window import PublisherWindow host = registered_host() ILoadHost.validate_load_methods(host) publisher_window = PublisherWindow( - parent=parent or self._parent + controller=controller, parent=parent or self._parent ) self._publisher_tool = publisher_window return self._publisher_tool - def show_publisher_tool(self, parent=None): + def show_publisher_tool(self, parent=None, controller=None): with qt_app_context(): - dialog = self.get_publisher_tool(parent) + dialog = self.get_publisher_tool(controller, parent) dialog.show() dialog.raise_() From 67f4112256d6ac3b6c5812f9a700e61e1d539c03 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 11 Oct 2022 14:06:51 +0200 Subject: [PATCH 113/201] removed duplicated topic from docstring --- openpype/tools/publisher/control.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index c0ffa942a4..11006dbc08 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -1320,7 +1320,6 @@ class BasePublishController(AbstractPublisherController): "publish.has_validated.changed" - Attr 'publish_has_validated' changed. "publish.is_running.changed" - Attr 'publish_is_running' changed. - "publish.has_validated.changed" - Attr 'has_validated' changed. "publish.has_crashed.changed" - Attr 'publish_has_crashed' changed. "publish.publish_error.changed" - Attr 'publish_error' "publish.has_validation_errors.changed" - Attr From e883f8743b179f8fadcc29b9ce9ffb25d6e43060 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 11 Oct 2022 14:13:39 +0200 Subject: [PATCH 114/201] renamed 'BasePublishController' to 'BasePublisherController' --- openpype/tools/publisher/control.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 11006dbc08..05b0bb39be 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -1251,7 +1251,7 @@ class AbstractPublisherController(object): pass -class BasePublishController(AbstractPublisherController): +class BasePublisherController(AbstractPublisherController): """Implement common logic for controllers. Implement event system, logger and common attributes. Attributes are @@ -1491,7 +1491,7 @@ class BasePublishController(AbstractPublisherController): return None -class PublisherController(BasePublishController): +class PublisherController(BasePublisherController): """Middleware between UI, CreateContext and publish Context. Handle both creation and publishing parts. From 75769804e974a4a33e83321b5e4eb0791d74281e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 11 Oct 2022 14:13:52 +0200 Subject: [PATCH 115/201] use 'BasePublisherController' for 'QtRemotePublishController' --- openpype/tools/publisher/control_qt.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openpype/tools/publisher/control_qt.py b/openpype/tools/publisher/control_qt.py index 10f576a3f3..006303ec6c 100644 --- a/openpype/tools/publisher/control_qt.py +++ b/openpype/tools/publisher/control_qt.py @@ -6,7 +6,11 @@ from Qt import QtCore from openpype.lib.events import Event from openpype.pipeline.create import CreatedInstance -from .control import MainThreadItem, PublisherController +from .control import ( + MainThreadItem, + PublisherController, + BasePublisherController, +) class MainThreadProcess(QtCore.QObject): @@ -92,7 +96,7 @@ class QtPublisherController(PublisherController): self._main_thread_processor.stop() -class QtRemotePublishController(PublisherController): +class QtRemotePublishController(BasePublisherController): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) From 18ebea7eb8825607cd13ea7d6adb23b6d486ba85 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 11 Oct 2022 14:14:03 +0200 Subject: [PATCH 116/201] handle 'host_is_valid' attribute change --- openpype/tools/publisher/control_qt.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/tools/publisher/control_qt.py b/openpype/tools/publisher/control_qt.py index 006303ec6c..51aeec65d1 100644 --- a/openpype/tools/publisher/control_qt.py +++ b/openpype/tools/publisher/control_qt.py @@ -167,6 +167,10 @@ class QtRemotePublishController(BasePublisherController): self.publish_finished = event["value"] return + if event.topic == "publish.host_is_valid.changed": + self.host_is_valid = event["value"] + return + # Topics that can be just passed by because are not affecting # controller itself # - "show.card.message" @@ -174,6 +178,7 @@ class QtRemotePublishController(BasePublisherController): # - "publish.reset.finished" # - "instances.refresh.finished" # - "plugins.refresh.finished" + # - "controller.reset.finished" # - "publish.process.started" # - "publish.process.stopped" # - "publish.process.plugin.changed" From c369b6e493f0401ae8fb7df10b31a7a739667136 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 12 Oct 2022 15:18:26 +0800 Subject: [PATCH 117/201] delete unneccessary schema and data of Create Rig --- .../defaults/project_settings/maya.json | 2 -- .../schemas/schema_maya_create.json | 34 +++---------------- 2 files changed, 4 insertions(+), 32 deletions(-) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index b4164c63f0..3e3a61fc8f 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -187,8 +187,6 @@ }, "CreateRig": { "enabled": true, - "write_color_sets": false, - "write_face_sets": false, "defaults": [ "Main", "Sim", diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json index 7e12897336..bc6520474d 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json @@ -170,36 +170,6 @@ } ] }, - { - "type": "dict", - "collapsible": true, - "key": "CreateRig", - "label": "Create Rig", - "checkbox_key": "enabled", - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, - { - "type": "boolean", - "key": "write_color_sets", - "label": "Write Color Sets" - }, - { - "type": "boolean", - "key": "write_face_sets", - "label": "Write Face Sets" - }, - { - "type": "list", - "key": "defaults", - "label": "Default Subsets", - "object_type": "text" - } - ] - }, { "type": "dict", "collapsible": true, @@ -275,6 +245,10 @@ "key": "CreateReview", "label": "Create Review" }, + { + "key": "CreateRig", + "label": "Create Rig" + }, { "key": "CreateSetDress", "label": "Create Set Dress" From e0a222c75ea49c0e82d24120600137ddf3b5f3c1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 12 Oct 2022 12:13:20 +0200 Subject: [PATCH 118/201] modified remote qt controller --- openpype/tools/publisher/control_qt.py | 59 +++++++++++++------------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/openpype/tools/publisher/control_qt.py b/openpype/tools/publisher/control_qt.py index 51aeec65d1..edcbb0c9f0 100644 --- a/openpype/tools/publisher/control_qt.py +++ b/openpype/tools/publisher/control_qt.py @@ -187,7 +187,7 @@ class QtRemotePublishController(BasePublisherController): @abstractproperty def project_name(self): - """Current context project name. + """Current context project name from client. Returns: str: Name of project. @@ -197,7 +197,7 @@ class QtRemotePublishController(BasePublisherController): @abstractproperty def current_asset_name(self): - """Current context asset name. + """Current context asset name from client. Returns: Union[str, None]: Name of asset. @@ -207,7 +207,7 @@ class QtRemotePublishController(BasePublisherController): @abstractproperty def current_task_name(self): - """Current context task name. + """Current context task name from client. Returns: Union[str, None]: Name of task. @@ -215,19 +215,6 @@ class QtRemotePublishController(BasePublisherController): pass - @abstractproperty - def host_is_valid(self): - """Host is valid for creation part. - - Host must have implemented certain functionality to be able create - in Publisher tool. - - Returns: - bool: Host can handle creation of instances. - """ - - pass - @property def instances(self): """Collected/created instances. @@ -260,16 +247,6 @@ class QtRemotePublishController(BasePublisherController): def get_existing_subset_names(self, asset_name): pass - @abstractmethod - def reset(self): - """Reset whole controller. - - This should reset create context, publish context and all variables - that are related to it. - """ - - pass - @abstractmethod def get_subset_name( self, @@ -311,17 +288,26 @@ class QtRemotePublishController(BasePublisherController): pass - @abstractmethod - def save_changes(self): - """Save changes happened during creation.""" + def _get_instance_changes_for_client(self): + """Preimplemented method to receive instance changes for client.""" created_instance_changes = {} for instance_id, instance in self._created_instances.items(): created_instance_changes[instance_id] = ( instance.remote_changes() ) + return created_instance_changes - # Send 'created_instance_changes' value to client + @abstractmethod + def _send_instance_changes_to_client(self): + instance_changes = self._get_instance_changes_for_client() + # Implement to send 'instance_changes' value to client + + @abstractmethod + def save_changes(self): + """Save changes happened during creation.""" + + self._send_instance_changes_to_client() @abstractmethod def remove_instances(self, instance_ids): @@ -338,16 +324,29 @@ class QtRemotePublishController(BasePublisherController): def get_validation_errors(self): pass + @abstractmethod + def reset(self): + """Reset whole controller. + + This should reset create context, publish context and all variables + that are related to it. + """ + + self._send_instance_changes_to_client() + pass + @abstractmethod def publish(self): """Trigger publishing without any order limitations.""" + self._send_instance_changes_to_client() pass @abstractmethod def validate(self): """Trigger publishing which will stop after validation order.""" + self._send_instance_changes_to_client() pass @abstractmethod From 8c3ffcc5675561b0322edd1e83eac0d184456124 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 12 Oct 2022 12:21:57 +0200 Subject: [PATCH 119/201] added a docstring to remote controller --- openpype/tools/publisher/control_qt.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/openpype/tools/publisher/control_qt.py b/openpype/tools/publisher/control_qt.py index edcbb0c9f0..ddc2dfa3e4 100644 --- a/openpype/tools/publisher/control_qt.py +++ b/openpype/tools/publisher/control_qt.py @@ -97,12 +97,24 @@ class QtPublisherController(PublisherController): class QtRemotePublishController(BasePublisherController): + """Abstract Remote controller for Qt UI. + + This controller should be used in process where UI is running and should + listen and ask for data on a client side. + + All objects that are used during UI processing should be able to convert + on client side to json serializable data and then recreated here. Keep in + mind that all changes made here should be send back to client controller + before critical actions. + + ATM Was not tested and will require some changes. All code written here is + based on theoretical idea how it could work. + """ + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._created_instances = {} - self._main_thread_processor = MainThreadProcess() - self._main_thread_processor.start() @abstractmethod def _get_serialized_instances(self): @@ -114,9 +126,6 @@ class QtRemotePublishController(BasePublisherController): pass - def _process_main_thread_item(self, item): - self._main_thread_processor.add_item(item) - def _on_create_instance_change(self): serialized_instances = self._get_serialized_instances() From 7c3e5be1dc0c703f2239de67c4641b94d93528bc Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 12 Oct 2022 13:31:33 +0200 Subject: [PATCH 120/201] OP-4180 - WIP - added new parent BaseAnatomy class Reason is to get info directly from project_doc, not from settings. --- openpype/pipeline/anatomy.py | 109 +++++++++++++++++++++++++---------- 1 file changed, 79 insertions(+), 30 deletions(-) diff --git a/openpype/pipeline/anatomy.py b/openpype/pipeline/anatomy.py index cb6e07154b..f60a23b421 100644 --- a/openpype/pipeline/anatomy.py +++ b/openpype/pipeline/anatomy.py @@ -6,8 +6,18 @@ import collections import numbers import six +import time -from openpype.settings.lib import get_anatomy_settings +from openpype.settings.lib import ( + # get_anatomy_settings, + get_project_settings, + get_default_project_settings, + get_local_settings, + create_settings_handler, + apply_local_settings_on_anatomy_settings +) + +from openpype.client import get_project from openpype.lib.path_templates import ( TemplateUnsolved, TemplateResult, @@ -39,34 +49,21 @@ class RootCombinationError(Exception): super(RootCombinationError, self).__init__(msg) -class Anatomy: +class BaseAnatomy(object): """Anatomy module helps to keep project settings. Wraps key project specifications, AnatomyTemplates and Roots. - - Args: - project_name (str): Project name to look on overrides. """ - root_key_regex = re.compile(r"{(root?[^}]+)}") root_name_regex = re.compile(r"root\[([^]]+)\]") - def __init__(self, project_name=None, site_name=None): - if not project_name: - project_name = os.environ.get("AVALON_PROJECT") - - if not project_name: - raise ProjectNotSet(( - "Implementation bug: Project name is not set. Anatomy requires" - " to load data for specific project." - )) - + def __init__(self, project_doc, local_settings): + project_name = project_doc["name"] self.project_name = project_name self._data = self._prepare_anatomy_data( - get_anatomy_settings(project_name, site_name) + project_doc, local_settings ) - self._site_name = site_name self._templates_obj = AnatomyTemplates(self) self._roots_obj = Roots(self) @@ -87,12 +84,15 @@ class Anatomy: def items(self): return copy.deepcopy(self._data).items() - @staticmethod - def _prepare_anatomy_data(anatomy_data): + def _prepare_anatomy_data(self, project_doc, local_settings): """Prepare anatomy data for further processing. Method added to replace `{task}` with `{task[name]}` in templates. """ + project_name = project_doc["name"] + handler = create_settings_handler() + anatomy_data = handler.project_doc_to_anatomy_data(project_doc) + templates_data = anatomy_data.get("templates") if templates_data: # Replace `{task}` with `{task[name]}` in templates @@ -103,20 +103,16 @@ class Anatomy: if not isinstance(item, dict): continue - for key in tuple(item.keys()): - value = item[key] - if isinstance(value, dict): - value_queue.append(value) + apply_local_settings_on_anatomy_settings(anatomy_data, + local_settings, project_name) - elif isinstance(value, six.string_types): - item[key] = value.replace("{task}", "{task[name]}") - return anatomy_data + self._data = anatomy_data def reset(self): """Reset values of cached data in templates and roots objects.""" - self._data = self._prepare_anatomy_data( - get_anatomy_settings(self.project_name, self._site_name) - ) + # self._data = self._prepare_anatomy_data( + # get_anatomy_settings(self.project_name, self._site_name) + # ) self.templates_obj.reset() self.roots_obj.reset() @@ -339,6 +335,59 @@ class Anatomy: return rootless_path.format(**data) +class Anatomy(BaseAnatomy): + _project_cache = {} + + def __init__(self, project_name=None, site_name=None): + if not project_name: + project_name = os.environ.get("AVALON_PROJECT") + + if not project_name: + raise ProjectNotSet(( + "Implementation bug: Project name is not set. Anatomy requires" + " to load data for specific project." + )) + + self._site_name = site_name + project_info = self.get_project_data_and_cache(project_name, site_name) + + super(Anatomy, self).__init__( + project_info["project_doc"], + project_info["local_settings"] + ) + + @classmethod + def get_project_data_and_cache(cls, project_name, site_name): + project_info = cls._project_cache.get(project_name) + if project_info is not None: + if time.time() - project_info["start"] > 10: + cls._project_cache.pop(project_name) + project_info = None + + if project_info is None: + if site_name is None: + if project_name: + project_settings = get_project_settings(project_name) + else: + project_settings = get_default_project_settings() + site_name = ( + project_settings["global"] + ["sync_server"] + ["config"] + ["active_site"] + ) + + project_info = { + "project_doc": get_project(project_name), + "local_settings": get_local_settings(), + "site_name": site_name, + "start": time.time() + } + cls._project_cache[project_name] = project_info + + return project_info + + class AnatomyTemplateUnsolved(TemplateUnsolved): """Exception for unsolved template when strict is set to True.""" From df4f3d45aa6c48ee209845b2a35a773b189455e0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 12 Oct 2022 14:44:54 +0200 Subject: [PATCH 121/201] fix instances access in 'get_subset_name' --- openpype/tools/publisher/control.py | 5 ++++- openpype/tools/publisher/widgets/widgets.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 05b0bb39be..699b8843cc 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -1820,9 +1820,12 @@ class PublisherController(BasePublisherController): creator = self._creators[creator_identifier] project_name = self.project_name asset_doc = self._asset_docs_cache.get_full_asset_by_name(asset_name) + instance = None + if instance_id: + instance = self.instances[instance_id] return creator.get_subset_name( - variant, task_name, asset_doc, project_name + variant, task_name, asset_doc, project_name, instance=instance ) def create( diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index edd9d55c75..536650e209 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -1082,8 +1082,8 @@ class GlobalAttrsWidget(QtWidgets.QWidget): new_task_name, new_asset_name, instance.id, - instance=instance ) + except TaskNotSetError: invalid_tasks = True instance.set_task_invalid(True) From 64f9d98c53f746a180e54e3022abfe734cd78f97 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 12 Oct 2022 14:46:11 +0200 Subject: [PATCH 122/201] hound fix --- openpype/tools/publisher/control.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 699b8843cc..da320b1f39 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -652,18 +652,22 @@ class PublishValidationErrorsReport: Dict[str, Any]: Serialized data. """ + error_items = [ + item.to_data() + for item in self._error_items + ] + + plugin_action_items = { + plugin_id: [ + action_item.to_data() + for action_item in action_items + ] + for plugin_id, action_items in self._plugin_action_items.items() + } + return { - "error_items": [ - item.to_data() - for item in self._error_items - ], - "plugin_action_items": { - plugin_id: [ - action_item.to_data() - for action_item in action_items - ] - for plugin_id, action_items in self._plugin_action_items.items() - } + "error_items": error_items, + "plugin_action_items": plugin_action_items } @classmethod From 8af0a486251b129f92addab10cc80fe0da4f318d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 13 Oct 2022 10:40:05 +0200 Subject: [PATCH 123/201] removed unused openpype.api imports --- .../blender/plugins/publish/validate_camera_zero_keyframe.py | 2 +- .../hosts/blender/plugins/publish/validate_no_colons_in_name.py | 2 +- .../hosts/blender/plugins/publish/validate_transform_zero.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py b/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py index 9ac0561ff3..84b9dd1a6e 100644 --- a/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py +++ b/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py @@ -3,7 +3,7 @@ from typing import List import bpy import pyblish.api -import openpype.api + import openpype.hosts.blender.api.action from openpype.pipeline.publish import ValidateContentsOrder diff --git a/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py b/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py index 3d7c5294f6..f5dc9fdd5c 100644 --- a/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py +++ b/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py @@ -3,7 +3,7 @@ from typing import List import bpy import pyblish.api -import openpype.api + import openpype.hosts.blender.api.action from openpype.pipeline.publish import ValidateContentsOrder diff --git a/openpype/hosts/blender/plugins/publish/validate_transform_zero.py b/openpype/hosts/blender/plugins/publish/validate_transform_zero.py index 249b14743b..742826d3d9 100644 --- a/openpype/hosts/blender/plugins/publish/validate_transform_zero.py +++ b/openpype/hosts/blender/plugins/publish/validate_transform_zero.py @@ -4,7 +4,7 @@ import mathutils import bpy import pyblish.api -import openpype.api + import openpype.hosts.blender.api.action from openpype.pipeline.publish import ValidateContentsOrder From 94baf5a746cf8e30a57b0a37b1e27f5eb12b22cc Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 13 Oct 2022 10:40:19 +0200 Subject: [PATCH 124/201] change import of 'ValidateContentsOrder' --- .../hosts/blender/plugins/publish/validate_mesh_has_uv.py | 5 +++-- .../plugins/publish/validate_mesh_no_negative_scale.py | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py b/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py index 83146c641e..cee855671d 100644 --- a/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py +++ b/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py @@ -3,14 +3,15 @@ from typing import List import bpy import pyblish.api -import openpype.api + +from openpype.pipeline.publish import ValidateContentsOrder import openpype.hosts.blender.api.action class ValidateMeshHasUvs(pyblish.api.InstancePlugin): """Validate that the current mesh has UV's.""" - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder hosts = ["blender"] families = ["model"] category = "geometry" diff --git a/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py b/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py index 329a8d80c3..45ac08811d 100644 --- a/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py +++ b/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py @@ -3,14 +3,15 @@ from typing import List import bpy import pyblish.api -import openpype.api + +from openpype.pipeline.publish import ValidateContentsOrder import openpype.hosts.blender.api.action class ValidateMeshNoNegativeScale(pyblish.api.Validator): """Ensure that meshes don't have a negative scale.""" - order = openpype.api.ValidateContentsOrder + order = ValidateContentsOrder hosts = ["blender"] families = ["model"] category = "geometry" From 39747041e8a417c0eaeadbc6ded439cc2eef245b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 13 Oct 2022 11:13:15 +0200 Subject: [PATCH 125/201] fix import of 'get_subset_name' in legacy creator --- openpype/pipeline/create/legacy_create.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/create/legacy_create.py b/openpype/pipeline/create/legacy_create.py index 2764b3cb95..82e5de7a8f 100644 --- a/openpype/pipeline/create/legacy_create.py +++ b/openpype/pipeline/create/legacy_create.py @@ -9,7 +9,9 @@ import os import logging import collections -from openpype.lib import get_subset_name +from openpype.client import get_asset_by_id + +from .subset_name import get_subset_name class LegacyCreator(object): @@ -147,11 +149,15 @@ class LegacyCreator(object): variant, task_name, asset_id, project_name, host_name ) + asset_doc = get_asset_by_id( + project_name, asset_id, fields=["data.tasks"] + ) + return get_subset_name( cls.family, variant, task_name, - asset_id, + asset_doc, project_name, host_name, dynamic_data=dynamic_data From 71f48425bff3ce0fe51620c8c3ac138672633f99 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 13 Oct 2022 11:29:57 +0100 Subject: [PATCH 126/201] Fix format string for Python 2 --- openpype/hosts/maya/plugins/publish/extract_layout.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_layout.py b/openpype/hosts/maya/plugins/publish/extract_layout.py index 92ca6c883f..48edbe547a 100644 --- a/openpype/hosts/maya/plugins/publish/extract_layout.py +++ b/openpype/hosts/maya/plugins/publish/extract_layout.py @@ -34,14 +34,15 @@ class ExtractLayout(openpype.api.Extractor): for asset in cmds.sets(str(instance), query=True): # Find the container grp_name = asset.split(':')[0] - containers = cmds.ls(f"{grp_name}*_CON") + containers = cmds.ls("{}*_CON".format(grp_name)) assert len(containers) == 1, \ - f"More than one container found for {asset}" + "More than one container found for {}".format(asset) container = containers[0] - representation_id = cmds.getAttr(f"{container}.representation") + representation_id = cmds.getAttr( + "{}.representation".format(container)) representation = legacy_io.find_one( { @@ -56,7 +57,8 @@ class ExtractLayout(openpype.api.Extractor): json_element = { "family": family, - "instance_name": cmds.getAttr(f"{container}.namespace"), + "instance_name": cmds.getAttr( + "{}.namespace".format(container)), "representation": str(representation_id), "version": str(version_id) } From 6f9ab9bfb956600b775747ba39f58421d28a46a9 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 13 Oct 2022 13:34:01 +0200 Subject: [PATCH 127/201] OP-4180 - updated Anatomy Cache split into project and site. Internalized 2 methods from to be abandoned classes. --- openpype/pipeline/anatomy.py | 223 ++++++++++++++++++++++++++++------- 1 file changed, 178 insertions(+), 45 deletions(-) diff --git a/openpype/pipeline/anatomy.py b/openpype/pipeline/anatomy.py index f60a23b421..0c6ce454d7 100644 --- a/openpype/pipeline/anatomy.py +++ b/openpype/pipeline/anatomy.py @@ -9,13 +9,13 @@ import six import time from openpype.settings.lib import ( - # get_anatomy_settings, get_project_settings, - get_default_project_settings, get_local_settings, - create_settings_handler, - apply_local_settings_on_anatomy_settings ) +from openpype.settings.constants import ( + DEFAULT_PROJECT_KEY +) +from openpype.settings import ProjectSettings from openpype.client import get_project from openpype.lib.path_templates import ( @@ -57,12 +57,12 @@ class BaseAnatomy(object): root_key_regex = re.compile(r"{(root?[^}]+)}") root_name_regex = re.compile(r"root\[([^]]+)\]") - def __init__(self, project_doc, local_settings): + def __init__(self, project_doc, local_settings, site_name): project_name = project_doc["name"] self.project_name = project_name self._data = self._prepare_anatomy_data( - project_doc, local_settings + project_doc, local_settings, site_name ) self._templates_obj = AnatomyTemplates(self) self._roots_obj = Roots(self) @@ -84,14 +84,13 @@ class BaseAnatomy(object): def items(self): return copy.deepcopy(self._data).items() - def _prepare_anatomy_data(self, project_doc, local_settings): + def _prepare_anatomy_data(self, project_doc, local_settings, site_name): """Prepare anatomy data for further processing. Method added to replace `{task}` with `{task[name]}` in templates. """ project_name = project_doc["name"] - handler = create_settings_handler() - anatomy_data = handler.project_doc_to_anatomy_data(project_doc) + anatomy_data = self._project_doc_to_anatomy_data(project_doc) templates_data = anatomy_data.get("templates") if templates_data: @@ -103,19 +102,13 @@ class BaseAnatomy(object): if not isinstance(item, dict): continue - apply_local_settings_on_anatomy_settings(anatomy_data, - local_settings, project_name) + self._apply_local_settings_on_anatomy_data(anatomy_data, + local_settings, + project_name, + site_name) self._data = anatomy_data - def reset(self): - """Reset values of cached data in templates and roots objects.""" - # self._data = self._prepare_anatomy_data( - # get_anatomy_settings(self.project_name, self._site_name) - # ) - self.templates_obj.reset() - self.roots_obj.reset() - @property def templates(self): """Wrap property `templates` of Anatomy's AnatomyTemplates instance.""" @@ -334,9 +327,118 @@ class BaseAnatomy(object): data = self.root_environmets_fill_data(template) return rootless_path.format(**data) + def _project_doc_to_anatomy_data(self, project_doc): + """Convert project document to anatomy data. + + Probably should fill missing keys and values. + """ + if not project_doc: + return {} + + project_settings_root = ProjectSettings( + project_doc["name"], reset=False, change_state=False + ) + anatomy_entity = project_settings_root["project_anatomy"] + anatomy_keys = set(anatomy_entity.keys()) + anatomy_keys.remove("attributes") + attribute_keys = set(anatomy_entity["attributes"].keys()) + + attributes = {} + project_doc_data = project_doc.get("data") or {} + for key in attribute_keys: + value = project_doc_data.get(key) + if value is not None: + attributes[key] = value + + project_doc_config = project_doc.get("config") or {} + + app_names = set() + if not project_doc_config or "apps" not in project_doc_config: + set_applications = False + else: + set_applications = True + for app_item in project_doc_config["apps"]: + if not app_item: + continue + app_name = app_item.get("name") + if app_name: + app_names.add(app_name) + + if set_applications: + attributes["applications"] = list(app_names) + + output = {"attributes": attributes} + for key in anatomy_keys: + value = project_doc_config.get(key) + if value is not None: + output[key] = value + + return output + + def _apply_local_settings_on_anatomy_data( + self, anatomy_data, local_settings, project_name, site_name + ): + """Apply local settings on anatomy data. + + ATM local settings can modify project roots. Project name is required + as local settings have data stored data by project's name. + + Local settings override root values in this order: + 1.) Check if local settings contain overrides for default project and + apply it's values on roots if there are any. + 2.) If passed `project_name` is not None then check project specific + overrides in local settings for the project and apply it's value on + roots if there are any. + + NOTE: Root values of default project from local settings are always + applied if are set. + + Args: + anatomy_data (dict): Data for anatomy. + local_settings (dict): Data of local settings. + project_name (str): Name of project for which anatomy data are. + """ + if not local_settings: + return + + local_project_settings = local_settings.get("projects") or {} + + # Check for roots existence in local settings first + roots_project_locals = ( + local_project_settings + .get(project_name, {}) + ) + roots_default_locals = ( + local_project_settings + .get(DEFAULT_PROJECT_KEY, {}) + ) + + # Skip rest of processing if roots are not set + if not roots_project_locals and not roots_default_locals: + return + + # Combine roots from local settings + roots_locals = roots_default_locals.get(site_name) or {} + roots_locals.update(roots_project_locals.get(site_name) or {}) + # Skip processing if roots for current active site are not available in + # local settings + if not roots_locals: + return + + current_platform = platform.system().lower() + + root_data = anatomy_data["roots"] + for root_name, path in roots_locals.items(): + if root_name not in root_data: + continue + anatomy_data["roots"][root_name][current_platform] = ( + path + ) + class Anatomy(BaseAnatomy): _project_cache = {} + _site_cache = {} def __init__(self, project_name=None, site_name=None): if not project_name: @@ -349,43 +451,74 @@ class Anatomy(BaseAnatomy): )) self._site_name = site_name - project_info = self.get_project_data_and_cache(project_name, site_name) + project_doc = self.get_project_doc_from_cache(project_name) + local_settings = get_local_settings() + if not site_name: + site_name = self.get_site_name_from_cache( + project_name, local_settings + ) super(Anatomy, self).__init__( - project_info["project_doc"], - project_info["local_settings"] + project_doc, + local_settings, + site_name ) @classmethod - def get_project_data_and_cache(cls, project_name, site_name): - project_info = cls._project_cache.get(project_name) - if project_info is not None: - if time.time() - project_info["start"] > 10: + def get_project_doc_from_cache(cls, project_name): + project_cache = cls._project_cache.get(project_name) + if project_cache is not None: + if time.time() - project_cache["start"] > 10: cls._project_cache.pop(project_name) - project_info = None + project_cache = None - if project_info is None: - if site_name is None: - if project_name: - project_settings = get_project_settings(project_name) - else: - project_settings = get_default_project_settings() - site_name = ( - project_settings["global"] - ["sync_server"] - ["config"] - ["active_site"] - ) - - project_info = { + if project_cache is None: + project_cache = { "project_doc": get_project(project_name), - "local_settings": get_local_settings(), - "site_name": site_name, "start": time.time() } - cls._project_cache[project_name] = project_info + cls._project_cache[project_name] = project_cache - return project_info + return copy.deepcopy( + cls._project_cache[project_name]["project_doc"] + ) + + @classmethod + def get_site_name_from_cache(cls, project_name, local_settings): + site_cache = cls._site_cache.get(project_name) + if site_cache is not None: + if time.time() - site_cache["start"] > 10: + cls._site_cache.pop(project_name) + site_cache = None + + if site_cache: + return site_cache["site_name"] + + local_project_settings = local_settings.get("projects") + if not local_project_settings: + return + + project_locals = local_project_settings.get(project_name) or {} + default_locals = local_project_settings.get(DEFAULT_PROJECT_KEY) or {} + active_site = ( + project_locals.get("active_site") + or default_locals.get("active_site") + ) + if not active_site: + project_settings = get_project_settings(project_name) + active_site = ( + project_settings + ["global"] + ["sync_server"] + ["config"] + ["active_site"] + ) + + cls._site_cache[project_name] = { + "site_name": active_site, + "start": time.time() + } + return active_site class AnatomyTemplateUnsolved(TemplateUnsolved): From 510326b92c1d37838b8463c2c6bfe7ac66d3bef6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 13 Oct 2022 15:21:43 +0200 Subject: [PATCH 128/201] hack py2 ftrack api to store schema to user's directory instead of temp dir --- .../ftrack-python-api/source/ftrack_api/session.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/openpype/modules/ftrack/python2_vendor/ftrack-python-api/source/ftrack_api/session.py b/openpype/modules/ftrack/python2_vendor/ftrack-python-api/source/ftrack_api/session.py index 1a5da44432..78f9d135b7 100644 --- a/openpype/modules/ftrack/python2_vendor/ftrack-python-api/source/ftrack_api/session.py +++ b/openpype/modules/ftrack/python2_vendor/ftrack-python-api/source/ftrack_api/session.py @@ -13,10 +13,9 @@ import functools import itertools import distutils.version import hashlib -import tempfile +import appdirs import threading import atexit -import warnings import requests import requests.auth @@ -241,7 +240,7 @@ class Session(object): ) self._auto_connect_event_hub_thread = None - if auto_connect_event_hub in (None, True): + if auto_connect_event_hub is True: # Connect to event hub in background thread so as not to block main # session usage waiting for event hub connection. self._auto_connect_event_hub_thread = threading.Thread( @@ -252,9 +251,7 @@ class Session(object): # To help with migration from auto_connect_event_hub default changing # from True to False. - self._event_hub._deprecation_warning_auto_connect = ( - auto_connect_event_hub is None - ) + self._event_hub._deprecation_warning_auto_connect = False # Register to auto-close session on exit. atexit.register(WeakMethod(self.close)) @@ -271,8 +268,9 @@ class Session(object): # rebuilding types)? if schema_cache_path is not False: if schema_cache_path is None: + schema_cache_path = appdirs.user_cache_dir() schema_cache_path = os.environ.get( - 'FTRACK_API_SCHEMA_CACHE_PATH', tempfile.gettempdir() + 'FTRACK_API_SCHEMA_CACHE_PATH', schema_cache_path ) schema_cache_path = os.path.join( From 543d22ffc76eed10874f8c9cc8d516b369aed0b3 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 13 Oct 2022 16:25:29 +0200 Subject: [PATCH 129/201] :bug: fix token for non-multipart outputs and unify variable names --- openpype/hosts/maya/api/lib_renderproducts.py | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/maya/api/lib_renderproducts.py b/openpype/hosts/maya/api/lib_renderproducts.py index 1ab771cfe6..cd204445b7 100644 --- a/openpype/hosts/maya/api/lib_renderproducts.py +++ b/openpype/hosts/maya/api/lib_renderproducts.py @@ -260,20 +260,20 @@ class ARenderProducts: """ try: - file_prefix_attr = IMAGE_PREFIXES[self.renderer] + prefix_attr = IMAGE_PREFIXES[self.renderer] except KeyError: raise UnsupportedRendererException( "Unsupported renderer {}".format(self.renderer) ) - file_prefix = self._get_attr(file_prefix_attr) + prefix = self._get_attr(prefix_attr) - if not file_prefix: + if not prefix: # Fall back to scene name by default log.debug("Image prefix not set, using ") file_prefix = "" - return file_prefix + return prefix def get_render_attribute(self, attribute): """Get attribute from render options. @@ -730,13 +730,16 @@ class RenderProductsVray(ARenderProducts): """Get image prefix for V-Ray. This overrides :func:`ARenderProducts.get_renderer_prefix()` as - we must add `` token manually. + we must add `` token manually. This is done only for + non-multipart outputs, where `` token doesn't make sense. See also: :func:`ARenderProducts.get_renderer_prefix()` """ prefix = super(RenderProductsVray, self).get_renderer_prefix() + if self.multipart: + return prefix aov_separator = self._get_aov_separator() prefix = "{}{}".format(prefix, aov_separator) return prefix @@ -974,15 +977,18 @@ class RenderProductsRedshift(ARenderProducts): """Get image prefix for Redshift. This overrides :func:`ARenderProducts.get_renderer_prefix()` as - we must add `` token manually. + we must add `` token manually. This is done only for + non-multipart outputs, where `` token doesn't make sense. See also: :func:`ARenderProducts.get_renderer_prefix()` """ - file_prefix = super(RenderProductsRedshift, self).get_renderer_prefix() - separator = self.extract_separator(file_prefix) - prefix = "{}{}".format(file_prefix, separator or "_") + prefix = super(RenderProductsRedshift, self).get_renderer_prefix() + if self.multipart: + return prefix + separator = self.extract_separator(prefix) + prefix = "{}{}".format(prefix, separator or "_") return prefix def get_render_products(self): From 91d3056534bc30190d5d5f31977afd0724635016 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Albert?= <89812691+sebasti1a@users.noreply.github.com> Date: Thu, 13 Oct 2022 16:48:29 +0200 Subject: [PATCH 130/201] Update openpype/modules/kitsu/actions/launcher_show_in_kitsu.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/modules/kitsu/actions/launcher_show_in_kitsu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/kitsu/actions/launcher_show_in_kitsu.py b/openpype/modules/kitsu/actions/launcher_show_in_kitsu.py index ab523876ed..c95079e042 100644 --- a/openpype/modules/kitsu/actions/launcher_show_in_kitsu.py +++ b/openpype/modules/kitsu/actions/launcher_show_in_kitsu.py @@ -37,7 +37,7 @@ class ShowInKitsu(LauncherAction): project_zou_id = project["data"].get("zou_id") if not project_zou_id: raise RuntimeError(f"Project {project_name} has no " - f"connected ftrack id.") + f"connected kitsu id.") asset_zou_name = None asset_zou_id = None From 088e442214ce82155c39713485af1e1fbf2ad0b4 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 13 Oct 2022 18:35:15 +0200 Subject: [PATCH 131/201] OP-4180 - fix missed return --- openpype/pipeline/anatomy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/anatomy.py b/openpype/pipeline/anatomy.py index 0c6ce454d7..437a03f898 100644 --- a/openpype/pipeline/anatomy.py +++ b/openpype/pipeline/anatomy.py @@ -107,7 +107,7 @@ class BaseAnatomy(object): project_name, site_name) - self._data = anatomy_data + return anatomy_data @property def templates(self): From 840792a82c2c443dee77b4e8ec02ce6ec72b1e70 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 13 Oct 2022 19:14:48 +0200 Subject: [PATCH 132/201] added mechanism to define custom paths to ffmpeg and oiio tools and more detailed validation of them --- openpype/lib/vendor_bin_utils.py | 220 +++++++++++++++++++++++++++++-- 1 file changed, 212 insertions(+), 8 deletions(-) diff --git a/openpype/lib/vendor_bin_utils.py b/openpype/lib/vendor_bin_utils.py index e5ab2872a0..31245d4ee4 100644 --- a/openpype/lib/vendor_bin_utils.py +++ b/openpype/lib/vendor_bin_utils.py @@ -1,10 +1,33 @@ import os import logging import platform +import subprocess log = logging.getLogger("Vendor utils") +class CachedToolPaths: + """Cache already used and discovered tools and their executables. + + Discovering path can take some time and can trigger subprocesses so it's + better to cache the paths on first get. + """ + + _cached_paths = {} + + @classmethod + def is_tool_cached(cls, tool): + return tool in cls._cached_paths + + @classmethod + def get_executable_path(cls, tool): + return cls._cached_paths.get(tool) + + @classmethod + def cache_executable_path(cls, tool, path): + cls._cached_paths[tool] = path + + def is_file_executable(filepath): """Filepath lead to executable file. @@ -98,6 +121,7 @@ def get_vendor_bin_path(bin_app): Returns: str: Path to vendorized binaries folder. """ + return os.path.join( os.environ["OPENPYPE_ROOT"], "vendor", @@ -107,6 +131,112 @@ def get_vendor_bin_path(bin_app): ) +def find_tool_in_custom_paths(paths, tool, validation_func=None): + """Find a tool executable in custom paths. + + Args: + paths (Iterable[str]): Iterable of paths where to look for tool. + tool (str): Name of tool (binary file) to find in passed paths. + validation_func (Function): Custom validation function of path. + Function must expect one argument which is path to executable. + If not passed only 'find_executable' is used to be able identify + if path is valid. + + Reuturns: + Union[str, None]: Path to validated executable or None if was not + found. + """ + + for path in paths: + # Skip empty strings + if not path: + continue + + # Handle cases when path is just an executable + # - it allows to use executable from PATH + # - basename must match 'tool' value (without extension) + extless_path, ext = os.path.splitext(path) + if extless_path == tool: + executable_path = find_executable(tool) + if executable_path and ( + validation_func is None + or validation_func(executable_path) + ): + return executable_path + continue + + # Normalize path because it should be a path and check if exists + normalized = os.path.normpath(path) + if not os.path.exists(normalized): + continue + + # Note: Path can be both file and directory + + # If path is a file validate it + if os.path.isfile(normalized): + basename, ext = os.path.splitext(os.path.basename(path)) + # Check if the filename has actually the sane bane as 'tool' + if basename == tool: + executable_path = find_executable(normalized) + if executable_path and ( + validation_func is None + or validation_func(executable_path) + ): + return executable_path + + # Check if path is a directory and look for tool inside the dir + if os.path.isdir(normalized): + executable_path = find_executable(os.path.join(normalized, tool)) + if executable_path and ( + validation_func is None + or validation_func(executable_path) + ): + return executable_path + return None + + +def _oiio_executable_validation(filepath): + """Validate oiio tool executable if can be executed. + + Validation has 2 steps. First is using 'find_executable' to fill possible + missing extension or fill directory then launch executable and validate + that it can be executed. For that is used '--help' argument which is fast + and does not need any other inputs. + + Any possible crash of missing libraries or invalid build should be catched. + + Main reason is to validate if executable can be executed on OS just running + which can be issue ob linux machines. + + Note: + It does not validate if the executable is really a oiio tool which + should be used. + + Args: + filepath (str): Path to executable. + + Returns: + bool: Filepath is valid executable. + """ + + filepath = find_executable(filepath) + if not filepath: + return False + + try: + proc = subprocess.Popen( + [filepath, "--help"], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + proc.wait() + return proc.returncode == 0 + + except Exception: + pass + return False + + def get_oiio_tools_path(tool="oiiotool"): """Path to vendorized OpenImageIO tool executables. @@ -117,10 +247,67 @@ def get_oiio_tools_path(tool="oiiotool"): Default is "oiiotool". """ - oiio_dir = get_vendor_bin_path("oiio") - if platform.system().lower() == "linux": - oiio_dir = os.path.join(oiio_dir, "bin") - return find_executable(os.path.join(oiio_dir, tool)) + if CachedToolPaths.is_tool_cached(tool): + return CachedToolPaths.get_executable_path(tool) + + custom_paths_str = os.environ.get("OPENPYPE_CUSTOM_OIIO_PATHS") or "" + tool_executable_path = find_tool_in_custom_paths( + custom_paths_str.split(os.pathsep), + tool, + _oiio_executable_validation + ) + + if not tool_executable_path: + oiio_dir = get_vendor_bin_path("oiio") + if platform.system().lower() == "linux": + oiio_dir = os.path.join(oiio_dir, "bin") + default_path = os.path.join(oiio_dir, tool) + if _oiio_executable_validation(default_path): + tool_executable_path = default_path + + CachedToolPaths.cache_executable_path(tool, tool_executable_path) + return tool_executable_path + + +def _ffmpeg_executable_validation(filepath): + """Validate ffmpeg tool executable if can be executed. + + Validation has 2 steps. First is using 'find_executable' to fill possible + missing extension or fill directory then launch executable and validate + that it can be executed. For that is used '-version' argument which is fast + and does not need any other inputs. + + Any possible crash of missing libraries or invalid build should be catched. + + Main reason is to validate if executable can be executed on OS just running + which can be issue ob linux machines. + + Note: + It does not validate if the executable is really a ffmpeg tool. + + Args: + filepath (str): Path to executable. + + Returns: + bool: Filepath is valid executable. + """ + + filepath = find_executable(filepath) + if not filepath: + return False + + try: + proc = subprocess.Popen( + [filepath, "-version"], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + proc.wait() + return proc.returncode == 0 + + except Exception: + pass + return False def get_ffmpeg_tool_path(tool="ffmpeg"): @@ -133,10 +320,27 @@ def get_ffmpeg_tool_path(tool="ffmpeg"): Returns: str: Full path to ffmpeg executable. """ - ffmpeg_dir = get_vendor_bin_path("ffmpeg") - if platform.system().lower() == "windows": - ffmpeg_dir = os.path.join(ffmpeg_dir, "bin") - return find_executable(os.path.join(ffmpeg_dir, tool)) + + if CachedToolPaths.is_tool_cached(tool): + return CachedToolPaths.get_executable_path(tool) + + custom_paths_str = os.environ.get("OPENPYPE_CUSTOM_FFMPEG_PATHS") or "" + tool_executable_path = find_tool_in_custom_paths( + custom_paths_str.split(os.pathsep), + tool, + _ffmpeg_executable_validation + ) + + if not tool_executable_path: + ffmpeg_dir = get_vendor_bin_path("ffmpeg") + if platform.system().lower() == "windows": + ffmpeg_dir = os.path.join(ffmpeg_dir, "bin") + tool_path = find_executable(os.path.join(ffmpeg_dir, tool)) + if tool_path and _ffmpeg_executable_validation(tool_path): + tool_executable_path = tool_path + + CachedToolPaths.cache_executable_path(tool, tool_executable_path) + return tool_executable_path def is_oiio_supported(): From 8a28b714a3f67552e14853576d3fa6db0f6ecc4f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 Oct 2022 20:14:57 +0200 Subject: [PATCH 133/201] fix usage of functions from lib --- openpype/hosts/resolve/api/plugin.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/resolve/api/plugin.py b/openpype/hosts/resolve/api/plugin.py index 3995077d21..0ed7beee59 100644 --- a/openpype/hosts/resolve/api/plugin.py +++ b/openpype/hosts/resolve/api/plugin.py @@ -4,13 +4,15 @@ import uuid import qargparse from Qt import QtWidgets, QtCore +from openpype.settings import get_current_project_settings +from openpype.pipeline.context_tools import get_current_project_asset from openpype.pipeline import ( LegacyCreator, LoaderPlugin, ) -from openpype.pipeline.context_tools import get_current_project_asset -from openpype.hosts import resolve + from . import lib +from .menu import load_stylesheet class CreatorWidget(QtWidgets.QDialog): @@ -86,7 +88,7 @@ class CreatorWidget(QtWidgets.QDialog): ok_btn.clicked.connect(self._on_ok_clicked) cancel_btn.clicked.connect(self._on_cancel_clicked) - stylesheet = resolve.api.menu.load_stylesheet() + stylesheet = load_stylesheet() self.setStyleSheet(stylesheet) def _on_ok_clicked(self): @@ -438,7 +440,7 @@ class ClipLoader: source_in = int(_clip_property("Start")) source_out = int(_clip_property("End")) - resolve.swap_clips( + lib.swap_clips( timeline_item, media_pool_item, source_in, @@ -504,7 +506,7 @@ class Creator(LegacyCreator): def __init__(self, *args, **kwargs): super(Creator, self).__init__(*args, **kwargs) - from openpype.settings import get_current_project_settings + resolve_p_settings = get_current_project_settings().get("resolve") self.presets = {} if resolve_p_settings: @@ -512,13 +514,13 @@ class Creator(LegacyCreator): self.__class__.__name__, {}) # adding basic current context resolve objects - self.project = resolve.get_current_project() - self.timeline = resolve.get_current_timeline() + self.project = lib.get_current_project() + self.timeline = lib.get_current_timeline() if (self.options or {}).get("useSelection"): - self.selected = resolve.get_current_timeline_items(filter=True) + self.selected = lib.get_current_timeline_items(filter=True) else: - self.selected = resolve.get_current_timeline_items(filter=False) + self.selected = lib.get_current_timeline_items(filter=False) self.widget = CreatorWidget From e3c2bb5a5e4d8a91508e0cd0db9ea2727424d6ed Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 14 Oct 2022 09:46:14 +0200 Subject: [PATCH 134/201] added one more last check for executable --- openpype/lib/vendor_bin_utils.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/openpype/lib/vendor_bin_utils.py b/openpype/lib/vendor_bin_utils.py index 31245d4ee4..7b52341290 100644 --- a/openpype/lib/vendor_bin_utils.py +++ b/openpype/lib/vendor_bin_utils.py @@ -265,6 +265,12 @@ def get_oiio_tools_path(tool="oiiotool"): if _oiio_executable_validation(default_path): tool_executable_path = default_path + # Look to PATH for the tool + if not tool_executable_path: + from_path = find_executable(tool) + if from_path and _oiio_executable_validation(from_path): + tool_executable_path = from_path + CachedToolPaths.cache_executable_path(tool, tool_executable_path) return tool_executable_path @@ -339,6 +345,12 @@ def get_ffmpeg_tool_path(tool="ffmpeg"): if tool_path and _ffmpeg_executable_validation(tool_path): tool_executable_path = tool_path + # Look to PATH for the tool + if not tool_executable_path: + from_path = find_executable(tool) + if from_path and _oiio_executable_validation(from_path): + tool_executable_path = from_path + CachedToolPaths.cache_executable_path(tool, tool_executable_path) return tool_executable_path From 9955ffe95c90fe181740aa81fd21015a0b99caba Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 14 Oct 2022 10:12:30 +0200 Subject: [PATCH 135/201] fix validation errors access --- .../tools/publisher/widgets/publish_frame.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/openpype/tools/publisher/widgets/publish_frame.py b/openpype/tools/publisher/widgets/publish_frame.py index 0a04b2a665..c5685461a7 100644 --- a/openpype/tools/publisher/widgets/publish_frame.py +++ b/openpype/tools/publisher/widgets/publish_frame.py @@ -384,12 +384,10 @@ class PublishFrame(QtWidgets.QWidget): self._validate_btn.setEnabled(validate_enabled) self._publish_btn.setEnabled(publish_enabled) - error_msg = self._controller.publish_error_msg - validation_errors = self._controller.get_validation_errors() - if error_msg: - self._set_error_msg(error_msg) + if self._controller.publish_has_crashed: + self._set_error_msg() - elif validation_errors: + elif self._controller.publish_has_validation_errors: self._set_progress_visibility(False) self._set_validation_errors() @@ -411,16 +409,12 @@ class PublishFrame(QtWidgets.QWidget): self._set_success_property(-1) - def _set_error_msg(self, error_msg): - """Show error message to artist. - - Args: - error_msg (str): Message which is showed to artist. - """ + def _set_error_msg(self): + """Show error message to artist on publish crash.""" self._set_main_label("Error happened") - self._message_label_top.setText(error_msg) + self._message_label_top.setText(self._controller.publish_error_msg) self._set_success_property(0) From ca32556b1177ee0dd36d4f863c5b6e816d804e86 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 14 Oct 2022 11:20:39 +0200 Subject: [PATCH 136/201] Fix - tags might be missing on representation --- .../ftrack/plugins/publish/integrate_ftrack_instances.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py index 96f573fe25..53c6e69ac0 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py @@ -169,7 +169,7 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): thumbnail_item["thumbnail"] = True # Create copy of item before setting location - if "delete" not in repre["tags"]: + if "delete" not in repre.get("tags", []): src_components_to_add.append(copy.deepcopy(thumbnail_item)) # Create copy of first thumbnail if first_thumbnail_component is None: @@ -284,7 +284,7 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): not_first_components.append(review_item) # Create copy of item before setting location - if "delete" not in repre["tags"]: + if "delete" not in repre.get("tags", []): src_components_to_add.append(copy.deepcopy(review_item)) # Set location From f152445b73352df2f32434cb57f3744c6ad1ab19 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 14 Oct 2022 12:40:00 +0200 Subject: [PATCH 137/201] resolve: fixing in callback import --- openpype/hosts/resolve/api/pipeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/resolve/api/pipeline.py b/openpype/hosts/resolve/api/pipeline.py index 1c8d9dc01c..899cb825bb 100644 --- a/openpype/hosts/resolve/api/pipeline.py +++ b/openpype/hosts/resolve/api/pipeline.py @@ -244,7 +244,7 @@ def on_pyblish_instance_toggled(instance, old_value, new_value): log.info("instance toggle: {}, old_value: {}, new_value:{} ".format( instance, old_value, new_value)) - from openpype.hosts.resolve import ( + from openpype.hosts.resolve.api import ( set_publish_attribute ) From 04f657327db467574d13682b25e2a9c9f6f62d28 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 14 Oct 2022 14:19:02 +0200 Subject: [PATCH 138/201] resolve: code improvements --- openpype/hosts/resolve/api/__init__.py | 7 +++---- openpype/hosts/resolve/api/preload_console.py | 2 +- openpype/hosts/resolve/utils.py | 3 ++- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/resolve/api/__init__.py b/openpype/hosts/resolve/api/__init__.py index cf1edb4c35..00a598548e 100644 --- a/openpype/hosts/resolve/api/__init__.py +++ b/openpype/hosts/resolve/api/__init__.py @@ -1,10 +1,6 @@ """ resolve api """ - -bmdvr = None -bmdvf = None - from .utils import ( get_resolve_module ) @@ -70,6 +66,9 @@ from .workio import ( from .testing_utils import TestGUI +bmdvr = None +bmdvf = None + __all__ = [ "bmdvr", "bmdvf", diff --git a/openpype/hosts/resolve/api/preload_console.py b/openpype/hosts/resolve/api/preload_console.py index a822ea2460..8b2b31fe1a 100644 --- a/openpype/hosts/resolve/api/preload_console.py +++ b/openpype/hosts/resolve/api/preload_console.py @@ -1,6 +1,6 @@ #!/usr/bin/env python import time -from openpype.hosts.resolve.utils import get_resolve_module +from openpype.hosts.resolve.api.utils import get_resolve_module from openpype.lib import Logger log = Logger.get_logger(__name__) diff --git a/openpype/hosts/resolve/utils.py b/openpype/hosts/resolve/utils.py index d5c133bbf5..5881f153ae 100644 --- a/openpype/hosts/resolve/utils.py +++ b/openpype/hosts/resolve/utils.py @@ -9,7 +9,8 @@ def setup(env): log = Logger.get_logger("ResolveSetup") scripts = {} us_env = env.get("RESOLVE_UTILITY_SCRIPTS_SOURCE_DIR") - us_dir = env.get("RESOLVE_UTILITY_SCRIPTS_DIR", "") + us_dir = env["RESOLVE_UTILITY_SCRIPTS_DIR"] + us_paths = [os.path.join( RESOLVE_ROOT_DIR, "utility_scripts" From db5311da87c45bc7850ff1bd2d90c669b87eaaf3 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 14 Oct 2022 14:19:42 +0200 Subject: [PATCH 139/201] resolve: transfering settings to prelaunch hook no need to expose environment variables --- .../hosts/resolve/hooks/pre_resolve_setup.py | 112 ++++++++++++++---- 1 file changed, 88 insertions(+), 24 deletions(-) diff --git a/openpype/hosts/resolve/hooks/pre_resolve_setup.py b/openpype/hosts/resolve/hooks/pre_resolve_setup.py index 1d977e2d8e..1a36715437 100644 --- a/openpype/hosts/resolve/hooks/pre_resolve_setup.py +++ b/openpype/hosts/resolve/hooks/pre_resolve_setup.py @@ -1,7 +1,8 @@ import os - +import platform from openpype.lib import PreLaunchHook from openpype.hosts.resolve.utils import setup +from openpype.hosts.resolve import api as rapi class ResolvePrelaunch(PreLaunchHook): @@ -14,35 +15,98 @@ class ResolvePrelaunch(PreLaunchHook): app_groups = ["resolve"] def execute(self): - # TODO: add OTIO installation from `openpype/requirements.py` - # making sure python 3.6 is installed at provided path - py36_dir = os.path.normpath( - self.launch_context.env.get("PYTHON36_RESOLVE", "")) - assert os.path.isdir(py36_dir), ( - "Python 3.6 is not installed at the provided folder path. Either " - "make sure the `environments\resolve.json` is having correctly " - "set `PYTHON36_RESOLVE` or make sure Python 3.6 is installed " - f"in given path. \nPYTHON36_RESOLVE: `{py36_dir}`" - ) - self.log.info(f"Path to Resolve Python folder: `{py36_dir}`...") + current_platform = platform.system().lower() + PROGRAMDATA = self.launch_context.env["PROGRAMDATA"] + RESOLVE_SCRIPT_API_ = { + "windows": ( + f"{PROGRAMDATA}/Blackmagic Design/" + "DaVinci Resolve/Support/Developer/Scripting" + ), + "darwin": ( + "/Library/Application Support/Blackmagic Design" + "/DaVinci Resolve/Developer/Scripting" + ), + "linux": "/opt/resolve/Developer/Scripting" + } + RESOLVE_SCRIPT_API = os.path.normpath( + RESOLVE_SCRIPT_API_[current_platform]) + self.launch_context.env["RESOLVE_SCRIPT_API"] = RESOLVE_SCRIPT_API + + RESOLVE_SCRIPT_LIB_ = { + "windows": ( + "C:/Program Files/Blackmagic Design" + "/DaVinci Resolve/fusionscript.dll" + ), + "darwin": ( + "/Applications/DaVinci Resolve/DaVinci Resolve.app" + "/Contents/Libraries/Fusion/fusionscript.so" + ), + "linux": "/opt/resolve/libs/Fusion/fusionscript.so" + } + RESOLVE_SCRIPT_LIB = os.path.normpath( + RESOLVE_SCRIPT_LIB_[current_platform]) + self.launch_context.env["RESOLVE_SCRIPT_LIB"] = RESOLVE_SCRIPT_LIB + + # TODO: add OTIO installation from `openpype/requirements.py` + # making sure python <3.9.* is installed at provided path + python3_home = os.path.normpath( + self.launch_context.env.get("RESOLVE_PYTHON3_HOME", "")) + + assert os.path.isdir(python3_home), ( + "Python 3 is not installed at the provided folder path. Either " + "make sure the `environments\resolve.json` is having correctly " + "set `RESOLVE_PYTHON3_HOME` or make sure Python 3 is installed " + f"in given path. \nRESOLVE_PYTHON3_HOME: `{python3_home}`" + ) + self.launch_context.env["PYTHONHOME"] = python3_home + self.log.info(f"Path to Resolve Python folder: `{python3_home}`...") + + # add to the python path to path + env_path = self.launch_context.env["PATH"] + self.launch_context.env["PATH"] = os.pathsep.join([ + python3_home, + os.path.join(python3_home, "Scripts") + ] + env_path.split(os.pathsep)) + + self.log.debug(f"PATH: {self.launch_context.env['PATH']}") + + # add to the PYTHONPATH + env_pythonpath = self.launch_context.env["PYTHONPATH"] + self.launch_context.env["PYTHONPATH"] = os.pathsep.join([ + os.path.join(python3_home, "Lib", "site-packages"), + os.path.join(RESOLVE_SCRIPT_API, "Modules"), + ] + env_pythonpath.split(os.pathsep)) + + self.log.debug(f"PYTHONPATH: {self.launch_context.env['PYTHONPATH']}") + + RESOLVE_UTILITY_SCRIPTS_DIR_ = { + "windows": ( + f"{PROGRAMDATA}/Blackmagic Design" + "/DaVinci Resolve/Fusion/Scripts/Comp" + ), + "darwin": ( + "/Library/Application Support/Blackmagic Design" + "/DaVinci Resolve/Fusion/Scripts/Comp" + ), + "linux": "/opt/resolve/Fusion/Scripts/Comp" + } + RESOLVE_UTILITY_SCRIPTS_DIR = os.path.normpath( + RESOLVE_UTILITY_SCRIPTS_DIR_[current_platform] + ) # setting utility scripts dir for scripts syncing - us_dir = os.path.normpath( - self.launch_context.env.get("RESOLVE_UTILITY_SCRIPTS_DIR", "") - ) - assert os.path.isdir(us_dir), ( - "Resolve utility script dir does not exists. Either make sure " - "the `environments\resolve.json` is having correctly set " - "`RESOLVE_UTILITY_SCRIPTS_DIR` or reinstall DaVinci Resolve. \n" - f"RESOLVE_UTILITY_SCRIPTS_DIR: `{us_dir}`" - ) - self.log.debug(f"-- us_dir: `{us_dir}`") + self.launch_context.env["RESOLVE_UTILITY_SCRIPTS_DIR"] = ( + RESOLVE_UTILITY_SCRIPTS_DIR) # correctly format path for pre python script - pre_py_sc = os.path.normpath( - self.launch_context.env.get("PRE_PYTHON_SCRIPT", "")) + rapi_path = os.path.dirname(rapi.__file__) + pre_py_sc = os.path.join( + rapi_path, "preload_console.py") self.launch_context.env["PRE_PYTHON_SCRIPT"] = pre_py_sc self.log.debug(f"-- pre_py_sc: `{pre_py_sc}`...") + # remove terminal coloring tags + self.launch_context.env["OPENPYPE_LOG_NO_COLORS"] = "True" + # Resolve Setup integration setup(self.launch_context.env) From 73fc3861f340b74e226ee56652d01d8355bcea70 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 14 Oct 2022 14:54:31 +0200 Subject: [PATCH 140/201] resolve: reducing settings to minimal --- .../system_settings/applications.json | 34 ++----------------- 1 file changed, 2 insertions(+), 32 deletions(-) diff --git a/openpype/settings/defaults/system_settings/applications.json b/openpype/settings/defaults/system_settings/applications.json index c37c3d299e..42eeb06191 100644 --- a/openpype/settings/defaults/system_settings/applications.json +++ b/openpype/settings/defaults/system_settings/applications.json @@ -782,41 +782,11 @@ "host_name": "resolve", "environment": { "RESOLVE_UTILITY_SCRIPTS_SOURCE_DIR": [], - "RESOLVE_SCRIPT_API": { - "windows": "{PROGRAMDATA}/Blackmagic Design/DaVinci Resolve/Support/Developer/Scripting", - "darwin": "/Library/Application Support/Blackmagic Design/DaVinci Resolve/Developer/Scripting", - "linux": "/opt/resolve/Developer/Scripting" - }, - "RESOLVE_SCRIPT_LIB": { - "windows": "C:/Program Files/Blackmagic Design/DaVinci Resolve/fusionscript.dll", - "darwin": "/Applications/DaVinci Resolve/DaVinci Resolve.app/Contents/Libraries/Fusion/fusionscript.so", - "linux": "/opt/resolve/libs/Fusion/fusionscript.so" - }, - "RESOLVE_UTILITY_SCRIPTS_DIR": { - "windows": "{PROGRAMDATA}/Blackmagic Design/DaVinci Resolve/Fusion/Scripts/Comp", - "darwin": "/Library/Application Support/Blackmagic Design/DaVinci Resolve/Fusion/Scripts/Comp", - "linux": "/opt/resolve/Fusion/Scripts/Comp" - }, - "PYTHON36_RESOLVE": { + "RESOLVE_PYTHON3_HOME": { "windows": "{LOCALAPPDATA}/Programs/Python/Python36", "darwin": "~/Library/Python/3.6/bin", "linux": "/opt/Python/3.6/bin" - }, - "PYTHONPATH": [ - "{PYTHON36_RESOLVE}/Lib/site-packages", - "{VIRTUAL_ENV}/Lib/site-packages", - "{PYTHONPATH}", - "{RESOLVE_SCRIPT_API}/Modules", - "{PYTHONPATH}" - ], - "PATH": [ - "{PYTHON36_RESOLVE}", - "{PYTHON36_RESOLVE}/Scripts", - "{PATH}" - ], - "PRE_PYTHON_SCRIPT": "{OPENPYPE_REPOS_ROOT}/openpype/resolve/preload_console.py", - "OPENPYPE_LOG_NO_COLORS": "True", - "RESOLVE_DEV": "True" + } }, "variants": { "stable": { From 09b9ba8b83ec82ed66f4ef1043bc4d380fc3ccff Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 14 Oct 2022 15:10:32 +0200 Subject: [PATCH 141/201] resolve: removing unused code --- .../RESOLVE_API_README_v16.2.0_down.txt | 189 ------------------ openpype/hosts/resolve/api/preload_console.py | 31 --- .../hosts/resolve/hooks/pre_resolve_setup.py | 9 - 3 files changed, 229 deletions(-) delete mode 100644 openpype/hosts/resolve/RESOLVE_API_README_v16.2.0_down.txt delete mode 100644 openpype/hosts/resolve/api/preload_console.py diff --git a/openpype/hosts/resolve/RESOLVE_API_README_v16.2.0_down.txt b/openpype/hosts/resolve/RESOLVE_API_README_v16.2.0_down.txt deleted file mode 100644 index 139b66bc24..0000000000 --- a/openpype/hosts/resolve/RESOLVE_API_README_v16.2.0_down.txt +++ /dev/null @@ -1,189 +0,0 @@ -Updated as of 08 March 2019 - --------------------------- -In this package, you will find a brief introduction to the Scripting API for DaVinci Resolve Studio. Apart from this README.txt file, this package contains folders containing the basic import modules for scripting access (DaVinciResolve.py) and some representative examples. - -Overview --------- - -As with Blackmagic Design Fusion scripts, user scripts written in Lua and Python programming languages are supported. By default, scripts can be invoked from the Console window in the Fusion page, or via command line. This permission can be changed in Resolve Preferences, to be only from Console, or to be invoked from the local network. Please be aware of the security implications when allowing scripting access from outside of the Resolve application. - - -Using a script --------------- -DaVinci Resolve needs to be running for a script to be invoked. - -For a Resolve script to be executed from an external folder, the script needs to know of the API location. -You may need to set the these environment variables to allow for your Python installation to pick up the appropriate dependencies as shown below: - - Mac OS X: - RESOLVE_SCRIPT_API="/Library/Application Support/Blackmagic Design/DaVinci Resolve/Developer/Scripting/" - RESOLVE_SCRIPT_LIB="/Applications/DaVinci Resolve/DaVinci Resolve.app/Contents/Libraries/Fusion/fusionscript.so" - PYTHONPATH="$PYTHONPATH:$RESOLVE_SCRIPT_API/Modules/" - - Windows: - RESOLVE_SCRIPT_API="%PROGRAMDATA%\\Blackmagic Design\\DaVinci Resolve\\Support\\Developer\\Scripting\\" - RESOLVE_SCRIPT_LIB="C:\\Program Files\\Blackmagic Design\\DaVinci Resolve\\fusionscript.dll" - PYTHONPATH="%PYTHONPATH%;%RESOLVE_SCRIPT_API%\\Modules\\" - - Linux: - RESOLVE_SCRIPT_API="/opt/resolve/Developer/Scripting/" - RESOLVE_SCRIPT_LIB="/opt/resolve/libs/Fusion/fusionscript.so" - PYTHONPATH="$PYTHONPATH:$RESOLVE_SCRIPT_API/Modules/" - (Note: For standard ISO Linux installations, the path above may need to be modified to refer to /home/resolve instead of /opt/resolve) - -As with Fusion scripts, Resolve scripts can also be invoked via the menu and the Console. - -On startup, DaVinci Resolve scans the Utility Scripts directory and enumerates the scripts found in the Script application menu. Placing your script in this folder and invoking it from this menu is the easiest way to use scripts. The Utility Scripts folder is located in: - Mac OS X: /Library/Application Support/Blackmagic Design/DaVinci Resolve/Fusion/Scripts/Comp/ - Windows: %APPDATA%\Blackmagic Design\DaVinci Resolve\Fusion\Scripts\Comp\ - Linux: /opt/resolve/Fusion/Scripts/Comp/ (or /home/resolve/Fusion/Scripts/Comp/ depending on installation) - -The interactive Console window allows for an easy way to execute simple scripting commands, to query or modify properties, and to test scripts. The console accepts commands in Python 2.7, Python 3.6 and Lua and evaluates and executes them immediately. For more information on how to use the Console, please refer to the DaVinci Resolve User Manual. - -This example Python script creates a simple project: - #!/usr/bin/env python - import DaVinciResolveScript as dvr_script - resolve = dvr_script.scriptapp("Resolve") - fusion = resolve.Fusion() - projectManager = resolve.GetProjectManager() - projectManager.CreateProject("Hello World") - -The resolve object is the fundamental starting point for scripting via Resolve. As a native object, it can be inspected for further scriptable properties - using table iteration and `getmetatable` in Lua and dir, help etc in Python (among other methods). A notable scriptable object above is fusion - it allows access to all existing Fusion scripting functionality. - -Running DaVinci Resolve in headless mode ----------------------------------------- - -DaVinci Resolve can be launched in a headless mode without the user interface using the -nogui command line option. When DaVinci Resolve is launched using this option, the user interface is disabled. However, the various scripting APIs will continue to work as expected. - -Basic Resolve API ------------------ - -Some commonly used API functions are described below (*). As with the resolve object, each object is inspectable for properties and functions. - - -Resolve - Fusion() --> Fusion # Returns the Fusion object. Starting point for Fusion scripts. - GetMediaStorage() --> MediaStorage # Returns media storage object to query and act on media locations. - GetProjectManager() --> ProjectManager # Returns project manager object for currently open database. - OpenPage(pageName) --> None # Switches to indicated page in DaVinci Resolve. Input can be one of ("media", "edit", "fusion", "color", "fairlight", "deliver"). -ProjectManager - CreateProject(projectName) --> Project # Creates and returns a project if projectName (text) is unique, and None if it is not. - LoadProject(projectName) --> Project # Loads and returns the project with name = projectName (text) if there is a match found, and None if there is no matching Project. - GetCurrentProject() --> Project # Returns the currently loaded Resolve project. - SaveProject() --> Bool # Saves the currently loaded project with its own name. Returns True if successful. - CreateFolder(folderName) --> Bool # Creates a folder if folderName (text) is unique. - GetProjectsInCurrentFolder() --> [project names...] # Returns an array of project names in current folder. - GetFoldersInCurrentFolder() --> [folder names...] # Returns an array of folder names in current folder. - GotoRootFolder() --> Bool # Opens root folder in database. - GotoParentFolder() --> Bool # Opens parent folder of current folder in database if current folder has parent. - OpenFolder(folderName) --> Bool # Opens folder under given name. - ImportProject(filePath) --> Bool # Imports a project under given file path. Returns true in case of success. - ExportProject(projectName, filePath) --> Bool # Exports a project based on given name into provided file path. Returns true in case of success. - RestoreProject(filePath) --> Bool # Restores a project under given backup file path. Returns true in case of success. -Project - GetMediaPool() --> MediaPool # Returns the Media Pool object. - GetTimelineCount() --> int # Returns the number of timelines currently present in the project. - GetTimelineByIndex(idx) --> Timeline # Returns timeline at the given index, 1 <= idx <= project.GetTimelineCount() - GetCurrentTimeline() --> Timeline # Returns the currently loaded timeline. - SetCurrentTimeline(timeline) --> Bool # Sets given timeline as current timeline for the project. Returns True if successful. - GetName() --> string # Returns project name. - SetName(projectName) --> Bool # Sets project name if given projectname (text) is unique. - GetPresets() --> [presets...] # Returns a table of presets and their information. - SetPreset(presetName) --> Bool # Sets preset by given presetName (string) into project. - GetRenderJobs() --> [render jobs...] # Returns a table of render jobs and their information. - GetRenderPresets() --> [presets...] # Returns a table of render presets and their information. - StartRendering(index1, index2, ...) --> Bool # Starts rendering for given render jobs based on their indices. If no parameter is given rendering would start for all render jobs. - StartRendering([idxs...]) --> Bool # Starts rendering for given render jobs based on their indices. If no parameter is given rendering would start for all render jobs. - StopRendering() --> None # Stops rendering for all render jobs. - IsRenderingInProgress() --> Bool # Returns true is rendering is in progress. - AddRenderJob() --> Bool # Adds render job to render queue. - DeleteRenderJobByIndex(idx) --> Bool # Deletes render job based on given job index (int). - DeleteAllRenderJobs() --> Bool # Deletes all render jobs. - LoadRenderPreset(presetName) --> Bool # Sets a preset as current preset for rendering if presetName (text) exists. - SaveAsNewRenderPreset(presetName) --> Bool # Creates a new render preset by given name if presetName(text) is unique. - SetRenderSettings([settings map]) --> Bool # Sets given settings for rendering. Settings map is a map, keys of map are: "SelectAllFrames", "MarkIn", "MarkOut", "TargetDir", "CustomName". - GetRenderJobStatus(idx) --> [status info] # Returns job status and completion rendering percentage of the job by given job index (int). - GetSetting(settingName) --> string # Returns setting value by given settingName (string) if the setting exist. With empty settingName the function returns a full list of settings. - SetSetting(settingName, settingValue) --> Bool # Sets project setting base on given name (string) and value (string). - GetRenderFormats() --> [render formats...]# Returns a list of available render formats. - GetRenderCodecs(renderFormat) --> [render codecs...] # Returns a list of available codecs for given render format (string). - GetCurrentRenderFormatAndCodec() --> [format, codec] # Returns currently selected render format and render codec. - SetCurrentRenderFormatAndCodec(format, codec) --> Bool # Sets given render format (string) and render codec (string) as options for rendering. -MediaStorage - GetMountedVolumes() --> [paths...] # Returns an array of folder paths corresponding to mounted volumes displayed in Resolve’s Media Storage. - GetSubFolders(folderPath) --> [paths...] # Returns an array of folder paths in the given absolute folder path. - GetFiles(folderPath) --> [paths...] # Returns an array of media and file listings in the given absolute folder path. Note that media listings may be logically consolidated entries. - RevealInStorage(path) --> None # Expands and displays a given file/folder path in Resolve’s Media Storage. - AddItemsToMediaPool(item1, item2, ...) --> [clips...] # Adds specified file/folder paths from Media Store into current Media Pool folder. Input is one or more file/folder paths. - AddItemsToMediaPool([items...]) --> [clips...] # Adds specified file/folder paths from Media Store into current Media Pool folder. Input is an array of file/folder paths. -MediaPool - GetRootFolder() --> Folder # Returns the root Folder of Media Pool - AddSubFolder(folder, name) --> Folder # Adds a new subfolder under specified Folder object with the given name. - CreateEmptyTimeline(name) --> Timeline # Adds a new timeline with given name. - AppendToTimeline(clip1, clip2...) --> Bool # Appends specified MediaPoolItem objects in the current timeline. Returns True if successful. - AppendToTimeline([clips]) --> Bool # Appends specified MediaPoolItem objects in the current timeline. Returns True if successful. - CreateTimelineFromClips(name, clip1, clip2, ...)--> Timeline # Creates a new timeline with specified name, and appends the specified MediaPoolItem objects. - CreateTimelineFromClips(name, [clips]) --> Timeline # Creates a new timeline with specified name, and appends the specified MediaPoolItem objects. - ImportTimelineFromFile(filePath) --> Timeline # Creates timeline based on parameters within given file. - GetCurrentFolder() --> Folder # Returns currently selected Folder. - SetCurrentFolder(Folder) --> Bool # Sets current folder by given Folder. -Folder - GetClips() --> [clips...] # Returns a list of clips (items) within the folder. - GetName() --> string # Returns user-defined name of the folder. - GetSubFolders() --> [folders...] # Returns a list of subfolders in the folder. -MediaPoolItem - GetMetadata(metadataType) --> [[types],[values]] # Returns a value of metadataType. If parameter is not specified returns all set metadata parameters. - SetMetadata(metadataType, metadataValue) --> Bool # Sets metadata by given type and value. Returns True if successful. - GetMediaId() --> string # Returns a unique ID name related to MediaPoolItem. - AddMarker(frameId, color, name, note, duration) --> Bool # Creates a new marker at given frameId position and with given marker information. - GetMarkers() --> [markers...] # Returns a list of all markers and their information. - AddFlag(color) --> Bool # Adds a flag with given color (text). - GetFlags() --> [colors...] # Returns a list of flag colors assigned to the item. - GetClipColor() --> string # Returns an item color as a string. - GetClipProperty(propertyName) --> [[types],[values]] # Returns property value related to the item based on given propertyName (string). if propertyName is empty then it returns a full list of properties. - SetClipProperty(propertyName, propertyValue) --> Bool # Sets into given propertyName (string) propertyValue (string). -Timeline - GetName() --> string # Returns user-defined name of the timeline. - SetName(timelineName) --> Bool # Sets timeline name is timelineName (text) is unique. - GetStartFrame() --> int # Returns frame number at the start of timeline. - GetEndFrame() --> int # Returns frame number at the end of timeline. - GetTrackCount(trackType) --> int # Returns a number of track based on specified track type ("audio", "video" or "subtitle"). - GetItemsInTrack(trackType, index) --> [items...] # Returns an array of Timeline items on the video or audio track (based on trackType) at specified index. 1 <= index <= GetTrackCount(trackType). - AddMarker(frameId, color, name, note, duration) --> Bool # Creates a new marker at given frameId position and with given marker information. - GetMarkers() --> [markers...] # Returns a list of all markers and their information. - ApplyGradeFromDRX(path, gradeMode, item1, item2, ...)--> Bool # Loads a still from given file path (string) and applies grade to Timeline Items with gradeMode (int): 0 - "No keyframes", 1 - "Source Timecode aligned", 2 - "Start Frames aligned". - ApplyGradeFromDRX(path, gradeMode, [items]) --> Bool # Loads a still from given file path (string) and applies grade to Timeline Items with gradeMode (int): 0 - "No keyframes", 1 - "Source Timecode aligned", 2 - "Start Frames aligned". - GetCurrentTimecode() --> string # Returns a string representing a timecode for current position of the timeline, while on Cut, Edit, Color and Deliver page. - GetCurrentVideoItem() --> item # Returns current video timeline item. - GetCurrentClipThumbnailImage() --> [width, height, format, data] # Returns raw thumbnail image data (This image data is encoded in base 64 format and the image format is RGB 8 bit) for the current media in the Color Page in the format of dictionary (in Python) and table (in Lua). Information return are "width", "height", "format" and "data". Example is provided in 6_get_current_media_thumbnail.py in Example folder. -TimelineItem - GetName() --> string # Returns a name of the item. - GetDuration() --> int # Returns a duration of item. - GetEnd() --> int # Returns a position of end frame. - GetFusionCompCount() --> int # Returns the number of Fusion compositions associated with the timeline item. - GetFusionCompByIndex(compIndex) --> fusionComp # Returns Fusion composition object based on given index. 1 <= compIndex <= timelineItem.GetFusionCompCount() - GetFusionCompNames() --> [names...] # Returns a list of Fusion composition names associated with the timeline item. - GetFusionCompByName(compName) --> fusionComp # Returns Fusion composition object based on given name. - GetLeftOffset() --> int # Returns a maximum extension by frame for clip from left side. - GetRightOffset() --> int # Returns a maximum extension by frame for clip from right side. - GetStart() --> int # Returns a position of first frame. - AddMarker(frameId, color, name, note, duration) --> Bool # Creates a new marker at given frameId position and with given marker information. - GetMarkers() --> [markers...] # Returns a list of all markers and their information. - GetFlags() --> [colors...] # Returns a list of flag colors assigned to the item. - GetClipColor() --> string # Returns an item color as a string. - AddFusionComp() --> fusionComp # Adds a new Fusion composition associated with the timeline item. - ImportFusionComp(path) --> fusionComp # Imports Fusion composition from given file path by creating and adding a new composition for the item. - ExportFusionComp(path, compIndex) --> Bool # Exports Fusion composition based on given index into provided file name path. - DeleteFusionCompByName(compName) --> Bool # Deletes Fusion composition by provided name. - LoadFusionCompByName(compName) --> fusionComp # Loads Fusion composition by provided name and sets it as active composition. - RenameFusionCompByName(oldName, newName) --> Bool # Renames Fusion composition by provided name with new given name. - AddVersion(versionName, versionType) --> Bool # Adds a new Version associated with the timeline item. versionType: 0 - local, 1 - remote. - DeleteVersionByName(versionName, versionType) --> Bool # Deletes Version by provided name. versionType: 0 - local, 1 - remote. - LoadVersionByName(versionName, versionType) --> Bool # Loads Version by provided name and sets it as active Version. versionType: 0 - local, 1 - remote. - RenameVersionByName(oldName, newName, versionType)--> Bool # Renames Version by provided name with new given name. versionType: 0 - local, 1 - remote. - GetMediaPoolItem() --> MediaPoolItem # Returns a corresponding to the timeline item media pool item if it exists. - GetVersionNames(versionType) --> [strings...] # Returns a list of version names by provided versionType: 0 - local, 1 - remote. - GetStereoConvergenceValues() --> [offset, value] # Returns a table of keyframe offsets and respective convergence values - GetStereoLeftFloatingWindowParams() --> [offset, value] # For the LEFT eye -> returns a table of keyframe offsets and respective floating window params. Value at particular offset includes the left, right, top and bottom floating window values - GetStereoRightFloatingWindowParams() --> [offset, value] # For the RIGHT eye -> returns a table of keyframe offsets and respective floating window params. Value at particular offset includes the left, right, top and bottom floating window values diff --git a/openpype/hosts/resolve/api/preload_console.py b/openpype/hosts/resolve/api/preload_console.py deleted file mode 100644 index 8b2b31fe1a..0000000000 --- a/openpype/hosts/resolve/api/preload_console.py +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env python -import time -from openpype.hosts.resolve.api.utils import get_resolve_module -from openpype.lib import Logger - -log = Logger.get_logger(__name__) - -wait_delay = 2.5 -wait = 0.00 -ready = None -while True: - try: - # Create project and set parameters: - resolve = get_resolve_module() - pm = resolve.GetProjectManager() - if pm: - ready = None - else: - ready = True - except AttributeError: - pass - - if ready is None: - time.sleep(wait_delay) - log.info(f"Waiting {wait}s for Resolve to have opened Project Manager") - wait += wait_delay - else: - print(f"Preloaded variables: \n\n\tResolve module: " - f"`resolve` > {type(resolve)} \n\tProject manager: " - f"`pm` > {type(pm)}") - break diff --git a/openpype/hosts/resolve/hooks/pre_resolve_setup.py b/openpype/hosts/resolve/hooks/pre_resolve_setup.py index 1a36715437..0cf9664457 100644 --- a/openpype/hosts/resolve/hooks/pre_resolve_setup.py +++ b/openpype/hosts/resolve/hooks/pre_resolve_setup.py @@ -2,8 +2,6 @@ import os import platform from openpype.lib import PreLaunchHook from openpype.hosts.resolve.utils import setup -from openpype.hosts.resolve import api as rapi - class ResolvePrelaunch(PreLaunchHook): """ @@ -98,13 +96,6 @@ class ResolvePrelaunch(PreLaunchHook): self.launch_context.env["RESOLVE_UTILITY_SCRIPTS_DIR"] = ( RESOLVE_UTILITY_SCRIPTS_DIR) - # correctly format path for pre python script - rapi_path = os.path.dirname(rapi.__file__) - pre_py_sc = os.path.join( - rapi_path, "preload_console.py") - self.launch_context.env["PRE_PYTHON_SCRIPT"] = pre_py_sc - self.log.debug(f"-- pre_py_sc: `{pre_py_sc}`...") - # remove terminal coloring tags self.launch_context.env["OPENPYPE_LOG_NO_COLORS"] = "True" From e43cf43a105f9aed18a1f7664af62d1fe21c8658 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 14 Oct 2022 15:10:54 +0200 Subject: [PATCH 142/201] resolve: updating resolve api readme and our readme --- openpype/hosts/resolve/README.markdown | 16 +- ...v16.2.0_up.txt => RESOLVE_API_v18.0.4.txt} | 332 ++++++++++++++---- 2 files changed, 274 insertions(+), 74 deletions(-) rename openpype/hosts/resolve/{RESOLVE_API_README_v16.2.0_up.txt => RESOLVE_API_v18.0.4.txt} (70%) diff --git a/openpype/hosts/resolve/README.markdown b/openpype/hosts/resolve/README.markdown index 8c9f72fb0c..38db8a8004 100644 --- a/openpype/hosts/resolve/README.markdown +++ b/openpype/hosts/resolve/README.markdown @@ -1,14 +1,16 @@ #### Basic setup -- Install [latest DaVinci Resolve](https://sw.blackmagicdesign.com/DaVinciResolve/v16.2.8/DaVinci_Resolve_Studio_16.2.8_Windows.zip?Key-Pair-Id=APKAJTKA3ZJMJRQITVEA&Signature=EcFuwQFKHZIBu2zDj5LTCQaQDXcKOjhZY7Fs07WGw24xdDqfwuALOyKu+EVzDX2Tik0cWDunYyV0r7hzp+mHmczp9XP4YaQXHdyhD/2BGWDgiMsiTQbNkBgbfy5MsAMFY8FHCl724Rxm8ke1foWeUVyt/Cdkil+ay+9sL72yFhaSV16sncko1jCIlCZeMkHhbzqPwyRuqLGmxmp8ey9KgBhI3wGFFPN201VMaV+RHrpX+KAfaR6p6dwo3FrPbRHK9TvMI1RA/1lJ3fVtrkDW69LImIKAWmIxgcStUxR9/taqLOD66FNiflHd1tufHv3FBa9iYQsjb3VLMPx7OCwLyg==&Expires=1608308139) -- add absolute path to ffmpeg into openpype settings - ![image](https://user-images.githubusercontent.com/40640033/102630786-43294f00-414d-11eb-98de-f0ae51f62077.png) -- install Python 3.6 into `%LOCALAPPDATA%/Programs/Python/Python36` (only respected path by Resolve) -- install OpenTimelineIO for 3.6 `%LOCALAPPDATA%\Programs\Python\Python36\python.exe -m pip install git+https://github.com/PixarAnimationStudios/OpenTimelineIO.git@5aa24fbe89d615448876948fe4b4900455c9a3e8` and move built files from `%LOCALAPPDATA%/Programs/Python/Python36/Lib/site-packages/opentimelineio/cxx-libs/bin and lib` to `%LOCALAPPDATA%/Programs/Python/Python36/Lib/site-packages/opentimelineio/`. I was building it on Win10 machine with Visual Studio Community 2019 and +- Actually supported version is up to v18 +- install Python 3.6.2 (latest tested v17) or up to 3.9.13 (latest tested on v18) +- pip install PySide2: + - Python 3.9.*: open terminal and go to python.exe directory, then `python -m pip install PySide2` +- pip install OpenTimelineIO: + - Python 3.9.*: open terminal and go to python.exe directory, then `python -m pip install OpenTimelineIO` + - Python 3.6: open terminal and go to python.exe directory, then `python -m pip install git+https://github.com/PixarAnimationStudios/OpenTimelineIO.git@5aa24fbe89d615448876948fe4b4900455c9a3e8` and move built files from `%LOCALAPPDATA%/Programs/Python/Python36/Lib/site-packages/opentimelineio/cxx-libs/bin and lib` to `/Lib/site-packages/opentimelineio/`. I was building it on Win10 machine with Visual Studio Community 2019 and ![image](https://user-images.githubusercontent.com/40640033/102792588-ffcb1c80-43a8-11eb-9c6b-bf2114ed578e.png) with installed CMake in PATH. -- install PySide2 for 3.6 `%LOCALAPPDATA%\Programs\Python\Python36\python.exe -m pip install PySide2` - make sure Resolve Fusion (Fusion Tab/menu/Fusion/Fusion Settings) is set to Python 3.6 ![image](https://user-images.githubusercontent.com/40640033/102631545-280b0f00-414e-11eb-89fc-98ac268d209d.png) +- Open OpenPype **Tray/Admin/Studio settings** > `applications/resolve/environment` and add Python3 path to `RESOLVE_PYTHON3_HOME` platform related. #### Editorial setup @@ -16,7 +18,7 @@ This is how it looks on my testing project timeline ![image](https://user-images.githubusercontent.com/40640033/102637638-96ec6600-4156-11eb-9656-6e8e3ce4baf8.png) Notice I had renamed tracks to `main` (holding metadata markers) and `review` used for generating review data with ffmpeg confersion to jpg sequence. -1. you need to start OpenPype menu from Resolve/EditTab/Menu/Workspace/Scripts/**__OpenPype_Menu__** +1. you need to start OpenPype menu from Resolve/EditTab/Menu/Workspace/Scripts/Comp/**__OpenPype_Menu__** 2. then select any clips in `main` track and change their color to `Chocolate` 3. in OpenPype Menu select `Create` 4. in Creator select `Create Publishable Clip [New]` (temporary name) diff --git a/openpype/hosts/resolve/RESOLVE_API_README_v16.2.0_up.txt b/openpype/hosts/resolve/RESOLVE_API_v18.0.4.txt similarity index 70% rename from openpype/hosts/resolve/RESOLVE_API_README_v16.2.0_up.txt rename to openpype/hosts/resolve/RESOLVE_API_v18.0.4.txt index f1b8b81a71..98597a12cb 100644 --- a/openpype/hosts/resolve/RESOLVE_API_README_v16.2.0_up.txt +++ b/openpype/hosts/resolve/RESOLVE_API_v18.0.4.txt @@ -1,5 +1,5 @@ -Updated as of 20 October 2020 ------------------------------ +Updated as of 9 May 2022 +---------------------------- In this package, you will find a brief introduction to the Scripting API for DaVinci Resolve Studio. Apart from this README.txt file, this package contains folders containing the basic import modules for scripting access (DaVinciResolve.py) and some representative examples. @@ -89,12 +89,25 @@ Resolve Fusion() --> Fusion # Returns the Fusion object. Starting point for Fusion scripts. GetMediaStorage() --> MediaStorage # Returns the media storage object to query and act on media locations. GetProjectManager() --> ProjectManager # Returns the project manager object for currently open database. - OpenPage(pageName) --> None # Switches to indicated page in DaVinci Resolve. Input can be one of ("media", "cut", "edit", "fusion", "color", "fairlight", "deliver"). + OpenPage(pageName) --> Bool # Switches to indicated page in DaVinci Resolve. Input can be one of ("media", "cut", "edit", "fusion", "color", "fairlight", "deliver"). + GetCurrentPage() --> String # Returns the page currently displayed in the main window. Returned value can be one of ("media", "cut", "edit", "fusion", "color", "fairlight", "deliver", None). GetProductName() --> string # Returns product name. GetVersion() --> [version fields] # Returns list of product version fields in [major, minor, patch, build, suffix] format. GetVersionString() --> string # Returns product version in "major.minor.patch[suffix].build" format. + LoadLayoutPreset(presetName) --> Bool # Loads UI layout from saved preset named 'presetName'. + UpdateLayoutPreset(presetName) --> Bool # Overwrites preset named 'presetName' with current UI layout. + ExportLayoutPreset(presetName, presetFilePath) --> Bool # Exports preset named 'presetName' to path 'presetFilePath'. + DeleteLayoutPreset(presetName) --> Bool # Deletes preset named 'presetName'. + SaveLayoutPreset(presetName) --> Bool # Saves current UI layout as a preset named 'presetName'. + ImportLayoutPreset(presetFilePath, presetName) --> Bool # Imports preset from path 'presetFilePath'. The optional argument 'presetName' specifies how the preset shall be named. If not specified, the preset is named based on the filename. + Quit() --> None # Quits the Resolve App. ProjectManager + ArchiveProject(projectName, + filePath, + isArchiveSrcMedia=True, + isArchiveRenderCache=True, + isArchiveProxyMedia=False) --> Bool # Archives project to provided file path with the configuration as provided by the optional arguments CreateProject(projectName) --> Project # Creates and returns a project if projectName (string) is unique, and None if it is not. DeleteProject(projectName) --> Bool # Delete project in the current folder if not currently loaded LoadProject(projectName) --> Project # Loads and returns the project with name = projectName (string) if there is a match found, and None if there is no matching Project. @@ -109,9 +122,9 @@ ProjectManager GotoParentFolder() --> Bool # Opens parent folder of current folder in database if current folder has parent. GetCurrentFolder() --> string # Returns the current folder name. OpenFolder(folderName) --> Bool # Opens folder under given name. - ImportProject(filePath) --> Bool # Imports a project from the file path provided. Returns True if successful. + ImportProject(filePath, projectName=None) --> Bool # Imports a project from the file path provided with given project name, if any. Returns True if successful. ExportProject(projectName, filePath, withStillsAndLUTs=True) --> Bool # Exports project to provided file path, including stills and LUTs if withStillsAndLUTs is True (enabled by default). Returns True in case of success. - RestoreProject(filePath) --> Bool # Restores a project from the file path provided. Returns True if successful. + RestoreProject(filePath, projectName=None) --> Bool # Restores a project from the file path provided with given project name, if any. Returns True if successful. GetCurrentDatabase() --> {dbInfo} # Returns a dictionary (with keys 'DbType', 'DbName' and optional 'IpAddress') corresponding to the current database connection GetDatabaseList() --> [{dbInfo}] # Returns a list of dictionary items (with keys 'DbType', 'DbName' and optional 'IpAddress') corresponding to all the databases added to Resolve SetCurrentDatabase({dbInfo}) --> Bool # Switches current database connection to the database specified by the keys below, and closes any open project. @@ -125,8 +138,9 @@ Project GetTimelineByIndex(idx) --> Timeline # Returns timeline at the given index, 1 <= idx <= project.GetTimelineCount() GetCurrentTimeline() --> Timeline # Returns the currently loaded timeline. SetCurrentTimeline(timeline) --> Bool # Sets given timeline as current timeline for the project. Returns True if successful. + GetGallery() --> Gallery # Returns the Gallery object. GetName() --> string # Returns project name. - SetName(projectName) --> Bool # Sets project name if given projectname (string) is unique. + SetName(projectName) --> Bool # Sets project name if given projectName (string) is unique. GetPresetList() --> [presets...] # Returns a list of presets and their information. SetPreset(presetName) --> Bool # Sets preset by given presetName (string) into project. AddRenderJob() --> string # Adds a render job based on current render settings to the render queue. Returns a unique job id (string) for the new render job. @@ -144,27 +158,7 @@ Project LoadRenderPreset(presetName) --> Bool # Sets a preset as current preset for rendering if presetName (string) exists. SaveAsNewRenderPreset(presetName) --> Bool # Creates new render preset by given name if presetName(string) is unique. SetRenderSettings({settings}) --> Bool # Sets given settings for rendering. Settings is a dict, with support for the keys: - # "SelectAllFrames": Bool - # "MarkIn": int - # "MarkOut": int - # "TargetDir": string - # "CustomName": string - # "UniqueFilenameStyle": 0 - Prefix, 1 - Suffix. - # "ExportVideo": Bool - # "ExportAudio": Bool - # "FormatWidth": int - # "FormatHeight": int - # "FrameRate": float (examples: 23.976, 24) - # "PixelAspectRatio": string (for SD resolution: "16_9" or "4_3") (other resolutions: "square" or "cinemascope") - # "VideoQuality" possible values for current codec (if applicable): - # 0 (int) - will set quality to automatic - # [1 -> MAX] (int) - will set input bit rate - # ["Least", "Low", "Medium", "High", "Best"] (String) - will set input quality level - # "AudioCodec": string (example: "aac") - # "AudioBitDepth": int - # "AudioSampleRate": int - # "ColorSpaceTag" : string (example: "Same as Project", "AstroDesign") - # "GammaTag" : string (example: "Same as Project", "ACEScct") + # Refer to "Looking up render settings" section for information for supported settings GetRenderJobStatus(jobId) --> {status info} # Returns a dict with job status and completion percentage of the job by given jobId (string). GetSetting(settingName) --> string # Returns value of project setting (indicated by settingName, string). Check the section below for more information. SetSetting(settingName, settingValue) --> Bool # Sets the project setting (indicated by settingName, string) to the value (settingValue, string). Check the section below for more information. @@ -176,12 +170,13 @@ Project SetCurrentRenderMode(renderMode) --> Bool # Sets the render mode. Specify renderMode = 0 for Individual clips, 1 for Single clip. GetRenderResolutions(format, codec) --> [{Resolution}] # Returns list of resolutions applicable for the given render format (string) and render codec (string). Returns full list of resolutions if no argument is provided. Each element in the list is a dictionary with 2 keys "Width" and "Height". RefreshLUTList() --> Bool # Refreshes LUT List + GetUniqueId() --> string # Returns a unique ID for the project item MediaStorage GetMountedVolumeList() --> [paths...] # Returns list of folder paths corresponding to mounted volumes displayed in Resolve’s Media Storage. GetSubFolderList(folderPath) --> [paths...] # Returns list of folder paths in the given absolute folder path. GetFileList(folderPath) --> [paths...] # Returns list of media and file listings in the given absolute folder path. Note that media listings may be logically consolidated entries. - RevealInStorage(path) --> None # Expands and displays given file/folder path in Resolve’s Media Storage. + RevealInStorage(path) --> Bool # Expands and displays given file/folder path in Resolve’s Media Storage. AddItemListToMediaPool(item1, item2, ...) --> [clips...] # Adds specified file/folder paths from Media Storage into current Media Pool folder. Input is one or more file/folder paths. Returns a list of the MediaPoolItems created. AddItemListToMediaPool([items...]) --> [clips...] # Adds specified file/folder paths from Media Storage into current Media Pool folder. Input is an array of file/folder paths. Returns a list of the MediaPoolItems created. AddClipMattesToMediaPool(MediaPoolItem, [paths], stereoEye) --> Bool # Adds specified media files as mattes for the specified MediaPoolItem. StereoEye is an optional argument for specifying which eye to add the matte to for stereo clips ("left" or "right"). Returns True if successful. @@ -190,10 +185,11 @@ MediaStorage MediaPool GetRootFolder() --> Folder # Returns root Folder of Media Pool AddSubFolder(folder, name) --> Folder # Adds new subfolder under specified Folder object with the given name. + RefreshFolders() --> Bool # Updates the folders in collaboration mode CreateEmptyTimeline(name) --> Timeline # Adds new timeline with given name. - AppendToTimeline(clip1, clip2, ...) --> Bool # Appends specified MediaPoolItem objects in the current timeline. Returns True if successful. - AppendToTimeline([clips]) --> Bool # Appends specified MediaPoolItem objects in the current timeline. Returns True if successful. - AppendToTimeline([{clipInfo}, ...]) --> Bool # Appends list of clipInfos specified as dict of "mediaPoolItem", "startFrame" (int), "endFrame" (int). + AppendToTimeline(clip1, clip2, ...) --> [TimelineItem] # Appends specified MediaPoolItem objects in the current timeline. Returns the list of appended timelineItems. + AppendToTimeline([clips]) --> [TimelineItem] # Appends specified MediaPoolItem objects in the current timeline. Returns the list of appended timelineItems. + AppendToTimeline([{clipInfo}, ...]) --> [TimelineItem] # Appends list of clipInfos specified as dict of "mediaPoolItem", "startFrame" (int), "endFrame" (int), (optional) "mediaType" (int; 1 - Video only, 2 - Audio only). Returns the list of appended timelineItems. CreateTimelineFromClips(name, clip1, clip2,...) --> Timeline # Creates new timeline with specified name, and appends the specified MediaPoolItem objects. CreateTimelineFromClips(name, [clips]) --> Timeline # Creates new timeline with specified name, and appends the specified MediaPoolItem objects. CreateTimelineFromClips(name, [{clipInfo}]) --> Timeline # Creates new timeline with specified name, appending the list of clipInfos specified as a dict of "mediaPoolItem", "startFrame" (int), "endFrame" (int). @@ -202,6 +198,8 @@ MediaPool # "importSourceClips": Bool, specifies whether source clips should be imported, True by default # "sourceClipsPath": string, specifies a filesystem path to search for source clips if the media is inaccessible in their original path and if "importSourceClips" is True # "sourceClipsFolders": List of Media Pool folder objects to search for source clips if the media is not present in current folder and if "importSourceClips" is False + # "interlaceProcessing": Bool, specifies whether to enable interlace processing on the imported timeline being created. valid only for AAF import + DeleteTimelines([timeline]) --> Bool # Deletes specified timelines in the media pool. GetCurrentFolder() --> Folder # Returns currently selected Folder. SetCurrentFolder(Folder) --> Bool # Sets current folder by given Folder. DeleteClips([clips]) --> Bool # Deletes specified clips or timeline mattes in the media pool @@ -214,19 +212,26 @@ MediaPool RelinkClips([MediaPoolItem], folderPath) --> Bool # Update the folder location of specified media pool clips with the specified folder path. UnlinkClips([MediaPoolItem]) --> Bool # Unlink specified media pool clips. ImportMedia([items...]) --> [MediaPoolItems] # Imports specified file/folder paths into current Media Pool folder. Input is an array of file/folder paths. Returns a list of the MediaPoolItems created. + ImportMedia([{clipInfo}]) --> [MediaPoolItems] # Imports file path(s) into current Media Pool folder as specified in list of clipInfo dict. Returns a list of the MediaPoolItems created. + # Each clipInfo gets imported as one MediaPoolItem unless 'Show Individual Frames' is turned on. + # Example: ImportMedia([{"FilePath":"file_%03d.dpx", "StartIndex":1, "EndIndex":100}]) would import clip "file_[001-100].dpx". ExportMetadata(fileName, [clips]) --> Bool # Exports metadata of specified clips to 'fileName' in CSV format. # If no clips are specified, all clips from media pool will be used. + GetUniqueId() --> string # Returns a unique ID for the media pool Folder GetClipList() --> [clips...] # Returns a list of clips (items) within the folder. GetName() --> string # Returns the media folder name. GetSubFolderList() --> [folders...] # Returns a list of subfolders in the folder. + GetIsFolderStale() --> bool # Returns true if folder is stale in collaboration mode, false otherwise + GetUniqueId() --> string # Returns a unique ID for the media pool folder MediaPoolItem GetName() --> string # Returns the clip name. GetMetadata(metadataType=None) --> string|dict # Returns the metadata value for the key 'metadataType'. # If no argument is specified, a dict of all set metadata properties is returned. SetMetadata(metadataType, metadataValue) --> Bool # Sets the given metadata to metadataValue (string). Returns True if successful. + SetMetadata({metadata}) --> Bool # Sets the item metadata with specified 'metadata' dict. Returns True if successful. GetMediaId() --> string # Returns the unique ID for the MediaPoolItem. AddMarker(frameId, color, name, note, duration, --> Bool # Creates a new marker at given frameId position and with given marker information. 'customData' is optional and helps to attach user specific data to the marker. customData) @@ -248,15 +253,18 @@ MediaPoolItem GetClipProperty(propertyName=None) --> string|dict # Returns the property value for the key 'propertyName'. # If no argument is specified, a dict of all clip properties is returned. Check the section below for more information. SetClipProperty(propertyName, propertyValue) --> Bool # Sets the given property to propertyValue (string). Check the section below for more information. - LinkProxyMedia(propertyName) --> Bool # Links proxy media (absolute path) with the current clip. + LinkProxyMedia(proxyMediaFilePath) --> Bool # Links proxy media located at path specified by arg 'proxyMediaFilePath' with the current clip. 'proxyMediaFilePath' should be absolute clip path. UnlinkProxyMedia() --> Bool # Unlinks any proxy media associated with clip. ReplaceClip(filePath) --> Bool # Replaces the underlying asset and metadata of MediaPoolItem with the specified absolute clip path. + GetUniqueId() --> string # Returns a unique ID for the media pool item Timeline GetName() --> string # Returns the timeline name. SetName(timelineName) --> Bool # Sets the timeline name if timelineName (string) is unique. Returns True if successful. GetStartFrame() --> int # Returns the frame number at the start of timeline. GetEndFrame() --> int # Returns the frame number at the end of timeline. + SetStartTimecode(timecode) --> Bool # Set the start timecode of the timeline to the string 'timecode'. Returns true when the change is successful, false otherwise. + GetStartTimecode() --> string # Returns the start timecode for the timeline. GetTrackCount(trackType) --> int # Returns the number of tracks for the given track type ("audio", "video" or "subtitle"). GetItemListInTrack(trackType, index) --> [items...] # Returns a list of timeline items on that track (based on trackType and index). 1 <= index <= GetTrackCount(trackType). AddMarker(frameId, color, name, note, duration, --> Bool # Creates a new marker at given frameId position and with given marker information. 'customData' is optional and helps to attach user specific data to the marker. @@ -271,7 +279,8 @@ Timeline DeleteMarkerByCustomData(customData) --> Bool # Delete first matching marker with specified customData. ApplyGradeFromDRX(path, gradeMode, item1, item2, ...)--> Bool # Loads a still from given file path (string) and applies grade to Timeline Items with gradeMode (int): 0 - "No keyframes", 1 - "Source Timecode aligned", 2 - "Start Frames aligned". ApplyGradeFromDRX(path, gradeMode, [items]) --> Bool # Loads a still from given file path (string) and applies grade to Timeline Items with gradeMode (int): 0 - "No keyframes", 1 - "Source Timecode aligned", 2 - "Start Frames aligned". - GetCurrentTimecode() --> string # Returns a string timecode representation for the current playhead position, while on Cut, Edit, Color and Deliver pages. + GetCurrentTimecode() --> string # Returns a string timecode representation for the current playhead position, while on Cut, Edit, Color, Fairlight and Deliver pages. + SetCurrentTimecode(timecode) --> Bool # Sets current playhead position from input timecode for Cut, Edit, Color, Fairlight and Deliver pages. GetCurrentVideoItem() --> item # Returns the current video timeline item. GetCurrentClipThumbnailImage() --> {thumbnailData} # Returns a dict (keys "width", "height", "format" and "data") with data containing raw thumbnail image data (RGB 8-bit image data encoded in base64 format) for current media in the Color Page. # An example of how to retrieve and interpret thumbnails is provided in 6_get_current_media_thumbnail.py in the Examples folder. @@ -280,37 +289,30 @@ Timeline DuplicateTimeline(timelineName) --> timeline # Duplicates the timeline and returns the created timeline, with the (optional) timelineName, on success. CreateCompoundClip([timelineItems], {clipInfo}) --> timelineItem # Creates a compound clip of input timeline items with an optional clipInfo map: {"startTimecode" : "00:00:00:00", "name" : "Compound Clip 1"}. It returns the created timeline item. CreateFusionClip([timelineItems]) --> timelineItem # Creates a Fusion clip of input timeline items. It returns the created timeline item. + ImportIntoTimeline(filePath, {importOptions}) --> Bool # Imports timeline items from an AAF file and optional importOptions dict into the timeline, with support for the keys: + # "autoImportSourceClipsIntoMediaPool": Bool, specifies if source clips should be imported into media pool, True by default + # "ignoreFileExtensionsWhenMatching": Bool, specifies if file extensions should be ignored when matching, False by default + # "linkToSourceCameraFiles": Bool, specifies if link to source camera files should be enabled, False by default + # "useSizingInfo": Bool, specifies if sizing information should be used, False by default + # "importMultiChannelAudioTracksAsLinkedGroups": Bool, specifies if multi-channel audio tracks should be imported as linked groups, False by default + # "insertAdditionalTracks": Bool, specifies if additional tracks should be inserted, True by default + # "insertWithOffset": string, specifies insert with offset value in timecode format - defaults to "00:00:00:00", applicable if "insertAdditionalTracks" is False + # "sourceClipsPath": string, specifies a filesystem path to search for source clips if the media is inaccessible in their original path and if "ignoreFileExtensionsWhenMatching" is True + # "sourceClipsFolders": string, list of Media Pool folder objects to search for source clips if the media is not present in current folder + Export(fileName, exportType, exportSubtype) --> Bool # Exports timeline to 'fileName' as per input exportType & exportSubtype format. - # exportType can be one of the following constants: - # resolve.EXPORT_AAF - # resolve.EXPORT_DRT - # resolve.EXPORT_EDL - # resolve.EXPORT_FCP_7_XML - # resolve.EXPORT_FCPXML_1_3 - # resolve.EXPORT_FCPXML_1_4 - # resolve.EXPORT_FCPXML_1_5 - # resolve.EXPORT_FCPXML_1_6 - # resolve.EXPORT_FCPXML_1_7 - # resolve.EXPORT_FCPXML_1_8 - # resolve.EXPORT_HDR_10_PROFILE_A - # resolve.EXPORT_HDR_10_PROFILE_B - # resolve.EXPORT_TEXT_CSV - # resolve.EXPORT_TEXT_TAB - # resolve.EXPORT_DOLBY_VISION_VER_2_9 - # resolve.EXPORT_DOLBY_VISION_VER_4_0 - # exportSubtype can be one of the following enums: - # resolve.EXPORT_NONE - # resolve.EXPORT_AAF_NEW - # resolve.EXPORT_AAF_EXISTING - # resolve.EXPORT_CDL - # resolve.EXPORT_SDL - # resolve.EXPORT_MISSING_CLIPS - # Please note that exportSubType is a required parameter for resolve.EXPORT_AAF and resolve.EXPORT_EDL. For rest of the exportType, exportSubtype is ignored. - # When exportType is resolve.EXPORT_AAF, valid exportSubtype values are resolve.EXPORT_AAF_NEW and resolve.EXPORT_AAF_EXISTING. - # When exportType is resolve.EXPORT_EDL, valid exportSubtype values are resolve.EXPORT_CDL, resolve.EXPORT_SDL, resolve.EXPORT_MISSING_CLIPS and resolve.EXPORT_NONE. - # Note: Replace 'resolve.' when using the constants above, if a different Resolve class instance name is used. + # Refer to section "Looking up timeline exports properties" for information on the parameters. GetSetting(settingName) --> string # Returns value of timeline setting (indicated by settingName : string). Check the section below for more information. SetSetting(settingName, settingValue) --> Bool # Sets timeline setting (indicated by settingName : string) to the value (settingValue : string). Check the section below for more information. + InsertGeneratorIntoTimeline(generatorName) --> TimelineItem # Inserts a generator (indicated by generatorName : string) into the timeline. + InsertFusionGeneratorIntoTimeline(generatorName) --> TimelineItem # Inserts a Fusion generator (indicated by generatorName : string) into the timeline. + InsertFusionCompositionIntoTimeline() --> TimelineItem # Inserts a Fusion composition into the timeline. + InsertOFXGeneratorIntoTimeline(generatorName) --> TimelineItem # Inserts an OFX generator (indicated by generatorName : string) into the timeline. + InsertTitleIntoTimeline(titleName) --> TimelineItem # Inserts a title (indicated by titleName : string) into the timeline. + InsertFusionTitleIntoTimeline(titleName) --> TimelineItem # Inserts a Fusion title (indicated by titleName : string) into the timeline. + GrabStill() --> galleryStill # Grabs still from the current video clip. Returns a GalleryStill object. + GrabAllStills(stillFrameSource) --> [galleryStill] # Grabs stills from all the clips of the timeline at 'stillFrameSource' (1 - First frame, 2 - Middle frame). Returns the list of GalleryStill objects. + GetUniqueId() --> string # Returns a unique ID for the timeline TimelineItem GetName() --> string # Returns the item name. @@ -323,6 +325,10 @@ TimelineItem GetLeftOffset() --> int # Returns the maximum extension by frame for clip from left side. GetRightOffset() --> int # Returns the maximum extension by frame for clip from right side. GetStart() --> int # Returns the start frame position on the timeline. + SetProperty(propertyKey, propertyValue) --> Bool # Sets the value of property "propertyKey" to value "propertyValue" + # Refer to "Looking up Timeline item properties" for more information + GetProperty(propertyKey) --> int/[key:value] # returns the value of the specified key + # if no key is specified, the method returns a dictionary(python) or table(lua) for all supported keys AddMarker(frameId, color, name, note, duration, --> Bool # Creates a new marker at given frameId position and with given marker information. 'customData' is optional and helps to attach user specific data to the marker. customData) GetMarkers() --> {markers...} # Returns a dict (frameId -> {information}) of all markers and dicts with their information. @@ -345,7 +351,8 @@ TimelineItem DeleteFusionCompByName(compName) --> Bool # Deletes the named Fusion composition. LoadFusionCompByName(compName) --> fusionComp # Loads the named Fusion composition as the active composition. RenameFusionCompByName(oldName, newName) --> Bool # Renames the Fusion composition identified by oldName. - AddVersion(versionName, versionType) --> Bool # Adds a new color version for a video clipbased on versionType (0 - local, 1 - remote). + AddVersion(versionName, versionType) --> Bool # Adds a new color version for a video clip based on versionType (0 - local, 1 - remote). + GetCurrentVersion() --> {versionName...} # Returns the current version of the video clip. The returned value will have the keys versionName and versionType(0 - local, 1 - remote). DeleteVersionByName(versionName, versionType) --> Bool # Deletes a color version by name and versionType (0 - local, 1 - remote). LoadVersionByName(versionName, versionType) --> Bool # Loads a named color version as the active version. versionType: 0 - local, 1 - remote. RenameVersionByName(oldName, newName, versionType)--> Bool # Renames the color version identified by oldName and versionType (0 - local, 1 - remote). @@ -354,12 +361,14 @@ TimelineItem GetStereoConvergenceValues() --> {keyframes...} # Returns a dict (offset -> value) of keyframe offsets and respective convergence values. GetStereoLeftFloatingWindowParams() --> {keyframes...} # For the LEFT eye -> returns a dict (offset -> dict) of keyframe offsets and respective floating window params. Value at particular offset includes the left, right, top and bottom floating window values. GetStereoRightFloatingWindowParams() --> {keyframes...} # For the RIGHT eye -> returns a dict (offset -> dict) of keyframe offsets and respective floating window params. Value at particular offset includes the left, right, top and bottom floating window values. + GetNumNodes() --> int # Returns the number of nodes in the current graph for the timeline item SetLUT(nodeIndex, lutPath) --> Bool # Sets LUT on the node mapping the node index provided, 1 <= nodeIndex <= total number of nodes. # The lutPath can be an absolute path, or a relative path (based off custom LUT paths or the master LUT path). # The operation is successful for valid lut paths that Resolve has already discovered (see Project.RefreshLUTList). + GetLUT(nodeIndex) --> String # Gets relative LUT path based on the node index provided, 1 <= nodeIndex <= total number of nodes. SetCDL([CDL map]) --> Bool # Keys of map are: "NodeIndex", "Slope", "Offset", "Power", "Saturation", where 1 <= NodeIndex <= total number of nodes. # Example python code - SetCDL({"NodeIndex" : "1", "Slope" : "0.5 0.4 0.2", "Offset" : "0.4 0.3 0.2", "Power" : "0.6 0.7 0.8", "Saturation" : "0.65"}) - AddTake(mediaPoolItem, startFrame=0, endFrame)=0 --> Bool # Adds mediaPoolItem as a new take. Initializes a take selector for the timeline item if needed. By default, the whole clip is added. startFrame and endFrame can be specified as extents. + AddTake(mediaPoolItem, startFrame, endFrame) --> Bool # Adds mediaPoolItem as a new take. Initializes a take selector for the timeline item if needed. By default, the full clip extents is added. startFrame (int) and endFrame (int) are optional arguments used to specify the extents. GetSelectedTakeIndex() --> int # Returns the index of the currently selected take, or 0 if the clip is not a take selector. GetTakesCount() --> int # Returns the number of takes in take selector, or 0 if the clip is not a take selector. GetTakeByIndex(idx) --> {takeInfo...} # Returns a dict (keys "startFrame", "endFrame" and "mediaPoolItem") with take info for specified index. @@ -367,7 +376,24 @@ TimelineItem SelectTakeByIndex(idx) --> Bool # Selects a take by index, 1 <= idx <= number of takes. FinalizeTake() --> Bool # Finalizes take selection. CopyGrades([tgtTimelineItems]) --> Bool # Copies the current grade to all the items in tgtTimelineItems list. Returns True on success and False if any error occurred. + UpdateSidecar() --> Bool # Updates sidecar file for BRAW clips or RMD file for R3D clips. + GetUniqueId() --> string # Returns a unique ID for the timeline item +Gallery + GetAlbumName(galleryStillAlbum) --> string # Returns the name of the GalleryStillAlbum object 'galleryStillAlbum'. + SetAlbumName(galleryStillAlbum, albumName) --> Bool # Sets the name of the GalleryStillAlbum object 'galleryStillAlbum' to 'albumName'. + GetCurrentStillAlbum() --> galleryStillAlbum # Returns current album as a GalleryStillAlbum object. + SetCurrentStillAlbum(galleryStillAlbum) --> Bool # Sets current album to GalleryStillAlbum object 'galleryStillAlbum'. + GetGalleryStillAlbums() --> [galleryStillAlbum] # Returns the gallery albums as a list of GalleryStillAlbum objects. + +GalleryStillAlbum + GetStills() --> [galleryStill] # Returns the list of GalleryStill objects in the album. + GetLabel(galleryStill) --> string # Returns the label of the galleryStill. + SetLabel(galleryStill, label) --> Bool # Sets the new 'label' to GalleryStill object 'galleryStill'. + ExportStills([galleryStill], folderPath, filePrefix, format) --> Bool # Exports list of GalleryStill objects '[galleryStill]' to directory 'folderPath', with filename prefix 'filePrefix', using file format 'format' (supported formats: dpx, cin, tif, jpg, png, ppm, bmp, xpm). + DeleteStills([galleryStill]) --> Bool # Deletes specified list of GalleryStill objects '[galleryStill]'. + +GalleryStill # This class does not provide any API functions but the object type is used by functions in other classes. List and Dict Data Structures ----------------------------- @@ -375,7 +401,6 @@ Beside primitive data types, Resolve's Python API mainly uses list and dict data As Lua does not support list and dict data structures, the Lua API implements "list" as a table with indices, e.g. { [1] = listValue1, [2] = listValue2, ... }. Similarly the Lua API implements "dict" as a table with the dictionary key as first element, e.g. { [dictKey1] = dictValue1, [dictKey2] = dictValue2, ... }. - Looking up Project and Clip properties -------------------------------------- This section covers additional notes for the functions "Project:GetSetting", "Project:SetSetting", "Timeline:GetSetting", "Timeline:SetSetting", "MediaPoolItem:GetClipProperty" and @@ -412,6 +437,179 @@ Affects: β€’ x = MediaPoolItem:GetClipProperty('Super Scale') and MediaPoolItem:SetClipProperty('Super Scale', x) +Looking up Render Settings +-------------------------- +This section covers the supported settings for the method SetRenderSettings({settings}) + +The parameter setting is a dictionary containing the following keys: + - "SelectAllFrames": Bool (when set True, the settings MarkIn and MarkOut are ignored) + - "MarkIn": int + - "MarkOut": int + - "TargetDir": string + - "CustomName": string + - "UniqueFilenameStyle": 0 - Prefix, 1 - Suffix. + - "ExportVideo": Bool + - "ExportAudio": Bool + - "FormatWidth": int + - "FormatHeight": int + - "FrameRate": float (examples: 23.976, 24) + - "PixelAspectRatio": string (for SD resolution: "16_9" or "4_3") (other resolutions: "square" or "cinemascope") + - "VideoQuality" possible values for current codec (if applicable): + - 0 (int) - will set quality to automatic + - [1 -> MAX] (int) - will set input bit rate + - ["Least", "Low", "Medium", "High", "Best"] (String) - will set input quality level + - "AudioCodec": string (example: "aac") + - "AudioBitDepth": int + - "AudioSampleRate": int + - "ColorSpaceTag" : string (example: "Same as Project", "AstroDesign") + - "GammaTag" : string (example: "Same as Project", "ACEScct") + - "ExportAlpha": Bool + - "EncodingProfile": string (example: "Main10"). Can only be set for H.264 and H.265. + - "MultiPassEncode": Bool. Can only be set for H.264. + - "AlphaMode": 0 - Premultiplied, 1 - Straight. Can only be set if "ExportAlpha" is true. + - "NetworkOptimization": Bool. Only supported by QuickTime and MP4 formats. + +Looking up timeline export properties +------------------------------------- +This section covers the parameters for the argument Export(fileName, exportType, exportSubtype). + +exportType can be one of the following constants: + - resolve.EXPORT_AAF + - resolve.EXPORT_DRT + - resolve.EXPORT_EDL + - resolve.EXPORT_FCP_7_XML + - resolve.EXPORT_FCPXML_1_3 + - resolve.EXPORT_FCPXML_1_4 + - resolve.EXPORT_FCPXML_1_5 + - resolve.EXPORT_FCPXML_1_6 + - resolve.EXPORT_FCPXML_1_7 + - resolve.EXPORT_FCPXML_1_8 + - resolve.EXPORT_FCPXML_1_9 + - resolve.EXPORT_FCPXML_1_10 + - resolve.EXPORT_HDR_10_PROFILE_A + - resolve.EXPORT_HDR_10_PROFILE_B + - resolve.EXPORT_TEXT_CSV + - resolve.EXPORT_TEXT_TAB + - resolve.EXPORT_DOLBY_VISION_VER_2_9 + - resolve.EXPORT_DOLBY_VISION_VER_4_0 +exportSubtype can be one of the following enums: + - resolve.EXPORT_NONE + - resolve.EXPORT_AAF_NEW + - resolve.EXPORT_AAF_EXISTING + - resolve.EXPORT_CDL + - resolve.EXPORT_SDL + - resolve.EXPORT_MISSING_CLIPS +Please note that exportSubType is a required parameter for resolve.EXPORT_AAF and resolve.EXPORT_EDL. For rest of the exportType, exportSubtype is ignored. +When exportType is resolve.EXPORT_AAF, valid exportSubtype values are resolve.EXPORT_AAF_NEW and resolve.EXPORT_AAF_EXISTING. +When exportType is resolve.EXPORT_EDL, valid exportSubtype values are resolve.EXPORT_CDL, resolve.EXPORT_SDL, resolve.EXPORT_MISSING_CLIPS and resolve.EXPORT_NONE. +Note: Replace 'resolve.' when using the constants above, if a different Resolve class instance name is used. + +Looking up Timeline item properties +----------------------------------- +This section covers additional notes for the function "TimelineItem:SetProperty" and "TimelineItem:GetProperty". These functions are used to get and set properties mentioned. + +The supported keys with their accepted values are: + "Pan" : floating point values from -4.0*width to 4.0*width + "Tilt" : floating point values from -4.0*height to 4.0*height + "ZoomX" : floating point values from 0.0 to 100.0 + "ZoomY" : floating point values from 0.0 to 100.0 + "ZoomGang" : a boolean value + "RotationAngle" : floating point values from -360.0 to 360.0 + "AnchorPointX" : floating point values from -4.0*width to 4.0*width + "AnchorPointY" : floating point values from -4.0*height to 4.0*height + "Pitch" : floating point values from -1.5 to 1.5 + "Yaw" : floating point values from -1.5 to 1.5 + "FlipX" : boolean value for flipping horizontally + "FlipY" : boolean value for flipping vertically + "CropLeft" : floating point values from 0.0 to width + "CropRight" : floating point values from 0.0 to width + "CropTop" : floating point values from 0.0 to height + "CropBottom" : floating point values from 0.0 to height + "CropSoftness" : floating point values from -100.0 to 100.0 + "CropRetain" : boolean value for "Retain Image Position" checkbox + "DynamicZoomEase" : A value from the following constants + - DYNAMIC_ZOOM_EASE_LINEAR = 0 + - DYNAMIC_ZOOM_EASE_IN + - DYNAMIC_ZOOM_EASE_OUT + - DYNAMIC_ZOOM_EASE_IN_AND_OUT + "CompositeMode" : A value from the following constants + - COMPOSITE_NORMAL = 0 + - COMPOSITE_ADD + - COMPOSITE_SUBTRACT + - COMPOSITE_DIFF + - COMPOSITE_MULTIPLY + - COMPOSITE_SCREEN + - COMPOSITE_OVERLAY + - COMPOSITE_HARDLIGHT + - COMPOSITE_SOFTLIGHT + - COMPOSITE_DARKEN + - COMPOSITE_LIGHTEN + - COMPOSITE_COLOR_DODGE + - COMPOSITE_COLOR_BURN + - COMPOSITE_EXCLUSION + - COMPOSITE_HUE + - COMPOSITE_SATURATE + - COMPOSITE_COLORIZE + - COMPOSITE_LUMA_MASK + - COMPOSITE_DIVIDE + - COMPOSITE_LINEAR_DODGE + - COMPOSITE_LINEAR_BURN + - COMPOSITE_LINEAR_LIGHT + - COMPOSITE_VIVID_LIGHT + - COMPOSITE_PIN_LIGHT + - COMPOSITE_HARD_MIX + - COMPOSITE_LIGHTER_COLOR + - COMPOSITE_DARKER_COLOR + - COMPOSITE_FOREGROUND + - COMPOSITE_ALPHA + - COMPOSITE_INVERTED_ALPHA + - COMPOSITE_LUM + - COMPOSITE_INVERTED_LUM + "Opacity" : floating point value from 0.0 to 100.0 + "Distortion" : floating point value from -1.0 to 1.0 + "RetimeProcess" : A value from the following constants + - RETIME_USE_PROJECT = 0 + - RETIME_NEAREST + - RETIME_FRAME_BLEND + - RETIME_OPTICAL_FLOW + "MotionEstimation" : A value from the following constants + - MOTION_EST_USE_PROJECT = 0 + - MOTION_EST_STANDARD_FASTER + - MOTION_EST_STANDARD_BETTER + - MOTION_EST_ENHANCED_FASTER + - MOTION_EST_ENHANCED_BETTER + - MOTION_EST_SPEED_WRAP + "Scaling" : A value from the following constants + - SCALE_USE_PROJECT = 0 + - SCALE_CROP + - SCALE_FIT + - SCALE_FILL + - SCALE_STRETCH + "ResizeFilter" : A value from the following constants + - RESIZE_FILTER_USE_PROJECT = 0 + - RESIZE_FILTER_SHARPER + - RESIZE_FILTER_SMOOTHER + - RESIZE_FILTER_BICUBIC + - RESIZE_FILTER_BILINEAR + - RESIZE_FILTER_BESSEL + - RESIZE_FILTER_BOX + - RESIZE_FILTER_CATMULL_ROM + - RESIZE_FILTER_CUBIC + - RESIZE_FILTER_GAUSSIAN + - RESIZE_FILTER_LANCZOS + - RESIZE_FILTER_MITCHELL + - RESIZE_FILTER_NEAREST_NEIGHBOR + - RESIZE_FILTER_QUADRATIC + - RESIZE_FILTER_SINC + - RESIZE_FILTER_LINEAR +Values beyond the range will be clipped +width and height are same as the UI max limits + +The arguments can be passed as a key and value pair or they can be grouped together into a dictionary (for python) or table (for lua) and passed +as a single argument. + +Getting the values for the keys that uses constants will return the number which is in the constant + Deprecated Resolve API Functions -------------------------------- The following API functions are deprecated. @@ -450,12 +648,12 @@ TimelineItem Unsupported Resolve API Functions --------------------------------- -The following API (functions and paraameters) are no longer supported. +The following API (functions and parameters) are no longer supported. Use job IDs instead of indices. Project StartRendering(index1, index2, ...) --> Bool # Please use unique job ids (string) instead of indices. StartRendering([idxs...]) --> Bool # Please use unique job ids (string) instead of indices. DeleteRenderJobByIndex(idx) --> Bool # Please use unique job ids (string) instead of indices. GetRenderJobStatus(idx) --> {status info} # Please use unique job ids (string) instead of indices. - GetSetting and SetSetting --> {} # settingName "videoMonitorUseRec601For422SDI" is no longer supported. - # Please use "videoMonitorUseMatrixOverrideFor422SDI" and "videoMonitorMatrixOverrideFor422SDI" instead. + GetSetting and SetSetting --> {} # settingName videoMonitorUseRec601For422SDI is now replaced with videoMonitorUseMatrixOverrideFor422SDI and videoMonitorMatrixOverrideFor422SDI. + # settingName perfProxyMediaOn is now replaced with perfProxyMediaMode which takes values 0 - disabled, 1 - when available, 2 - when source not available. From ca71cbc4d03fd289fecd2f4355c79138520da49b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 14 Oct 2022 15:13:26 +0200 Subject: [PATCH 143/201] resolve: readme update --- openpype/hosts/resolve/README.markdown | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/resolve/README.markdown b/openpype/hosts/resolve/README.markdown index 38db8a8004..a8bb071e7e 100644 --- a/openpype/hosts/resolve/README.markdown +++ b/openpype/hosts/resolve/README.markdown @@ -1,4 +1,4 @@ -#### Basic setup +## Basic setup - Actually supported version is up to v18 - install Python 3.6.2 (latest tested v17) or up to 3.9.13 (latest tested on v18) @@ -6,13 +6,13 @@ - Python 3.9.*: open terminal and go to python.exe directory, then `python -m pip install PySide2` - pip install OpenTimelineIO: - Python 3.9.*: open terminal and go to python.exe directory, then `python -m pip install OpenTimelineIO` - - Python 3.6: open terminal and go to python.exe directory, then `python -m pip install git+https://github.com/PixarAnimationStudios/OpenTimelineIO.git@5aa24fbe89d615448876948fe4b4900455c9a3e8` and move built files from `%LOCALAPPDATA%/Programs/Python/Python36/Lib/site-packages/opentimelineio/cxx-libs/bin and lib` to `/Lib/site-packages/opentimelineio/`. I was building it on Win10 machine with Visual Studio Community 2019 and + - Python 3.6: open terminal and go to python.exe directory, then `python -m pip install git+https://github.com/PixarAnimationStudios/OpenTimelineIO.git@5aa24fbe89d615448876948fe4b4900455c9a3e8` and move built files from `./Lib/site-packages/opentimelineio/cxx-libs/bin and lib` to `./Lib/site-packages/opentimelineio/`. I was building it on Win10 machine with Visual Studio Community 2019 and ![image](https://user-images.githubusercontent.com/40640033/102792588-ffcb1c80-43a8-11eb-9c6b-bf2114ed578e.png) with installed CMake in PATH. - make sure Resolve Fusion (Fusion Tab/menu/Fusion/Fusion Settings) is set to Python 3.6 ![image](https://user-images.githubusercontent.com/40640033/102631545-280b0f00-414e-11eb-89fc-98ac268d209d.png) - Open OpenPype **Tray/Admin/Studio settings** > `applications/resolve/environment` and add Python3 path to `RESOLVE_PYTHON3_HOME` platform related. -#### Editorial setup +## Editorial setup This is how it looks on my testing project timeline ![image](https://user-images.githubusercontent.com/40640033/102637638-96ec6600-4156-11eb-9656-6e8e3ce4baf8.png) From e5a36df6606cee975fff95f0c2a8e87287ae4a90 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 14 Oct 2022 15:22:08 +0200 Subject: [PATCH 144/201] resolve: rename Inventory to Manager --- openpype/hosts/resolve/api/menu.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/resolve/api/menu.py b/openpype/hosts/resolve/api/menu.py index 2c7678ee5b..86b292105a 100644 --- a/openpype/hosts/resolve/api/menu.py +++ b/openpype/hosts/resolve/api/menu.py @@ -54,15 +54,15 @@ class OpenPypeMenu(QtWidgets.QWidget): ) self.setWindowTitle("OpenPype") - workfiles_btn = QtWidgets.QPushButton("Workfiles...", self) - create_btn = QtWidgets.QPushButton("Create...", self) - publish_btn = QtWidgets.QPushButton("Publish...", self) - load_btn = QtWidgets.QPushButton("Load...", self) - inventory_btn = QtWidgets.QPushButton("Inventory...", self) - subsetm_btn = QtWidgets.QPushButton("Subset Manager...", self) - libload_btn = QtWidgets.QPushButton("Library...", self) + workfiles_btn = QtWidgets.QPushButton("Workfiles ...", self) + create_btn = QtWidgets.QPushButton("Create ...", self) + publish_btn = QtWidgets.QPushButton("Publish ...", self) + load_btn = QtWidgets.QPushButton("Load ...", self) + inventory_btn = QtWidgets.QPushButton("Manager ...", self) + subsetm_btn = QtWidgets.QPushButton("Subset Manager ...", self) + libload_btn = QtWidgets.QPushButton("Library ...", self) experimental_btn = QtWidgets.QPushButton( - "Experimental tools...", self + "Experimental tools ...", self ) # rename_btn = QtWidgets.QPushButton("Rename", self) # set_colorspace_btn = QtWidgets.QPushButton( From 45536f613d6a9414830cf0d8dff99296b82c570a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Fri, 14 Oct 2022 16:28:48 +0200 Subject: [PATCH 145/201] :sparkles: add originalBasename data to Tray Publisher --- .../traypublisher/plugins/publish/collect_simple_instances.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py b/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py index c0ae694c3c..0ccef3f375 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py @@ -1,5 +1,6 @@ import os import tempfile +from pathlib import Path import clique import pyblish.api @@ -72,6 +73,8 @@ class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin): instance.data["source"] = source instance.data["sourceFilepaths"] = list(set(source_filepaths)) + instance.data["originalBasename"] = Path( + instance.data["sourceFilepaths"][0]).stem self.log.debug( ( From 4e019e1eef3b18a88ab4ba3748fa76aa8e6faf44 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 14 Oct 2022 16:55:58 +0200 Subject: [PATCH 146/201] fix typo --- openpype/tools/publisher/widgets/validations_widget.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/publisher/widgets/validations_widget.py b/openpype/tools/publisher/widgets/validations_widget.py index f35d286e88..a24797de3e 100644 --- a/openpype/tools/publisher/widgets/validations_widget.py +++ b/openpype/tools/publisher/widgets/validations_widget.py @@ -651,7 +651,7 @@ class ValidationsWidget(QtWidgets.QFrame): self.set_errors(validation_errors) return - if self._contoller.publish_has_finished: + if self._controller.publish_has_finished: self._set_current_widget(self._publish_stop_ok_widget) return From 5009a37b73412c76f3d15d8a9a5ec60911af7149 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 14 Oct 2022 18:57:37 +0200 Subject: [PATCH 147/201] return instance ids instead of instance objects --- .../publisher/widgets/card_view_widgets.py | 2 +- .../publisher/widgets/list_view_widgets.py | 17 +++++++--------- .../publisher/widgets/overview_widget.py | 20 ++++++++++--------- 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/openpype/tools/publisher/widgets/card_view_widgets.py b/openpype/tools/publisher/widgets/card_view_widgets.py index 2be37ea44c..4c7d6ce109 100644 --- a/openpype/tools/publisher/widgets/card_view_widgets.py +++ b/openpype/tools/publisher/widgets/card_view_widgets.py @@ -541,6 +541,6 @@ class InstanceCardView(AbstractInstanceView): context_selected = True elif selected_widget is not None: - instances.append(selected_widget.instance) + instances.append(selected_widget.instance.id) return instances, context_selected diff --git a/openpype/tools/publisher/widgets/list_view_widgets.py b/openpype/tools/publisher/widgets/list_view_widgets.py index 17b50b764a..6d90e63683 100644 --- a/openpype/tools/publisher/widgets/list_view_widgets.py +++ b/openpype/tools/publisher/widgets/list_view_widgets.py @@ -723,13 +723,13 @@ class InstanceListView(AbstractInstanceView): widget.update_instance_values() def _on_active_changed(self, changed_instance_id, new_value): - selected_instances, _ = self.get_selected_items() + selected_instance_ids, _ = self.get_selected_items() selected_ids = set() found = False - for instance in selected_instances: - selected_ids.add(instance.id) - if not found and instance.id == changed_instance_id: + for instance_id in selected_instance_ids: + selected_ids.add(instance_id) + if not found and instance_id == changed_instance_id: found = True if not found: @@ -767,9 +767,8 @@ class InstanceListView(AbstractInstanceView): tuple: Selected instance ids and boolean if context is selected. """ - instances = [] + instance_ids = [] context_selected = False - instances_by_id = self._controller.instances for index in self._instance_view.selectionModel().selectedIndexes(): instance_id = index.data(INSTANCE_ID_ROLE) @@ -777,11 +776,9 @@ class InstanceListView(AbstractInstanceView): context_selected = True elif instance_id is not None: - instance = instances_by_id.get(instance_id) - if instance: - instances.append(instance) + instance_ids.append(instance_id) - return instances, context_selected + return instance_ids, context_selected def _on_selection_change(self, *_args): self.selection_changed.emit() diff --git a/openpype/tools/publisher/widgets/overview_widget.py b/openpype/tools/publisher/widgets/overview_widget.py index 3c67e6298e..8759d2ad49 100644 --- a/openpype/tools/publisher/widgets/overview_widget.py +++ b/openpype/tools/publisher/widgets/overview_widget.py @@ -201,16 +201,16 @@ class OverviewWidget(QtWidgets.QFrame): self.create_requested.emit() def _on_delete_clicked(self): - instances, _ = self.get_selected_items() + instance_ids, _ = self.get_selected_items() # Ask user if he really wants to remove instances dialog = QtWidgets.QMessageBox(self) dialog.setIcon(QtWidgets.QMessageBox.Question) dialog.setWindowTitle("Are you sure?") - if len(instances) > 1: + if len(instance_ids) > 1: msg = ( "Do you really want to remove {} instances?" - ).format(len(instances)) + ).format(len(instance_ids)) else: msg = ( "Do you really want to remove the instance?" @@ -224,10 +224,7 @@ class OverviewWidget(QtWidgets.QFrame): dialog.exec_() # Skip if OK was not clicked if dialog.result() == QtWidgets.QMessageBox.Ok: - instance_ids = { - instance.id - for instance in instances - } + instance_ids = set(instance_ids) self._controller.remove_instances(instance_ids) def _on_change_view_clicked(self): @@ -238,11 +235,16 @@ class OverviewWidget(QtWidgets.QFrame): if self._refreshing_instances: return - instances, context_selected = self.get_selected_items() + instance_ids, context_selected = self.get_selected_items() # Disable delete button if nothing is selected - self._delete_btn.setEnabled(len(instances) > 0) + self._delete_btn.setEnabled(len(instance_ids) > 0) + instances_by_id = self._controller.instances + instances = [ + instances_by_id[instance_id] + for instance_id in instance_ids + ] self._subset_attributes_widget.set_current_instances( instances, context_selected ) From 804a92384f86df6fded616abf45061f7144761d5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 14 Oct 2022 19:00:08 +0200 Subject: [PATCH 148/201] pass type of selection from clicked widget --- .../publisher/widgets/card_view_widgets.py | 40 ++++++++++++++++--- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/openpype/tools/publisher/widgets/card_view_widgets.py b/openpype/tools/publisher/widgets/card_view_widgets.py index 4c7d6ce109..28db844303 100644 --- a/openpype/tools/publisher/widgets/card_view_widgets.py +++ b/openpype/tools/publisher/widgets/card_view_widgets.py @@ -41,9 +41,26 @@ from ..constants import ( ) +class SelectionType: + def __init__(self, name): + self.name = name + + def __eq__(self, other): + if isinstance(other, SelectionType): + other = other.name + return self.name == other + + +class SelectionTypes: + clear = SelectionType("clear") + extend = SelectionType("extend") + extend_to = SelectionType("extend_to") + + class GroupWidget(QtWidgets.QWidget): """Widget wrapping instances under group.""" - selected = QtCore.Signal(str, str) + + selected = QtCore.Signal(str, str, SelectionType) active_changed = QtCore.Signal() removed_selected = QtCore.Signal() @@ -135,17 +152,21 @@ class GroupWidget(QtWidgets.QWidget): widget = InstanceCardWidget( instance, group_icon, self ) - widget.selected.connect(self.selected) + widget.selected.connect(self._on_widget_selection) widget.active_changed.connect(self.active_changed) self._widgets_by_id[instance.id] = widget self._content_layout.insertWidget(widget_idx, widget) widget_idx += 1 + def _on_widget_selection(self, instance_id, group_id, selection_type): + self.selected.emit(instance_id, group_id, selection_type) + + class CardWidget(BaseClickableFrame): """Clickable card used as bigger button.""" - selected = QtCore.Signal(str, str) + selected = QtCore.Signal(str, str, SelectionType) # Group identifier of card # - this must be set because if send when mouse is released with card id _group_identifier = None @@ -173,7 +194,16 @@ class CardWidget(BaseClickableFrame): def _mouse_release_callback(self): """Trigger selected signal.""" - self.selected.emit(self._id, self._group_identifier) + + modifiers = QtWidgets.QApplication.keyboardModifiers() + selection_type = SelectionTypes.clear + if bool(modifiers & QtCore.Qt.ShiftModifier): + selection_type = SelectionTypes.extend_to + + elif bool(modifiers & QtCore.Qt.ControlModifier): + selection_type = SelectionTypes.extend + + self.selected.emit(self._id, self._group_identifier, selection_type) class ContextCardWidget(CardWidget): @@ -498,7 +528,7 @@ class InstanceCardView(AbstractInstanceView): def _on_active_changed(self): self.active_changed.emit() - def _on_widget_selection(self, instance_id, group_name): + def _on_widget_selection(self, instance_id, group_name, selection_type): self.select_item(instance_id, group_name) def select_item(self, instance_id, group_name): From d20adf201ac7073ed3f4a7aa320ce220c6b34f5f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 14 Oct 2022 19:00:58 +0200 Subject: [PATCH 149/201] added additional helper attributes and methods --- .../publisher/widgets/card_view_widgets.py | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/openpype/tools/publisher/widgets/card_view_widgets.py b/openpype/tools/publisher/widgets/card_view_widgets.py index 28db844303..80a3bf0fb1 100644 --- a/openpype/tools/publisher/widgets/card_view_widgets.py +++ b/openpype/tools/publisher/widgets/card_view_widgets.py @@ -89,21 +89,73 @@ class GroupWidget(QtWidgets.QWidget): self._group_icons = group_icons self._widgets_by_id = {} + self._ordered_instance_ids = [] self._label_widget = label_widget self._content_layout = layout + @property + def group_name(self): + """Group which widget represent. + + Returns: + str: Name of group. + """ + + return self._group + + def get_selected_instance_ids(self): + """Selected instance ids. + + Returns: + Set[str]: Instance ids that are selected. + """ + + return { + instance_id + for instance_id, widget in self._widgets_by_id.items() + if widget.is_selected + } + + def get_selected_widgets(self): + """Access to widgets marked as selected. + + Returns: + List[InstanceCardWidget]: Instance widgets that are selected. + """ + + return [ + widget + for instance_id, widget in self._widgets_by_id.items() + if widget.is_selected + ] + + def get_ordered_widgets(self): + """Get instance ids in order as are shown in ui. + + Returns: + List[str]: Instance ids. + """ + + return [ + self._widgets_by_id[instance_id] + for instance_id in self._ordered_instance_ids + ] + def get_widget_by_instance_id(self, instance_id): """Get instance widget by it's id.""" + return self._widgets_by_id.get(instance_id) def update_instance_values(self): """Trigger update on instance widgets.""" + for widget in self._widgets_by_id.values(): widget.update_instance_values() def confirm_remove_instance_id(self, instance_id): """Delete widget by instance id.""" + widget = self._widgets_by_id.pop(instance_id) widget.setVisible(False) self._content_layout.removeWidget(widget) @@ -140,6 +192,7 @@ class GroupWidget(QtWidgets.QWidget): # Sort instances by subset name sorted_subset_names = list(sorted(instances_by_subset_name.keys())) + # Add new instances to widget widget_idx = 1 for subset_names in sorted_subset_names: @@ -158,6 +211,15 @@ class GroupWidget(QtWidgets.QWidget): self._content_layout.insertWidget(widget_idx, widget) widget_idx += 1 + ordered_instance_ids = [] + for idx in range(self._content_layout.count()): + if idx > 0: + item = self._content_layout.itemAt(idx) + widget = item.widget() + if widget is not None: + ordered_instance_ids.append(widget.id) + + self._ordered_instance_ids = ordered_instance_ids def _on_widget_selection(self, instance_id, group_id, selection_type): self.selected.emit(instance_id, group_id, selection_type) @@ -178,6 +240,12 @@ class CardWidget(BaseClickableFrame): self._selected = False self._id = None + @property + def id(self): + """Id of card.""" + + return self._id + @property def is_selected(self): """Is card selected.""" From 0c27f807955ec312fe0628b331b3ba8416be85c9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 14 Oct 2022 19:02:53 +0200 Subject: [PATCH 150/201] implemented logic to handle multiselection --- .../publisher/widgets/card_view_widgets.py | 355 +++++++++++++++--- 1 file changed, 308 insertions(+), 47 deletions(-) diff --git a/openpype/tools/publisher/widgets/card_view_widgets.py b/openpype/tools/publisher/widgets/card_view_widgets.py index 80a3bf0fb1..c0cc3389c7 100644 --- a/openpype/tools/publisher/widgets/card_view_widgets.py +++ b/openpype/tools/publisher/widgets/card_view_widgets.py @@ -480,11 +480,12 @@ class InstanceCardView(AbstractInstanceView): self._content_layout = content_layout self._content_widget = content_widget - self._widgets_by_group = {} self._context_widget = None + self._widgets_by_group = {} + self._ordered_groups = [] - self._selected_group = None - self._selected_instance_id = None + self._explicitly_selected_instance_ids = [] + self._explicitly_selected_groups = [] self.setSizePolicy( QtWidgets.QSizePolicy.Minimum, @@ -504,21 +505,30 @@ class InstanceCardView(AbstractInstanceView): result.setWidth(width) return result - def _get_selected_widget(self): - if self._selected_instance_id == CONTEXT_ID: - return self._context_widget + def _get_selected_widgets(self): + output = [] + if ( + self._context_widget is not None + and self._context_widget.is_selected + ): + output.append(self._context_widget) - group_widget = self._widgets_by_group.get( - self._selected_group - ) - if group_widget is not None: - widget = group_widget.get_widget_by_instance_id( - self._selected_instance_id - ) - if widget is not None: - return widget + for group_widget in self._widgets_by_group.values(): + for widget in group_widget.get_selected_widgets(): + output.append(widget) + return output - return None + def _get_selected_instance_ids(self): + output = [] + if ( + self._context_widget is not None + and self._context_widget.is_selected + ): + output.append(CONTEXT_ID) + + for group_widget in self._widgets_by_group.values(): + output.extend(group_widget.get_selected_instance_ids()) + return output def refresh(self): """Refresh instances in view based on CreatedContext.""" @@ -534,8 +544,6 @@ class InstanceCardView(AbstractInstanceView): self.selection_changed.emit() self._content_layout.insertWidget(0, widget) - self.select_item(CONTEXT_ID, None) - # Prepare instances by group and identifiers by group instances_by_group = collections.defaultdict(list) identifiers_by_group = collections.defaultdict(set) @@ -551,15 +559,17 @@ class InstanceCardView(AbstractInstanceView): if group_name in instances_by_group: continue - if group_name == self._selected_group: - self._on_remove_selected() widget = self._widgets_by_group.pop(group_name) widget.setVisible(False) self._content_layout.removeWidget(widget) widget.deleteLater() + if group_name in self._explicitly_selected_groups: + self._explicitly_selected_groups.remove(group_name) + # Sort groups sorted_group_names = list(sorted(instances_by_group.keys())) + # Keep track of widget indexes # - we start with 1 because Context item as at the top widget_idx = 1 @@ -577,9 +587,6 @@ class InstanceCardView(AbstractInstanceView): ) group_widget.active_changed.connect(self._on_active_changed) group_widget.selected.connect(self._on_widget_selection) - group_widget.removed_selected.connect( - self._on_remove_selected - ) self._content_layout.insertWidget(widget_idx, group_widget) self._widgets_by_group[group_name] = group_widget @@ -588,6 +595,16 @@ class InstanceCardView(AbstractInstanceView): instances_by_group[group_name] ) + ordered_group_names = [""] + for idx in range(self._content_layout.count()): + if idx > 0: + item = self._content_layout.itemAt(idx) + group_widget = item.widget() + if group_widget is not None: + ordered_group_names.append(group_widget.group_name) + + self._ordered_groups = ordered_group_names + def refresh_instance_states(self): """Trigger update of instances on group widgets.""" for widget in self._widgets_by_group.values(): @@ -597,9 +614,6 @@ class InstanceCardView(AbstractInstanceView): self.active_changed.emit() def _on_widget_selection(self, instance_id, group_name, selection_type): - self.select_item(instance_id, group_name) - - def select_item(self, instance_id, group_name): """Select specific item by instance id. Pass `CONTEXT_ID` as instance id and empty string as group to select @@ -611,34 +625,281 @@ class InstanceCardView(AbstractInstanceView): group_widget = self._widgets_by_group[group_name] new_widget = group_widget.get_widget_by_instance_id(instance_id) - selected_widget = self._get_selected_widget() - if new_widget is selected_widget: - return - - if selected_widget is not None: - selected_widget.set_selected(False) - - self._selected_instance_id = instance_id - self._selected_group = group_name - if new_widget is not None: - new_widget.set_selected(True) + if selection_type is SelectionTypes.clear: + self._select_item_clear(instance_id, group_name, new_widget) + elif selection_type is SelectionTypes.extend: + self._select_item_extend(instance_id, group_name, new_widget) + elif selection_type is SelectionTypes.extend_to: + self._select_item_extend_to(instance_id, group_name, new_widget) self.selection_changed.emit() - def _on_remove_selected(self): - selected_widget = self._get_selected_widget() - if selected_widget is None: - self._on_widget_selection(CONTEXT_ID, None) + def _select_item_clear(self, instance_id, group_name, new_widget): + """Select specific item by instance id and clear previous selection. + + Pass `CONTEXT_ID` as instance id and empty string as group to select + global context item. + """ + + selected_widgets = self._get_selected_widgets() + for widget in selected_widgets: + if widget.id != instance_id: + widget.set_selected(False) + + self._explicitly_selected_groups = [group_name] + self._explicitly_selected_instance_ids = [instance_id] + + if new_widget is not None: + new_widget.set_selected(True) + + def _select_item_extend(self, instance_id, group_name, new_widget): + """Add/Remove single item to/from current selection. + + If item is already selected the selection is removed. + """ + + self._explicitly_selected_instance_ids = ( + self._get_selected_instance_ids() + ) + if new_widget.is_selected: + self._explicitly_selected_instance_ids.remove(instance_id) + new_widget.set_selected(False) + remove_group = False + if instance_id == CONTEXT_ID: + remove_group = True + else: + group_widget = self._widgets_by_group[group_name] + if not group_widget.get_selected_widgets(): + remove_group = True + + if remove_group: + self._explicitly_selected_groups.remove(group_name) + return + + self._explicitly_selected_instance_ids.append(instance_id) + if group_name in self._explicitly_selected_groups: + self._explicitly_selected_groups.remove(group_name) + self._explicitly_selected_groups.append(group_name) + new_widget.set_selected(True) + + def _select_item_extend_to(self, instance_id, group_name, new_widget): + """Extend selected items to specific instance id. + + This method is handling Shift+click selection of widgets. Selection + is not stored to explicit selection items. That's because user can + shift select again and it should use last explicit selected item as + source item for selection. + + Items selected via this function can get to explicit selection only if + selection is extended by one specific item ('_select_item_extend'). + From that moment the selection is locked to new last explicit selected + item. + + It's required to traverse through group widgets in their UI order and + through their instances in UI order. All explicitly selected items + must not change their selection state during this function. Passed + instance id can be above or under last selected item so a start item + and end item must be found to be able know which direction is selection + happening. + """ + + # Start group name (in '_ordered_groups') + start_group = None + # End group name (in '_ordered_groups') + end_group = None + # Instance id of first selected item + start_instance_id = None + # Instance id of last selected item + end_instance_id = None + + # Get previously selected group by explicit selected groups + previous_group = None + if self._explicitly_selected_groups: + previous_group = self._explicitly_selected_groups[-1] + + # Find last explicitly selected instance id + previous_last_selected_id = None + if self._explicitly_selected_instance_ids: + previous_last_selected_id = ( + self._explicitly_selected_instance_ids[-1] + ) + + # If last instance id was not found or available then last selected + # group is also invalid. + # NOTE: This probably never happen? + if previous_last_selected_id is None: + previous_group = None + + # Check if previously selected group is available and find out if + # new instance group is above or under previous selection + # - based on these information are start/end group/instance filled + if previous_group in self._ordered_groups: + new_idx = self._ordered_groups.index(group_name) + prev_idx = self._ordered_groups.index(previous_group) + if new_idx < prev_idx: + start_group = group_name + end_group = previous_group + start_instance_id = instance_id + end_instance_id = previous_last_selected_id + else: + start_group = previous_group + end_group = group_name + start_instance_id = previous_last_selected_id + end_instance_id = instance_id + + # If start group is not set then use context item group name + if start_group is None: + start_group = "" + + # If start instance id is not filled then use context id (similar to + # group) + if start_instance_id is None: + start_instance_id = CONTEXT_ID + + # If end group is not defined then use passed group name + # - this can be happen when previous group was not selected + # - when this happens the selection will probably happen from context + # item to item selected by user + if end_group is None: + end_group = group_name + + # If end instance is not filled then use instance selected by user + if end_instance_id is None: + end_instance_id = instance_id + + # Start and end group are the same + # - a different logic is needed in that case + same_group = start_group == end_group + + # Process known information and change selection of items + passed_start_group = False + passed_end_group = False + # Go through ordered groups (from top to bottom) and change selection + for name in self._ordered_groups: + # Prepare sorted instance widgets + if name == "": + sorted_widgets = [self._context_widget] + else: + group_widget = self._widgets_by_group[name] + sorted_widgets = group_widget.get_ordered_widgets() + + # Change selection based on explicit selection if start group + # was not passed yet + if not passed_start_group: + if name != start_group: + for widget in sorted_widgets: + widget.set_selected( + widget.id in self._explicitly_selected_instance_ids + ) + continue + + # Change selection based on explicit selection if end group + # already passed + if passed_end_group: + for widget in sorted_widgets: + widget.set_selected( + widget.id in self._explicitly_selected_instance_ids + ) + continue + + # Start group is already passed and end group was not yet hit + if same_group: + passed_start_group = True + passed_end_group = True + passed_start_instance = False + passed_end_instance = False + for widget in sorted_widgets: + if not passed_start_instance: + if widget.id in (start_instance_id, end_instance_id): + if widget.id != start_instance_id: + # Swap start/end instance if start instance is + # after end + # - fix 'passed_end_instance' check + start_instance_id, end_instance_id = ( + end_instance_id, start_instance_id + ) + passed_start_instance = True + + # Find out if widget should be selected + select = False + if passed_end_instance: + select = False + + elif passed_start_instance: + select = True + + # Check if instance is in explicitly selected items if + # should ont be selected + if ( + not select + and widget.id in self._explicitly_selected_instance_ids + ): + select = True + + widget.set_selected(select) + + if ( + not passed_end_instance + and widget.id == end_instance_id + ): + passed_end_instance = True + + elif name == start_group: + # First group from which selection should start + # - look for start instance first from which the selection + # should happen + passed_start_group = True + passed_start_instance = False + for widget in sorted_widgets: + if widget.id == start_instance_id: + passed_start_instance = True + + select = False + # Check if passed start instance or instance is + # in explicitly selected items to be selected + if ( + passed_start_instance + or widget.id in self._explicitly_selected_instance_ids + ): + select = True + widget.set_selected(select) + + elif name == end_group: + # Last group where selection should happen + # - look for end instance first after which the selection + # should stop + passed_end_group = True + passed_end_instance = False + for widget in sorted_widgets: + select = False + # Check if not yet passed end instance or if instance is + # in explicitly selected items to be selected + if ( + not passed_end_instance + or widget.id in self._explicitly_selected_instance_ids + ): + select = True + + widget.set_selected(select) + + if widget.id == end_instance_id: + passed_end_instance = True + + else: + # Just select everything between start and end group + for widget in sorted_widgets: + widget.set_selected(True) def get_selected_items(self): """Get selected instance ids and context.""" instances = [] - context_selected = False - selected_widget = self._get_selected_widget() - if selected_widget is self._context_widget: - context_selected = True + selected_widgets = self._get_selected_widgets() - elif selected_widget is not None: - instances.append(selected_widget.instance.id) + context_selected = False + for widget in selected_widgets: + if widget is self._context_widget: + context_selected = True + else: + instances.append(widget.id) return instances, context_selected From 1b8dff405a6737c45e62617da2fba8ea4604b308 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sat, 15 Oct 2022 18:31:03 +0200 Subject: [PATCH 151/201] add process time to publish report --- openpype/tools/publisher/control.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index da320b1f39..9eff431171 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -3,6 +3,7 @@ import copy import logging import traceback import collections +import time from abc import ABCMeta, abstractmethod, abstractproperty import six @@ -232,15 +233,17 @@ class PublishReport: """Set that current plugin has been skipped.""" self._current_plugin_data["skipped"] = True - def add_result(self, result): + def add_result(self, result, process_time): """Handle result of one plugin and it's instance.""" + instance = result["instance"] instance_id = None if instance is not None: instance_id = instance.id self._current_plugin_data["instances_data"].append({ "id": instance_id, - "logs": self._extract_instance_log_items(result) + "logs": self._extract_instance_log_items(result), + "process_time": process_time }) def add_action_result(self, action, result): @@ -2100,9 +2103,11 @@ class PublisherController(BasePublisherController): ) def _process_and_continue(self, plugin, instance): + start = time.time() result = pyblish.plugin.process( plugin, self._publish_context, instance ) + process_time = time.time() - start self._publish_report.add_result(result) From 2787dbd83a1d621ddbcf0372d36fad825acf87d9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sat, 15 Oct 2022 18:32:09 +0200 Subject: [PATCH 152/201] add report version to report data --- openpype/tools/publisher/control.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 9eff431171..17db324a68 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -4,6 +4,7 @@ import logging import traceback import collections import time +import uuid from abc import ABCMeta, abstractmethod, abstractproperty import six @@ -293,7 +294,9 @@ class PublishReport: "plugins_data": plugins_data, "instances": instances_details, "context": self._extract_context_data(self._current_context), - "crashed_file_paths": crashed_file_paths + "crashed_file_paths": crashed_file_paths, + "id": str(uuid.uuid4()), + "report_version": "1.0.0" } def _extract_context_data(self, context): From d3e5041379291c899ddbe5a14082915176c5776a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sat, 15 Oct 2022 20:39:02 +0200 Subject: [PATCH 153/201] fix not passed argument --- openpype/tools/publisher/control.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 17db324a68..b415644a43 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -2112,7 +2112,7 @@ class PublisherController(BasePublisherController): ) process_time = time.time() - start - self._publish_report.add_result(result) + self._publish_report.add_result(result, process_time) exception = result.get("error") if exception: From a877176b39836b0d048bb0dc235225c6949008b5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 17 Oct 2022 00:26:08 +0200 Subject: [PATCH 154/201] don't crash in collection when files are not filled --- .../plugins/publish/collect_simple_instances.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py b/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py index d91694ef69..7035a61d7b 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py @@ -70,11 +70,17 @@ class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin): repre_names, representation_files_mapping ) - + source_filepaths = list(set(source_filepaths)) instance.data["source"] = source - instance.data["sourceFilepaths"] = list(set(source_filepaths)) - instance.data["originalBasename"] = Path( - instance.data["sourceFilepaths"][0]).stem + instance.data["sourceFilepaths"] = source_filepaths + + # NOTE: Missing filepaths should not cause crashes (at least not here) + # - if filepaths are required they should crash on validation + if source_filepaths: + # NOTE: Original basename is not handling sequences + # - we should maybe not fill the key when sequence is used? + origin_basename = Path(source_filepaths[0]).stem + instance.data["originalBasename"] = origin_basename self.log.debug( ( From 0df15975b16aabac45a61f2f025956180537c2b2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 17 Oct 2022 00:34:02 +0200 Subject: [PATCH 155/201] fix unwanted zooming if control was released in different widget --- .../publisher/publish_report_viewer/widgets.py | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/openpype/tools/publisher/publish_report_viewer/widgets.py b/openpype/tools/publisher/publish_report_viewer/widgets.py index dc82448495..4770bdcc65 100644 --- a/openpype/tools/publisher/publish_report_viewer/widgets.py +++ b/openpype/tools/publisher/publish_report_viewer/widgets.py @@ -148,12 +148,12 @@ class ZoomPlainText(QtWidgets.QPlainTextEdit): anim_timer.timeout.connect(self._scaling_callback) self._anim_timer = anim_timer - self._zoom_enabled = False self._scheduled_scalings = 0 self._point_size = None def wheelEvent(self, event): - if not self._zoom_enabled: + modifiers = QtWidgets.QApplication.keyboardModifiers() + if modifiers != QtCore.Qt.ControlModifier: super(ZoomPlainText, self).wheelEvent(event) return @@ -189,16 +189,6 @@ class ZoomPlainText(QtWidgets.QPlainTextEdit): else: self._scheduled_scalings += 1 - def keyPressEvent(self, event): - if event.key() == QtCore.Qt.Key_Control: - self._zoom_enabled = True - super(ZoomPlainText, self).keyPressEvent(event) - - def keyReleaseEvent(self, event): - if event.key() == QtCore.Qt.Key_Control: - self._zoom_enabled = False - super(ZoomPlainText, self).keyReleaseEvent(event) - class DetailsWidget(QtWidgets.QWidget): def __init__(self, parent): From 3afee7370a0c44baa4c611250be6c9c176c6dd82 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 17 Oct 2022 00:34:16 +0200 Subject: [PATCH 156/201] define min/max of text sizes --- .../publish_report_viewer/widgets.py | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/openpype/tools/publisher/publish_report_viewer/widgets.py b/openpype/tools/publisher/publish_report_viewer/widgets.py index 4770bdcc65..ff388fb277 100644 --- a/openpype/tools/publisher/publish_report_viewer/widgets.py +++ b/openpype/tools/publisher/publish_report_viewer/widgets.py @@ -139,6 +139,9 @@ class PluginLoadReportWidget(QtWidgets.QWidget): class ZoomPlainText(QtWidgets.QPlainTextEdit): + min_point_size = 1.0 + max_point_size = 200.0 + def __init__(self, *args, **kwargs): super(ZoomPlainText, self).__init__(*args, **kwargs) @@ -172,19 +175,36 @@ class ZoomPlainText(QtWidgets.QPlainTextEdit): factor = 1.0 + (self._scheduled_scalings / 300) font = self.font() + if self._point_size is None: - self._point_size = font.pointSizeF() + point_size = font.pointSizeF() + else: + point_size = self._point_size - self._point_size *= factor - if self._point_size < 1: - self._point_size = 1.0 + point_size *= factor + min_hit = False + max_hit = False + if point_size < self.min_point_size: + point_size = self.min_point_size + min_hit = True + elif point_size > self.max_point_size: + point_size = self.max_point_size + max_hit = True - font.setPointSizeF(self._point_size) + self._point_size = point_size + + font.setPointSizeF(point_size) # Using 'self.setFont(font)' would not be propagated when stylesheets # are applied on this widget self.setStyleSheet("font-size: {}pt".format(font.pointSize())) - if self._scheduled_scalings > 0: + if ( + (max_hit and self._scheduled_scalings > 0) + or (min_hit and self._scheduled_scalings < 0) + ): + self._scheduled_scalings = 0 + + elif self._scheduled_scalings > 0: self._scheduled_scalings -= 1 else: self._scheduled_scalings += 1 From 890b77214acd24482501ce19722f6701f6dac537 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 17 Oct 2022 10:49:39 +0200 Subject: [PATCH 157/201] import lib content from lib directly --- openpype/hosts/flame/api/workio.py | 2 +- openpype/hosts/flame/hooks/pre_flame_setup.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/flame/api/workio.py b/openpype/hosts/flame/api/workio.py index 0c96c0752a..e49321c75a 100644 --- a/openpype/hosts/flame/api/workio.py +++ b/openpype/hosts/flame/api/workio.py @@ -1,7 +1,7 @@ """Host API required Work Files tool""" import os -from openpype.api import Logger +from openpype.lib import Logger # from .. import ( # get_project_manager, # get_current_project diff --git a/openpype/hosts/flame/hooks/pre_flame_setup.py b/openpype/hosts/flame/hooks/pre_flame_setup.py index f0fdaa86ba..713daf1031 100644 --- a/openpype/hosts/flame/hooks/pre_flame_setup.py +++ b/openpype/hosts/flame/hooks/pre_flame_setup.py @@ -3,16 +3,17 @@ import json import tempfile import contextlib import socket +from pprint import pformat + from openpype.lib import ( PreLaunchHook, - get_openpype_username + get_openpype_username, + run_subprocess, ) from openpype.lib.applications import ( ApplicationLaunchFailed ) from openpype.hosts import flame as opflame -import openpype -from pprint import pformat class FlamePrelaunch(PreLaunchHook): @@ -127,7 +128,6 @@ class FlamePrelaunch(PreLaunchHook): except OSError as exc: self.log.warning("Not able to open files: {}".format(exc)) - def _get_flame_fps(self, fps_num): fps_table = { float(23.976): "23.976 fps", @@ -179,7 +179,7 @@ class FlamePrelaunch(PreLaunchHook): "env": self.launch_context.env } - openpype.api.run_subprocess(args, **process_kwargs) + run_subprocess(args, **process_kwargs) # process returned json file to pass launch args return_json_data = open(tmp_json_path).read() From 49190b9f7876a9ed86136c6004dc671288c8e031 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 17 Oct 2022 12:19:26 +0200 Subject: [PATCH 158/201] keep selectio between instance views --- .../publisher/widgets/card_view_widgets.py | 37 ++++++ .../publisher/widgets/list_view_widgets.py | 119 +++++++++++++++--- .../publisher/widgets/overview_widget.py | 10 +- openpype/tools/publisher/widgets/widgets.py | 15 +++ 4 files changed, 159 insertions(+), 22 deletions(-) diff --git a/openpype/tools/publisher/widgets/card_view_widgets.py b/openpype/tools/publisher/widgets/card_view_widgets.py index c0cc3389c7..5daf8059b0 100644 --- a/openpype/tools/publisher/widgets/card_view_widgets.py +++ b/openpype/tools/publisher/widgets/card_view_widgets.py @@ -903,3 +903,40 @@ class InstanceCardView(AbstractInstanceView): instances.append(widget.id) return instances, context_selected + + def set_selected_items(self, instance_ids, context_selected): + s_instance_ids = set(instance_ids) + cur_ids, cur_context = self.get_selected_items() + if ( + set(cur_ids) == s_instance_ids + and cur_context == context_selected + ): + return + + selected_groups = [] + selected_instances = [] + if context_selected: + selected_groups.append("") + selected_instances.append(CONTEXT_ID) + + self._context_widget.set_selected(context_selected) + + for group_name in self._ordered_groups: + if group_name == "": + continue + + group_widget = self._widgets_by_group[group_name] + group_selected = False + for widget in group_widget.get_ordered_widgets(): + select = False + if widget.id in s_instance_ids: + selected_instances.append(widget.id) + group_selected = True + select = True + widget.set_selected(select) + + if group_selected: + selected_groups.append(group_name) + + self._explicitly_selected_groups = selected_groups + self._explicitly_selected_instance_ids = selected_instances diff --git a/openpype/tools/publisher/widgets/list_view_widgets.py b/openpype/tools/publisher/widgets/list_view_widgets.py index 6d90e63683..c329ca0e8c 100644 --- a/openpype/tools/publisher/widgets/list_view_widgets.py +++ b/openpype/tools/publisher/widgets/list_view_widgets.py @@ -760,26 +760,6 @@ class InstanceListView(AbstractInstanceView): if changed_ids: self.active_changed.emit() - def get_selected_items(self): - """Get selected instance ids and context selection. - - Returns: - tuple: Selected instance ids and boolean if context - is selected. - """ - instance_ids = [] - context_selected = False - - for index in self._instance_view.selectionModel().selectedIndexes(): - instance_id = index.data(INSTANCE_ID_ROLE) - if not context_selected and instance_id == CONTEXT_ID: - context_selected = True - - elif instance_id is not None: - instance_ids.append(instance_id) - - return instance_ids, context_selected - def _on_selection_change(self, *_args): self.selection_changed.emit() @@ -819,3 +799,102 @@ class InstanceListView(AbstractInstanceView): proxy_index = self._proxy_model.mapFromSource(group_item.index()) if not self._instance_view.isExpanded(proxy_index): self._instance_view.expand(proxy_index) + + def get_selected_items(self): + """Get selected instance ids and context selection. + + Returns: + tuple: Selected instance ids and boolean if context + is selected. + """ + instance_ids = [] + context_selected = False + + for index in self._instance_view.selectionModel().selectedIndexes(): + instance_id = index.data(INSTANCE_ID_ROLE) + if not context_selected and instance_id == CONTEXT_ID: + context_selected = True + + elif instance_id is not None: + instance_ids.append(instance_id) + + return instance_ids, context_selected + + def set_selected_items(self, instance_ids, context_selected): + s_instance_ids = set(instance_ids) + cur_ids, cur_context = self.get_selected_items() + if ( + set(cur_ids) == s_instance_ids + and cur_context == context_selected + ): + return + + view = self._instance_view + src_model = self._instance_model + proxy_model = self._proxy_model + + select_indexes = [] + + select_queue = collections.deque() + select_queue.append( + (src_model.invisibleRootItem(), []) + ) + while select_queue: + queue_item = select_queue.popleft() + item, parent_items = queue_item + + if item.hasChildren(): + new_parent_items = list(parent_items) + new_parent_items.append(item) + for row in range(item.rowCount()): + select_queue.append( + (item.child(row), list(new_parent_items)) + ) + + instance_id = item.data(INSTANCE_ID_ROLE) + if not instance_id: + continue + + if instance_id in s_instance_ids: + select_indexes.append(item.index()) + for parent_item in parent_items: + index = parent_item.index() + proxy_index = proxy_model.mapFromSource(index) + if not view.isExpanded(proxy_index): + view.expand(proxy_index) + + elif context_selected and instance_id == CONTEXT_ID: + select_indexes.append(item.index()) + + selection_model = view.selectionModel() + if not select_indexes: + selection_model.clear() + return + + if len(select_indexes) == 1: + proxy_index = proxy_model.mapFromSource(select_indexes[0]) + selection_model.setCurrentIndex( + proxy_index, + selection_model.ClearAndSelect | selection_model.Rows + ) + return + + first_index = proxy_model.mapFromSource(select_indexes.pop(0)) + last_index = proxy_model.mapFromSource(select_indexes.pop(-1)) + + selection_model.setCurrentIndex( + first_index, + selection_model.ClearAndSelect | selection_model.Rows + ) + + for index in select_indexes: + proxy_index = proxy_model.mapFromSource(index) + selection_model.select( + proxy_index, + selection_model.Select | selection_model.Rows + ) + + selection_model.setCurrentIndex( + last_index, + selection_model.Select | selection_model.Rows + ) diff --git a/openpype/tools/publisher/widgets/overview_widget.py b/openpype/tools/publisher/widgets/overview_widget.py index 8759d2ad49..5bd3017c2a 100644 --- a/openpype/tools/publisher/widgets/overview_widget.py +++ b/openpype/tools/publisher/widgets/overview_widget.py @@ -321,15 +321,21 @@ class OverviewWidget(QtWidgets.QFrame): def _change_view_type(self): idx = self._subset_views_layout.currentIndex() new_idx = (idx + 1) % self._subset_views_layout.count() - self._subset_views_layout.setCurrentIndex(new_idx) - new_view = self._subset_views_layout.currentWidget() + old_view = self._subset_views_layout.currentWidget() + new_view = self._subset_views_layout.widget(new_idx) + if not new_view.refreshed: new_view.refresh() new_view.set_refreshed(True) else: new_view.refresh_instance_states() + instance_ids, context_selected = old_view.get_selected_items() + new_view.set_selected_items(instance_ids, context_selected) + + self._subset_views_layout.setCurrentIndex(new_idx) + self._on_subset_change() def _refresh_instances(self): diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index 536650e209..ddbe1eb6b7 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -306,10 +306,25 @@ class AbstractInstanceView(QtWidgets.QWidget): Example: When delete button is clicked to know what should be deleted. """ + raise NotImplementedError(( "{} Method 'get_selected_items' is not implemented." ).format(self.__class__.__name__)) + def set_selected_items(self, instance_ids, context_selected): + """Change selection for instances and context. + + Used to applying selection from one view to other. + + Args: + instance_ids (List[str]): Selected instance ids. + context_selected (bool): Context is selected. + """ + + raise NotImplementedError(( + "{} Method 'set_selected_items' is not implemented." + ).format(self.__class__.__name__)) + class ClickableLineEdit(QtWidgets.QLineEdit): """QLineEdit capturing left mouse click. From c410ee662b8c4c81fb147aca90ebd9b963f2b934 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 17 Oct 2022 21:24:17 +0800 Subject: [PATCH 159/201] clean up --- openpype/settings/defaults/project_settings/maya.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 3e3a61fc8f..1e57a7baeb 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -33,7 +33,7 @@ }, "RenderSettings": { "apply_render_settings": true, - "default_render_image_folder": "renders", + "default_render_image_folder": "", "aov_separator": "underscore", "reset_current_frame": false, "arnold_renderer": { From 1ecc673c6ccf3abeb6acdf2529a617daf447a51e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 17 Oct 2022 17:15:51 +0200 Subject: [PATCH 160/201] error message fix --- openpype/pipeline/create/context.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index a35541f339..4ec6d7bdad 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -1096,7 +1096,8 @@ class CreateContext: and creator_class.host_name != self.host_name ): self.log.info(( - "Creator's host name is not supported for current host {}" + "Creator's host name \"{}\"" + " is not supported for current host \"{}\"" ).format(creator_class.host_name, self.host_name)) continue From 495b5479140af18f5bfbd8342b2ba20132dc9888 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 17 Oct 2022 17:21:29 +0200 Subject: [PATCH 161/201] fix args order --- openpype/tools/utils/host_tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/utils/host_tools.py b/openpype/tools/utils/host_tools.py index eababfee32..046dcbdf6a 100644 --- a/openpype/tools/utils/host_tools.py +++ b/openpype/tools/utils/host_tools.py @@ -287,7 +287,7 @@ class HostToolsHelper: def show_publisher_tool(self, parent=None, controller=None): with qt_app_context(): - dialog = self.get_publisher_tool(controller, parent) + dialog = self.get_publisher_tool(parent, controller) dialog.show() dialog.raise_() From d1bd6943167a860d1fdd2584771ce16b665c75b0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 17 Oct 2022 17:29:54 +0200 Subject: [PATCH 162/201] disable sequences in mov fielpaths input --- .../hosts/traypublisher/plugins/create/create_movie_batch.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/traypublisher/plugins/create/create_movie_batch.py b/openpype/hosts/traypublisher/plugins/create/create_movie_batch.py index abe29d7473..cf25a37918 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_movie_batch.py +++ b/openpype/hosts/traypublisher/plugins/create/create_movie_batch.py @@ -188,6 +188,7 @@ class BatchMovieCreator(TrayPublishCreator): folders=False, single_item=False, extensions=self.extensions, + allow_sequences=False, label="Filepath" ), BoolDef( From 609f9f12851dfc775edd04344a2c9aa1eaba8426 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 17 Oct 2022 17:44:08 +0200 Subject: [PATCH 163/201] fix attribute access --- openpype/tools/publisher/control.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index b415644a43..911d464f80 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -1563,10 +1563,10 @@ class PublisherController(BasePublisherController): str: Project name. """ - if not hasattr(self.host, "get_current_context"): + if not hasattr(self._host, "get_current_context"): return legacy_io.active_project() - return self.host.get_current_context()["project_name"] + return self._host.get_current_context()["project_name"] @property def current_asset_name(self): @@ -1576,10 +1576,10 @@ class PublisherController(BasePublisherController): Union[str, None]: Asset name or None if asset is not set. """ - if not hasattr(self.host, "get_current_context"): + if not hasattr(self._host, "get_current_context"): return legacy_io.Session["AVALON_ASSET"] - return self.host.get_current_context()["asset_name"] + return self._host.get_current_context()["asset_name"] @property def current_task_name(self): @@ -1589,10 +1589,10 @@ class PublisherController(BasePublisherController): Union[str, None]: Task name or None if task is not set. """ - if not hasattr(self.host, "get_current_context"): + if not hasattr(self._host, "get_current_context"): return legacy_io.Session["AVALON_TASK"] - return self.host.get_current_context()["task_name"] + return self._host.get_current_context()["task_name"] @property def instances(self): From 27d4f1fc70684fe2abb9f70ccbe04e9e7cae42fb Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 17 Oct 2022 18:47:58 +0200 Subject: [PATCH 164/201] reuse duration from pyblish result instead of calculating own --- openpype/tools/publisher/control.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 911d464f80..c8f38cb080 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -234,7 +234,7 @@ class PublishReport: """Set that current plugin has been skipped.""" self._current_plugin_data["skipped"] = True - def add_result(self, result, process_time): + def add_result(self, result): """Handle result of one plugin and it's instance.""" instance = result["instance"] @@ -244,7 +244,7 @@ class PublishReport: self._current_plugin_data["instances_data"].append({ "id": instance_id, "logs": self._extract_instance_log_items(result), - "process_time": process_time + "process_time": result["duration"] }) def add_action_result(self, action, result): @@ -2106,13 +2106,11 @@ class PublisherController(BasePublisherController): ) def _process_and_continue(self, plugin, instance): - start = time.time() result = pyblish.plugin.process( plugin, self._publish_context, instance ) - process_time = time.time() - start - self._publish_report.add_result(result, process_time) + self._publish_report.add_result(result) exception = result.get("error") if exception: From b4d6fa3a3af7874d05a48cbeb1ab1010af7d7d52 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 17 Oct 2022 18:54:27 +0200 Subject: [PATCH 165/201] removed unused import --- openpype/tools/publisher/control.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index c8f38cb080..13c1044201 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -3,7 +3,6 @@ import copy import logging import traceback import collections -import time import uuid from abc import ABCMeta, abstractmethod, abstractproperty From 27bf66a6b47aff79faee4eed3e18b7e59ed17667 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 17 Oct 2022 20:59:17 +0200 Subject: [PATCH 166/201] resolve: other platform compatibility --- openpype/hosts/resolve/hooks/pre_resolve_setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/resolve/hooks/pre_resolve_setup.py b/openpype/hosts/resolve/hooks/pre_resolve_setup.py index 0cf9664457..8574b3ad01 100644 --- a/openpype/hosts/resolve/hooks/pre_resolve_setup.py +++ b/openpype/hosts/resolve/hooks/pre_resolve_setup.py @@ -3,6 +3,7 @@ import platform from openpype.lib import PreLaunchHook from openpype.hosts.resolve.utils import setup + class ResolvePrelaunch(PreLaunchHook): """ This hook will check if current workfile path has Resolve @@ -15,7 +16,7 @@ class ResolvePrelaunch(PreLaunchHook): def execute(self): current_platform = platform.system().lower() - PROGRAMDATA = self.launch_context.env["PROGRAMDATA"] + PROGRAMDATA = self.launch_context.env.get("PROGRAMDATA", "") RESOLVE_SCRIPT_API_ = { "windows": ( f"{PROGRAMDATA}/Blackmagic Design/" From 3fd2a8826c183fbc7f549f1aa573fca90e47ecbb Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 17 Oct 2022 23:54:00 +0200 Subject: [PATCH 167/201] fix wrong attribute name --- openpype/tools/publisher/control.py | 14 +++++++------- openpype/tools/publisher/control_qt.py | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 13c1044201..a340f8c1d2 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -1289,7 +1289,7 @@ class BasePublisherController(AbstractPublisherController): self._publish_has_validation_errors = False self._publish_has_crashed = False # All publish plugins are processed - self._publish_finished = False + self._publish_has_finished = False self._publish_max_progress = 0 self._publish_progress = 0 @@ -1337,7 +1337,7 @@ class BasePublisherController(AbstractPublisherController): changed. "publish.progress.changed" - Attr 'publish_progress' changed. "publish.host_is_valid.changed" - Attr 'host_is_valid' changed. - "publish.finished.changed" - Attr 'publish_finished' changed. + "publish.finished.changed" - Attr 'publish_has_finished' changed. Returns: EventSystem: Event system which can trigger callbacks for topics. @@ -1361,11 +1361,11 @@ class BasePublisherController(AbstractPublisherController): self._emit_event("publish.host_is_valid.changed", {"value": value}) def _get_publish_has_finished(self): - return self._publish_finished + return self._publish_has_finished def _set_publish_has_finished(self, value): - if self._publish_finished != value: - self._publish_finished = value + if self._publish_has_finished != value: + self._publish_has_finished = value self._emit_event("publish.finished.changed", {"value": value}) def _get_publish_is_running(self): @@ -1465,7 +1465,7 @@ class BasePublisherController(AbstractPublisherController): self.publish_has_validated = False self.publish_has_crashed = False self.publish_has_validation_errors = False - self.publish_finished = False + self.publish_has_finished = False self.publish_error_msg = None self.publish_progress = 0 @@ -2092,7 +2092,7 @@ class PublisherController(BasePublisherController): self._publish_report.set_plugin_skipped() # Cleanup of publishing process - self.publish_finished = True + self.publish_has_finished = True self.publish_progress = self.publish_max_progress yield MainThreadItem(self.stop_publish) diff --git a/openpype/tools/publisher/control_qt.py b/openpype/tools/publisher/control_qt.py index ddc2dfa3e4..56132a4046 100644 --- a/openpype/tools/publisher/control_qt.py +++ b/openpype/tools/publisher/control_qt.py @@ -173,7 +173,7 @@ class QtRemotePublishController(BasePublisherController): return if event.topic == "publish.finished.changed": - self.publish_finished = event["value"] + self.publish_has_finished = event["value"] return if event.topic == "publish.host_is_valid.changed": From 7190c0785cebc78b3b68dab21923e19e958c23fe Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 18 Oct 2022 00:05:34 +0200 Subject: [PATCH 168/201] go to report on publish stop if on publish tab --- openpype/tools/publisher/window.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index a0d1ac68fb..1424a3eccd 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -499,6 +499,9 @@ class PublisherWindow(QtWidgets.QDialog): publish_has_crashed = self._controller.publish_has_crashed validate_enabled = not publish_has_crashed publish_enabled = not publish_has_crashed + if self._tabs_widget.is_current_tab("publish"): + self._go_to_report_tab() + if validate_enabled: validate_enabled = not self._controller.publish_has_validated if publish_enabled: @@ -507,8 +510,6 @@ class PublisherWindow(QtWidgets.QDialog): and self._controller.publish_has_validation_errors ): publish_enabled = False - if self._tabs_widget.is_current_tab("publish"): - self._go_to_report_tab() else: publish_enabled = not self._controller.publish_has_finished From c9e10f6147356c618aaeb30251a39f428ae88ad5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 18 Oct 2022 00:14:35 +0200 Subject: [PATCH 169/201] change progress bar on validation error --- openpype/style/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/style/style.css b/openpype/style/style.css index 4d13dc7c89..b466bd0820 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -1086,7 +1086,7 @@ ValidationArtistMessage QLabel { border-color: {color:publisher:error}; } -#PublishProgressBar[state="0"]::chunk { +#PublishProgressBar[state="0"]::chunk, #PublishProgressBar[state="2"]::chunk { background: {color:bg-buttons}; } From 9e37f3448e2f266dc9fe019e303ecca6864bdb76 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 18 Oct 2022 00:14:46 +0200 Subject: [PATCH 170/201] change page to publish on reset --- openpype/tools/publisher/window.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index 1424a3eccd..39075d2489 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -470,6 +470,11 @@ class PublisherWindow(QtWidgets.QDialog): self._set_publish_visibility(False) self._set_footer_enabled(False) self._update_publish_details_widget() + if ( + not self._tabs_widget.is_current_tab("create") + or not self._tabs_widget.is_current_tab("publish") + ): + self._tabs_widget.set_current_tab("publish") def _on_publish_start(self): self._create_tab.setEnabled(False) From 7f533390712c308e5eaa2c8c73c7ad2cb3bdc20a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 18 Oct 2022 00:46:01 +0200 Subject: [PATCH 171/201] change progress bar colors on pause --- openpype/style/style.css | 15 +++++++-------- .../tools/publisher/widgets/publish_frame.py | 19 +++++++++++++++---- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/openpype/style/style.css b/openpype/style/style.css index b466bd0820..a6818a5792 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -973,23 +973,22 @@ VariantInputsWidget QToolButton { background: {color:bg}; border-radius: 0.3em; } - -#PublishInfoFrame[state="-1"] { - background: rgb(194, 226, 236); -} - #PublishInfoFrame[state="0"] { - background: {color:publisher:crash}; + background: {color:publisher:success}; } #PublishInfoFrame[state="1"] { - background: {color:publisher:success}; + background: {color:publisher:crash}; } #PublishInfoFrame[state="2"] { background: {color:publisher:warning}; } +#PublishInfoFrame[state="3"], #PublishInfoFrame[state="4"] { + background: rgb(194, 226, 236); +} + #PublishInfoFrame QLabel { color: black; font-style: bold; @@ -1086,7 +1085,7 @@ ValidationArtistMessage QLabel { border-color: {color:publisher:error}; } -#PublishProgressBar[state="0"]::chunk, #PublishProgressBar[state="2"]::chunk { +#PublishProgressBar[state="1"]::chunk, #PublishProgressBar[state="4"]::chunk { background: {color:bg-buttons}; } diff --git a/openpype/tools/publisher/widgets/publish_frame.py b/openpype/tools/publisher/widgets/publish_frame.py index c5685461a7..e6333a104f 100644 --- a/openpype/tools/publisher/widgets/publish_frame.py +++ b/openpype/tools/publisher/widgets/publish_frame.py @@ -328,7 +328,7 @@ class PublishFrame(QtWidgets.QWidget): if self._last_instance_label: self._instance_label.setText(self._last_instance_label) - self._set_success_property(-1) + self._set_success_property(3) self._set_progress_visibility(True) self._set_main_label("Publishing...") @@ -407,7 +407,7 @@ class PublishFrame(QtWidgets.QWidget): "Hit publish (play button) to continue." ) - self._set_success_property(-1) + self._set_success_property(4) def _set_error_msg(self): """Show error message to artist on publish crash.""" @@ -416,7 +416,7 @@ class PublishFrame(QtWidgets.QWidget): self._message_label_top.setText(self._controller.publish_error_msg) - self._set_success_property(0) + self._set_success_property(1) def _set_validation_errors(self): self._set_main_label("Your publish didn't pass studio validations") @@ -426,7 +426,7 @@ class PublishFrame(QtWidgets.QWidget): def _set_finished(self): self._set_main_label("Finished") self._message_label_top.setText("") - self._set_success_property(1) + self._set_success_property(0) def _set_progress_visibility(self, visible): window_height = self.height() @@ -447,6 +447,17 @@ class PublishFrame(QtWidgets.QWidget): self.move(window_pos.x(), window_pos_y) def _set_success_property(self, state=None): + """Apply styles by state. + + State enum: + - None - Default state after restart + - 0 - Success finish + - 1 - Error happened + - 2 - Validation error + - 3 - In progress + - 4 - Stopped/Paused + """ + if state is None: state = "" else: From 88438127ce6bae249ea76fb1b8a47e28574d6dde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Tue, 18 Oct 2022 11:06:20 +0200 Subject: [PATCH 172/201] upgrading change log generator to 2.3 also rising sinceTag to 3.12.0 --- .github/workflows/prerelease.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index bf39f8f956..81d5f05b17 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -40,13 +40,13 @@ jobs: - name: "✏️ Generate full changelog" if: steps.version_type.outputs.type != 'skip' id: generate-full-changelog - uses: heinrichreimer/github-changelog-generator-action@v2.2 + uses: heinrichreimer/github-changelog-generator-action@v2.3 with: token: ${{ secrets.ADMIN_TOKEN }} addSections: '{"documentation":{"prefix":"### πŸ“– Documentation","labels":["type: documentation"]},"tests":{"prefix":"### βœ… Testing","labels":["tests"]},"feature":{"prefix":"**πŸ†• New features**", "labels":["type: feature"]},"breaking":{"prefix":"**πŸ’₯ Breaking**", "labels":["breaking"]},"enhancements":{"prefix":"**πŸš€ Enhancements**", "labels":["type: enhancement"]},"bugs":{"prefix":"**πŸ› Bug fixes**", "labels":["type: bug"]},"deprecated":{"prefix":"**⚠️ Deprecations**", "labels":["depreciated"]}, "refactor":{"prefix":"**πŸ”€ Refactored code**", "labels":["refactor"]}}' issues: false issuesWoLabels: false - sinceTag: "3.0.0" + sinceTag: "3.12.0" maxIssues: 100 pullRequests: true prWoLabels: false @@ -92,4 +92,4 @@ jobs: github_token: ${{ secrets.ADMIN_TOKEN }} source_ref: 'main' target_branch: 'develop' - commit_message_template: '[Automated] Merged {source_ref} into {target_branch}' \ No newline at end of file + commit_message_template: '[Automated] Merged {source_ref} into {target_branch}' From 4c99546b035def8a9d4854e9d90df7edffc76a7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Tue, 18 Oct 2022 11:07:55 +0200 Subject: [PATCH 173/201] update change log generator to 2.3 rise sinceTag to 3.12.0 --- .github/workflows/release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 85864b4442..cc69e1643a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -36,13 +36,13 @@ jobs: - name: "✏️ Generate full changelog" if: steps.version.outputs.release_tag != 'skip' id: generate-full-changelog - uses: heinrichreimer/github-changelog-generator-action@v2.2 + uses: heinrichreimer/github-changelog-generator-action@v2.3 with: token: ${{ secrets.ADMIN_TOKEN }} addSections: '{"documentation":{"prefix":"### πŸ“– Documentation","labels":["type: documentation"]},"tests":{"prefix":"### βœ… Testing","labels":["tests"]},"feature":{"prefix":"**πŸ†• New features**", "labels":["type: feature"]},"breaking":{"prefix":"**πŸ’₯ Breaking**", "labels":["breaking"]},"enhancements":{"prefix":"**πŸš€ Enhancements**", "labels":["type: enhancement"]},"bugs":{"prefix":"**πŸ› Bug fixes**", "labels":["type: bug"]},"deprecated":{"prefix":"**⚠️ Deprecations**", "labels":["depreciated"]}, "refactor":{"prefix":"**πŸ”€ Refactored code**", "labels":["refactor"]}}' issues: false issuesWoLabels: false - sinceTag: "3.0.0" + sinceTag: "3.12.0" maxIssues: 100 pullRequests: true prWoLabels: false @@ -121,4 +121,4 @@ jobs: github_token: ${{ secrets.ADMIN_TOKEN }} source_ref: 'main' target_branch: 'develop' - commit_message_template: '[Automated] Merged release {source_ref} into {target_branch}' \ No newline at end of file + commit_message_template: '[Automated] Merged release {source_ref} into {target_branch}' From 4641cb5bae28620e53fec1f2b75dd673b33de55c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 18 Oct 2022 14:03:05 +0200 Subject: [PATCH 174/201] added backrwards compatibility for PyQt4 --- openpype/tools/publisher/widgets/help_widget.py | 4 +++- openpype/tools/publisher/widgets/validations_widget.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/openpype/tools/publisher/widgets/help_widget.py b/openpype/tools/publisher/widgets/help_widget.py index 7da07b1e78..0090111889 100644 --- a/openpype/tools/publisher/widgets/help_widget.py +++ b/openpype/tools/publisher/widgets/help_widget.py @@ -44,8 +44,10 @@ class HelpWidget(QtWidgets.QWidget): if commonmark: html = commonmark.commonmark(text) self._detail_description_input.setHtml(html) - else: + elif hasattr(self._detail_description_input, "setMarkdown"): self._detail_description_input.setMarkdown(text) + else: + self._detail_description_input.setText(text) class HelpDialog(QtWidgets.QDialog): diff --git a/openpype/tools/publisher/widgets/validations_widget.py b/openpype/tools/publisher/widgets/validations_widget.py index 772a561504..8c483e8088 100644 --- a/openpype/tools/publisher/widgets/validations_widget.py +++ b/openpype/tools/publisher/widgets/validations_widget.py @@ -709,5 +709,7 @@ class ValidationsWidget(QtWidgets.QFrame): if commonmark: html = commonmark.commonmark(description) self._error_details_input.setHtml(html) - else: + elif hasattr(self._error_details_input, "setMarkdown"): self._error_details_input.setMarkdown(description) + else: + self._error_details_input.setText(description) From def2fc4bc81d3ec21f8a6cb59726456d7c3d4080 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 18 Oct 2022 14:51:54 +0200 Subject: [PATCH 175/201] removed '_CUSTOM' from env keys --- openpype/lib/vendor_bin_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/lib/vendor_bin_utils.py b/openpype/lib/vendor_bin_utils.py index 7b52341290..eb7987c8a1 100644 --- a/openpype/lib/vendor_bin_utils.py +++ b/openpype/lib/vendor_bin_utils.py @@ -250,7 +250,7 @@ def get_oiio_tools_path(tool="oiiotool"): if CachedToolPaths.is_tool_cached(tool): return CachedToolPaths.get_executable_path(tool) - custom_paths_str = os.environ.get("OPENPYPE_CUSTOM_OIIO_PATHS") or "" + custom_paths_str = os.environ.get("OPENPYPE_OIIO_PATHS") or "" tool_executable_path = find_tool_in_custom_paths( custom_paths_str.split(os.pathsep), tool, @@ -330,7 +330,7 @@ def get_ffmpeg_tool_path(tool="ffmpeg"): if CachedToolPaths.is_tool_cached(tool): return CachedToolPaths.get_executable_path(tool) - custom_paths_str = os.environ.get("OPENPYPE_CUSTOM_FFMPEG_PATHS") or "" + custom_paths_str = os.environ.get("OPENPYPE_FFMPEG_PATHS") or "" tool_executable_path = find_tool_in_custom_paths( custom_paths_str.split(os.pathsep), tool, From 8e90bf73c9bb0ccbb46fedcc557118b2cfb8adb8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 18 Oct 2022 15:00:56 +0200 Subject: [PATCH 176/201] added information about ffmeg and oiio to documentation --- website/docs/admin_settings_system.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/website/docs/admin_settings_system.md b/website/docs/admin_settings_system.md index 8daba91db1..800ec1c840 100644 --- a/website/docs/admin_settings_system.md +++ b/website/docs/admin_settings_system.md @@ -26,7 +26,14 @@ as a naive barier to prevent artists from accidental setting changes. **`Disk mapping`** - Platform dependent configuration for mapping of virtual disk(s) on an artist's OpenPype machines before OP starts up. Uses `subst` command, if configured volume character in `Destination` field already exists, no re-mapping is done for that character(volume). + +### FFmpeg and OpenImageIO tools + +We bundle FFmpeg and OpenImageIO tools with OpenPype build for Windows and Linux builds. For MacOs support or to use different build is it possible to set environment variables `OPENPYPE_FFMPEG_PATHS` and `OPENPYPE_OIIO_PATHS` in system settings. Both should lead to directory where tool executables are located. Multiple paths are supported. + + ### OpenPype deployment control + **`Versions Repository`** - Location where automatic update mechanism searches for zip files with OpenPype update packages. To read more about preparing OpenPype for automatic updates go to [Admin Distribute docs](admin_distribute.md#2-openpype-codebase) From 952fd6c15c6f60a062bd3e14e0bc6df5dd7b33ee Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 18 Oct 2022 15:01:43 +0200 Subject: [PATCH 177/201] removed unnecessary lines --- website/docs/admin_settings_system.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/website/docs/admin_settings_system.md b/website/docs/admin_settings_system.md index 800ec1c840..cef4571c84 100644 --- a/website/docs/admin_settings_system.md +++ b/website/docs/admin_settings_system.md @@ -26,14 +26,10 @@ as a naive barier to prevent artists from accidental setting changes. **`Disk mapping`** - Platform dependent configuration for mapping of virtual disk(s) on an artist's OpenPype machines before OP starts up. Uses `subst` command, if configured volume character in `Destination` field already exists, no re-mapping is done for that character(volume). - ### FFmpeg and OpenImageIO tools - We bundle FFmpeg and OpenImageIO tools with OpenPype build for Windows and Linux builds. For MacOs support or to use different build is it possible to set environment variables `OPENPYPE_FFMPEG_PATHS` and `OPENPYPE_OIIO_PATHS` in system settings. Both should lead to directory where tool executables are located. Multiple paths are supported. - ### OpenPype deployment control - **`Versions Repository`** - Location where automatic update mechanism searches for zip files with OpenPype update packages. To read more about preparing OpenPype for automatic updates go to [Admin Distribute docs](admin_distribute.md#2-openpype-codebase) From e9db3dbadce7bee241608f010785a3b14ce95641 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 18 Oct 2022 15:07:56 +0200 Subject: [PATCH 178/201] rephrase sentence --- website/docs/admin_settings_system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/admin_settings_system.md b/website/docs/admin_settings_system.md index cef4571c84..66715e7288 100644 --- a/website/docs/admin_settings_system.md +++ b/website/docs/admin_settings_system.md @@ -27,7 +27,7 @@ as a naive barier to prevent artists from accidental setting changes. Uses `subst` command, if configured volume character in `Destination` field already exists, no re-mapping is done for that character(volume). ### FFmpeg and OpenImageIO tools -We bundle FFmpeg and OpenImageIO tools with OpenPype build for Windows and Linux builds. For MacOs support or to use different build is it possible to set environment variables `OPENPYPE_FFMPEG_PATHS` and `OPENPYPE_OIIO_PATHS` in system settings. Both should lead to directory where tool executables are located. Multiple paths are supported. +We bundle FFmpeg tools for all platforms and OpenImageIO tools for Windows and Linux. By default are used bundled tools but it is possible to set environment variables `OPENPYPE_FFMPEG_PATHS` and `OPENPYPE_OIIO_PATHS` in system settings environments to look for them in different directory e.g. for different linux distributions or to add oiio support for MacOs. Values of both environment variables should lead to directory where tool executables are located (multiple paths are supported). ### OpenPype deployment control **`Versions Repository`** - Location where automatic update mechanism searches for zip files with From 9a9aeef3ac22d9504a1098abd7c5af843dd89b62 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 19 Oct 2022 10:42:09 +0200 Subject: [PATCH 179/201] fix removement of instances --- openpype/hosts/tvpaint/api/pipeline.py | 37 ++++++++++++++------------ 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/openpype/hosts/tvpaint/api/pipeline.py b/openpype/hosts/tvpaint/api/pipeline.py index ac4dc05040..249326791b 100644 --- a/openpype/hosts/tvpaint/api/pipeline.py +++ b/openpype/hosts/tvpaint/api/pipeline.py @@ -139,6 +139,26 @@ class TVPaintHost(HostBase, IWorkfileHost, ILoadHost): log.info("Setting up project...") set_context_settings() + def remove_instance(self, instance): + """Remove instance from current workfile metadata. + + Implementation for Subset manager tool. + """ + + current_instances = get_workfile_metadata(SECTION_NAME_INSTANCES) + instance_id = instance.get("uuid") + found_idx = None + if instance_id: + for idx, _inst in enumerate(current_instances): + if _inst["uuid"] == instance_id: + found_idx = idx + break + + if found_idx is None: + return + current_instances.pop(found_idx) + write_instances(current_instances) + def application_exit(self): """Logic related to TimerManager. @@ -421,23 +441,6 @@ def save_current_workfile_context(context): return write_workfile_metadata(SECTION_NAME_CONTEXT, context) -def remove_instance(instance): - """Remove instance from current workfile metadata.""" - current_instances = get_workfile_metadata(SECTION_NAME_INSTANCES) - instance_id = instance.get("uuid") - found_idx = None - if instance_id: - for idx, _inst in enumerate(current_instances): - if _inst["uuid"] == instance_id: - found_idx = idx - break - - if found_idx is None: - return - current_instances.pop(found_idx) - write_instances(current_instances) - - def list_instances(): """List all created instances from current workfile.""" return get_workfile_metadata(SECTION_NAME_INSTANCES) From 7cb370e4150e638b06fcdc50f165c1cb022a23e7 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 19 Oct 2022 11:34:50 +0200 Subject: [PATCH 180/201] OP-4180 - cleanup of _project_doc_to_anatomy_data Previous implementation had only syntax sugar and filtering because of Settings, in base class it is not necessary. --- openpype/pipeline/anatomy.py | 42 ++++-------------------------------- 1 file changed, 4 insertions(+), 38 deletions(-) diff --git a/openpype/pipeline/anatomy.py b/openpype/pipeline/anatomy.py index 437a03f898..aad42683e8 100644 --- a/openpype/pipeline/anatomy.py +++ b/openpype/pipeline/anatomy.py @@ -61,6 +61,8 @@ class BaseAnatomy(object): project_name = project_doc["name"] self.project_name = project_name + self._site_name = site_name + self._data = self._prepare_anatomy_data( project_doc, local_settings, site_name ) @@ -335,43 +337,8 @@ class BaseAnatomy(object): if not project_doc: return {} - project_settings_root = ProjectSettings( - project_doc["name"], reset=False, change_state=False - ) - anatomy_entity = project_settings_root["project_anatomy"] - anatomy_keys = set(anatomy_entity.keys()) - anatomy_keys.remove("attributes") - attribute_keys = set(anatomy_entity["attributes"].keys()) - - attributes = {} - project_doc_data = project_doc.get("data") or {} - for key in attribute_keys: - value = project_doc_data.get(key) - if value is not None: - attributes[key] = value - - project_doc_config = project_doc.get("config") or {} - - app_names = set() - if not project_doc_config or "apps" not in project_doc_config: - set_applications = False - else: - set_applications = True - for app_item in project_doc_config["apps"]: - if not app_item: - continue - app_name = app_item.get("name") - if app_name: - app_names.add(app_name) - - if set_applications: - attributes["applications"] = list(app_names) - - output = {"attributes": attributes} - for key in anatomy_keys: - value = project_doc_config.get(key) - if value is not None: - output[key] = value + output = copy.deepcopy(project_doc["config"]) + output["attributes"] = copy.deepcopy(project_doc["data"]) return output @@ -450,7 +417,6 @@ class Anatomy(BaseAnatomy): " to load data for specific project." )) - self._site_name = site_name project_doc = self.get_project_doc_from_cache(project_name) local_settings = get_local_settings() if not site_name: From 5d91a904aebd77e8c290d3a426701ed86b0bf114 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 19 Oct 2022 11:35:45 +0200 Subject: [PATCH 181/201] OP-4180 - Hound --- openpype/pipeline/anatomy.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/pipeline/anatomy.py b/openpype/pipeline/anatomy.py index aad42683e8..fd32a16bb2 100644 --- a/openpype/pipeline/anatomy.py +++ b/openpype/pipeline/anatomy.py @@ -15,7 +15,6 @@ from openpype.settings.lib import ( from openpype.settings.constants import ( DEFAULT_PROJECT_KEY ) -from openpype.settings import ProjectSettings from openpype.client import get_project from openpype.lib.path_templates import ( From c930212f1c3c1cfa0de281ffaf95316d91ad0488 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 19 Oct 2022 12:12:43 +0200 Subject: [PATCH 182/201] updating history.md --- HISTORY.md | 1818 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 1808 insertions(+), 10 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 032f876aa3..ca54c60273 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,1811 @@ # Changelog +## [3.14.2](https://github.com/pypeclub/OpenPype/tree/3.14.2) (2022-09-12) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.1...3.14.2) + +### πŸ“– Documentation + +- Documentation: Anatomy templates [\#3618](https://github.com/pypeclub/OpenPype/pull/3618) + +**πŸ†• New features** + +- Nuke: Build workfile by template [\#3763](https://github.com/pypeclub/OpenPype/pull/3763) +- Houdini: Publishing workfiles [\#3697](https://github.com/pypeclub/OpenPype/pull/3697) +- Global: making collect audio plugin global [\#3679](https://github.com/pypeclub/OpenPype/pull/3679) + +**πŸš€ Enhancements** + +- Flame: Adding Creator's retimed shot and handles switch [\#3826](https://github.com/pypeclub/OpenPype/pull/3826) +- Flame: OpenPype submenu to batch and media manager [\#3825](https://github.com/pypeclub/OpenPype/pull/3825) +- General: Better pixmap scaling [\#3809](https://github.com/pypeclub/OpenPype/pull/3809) +- Photoshop: attempt to speed up ExtractImage [\#3793](https://github.com/pypeclub/OpenPype/pull/3793) +- SyncServer: Added cli commands for sync server [\#3765](https://github.com/pypeclub/OpenPype/pull/3765) +- Kitsu: Drop 'entities root' setting. [\#3739](https://github.com/pypeclub/OpenPype/pull/3739) +- git: update gitignore [\#3722](https://github.com/pypeclub/OpenPype/pull/3722) +- Blender: Publisher collect workfile representation [\#3670](https://github.com/pypeclub/OpenPype/pull/3670) +- Maya: move set render settings menu entry [\#3669](https://github.com/pypeclub/OpenPype/pull/3669) +- Scene Inventory: Maya add actions to select from or to scene [\#3659](https://github.com/pypeclub/OpenPype/pull/3659) +- Scene Inventory: Add subsetGroup column [\#3658](https://github.com/pypeclub/OpenPype/pull/3658) + +**πŸ› Bug fixes** + +- General: Fix Pattern access in client code [\#3828](https://github.com/pypeclub/OpenPype/pull/3828) +- Launcher: Skip opening last work file works for groups [\#3822](https://github.com/pypeclub/OpenPype/pull/3822) +- Maya: Publishing data key change [\#3811](https://github.com/pypeclub/OpenPype/pull/3811) +- Igniter: Fix status handling when version is already installed [\#3804](https://github.com/pypeclub/OpenPype/pull/3804) +- Resolve: Addon import is Python 2 compatible [\#3798](https://github.com/pypeclub/OpenPype/pull/3798) +- Hiero: retimed clip publishing is working [\#3792](https://github.com/pypeclub/OpenPype/pull/3792) +- nuke: validate write node is not failing due wrong type [\#3780](https://github.com/pypeclub/OpenPype/pull/3780) +- Fix - changed format of version string in pyproject.toml [\#3777](https://github.com/pypeclub/OpenPype/pull/3777) +- Ftrack status fix typo prgoress -\> progress [\#3761](https://github.com/pypeclub/OpenPype/pull/3761) +- Fix version resolution [\#3757](https://github.com/pypeclub/OpenPype/pull/3757) +- Maya: `containerise` dont skip empty values [\#3674](https://github.com/pypeclub/OpenPype/pull/3674) + +**πŸ”€ Refactored code** + +- Photoshop: Use new Extractor location [\#3789](https://github.com/pypeclub/OpenPype/pull/3789) +- Blender: Use new Extractor location [\#3787](https://github.com/pypeclub/OpenPype/pull/3787) +- AfterEffects: Use new Extractor location [\#3784](https://github.com/pypeclub/OpenPype/pull/3784) +- General: Remove unused teshost [\#3773](https://github.com/pypeclub/OpenPype/pull/3773) +- General: Copied 'Extractor' plugin to publish pipeline [\#3771](https://github.com/pypeclub/OpenPype/pull/3771) +- General: Move queries of asset and representation links [\#3770](https://github.com/pypeclub/OpenPype/pull/3770) +- General: Move create project folders to pipeline [\#3768](https://github.com/pypeclub/OpenPype/pull/3768) +- General: Create project function moved to client code [\#3766](https://github.com/pypeclub/OpenPype/pull/3766) +- Maya: Refactor submit deadline to use AbstractSubmitDeadline [\#3759](https://github.com/pypeclub/OpenPype/pull/3759) +- General: Change publish template settings location [\#3755](https://github.com/pypeclub/OpenPype/pull/3755) +- General: Move hostdirname functionality into host [\#3749](https://github.com/pypeclub/OpenPype/pull/3749) +- General: Move publish utils to pipeline [\#3745](https://github.com/pypeclub/OpenPype/pull/3745) +- Houdini: Define houdini as addon [\#3735](https://github.com/pypeclub/OpenPype/pull/3735) +- Fusion: Defined fusion as addon [\#3733](https://github.com/pypeclub/OpenPype/pull/3733) +- Flame: Defined flame as addon [\#3732](https://github.com/pypeclub/OpenPype/pull/3732) +- Resolve: Define resolve as addon [\#3727](https://github.com/pypeclub/OpenPype/pull/3727) + +**Merged pull requests:** + +- Standalone Publisher: Ignore empty labels, then still use name like other asset models [\#3779](https://github.com/pypeclub/OpenPype/pull/3779) +- Kitsu - sync\_all\_project - add list ignore\_projects [\#3776](https://github.com/pypeclub/OpenPype/pull/3776) + +## [3.14.1](https://github.com/pypeclub/OpenPype/tree/3.14.1) (2022-08-30) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.0...3.14.1) + +### πŸ“– Documentation + +- Documentation: Few updates [\#3698](https://github.com/pypeclub/OpenPype/pull/3698) +- Documentation: Settings development [\#3660](https://github.com/pypeclub/OpenPype/pull/3660) + +**πŸ†• New features** + +- Webpublisher:change create flatten image into tri state [\#3678](https://github.com/pypeclub/OpenPype/pull/3678) +- Blender: validators code correction with settings and defaults [\#3662](https://github.com/pypeclub/OpenPype/pull/3662) + +**πŸš€ Enhancements** + +- General: Thumbnail can use project roots [\#3750](https://github.com/pypeclub/OpenPype/pull/3750) +- Settings: Remove settings lock on tray exit [\#3720](https://github.com/pypeclub/OpenPype/pull/3720) +- General: Added helper getters to modules manager [\#3712](https://github.com/pypeclub/OpenPype/pull/3712) +- Unreal: Define unreal as module and use host class [\#3701](https://github.com/pypeclub/OpenPype/pull/3701) +- Settings: Lock settings UI session [\#3700](https://github.com/pypeclub/OpenPype/pull/3700) +- General: Benevolent context label collector [\#3686](https://github.com/pypeclub/OpenPype/pull/3686) +- Ftrack: Store ftrack entities on hierarchy integration to instances [\#3677](https://github.com/pypeclub/OpenPype/pull/3677) +- Ftrack: More logs related to auto sync value change [\#3671](https://github.com/pypeclub/OpenPype/pull/3671) +- Blender: ops refresh manager after process events [\#3663](https://github.com/pypeclub/OpenPype/pull/3663) + +**πŸ› Bug fixes** + +- Maya: Fix typo in getPanel argument `with_focus` -\> `withFocus` [\#3753](https://github.com/pypeclub/OpenPype/pull/3753) +- General: Smaller fixes of imports [\#3748](https://github.com/pypeclub/OpenPype/pull/3748) +- General: Logger tweaks [\#3741](https://github.com/pypeclub/OpenPype/pull/3741) +- Nuke: missing job dependency if multiple bake streams [\#3737](https://github.com/pypeclub/OpenPype/pull/3737) +- Nuke: color-space settings from anatomy is working [\#3721](https://github.com/pypeclub/OpenPype/pull/3721) +- Settings: Fix studio default anatomy save [\#3716](https://github.com/pypeclub/OpenPype/pull/3716) +- Maya: Use project name instead of project code [\#3709](https://github.com/pypeclub/OpenPype/pull/3709) +- Settings: Fix project overrides save [\#3708](https://github.com/pypeclub/OpenPype/pull/3708) +- Workfiles tool: Fix published workfile filtering [\#3704](https://github.com/pypeclub/OpenPype/pull/3704) +- PS, AE: Provide default variant value for workfile subset [\#3703](https://github.com/pypeclub/OpenPype/pull/3703) +- RoyalRender: handle host name that is not set [\#3695](https://github.com/pypeclub/OpenPype/pull/3695) +- Flame: retime is working on clip publishing [\#3684](https://github.com/pypeclub/OpenPype/pull/3684) +- Webpublisher: added check for empty context [\#3682](https://github.com/pypeclub/OpenPype/pull/3682) + +**πŸ”€ Refactored code** + +- General: Move delivery logic to pipeline [\#3751](https://github.com/pypeclub/OpenPype/pull/3751) +- General: Host addons cleanup [\#3744](https://github.com/pypeclub/OpenPype/pull/3744) +- Webpublisher: Webpublisher is used as addon [\#3740](https://github.com/pypeclub/OpenPype/pull/3740) +- Photoshop: Defined photoshop as addon [\#3736](https://github.com/pypeclub/OpenPype/pull/3736) +- Harmony: Defined harmony as addon [\#3734](https://github.com/pypeclub/OpenPype/pull/3734) +- General: Module interfaces cleanup [\#3731](https://github.com/pypeclub/OpenPype/pull/3731) +- AfterEffects: Move AE functions from general lib [\#3730](https://github.com/pypeclub/OpenPype/pull/3730) +- Blender: Define blender as module [\#3729](https://github.com/pypeclub/OpenPype/pull/3729) +- AfterEffects: Define AfterEffects as module [\#3728](https://github.com/pypeclub/OpenPype/pull/3728) +- General: Replace PypeLogger with Logger [\#3725](https://github.com/pypeclub/OpenPype/pull/3725) +- Nuke: Define nuke as module [\#3724](https://github.com/pypeclub/OpenPype/pull/3724) +- General: Move subset name functionality [\#3723](https://github.com/pypeclub/OpenPype/pull/3723) +- General: Move creators plugin getter [\#3714](https://github.com/pypeclub/OpenPype/pull/3714) +- General: Move constants from lib to client [\#3713](https://github.com/pypeclub/OpenPype/pull/3713) +- Loader: Subset groups using client operations [\#3710](https://github.com/pypeclub/OpenPype/pull/3710) +- TVPaint: Defined as module [\#3707](https://github.com/pypeclub/OpenPype/pull/3707) +- StandalonePublisher: Define StandalonePublisher as module [\#3706](https://github.com/pypeclub/OpenPype/pull/3706) +- TrayPublisher: Define TrayPublisher as module [\#3705](https://github.com/pypeclub/OpenPype/pull/3705) +- General: Move context specific functions to context tools [\#3702](https://github.com/pypeclub/OpenPype/pull/3702) + +**Merged pull requests:** + +- Hiero: Define hiero as module [\#3717](https://github.com/pypeclub/OpenPype/pull/3717) +- Deadline: better logging for DL webservice failures [\#3694](https://github.com/pypeclub/OpenPype/pull/3694) +- Photoshop: resize saved images in ExtractReview for ffmpeg [\#3676](https://github.com/pypeclub/OpenPype/pull/3676) +- Nuke: Validation refactory to new publisher [\#3567](https://github.com/pypeclub/OpenPype/pull/3567) + +## [3.14.0](https://github.com/pypeclub/OpenPype/tree/3.14.0) (2022-08-18) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.13.0...3.14.0) + +**πŸ†• New features** + +- Maya: Build workfile by template [\#3578](https://github.com/pypeclub/OpenPype/pull/3578) +- Maya: Implementation of JSON layout for Unreal workflow [\#3353](https://github.com/pypeclub/OpenPype/pull/3353) +- Maya: Build workfile by template [\#3315](https://github.com/pypeclub/OpenPype/pull/3315) + +**πŸš€ Enhancements** + +- Ftrack: Addiotional component metadata [\#3685](https://github.com/pypeclub/OpenPype/pull/3685) +- Ftrack: Set task status on farm publishing [\#3680](https://github.com/pypeclub/OpenPype/pull/3680) +- Ftrack: Set task status on task creation in integrate hierarchy [\#3675](https://github.com/pypeclub/OpenPype/pull/3675) +- Maya: Disable rendering of all lights for render instances submitted through Deadline. [\#3661](https://github.com/pypeclub/OpenPype/pull/3661) +- General: Optimized OCIO configs [\#3650](https://github.com/pypeclub/OpenPype/pull/3650) + +**πŸ› Bug fixes** + +- General: Switch from hero version to versioned works [\#3691](https://github.com/pypeclub/OpenPype/pull/3691) +- General: Fix finding of last version [\#3656](https://github.com/pypeclub/OpenPype/pull/3656) +- General: Extract Review can scale with pixel aspect ratio [\#3644](https://github.com/pypeclub/OpenPype/pull/3644) +- Maya: Refactor moved usage of CreateRender settings [\#3643](https://github.com/pypeclub/OpenPype/pull/3643) +- General: Hero version representations have full context [\#3638](https://github.com/pypeclub/OpenPype/pull/3638) +- Nuke: color settings for render write node is working now [\#3632](https://github.com/pypeclub/OpenPype/pull/3632) +- Maya: FBX support for update in reference loader [\#3631](https://github.com/pypeclub/OpenPype/pull/3631) + +**πŸ”€ Refactored code** + +- General: Use client projects getter [\#3673](https://github.com/pypeclub/OpenPype/pull/3673) +- Resolve: Match folder structure to other hosts [\#3653](https://github.com/pypeclub/OpenPype/pull/3653) +- Maya: Hosts as modules [\#3647](https://github.com/pypeclub/OpenPype/pull/3647) +- TimersManager: Plugins are in timers manager module [\#3639](https://github.com/pypeclub/OpenPype/pull/3639) +- General: Move workfiles functions into pipeline [\#3637](https://github.com/pypeclub/OpenPype/pull/3637) +- General: Workfiles builder using query functions [\#3598](https://github.com/pypeclub/OpenPype/pull/3598) + +**Merged pull requests:** + +- Deadline: Global job pre load is not Pype 2 compatible [\#3666](https://github.com/pypeclub/OpenPype/pull/3666) +- Maya: Remove unused get current renderer logic [\#3645](https://github.com/pypeclub/OpenPype/pull/3645) +- Kitsu|Fix: Movie project type fails & first loop children names [\#3636](https://github.com/pypeclub/OpenPype/pull/3636) +- fix the bug of failing to extract look when UDIMs format used in AiImage [\#3628](https://github.com/pypeclub/OpenPype/pull/3628) + +## [3.13.0](https://github.com/pypeclub/OpenPype/tree/3.13.0) (2022-08-09) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.12.2...3.13.0) + +**πŸ†• New features** + +- Support for mutliple installed versions - 3.13 [\#3605](https://github.com/pypeclub/OpenPype/pull/3605) +- Traypublisher: simple editorial publishing [\#3492](https://github.com/pypeclub/OpenPype/pull/3492) + +**πŸš€ Enhancements** + +- Editorial: Mix audio use side file for ffmpeg filters [\#3630](https://github.com/pypeclub/OpenPype/pull/3630) +- Ftrack: Comment template can contain optional keys [\#3615](https://github.com/pypeclub/OpenPype/pull/3615) +- Ftrack: Add more metadata to ftrack components [\#3612](https://github.com/pypeclub/OpenPype/pull/3612) +- General: Add context to pyblish context [\#3594](https://github.com/pypeclub/OpenPype/pull/3594) +- Kitsu: Shot&Sequence name with prefix over appends [\#3593](https://github.com/pypeclub/OpenPype/pull/3593) +- Photoshop: implemented {layer} placeholder in subset template [\#3591](https://github.com/pypeclub/OpenPype/pull/3591) +- General: Python module appdirs from git [\#3589](https://github.com/pypeclub/OpenPype/pull/3589) +- Ftrack: Update ftrack api to 2.3.3 [\#3588](https://github.com/pypeclub/OpenPype/pull/3588) +- General: New Integrator small fixes [\#3583](https://github.com/pypeclub/OpenPype/pull/3583) +- Maya: Render Creator has configurable options. [\#3097](https://github.com/pypeclub/OpenPype/pull/3097) + +**πŸ› Bug fixes** + +- Maya: fix aov separator in Redshift [\#3625](https://github.com/pypeclub/OpenPype/pull/3625) +- Fix for multi-version build on Mac [\#3622](https://github.com/pypeclub/OpenPype/pull/3622) +- Ftrack: Sync hierarchical attributes can handle new created entities [\#3621](https://github.com/pypeclub/OpenPype/pull/3621) +- General: Extract review aspect ratio scale is calculated by ffmpeg [\#3620](https://github.com/pypeclub/OpenPype/pull/3620) +- Maya: Fix types of default settings [\#3617](https://github.com/pypeclub/OpenPype/pull/3617) +- Integrator: Don't force to have dot before frame [\#3611](https://github.com/pypeclub/OpenPype/pull/3611) +- AfterEffects: refactored integrate doesnt work formulti frame publishes [\#3610](https://github.com/pypeclub/OpenPype/pull/3610) +- Maya look data contents fails with custom attribute on group [\#3607](https://github.com/pypeclub/OpenPype/pull/3607) +- TrayPublisher: Fix wrong conflict merge [\#3600](https://github.com/pypeclub/OpenPype/pull/3600) +- Bugfix: Add OCIO as submodule to prepare for handling `maketx` color space conversion. [\#3590](https://github.com/pypeclub/OpenPype/pull/3590) +- Fix general settings environment variables resolution [\#3587](https://github.com/pypeclub/OpenPype/pull/3587) +- Editorial publishing workflow improvements [\#3580](https://github.com/pypeclub/OpenPype/pull/3580) +- General: Update imports in start script [\#3579](https://github.com/pypeclub/OpenPype/pull/3579) +- Nuke: render family integration consistency [\#3576](https://github.com/pypeclub/OpenPype/pull/3576) +- Ftrack: Handle missing published path in integrator [\#3570](https://github.com/pypeclub/OpenPype/pull/3570) +- Nuke: publish existing frames with slate with correct range [\#3555](https://github.com/pypeclub/OpenPype/pull/3555) + +**πŸ”€ Refactored code** + +- General: Plugin settings handled by plugins [\#3623](https://github.com/pypeclub/OpenPype/pull/3623) +- General: Naive implementation of document create, update, delete [\#3601](https://github.com/pypeclub/OpenPype/pull/3601) +- General: Use query functions in general code [\#3596](https://github.com/pypeclub/OpenPype/pull/3596) +- General: Separate extraction of template data into more functions [\#3574](https://github.com/pypeclub/OpenPype/pull/3574) +- General: Lib cleanup [\#3571](https://github.com/pypeclub/OpenPype/pull/3571) + +**Merged pull requests:** + +- Webpublisher: timeout for PS studio processing [\#3619](https://github.com/pypeclub/OpenPype/pull/3619) +- Core: translated validate\_containers.py into New publisher style [\#3614](https://github.com/pypeclub/OpenPype/pull/3614) +- Enable write color sets on animation publish automatically [\#3582](https://github.com/pypeclub/OpenPype/pull/3582) + +## [3.12.2](https://github.com/pypeclub/OpenPype/tree/3.12.2) (2022-07-27) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.12.1...3.12.2) + +### πŸ“– Documentation + +- Update website with more studios [\#3554](https://github.com/pypeclub/OpenPype/pull/3554) +- Documentation: Update publishing dev docs [\#3549](https://github.com/pypeclub/OpenPype/pull/3549) + +**πŸš€ Enhancements** + +- General: Global thumbnail extractor is ready for more cases [\#3561](https://github.com/pypeclub/OpenPype/pull/3561) +- Maya: add additional validators to Settings [\#3540](https://github.com/pypeclub/OpenPype/pull/3540) +- General: Interactive console in cli [\#3526](https://github.com/pypeclub/OpenPype/pull/3526) +- Ftrack: Automatic daily review session creation can define trigger hour [\#3516](https://github.com/pypeclub/OpenPype/pull/3516) +- Ftrack: add source into Note [\#3509](https://github.com/pypeclub/OpenPype/pull/3509) +- Ftrack: Trigger custom ftrack topic of project structure creation [\#3506](https://github.com/pypeclub/OpenPype/pull/3506) +- Settings UI: Add extract to file action on project view [\#3505](https://github.com/pypeclub/OpenPype/pull/3505) +- Add pack and unpack convenience scripts [\#3502](https://github.com/pypeclub/OpenPype/pull/3502) +- General: Event system [\#3499](https://github.com/pypeclub/OpenPype/pull/3499) +- NewPublisher: Keep plugins with mismatch target in report [\#3498](https://github.com/pypeclub/OpenPype/pull/3498) +- Nuke: load clip with options from settings [\#3497](https://github.com/pypeclub/OpenPype/pull/3497) +- TrayPublisher: implemented render\_mov\_batch [\#3486](https://github.com/pypeclub/OpenPype/pull/3486) +- Migrate basic families to the new Tray Publisher [\#3469](https://github.com/pypeclub/OpenPype/pull/3469) +- Enhance powershell build scripts [\#1827](https://github.com/pypeclub/OpenPype/pull/1827) + +**πŸ› Bug fixes** + +- Maya: fix Review image plane attribute [\#3569](https://github.com/pypeclub/OpenPype/pull/3569) +- Maya: Fix animated attributes \(ie. overscan\) on loaded cameras breaking review publishing. [\#3562](https://github.com/pypeclub/OpenPype/pull/3562) +- NewPublisher: Python 2 compatible html escape [\#3559](https://github.com/pypeclub/OpenPype/pull/3559) +- Remove invalid submodules from `/vendor` [\#3557](https://github.com/pypeclub/OpenPype/pull/3557) +- General: Remove hosts filter on integrator plugins [\#3556](https://github.com/pypeclub/OpenPype/pull/3556) +- Settings: Clean default values of environments [\#3550](https://github.com/pypeclub/OpenPype/pull/3550) +- Module interfaces: Fix import error [\#3547](https://github.com/pypeclub/OpenPype/pull/3547) +- Workfiles tool: Show of tool and it's flags [\#3539](https://github.com/pypeclub/OpenPype/pull/3539) +- General: Create workfile documents works again [\#3538](https://github.com/pypeclub/OpenPype/pull/3538) +- Additional fixes for powershell scripts [\#3525](https://github.com/pypeclub/OpenPype/pull/3525) +- Maya: Added wrapper around cmds.setAttr [\#3523](https://github.com/pypeclub/OpenPype/pull/3523) +- Nuke: double slate [\#3521](https://github.com/pypeclub/OpenPype/pull/3521) +- General: Fix hash of centos oiio archive [\#3519](https://github.com/pypeclub/OpenPype/pull/3519) +- Maya: Renderman display output fix [\#3514](https://github.com/pypeclub/OpenPype/pull/3514) +- TrayPublisher: Simple creation enhancements and fixes [\#3513](https://github.com/pypeclub/OpenPype/pull/3513) +- NewPublisher: Publish attributes are properly collected [\#3510](https://github.com/pypeclub/OpenPype/pull/3510) +- TrayPublisher: Make sure host name is filled [\#3504](https://github.com/pypeclub/OpenPype/pull/3504) +- NewPublisher: Groups work and enum multivalue [\#3501](https://github.com/pypeclub/OpenPype/pull/3501) + +**πŸ”€ Refactored code** + +- General: Use query functions in integrator [\#3563](https://github.com/pypeclub/OpenPype/pull/3563) +- General: Mongo core connection moved to client [\#3531](https://github.com/pypeclub/OpenPype/pull/3531) +- Refactor Integrate Asset [\#3530](https://github.com/pypeclub/OpenPype/pull/3530) +- General: Client docstrings cleanup [\#3529](https://github.com/pypeclub/OpenPype/pull/3529) +- General: Move load related functions into pipeline [\#3527](https://github.com/pypeclub/OpenPype/pull/3527) +- General: Get current context document functions [\#3522](https://github.com/pypeclub/OpenPype/pull/3522) +- Kitsu: Use query function from client [\#3496](https://github.com/pypeclub/OpenPype/pull/3496) +- TimersManager: Use query functions [\#3495](https://github.com/pypeclub/OpenPype/pull/3495) +- Deadline: Use query functions [\#3466](https://github.com/pypeclub/OpenPype/pull/3466) +- Refactor Integrate Asset [\#2898](https://github.com/pypeclub/OpenPype/pull/2898) + +**Merged pull requests:** + +- Maya: fix active pane loss [\#3566](https://github.com/pypeclub/OpenPype/pull/3566) + +## [3.12.1](https://github.com/pypeclub/OpenPype/tree/3.12.1) (2022-07-13) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.12.0...3.12.1) + +### πŸ“– Documentation + +- Docs: Added minimal permissions for MongoDB [\#3441](https://github.com/pypeclub/OpenPype/pull/3441) + +**πŸ†• New features** + +- Maya: Add VDB to Arnold loader [\#3433](https://github.com/pypeclub/OpenPype/pull/3433) + +**πŸš€ Enhancements** + +- TrayPublisher: Added more options for grouping of instances [\#3494](https://github.com/pypeclub/OpenPype/pull/3494) +- NewPublisher: Align creator attributes from top to bottom [\#3487](https://github.com/pypeclub/OpenPype/pull/3487) +- NewPublisher: Added ability to use label of instance [\#3484](https://github.com/pypeclub/OpenPype/pull/3484) +- General: Creator Plugins have access to project [\#3476](https://github.com/pypeclub/OpenPype/pull/3476) +- General: Better arguments order in creator init [\#3475](https://github.com/pypeclub/OpenPype/pull/3475) +- Ftrack: Trigger custom ftrack events on project creation and preparation [\#3465](https://github.com/pypeclub/OpenPype/pull/3465) +- Windows installer: Clean old files and add version subfolder [\#3445](https://github.com/pypeclub/OpenPype/pull/3445) +- Blender: Bugfix - Set fps properly on open [\#3426](https://github.com/pypeclub/OpenPype/pull/3426) +- Hiero: Add custom scripts menu [\#3425](https://github.com/pypeclub/OpenPype/pull/3425) +- Blender: pre pyside install for all platforms [\#3400](https://github.com/pypeclub/OpenPype/pull/3400) +- Maya: Add additional playblast options to review Extractor. [\#3384](https://github.com/pypeclub/OpenPype/pull/3384) +- Maya: Ability to set resolution for playblasts from asset, and override through review instance. [\#3360](https://github.com/pypeclub/OpenPype/pull/3360) +- Maya: Redshift Volume Loader Implement update, remove, switch + fix vdb sequence support [\#3197](https://github.com/pypeclub/OpenPype/pull/3197) +- Maya: Implement `iter_visible_nodes_in_range` for extracting Alembics [\#3100](https://github.com/pypeclub/OpenPype/pull/3100) + +**πŸ› Bug fixes** + +- TrayPublisher: Keep use instance label in list view [\#3493](https://github.com/pypeclub/OpenPype/pull/3493) +- General: Extract review use first frame of input sequence [\#3491](https://github.com/pypeclub/OpenPype/pull/3491) +- General: Fix Plist loading for application launch [\#3485](https://github.com/pypeclub/OpenPype/pull/3485) +- Nuke: Workfile tools open on start [\#3479](https://github.com/pypeclub/OpenPype/pull/3479) +- New Publisher: Disabled context change allows creation [\#3478](https://github.com/pypeclub/OpenPype/pull/3478) +- General: thumbnail extractor fix [\#3474](https://github.com/pypeclub/OpenPype/pull/3474) +- Kitsu: bugfix with sync-service ans publish plugins [\#3473](https://github.com/pypeclub/OpenPype/pull/3473) +- Flame: solved problem with multi-selected loading [\#3470](https://github.com/pypeclub/OpenPype/pull/3470) +- General: Fix query function in update logic [\#3468](https://github.com/pypeclub/OpenPype/pull/3468) +- Resolve: removed few bugs [\#3464](https://github.com/pypeclub/OpenPype/pull/3464) +- General: Delete old versions is safer when ftrack is disabled [\#3462](https://github.com/pypeclub/OpenPype/pull/3462) +- Nuke: fixing metadata slate TC difference [\#3455](https://github.com/pypeclub/OpenPype/pull/3455) +- Nuke: prerender reviewable fails [\#3450](https://github.com/pypeclub/OpenPype/pull/3450) +- Maya: fix hashing in Python 3 for tile rendering [\#3447](https://github.com/pypeclub/OpenPype/pull/3447) +- LogViewer: Escape html characters in log message [\#3443](https://github.com/pypeclub/OpenPype/pull/3443) +- Nuke: Slate frame is integrated [\#3427](https://github.com/pypeclub/OpenPype/pull/3427) +- Maya: Camera extra data - additional fix for \#3304 [\#3386](https://github.com/pypeclub/OpenPype/pull/3386) +- Maya: Handle excluding `model` family from frame range validator. [\#3370](https://github.com/pypeclub/OpenPype/pull/3370) + +**πŸ”€ Refactored code** + +- Maya: Merge animation + pointcache extractor logic [\#3461](https://github.com/pypeclub/OpenPype/pull/3461) +- Maya: Re-use `maintained_time` from lib [\#3460](https://github.com/pypeclub/OpenPype/pull/3460) +- General: Use query functions in global plugins [\#3459](https://github.com/pypeclub/OpenPype/pull/3459) +- Clockify: Use query functions in clockify actions [\#3458](https://github.com/pypeclub/OpenPype/pull/3458) +- General: Use query functions in rest api calls [\#3457](https://github.com/pypeclub/OpenPype/pull/3457) +- General: Use query functions in openpype lib functions [\#3454](https://github.com/pypeclub/OpenPype/pull/3454) +- General: Use query functions in load utils [\#3446](https://github.com/pypeclub/OpenPype/pull/3446) +- General: Move publish plugin and publish render abstractions [\#3442](https://github.com/pypeclub/OpenPype/pull/3442) +- General: Use Anatomy after move to pipeline [\#3436](https://github.com/pypeclub/OpenPype/pull/3436) +- General: Anatomy moved to pipeline [\#3435](https://github.com/pypeclub/OpenPype/pull/3435) +- Fusion: Use client query functions [\#3380](https://github.com/pypeclub/OpenPype/pull/3380) +- Resolve: Use client query functions [\#3379](https://github.com/pypeclub/OpenPype/pull/3379) +- General: Host implementation defined with class [\#3337](https://github.com/pypeclub/OpenPype/pull/3337) + +## [3.12.0](https://github.com/pypeclub/OpenPype/tree/3.12.0) (2022-06-28) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.11.1...3.12.0) + +### πŸ“– Documentation + +- Fix typo in documentation: pyenv on mac [\#3417](https://github.com/pypeclub/OpenPype/pull/3417) +- Linux: update OIIO package [\#3401](https://github.com/pypeclub/OpenPype/pull/3401) + +**πŸ†• New features** + +- Shotgrid: Add production beta of shotgrid integration [\#2921](https://github.com/pypeclub/OpenPype/pull/2921) + +**πŸš€ Enhancements** + +- Webserver: Added CORS middleware [\#3422](https://github.com/pypeclub/OpenPype/pull/3422) +- Attribute Defs UI: Files widget show what is allowed to drop in [\#3411](https://github.com/pypeclub/OpenPype/pull/3411) +- General: Add ability to change user value for templates [\#3366](https://github.com/pypeclub/OpenPype/pull/3366) +- Hosts: More options for in-host callbacks [\#3357](https://github.com/pypeclub/OpenPype/pull/3357) +- Multiverse: expose some settings to GUI [\#3350](https://github.com/pypeclub/OpenPype/pull/3350) +- Maya: Allow more data to be published along camera πŸŽ₯ [\#3304](https://github.com/pypeclub/OpenPype/pull/3304) +- Add root keys and project keys to create starting folder [\#2755](https://github.com/pypeclub/OpenPype/pull/2755) + +**πŸ› Bug fixes** + +- NewPublisher: Fix subset name change on change of creator plugin [\#3420](https://github.com/pypeclub/OpenPype/pull/3420) +- Bug: fix invalid avalon import [\#3418](https://github.com/pypeclub/OpenPype/pull/3418) +- Nuke: Fix keyword argument in query function [\#3414](https://github.com/pypeclub/OpenPype/pull/3414) +- Houdini: fix loading and updating vbd/bgeo sequences [\#3408](https://github.com/pypeclub/OpenPype/pull/3408) +- Nuke: Collect representation files based on Write [\#3407](https://github.com/pypeclub/OpenPype/pull/3407) +- General: Filter representations before integration start [\#3398](https://github.com/pypeclub/OpenPype/pull/3398) +- Maya: look collector typo [\#3392](https://github.com/pypeclub/OpenPype/pull/3392) +- TVPaint: Make sure exit code is set to not None [\#3382](https://github.com/pypeclub/OpenPype/pull/3382) +- Maya: vray device aspect ratio fix [\#3381](https://github.com/pypeclub/OpenPype/pull/3381) +- Flame: bunch of publishing issues [\#3377](https://github.com/pypeclub/OpenPype/pull/3377) +- Harmony: added unc path to zifile command in Harmony [\#3372](https://github.com/pypeclub/OpenPype/pull/3372) +- Standalone: settings improvements [\#3355](https://github.com/pypeclub/OpenPype/pull/3355) +- Nuke: Load full model hierarchy by default [\#3328](https://github.com/pypeclub/OpenPype/pull/3328) +- Nuke: multiple baking streams with correct slate [\#3245](https://github.com/pypeclub/OpenPype/pull/3245) +- Maya: fix image prefix warning in validator [\#3128](https://github.com/pypeclub/OpenPype/pull/3128) + +**πŸ”€ Refactored code** + +- Unreal: Use client query functions [\#3421](https://github.com/pypeclub/OpenPype/pull/3421) +- General: Move editorial lib to pipeline [\#3419](https://github.com/pypeclub/OpenPype/pull/3419) +- Kitsu: renaming to plural func sync\_all\_projects [\#3397](https://github.com/pypeclub/OpenPype/pull/3397) +- Houdini: Use client query functions [\#3395](https://github.com/pypeclub/OpenPype/pull/3395) +- Hiero: Use client query functions [\#3393](https://github.com/pypeclub/OpenPype/pull/3393) +- Nuke: Use client query functions [\#3391](https://github.com/pypeclub/OpenPype/pull/3391) +- Maya: Use client query functions [\#3385](https://github.com/pypeclub/OpenPype/pull/3385) +- Harmony: Use client query functions [\#3378](https://github.com/pypeclub/OpenPype/pull/3378) +- Celaction: Use client query functions [\#3376](https://github.com/pypeclub/OpenPype/pull/3376) +- Photoshop: Use client query functions [\#3375](https://github.com/pypeclub/OpenPype/pull/3375) +- AfterEffects: Use client query functions [\#3374](https://github.com/pypeclub/OpenPype/pull/3374) +- TVPaint: Use client query functions [\#3340](https://github.com/pypeclub/OpenPype/pull/3340) +- Ftrack: Use client query functions [\#3339](https://github.com/pypeclub/OpenPype/pull/3339) +- Standalone Publisher: Use client query functions [\#3330](https://github.com/pypeclub/OpenPype/pull/3330) + +**Merged pull requests:** + +- Sync Queue: Added far future value for null values for dates [\#3371](https://github.com/pypeclub/OpenPype/pull/3371) +- Maya - added support for single frame playblast review [\#3369](https://github.com/pypeclub/OpenPype/pull/3369) +- Houdini: Implement Redshift Proxy Export [\#3196](https://github.com/pypeclub/OpenPype/pull/3196) + +## [3.11.1](https://github.com/pypeclub/OpenPype/tree/3.11.1) (2022-06-20) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.11.0...3.11.1) + +**πŸ†• New features** + +- Flame: custom export temp folder [\#3346](https://github.com/pypeclub/OpenPype/pull/3346) +- Nuke: removing third-party plugins [\#3344](https://github.com/pypeclub/OpenPype/pull/3344) + +**πŸš€ Enhancements** + +- Pyblish Pype: Hiding/Close issues [\#3367](https://github.com/pypeclub/OpenPype/pull/3367) +- Ftrack: Removed requirement of pypeclub role from default settings [\#3354](https://github.com/pypeclub/OpenPype/pull/3354) +- Kitsu: Prevent crash on missing frames information [\#3352](https://github.com/pypeclub/OpenPype/pull/3352) +- Ftrack: Open browser from tray [\#3320](https://github.com/pypeclub/OpenPype/pull/3320) +- Enhancement: More control over thumbnail processing. [\#3259](https://github.com/pypeclub/OpenPype/pull/3259) + +**πŸ› Bug fixes** + +- Nuke: bake streams with slate on farm [\#3368](https://github.com/pypeclub/OpenPype/pull/3368) +- Harmony: audio validator has wrong logic [\#3364](https://github.com/pypeclub/OpenPype/pull/3364) +- Nuke: Fix missing variable in extract thumbnail [\#3363](https://github.com/pypeclub/OpenPype/pull/3363) +- Nuke: Fix precollect writes [\#3361](https://github.com/pypeclub/OpenPype/pull/3361) +- AE- fix validate\_scene\_settings and renderLocal [\#3358](https://github.com/pypeclub/OpenPype/pull/3358) +- deadline: fixing misidentification of revieables [\#3356](https://github.com/pypeclub/OpenPype/pull/3356) +- General: Create only one thumbnail per instance [\#3351](https://github.com/pypeclub/OpenPype/pull/3351) +- nuke: adding extract thumbnail settings 3.10 [\#3347](https://github.com/pypeclub/OpenPype/pull/3347) +- General: Fix last version function [\#3345](https://github.com/pypeclub/OpenPype/pull/3345) +- Deadline: added OPENPYPE\_MONGO to filter [\#3336](https://github.com/pypeclub/OpenPype/pull/3336) +- Nuke: fixing farm publishing if review is disabled [\#3306](https://github.com/pypeclub/OpenPype/pull/3306) +- Maya: Fix Yeti errors on Create, Publish and Load [\#3198](https://github.com/pypeclub/OpenPype/pull/3198) + +**πŸ”€ Refactored code** + +- Webpublisher: Use client query functions [\#3333](https://github.com/pypeclub/OpenPype/pull/3333) + +## [3.11.0](https://github.com/pypeclub/OpenPype/tree/3.11.0) (2022-06-17) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.10.0...3.11.0) + +### πŸ“– Documentation + +- Documentation: Add app key to template documentation [\#3299](https://github.com/pypeclub/OpenPype/pull/3299) +- doc: adding royal render and multiverse to the web site [\#3285](https://github.com/pypeclub/OpenPype/pull/3285) +- Module: Kitsu module [\#2650](https://github.com/pypeclub/OpenPype/pull/2650) + +**πŸ†• New features** + +- Multiverse: fixed composition write, full docs, cosmetics [\#3178](https://github.com/pypeclub/OpenPype/pull/3178) + +**πŸš€ Enhancements** + +- Settings: Settings can be extracted from UI [\#3323](https://github.com/pypeclub/OpenPype/pull/3323) +- updated poetry installation source [\#3316](https://github.com/pypeclub/OpenPype/pull/3316) +- Ftrack: Action to easily create daily review session [\#3310](https://github.com/pypeclub/OpenPype/pull/3310) +- TVPaint: Extractor use mark in/out range to render [\#3309](https://github.com/pypeclub/OpenPype/pull/3309) +- Ftrack: Delivery action can work on ReviewSessions [\#3307](https://github.com/pypeclub/OpenPype/pull/3307) +- Maya: Look assigner UI improvements [\#3298](https://github.com/pypeclub/OpenPype/pull/3298) +- Ftrack: Action to transfer values of hierarchical attributes [\#3284](https://github.com/pypeclub/OpenPype/pull/3284) +- Maya: better handling of legacy review subsets names [\#3269](https://github.com/pypeclub/OpenPype/pull/3269) +- General: Updated windows oiio tool [\#3268](https://github.com/pypeclub/OpenPype/pull/3268) +- Unreal: add support for skeletalMesh and staticMesh to loaders [\#3267](https://github.com/pypeclub/OpenPype/pull/3267) +- Maya: reference loaders could store placeholder in referenced url [\#3264](https://github.com/pypeclub/OpenPype/pull/3264) +- TVPaint: Init file for TVPaint worker also handle guideline images [\#3250](https://github.com/pypeclub/OpenPype/pull/3250) +- Nuke: Change default icon path in settings [\#3247](https://github.com/pypeclub/OpenPype/pull/3247) +- Maya: publishing of animation and pointcache on a farm [\#3225](https://github.com/pypeclub/OpenPype/pull/3225) +- Maya: Look assigner UI improvements [\#3208](https://github.com/pypeclub/OpenPype/pull/3208) +- Nuke: add pointcache and animation to loader [\#3186](https://github.com/pypeclub/OpenPype/pull/3186) +- Nuke: Add a gizmo menu [\#3172](https://github.com/pypeclub/OpenPype/pull/3172) +- Support for Unreal 5 [\#3122](https://github.com/pypeclub/OpenPype/pull/3122) + +**πŸ› Bug fixes** + +- General: Handle empty source key on instance [\#3342](https://github.com/pypeclub/OpenPype/pull/3342) +- Houdini: Fix Houdini VDB manage update wrong file attribute name [\#3322](https://github.com/pypeclub/OpenPype/pull/3322) +- Nuke: anatomy compatibility issue hacks [\#3321](https://github.com/pypeclub/OpenPype/pull/3321) +- hiero: otio p3 compatibility issue - metadata on effect use update 3.11 [\#3314](https://github.com/pypeclub/OpenPype/pull/3314) +- General: Vendorized modules for Python 2 and update poetry lock [\#3305](https://github.com/pypeclub/OpenPype/pull/3305) +- Fix - added local targets to install host [\#3303](https://github.com/pypeclub/OpenPype/pull/3303) +- Settings: Add missing default settings for nuke gizmo [\#3301](https://github.com/pypeclub/OpenPype/pull/3301) +- Maya: Fix swaped width and height in reviews [\#3300](https://github.com/pypeclub/OpenPype/pull/3300) +- Maya: point cache publish handles Maya instances [\#3297](https://github.com/pypeclub/OpenPype/pull/3297) +- Global: extract review slate issues [\#3286](https://github.com/pypeclub/OpenPype/pull/3286) +- Webpublisher: return only active projects in ProjectsEndpoint [\#3281](https://github.com/pypeclub/OpenPype/pull/3281) +- Hiero: add support for task tags 3.10.x [\#3279](https://github.com/pypeclub/OpenPype/pull/3279) +- General: Fix Oiio tool path resolving [\#3278](https://github.com/pypeclub/OpenPype/pull/3278) +- Maya: Fix udim support for e.g. uppercase \ tag [\#3266](https://github.com/pypeclub/OpenPype/pull/3266) +- Nuke: bake reformat was failing on string type [\#3261](https://github.com/pypeclub/OpenPype/pull/3261) +- Maya: hotfix Pxr multitexture in looks [\#3260](https://github.com/pypeclub/OpenPype/pull/3260) +- Unreal: Fix Camera Loading if Layout is missing [\#3255](https://github.com/pypeclub/OpenPype/pull/3255) +- Unreal: Fixed Animation loading in UE5 [\#3240](https://github.com/pypeclub/OpenPype/pull/3240) +- Unreal: Fixed Render creation in UE5 [\#3239](https://github.com/pypeclub/OpenPype/pull/3239) +- Unreal: Fixed Camera loading in UE5 [\#3238](https://github.com/pypeclub/OpenPype/pull/3238) +- Flame: debugging [\#3224](https://github.com/pypeclub/OpenPype/pull/3224) +- add silent audio to slate [\#3162](https://github.com/pypeclub/OpenPype/pull/3162) +- Add timecode to slate [\#2929](https://github.com/pypeclub/OpenPype/pull/2929) + +**πŸ”€ Refactored code** + +- Blender: Use client query functions [\#3331](https://github.com/pypeclub/OpenPype/pull/3331) +- General: Define query functions [\#3288](https://github.com/pypeclub/OpenPype/pull/3288) + +**Merged pull requests:** + +- Maya: add pointcache family to gpu cache loader [\#3318](https://github.com/pypeclub/OpenPype/pull/3318) +- Maya look: skip empty file attributes [\#3274](https://github.com/pypeclub/OpenPype/pull/3274) + +## [3.10.0](https://github.com/pypeclub/OpenPype/tree/3.10.0) (2022-05-26) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.9.8...3.10.0) + +### πŸ“– Documentation + +- Docs: add all-contributors config and initial list [\#3094](https://github.com/pypeclub/OpenPype/pull/3094) +- Nuke docs with videos [\#3052](https://github.com/pypeclub/OpenPype/pull/3052) + +**πŸ†• New features** + +- General: OpenPype modules publish plugins are registered in host [\#3180](https://github.com/pypeclub/OpenPype/pull/3180) +- General: Creator plugins from addons can be registered [\#3179](https://github.com/pypeclub/OpenPype/pull/3179) +- Ftrack: Single image reviewable [\#3157](https://github.com/pypeclub/OpenPype/pull/3157) +- Nuke: Expose write attributes to settings [\#3123](https://github.com/pypeclub/OpenPype/pull/3123) +- Hiero: Initial frame publish support [\#3106](https://github.com/pypeclub/OpenPype/pull/3106) +- Unreal: Render Publishing [\#2917](https://github.com/pypeclub/OpenPype/pull/2917) +- AfterEffects: Implemented New Publisher [\#2838](https://github.com/pypeclub/OpenPype/pull/2838) +- Unreal: Rendering implementation [\#2410](https://github.com/pypeclub/OpenPype/pull/2410) + +**πŸš€ Enhancements** + +- Maya: FBX camera export [\#3253](https://github.com/pypeclub/OpenPype/pull/3253) +- General: updating common vendor `scriptmenu` to 1.5.2 [\#3246](https://github.com/pypeclub/OpenPype/pull/3246) +- Project Manager: Allow to paste Tasks into multiple assets at the same time [\#3226](https://github.com/pypeclub/OpenPype/pull/3226) +- Project manager: Sped up project load [\#3216](https://github.com/pypeclub/OpenPype/pull/3216) +- Loader UI: Speed issues of loader with sync server [\#3199](https://github.com/pypeclub/OpenPype/pull/3199) +- Looks: add basic support for Renderman [\#3190](https://github.com/pypeclub/OpenPype/pull/3190) +- Maya: added clean\_import option to Import loader [\#3181](https://github.com/pypeclub/OpenPype/pull/3181) +- Add the scripts menu definition to nuke [\#3168](https://github.com/pypeclub/OpenPype/pull/3168) +- Maya: add maya 2023 to default applications [\#3167](https://github.com/pypeclub/OpenPype/pull/3167) +- Compressed bgeo publishing in SAP and Houdini loader [\#3153](https://github.com/pypeclub/OpenPype/pull/3153) +- General: Add 'dataclasses' to required python modules [\#3149](https://github.com/pypeclub/OpenPype/pull/3149) +- Hooks: Tweak logging grammar [\#3147](https://github.com/pypeclub/OpenPype/pull/3147) +- Nuke: settings for reformat node in CreateWriteRender node [\#3143](https://github.com/pypeclub/OpenPype/pull/3143) +- Houdini: Add loader for alembic through Alembic Archive node [\#3140](https://github.com/pypeclub/OpenPype/pull/3140) +- Publisher: UI Modifications and fixes [\#3139](https://github.com/pypeclub/OpenPype/pull/3139) +- General: Simplified OP modules/addons import [\#3137](https://github.com/pypeclub/OpenPype/pull/3137) +- Terminal: Tweak coloring of TrayModuleManager logging enabled states [\#3133](https://github.com/pypeclub/OpenPype/pull/3133) +- General: Cleanup some Loader docstrings [\#3131](https://github.com/pypeclub/OpenPype/pull/3131) +- Nuke: render instance with subset name filtered overrides [\#3117](https://github.com/pypeclub/OpenPype/pull/3117) +- Unreal: Layout and Camera update and remove functions reimplemented and improvements [\#3116](https://github.com/pypeclub/OpenPype/pull/3116) +- Settings: Remove environment groups from settings [\#3115](https://github.com/pypeclub/OpenPype/pull/3115) +- TVPaint: Match renderlayer key with other hosts [\#3110](https://github.com/pypeclub/OpenPype/pull/3110) +- Ftrack: AssetVersion status on publish [\#3108](https://github.com/pypeclub/OpenPype/pull/3108) +- Tray publisher: Simple families from settings [\#3105](https://github.com/pypeclub/OpenPype/pull/3105) +- Local Settings UI: Overlay messages on save and reset [\#3104](https://github.com/pypeclub/OpenPype/pull/3104) +- General: Remove repos related logic [\#3087](https://github.com/pypeclub/OpenPype/pull/3087) +- Standalone publisher: add support for bgeo and vdb [\#3080](https://github.com/pypeclub/OpenPype/pull/3080) +- Houdini: Fix FPS + outdated content pop-ups [\#3079](https://github.com/pypeclub/OpenPype/pull/3079) +- General: Add global log verbose arguments [\#3070](https://github.com/pypeclub/OpenPype/pull/3070) +- Flame: extract presets distribution [\#3063](https://github.com/pypeclub/OpenPype/pull/3063) +- Update collect\_render.py [\#3055](https://github.com/pypeclub/OpenPype/pull/3055) +- SiteSync: Added compute\_resource\_sync\_sites to sync\_server\_module [\#2983](https://github.com/pypeclub/OpenPype/pull/2983) +- Maya: Implement Hardware Renderer 2.0 support for Render Products [\#2611](https://github.com/pypeclub/OpenPype/pull/2611) + +**πŸ› Bug fixes** + +- nuke: use framerange issue [\#3254](https://github.com/pypeclub/OpenPype/pull/3254) +- Ftrack: Chunk sizes for queries has minimal condition [\#3244](https://github.com/pypeclub/OpenPype/pull/3244) +- Maya: renderman displays needs to be filtered [\#3242](https://github.com/pypeclub/OpenPype/pull/3242) +- Ftrack: Validate that the user exists on ftrack [\#3237](https://github.com/pypeclub/OpenPype/pull/3237) +- Maya: Fix support for multiple resolutions [\#3236](https://github.com/pypeclub/OpenPype/pull/3236) +- TVPaint: Look for more groups than 12 [\#3228](https://github.com/pypeclub/OpenPype/pull/3228) +- Hiero: debugging frame range and other 3.10 [\#3222](https://github.com/pypeclub/OpenPype/pull/3222) +- Project Manager: Fix persistent editors on project change [\#3218](https://github.com/pypeclub/OpenPype/pull/3218) +- Deadline: instance data overwrite fix [\#3214](https://github.com/pypeclub/OpenPype/pull/3214) +- Ftrack: Push hierarchical attributes action works [\#3210](https://github.com/pypeclub/OpenPype/pull/3210) +- Standalone Publisher: Always create new representation for thumbnail [\#3203](https://github.com/pypeclub/OpenPype/pull/3203) +- Photoshop: skip collector when automatic testing [\#3202](https://github.com/pypeclub/OpenPype/pull/3202) +- Nuke: render/workfile version sync doesn't work on farm [\#3185](https://github.com/pypeclub/OpenPype/pull/3185) +- Ftrack: Review image only if there are no mp4 reviews [\#3183](https://github.com/pypeclub/OpenPype/pull/3183) +- Ftrack: Locations deepcopy issue [\#3177](https://github.com/pypeclub/OpenPype/pull/3177) +- General: Avoid creating multiple thumbnails [\#3176](https://github.com/pypeclub/OpenPype/pull/3176) +- General/Hiero: better clip duration calculation [\#3169](https://github.com/pypeclub/OpenPype/pull/3169) +- General: Oiio conversion for ffmpeg checks for invalid characters [\#3166](https://github.com/pypeclub/OpenPype/pull/3166) +- Fix for attaching render to subset [\#3164](https://github.com/pypeclub/OpenPype/pull/3164) +- Harmony: fixed missing task name in render instance [\#3163](https://github.com/pypeclub/OpenPype/pull/3163) +- Ftrack: Action delete old versions formatting works [\#3152](https://github.com/pypeclub/OpenPype/pull/3152) +- Deadline: fix the output directory [\#3144](https://github.com/pypeclub/OpenPype/pull/3144) +- General: New Session schema [\#3141](https://github.com/pypeclub/OpenPype/pull/3141) +- General: Missing version on headless mode crash properly [\#3136](https://github.com/pypeclub/OpenPype/pull/3136) +- TVPaint: Composite layers in reversed order [\#3135](https://github.com/pypeclub/OpenPype/pull/3135) +- Nuke: fixing default settings for workfile builder loaders [\#3120](https://github.com/pypeclub/OpenPype/pull/3120) +- Nuke: fix anatomy imageio regex default [\#3119](https://github.com/pypeclub/OpenPype/pull/3119) +- General: Python 3 compatibility in queries [\#3112](https://github.com/pypeclub/OpenPype/pull/3112) +- General: TemplateResult can be copied [\#3099](https://github.com/pypeclub/OpenPype/pull/3099) +- General: Collect loaded versions skips not existing representations [\#3095](https://github.com/pypeclub/OpenPype/pull/3095) +- RoyalRender Control Submission - AVALON\_APP\_NAME default [\#3091](https://github.com/pypeclub/OpenPype/pull/3091) +- Ftrack: Update Create Folders action [\#3089](https://github.com/pypeclub/OpenPype/pull/3089) +- Maya: Collect Render fix any render cameras check [\#3088](https://github.com/pypeclub/OpenPype/pull/3088) +- Project Manager: Avoid unnecessary updates of asset documents [\#3083](https://github.com/pypeclub/OpenPype/pull/3083) +- Standalone publisher: Fix plugins install [\#3077](https://github.com/pypeclub/OpenPype/pull/3077) +- General: Extract review sequence is not converted with same names [\#3076](https://github.com/pypeclub/OpenPype/pull/3076) +- Webpublisher: Use variant value [\#3068](https://github.com/pypeclub/OpenPype/pull/3068) +- Nuke: Add aov matching even for remainder and prerender [\#3060](https://github.com/pypeclub/OpenPype/pull/3060) +- Fix support for Renderman in Maya [\#3006](https://github.com/pypeclub/OpenPype/pull/3006) + +**πŸ”€ Refactored code** + +- Avalon repo removed from Jobs workflow [\#3193](https://github.com/pypeclub/OpenPype/pull/3193) +- General: Remove remaining imports from avalon [\#3130](https://github.com/pypeclub/OpenPype/pull/3130) +- General: Move mongo db logic and remove avalon repository [\#3066](https://github.com/pypeclub/OpenPype/pull/3066) +- General: Move host install [\#3009](https://github.com/pypeclub/OpenPype/pull/3009) + +**Merged pull requests:** + +- Harmony: message length in 21.1 [\#3257](https://github.com/pypeclub/OpenPype/pull/3257) +- Harmony: 21.1 fix [\#3249](https://github.com/pypeclub/OpenPype/pull/3249) +- Maya: added jpg to filter for Image Plane Loader [\#3223](https://github.com/pypeclub/OpenPype/pull/3223) +- Webpublisher: replace space by underscore in subset names [\#3160](https://github.com/pypeclub/OpenPype/pull/3160) +- StandalonePublisher: removed Extract Background plugins [\#3093](https://github.com/pypeclub/OpenPype/pull/3093) +- Nuke: added suspend\_publish knob [\#3078](https://github.com/pypeclub/OpenPype/pull/3078) +- Bump async from 2.6.3 to 2.6.4 in /website [\#3065](https://github.com/pypeclub/OpenPype/pull/3065) +- SiteSync: Download all workfile inputs [\#2966](https://github.com/pypeclub/OpenPype/pull/2966) +- Photoshop: New Publisher [\#2933](https://github.com/pypeclub/OpenPype/pull/2933) +- Bump pillow from 9.0.0 to 9.0.1 [\#2880](https://github.com/pypeclub/OpenPype/pull/2880) +- AfterEffects: Allow configuration of default variant via Settings [\#2856](https://github.com/pypeclub/OpenPype/pull/2856) + +## [3.9.8](https://github.com/pypeclub/OpenPype/tree/3.9.8) (2022-05-19) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.9.7...3.9.8) + +## [3.9.7](https://github.com/pypeclub/OpenPype/tree/3.9.7) (2022-05-11) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.9.6...3.9.7) + +## [3.9.6](https://github.com/pypeclub/OpenPype/tree/3.9.6) (2022-05-03) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.9.5...3.9.6) + +## [3.9.5](https://github.com/pypeclub/OpenPype/tree/3.9.5) (2022-04-25) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.9.4...3.9.5) + +## [3.9.4](https://github.com/pypeclub/OpenPype/tree/3.9.4) (2022-04-15) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.9.3...3.9.4) + +### πŸ“– Documentation + +- Documentation: more info about Tasks [\#3062](https://github.com/pypeclub/OpenPype/pull/3062) +- Documentation: Python requirements to 3.7.9 [\#3035](https://github.com/pypeclub/OpenPype/pull/3035) +- Website Docs: Remove unused pages [\#2974](https://github.com/pypeclub/OpenPype/pull/2974) + +**πŸ†• New features** + +- General: Local overrides for environment variables [\#3045](https://github.com/pypeclub/OpenPype/pull/3045) +- Flame: Flare integration preparation [\#2928](https://github.com/pypeclub/OpenPype/pull/2928) + +**πŸš€ Enhancements** + +- TVPaint: Added init file for worker to triggers missing sound file dialog [\#3053](https://github.com/pypeclub/OpenPype/pull/3053) +- Ftrack: Custom attributes can be filled in slate values [\#3036](https://github.com/pypeclub/OpenPype/pull/3036) +- Resolve environment variable in google drive credential path [\#3008](https://github.com/pypeclub/OpenPype/pull/3008) + +**πŸ› Bug fixes** + +- GitHub: Updated push-protected action in github workflow [\#3064](https://github.com/pypeclub/OpenPype/pull/3064) +- Nuke: Typos in imports from Nuke implementation [\#3061](https://github.com/pypeclub/OpenPype/pull/3061) +- Hotfix: fixing deadline job publishing [\#3059](https://github.com/pypeclub/OpenPype/pull/3059) +- General: Extract Review handle invalid characters for ffmpeg [\#3050](https://github.com/pypeclub/OpenPype/pull/3050) +- Slate Review: Support to keep format on slate concatenation [\#3049](https://github.com/pypeclub/OpenPype/pull/3049) +- Webpublisher: fix processing of workfile [\#3048](https://github.com/pypeclub/OpenPype/pull/3048) +- Ftrack: Integrate ftrack api fix [\#3044](https://github.com/pypeclub/OpenPype/pull/3044) +- Webpublisher - removed wrong hardcoded family [\#3043](https://github.com/pypeclub/OpenPype/pull/3043) +- LibraryLoader: Use current project for asset query in families filter [\#3042](https://github.com/pypeclub/OpenPype/pull/3042) +- SiteSync: Providers ignore that site is disabled [\#3041](https://github.com/pypeclub/OpenPype/pull/3041) +- Unreal: Creator import fixes [\#3040](https://github.com/pypeclub/OpenPype/pull/3040) +- SiteSync: fix transitive alternate sites, fix dropdown in Local Settings [\#3018](https://github.com/pypeclub/OpenPype/pull/3018) +- Maya: invalid review flag on rendered AOVs [\#2915](https://github.com/pypeclub/OpenPype/pull/2915) + +**Merged pull requests:** + +- Deadline: reworked pools assignment [\#3051](https://github.com/pypeclub/OpenPype/pull/3051) +- Houdini: Avoid ImportError on `hdefereval` when Houdini runs without UI [\#2987](https://github.com/pypeclub/OpenPype/pull/2987) + +## [3.9.3](https://github.com/pypeclub/OpenPype/tree/3.9.3) (2022-04-07) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.9.2...3.9.3) + +### πŸ“– Documentation + +- Documentation: Added mention of adding My Drive as a root [\#2999](https://github.com/pypeclub/OpenPype/pull/2999) +- Website Docs: Manager Ftrack fix broken links [\#2979](https://github.com/pypeclub/OpenPype/pull/2979) +- Docs: Added MongoDB requirements [\#2951](https://github.com/pypeclub/OpenPype/pull/2951) +- Documentation: New publisher develop docs [\#2896](https://github.com/pypeclub/OpenPype/pull/2896) + +**πŸ†• New features** + +- Ftrack: Add description integrator [\#3027](https://github.com/pypeclub/OpenPype/pull/3027) +- nuke: bypass baking [\#2992](https://github.com/pypeclub/OpenPype/pull/2992) +- Publishing textures for Unreal [\#2988](https://github.com/pypeclub/OpenPype/pull/2988) +- Maya to Unreal: Static and Skeletal Meshes [\#2978](https://github.com/pypeclub/OpenPype/pull/2978) +- Multiverse: Initial Support [\#2908](https://github.com/pypeclub/OpenPype/pull/2908) + +**πŸš€ Enhancements** + +- General: default workfile subset name for workfile [\#3011](https://github.com/pypeclub/OpenPype/pull/3011) +- Ftrack: Add more options for note text of integrate ftrack note [\#3025](https://github.com/pypeclub/OpenPype/pull/3025) +- Console Interpreter: Changed how console splitter size are reused on show [\#3016](https://github.com/pypeclub/OpenPype/pull/3016) +- Deadline: Use more suitable name for sequence review logic [\#3015](https://github.com/pypeclub/OpenPype/pull/3015) +- Nuke: add concurrency attr to deadline job [\#3005](https://github.com/pypeclub/OpenPype/pull/3005) +- Photoshop: create image without instance [\#3001](https://github.com/pypeclub/OpenPype/pull/3001) +- TVPaint: Render scene family [\#3000](https://github.com/pypeclub/OpenPype/pull/3000) +- Deadline: priority configurable in Maya jobs [\#2995](https://github.com/pypeclub/OpenPype/pull/2995) +- Nuke: ReviewDataMov Read RAW attribute [\#2985](https://github.com/pypeclub/OpenPype/pull/2985) +- General: `METADATA_KEYS` constant as `frozenset` for optimal immutable lookup [\#2980](https://github.com/pypeclub/OpenPype/pull/2980) +- General: Tools with host filters [\#2975](https://github.com/pypeclub/OpenPype/pull/2975) +- Hero versions: Use custom templates [\#2967](https://github.com/pypeclub/OpenPype/pull/2967) +- Slack: Added configurable maximum file size of review upload to Slack [\#2945](https://github.com/pypeclub/OpenPype/pull/2945) +- NewPublisher: Prepared implementation of optional pyblish plugin [\#2943](https://github.com/pypeclub/OpenPype/pull/2943) +- TVPaint: Extractor to convert PNG into EXR [\#2942](https://github.com/pypeclub/OpenPype/pull/2942) +- Workfiles tool: Save as published workfiles [\#2937](https://github.com/pypeclub/OpenPype/pull/2937) +- Workfiles: Open published workfiles [\#2925](https://github.com/pypeclub/OpenPype/pull/2925) +- General: Default modules loaded dynamically [\#2923](https://github.com/pypeclub/OpenPype/pull/2923) +- CI: change the version bump logic [\#2919](https://github.com/pypeclub/OpenPype/pull/2919) +- Deadline: Add headless argument [\#2916](https://github.com/pypeclub/OpenPype/pull/2916) +- Nuke: Add no-audio Tag [\#2911](https://github.com/pypeclub/OpenPype/pull/2911) +- Ftrack: Fill workfile in custom attribute [\#2906](https://github.com/pypeclub/OpenPype/pull/2906) +- Nuke: improving readability [\#2903](https://github.com/pypeclub/OpenPype/pull/2903) +- Settings UI: Add simple tooltips for settings entities [\#2901](https://github.com/pypeclub/OpenPype/pull/2901) + +**πŸ› Bug fixes** + +- General: Fix validate asset docs plug-in filename and class name [\#3029](https://github.com/pypeclub/OpenPype/pull/3029) +- Deadline: Fixed default value of use sequence for review [\#3033](https://github.com/pypeclub/OpenPype/pull/3033) +- Settings UI: Version column can be extended so version are visible [\#3032](https://github.com/pypeclub/OpenPype/pull/3032) +- General: Fix import after movements [\#3028](https://github.com/pypeclub/OpenPype/pull/3028) +- Harmony: Added creating subset name for workfile from template [\#3024](https://github.com/pypeclub/OpenPype/pull/3024) +- AfterEffects: Added creating subset name for workfile from template [\#3023](https://github.com/pypeclub/OpenPype/pull/3023) +- General: Add example addons to ignored [\#3022](https://github.com/pypeclub/OpenPype/pull/3022) +- Maya: Remove missing import [\#3017](https://github.com/pypeclub/OpenPype/pull/3017) +- Ftrack: multiple reviewable componets [\#3012](https://github.com/pypeclub/OpenPype/pull/3012) +- Tray publisher: Fixes after code movement [\#3010](https://github.com/pypeclub/OpenPype/pull/3010) +- Hosts: Remove path existence checks in 'add\_implementation\_envs' [\#3004](https://github.com/pypeclub/OpenPype/pull/3004) +- Nuke: fixing unicode type detection in effect loaders [\#3002](https://github.com/pypeclub/OpenPype/pull/3002) +- Fix - remove doubled dot in workfile created from template [\#2998](https://github.com/pypeclub/OpenPype/pull/2998) +- Nuke: removing redundant Ftrack asset when farm publishing [\#2996](https://github.com/pypeclub/OpenPype/pull/2996) +- PS: fix renaming subset incorrectly in PS [\#2991](https://github.com/pypeclub/OpenPype/pull/2991) +- Fix: Disable setuptools auto discovery [\#2990](https://github.com/pypeclub/OpenPype/pull/2990) +- AEL: fix opening existing workfile if no scene opened [\#2989](https://github.com/pypeclub/OpenPype/pull/2989) +- Maya: Don't do hardlinks on windows for look publishing [\#2986](https://github.com/pypeclub/OpenPype/pull/2986) +- Settings UI: Fix version completer on linux [\#2981](https://github.com/pypeclub/OpenPype/pull/2981) +- Photoshop: Fix creation of subset names in PS review and workfile [\#2969](https://github.com/pypeclub/OpenPype/pull/2969) +- Slack: Added default for review\_upload\_limit for Slack [\#2965](https://github.com/pypeclub/OpenPype/pull/2965) +- General: OIIO conversion for ffmeg can handle sequences [\#2958](https://github.com/pypeclub/OpenPype/pull/2958) +- Settings: Conditional dictionary avoid invalid logs [\#2956](https://github.com/pypeclub/OpenPype/pull/2956) +- General: Smaller fixes and typos [\#2950](https://github.com/pypeclub/OpenPype/pull/2950) +- LogViewer: Don't refresh on initialization [\#2949](https://github.com/pypeclub/OpenPype/pull/2949) +- nuke: python3 compatibility issue with `iteritems` [\#2948](https://github.com/pypeclub/OpenPype/pull/2948) +- General: anatomy data with correct task short key [\#2947](https://github.com/pypeclub/OpenPype/pull/2947) +- SceneInventory: Fix imports in UI [\#2944](https://github.com/pypeclub/OpenPype/pull/2944) +- Slack: add generic exception [\#2941](https://github.com/pypeclub/OpenPype/pull/2941) +- General: Python specific vendor paths on env injection [\#2939](https://github.com/pypeclub/OpenPype/pull/2939) +- General: More fail safe delete old versions [\#2936](https://github.com/pypeclub/OpenPype/pull/2936) +- Settings UI: Collapsed of collapsible wrapper works as expected [\#2934](https://github.com/pypeclub/OpenPype/pull/2934) +- Maya: Do not pass `set` to maya commands \(fixes support for older maya versions\) [\#2932](https://github.com/pypeclub/OpenPype/pull/2932) +- General: Don't print log record on OSError [\#2926](https://github.com/pypeclub/OpenPype/pull/2926) +- Hiero: Fix import of 'register\_event\_callback' [\#2924](https://github.com/pypeclub/OpenPype/pull/2924) +- Flame: centos related debugging [\#2922](https://github.com/pypeclub/OpenPype/pull/2922) +- Ftrack: Missing Ftrack id after editorial publish [\#2905](https://github.com/pypeclub/OpenPype/pull/2905) +- AfterEffects: Fix rendering for single frame in DL [\#2875](https://github.com/pypeclub/OpenPype/pull/2875) + +**πŸ”€ Refactored code** + +- General: Move plugins register and discover [\#2935](https://github.com/pypeclub/OpenPype/pull/2935) +- General: Move Attribute Definitions from pipeline [\#2931](https://github.com/pypeclub/OpenPype/pull/2931) +- General: Removed silo references and terminal splash [\#2927](https://github.com/pypeclub/OpenPype/pull/2927) +- General: Move pipeline constants to OpenPype [\#2918](https://github.com/pypeclub/OpenPype/pull/2918) +- General: Move formatting and workfile functions [\#2914](https://github.com/pypeclub/OpenPype/pull/2914) +- General: Move remaining plugins from avalon [\#2912](https://github.com/pypeclub/OpenPype/pull/2912) + +**Merged pull requests:** + +- Maya: Allow to select invalid camera contents if no cameras found [\#3030](https://github.com/pypeclub/OpenPype/pull/3030) +- Bump paramiko from 2.9.2 to 2.10.1 [\#2973](https://github.com/pypeclub/OpenPype/pull/2973) +- Bump minimist from 1.2.5 to 1.2.6 in /website [\#2954](https://github.com/pypeclub/OpenPype/pull/2954) +- Bump node-forge from 1.2.1 to 1.3.0 in /website [\#2953](https://github.com/pypeclub/OpenPype/pull/2953) +- Maya - added transparency into review creator [\#2952](https://github.com/pypeclub/OpenPype/pull/2952) + +## [3.9.2](https://github.com/pypeclub/OpenPype/tree/3.9.2) (2022-04-04) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.9.1...3.9.2) + +## [3.9.1](https://github.com/pypeclub/OpenPype/tree/3.9.1) (2022-03-18) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.9.0...3.9.1) + +**πŸš€ Enhancements** + +- General: Change how OPENPYPE\_DEBUG value is handled [\#2907](https://github.com/pypeclub/OpenPype/pull/2907) +- nuke: imageio adding ocio config version 1.2 [\#2897](https://github.com/pypeclub/OpenPype/pull/2897) +- Flame: support for comment with xml attribute overrides [\#2892](https://github.com/pypeclub/OpenPype/pull/2892) +- Nuke: ExtractReviewSlate can handle more codes and profiles [\#2879](https://github.com/pypeclub/OpenPype/pull/2879) +- Flame: sequence used for reference video [\#2869](https://github.com/pypeclub/OpenPype/pull/2869) + +**πŸ› Bug fixes** + +- General: Fix use of Anatomy roots [\#2904](https://github.com/pypeclub/OpenPype/pull/2904) +- Fixing gap detection in extract review [\#2902](https://github.com/pypeclub/OpenPype/pull/2902) +- Pyblish Pype - ensure current state is correct when entering new group order [\#2899](https://github.com/pypeclub/OpenPype/pull/2899) +- SceneInventory: Fix import of load function [\#2894](https://github.com/pypeclub/OpenPype/pull/2894) +- Harmony - fixed creator issue [\#2891](https://github.com/pypeclub/OpenPype/pull/2891) +- General: Remove forgotten use of avalon Creator [\#2885](https://github.com/pypeclub/OpenPype/pull/2885) +- General: Avoid circular import [\#2884](https://github.com/pypeclub/OpenPype/pull/2884) +- Fixes for attaching loaded containers \(\#2837\) [\#2874](https://github.com/pypeclub/OpenPype/pull/2874) +- Maya: Deformer node ids validation plugin [\#2826](https://github.com/pypeclub/OpenPype/pull/2826) +- Flame Babypublisher optimalization [\#2806](https://github.com/pypeclub/OpenPype/pull/2806) +- hotfix: OIIO tool path - add extension on windows [\#2618](https://github.com/pypeclub/OpenPype/pull/2618) + +**πŸ”€ Refactored code** + +- General: Reduce style usage to OpenPype repository [\#2889](https://github.com/pypeclub/OpenPype/pull/2889) +- General: Move loader logic from avalon to openpype [\#2886](https://github.com/pypeclub/OpenPype/pull/2886) + +## [3.9.0](https://github.com/pypeclub/OpenPype/tree/3.9.0) (2022-03-14) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.8.2...3.9.0) + +**Deprecated:** + +- Houdini: Remove unused code [\#2779](https://github.com/pypeclub/OpenPype/pull/2779) +- Loader: Remove default family states for hosts from code [\#2706](https://github.com/pypeclub/OpenPype/pull/2706) +- AssetCreator: Remove the tool [\#2845](https://github.com/pypeclub/OpenPype/pull/2845) + +### πŸ“– Documentation + +- Documentation: fixed broken links [\#2799](https://github.com/pypeclub/OpenPype/pull/2799) +- Documentation: broken link fix [\#2785](https://github.com/pypeclub/OpenPype/pull/2785) +- Documentation: link fixes [\#2772](https://github.com/pypeclub/OpenPype/pull/2772) +- Update docusaurus to latest version [\#2760](https://github.com/pypeclub/OpenPype/pull/2760) +- Various testing updates [\#2726](https://github.com/pypeclub/OpenPype/pull/2726) +- documentation: add example to `repack-version` command [\#2669](https://github.com/pypeclub/OpenPype/pull/2669) +- Update docusaurus [\#2639](https://github.com/pypeclub/OpenPype/pull/2639) +- Documentation: Fixed relative links [\#2621](https://github.com/pypeclub/OpenPype/pull/2621) +- Documentation: Change Photoshop & AfterEffects plugin path [\#2878](https://github.com/pypeclub/OpenPype/pull/2878) + +**πŸ†• New features** + +- Flame: loading clips to reels [\#2622](https://github.com/pypeclub/OpenPype/pull/2622) +- General: Store settings by OpenPype version [\#2570](https://github.com/pypeclub/OpenPype/pull/2570) + +**πŸš€ Enhancements** + +- New: Validation exceptions [\#2841](https://github.com/pypeclub/OpenPype/pull/2841) +- General: Set context environments for non host applications [\#2803](https://github.com/pypeclub/OpenPype/pull/2803) +- Houdini: Remove duplicate ValidateOutputNode plug-in [\#2780](https://github.com/pypeclub/OpenPype/pull/2780) +- Tray publisher: New Tray Publisher host \(beta\) [\#2778](https://github.com/pypeclub/OpenPype/pull/2778) +- Slack: Added regex for filtering on subset names [\#2775](https://github.com/pypeclub/OpenPype/pull/2775) +- Houdini: Implement Reset Frame Range [\#2770](https://github.com/pypeclub/OpenPype/pull/2770) +- Pyblish Pype: Remove redundant new line in installed fonts printing [\#2758](https://github.com/pypeclub/OpenPype/pull/2758) +- Flame: use Shot Name on segment for asset name [\#2751](https://github.com/pypeclub/OpenPype/pull/2751) +- Flame: adding validator source clip [\#2746](https://github.com/pypeclub/OpenPype/pull/2746) +- Work Files: Preserve subversion comment of current filename by default [\#2734](https://github.com/pypeclub/OpenPype/pull/2734) +- Maya: set Deadline job/batch name to original source workfile name instead of published workfile [\#2733](https://github.com/pypeclub/OpenPype/pull/2733) +- Ftrack: Disable ftrack module by default [\#2732](https://github.com/pypeclub/OpenPype/pull/2732) +- Project Manager: Disable add task, add asset and save button when not in a project [\#2727](https://github.com/pypeclub/OpenPype/pull/2727) +- dropbox handle big file [\#2718](https://github.com/pypeclub/OpenPype/pull/2718) +- Fusion Move PR: Minor tweaks to Fusion integration [\#2716](https://github.com/pypeclub/OpenPype/pull/2716) +- RoyalRender: Minor enhancements [\#2700](https://github.com/pypeclub/OpenPype/pull/2700) +- Nuke: prerender with review knob [\#2691](https://github.com/pypeclub/OpenPype/pull/2691) +- Maya configurable unit validator [\#2680](https://github.com/pypeclub/OpenPype/pull/2680) +- General: Add settings for CleanUpFarm and disable the plugin by default [\#2679](https://github.com/pypeclub/OpenPype/pull/2679) +- Project Manager: Only allow scroll wheel edits when spinbox is active [\#2678](https://github.com/pypeclub/OpenPype/pull/2678) +- Ftrack: Sync description to assets [\#2670](https://github.com/pypeclub/OpenPype/pull/2670) +- Houdini: Moved to OpenPype [\#2658](https://github.com/pypeclub/OpenPype/pull/2658) +- Maya: Move implementation to OpenPype [\#2649](https://github.com/pypeclub/OpenPype/pull/2649) +- General: FFmpeg conversion also check attribute string length [\#2635](https://github.com/pypeclub/OpenPype/pull/2635) +- Houdini: Load Arnold .ass procedurals into Houdini [\#2606](https://github.com/pypeclub/OpenPype/pull/2606) +- Deadline: Simplify GlobalJobPreLoad logic [\#2605](https://github.com/pypeclub/OpenPype/pull/2605) +- Houdini: Implement Arnold .ass standin extraction from Houdini \(also support .ass.gz\) [\#2603](https://github.com/pypeclub/OpenPype/pull/2603) +- New Publisher: New features and preparations for new standalone publisher [\#2556](https://github.com/pypeclub/OpenPype/pull/2556) +- Fix Maya 2022 Python 3 compatibility [\#2445](https://github.com/pypeclub/OpenPype/pull/2445) +- TVPaint: Use new publisher exceptions in validators [\#2435](https://github.com/pypeclub/OpenPype/pull/2435) +- Harmony: Added new style validations for New Publisher [\#2434](https://github.com/pypeclub/OpenPype/pull/2434) +- Aftereffects: New style validations for New publisher [\#2430](https://github.com/pypeclub/OpenPype/pull/2430) +- Farm publishing: New cleanup plugin for Maya renders on farm [\#2390](https://github.com/pypeclub/OpenPype/pull/2390) +- General: Subset name filtering in ExtractReview outpus [\#2872](https://github.com/pypeclub/OpenPype/pull/2872) +- NewPublisher: Descriptions and Icons in creator dialog [\#2867](https://github.com/pypeclub/OpenPype/pull/2867) +- NewPublisher: Changing task on publishing instance [\#2863](https://github.com/pypeclub/OpenPype/pull/2863) +- TrayPublisher: Choose project widget is more clear [\#2859](https://github.com/pypeclub/OpenPype/pull/2859) +- Maya: add loaded containers to published instance [\#2837](https://github.com/pypeclub/OpenPype/pull/2837) +- Ftrack: Can sync fps as string [\#2836](https://github.com/pypeclub/OpenPype/pull/2836) +- General: Custom function for find executable [\#2822](https://github.com/pypeclub/OpenPype/pull/2822) +- General: Color dialog UI fixes [\#2817](https://github.com/pypeclub/OpenPype/pull/2817) +- global: letter box calculated on output as last process [\#2812](https://github.com/pypeclub/OpenPype/pull/2812) +- Nuke: adding Reformat to baking mov plugin [\#2811](https://github.com/pypeclub/OpenPype/pull/2811) +- Manager: Update all to latest button [\#2805](https://github.com/pypeclub/OpenPype/pull/2805) +- Houdini: Move Houdini Save Current File to beginning of ExtractorOrder [\#2747](https://github.com/pypeclub/OpenPype/pull/2747) +- Global: adding studio name/code to anatomy template formatting data [\#2630](https://github.com/pypeclub/OpenPype/pull/2630) + +**πŸ› Bug fixes** + +- Settings UI: Search case sensitivity [\#2810](https://github.com/pypeclub/OpenPype/pull/2810) +- resolve: fixing fusion module loading [\#2802](https://github.com/pypeclub/OpenPype/pull/2802) +- Ftrack: Unset task ids from asset versions before tasks are removed [\#2800](https://github.com/pypeclub/OpenPype/pull/2800) +- Slack: fail gracefully if slack exception [\#2798](https://github.com/pypeclub/OpenPype/pull/2798) +- Flame: Fix version string in default settings [\#2783](https://github.com/pypeclub/OpenPype/pull/2783) +- After Effects: Fix typo in name `afftereffects` -\> `aftereffects` [\#2768](https://github.com/pypeclub/OpenPype/pull/2768) +- Houdini: Fix open last workfile [\#2767](https://github.com/pypeclub/OpenPype/pull/2767) +- Avoid renaming udim indexes [\#2765](https://github.com/pypeclub/OpenPype/pull/2765) +- Maya: Fix `unique_namespace` when in an namespace that is empty [\#2759](https://github.com/pypeclub/OpenPype/pull/2759) +- Loader UI: Fix right click in representation widget [\#2757](https://github.com/pypeclub/OpenPype/pull/2757) +- Harmony: Rendering in Deadline didn't work in other machines than submitter [\#2754](https://github.com/pypeclub/OpenPype/pull/2754) +- Aftereffects 2022 and Deadline [\#2748](https://github.com/pypeclub/OpenPype/pull/2748) +- Flame: bunch of bugs [\#2745](https://github.com/pypeclub/OpenPype/pull/2745) +- Maya: Save current scene on workfile publish [\#2744](https://github.com/pypeclub/OpenPype/pull/2744) +- Version Up: Preserve parts of filename after version number \(like subversion\) on version\_up [\#2741](https://github.com/pypeclub/OpenPype/pull/2741) +- Loader UI: Multiple asset selection and underline colors fixed [\#2731](https://github.com/pypeclub/OpenPype/pull/2731) +- General: Fix loading of unused chars in xml format [\#2729](https://github.com/pypeclub/OpenPype/pull/2729) +- TVPaint: Set objectName with members [\#2725](https://github.com/pypeclub/OpenPype/pull/2725) +- General: Don't use 'objectName' from loaded references [\#2715](https://github.com/pypeclub/OpenPype/pull/2715) +- Settings: Studio Project anatomy is queried using right keys [\#2711](https://github.com/pypeclub/OpenPype/pull/2711) +- Local Settings: Additional applications don't break UI [\#2710](https://github.com/pypeclub/OpenPype/pull/2710) +- Maya: Remove some unused code [\#2709](https://github.com/pypeclub/OpenPype/pull/2709) +- Houdini: Fix refactor of Houdini host move for CreateArnoldAss [\#2704](https://github.com/pypeclub/OpenPype/pull/2704) +- LookAssigner: Fix imports after moving code to OpenPype repository [\#2701](https://github.com/pypeclub/OpenPype/pull/2701) +- Multiple hosts: unify menu style across hosts [\#2693](https://github.com/pypeclub/OpenPype/pull/2693) +- Maya Redshift fixes [\#2692](https://github.com/pypeclub/OpenPype/pull/2692) +- Maya: fix fps validation popup [\#2685](https://github.com/pypeclub/OpenPype/pull/2685) +- Houdini Explicitly collect correct frame name even in case of single frame render when `frameStart` is provided [\#2676](https://github.com/pypeclub/OpenPype/pull/2676) +- hiero: fix effect collector name and order [\#2673](https://github.com/pypeclub/OpenPype/pull/2673) +- Maya: Fix menu callbacks [\#2671](https://github.com/pypeclub/OpenPype/pull/2671) +- hiero: removing obsolete unsupported plugin [\#2667](https://github.com/pypeclub/OpenPype/pull/2667) +- Launcher: Fix access to 'data' attribute on actions [\#2659](https://github.com/pypeclub/OpenPype/pull/2659) +- Maya `vrscene` loader fixes [\#2633](https://github.com/pypeclub/OpenPype/pull/2633) +- Houdini: fix usd family in loader and integrators [\#2631](https://github.com/pypeclub/OpenPype/pull/2631) +- Maya: Add only reference node to look family container like with other families [\#2508](https://github.com/pypeclub/OpenPype/pull/2508) +- General: Missing time function [\#2877](https://github.com/pypeclub/OpenPype/pull/2877) +- Deadline: Fix plugin name for tile assemble [\#2868](https://github.com/pypeclub/OpenPype/pull/2868) +- Nuke: gizmo precollect fix [\#2866](https://github.com/pypeclub/OpenPype/pull/2866) +- General: Fix hardlink for windows [\#2864](https://github.com/pypeclub/OpenPype/pull/2864) +- General: ffmpeg was crashing on slate merge [\#2860](https://github.com/pypeclub/OpenPype/pull/2860) +- WebPublisher: Video file was published with one too many frame [\#2858](https://github.com/pypeclub/OpenPype/pull/2858) +- New Publisher: Error dialog got right styles [\#2857](https://github.com/pypeclub/OpenPype/pull/2857) +- General: Fix getattr clalback on dynamic modules [\#2855](https://github.com/pypeclub/OpenPype/pull/2855) +- Nuke: slate resolution to input video resolution [\#2853](https://github.com/pypeclub/OpenPype/pull/2853) +- WebPublisher: Fix username stored in DB [\#2852](https://github.com/pypeclub/OpenPype/pull/2852) +- WebPublisher: Fix wrong number of frames for video file [\#2851](https://github.com/pypeclub/OpenPype/pull/2851) +- Nuke: Fix family test in validate\_write\_legacy to work with stillImage [\#2847](https://github.com/pypeclub/OpenPype/pull/2847) +- Nuke: fix multiple baking profile farm publishing [\#2842](https://github.com/pypeclub/OpenPype/pull/2842) +- Blender: Fixed parameters for FBX export of the camera [\#2840](https://github.com/pypeclub/OpenPype/pull/2840) +- Maya: Stop creation of reviews for Cryptomattes [\#2832](https://github.com/pypeclub/OpenPype/pull/2832) +- Deadline: Remove recreated event [\#2828](https://github.com/pypeclub/OpenPype/pull/2828) +- Deadline: Added missing events folder [\#2827](https://github.com/pypeclub/OpenPype/pull/2827) +- Settings: Missing document with OP versions may break start of OpenPype [\#2825](https://github.com/pypeclub/OpenPype/pull/2825) +- Deadline: more detailed temp file name for environment json [\#2824](https://github.com/pypeclub/OpenPype/pull/2824) +- General: Host name was formed from obsolete code [\#2821](https://github.com/pypeclub/OpenPype/pull/2821) +- Settings UI: Fix "Apply from" action [\#2820](https://github.com/pypeclub/OpenPype/pull/2820) +- Ftrack: Job killer with missing user [\#2819](https://github.com/pypeclub/OpenPype/pull/2819) +- Nuke: Use AVALON\_APP to get value for "app" key [\#2818](https://github.com/pypeclub/OpenPype/pull/2818) +- StandalonePublisher: use dynamic groups in subset names [\#2816](https://github.com/pypeclub/OpenPype/pull/2816) + +**πŸ”€ Refactored code** + +- Ftrack: Moved module one hierarchy level higher [\#2792](https://github.com/pypeclub/OpenPype/pull/2792) +- SyncServer: Moved module one hierarchy level higher [\#2791](https://github.com/pypeclub/OpenPype/pull/2791) +- Royal render: Move module one hierarchy level higher [\#2790](https://github.com/pypeclub/OpenPype/pull/2790) +- Deadline: Move module one hierarchy level higher [\#2789](https://github.com/pypeclub/OpenPype/pull/2789) +- Refactor: move webserver tool to openpype [\#2876](https://github.com/pypeclub/OpenPype/pull/2876) +- General: Move create logic from avalon to OpenPype [\#2854](https://github.com/pypeclub/OpenPype/pull/2854) +- General: Add vendors from avalon [\#2848](https://github.com/pypeclub/OpenPype/pull/2848) +- General: Basic event system [\#2846](https://github.com/pypeclub/OpenPype/pull/2846) +- General: Move change context functions [\#2839](https://github.com/pypeclub/OpenPype/pull/2839) +- Tools: Don't use avalon tools code [\#2829](https://github.com/pypeclub/OpenPype/pull/2829) +- Move Unreal Implementation to OpenPype [\#2823](https://github.com/pypeclub/OpenPype/pull/2823) +- General: Extract template formatting from anatomy [\#2766](https://github.com/pypeclub/OpenPype/pull/2766) + +**Merged pull requests:** + +- Fusion: Moved implementation into OpenPype [\#2713](https://github.com/pypeclub/OpenPype/pull/2713) +- TVPaint: Plugin build without dependencies [\#2705](https://github.com/pypeclub/OpenPype/pull/2705) +- Webpublisher: Photoshop create a beauty png [\#2689](https://github.com/pypeclub/OpenPype/pull/2689) +- Ftrack: Hierarchical attributes are queried properly [\#2682](https://github.com/pypeclub/OpenPype/pull/2682) +- Maya: Add Validate Frame Range settings [\#2661](https://github.com/pypeclub/OpenPype/pull/2661) +- Harmony: move to Openpype [\#2657](https://github.com/pypeclub/OpenPype/pull/2657) +- Maya: cleanup duplicate rendersetup code [\#2642](https://github.com/pypeclub/OpenPype/pull/2642) +- Deadline: Be able to pass Mongo url to job [\#2616](https://github.com/pypeclub/OpenPype/pull/2616) + +## [3.8.2](https://github.com/pypeclub/OpenPype/tree/3.8.2) (2022-02-07) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.8.1...3.8.2) + +### πŸ“– Documentation + +- Cosmetics: Fix common typos in openpype/website [\#2617](https://github.com/pypeclub/OpenPype/pull/2617) + +**πŸš€ Enhancements** + +- TVPaint: Image loaders also work on review family [\#2638](https://github.com/pypeclub/OpenPype/pull/2638) +- General: Project backup tools [\#2629](https://github.com/pypeclub/OpenPype/pull/2629) +- nuke: adding clear button to write nodes [\#2627](https://github.com/pypeclub/OpenPype/pull/2627) +- Ftrack: Family to Asset type mapping is in settings [\#2602](https://github.com/pypeclub/OpenPype/pull/2602) +- Nuke: load color space from representation data [\#2576](https://github.com/pypeclub/OpenPype/pull/2576) + +**πŸ› Bug fixes** + +- Fix pulling of cx\_freeze 6.10 [\#2628](https://github.com/pypeclub/OpenPype/pull/2628) +- Global: fix broken otio review extractor [\#2590](https://github.com/pypeclub/OpenPype/pull/2590) + +**Merged pull requests:** + +- WebPublisher: fix instance duplicates [\#2641](https://github.com/pypeclub/OpenPype/pull/2641) +- Fix - safer pulling of task name for webpublishing from PS [\#2613](https://github.com/pypeclub/OpenPype/pull/2613) + +## [3.8.1](https://github.com/pypeclub/OpenPype/tree/3.8.1) (2022-02-01) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.8.0...3.8.1) + +**πŸš€ Enhancements** + +- Webpublisher: Thumbnail extractor [\#2600](https://github.com/pypeclub/OpenPype/pull/2600) +- Loader: Allow to toggle default family filters between "include" or "exclude" filtering [\#2541](https://github.com/pypeclub/OpenPype/pull/2541) +- Launcher: Added context menu to to skip opening last workfile [\#2536](https://github.com/pypeclub/OpenPype/pull/2536) +- Unreal: JSON Layout Loading support [\#2066](https://github.com/pypeclub/OpenPype/pull/2066) + +**πŸ› Bug fixes** + +- Release/3.8.0 [\#2619](https://github.com/pypeclub/OpenPype/pull/2619) +- Settings: Enum does not store empty string if has single item to select [\#2615](https://github.com/pypeclub/OpenPype/pull/2615) +- switch distutils to sysconfig for `get_platform()` [\#2594](https://github.com/pypeclub/OpenPype/pull/2594) +- Fix poetry index and speedcopy update [\#2589](https://github.com/pypeclub/OpenPype/pull/2589) +- Webpublisher: Fix - subset names from processed .psd used wrong value for task [\#2586](https://github.com/pypeclub/OpenPype/pull/2586) +- `vrscene` creator Deadline webservice URL handling [\#2580](https://github.com/pypeclub/OpenPype/pull/2580) +- global: track name was failing if duplicated root word in name [\#2568](https://github.com/pypeclub/OpenPype/pull/2568) +- Validate Maya Rig produces no cycle errors [\#2484](https://github.com/pypeclub/OpenPype/pull/2484) + +**Merged pull requests:** + +- Bump pillow from 8.4.0 to 9.0.0 [\#2595](https://github.com/pypeclub/OpenPype/pull/2595) +- Webpublisher: Skip version collect [\#2591](https://github.com/pypeclub/OpenPype/pull/2591) +- build\(deps\): bump pillow from 8.4.0 to 9.0.0 [\#2523](https://github.com/pypeclub/OpenPype/pull/2523) + +## [3.8.0](https://github.com/pypeclub/OpenPype/tree/3.8.0) (2022-01-24) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.7.0...3.8.0) + +### πŸ“– Documentation + +- Variable in docs renamed to proper name [\#2546](https://github.com/pypeclub/OpenPype/pull/2546) + +**πŸ†• New features** + +- Flame: extracting segments with trans-coding [\#2547](https://github.com/pypeclub/OpenPype/pull/2547) +- Maya : V-Ray Proxy - load all ABC files via proxy [\#2544](https://github.com/pypeclub/OpenPype/pull/2544) +- Maya to Unreal: Extended static mesh workflow [\#2537](https://github.com/pypeclub/OpenPype/pull/2537) +- Flame: collecting publishable instances [\#2519](https://github.com/pypeclub/OpenPype/pull/2519) +- Flame: create publishable clips [\#2495](https://github.com/pypeclub/OpenPype/pull/2495) +- Flame: OpenTimelineIO Export Modul [\#2398](https://github.com/pypeclub/OpenPype/pull/2398) + +**πŸš€ Enhancements** + +- Webpublisher: Moved error at the beginning of the log [\#2559](https://github.com/pypeclub/OpenPype/pull/2559) +- Ftrack: Use ApplicationManager to get DJV path [\#2558](https://github.com/pypeclub/OpenPype/pull/2558) +- Webpublisher: Added endpoint to reprocess batch through UI [\#2555](https://github.com/pypeclub/OpenPype/pull/2555) +- Settings: PathInput strip passed string [\#2550](https://github.com/pypeclub/OpenPype/pull/2550) +- Global: Exctract Review anatomy fill data with output name [\#2548](https://github.com/pypeclub/OpenPype/pull/2548) +- Cosmetics: Clean up some cosmetics / typos [\#2542](https://github.com/pypeclub/OpenPype/pull/2542) +- General: Validate if current process OpenPype version is requested version [\#2529](https://github.com/pypeclub/OpenPype/pull/2529) +- General: Be able to use anatomy data in ffmpeg output arguments [\#2525](https://github.com/pypeclub/OpenPype/pull/2525) +- Expose toggle publish plug-in settings for Maya Look Shading Engine Naming [\#2521](https://github.com/pypeclub/OpenPype/pull/2521) +- Photoshop: Move implementation to OpenPype [\#2510](https://github.com/pypeclub/OpenPype/pull/2510) +- TimersManager: Move module one hierarchy higher [\#2501](https://github.com/pypeclub/OpenPype/pull/2501) +- Slack: notifications are sent with Openpype logo and bot name [\#2499](https://github.com/pypeclub/OpenPype/pull/2499) +- Slack: Add review to notification message [\#2498](https://github.com/pypeclub/OpenPype/pull/2498) +- Ftrack: Event handlers settings [\#2496](https://github.com/pypeclub/OpenPype/pull/2496) +- Tools: Fix style and modality of errors in loader and creator [\#2489](https://github.com/pypeclub/OpenPype/pull/2489) +- Maya: Collect 'fps' animation data only for "review" instances [\#2486](https://github.com/pypeclub/OpenPype/pull/2486) +- Project Manager: Remove project button cleanup [\#2482](https://github.com/pypeclub/OpenPype/pull/2482) +- Tools: Be able to change models of tasks and assets widgets [\#2475](https://github.com/pypeclub/OpenPype/pull/2475) +- Publish pype: Reduce publish process defering [\#2464](https://github.com/pypeclub/OpenPype/pull/2464) +- Maya: Improve speed of Collect History logic [\#2460](https://github.com/pypeclub/OpenPype/pull/2460) +- Maya: Validate Rig Controllers - fix Error: in script editor [\#2459](https://github.com/pypeclub/OpenPype/pull/2459) +- Maya: Validate NGONs simplify and speed-up [\#2458](https://github.com/pypeclub/OpenPype/pull/2458) +- Maya: Optimize Validate Locked Normals speed for dense polymeshes [\#2457](https://github.com/pypeclub/OpenPype/pull/2457) +- Maya: Refactor missing \_get\_reference\_node method [\#2455](https://github.com/pypeclub/OpenPype/pull/2455) +- Houdini: Remove broken unique name counter [\#2450](https://github.com/pypeclub/OpenPype/pull/2450) +- Maya: Improve lib.polyConstraint performance when Select tool is not the active tool context [\#2447](https://github.com/pypeclub/OpenPype/pull/2447) +- General: Validate third party before build [\#2425](https://github.com/pypeclub/OpenPype/pull/2425) +- Maya : add option to not group reference in ReferenceLoader [\#2383](https://github.com/pypeclub/OpenPype/pull/2383) + +**πŸ› Bug fixes** + +- AfterEffects: Fix - removed obsolete import [\#2577](https://github.com/pypeclub/OpenPype/pull/2577) +- General: OpenPype version updates [\#2575](https://github.com/pypeclub/OpenPype/pull/2575) +- Ftrack: Delete action revision [\#2563](https://github.com/pypeclub/OpenPype/pull/2563) +- Webpublisher: ftrack shows incorrect user names [\#2560](https://github.com/pypeclub/OpenPype/pull/2560) +- General: Do not validate version if build does not support it [\#2557](https://github.com/pypeclub/OpenPype/pull/2557) +- Webpublisher: Fixed progress reporting [\#2553](https://github.com/pypeclub/OpenPype/pull/2553) +- Fix Maya AssProxyLoader version switch [\#2551](https://github.com/pypeclub/OpenPype/pull/2551) +- General: Fix install thread in igniter [\#2549](https://github.com/pypeclub/OpenPype/pull/2549) +- Houdini: vdbcache family preserve frame numbers on publish integration + enable validate version for Houdini [\#2535](https://github.com/pypeclub/OpenPype/pull/2535) +- Maya: Fix Load VDB to V-Ray [\#2533](https://github.com/pypeclub/OpenPype/pull/2533) +- Maya: ReferenceLoader fix not unique group name error for attach to root [\#2532](https://github.com/pypeclub/OpenPype/pull/2532) +- Maya: namespaced context go back to original namespace when started from inside a namespace [\#2531](https://github.com/pypeclub/OpenPype/pull/2531) +- Fix create zip tool - path argument [\#2522](https://github.com/pypeclub/OpenPype/pull/2522) +- Maya: Fix Extract Look with space in names [\#2518](https://github.com/pypeclub/OpenPype/pull/2518) +- Fix published frame content for sequence starting with 0 [\#2513](https://github.com/pypeclub/OpenPype/pull/2513) +- Maya: reset empty string attributes correctly to "" instead of "None" [\#2506](https://github.com/pypeclub/OpenPype/pull/2506) +- Improve FusionPreLaunch hook errors [\#2505](https://github.com/pypeclub/OpenPype/pull/2505) +- General: Settings work if OpenPypeVersion is available [\#2494](https://github.com/pypeclub/OpenPype/pull/2494) +- General: PYTHONPATH may break OpenPype dependencies [\#2493](https://github.com/pypeclub/OpenPype/pull/2493) +- General: Modules import function output fix [\#2492](https://github.com/pypeclub/OpenPype/pull/2492) +- AE: fix hiding of alert window below Publish [\#2491](https://github.com/pypeclub/OpenPype/pull/2491) +- Workfiles tool: Files widget show files on first show [\#2488](https://github.com/pypeclub/OpenPype/pull/2488) +- General: Custom template paths filter fix [\#2483](https://github.com/pypeclub/OpenPype/pull/2483) +- Loader: Remove always on top flag in tray [\#2480](https://github.com/pypeclub/OpenPype/pull/2480) +- General: Anatomy does not return root envs as unicode [\#2465](https://github.com/pypeclub/OpenPype/pull/2465) +- Maya: Validate Shape Zero do not keep fixed geometry vertices selected/active after repair [\#2456](https://github.com/pypeclub/OpenPype/pull/2456) + +**Merged pull requests:** + +- AfterEffects: Move implementation to OpenPype [\#2543](https://github.com/pypeclub/OpenPype/pull/2543) +- Maya: Remove Maya Look Assigner check on startup [\#2540](https://github.com/pypeclub/OpenPype/pull/2540) +- build\(deps\): bump shelljs from 0.8.4 to 0.8.5 in /website [\#2538](https://github.com/pypeclub/OpenPype/pull/2538) +- build\(deps\): bump follow-redirects from 1.14.4 to 1.14.7 in /website [\#2534](https://github.com/pypeclub/OpenPype/pull/2534) +- Nuke: Merge avalon's implementation into OpenPype [\#2514](https://github.com/pypeclub/OpenPype/pull/2514) +- Maya: Vray fix proxies look assignment [\#2392](https://github.com/pypeclub/OpenPype/pull/2392) +- Bump algoliasearch-helper from 3.4.4 to 3.6.2 in /website [\#2297](https://github.com/pypeclub/OpenPype/pull/2297) + +## [3.7.0](https://github.com/pypeclub/OpenPype/tree/3.7.0) (2022-01-04) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.6.4...3.7.0) + +**Deprecated:** + +- General: Default modules hierarchy n2 [\#2368](https://github.com/pypeclub/OpenPype/pull/2368) + +### πŸ“– Documentation + +- docs\[website\]: Add Ellipse Studio \(logo\) as an OpenPype contributor [\#2324](https://github.com/pypeclub/OpenPype/pull/2324) + +**πŸ†• New features** + +- Settings UI use OpenPype styles [\#2296](https://github.com/pypeclub/OpenPype/pull/2296) +- Store typed version dependencies for workfiles [\#2192](https://github.com/pypeclub/OpenPype/pull/2192) +- OpenPypeV3: add key task type, task shortname and user to path templating construction [\#2157](https://github.com/pypeclub/OpenPype/pull/2157) +- Nuke: Alembic model workflow [\#2140](https://github.com/pypeclub/OpenPype/pull/2140) +- TVPaint: Load workfile from published. [\#1980](https://github.com/pypeclub/OpenPype/pull/1980) + +**πŸš€ Enhancements** + +- General: Workdir extra folders [\#2462](https://github.com/pypeclub/OpenPype/pull/2462) +- Photoshop: New style validations for New publisher [\#2429](https://github.com/pypeclub/OpenPype/pull/2429) +- General: Environment variables groups [\#2424](https://github.com/pypeclub/OpenPype/pull/2424) +- Unreal: Dynamic menu created in Python [\#2422](https://github.com/pypeclub/OpenPype/pull/2422) +- Settings UI: Hyperlinks to settings [\#2420](https://github.com/pypeclub/OpenPype/pull/2420) +- Modules: JobQueue module moved one hierarchy level higher [\#2419](https://github.com/pypeclub/OpenPype/pull/2419) +- TimersManager: Start timer post launch hook [\#2418](https://github.com/pypeclub/OpenPype/pull/2418) +- General: Run applications as separate processes under linux [\#2408](https://github.com/pypeclub/OpenPype/pull/2408) +- Ftrack: Check existence of object type on recreation [\#2404](https://github.com/pypeclub/OpenPype/pull/2404) +- Enhancement: Global cleanup plugin that explicitly remove paths from context [\#2402](https://github.com/pypeclub/OpenPype/pull/2402) +- General: MongoDB ability to specify replica set groups [\#2401](https://github.com/pypeclub/OpenPype/pull/2401) +- Flame: moving `utility_scripts` to api folder also with `scripts` [\#2385](https://github.com/pypeclub/OpenPype/pull/2385) +- Centos 7 dependency compatibility [\#2384](https://github.com/pypeclub/OpenPype/pull/2384) +- Enhancement: Settings: Use project settings values from another project [\#2382](https://github.com/pypeclub/OpenPype/pull/2382) +- Blender 3: Support auto install for new blender version [\#2377](https://github.com/pypeclub/OpenPype/pull/2377) +- Maya add render image path to settings [\#2375](https://github.com/pypeclub/OpenPype/pull/2375) +- Settings: Webpublisher in hosts enum [\#2367](https://github.com/pypeclub/OpenPype/pull/2367) +- Hiero: python3 compatibility [\#2365](https://github.com/pypeclub/OpenPype/pull/2365) +- Burnins: Be able recognize mxf OPAtom format [\#2361](https://github.com/pypeclub/OpenPype/pull/2361) +- Maya: Add is\_static\_image\_plane and is\_in\_all\_views option in imagePlaneLoader [\#2356](https://github.com/pypeclub/OpenPype/pull/2356) +- Local settings: Copyable studio paths [\#2349](https://github.com/pypeclub/OpenPype/pull/2349) +- Assets Widget: Clear model on project change [\#2345](https://github.com/pypeclub/OpenPype/pull/2345) +- General: OpenPype default modules hierarchy [\#2338](https://github.com/pypeclub/OpenPype/pull/2338) +- TVPaint: Move implementation to OpenPype [\#2336](https://github.com/pypeclub/OpenPype/pull/2336) +- General: FFprobe error exception contain original error message [\#2328](https://github.com/pypeclub/OpenPype/pull/2328) +- Resolve: Add experimental button to menu [\#2325](https://github.com/pypeclub/OpenPype/pull/2325) +- Hiero: Add experimental tools action [\#2323](https://github.com/pypeclub/OpenPype/pull/2323) +- Input links: Cleanup and unification of differences [\#2322](https://github.com/pypeclub/OpenPype/pull/2322) +- General: Don't validate vendor bin with executing them [\#2317](https://github.com/pypeclub/OpenPype/pull/2317) +- General: Multilayer EXRs support [\#2315](https://github.com/pypeclub/OpenPype/pull/2315) +- General: Run process log stderr as info log level [\#2309](https://github.com/pypeclub/OpenPype/pull/2309) +- General: Reduce vendor imports [\#2305](https://github.com/pypeclub/OpenPype/pull/2305) +- Tools: Cleanup of unused classes [\#2304](https://github.com/pypeclub/OpenPype/pull/2304) +- Project Manager: Added ability to delete project [\#2298](https://github.com/pypeclub/OpenPype/pull/2298) +- Ftrack: Synchronize input links [\#2287](https://github.com/pypeclub/OpenPype/pull/2287) +- StandalonePublisher: Remove unused plugin ExtractHarmonyZip [\#2277](https://github.com/pypeclub/OpenPype/pull/2277) +- Ftrack: Support multiple reviews [\#2271](https://github.com/pypeclub/OpenPype/pull/2271) +- Ftrack: Remove unused clean component plugin [\#2269](https://github.com/pypeclub/OpenPype/pull/2269) +- Royal Render: Support for rr channels in separate dirs [\#2268](https://github.com/pypeclub/OpenPype/pull/2268) +- Houdini: Add experimental tools action [\#2267](https://github.com/pypeclub/OpenPype/pull/2267) +- Nuke: extract baked review videos presets [\#2248](https://github.com/pypeclub/OpenPype/pull/2248) +- TVPaint: Workers rendering [\#2209](https://github.com/pypeclub/OpenPype/pull/2209) +- OpenPypeV3: Add key parent asset to path templating construction [\#2186](https://github.com/pypeclub/OpenPype/pull/2186) + +**πŸ› Bug fixes** + +- TVPaint: Create render layer dialog is in front [\#2471](https://github.com/pypeclub/OpenPype/pull/2471) +- Short Pyblish plugin path [\#2428](https://github.com/pypeclub/OpenPype/pull/2428) +- PS: Introduced settings for invalid characters to use in ValidateNaming plugin [\#2417](https://github.com/pypeclub/OpenPype/pull/2417) +- Settings UI: Breadcrumbs path does not create new entities [\#2416](https://github.com/pypeclub/OpenPype/pull/2416) +- AfterEffects: Variant 2022 is in defaults but missing in schemas [\#2412](https://github.com/pypeclub/OpenPype/pull/2412) +- Nuke: baking representations was not additive [\#2406](https://github.com/pypeclub/OpenPype/pull/2406) +- General: Fix access to environments from default settings [\#2403](https://github.com/pypeclub/OpenPype/pull/2403) +- Fix: Placeholder Input color set fix [\#2399](https://github.com/pypeclub/OpenPype/pull/2399) +- Settings: Fix state change of wrapper label [\#2396](https://github.com/pypeclub/OpenPype/pull/2396) +- Flame: fix ftrack publisher [\#2381](https://github.com/pypeclub/OpenPype/pull/2381) +- hiero: solve custom ocio path [\#2379](https://github.com/pypeclub/OpenPype/pull/2379) +- hiero: fix workio and flatten [\#2378](https://github.com/pypeclub/OpenPype/pull/2378) +- Nuke: fixing menu re-drawing during context change [\#2374](https://github.com/pypeclub/OpenPype/pull/2374) +- Webpublisher: Fix assignment of families of TVpaint instances [\#2373](https://github.com/pypeclub/OpenPype/pull/2373) +- Nuke: fixing node name based on switched asset name [\#2369](https://github.com/pypeclub/OpenPype/pull/2369) +- JobQueue: Fix loading of settings [\#2362](https://github.com/pypeclub/OpenPype/pull/2362) +- Tools: Placeholder color [\#2359](https://github.com/pypeclub/OpenPype/pull/2359) +- Launcher: Minimize button on MacOs [\#2355](https://github.com/pypeclub/OpenPype/pull/2355) +- StandalonePublisher: Fix import of constant [\#2354](https://github.com/pypeclub/OpenPype/pull/2354) +- Houdini: Fix HDA creation [\#2350](https://github.com/pypeclub/OpenPype/pull/2350) +- Adobe products show issue [\#2347](https://github.com/pypeclub/OpenPype/pull/2347) +- Maya Look Assigner: Fix Python 3 compatibility [\#2343](https://github.com/pypeclub/OpenPype/pull/2343) +- Remove wrongly used host for hook [\#2342](https://github.com/pypeclub/OpenPype/pull/2342) +- Tools: Use Qt context on tools show [\#2340](https://github.com/pypeclub/OpenPype/pull/2340) +- Flame: Fix default argument value in custom dictionary [\#2339](https://github.com/pypeclub/OpenPype/pull/2339) +- Timers Manager: Disable auto stop timer on linux platform [\#2334](https://github.com/pypeclub/OpenPype/pull/2334) +- nuke: bake preset single input exception [\#2331](https://github.com/pypeclub/OpenPype/pull/2331) +- Hiero: fixing multiple templates at a hierarchy parent [\#2330](https://github.com/pypeclub/OpenPype/pull/2330) +- Fix - provider icons are pulled from a folder [\#2326](https://github.com/pypeclub/OpenPype/pull/2326) +- InputLinks: Typo in "inputLinks" key [\#2314](https://github.com/pypeclub/OpenPype/pull/2314) +- Deadline timeout and logging [\#2312](https://github.com/pypeclub/OpenPype/pull/2312) +- nuke: do not multiply representation on class method [\#2311](https://github.com/pypeclub/OpenPype/pull/2311) +- Workfiles tool: Fix task formatting [\#2306](https://github.com/pypeclub/OpenPype/pull/2306) +- Delivery: Fix delivery paths created on windows [\#2302](https://github.com/pypeclub/OpenPype/pull/2302) +- Maya: Deadline - fix limit groups [\#2295](https://github.com/pypeclub/OpenPype/pull/2295) +- Royal Render: Fix plugin order and OpenPype auto-detection [\#2291](https://github.com/pypeclub/OpenPype/pull/2291) +- New Publisher: Fix mapping of indexes [\#2285](https://github.com/pypeclub/OpenPype/pull/2285) +- Alternate site for site sync doesnt work for sequences [\#2284](https://github.com/pypeclub/OpenPype/pull/2284) +- FFmpeg: Execute ffprobe using list of arguments instead of string command [\#2281](https://github.com/pypeclub/OpenPype/pull/2281) +- Nuke: Anatomy fill data use task as dictionary [\#2278](https://github.com/pypeclub/OpenPype/pull/2278) +- Bug: fix variable name \_asset\_id in workfiles application [\#2274](https://github.com/pypeclub/OpenPype/pull/2274) +- Version handling fixes [\#2272](https://github.com/pypeclub/OpenPype/pull/2272) + +**Merged pull requests:** + +- Maya: Replaced PATH usage with vendored oiio path for maketx utility [\#2405](https://github.com/pypeclub/OpenPype/pull/2405) +- \[Fix\]\[MAYA\] Handle message type attribute within CollectLook [\#2394](https://github.com/pypeclub/OpenPype/pull/2394) +- Add validator to check correct version of extension for PS and AE [\#2387](https://github.com/pypeclub/OpenPype/pull/2387) +- Maya: configurable model top level validation [\#2321](https://github.com/pypeclub/OpenPype/pull/2321) +- Create test publish class for After Effects [\#2270](https://github.com/pypeclub/OpenPype/pull/2270) + +## [3.6.4](https://github.com/pypeclub/OpenPype/tree/3.6.4) (2021-11-23) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.6.3...3.6.4) + +**πŸ› Bug fixes** + +- Nuke: inventory update removes all loaded read nodes [\#2294](https://github.com/pypeclub/OpenPype/pull/2294) + +## [3.6.3](https://github.com/pypeclub/OpenPype/tree/3.6.3) (2021-11-19) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.6.2...3.6.3) + +**πŸ› Bug fixes** + +- Deadline: Fix publish targets [\#2280](https://github.com/pypeclub/OpenPype/pull/2280) + +## [3.6.2](https://github.com/pypeclub/OpenPype/tree/3.6.2) (2021-11-18) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.6.1...3.6.2) + +**πŸš€ Enhancements** + +- Tools: Assets widget [\#2265](https://github.com/pypeclub/OpenPype/pull/2265) +- SceneInventory: Choose loader in asset switcher [\#2262](https://github.com/pypeclub/OpenPype/pull/2262) +- Style: New fonts in OpenPype style [\#2256](https://github.com/pypeclub/OpenPype/pull/2256) +- Tools: SceneInventory in OpenPype [\#2255](https://github.com/pypeclub/OpenPype/pull/2255) +- Tools: Tasks widget [\#2251](https://github.com/pypeclub/OpenPype/pull/2251) +- Tools: Creator in OpenPype [\#2244](https://github.com/pypeclub/OpenPype/pull/2244) +- Added endpoint for configured extensions [\#2221](https://github.com/pypeclub/OpenPype/pull/2221) + +**πŸ› Bug fixes** + +- Tools: Parenting of tools in Nuke and Hiero [\#2266](https://github.com/pypeclub/OpenPype/pull/2266) +- limiting validator to specific editorial hosts [\#2264](https://github.com/pypeclub/OpenPype/pull/2264) +- Tools: Select Context dialog attribute fix [\#2261](https://github.com/pypeclub/OpenPype/pull/2261) +- Maya: Render publishing fails on linux [\#2260](https://github.com/pypeclub/OpenPype/pull/2260) +- LookAssigner: Fix tool reopen [\#2259](https://github.com/pypeclub/OpenPype/pull/2259) +- Standalone: editorial not publishing thumbnails on all subsets [\#2258](https://github.com/pypeclub/OpenPype/pull/2258) +- Burnins: Support mxf metadata [\#2247](https://github.com/pypeclub/OpenPype/pull/2247) +- Maya: Support for configurable AOV separator characters [\#2197](https://github.com/pypeclub/OpenPype/pull/2197) +- Maya: texture colorspace modes in looks [\#2195](https://github.com/pypeclub/OpenPype/pull/2195) + +## [3.6.1](https://github.com/pypeclub/OpenPype/tree/3.6.1) (2021-11-16) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.6.0...3.6.1) + +**πŸ› Bug fixes** + +- Loader doesn't allow changing of version before loading [\#2254](https://github.com/pypeclub/OpenPype/pull/2254) + +## [3.6.0](https://github.com/pypeclub/OpenPype/tree/3.6.0) (2021-11-15) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.5.0...3.6.0) + +### πŸ“– Documentation + +- Add alternative sites for Site Sync [\#2206](https://github.com/pypeclub/OpenPype/pull/2206) +- Add command line way of running site sync server [\#2188](https://github.com/pypeclub/OpenPype/pull/2188) + +**πŸ†• New features** + +- Add validate active site button to sync queue on a project [\#2176](https://github.com/pypeclub/OpenPype/pull/2176) +- Maya : Colorspace configuration [\#2170](https://github.com/pypeclub/OpenPype/pull/2170) +- Blender: Added support for audio [\#2168](https://github.com/pypeclub/OpenPype/pull/2168) +- Flame: a host basic integration [\#2165](https://github.com/pypeclub/OpenPype/pull/2165) +- Houdini: simple HDA workflow [\#2072](https://github.com/pypeclub/OpenPype/pull/2072) +- Basic Royal Render Integration ✨ [\#2061](https://github.com/pypeclub/OpenPype/pull/2061) +- Camera handling between Blender and Unreal [\#1988](https://github.com/pypeclub/OpenPype/pull/1988) +- switch PyQt5 for PySide2 [\#1744](https://github.com/pypeclub/OpenPype/pull/1744) + +**πŸš€ Enhancements** + +- Tools: Subset manager in OpenPype [\#2243](https://github.com/pypeclub/OpenPype/pull/2243) +- General: Skip module directories without init file [\#2239](https://github.com/pypeclub/OpenPype/pull/2239) +- General: Static interfaces [\#2238](https://github.com/pypeclub/OpenPype/pull/2238) +- Style: Fix transparent image in style [\#2235](https://github.com/pypeclub/OpenPype/pull/2235) +- Add a "following workfile versioning" option on publish [\#2225](https://github.com/pypeclub/OpenPype/pull/2225) +- Modules: Module can add cli commands [\#2224](https://github.com/pypeclub/OpenPype/pull/2224) +- Webpublisher: Separate webpublisher logic [\#2222](https://github.com/pypeclub/OpenPype/pull/2222) +- Add both side availability on Site Sync sites to Loader [\#2220](https://github.com/pypeclub/OpenPype/pull/2220) +- Tools: Center loader and library loader on show [\#2219](https://github.com/pypeclub/OpenPype/pull/2219) +- Maya : Validate shape zero [\#2212](https://github.com/pypeclub/OpenPype/pull/2212) +- Maya : validate unique names [\#2211](https://github.com/pypeclub/OpenPype/pull/2211) +- Tools: OpenPype stylesheet in workfiles tool [\#2208](https://github.com/pypeclub/OpenPype/pull/2208) +- Ftrack: Replace Queue with deque in event handlers logic [\#2204](https://github.com/pypeclub/OpenPype/pull/2204) +- Tools: New select context dialog [\#2200](https://github.com/pypeclub/OpenPype/pull/2200) +- Maya : Validate mesh ngons [\#2199](https://github.com/pypeclub/OpenPype/pull/2199) +- Dirmap in Nuke [\#2198](https://github.com/pypeclub/OpenPype/pull/2198) +- Delivery: Check 'frame' key in template for sequence delivery [\#2196](https://github.com/pypeclub/OpenPype/pull/2196) +- Settings: Site sync project settings improvement [\#2193](https://github.com/pypeclub/OpenPype/pull/2193) +- Usage of tools code [\#2185](https://github.com/pypeclub/OpenPype/pull/2185) +- Settings: Dictionary based on project roots [\#2184](https://github.com/pypeclub/OpenPype/pull/2184) +- Subset name: Be able to pass asset document to get subset name [\#2179](https://github.com/pypeclub/OpenPype/pull/2179) +- Tools: Experimental tools [\#2167](https://github.com/pypeclub/OpenPype/pull/2167) +- Loader: Refactor and use OpenPype stylesheets [\#2166](https://github.com/pypeclub/OpenPype/pull/2166) +- Add loader for linked smart objects in photoshop [\#2149](https://github.com/pypeclub/OpenPype/pull/2149) +- Burnins: DNxHD profiles handling [\#2142](https://github.com/pypeclub/OpenPype/pull/2142) +- Tools: Single access point for host tools [\#2139](https://github.com/pypeclub/OpenPype/pull/2139) + +**πŸ› Bug fixes** + +- Ftrack: Sync project ftrack id cache issue [\#2250](https://github.com/pypeclub/OpenPype/pull/2250) +- Ftrack: Session creation and Prepare project [\#2245](https://github.com/pypeclub/OpenPype/pull/2245) +- Added queue for studio processing in PS [\#2237](https://github.com/pypeclub/OpenPype/pull/2237) +- Python 2: Unicode to string conversion [\#2236](https://github.com/pypeclub/OpenPype/pull/2236) +- Fix - enum for color coding in PS [\#2234](https://github.com/pypeclub/OpenPype/pull/2234) +- Pyblish Tool: Fix targets handling [\#2232](https://github.com/pypeclub/OpenPype/pull/2232) +- Ftrack: Base event fix of 'get\_project\_from\_entity' method [\#2214](https://github.com/pypeclub/OpenPype/pull/2214) +- Maya : multiple subsets review broken [\#2210](https://github.com/pypeclub/OpenPype/pull/2210) +- Fix - different command used for Linux and Mac OS [\#2207](https://github.com/pypeclub/OpenPype/pull/2207) +- Tools: Workfiles tool don't use avalon widgets [\#2205](https://github.com/pypeclub/OpenPype/pull/2205) +- Ftrack: Fill missing ftrack id on mongo project [\#2203](https://github.com/pypeclub/OpenPype/pull/2203) +- Project Manager: Fix copying of tasks [\#2191](https://github.com/pypeclub/OpenPype/pull/2191) +- StandalonePublisher: Source validator don't expect representations [\#2190](https://github.com/pypeclub/OpenPype/pull/2190) +- Blender: Fix trying to pack an image when the shader node has no texture [\#2183](https://github.com/pypeclub/OpenPype/pull/2183) +- Maya: review viewport settings [\#2177](https://github.com/pypeclub/OpenPype/pull/2177) +- MacOS: Launching of applications may cause Permissions error [\#2175](https://github.com/pypeclub/OpenPype/pull/2175) +- Maya: Aspect ratio [\#2174](https://github.com/pypeclub/OpenPype/pull/2174) +- Blender: Fix 'Deselect All' with object not in 'Object Mode' [\#2163](https://github.com/pypeclub/OpenPype/pull/2163) +- Tools: Stylesheets are applied after tool show [\#2161](https://github.com/pypeclub/OpenPype/pull/2161) +- Maya: Collect render - fix UNC path support πŸ› [\#2158](https://github.com/pypeclub/OpenPype/pull/2158) +- Maya: Fix hotbox broken by scriptsmenu [\#2151](https://github.com/pypeclub/OpenPype/pull/2151) +- Ftrack: Ignore save warnings exception in Prepare project action [\#2150](https://github.com/pypeclub/OpenPype/pull/2150) +- Loader thumbnails with smooth edges [\#2147](https://github.com/pypeclub/OpenPype/pull/2147) +- Added validator for source files for Standalone Publisher [\#2138](https://github.com/pypeclub/OpenPype/pull/2138) + +**Merged pull requests:** + +- Bump pillow from 8.2.0 to 8.3.2 [\#2162](https://github.com/pypeclub/OpenPype/pull/2162) +- Bump axios from 0.21.1 to 0.21.4 in /website [\#2059](https://github.com/pypeclub/OpenPype/pull/2059) + +## [3.5.0](https://github.com/pypeclub/OpenPype/tree/3.5.0) (2021-10-17) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.4.1...3.5.0) + +**Deprecated:** + +- Maya: Change mayaAscii family to mayaScene [\#2106](https://github.com/pypeclub/OpenPype/pull/2106) + +**πŸ†• New features** + +- Added project and task into context change message in Maya [\#2131](https://github.com/pypeclub/OpenPype/pull/2131) +- Add ExtractBurnin to photoshop review [\#2124](https://github.com/pypeclub/OpenPype/pull/2124) +- PYPE-1218 - changed namespace to contain subset name in Maya [\#2114](https://github.com/pypeclub/OpenPype/pull/2114) +- Added running configurable disk mapping command before start of OP [\#2091](https://github.com/pypeclub/OpenPype/pull/2091) +- SFTP provider [\#2073](https://github.com/pypeclub/OpenPype/pull/2073) +- Maya: Validate setdress top group [\#2068](https://github.com/pypeclub/OpenPype/pull/2068) +- Maya: Enable publishing render attrib sets \(e.g. V-Ray Displacement\) with model [\#1955](https://github.com/pypeclub/OpenPype/pull/1955) + +**πŸš€ Enhancements** + +- Maya: make rig validators configurable in settings [\#2137](https://github.com/pypeclub/OpenPype/pull/2137) +- Settings: Updated readme for entity types in settings [\#2132](https://github.com/pypeclub/OpenPype/pull/2132) +- Nuke: unified clip loader [\#2128](https://github.com/pypeclub/OpenPype/pull/2128) +- Settings UI: Project model refreshing and sorting [\#2104](https://github.com/pypeclub/OpenPype/pull/2104) +- Create Read From Rendered - Disable Relative paths by default [\#2093](https://github.com/pypeclub/OpenPype/pull/2093) +- Added choosing different dirmap mapping if workfile synched locally [\#2088](https://github.com/pypeclub/OpenPype/pull/2088) +- General: Remove IdleManager module [\#2084](https://github.com/pypeclub/OpenPype/pull/2084) +- Tray UI: Message box about missing settings defaults [\#2080](https://github.com/pypeclub/OpenPype/pull/2080) +- Tray UI: Show menu where first click happened [\#2079](https://github.com/pypeclub/OpenPype/pull/2079) +- Global: add global validators to settings [\#2078](https://github.com/pypeclub/OpenPype/pull/2078) +- Use CRF for burnin when available [\#2070](https://github.com/pypeclub/OpenPype/pull/2070) +- Project manager: Filter first item after selection of project [\#2069](https://github.com/pypeclub/OpenPype/pull/2069) +- Nuke: Adding `still` image family workflow [\#2064](https://github.com/pypeclub/OpenPype/pull/2064) +- Maya: validate authorized loaded plugins [\#2062](https://github.com/pypeclub/OpenPype/pull/2062) +- Tools: add support for pyenv on windows [\#2051](https://github.com/pypeclub/OpenPype/pull/2051) +- SyncServer: Dropbox Provider [\#1979](https://github.com/pypeclub/OpenPype/pull/1979) +- Burnin: Get data from context with defined keys. [\#1897](https://github.com/pypeclub/OpenPype/pull/1897) +- Timers manager: Get task time [\#1896](https://github.com/pypeclub/OpenPype/pull/1896) +- TVPaint: Option to stop timer on application exit. [\#1887](https://github.com/pypeclub/OpenPype/pull/1887) + +**πŸ› Bug fixes** + +- Maya: fix model publishing [\#2130](https://github.com/pypeclub/OpenPype/pull/2130) +- Fix - oiiotool wasn't recognized even if present [\#2129](https://github.com/pypeclub/OpenPype/pull/2129) +- General: Disk mapping group [\#2120](https://github.com/pypeclub/OpenPype/pull/2120) +- Hiero: publishing effect first time makes wrong resources path [\#2115](https://github.com/pypeclub/OpenPype/pull/2115) +- Add startup script for Houdini Core. [\#2110](https://github.com/pypeclub/OpenPype/pull/2110) +- TVPaint: Behavior name of loop also accept repeat [\#2109](https://github.com/pypeclub/OpenPype/pull/2109) +- Ftrack: Project settings save custom attributes skip unknown attributes [\#2103](https://github.com/pypeclub/OpenPype/pull/2103) +- Blender: Fix NoneType error when animation\_data is missing for a rig [\#2101](https://github.com/pypeclub/OpenPype/pull/2101) +- Fix broken import in sftp provider [\#2100](https://github.com/pypeclub/OpenPype/pull/2100) +- Global: Fix docstring on publish plugin extract review [\#2097](https://github.com/pypeclub/OpenPype/pull/2097) +- Delivery Action Files Sequence fix [\#2096](https://github.com/pypeclub/OpenPype/pull/2096) +- General: Cloud mongo ca certificate issue [\#2095](https://github.com/pypeclub/OpenPype/pull/2095) +- TVPaint: Creator use context from workfile [\#2087](https://github.com/pypeclub/OpenPype/pull/2087) +- Blender: fix texture missing when publishing blend files [\#2085](https://github.com/pypeclub/OpenPype/pull/2085) +- General: Startup validations oiio tool path fix on linux [\#2083](https://github.com/pypeclub/OpenPype/pull/2083) +- Deadline: Collect deadline server does not check existence of deadline key [\#2082](https://github.com/pypeclub/OpenPype/pull/2082) +- Blender: fixed Curves with modifiers in Rigs [\#2081](https://github.com/pypeclub/OpenPype/pull/2081) +- Nuke UI scaling [\#2077](https://github.com/pypeclub/OpenPype/pull/2077) +- Maya: Fix multi-camera renders [\#2065](https://github.com/pypeclub/OpenPype/pull/2065) +- Fix Sync Queue when project disabled [\#2063](https://github.com/pypeclub/OpenPype/pull/2063) + +**Merged pull requests:** + +- Bump pywin32 from 300 to 301 [\#2086](https://github.com/pypeclub/OpenPype/pull/2086) + +## [3.4.1](https://github.com/pypeclub/OpenPype/tree/3.4.1) (2021-09-23) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.4.0...3.4.1) + +**πŸ†• New features** + +- Settings: Flag project as deactivated and hide from tools' view [\#2008](https://github.com/pypeclub/OpenPype/pull/2008) + +**πŸš€ Enhancements** + +- General: Startup validations [\#2054](https://github.com/pypeclub/OpenPype/pull/2054) +- Nuke: proxy mode validator [\#2052](https://github.com/pypeclub/OpenPype/pull/2052) +- Ftrack: Removed ftrack interface [\#2049](https://github.com/pypeclub/OpenPype/pull/2049) +- Settings UI: Deffered set value on entity [\#2044](https://github.com/pypeclub/OpenPype/pull/2044) +- Loader: Families filtering [\#2043](https://github.com/pypeclub/OpenPype/pull/2043) +- Settings UI: Project view enhancements [\#2042](https://github.com/pypeclub/OpenPype/pull/2042) +- Settings for Nuke IncrementScriptVersion [\#2039](https://github.com/pypeclub/OpenPype/pull/2039) +- Loader & Library loader: Use tools from OpenPype [\#2038](https://github.com/pypeclub/OpenPype/pull/2038) +- Adding predefined project folders creation in PM [\#2030](https://github.com/pypeclub/OpenPype/pull/2030) +- WebserverModule: Removed interface of webserver module [\#2028](https://github.com/pypeclub/OpenPype/pull/2028) +- TimersManager: Removed interface of timers manager [\#2024](https://github.com/pypeclub/OpenPype/pull/2024) +- Feature Maya import asset from scene inventory [\#2018](https://github.com/pypeclub/OpenPype/pull/2018) + +**πŸ› Bug fixes** + +- Timers manger: Typo fix [\#2058](https://github.com/pypeclub/OpenPype/pull/2058) +- Hiero: Editorial fixes [\#2057](https://github.com/pypeclub/OpenPype/pull/2057) +- Differentiate jpg sequences from thumbnail [\#2056](https://github.com/pypeclub/OpenPype/pull/2056) +- FFmpeg: Split command to list does not work [\#2046](https://github.com/pypeclub/OpenPype/pull/2046) +- Removed shell flag in subprocess call [\#2045](https://github.com/pypeclub/OpenPype/pull/2045) + +**Merged pull requests:** + +- Bump prismjs from 1.24.0 to 1.25.0 in /website [\#2050](https://github.com/pypeclub/OpenPype/pull/2050) + +## [3.4.0](https://github.com/pypeclub/OpenPype/tree/3.4.0) (2021-09-17) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.3.1...3.4.0) + +### πŸ“– Documentation + +- Documentation: Ftrack launch argsuments update [\#2014](https://github.com/pypeclub/OpenPype/pull/2014) +- Nuke Quick Start / Tutorial [\#1952](https://github.com/pypeclub/OpenPype/pull/1952) +- Houdini: add Camera, Point Cache, Composite, Redshift ROP and VDB Cache support [\#1821](https://github.com/pypeclub/OpenPype/pull/1821) + +**πŸ†• New features** + +- Nuke: Compatibility with Nuke 13 [\#2003](https://github.com/pypeclub/OpenPype/pull/2003) +- Maya: Add Xgen family support [\#1947](https://github.com/pypeclub/OpenPype/pull/1947) +- Feature/webpublisher backend [\#1876](https://github.com/pypeclub/OpenPype/pull/1876) +- Blender: Improved assets handling [\#1615](https://github.com/pypeclub/OpenPype/pull/1615) + +**πŸš€ Enhancements** + +- Added possibility to configure of synchronization of workfile version… [\#2041](https://github.com/pypeclub/OpenPype/pull/2041) +- General: Task types in profiles [\#2036](https://github.com/pypeclub/OpenPype/pull/2036) +- Console interpreter: Handle invalid sizes on initialization [\#2022](https://github.com/pypeclub/OpenPype/pull/2022) +- Ftrack: Show OpenPype versions in event server status [\#2019](https://github.com/pypeclub/OpenPype/pull/2019) +- General: Staging icon [\#2017](https://github.com/pypeclub/OpenPype/pull/2017) +- Ftrack: Sync to avalon actions have jobs [\#2015](https://github.com/pypeclub/OpenPype/pull/2015) +- Modules: Connect method is not required [\#2009](https://github.com/pypeclub/OpenPype/pull/2009) +- Settings UI: Number with configurable steps [\#2001](https://github.com/pypeclub/OpenPype/pull/2001) +- Moving project folder structure creation out of ftrack module \#1989 [\#1996](https://github.com/pypeclub/OpenPype/pull/1996) +- Configurable items for providers without Settings [\#1987](https://github.com/pypeclub/OpenPype/pull/1987) +- Global: Example addons [\#1986](https://github.com/pypeclub/OpenPype/pull/1986) +- Standalone Publisher: Extract harmony zip handle workfile template [\#1982](https://github.com/pypeclub/OpenPype/pull/1982) +- Settings UI: Number sliders [\#1978](https://github.com/pypeclub/OpenPype/pull/1978) +- Workfiles: Support more workfile templates [\#1966](https://github.com/pypeclub/OpenPype/pull/1966) +- Launcher: Fix crashes on action click [\#1964](https://github.com/pypeclub/OpenPype/pull/1964) +- Settings: Minor fixes in UI and missing default values [\#1963](https://github.com/pypeclub/OpenPype/pull/1963) +- Blender: Toggle system console works on windows [\#1962](https://github.com/pypeclub/OpenPype/pull/1962) +- Global: Settings defined by Addons/Modules [\#1959](https://github.com/pypeclub/OpenPype/pull/1959) +- CI: change release numbering triggers [\#1954](https://github.com/pypeclub/OpenPype/pull/1954) +- Global: Avalon Host name collector [\#1949](https://github.com/pypeclub/OpenPype/pull/1949) +- Global: Define hosts in CollectSceneVersion [\#1948](https://github.com/pypeclub/OpenPype/pull/1948) +- Add face sets to exported alembics [\#1942](https://github.com/pypeclub/OpenPype/pull/1942) +- OpenPype: Add version validation and `--headless` mode and update progress πŸ”„ [\#1939](https://github.com/pypeclub/OpenPype/pull/1939) +- \#1894 - adds host to template\_name\_profiles for filtering [\#1915](https://github.com/pypeclub/OpenPype/pull/1915) +- Environments: Tool environments in alphabetical order [\#1910](https://github.com/pypeclub/OpenPype/pull/1910) +- Disregard publishing time. [\#1888](https://github.com/pypeclub/OpenPype/pull/1888) +- Dynamic modules [\#1872](https://github.com/pypeclub/OpenPype/pull/1872) + +**πŸ› Bug fixes** + +- Workfiles tool: Task selection [\#2040](https://github.com/pypeclub/OpenPype/pull/2040) +- Ftrack: Delete old versions missing settings key [\#2037](https://github.com/pypeclub/OpenPype/pull/2037) +- Nuke: typo on a button [\#2034](https://github.com/pypeclub/OpenPype/pull/2034) +- Hiero: Fix "none" named tags [\#2033](https://github.com/pypeclub/OpenPype/pull/2033) +- FFmpeg: Subprocess arguments as list [\#2032](https://github.com/pypeclub/OpenPype/pull/2032) +- General: Fix Python 2 breaking line [\#2016](https://github.com/pypeclub/OpenPype/pull/2016) +- Bugfix/webpublisher task type [\#2006](https://github.com/pypeclub/OpenPype/pull/2006) +- Nuke thumbnails generated from middle of the sequence [\#1992](https://github.com/pypeclub/OpenPype/pull/1992) +- Nuke: last version from path gets correct version [\#1990](https://github.com/pypeclub/OpenPype/pull/1990) +- nuke, resolve, hiero: precollector order lest then 0.5 [\#1984](https://github.com/pypeclub/OpenPype/pull/1984) +- Last workfile with multiple work templates [\#1981](https://github.com/pypeclub/OpenPype/pull/1981) +- Collectors order [\#1977](https://github.com/pypeclub/OpenPype/pull/1977) +- Stop timer was within validator order range. [\#1975](https://github.com/pypeclub/OpenPype/pull/1975) +- Ftrack: arrow submodule has https url source [\#1974](https://github.com/pypeclub/OpenPype/pull/1974) +- Ftrack: Fix hosts attribute in collect ftrack username [\#1972](https://github.com/pypeclub/OpenPype/pull/1972) +- Deadline: Houdini plugins in different hierarchy [\#1970](https://github.com/pypeclub/OpenPype/pull/1970) +- Removed deprecated submodules [\#1967](https://github.com/pypeclub/OpenPype/pull/1967) +- Global: ExtractJpeg can handle filepaths with spaces [\#1961](https://github.com/pypeclub/OpenPype/pull/1961) +- Resolve path when adding to zip [\#1960](https://github.com/pypeclub/OpenPype/pull/1960) + +**Merged pull requests:** + +- Bump url-parse from 1.5.1 to 1.5.3 in /website [\#1958](https://github.com/pypeclub/OpenPype/pull/1958) +- Bump path-parse from 1.0.6 to 1.0.7 in /website [\#1933](https://github.com/pypeclub/OpenPype/pull/1933) + +## [3.3.1](https://github.com/pypeclub/OpenPype/tree/3.3.1) (2021-08-20) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.3.0...3.3.1) + +**πŸ› Bug fixes** + +- TVPaint: Fixed rendered frame indexes [\#1946](https://github.com/pypeclub/OpenPype/pull/1946) +- Maya: Menu actions fix [\#1945](https://github.com/pypeclub/OpenPype/pull/1945) +- standalone: editorial shared object problem [\#1941](https://github.com/pypeclub/OpenPype/pull/1941) +- Bugfix nuke deadline app name [\#1928](https://github.com/pypeclub/OpenPype/pull/1928) + +## [3.3.0](https://github.com/pypeclub/OpenPype/tree/3.3.0) (2021-08-17) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.2.0...3.3.0) + +### πŸ“– Documentation + +- Standalone Publish of textures family [\#1834](https://github.com/pypeclub/OpenPype/pull/1834) + +**πŸ†• New features** + +- Settings UI: Breadcrumbs in settings [\#1932](https://github.com/pypeclub/OpenPype/pull/1932) +- Maya: Scene patching 🩹on submission to Deadline [\#1923](https://github.com/pypeclub/OpenPype/pull/1923) +- Feature AE local render [\#1901](https://github.com/pypeclub/OpenPype/pull/1901) + +**πŸš€ Enhancements** + +- Python console interpreter [\#1940](https://github.com/pypeclub/OpenPype/pull/1940) +- Global: Updated logos and Default settings [\#1927](https://github.com/pypeclub/OpenPype/pull/1927) +- Check for missing ✨ Python when using `pyenv` [\#1925](https://github.com/pypeclub/OpenPype/pull/1925) +- Settings: Default values for enum [\#1920](https://github.com/pypeclub/OpenPype/pull/1920) +- Settings UI: Modifiable dict view enhance [\#1919](https://github.com/pypeclub/OpenPype/pull/1919) +- submodules: avalon-core update [\#1911](https://github.com/pypeclub/OpenPype/pull/1911) +- Ftrack: Where I run action enhancement [\#1900](https://github.com/pypeclub/OpenPype/pull/1900) +- Ftrack: Private project server actions [\#1899](https://github.com/pypeclub/OpenPype/pull/1899) +- Support nested studio plugins paths. [\#1898](https://github.com/pypeclub/OpenPype/pull/1898) +- Settings: global validators with options [\#1892](https://github.com/pypeclub/OpenPype/pull/1892) +- Settings: Conditional dict enum positioning [\#1891](https://github.com/pypeclub/OpenPype/pull/1891) +- Expose stop timer through rest api. [\#1886](https://github.com/pypeclub/OpenPype/pull/1886) +- TVPaint: Increment workfile [\#1885](https://github.com/pypeclub/OpenPype/pull/1885) +- Allow Multiple Notes to run on tasks. [\#1882](https://github.com/pypeclub/OpenPype/pull/1882) +- Prepare for pyside2 [\#1869](https://github.com/pypeclub/OpenPype/pull/1869) +- Filter hosts in settings host-enum [\#1868](https://github.com/pypeclub/OpenPype/pull/1868) +- Local actions with process identifier [\#1867](https://github.com/pypeclub/OpenPype/pull/1867) +- Workfile tool start at host launch support [\#1865](https://github.com/pypeclub/OpenPype/pull/1865) +- Anatomy schema validation [\#1864](https://github.com/pypeclub/OpenPype/pull/1864) +- Ftrack prepare project structure [\#1861](https://github.com/pypeclub/OpenPype/pull/1861) +- Maya: support for configurable `dirmap` πŸ—ΊοΈ [\#1859](https://github.com/pypeclub/OpenPype/pull/1859) +- Independent general environments [\#1853](https://github.com/pypeclub/OpenPype/pull/1853) +- TVPaint Start Frame [\#1844](https://github.com/pypeclub/OpenPype/pull/1844) +- Ftrack push attributes action adds traceback to job [\#1843](https://github.com/pypeclub/OpenPype/pull/1843) +- Prepare project action enhance [\#1838](https://github.com/pypeclub/OpenPype/pull/1838) +- nuke: settings create missing default subsets [\#1829](https://github.com/pypeclub/OpenPype/pull/1829) +- Update poetry lock [\#1823](https://github.com/pypeclub/OpenPype/pull/1823) +- Settings: settings for plugins [\#1819](https://github.com/pypeclub/OpenPype/pull/1819) +- Settings list can use template or schema as object type [\#1815](https://github.com/pypeclub/OpenPype/pull/1815) +- Maya: Deadline custom settings [\#1797](https://github.com/pypeclub/OpenPype/pull/1797) +- Maya: Shader name validation [\#1762](https://github.com/pypeclub/OpenPype/pull/1762) + +**πŸ› Bug fixes** + +- Fix - ftrack family was added incorrectly in some cases [\#1935](https://github.com/pypeclub/OpenPype/pull/1935) +- Fix - Deadline publish on Linux started Tray instead of headless publishing [\#1930](https://github.com/pypeclub/OpenPype/pull/1930) +- Maya: Validate Model Name - repair accident deletion in settings defaults [\#1929](https://github.com/pypeclub/OpenPype/pull/1929) +- Nuke: submit to farm failed due `ftrack` family remove [\#1926](https://github.com/pypeclub/OpenPype/pull/1926) +- Fix - validate takes repre\["files"\] as list all the time [\#1922](https://github.com/pypeclub/OpenPype/pull/1922) +- standalone: validator asset parents [\#1917](https://github.com/pypeclub/OpenPype/pull/1917) +- Nuke: update video file crassing [\#1916](https://github.com/pypeclub/OpenPype/pull/1916) +- Fix - texture validators for workfiles triggers only for textures workfiles [\#1914](https://github.com/pypeclub/OpenPype/pull/1914) +- Settings UI: List order works as expected [\#1906](https://github.com/pypeclub/OpenPype/pull/1906) +- Hiero: loaded clip was not set colorspace from version data [\#1904](https://github.com/pypeclub/OpenPype/pull/1904) +- Pyblish UI: Fix collecting stage processing [\#1903](https://github.com/pypeclub/OpenPype/pull/1903) +- Burnins: Use input's bitrate in h624 [\#1902](https://github.com/pypeclub/OpenPype/pull/1902) +- Bug: fixed python detection [\#1893](https://github.com/pypeclub/OpenPype/pull/1893) +- global: integrate name missing default template [\#1890](https://github.com/pypeclub/OpenPype/pull/1890) +- publisher: editorial plugins fixes [\#1889](https://github.com/pypeclub/OpenPype/pull/1889) +- Normalize path returned from Workfiles. [\#1880](https://github.com/pypeclub/OpenPype/pull/1880) +- Workfiles tool event arguments fix [\#1862](https://github.com/pypeclub/OpenPype/pull/1862) +- imageio: fix grouping [\#1856](https://github.com/pypeclub/OpenPype/pull/1856) +- Maya: don't add reference members as connections to the container set πŸ“¦ [\#1855](https://github.com/pypeclub/OpenPype/pull/1855) +- publisher: missing version in subset prop [\#1849](https://github.com/pypeclub/OpenPype/pull/1849) +- Ftrack type error fix in sync to avalon event handler [\#1845](https://github.com/pypeclub/OpenPype/pull/1845) +- Nuke: updating effects subset fail [\#1841](https://github.com/pypeclub/OpenPype/pull/1841) +- nuke: write render node skipped with crop [\#1836](https://github.com/pypeclub/OpenPype/pull/1836) +- Project folder structure overrides [\#1813](https://github.com/pypeclub/OpenPype/pull/1813) +- Maya: fix yeti settings path in extractor [\#1809](https://github.com/pypeclub/OpenPype/pull/1809) +- Failsafe for cross project containers. [\#1806](https://github.com/pypeclub/OpenPype/pull/1806) +- Houdini colector formatting keys fix [\#1802](https://github.com/pypeclub/OpenPype/pull/1802) +- Settings error dialog on show [\#1798](https://github.com/pypeclub/OpenPype/pull/1798) +- Application launch stdout/stderr in GUI build [\#1684](https://github.com/pypeclub/OpenPype/pull/1684) +- Nuke: re-use instance nodes output path [\#1577](https://github.com/pypeclub/OpenPype/pull/1577) + +**Merged pull requests:** + +- Fix - make AE workfile publish to Ftrack configurable [\#1937](https://github.com/pypeclub/OpenPype/pull/1937) +- Add support for multiple Deadline β˜ οΈβž– servers [\#1905](https://github.com/pypeclub/OpenPype/pull/1905) +- Maya: add support for `RedshiftNormalMap` node, fix `tx` linear space πŸš€ [\#1863](https://github.com/pypeclub/OpenPype/pull/1863) +- Maya: expected files -\> render products βš™οΈ overhaul [\#1812](https://github.com/pypeclub/OpenPype/pull/1812) +- PS, AE - send actual context when another webserver is running [\#1811](https://github.com/pypeclub/OpenPype/pull/1811) + +## [3.2.0](https://github.com/pypeclub/OpenPype/tree/3.2.0) (2021-07-13) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/2.18.4...3.2.0) + +### πŸ“– Documentation + +- Fix: staging and `--use-version` option [\#1786](https://github.com/pypeclub/OpenPype/pull/1786) +- Subset template and TVPaint subset template docs [\#1717](https://github.com/pypeclub/OpenPype/pull/1717) +- Overscan color extract review [\#1701](https://github.com/pypeclub/OpenPype/pull/1701) + +**πŸš€ Enhancements** + +- Nuke: ftrack family plugin settings preset [\#1805](https://github.com/pypeclub/OpenPype/pull/1805) +- Standalone publisher last project [\#1799](https://github.com/pypeclub/OpenPype/pull/1799) +- Ftrack Multiple notes as server action [\#1795](https://github.com/pypeclub/OpenPype/pull/1795) +- Settings conditional dict [\#1777](https://github.com/pypeclub/OpenPype/pull/1777) +- Settings application use python 2 only where needed [\#1776](https://github.com/pypeclub/OpenPype/pull/1776) +- Settings UI copy/paste [\#1769](https://github.com/pypeclub/OpenPype/pull/1769) +- Workfile tool widths [\#1766](https://github.com/pypeclub/OpenPype/pull/1766) +- Push hierarchical attributes care about task parent changes [\#1763](https://github.com/pypeclub/OpenPype/pull/1763) +- Application executables with environment variables [\#1757](https://github.com/pypeclub/OpenPype/pull/1757) +- Deadline: Nuke submission additional attributes [\#1756](https://github.com/pypeclub/OpenPype/pull/1756) +- Settings schema without prefill [\#1753](https://github.com/pypeclub/OpenPype/pull/1753) +- Settings Hosts enum [\#1739](https://github.com/pypeclub/OpenPype/pull/1739) +- Validate containers settings [\#1736](https://github.com/pypeclub/OpenPype/pull/1736) +- PS - added loader from sequence [\#1726](https://github.com/pypeclub/OpenPype/pull/1726) +- Autoupdate launcher [\#1725](https://github.com/pypeclub/OpenPype/pull/1725) +- Toggle Ftrack upload in StandalonePublisher [\#1708](https://github.com/pypeclub/OpenPype/pull/1708) +- Nuke: Prerender Frame Range by default [\#1699](https://github.com/pypeclub/OpenPype/pull/1699) +- Smoother edges of color triangle [\#1695](https://github.com/pypeclub/OpenPype/pull/1695) + +**πŸ› Bug fixes** + +- nuke: fixing wrong name of family folder when `used existing frames` [\#1803](https://github.com/pypeclub/OpenPype/pull/1803) +- Collect ftrack family bugs [\#1801](https://github.com/pypeclub/OpenPype/pull/1801) +- Invitee email can be None which break the Ftrack commit. [\#1788](https://github.com/pypeclub/OpenPype/pull/1788) +- Otio unrelated error on import [\#1782](https://github.com/pypeclub/OpenPype/pull/1782) +- FFprobe streams order [\#1775](https://github.com/pypeclub/OpenPype/pull/1775) +- Fix - single file files are str only, cast it to list to count properly [\#1772](https://github.com/pypeclub/OpenPype/pull/1772) +- Environments in app executable for MacOS [\#1768](https://github.com/pypeclub/OpenPype/pull/1768) +- Project specific environments [\#1767](https://github.com/pypeclub/OpenPype/pull/1767) +- Settings UI with refresh button [\#1764](https://github.com/pypeclub/OpenPype/pull/1764) +- Standalone publisher thumbnail extractor fix [\#1761](https://github.com/pypeclub/OpenPype/pull/1761) +- Anatomy others templates don't cause crash [\#1758](https://github.com/pypeclub/OpenPype/pull/1758) +- Backend acre module commit update [\#1745](https://github.com/pypeclub/OpenPype/pull/1745) +- hiero: precollect instances failing when audio selected [\#1743](https://github.com/pypeclub/OpenPype/pull/1743) +- Hiero: creator instance error [\#1742](https://github.com/pypeclub/OpenPype/pull/1742) +- Nuke: fixing render creator for no selection format failing [\#1741](https://github.com/pypeclub/OpenPype/pull/1741) +- StandalonePublisher: failing collector for editorial [\#1738](https://github.com/pypeclub/OpenPype/pull/1738) +- Local settings UI crash on missing defaults [\#1737](https://github.com/pypeclub/OpenPype/pull/1737) +- TVPaint white background on thumbnail [\#1735](https://github.com/pypeclub/OpenPype/pull/1735) +- Ftrack missing custom attribute message [\#1734](https://github.com/pypeclub/OpenPype/pull/1734) +- Launcher project changes [\#1733](https://github.com/pypeclub/OpenPype/pull/1733) +- Ftrack sync status [\#1732](https://github.com/pypeclub/OpenPype/pull/1732) +- TVPaint use layer name for default variant [\#1724](https://github.com/pypeclub/OpenPype/pull/1724) +- Default subset template for TVPaint review and workfile families [\#1716](https://github.com/pypeclub/OpenPype/pull/1716) +- Maya: Extract review hotfix [\#1714](https://github.com/pypeclub/OpenPype/pull/1714) +- Settings: Imageio improving granularity [\#1711](https://github.com/pypeclub/OpenPype/pull/1711) +- Application without executables [\#1679](https://github.com/pypeclub/OpenPype/pull/1679) +- Unreal: launching on Linux [\#1672](https://github.com/pypeclub/OpenPype/pull/1672) + +**Merged pull requests:** + +- Bump prismjs from 1.23.0 to 1.24.0 in /website [\#1773](https://github.com/pypeclub/OpenPype/pull/1773) +- TVPaint ftrack family [\#1755](https://github.com/pypeclub/OpenPype/pull/1755) + +## [2.18.4](https://github.com/pypeclub/OpenPype/tree/2.18.4) (2021-06-24) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/2.18.3...2.18.4) + +## [2.18.3](https://github.com/pypeclub/OpenPype/tree/2.18.3) (2021-06-23) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/2.18.2...2.18.3) + +## [2.18.2](https://github.com/pypeclub/OpenPype/tree/2.18.2) (2021-06-16) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.1.0...2.18.2) + +## [3.1.0](https://github.com/pypeclub/OpenPype/tree/3.1.0) (2021-06-15) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.0.0...3.1.0) + +### πŸ“– Documentation + +- Feature Slack integration [\#1657](https://github.com/pypeclub/OpenPype/pull/1657) + +**πŸš€ Enhancements** + +- Log Viewer with OpenPype style [\#1703](https://github.com/pypeclub/OpenPype/pull/1703) +- Scrolling in OpenPype info widget [\#1702](https://github.com/pypeclub/OpenPype/pull/1702) +- OpenPype style in modules [\#1694](https://github.com/pypeclub/OpenPype/pull/1694) +- Sort applications and tools alphabetically in Settings UI [\#1689](https://github.com/pypeclub/OpenPype/pull/1689) +- \#683 - Validate Frame Range in Standalone Publisher [\#1683](https://github.com/pypeclub/OpenPype/pull/1683) +- Hiero: old container versions identify with red color [\#1682](https://github.com/pypeclub/OpenPype/pull/1682) +- Project Manger: Default name column width [\#1669](https://github.com/pypeclub/OpenPype/pull/1669) +- Remove outline in stylesheet [\#1667](https://github.com/pypeclub/OpenPype/pull/1667) +- TVPaint: Creator take layer name as default value for subset variant [\#1663](https://github.com/pypeclub/OpenPype/pull/1663) +- TVPaint custom subset template [\#1662](https://github.com/pypeclub/OpenPype/pull/1662) +- Editorial: conform assets validator [\#1659](https://github.com/pypeclub/OpenPype/pull/1659) +- Nuke - Publish simplification [\#1653](https://github.com/pypeclub/OpenPype/pull/1653) +- \#1333 - added tooltip hints to Pyblish buttons [\#1649](https://github.com/pypeclub/OpenPype/pull/1649) + +**πŸ› Bug fixes** + +- Nuke: broken publishing rendered frames [\#1707](https://github.com/pypeclub/OpenPype/pull/1707) +- Standalone publisher Thumbnail export args [\#1705](https://github.com/pypeclub/OpenPype/pull/1705) +- Bad zip can break OpenPype start [\#1691](https://github.com/pypeclub/OpenPype/pull/1691) +- Hiero: published whole edit mov [\#1687](https://github.com/pypeclub/OpenPype/pull/1687) +- Ftrack subprocess handle of stdout/stderr [\#1675](https://github.com/pypeclub/OpenPype/pull/1675) +- Settings list race condifiton and mutable dict list conversion [\#1671](https://github.com/pypeclub/OpenPype/pull/1671) +- Mac launch arguments fix [\#1660](https://github.com/pypeclub/OpenPype/pull/1660) +- Fix missing dbm python module [\#1652](https://github.com/pypeclub/OpenPype/pull/1652) +- Transparent branches in view on Mac [\#1648](https://github.com/pypeclub/OpenPype/pull/1648) +- Add asset on task item [\#1646](https://github.com/pypeclub/OpenPype/pull/1646) +- Project manager save and queue [\#1645](https://github.com/pypeclub/OpenPype/pull/1645) +- New project anatomy values [\#1644](https://github.com/pypeclub/OpenPype/pull/1644) +- Farm publishing: check if published items do exist [\#1573](https://github.com/pypeclub/OpenPype/pull/1573) + +**Merged pull requests:** + +- Bump normalize-url from 4.5.0 to 4.5.1 in /website [\#1686](https://github.com/pypeclub/OpenPype/pull/1686) + ## [3.0.0](https://github.com/pypeclub/openpype/tree/3.0.0) @@ -11,12 +1817,12 @@ - Easy to add Application versions. - Per Project Environment and plugin management. - Robust profile system for creating reviewables and burnins, with filtering based on Application, Task and data family. -- Configurable publish plugins. +- Configurable publish plugins. - Options to make any validator or extractor, optional or disabled. - Color Management is now unified under anatomy settings. - Subset naming and grouping is fully configurable. - All project attributes can now be set directly in OpenPype settings. -- Studio Setting can be locked to prevent unwanted artist changes. +- Studio Setting can be locked to prevent unwanted artist changes. - You can now add per project and per task type templates for workfile initialization in most hosts. - Too many other individual configurable option to list in this changelog :) @@ -774,8 +2580,6 @@ - Standalone Publisher: getting fps from context instead of nonexistent entity [\#729](https://github.com/pypeclub/pype/pull/729) -# Changelog - ## [2.13.6](https://github.com/pypeclub/pype/tree/2.13.6) (2020-11-15) [Full Changelog](https://github.com/pypeclub/pype/compare/2.13.5...2.13.6) @@ -1565,10 +3369,4 @@ A large cleanup release. Most of the change are under the hood. - _(avalon)_ subsets in maya 2019 weren't behaving correctly in the outliner -\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* - - -\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* - - \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* From d05ce1591022e95f802c8a4e7cc73249f42b2cde Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 19 Oct 2022 12:13:32 +0200 Subject: [PATCH 183/201] add .github_change_generator config file to gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 4b773e97ed..18e7cd7bf2 100644 --- a/.gitignore +++ b/.gitignore @@ -110,3 +110,5 @@ tools/run_eventserver.* # Developer tools tools/dev_* + +.github_changelog_generator From 3c6fb3511e89fc12e6945a78f228ae9dd91a3c09 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 19 Oct 2022 12:30:07 +0200 Subject: [PATCH 184/201] update changelog.md --- CHANGELOG.md | 1763 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 1706 insertions(+), 57 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dca0e7ecef..d841eb9747 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,44 +1,72 @@ # Changelog -## [3.14.4-nightly.3](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [Unreleased](https://github.com/pypeclub/OpenPype/tree/HEAD) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.3...HEAD) +**πŸ†• New features** + +- Webpublisher: use max next published version number for all items in batch [\#3961](https://github.com/pypeclub/OpenPype/pull/3961) +- General: Control Thumbnail integration via explicit configuration profiles [\#3951](https://github.com/pypeclub/OpenPype/pull/3951) + **πŸš€ Enhancements** +- Publisher: Multiselection in card view [\#3993](https://github.com/pypeclub/OpenPype/pull/3993) +- TrayPublisher: Original Basename cause crash too early [\#3990](https://github.com/pypeclub/OpenPype/pull/3990) +- Tray Publisher: add `originalBasename` data to simple creators [\#3988](https://github.com/pypeclub/OpenPype/pull/3988) +- General: Custom paths to ffmpeg and OpenImageIO tools [\#3982](https://github.com/pypeclub/OpenPype/pull/3982) +- Integrate: Preserve existing subset group if instance does not set it for new version [\#3976](https://github.com/pypeclub/OpenPype/pull/3976) +- Publisher: Prepare publisher controller for remote publishing [\#3972](https://github.com/pypeclub/OpenPype/pull/3972) +- Maya: new style dataclasses in maya deadline submitter plugin [\#3968](https://github.com/pypeclub/OpenPype/pull/3968) +- Maya: Define preffered Qt bindings for Qt.py and qtpy [\#3963](https://github.com/pypeclub/OpenPype/pull/3963) +- Settings: Move imageio from project anatomy to project settings \[pypeclub\] [\#3959](https://github.com/pypeclub/OpenPype/pull/3959) +- TrayPublisher: Extract thumbnail for other families [\#3952](https://github.com/pypeclub/OpenPype/pull/3952) +- Publisher: Pass instance to subset name method on update [\#3949](https://github.com/pypeclub/OpenPype/pull/3949) - General: Set root environments before DCC launch [\#3947](https://github.com/pypeclub/OpenPype/pull/3947) - Refactor: changed legacy way to update database for Hero version integrate [\#3941](https://github.com/pypeclub/OpenPype/pull/3941) - Maya: Moved plugin from global to maya [\#3939](https://github.com/pypeclub/OpenPype/pull/3939) +- Publisher: Create dialog is part of main window [\#3936](https://github.com/pypeclub/OpenPype/pull/3936) - Fusion: Implement Alembic and FBX mesh loader [\#3927](https://github.com/pypeclub/OpenPype/pull/3927) -- Publisher: Instances can be marked as stored [\#3846](https://github.com/pypeclub/OpenPype/pull/3846) +- Maya: Remove hardcoded requirement for maya/ start for image file prefix [\#3873](https://github.com/pypeclub/OpenPype/pull/3873) **πŸ› Bug fixes** +- TrayPublisher: Disable sequences in batch mov creator [\#3996](https://github.com/pypeclub/OpenPype/pull/3996) +- Fix - tags might be missing on representation [\#3985](https://github.com/pypeclub/OpenPype/pull/3985) +- Resolve: Fix usage of functions from lib [\#3983](https://github.com/pypeclub/OpenPype/pull/3983) +- Maya: remove invalid prefix token for non-multipart outputs [\#3981](https://github.com/pypeclub/OpenPype/pull/3981) +- Ftrack: Fix schema cache for Python 2 [\#3980](https://github.com/pypeclub/OpenPype/pull/3980) +- Maya: add object to attr.s declaration [\#3973](https://github.com/pypeclub/OpenPype/pull/3973) - Maya: Deadline OutputFilePath hack regression for Renderman [\#3950](https://github.com/pypeclub/OpenPype/pull/3950) - Houdini: Fix validate workfile paths for non-parm file references [\#3948](https://github.com/pypeclub/OpenPype/pull/3948) - Photoshop: missed sync published version of workfile with workfile [\#3946](https://github.com/pypeclub/OpenPype/pull/3946) +- Maya: Set default value for RenderSetupIncludeLights option [\#3944](https://github.com/pypeclub/OpenPype/pull/3944) - Maya: fix regression of Renderman Deadline hack [\#3943](https://github.com/pypeclub/OpenPype/pull/3943) +- Kitsu: 2 fixes, nb\_frames and Shot type error [\#3940](https://github.com/pypeclub/OpenPype/pull/3940) - Tray: Change order of attribute changes [\#3938](https://github.com/pypeclub/OpenPype/pull/3938) - AttributeDefs: Fix crashing multivalue of files widget [\#3937](https://github.com/pypeclub/OpenPype/pull/3937) - General: Fix links query on hero version [\#3900](https://github.com/pypeclub/OpenPype/pull/3900) - Publisher: Files Drag n Drop cleanup [\#3888](https://github.com/pypeclub/OpenPype/pull/3888) -- Maya: Render settings validation attribute check tweak logging [\#3821](https://github.com/pypeclub/OpenPype/pull/3821) **πŸ”€ Refactored code** +- Flame: Import lib functions from lib [\#3992](https://github.com/pypeclub/OpenPype/pull/3992) +- General: Fix deprecated warning in legacy creator [\#3978](https://github.com/pypeclub/OpenPype/pull/3978) +- Blender: Remove openpype api imports [\#3977](https://github.com/pypeclub/OpenPype/pull/3977) +- General: Use direct import of resources [\#3964](https://github.com/pypeclub/OpenPype/pull/3964) - General: Direct settings imports [\#3934](https://github.com/pypeclub/OpenPype/pull/3934) - General: import 'Logger' from 'openpype.lib' [\#3926](https://github.com/pypeclub/OpenPype/pull/3926) +- General: Remove deprecated functions from lib [\#3907](https://github.com/pypeclub/OpenPype/pull/3907) **Merged pull requests:** - Maya + Yeti: Load Yeti Cache fix frame number recognition [\#3942](https://github.com/pypeclub/OpenPype/pull/3942) - Fusion: Implement callbacks to Fusion's event system thread [\#3928](https://github.com/pypeclub/OpenPype/pull/3928) - Photoshop: create single frame image in Ftrack as review [\#3908](https://github.com/pypeclub/OpenPype/pull/3908) -- Maya: Warn correctly about nodes in render instance with unexpected names [\#3816](https://github.com/pypeclub/OpenPype/pull/3816) ## [3.14.3](https://github.com/pypeclub/OpenPype/tree/3.14.3) (2022-10-03) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.14.3-nightly.7...3.14.3) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.2...3.14.3) **πŸš€ Enhancements** @@ -51,10 +79,6 @@ - Publisher: Increase size of main window [\#3862](https://github.com/pypeclub/OpenPype/pull/3862) - Flame: make migratable projects after creation [\#3860](https://github.com/pypeclub/OpenPype/pull/3860) - Photoshop: synchronize image version with workfile [\#3854](https://github.com/pypeclub/OpenPype/pull/3854) -- General: Transcoding handle float2 attr type [\#3849](https://github.com/pypeclub/OpenPype/pull/3849) -- General: Simple script for getting license information about used packages [\#3843](https://github.com/pypeclub/OpenPype/pull/3843) -- General: Workfile template build enhancements [\#3838](https://github.com/pypeclub/OpenPype/pull/3838) -- General: lock task workfiles when they are working on [\#3810](https://github.com/pypeclub/OpenPype/pull/3810) **πŸ› Bug fixes** @@ -68,7 +92,6 @@ - General: Copy of workfile does not use 'copy' function but 'copyfile' [\#3869](https://github.com/pypeclub/OpenPype/pull/3869) - Tray Publisher: skip plugin if otioTimeline is missing [\#3856](https://github.com/pypeclub/OpenPype/pull/3856) - Flame: retimed attributes are integrated with settings [\#3855](https://github.com/pypeclub/OpenPype/pull/3855) -- Maya: Extract Playblast fix textures + labelize viewport show settings [\#3852](https://github.com/pypeclub/OpenPype/pull/3852) **πŸ”€ Refactored code** @@ -78,9 +101,6 @@ - Flame: Use new Extractor location [\#3916](https://github.com/pypeclub/OpenPype/pull/3916) - Houdini: Use new Extractor location [\#3894](https://github.com/pypeclub/OpenPype/pull/3894) - Harmony: Use new Extractor location [\#3893](https://github.com/pypeclub/OpenPype/pull/3893) -- Hiero: Use new Extractor location [\#3851](https://github.com/pypeclub/OpenPype/pull/3851) -- Maya: Remove old legacy \(ftrack\) plug-ins that are of no use anymore [\#3819](https://github.com/pypeclub/OpenPype/pull/3819) -- Nuke: Use new Extractor location [\#3799](https://github.com/pypeclub/OpenPype/pull/3799) **Merged pull requests:** @@ -90,7 +110,17 @@ ## [3.14.2](https://github.com/pypeclub/OpenPype/tree/3.14.2) (2022-09-12) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.14.2-nightly.5...3.14.2) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.1...3.14.2) + +### πŸ“– Documentation + +- Documentation: Anatomy templates [\#3618](https://github.com/pypeclub/OpenPype/pull/3618) + +**πŸ†• New features** + +- Nuke: Build workfile by template [\#3763](https://github.com/pypeclub/OpenPype/pull/3763) +- Houdini: Publishing workfiles [\#3697](https://github.com/pypeclub/OpenPype/pull/3697) +- Global: making collect audio plugin global [\#3679](https://github.com/pypeclub/OpenPype/pull/3679) **πŸš€ Enhancements** @@ -98,6 +128,13 @@ - Flame: OpenPype submenu to batch and media manager [\#3825](https://github.com/pypeclub/OpenPype/pull/3825) - General: Better pixmap scaling [\#3809](https://github.com/pypeclub/OpenPype/pull/3809) - Photoshop: attempt to speed up ExtractImage [\#3793](https://github.com/pypeclub/OpenPype/pull/3793) +- SyncServer: Added cli commands for sync server [\#3765](https://github.com/pypeclub/OpenPype/pull/3765) +- Kitsu: Drop 'entities root' setting. [\#3739](https://github.com/pypeclub/OpenPype/pull/3739) +- git: update gitignore [\#3722](https://github.com/pypeclub/OpenPype/pull/3722) +- Blender: Publisher collect workfile representation [\#3670](https://github.com/pypeclub/OpenPype/pull/3670) +- Maya: move set render settings menu entry [\#3669](https://github.com/pypeclub/OpenPype/pull/3669) +- Scene Inventory: Maya add actions to select from or to scene [\#3659](https://github.com/pypeclub/OpenPype/pull/3659) +- Scene Inventory: Add subsetGroup column [\#3658](https://github.com/pypeclub/OpenPype/pull/3658) **πŸ› Bug fixes** @@ -107,42 +144,625 @@ - Igniter: Fix status handling when version is already installed [\#3804](https://github.com/pypeclub/OpenPype/pull/3804) - Resolve: Addon import is Python 2 compatible [\#3798](https://github.com/pypeclub/OpenPype/pull/3798) - Hiero: retimed clip publishing is working [\#3792](https://github.com/pypeclub/OpenPype/pull/3792) +- nuke: validate write node is not failing due wrong type [\#3780](https://github.com/pypeclub/OpenPype/pull/3780) +- Fix - changed format of version string in pyproject.toml [\#3777](https://github.com/pypeclub/OpenPype/pull/3777) +- Ftrack status fix typo prgoress -\> progress [\#3761](https://github.com/pypeclub/OpenPype/pull/3761) +- Fix version resolution [\#3757](https://github.com/pypeclub/OpenPype/pull/3757) +- Maya: `containerise` dont skip empty values [\#3674](https://github.com/pypeclub/OpenPype/pull/3674) + +**πŸ”€ Refactored code** + +- Photoshop: Use new Extractor location [\#3789](https://github.com/pypeclub/OpenPype/pull/3789) +- Blender: Use new Extractor location [\#3787](https://github.com/pypeclub/OpenPype/pull/3787) +- AfterEffects: Use new Extractor location [\#3784](https://github.com/pypeclub/OpenPype/pull/3784) +- General: Remove unused teshost [\#3773](https://github.com/pypeclub/OpenPype/pull/3773) +- General: Copied 'Extractor' plugin to publish pipeline [\#3771](https://github.com/pypeclub/OpenPype/pull/3771) +- General: Move queries of asset and representation links [\#3770](https://github.com/pypeclub/OpenPype/pull/3770) +- General: Move create project folders to pipeline [\#3768](https://github.com/pypeclub/OpenPype/pull/3768) +- General: Create project function moved to client code [\#3766](https://github.com/pypeclub/OpenPype/pull/3766) +- Maya: Refactor submit deadline to use AbstractSubmitDeadline [\#3759](https://github.com/pypeclub/OpenPype/pull/3759) +- General: Change publish template settings location [\#3755](https://github.com/pypeclub/OpenPype/pull/3755) +- General: Move hostdirname functionality into host [\#3749](https://github.com/pypeclub/OpenPype/pull/3749) +- General: Move publish utils to pipeline [\#3745](https://github.com/pypeclub/OpenPype/pull/3745) +- Houdini: Define houdini as addon [\#3735](https://github.com/pypeclub/OpenPype/pull/3735) +- Fusion: Defined fusion as addon [\#3733](https://github.com/pypeclub/OpenPype/pull/3733) +- Flame: Defined flame as addon [\#3732](https://github.com/pypeclub/OpenPype/pull/3732) +- Resolve: Define resolve as addon [\#3727](https://github.com/pypeclub/OpenPype/pull/3727) + +**Merged pull requests:** + +- Standalone Publisher: Ignore empty labels, then still use name like other asset models [\#3779](https://github.com/pypeclub/OpenPype/pull/3779) +- Kitsu - sync\_all\_project - add list ignore\_projects [\#3776](https://github.com/pypeclub/OpenPype/pull/3776) ## [3.14.1](https://github.com/pypeclub/OpenPype/tree/3.14.1) (2022-08-30) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.14.1-nightly.4...3.14.1) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.0...3.14.1) + +### πŸ“– Documentation + +- Documentation: Few updates [\#3698](https://github.com/pypeclub/OpenPype/pull/3698) +- Documentation: Settings development [\#3660](https://github.com/pypeclub/OpenPype/pull/3660) + +**πŸ†• New features** + +- Webpublisher:change create flatten image into tri state [\#3678](https://github.com/pypeclub/OpenPype/pull/3678) +- Blender: validators code correction with settings and defaults [\#3662](https://github.com/pypeclub/OpenPype/pull/3662) + +**πŸš€ Enhancements** + +- General: Thumbnail can use project roots [\#3750](https://github.com/pypeclub/OpenPype/pull/3750) +- Settings: Remove settings lock on tray exit [\#3720](https://github.com/pypeclub/OpenPype/pull/3720) +- General: Added helper getters to modules manager [\#3712](https://github.com/pypeclub/OpenPype/pull/3712) +- Unreal: Define unreal as module and use host class [\#3701](https://github.com/pypeclub/OpenPype/pull/3701) +- Settings: Lock settings UI session [\#3700](https://github.com/pypeclub/OpenPype/pull/3700) +- General: Benevolent context label collector [\#3686](https://github.com/pypeclub/OpenPype/pull/3686) +- Ftrack: Store ftrack entities on hierarchy integration to instances [\#3677](https://github.com/pypeclub/OpenPype/pull/3677) +- Ftrack: More logs related to auto sync value change [\#3671](https://github.com/pypeclub/OpenPype/pull/3671) +- Blender: ops refresh manager after process events [\#3663](https://github.com/pypeclub/OpenPype/pull/3663) + +**πŸ› Bug fixes** + +- Maya: Fix typo in getPanel argument `with_focus` -\> `withFocus` [\#3753](https://github.com/pypeclub/OpenPype/pull/3753) +- General: Smaller fixes of imports [\#3748](https://github.com/pypeclub/OpenPype/pull/3748) +- General: Logger tweaks [\#3741](https://github.com/pypeclub/OpenPype/pull/3741) +- Nuke: missing job dependency if multiple bake streams [\#3737](https://github.com/pypeclub/OpenPype/pull/3737) +- Nuke: color-space settings from anatomy is working [\#3721](https://github.com/pypeclub/OpenPype/pull/3721) +- Settings: Fix studio default anatomy save [\#3716](https://github.com/pypeclub/OpenPype/pull/3716) +- Maya: Use project name instead of project code [\#3709](https://github.com/pypeclub/OpenPype/pull/3709) +- Settings: Fix project overrides save [\#3708](https://github.com/pypeclub/OpenPype/pull/3708) +- Workfiles tool: Fix published workfile filtering [\#3704](https://github.com/pypeclub/OpenPype/pull/3704) +- PS, AE: Provide default variant value for workfile subset [\#3703](https://github.com/pypeclub/OpenPype/pull/3703) +- RoyalRender: handle host name that is not set [\#3695](https://github.com/pypeclub/OpenPype/pull/3695) +- Flame: retime is working on clip publishing [\#3684](https://github.com/pypeclub/OpenPype/pull/3684) +- Webpublisher: added check for empty context [\#3682](https://github.com/pypeclub/OpenPype/pull/3682) + +**πŸ”€ Refactored code** + +- General: Move delivery logic to pipeline [\#3751](https://github.com/pypeclub/OpenPype/pull/3751) +- General: Host addons cleanup [\#3744](https://github.com/pypeclub/OpenPype/pull/3744) +- Webpublisher: Webpublisher is used as addon [\#3740](https://github.com/pypeclub/OpenPype/pull/3740) +- Photoshop: Defined photoshop as addon [\#3736](https://github.com/pypeclub/OpenPype/pull/3736) +- Harmony: Defined harmony as addon [\#3734](https://github.com/pypeclub/OpenPype/pull/3734) +- General: Module interfaces cleanup [\#3731](https://github.com/pypeclub/OpenPype/pull/3731) +- AfterEffects: Move AE functions from general lib [\#3730](https://github.com/pypeclub/OpenPype/pull/3730) +- Blender: Define blender as module [\#3729](https://github.com/pypeclub/OpenPype/pull/3729) +- AfterEffects: Define AfterEffects as module [\#3728](https://github.com/pypeclub/OpenPype/pull/3728) +- General: Replace PypeLogger with Logger [\#3725](https://github.com/pypeclub/OpenPype/pull/3725) +- Nuke: Define nuke as module [\#3724](https://github.com/pypeclub/OpenPype/pull/3724) +- General: Move subset name functionality [\#3723](https://github.com/pypeclub/OpenPype/pull/3723) +- General: Move creators plugin getter [\#3714](https://github.com/pypeclub/OpenPype/pull/3714) +- General: Move constants from lib to client [\#3713](https://github.com/pypeclub/OpenPype/pull/3713) +- Loader: Subset groups using client operations [\#3710](https://github.com/pypeclub/OpenPype/pull/3710) +- TVPaint: Defined as module [\#3707](https://github.com/pypeclub/OpenPype/pull/3707) +- StandalonePublisher: Define StandalonePublisher as module [\#3706](https://github.com/pypeclub/OpenPype/pull/3706) +- TrayPublisher: Define TrayPublisher as module [\#3705](https://github.com/pypeclub/OpenPype/pull/3705) +- General: Move context specific functions to context tools [\#3702](https://github.com/pypeclub/OpenPype/pull/3702) + +**Merged pull requests:** + +- Hiero: Define hiero as module [\#3717](https://github.com/pypeclub/OpenPype/pull/3717) +- Deadline: better logging for DL webservice failures [\#3694](https://github.com/pypeclub/OpenPype/pull/3694) +- Photoshop: resize saved images in ExtractReview for ffmpeg [\#3676](https://github.com/pypeclub/OpenPype/pull/3676) +- Nuke: Validation refactory to new publisher [\#3567](https://github.com/pypeclub/OpenPype/pull/3567) ## [3.14.0](https://github.com/pypeclub/OpenPype/tree/3.14.0) (2022-08-18) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.14.0-nightly.1...3.14.0) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.13.0...3.14.0) + +**πŸ†• New features** + +- Maya: Build workfile by template [\#3578](https://github.com/pypeclub/OpenPype/pull/3578) +- Maya: Implementation of JSON layout for Unreal workflow [\#3353](https://github.com/pypeclub/OpenPype/pull/3353) +- Maya: Build workfile by template [\#3315](https://github.com/pypeclub/OpenPype/pull/3315) + +**πŸš€ Enhancements** + +- Ftrack: Addiotional component metadata [\#3685](https://github.com/pypeclub/OpenPype/pull/3685) +- Ftrack: Set task status on farm publishing [\#3680](https://github.com/pypeclub/OpenPype/pull/3680) +- Ftrack: Set task status on task creation in integrate hierarchy [\#3675](https://github.com/pypeclub/OpenPype/pull/3675) +- Maya: Disable rendering of all lights for render instances submitted through Deadline. [\#3661](https://github.com/pypeclub/OpenPype/pull/3661) +- General: Optimized OCIO configs [\#3650](https://github.com/pypeclub/OpenPype/pull/3650) + +**πŸ› Bug fixes** + +- General: Switch from hero version to versioned works [\#3691](https://github.com/pypeclub/OpenPype/pull/3691) +- General: Fix finding of last version [\#3656](https://github.com/pypeclub/OpenPype/pull/3656) +- General: Extract Review can scale with pixel aspect ratio [\#3644](https://github.com/pypeclub/OpenPype/pull/3644) +- Maya: Refactor moved usage of CreateRender settings [\#3643](https://github.com/pypeclub/OpenPype/pull/3643) +- General: Hero version representations have full context [\#3638](https://github.com/pypeclub/OpenPype/pull/3638) +- Nuke: color settings for render write node is working now [\#3632](https://github.com/pypeclub/OpenPype/pull/3632) +- Maya: FBX support for update in reference loader [\#3631](https://github.com/pypeclub/OpenPype/pull/3631) + +**πŸ”€ Refactored code** + +- General: Use client projects getter [\#3673](https://github.com/pypeclub/OpenPype/pull/3673) +- Resolve: Match folder structure to other hosts [\#3653](https://github.com/pypeclub/OpenPype/pull/3653) +- Maya: Hosts as modules [\#3647](https://github.com/pypeclub/OpenPype/pull/3647) +- TimersManager: Plugins are in timers manager module [\#3639](https://github.com/pypeclub/OpenPype/pull/3639) +- General: Move workfiles functions into pipeline [\#3637](https://github.com/pypeclub/OpenPype/pull/3637) +- General: Workfiles builder using query functions [\#3598](https://github.com/pypeclub/OpenPype/pull/3598) + +**Merged pull requests:** + +- Deadline: Global job pre load is not Pype 2 compatible [\#3666](https://github.com/pypeclub/OpenPype/pull/3666) +- Maya: Remove unused get current renderer logic [\#3645](https://github.com/pypeclub/OpenPype/pull/3645) +- Kitsu|Fix: Movie project type fails & first loop children names [\#3636](https://github.com/pypeclub/OpenPype/pull/3636) +- fix the bug of failing to extract look when UDIMs format used in AiImage [\#3628](https://github.com/pypeclub/OpenPype/pull/3628) ## [3.13.0](https://github.com/pypeclub/OpenPype/tree/3.13.0) (2022-08-09) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.13.0-nightly.1...3.13.0) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.12.2...3.13.0) + +**πŸ†• New features** + +- Support for mutliple installed versions - 3.13 [\#3605](https://github.com/pypeclub/OpenPype/pull/3605) +- Traypublisher: simple editorial publishing [\#3492](https://github.com/pypeclub/OpenPype/pull/3492) + +**πŸš€ Enhancements** + +- Editorial: Mix audio use side file for ffmpeg filters [\#3630](https://github.com/pypeclub/OpenPype/pull/3630) +- Ftrack: Comment template can contain optional keys [\#3615](https://github.com/pypeclub/OpenPype/pull/3615) +- Ftrack: Add more metadata to ftrack components [\#3612](https://github.com/pypeclub/OpenPype/pull/3612) +- General: Add context to pyblish context [\#3594](https://github.com/pypeclub/OpenPype/pull/3594) +- Kitsu: Shot&Sequence name with prefix over appends [\#3593](https://github.com/pypeclub/OpenPype/pull/3593) +- Photoshop: implemented {layer} placeholder in subset template [\#3591](https://github.com/pypeclub/OpenPype/pull/3591) +- General: Python module appdirs from git [\#3589](https://github.com/pypeclub/OpenPype/pull/3589) +- Ftrack: Update ftrack api to 2.3.3 [\#3588](https://github.com/pypeclub/OpenPype/pull/3588) +- General: New Integrator small fixes [\#3583](https://github.com/pypeclub/OpenPype/pull/3583) +- Maya: Render Creator has configurable options. [\#3097](https://github.com/pypeclub/OpenPype/pull/3097) + +**πŸ› Bug fixes** + +- Maya: fix aov separator in Redshift [\#3625](https://github.com/pypeclub/OpenPype/pull/3625) +- Fix for multi-version build on Mac [\#3622](https://github.com/pypeclub/OpenPype/pull/3622) +- Ftrack: Sync hierarchical attributes can handle new created entities [\#3621](https://github.com/pypeclub/OpenPype/pull/3621) +- General: Extract review aspect ratio scale is calculated by ffmpeg [\#3620](https://github.com/pypeclub/OpenPype/pull/3620) +- Maya: Fix types of default settings [\#3617](https://github.com/pypeclub/OpenPype/pull/3617) +- Integrator: Don't force to have dot before frame [\#3611](https://github.com/pypeclub/OpenPype/pull/3611) +- AfterEffects: refactored integrate doesnt work formulti frame publishes [\#3610](https://github.com/pypeclub/OpenPype/pull/3610) +- Maya look data contents fails with custom attribute on group [\#3607](https://github.com/pypeclub/OpenPype/pull/3607) +- TrayPublisher: Fix wrong conflict merge [\#3600](https://github.com/pypeclub/OpenPype/pull/3600) +- Bugfix: Add OCIO as submodule to prepare for handling `maketx` color space conversion. [\#3590](https://github.com/pypeclub/OpenPype/pull/3590) +- Fix general settings environment variables resolution [\#3587](https://github.com/pypeclub/OpenPype/pull/3587) +- Editorial publishing workflow improvements [\#3580](https://github.com/pypeclub/OpenPype/pull/3580) +- General: Update imports in start script [\#3579](https://github.com/pypeclub/OpenPype/pull/3579) +- Nuke: render family integration consistency [\#3576](https://github.com/pypeclub/OpenPype/pull/3576) +- Ftrack: Handle missing published path in integrator [\#3570](https://github.com/pypeclub/OpenPype/pull/3570) +- Nuke: publish existing frames with slate with correct range [\#3555](https://github.com/pypeclub/OpenPype/pull/3555) + +**πŸ”€ Refactored code** + +- General: Plugin settings handled by plugins [\#3623](https://github.com/pypeclub/OpenPype/pull/3623) +- General: Naive implementation of document create, update, delete [\#3601](https://github.com/pypeclub/OpenPype/pull/3601) +- General: Use query functions in general code [\#3596](https://github.com/pypeclub/OpenPype/pull/3596) +- General: Separate extraction of template data into more functions [\#3574](https://github.com/pypeclub/OpenPype/pull/3574) +- General: Lib cleanup [\#3571](https://github.com/pypeclub/OpenPype/pull/3571) + +**Merged pull requests:** + +- Webpublisher: timeout for PS studio processing [\#3619](https://github.com/pypeclub/OpenPype/pull/3619) +- Core: translated validate\_containers.py into New publisher style [\#3614](https://github.com/pypeclub/OpenPype/pull/3614) +- Enable write color sets on animation publish automatically [\#3582](https://github.com/pypeclub/OpenPype/pull/3582) ## [3.12.2](https://github.com/pypeclub/OpenPype/tree/3.12.2) (2022-07-27) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.12.2-nightly.4...3.12.2) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.12.1...3.12.2) + +### πŸ“– Documentation + +- Update website with more studios [\#3554](https://github.com/pypeclub/OpenPype/pull/3554) +- Documentation: Update publishing dev docs [\#3549](https://github.com/pypeclub/OpenPype/pull/3549) + +**πŸš€ Enhancements** + +- General: Global thumbnail extractor is ready for more cases [\#3561](https://github.com/pypeclub/OpenPype/pull/3561) +- Maya: add additional validators to Settings [\#3540](https://github.com/pypeclub/OpenPype/pull/3540) +- General: Interactive console in cli [\#3526](https://github.com/pypeclub/OpenPype/pull/3526) +- Ftrack: Automatic daily review session creation can define trigger hour [\#3516](https://github.com/pypeclub/OpenPype/pull/3516) +- Ftrack: add source into Note [\#3509](https://github.com/pypeclub/OpenPype/pull/3509) +- Ftrack: Trigger custom ftrack topic of project structure creation [\#3506](https://github.com/pypeclub/OpenPype/pull/3506) +- Settings UI: Add extract to file action on project view [\#3505](https://github.com/pypeclub/OpenPype/pull/3505) +- Add pack and unpack convenience scripts [\#3502](https://github.com/pypeclub/OpenPype/pull/3502) +- General: Event system [\#3499](https://github.com/pypeclub/OpenPype/pull/3499) +- NewPublisher: Keep plugins with mismatch target in report [\#3498](https://github.com/pypeclub/OpenPype/pull/3498) +- Nuke: load clip with options from settings [\#3497](https://github.com/pypeclub/OpenPype/pull/3497) +- TrayPublisher: implemented render\_mov\_batch [\#3486](https://github.com/pypeclub/OpenPype/pull/3486) +- Migrate basic families to the new Tray Publisher [\#3469](https://github.com/pypeclub/OpenPype/pull/3469) +- Enhance powershell build scripts [\#1827](https://github.com/pypeclub/OpenPype/pull/1827) + +**πŸ› Bug fixes** + +- Maya: fix Review image plane attribute [\#3569](https://github.com/pypeclub/OpenPype/pull/3569) +- Maya: Fix animated attributes \(ie. overscan\) on loaded cameras breaking review publishing. [\#3562](https://github.com/pypeclub/OpenPype/pull/3562) +- NewPublisher: Python 2 compatible html escape [\#3559](https://github.com/pypeclub/OpenPype/pull/3559) +- Remove invalid submodules from `/vendor` [\#3557](https://github.com/pypeclub/OpenPype/pull/3557) +- General: Remove hosts filter on integrator plugins [\#3556](https://github.com/pypeclub/OpenPype/pull/3556) +- Settings: Clean default values of environments [\#3550](https://github.com/pypeclub/OpenPype/pull/3550) +- Module interfaces: Fix import error [\#3547](https://github.com/pypeclub/OpenPype/pull/3547) +- Workfiles tool: Show of tool and it's flags [\#3539](https://github.com/pypeclub/OpenPype/pull/3539) +- General: Create workfile documents works again [\#3538](https://github.com/pypeclub/OpenPype/pull/3538) +- Additional fixes for powershell scripts [\#3525](https://github.com/pypeclub/OpenPype/pull/3525) +- Maya: Added wrapper around cmds.setAttr [\#3523](https://github.com/pypeclub/OpenPype/pull/3523) +- Nuke: double slate [\#3521](https://github.com/pypeclub/OpenPype/pull/3521) +- General: Fix hash of centos oiio archive [\#3519](https://github.com/pypeclub/OpenPype/pull/3519) +- Maya: Renderman display output fix [\#3514](https://github.com/pypeclub/OpenPype/pull/3514) +- TrayPublisher: Simple creation enhancements and fixes [\#3513](https://github.com/pypeclub/OpenPype/pull/3513) +- NewPublisher: Publish attributes are properly collected [\#3510](https://github.com/pypeclub/OpenPype/pull/3510) +- TrayPublisher: Make sure host name is filled [\#3504](https://github.com/pypeclub/OpenPype/pull/3504) +- NewPublisher: Groups work and enum multivalue [\#3501](https://github.com/pypeclub/OpenPype/pull/3501) + +**πŸ”€ Refactored code** + +- General: Use query functions in integrator [\#3563](https://github.com/pypeclub/OpenPype/pull/3563) +- General: Mongo core connection moved to client [\#3531](https://github.com/pypeclub/OpenPype/pull/3531) +- Refactor Integrate Asset [\#3530](https://github.com/pypeclub/OpenPype/pull/3530) +- General: Client docstrings cleanup [\#3529](https://github.com/pypeclub/OpenPype/pull/3529) +- General: Move load related functions into pipeline [\#3527](https://github.com/pypeclub/OpenPype/pull/3527) +- General: Get current context document functions [\#3522](https://github.com/pypeclub/OpenPype/pull/3522) +- Kitsu: Use query function from client [\#3496](https://github.com/pypeclub/OpenPype/pull/3496) +- TimersManager: Use query functions [\#3495](https://github.com/pypeclub/OpenPype/pull/3495) +- Deadline: Use query functions [\#3466](https://github.com/pypeclub/OpenPype/pull/3466) +- Refactor Integrate Asset [\#2898](https://github.com/pypeclub/OpenPype/pull/2898) + +**Merged pull requests:** + +- Maya: fix active pane loss [\#3566](https://github.com/pypeclub/OpenPype/pull/3566) ## [3.12.1](https://github.com/pypeclub/OpenPype/tree/3.12.1) (2022-07-13) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.12.1-nightly.6...3.12.1) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.12.0...3.12.1) + +### πŸ“– Documentation + +- Docs: Added minimal permissions for MongoDB [\#3441](https://github.com/pypeclub/OpenPype/pull/3441) + +**πŸ†• New features** + +- Maya: Add VDB to Arnold loader [\#3433](https://github.com/pypeclub/OpenPype/pull/3433) + +**πŸš€ Enhancements** + +- TrayPublisher: Added more options for grouping of instances [\#3494](https://github.com/pypeclub/OpenPype/pull/3494) +- NewPublisher: Align creator attributes from top to bottom [\#3487](https://github.com/pypeclub/OpenPype/pull/3487) +- NewPublisher: Added ability to use label of instance [\#3484](https://github.com/pypeclub/OpenPype/pull/3484) +- General: Creator Plugins have access to project [\#3476](https://github.com/pypeclub/OpenPype/pull/3476) +- General: Better arguments order in creator init [\#3475](https://github.com/pypeclub/OpenPype/pull/3475) +- Ftrack: Trigger custom ftrack events on project creation and preparation [\#3465](https://github.com/pypeclub/OpenPype/pull/3465) +- Windows installer: Clean old files and add version subfolder [\#3445](https://github.com/pypeclub/OpenPype/pull/3445) +- Blender: Bugfix - Set fps properly on open [\#3426](https://github.com/pypeclub/OpenPype/pull/3426) +- Hiero: Add custom scripts menu [\#3425](https://github.com/pypeclub/OpenPype/pull/3425) +- Blender: pre pyside install for all platforms [\#3400](https://github.com/pypeclub/OpenPype/pull/3400) +- Maya: Add additional playblast options to review Extractor. [\#3384](https://github.com/pypeclub/OpenPype/pull/3384) +- Maya: Ability to set resolution for playblasts from asset, and override through review instance. [\#3360](https://github.com/pypeclub/OpenPype/pull/3360) +- Maya: Redshift Volume Loader Implement update, remove, switch + fix vdb sequence support [\#3197](https://github.com/pypeclub/OpenPype/pull/3197) +- Maya: Implement `iter_visible_nodes_in_range` for extracting Alembics [\#3100](https://github.com/pypeclub/OpenPype/pull/3100) + +**πŸ› Bug fixes** + +- TrayPublisher: Keep use instance label in list view [\#3493](https://github.com/pypeclub/OpenPype/pull/3493) +- General: Extract review use first frame of input sequence [\#3491](https://github.com/pypeclub/OpenPype/pull/3491) +- General: Fix Plist loading for application launch [\#3485](https://github.com/pypeclub/OpenPype/pull/3485) +- Nuke: Workfile tools open on start [\#3479](https://github.com/pypeclub/OpenPype/pull/3479) +- New Publisher: Disabled context change allows creation [\#3478](https://github.com/pypeclub/OpenPype/pull/3478) +- General: thumbnail extractor fix [\#3474](https://github.com/pypeclub/OpenPype/pull/3474) +- Kitsu: bugfix with sync-service ans publish plugins [\#3473](https://github.com/pypeclub/OpenPype/pull/3473) +- Flame: solved problem with multi-selected loading [\#3470](https://github.com/pypeclub/OpenPype/pull/3470) +- General: Fix query function in update logic [\#3468](https://github.com/pypeclub/OpenPype/pull/3468) +- Resolve: removed few bugs [\#3464](https://github.com/pypeclub/OpenPype/pull/3464) +- General: Delete old versions is safer when ftrack is disabled [\#3462](https://github.com/pypeclub/OpenPype/pull/3462) +- Nuke: fixing metadata slate TC difference [\#3455](https://github.com/pypeclub/OpenPype/pull/3455) +- Nuke: prerender reviewable fails [\#3450](https://github.com/pypeclub/OpenPype/pull/3450) +- Maya: fix hashing in Python 3 for tile rendering [\#3447](https://github.com/pypeclub/OpenPype/pull/3447) +- LogViewer: Escape html characters in log message [\#3443](https://github.com/pypeclub/OpenPype/pull/3443) +- Nuke: Slate frame is integrated [\#3427](https://github.com/pypeclub/OpenPype/pull/3427) +- Maya: Camera extra data - additional fix for \#3304 [\#3386](https://github.com/pypeclub/OpenPype/pull/3386) +- Maya: Handle excluding `model` family from frame range validator. [\#3370](https://github.com/pypeclub/OpenPype/pull/3370) + +**πŸ”€ Refactored code** + +- Maya: Merge animation + pointcache extractor logic [\#3461](https://github.com/pypeclub/OpenPype/pull/3461) +- Maya: Re-use `maintained_time` from lib [\#3460](https://github.com/pypeclub/OpenPype/pull/3460) +- General: Use query functions in global plugins [\#3459](https://github.com/pypeclub/OpenPype/pull/3459) +- Clockify: Use query functions in clockify actions [\#3458](https://github.com/pypeclub/OpenPype/pull/3458) +- General: Use query functions in rest api calls [\#3457](https://github.com/pypeclub/OpenPype/pull/3457) +- General: Use query functions in openpype lib functions [\#3454](https://github.com/pypeclub/OpenPype/pull/3454) +- General: Use query functions in load utils [\#3446](https://github.com/pypeclub/OpenPype/pull/3446) +- General: Move publish plugin and publish render abstractions [\#3442](https://github.com/pypeclub/OpenPype/pull/3442) +- General: Use Anatomy after move to pipeline [\#3436](https://github.com/pypeclub/OpenPype/pull/3436) +- General: Anatomy moved to pipeline [\#3435](https://github.com/pypeclub/OpenPype/pull/3435) +- Fusion: Use client query functions [\#3380](https://github.com/pypeclub/OpenPype/pull/3380) +- Resolve: Use client query functions [\#3379](https://github.com/pypeclub/OpenPype/pull/3379) +- General: Host implementation defined with class [\#3337](https://github.com/pypeclub/OpenPype/pull/3337) ## [3.12.0](https://github.com/pypeclub/OpenPype/tree/3.12.0) (2022-06-28) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.12.0-nightly.3...3.12.0) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.11.1...3.12.0) + +### πŸ“– Documentation + +- Fix typo in documentation: pyenv on mac [\#3417](https://github.com/pypeclub/OpenPype/pull/3417) +- Linux: update OIIO package [\#3401](https://github.com/pypeclub/OpenPype/pull/3401) + +**πŸ†• New features** + +- Shotgrid: Add production beta of shotgrid integration [\#2921](https://github.com/pypeclub/OpenPype/pull/2921) + +**πŸš€ Enhancements** + +- Webserver: Added CORS middleware [\#3422](https://github.com/pypeclub/OpenPype/pull/3422) +- Attribute Defs UI: Files widget show what is allowed to drop in [\#3411](https://github.com/pypeclub/OpenPype/pull/3411) +- General: Add ability to change user value for templates [\#3366](https://github.com/pypeclub/OpenPype/pull/3366) +- Hosts: More options for in-host callbacks [\#3357](https://github.com/pypeclub/OpenPype/pull/3357) +- Multiverse: expose some settings to GUI [\#3350](https://github.com/pypeclub/OpenPype/pull/3350) +- Maya: Allow more data to be published along camera πŸŽ₯ [\#3304](https://github.com/pypeclub/OpenPype/pull/3304) +- Add root keys and project keys to create starting folder [\#2755](https://github.com/pypeclub/OpenPype/pull/2755) + +**πŸ› Bug fixes** + +- NewPublisher: Fix subset name change on change of creator plugin [\#3420](https://github.com/pypeclub/OpenPype/pull/3420) +- Bug: fix invalid avalon import [\#3418](https://github.com/pypeclub/OpenPype/pull/3418) +- Nuke: Fix keyword argument in query function [\#3414](https://github.com/pypeclub/OpenPype/pull/3414) +- Houdini: fix loading and updating vbd/bgeo sequences [\#3408](https://github.com/pypeclub/OpenPype/pull/3408) +- Nuke: Collect representation files based on Write [\#3407](https://github.com/pypeclub/OpenPype/pull/3407) +- General: Filter representations before integration start [\#3398](https://github.com/pypeclub/OpenPype/pull/3398) +- Maya: look collector typo [\#3392](https://github.com/pypeclub/OpenPype/pull/3392) +- TVPaint: Make sure exit code is set to not None [\#3382](https://github.com/pypeclub/OpenPype/pull/3382) +- Maya: vray device aspect ratio fix [\#3381](https://github.com/pypeclub/OpenPype/pull/3381) +- Flame: bunch of publishing issues [\#3377](https://github.com/pypeclub/OpenPype/pull/3377) +- Harmony: added unc path to zifile command in Harmony [\#3372](https://github.com/pypeclub/OpenPype/pull/3372) +- Standalone: settings improvements [\#3355](https://github.com/pypeclub/OpenPype/pull/3355) +- Nuke: Load full model hierarchy by default [\#3328](https://github.com/pypeclub/OpenPype/pull/3328) +- Nuke: multiple baking streams with correct slate [\#3245](https://github.com/pypeclub/OpenPype/pull/3245) +- Maya: fix image prefix warning in validator [\#3128](https://github.com/pypeclub/OpenPype/pull/3128) + +**πŸ”€ Refactored code** + +- Unreal: Use client query functions [\#3421](https://github.com/pypeclub/OpenPype/pull/3421) +- General: Move editorial lib to pipeline [\#3419](https://github.com/pypeclub/OpenPype/pull/3419) +- Kitsu: renaming to plural func sync\_all\_projects [\#3397](https://github.com/pypeclub/OpenPype/pull/3397) +- Houdini: Use client query functions [\#3395](https://github.com/pypeclub/OpenPype/pull/3395) +- Hiero: Use client query functions [\#3393](https://github.com/pypeclub/OpenPype/pull/3393) +- Nuke: Use client query functions [\#3391](https://github.com/pypeclub/OpenPype/pull/3391) +- Maya: Use client query functions [\#3385](https://github.com/pypeclub/OpenPype/pull/3385) +- Harmony: Use client query functions [\#3378](https://github.com/pypeclub/OpenPype/pull/3378) +- Celaction: Use client query functions [\#3376](https://github.com/pypeclub/OpenPype/pull/3376) +- Photoshop: Use client query functions [\#3375](https://github.com/pypeclub/OpenPype/pull/3375) +- AfterEffects: Use client query functions [\#3374](https://github.com/pypeclub/OpenPype/pull/3374) +- TVPaint: Use client query functions [\#3340](https://github.com/pypeclub/OpenPype/pull/3340) +- Ftrack: Use client query functions [\#3339](https://github.com/pypeclub/OpenPype/pull/3339) +- Standalone Publisher: Use client query functions [\#3330](https://github.com/pypeclub/OpenPype/pull/3330) + +**Merged pull requests:** + +- Sync Queue: Added far future value for null values for dates [\#3371](https://github.com/pypeclub/OpenPype/pull/3371) +- Maya - added support for single frame playblast review [\#3369](https://github.com/pypeclub/OpenPype/pull/3369) +- Houdini: Implement Redshift Proxy Export [\#3196](https://github.com/pypeclub/OpenPype/pull/3196) ## [3.11.1](https://github.com/pypeclub/OpenPype/tree/3.11.1) (2022-06-20) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.11.1-nightly.1...3.11.1) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.11.0...3.11.1) + +**πŸ†• New features** + +- Flame: custom export temp folder [\#3346](https://github.com/pypeclub/OpenPype/pull/3346) +- Nuke: removing third-party plugins [\#3344](https://github.com/pypeclub/OpenPype/pull/3344) + +**πŸš€ Enhancements** + +- Pyblish Pype: Hiding/Close issues [\#3367](https://github.com/pypeclub/OpenPype/pull/3367) +- Ftrack: Removed requirement of pypeclub role from default settings [\#3354](https://github.com/pypeclub/OpenPype/pull/3354) +- Kitsu: Prevent crash on missing frames information [\#3352](https://github.com/pypeclub/OpenPype/pull/3352) +- Ftrack: Open browser from tray [\#3320](https://github.com/pypeclub/OpenPype/pull/3320) +- Enhancement: More control over thumbnail processing. [\#3259](https://github.com/pypeclub/OpenPype/pull/3259) + +**πŸ› Bug fixes** + +- Nuke: bake streams with slate on farm [\#3368](https://github.com/pypeclub/OpenPype/pull/3368) +- Harmony: audio validator has wrong logic [\#3364](https://github.com/pypeclub/OpenPype/pull/3364) +- Nuke: Fix missing variable in extract thumbnail [\#3363](https://github.com/pypeclub/OpenPype/pull/3363) +- Nuke: Fix precollect writes [\#3361](https://github.com/pypeclub/OpenPype/pull/3361) +- AE- fix validate\_scene\_settings and renderLocal [\#3358](https://github.com/pypeclub/OpenPype/pull/3358) +- deadline: fixing misidentification of revieables [\#3356](https://github.com/pypeclub/OpenPype/pull/3356) +- General: Create only one thumbnail per instance [\#3351](https://github.com/pypeclub/OpenPype/pull/3351) +- nuke: adding extract thumbnail settings 3.10 [\#3347](https://github.com/pypeclub/OpenPype/pull/3347) +- General: Fix last version function [\#3345](https://github.com/pypeclub/OpenPype/pull/3345) +- Deadline: added OPENPYPE\_MONGO to filter [\#3336](https://github.com/pypeclub/OpenPype/pull/3336) +- Nuke: fixing farm publishing if review is disabled [\#3306](https://github.com/pypeclub/OpenPype/pull/3306) +- Maya: Fix Yeti errors on Create, Publish and Load [\#3198](https://github.com/pypeclub/OpenPype/pull/3198) + +**πŸ”€ Refactored code** + +- Webpublisher: Use client query functions [\#3333](https://github.com/pypeclub/OpenPype/pull/3333) ## [3.11.0](https://github.com/pypeclub/OpenPype/tree/3.11.0) (2022-06-17) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.11.0-nightly.4...3.11.0) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.10.0...3.11.0) + +### πŸ“– Documentation + +- Documentation: Add app key to template documentation [\#3299](https://github.com/pypeclub/OpenPype/pull/3299) +- doc: adding royal render and multiverse to the web site [\#3285](https://github.com/pypeclub/OpenPype/pull/3285) +- Module: Kitsu module [\#2650](https://github.com/pypeclub/OpenPype/pull/2650) + +**πŸ†• New features** + +- Multiverse: fixed composition write, full docs, cosmetics [\#3178](https://github.com/pypeclub/OpenPype/pull/3178) + +**πŸš€ Enhancements** + +- Settings: Settings can be extracted from UI [\#3323](https://github.com/pypeclub/OpenPype/pull/3323) +- updated poetry installation source [\#3316](https://github.com/pypeclub/OpenPype/pull/3316) +- Ftrack: Action to easily create daily review session [\#3310](https://github.com/pypeclub/OpenPype/pull/3310) +- TVPaint: Extractor use mark in/out range to render [\#3309](https://github.com/pypeclub/OpenPype/pull/3309) +- Ftrack: Delivery action can work on ReviewSessions [\#3307](https://github.com/pypeclub/OpenPype/pull/3307) +- Maya: Look assigner UI improvements [\#3298](https://github.com/pypeclub/OpenPype/pull/3298) +- Ftrack: Action to transfer values of hierarchical attributes [\#3284](https://github.com/pypeclub/OpenPype/pull/3284) +- Maya: better handling of legacy review subsets names [\#3269](https://github.com/pypeclub/OpenPype/pull/3269) +- General: Updated windows oiio tool [\#3268](https://github.com/pypeclub/OpenPype/pull/3268) +- Unreal: add support for skeletalMesh and staticMesh to loaders [\#3267](https://github.com/pypeclub/OpenPype/pull/3267) +- Maya: reference loaders could store placeholder in referenced url [\#3264](https://github.com/pypeclub/OpenPype/pull/3264) +- TVPaint: Init file for TVPaint worker also handle guideline images [\#3250](https://github.com/pypeclub/OpenPype/pull/3250) +- Nuke: Change default icon path in settings [\#3247](https://github.com/pypeclub/OpenPype/pull/3247) +- Maya: publishing of animation and pointcache on a farm [\#3225](https://github.com/pypeclub/OpenPype/pull/3225) +- Maya: Look assigner UI improvements [\#3208](https://github.com/pypeclub/OpenPype/pull/3208) +- Nuke: add pointcache and animation to loader [\#3186](https://github.com/pypeclub/OpenPype/pull/3186) +- Nuke: Add a gizmo menu [\#3172](https://github.com/pypeclub/OpenPype/pull/3172) +- Support for Unreal 5 [\#3122](https://github.com/pypeclub/OpenPype/pull/3122) + +**πŸ› Bug fixes** + +- General: Handle empty source key on instance [\#3342](https://github.com/pypeclub/OpenPype/pull/3342) +- Houdini: Fix Houdini VDB manage update wrong file attribute name [\#3322](https://github.com/pypeclub/OpenPype/pull/3322) +- Nuke: anatomy compatibility issue hacks [\#3321](https://github.com/pypeclub/OpenPype/pull/3321) +- hiero: otio p3 compatibility issue - metadata on effect use update 3.11 [\#3314](https://github.com/pypeclub/OpenPype/pull/3314) +- General: Vendorized modules for Python 2 and update poetry lock [\#3305](https://github.com/pypeclub/OpenPype/pull/3305) +- Fix - added local targets to install host [\#3303](https://github.com/pypeclub/OpenPype/pull/3303) +- Settings: Add missing default settings for nuke gizmo [\#3301](https://github.com/pypeclub/OpenPype/pull/3301) +- Maya: Fix swaped width and height in reviews [\#3300](https://github.com/pypeclub/OpenPype/pull/3300) +- Maya: point cache publish handles Maya instances [\#3297](https://github.com/pypeclub/OpenPype/pull/3297) +- Global: extract review slate issues [\#3286](https://github.com/pypeclub/OpenPype/pull/3286) +- Webpublisher: return only active projects in ProjectsEndpoint [\#3281](https://github.com/pypeclub/OpenPype/pull/3281) +- Hiero: add support for task tags 3.10.x [\#3279](https://github.com/pypeclub/OpenPype/pull/3279) +- General: Fix Oiio tool path resolving [\#3278](https://github.com/pypeclub/OpenPype/pull/3278) +- Maya: Fix udim support for e.g. uppercase \ tag [\#3266](https://github.com/pypeclub/OpenPype/pull/3266) +- Nuke: bake reformat was failing on string type [\#3261](https://github.com/pypeclub/OpenPype/pull/3261) +- Maya: hotfix Pxr multitexture in looks [\#3260](https://github.com/pypeclub/OpenPype/pull/3260) +- Unreal: Fix Camera Loading if Layout is missing [\#3255](https://github.com/pypeclub/OpenPype/pull/3255) +- Unreal: Fixed Animation loading in UE5 [\#3240](https://github.com/pypeclub/OpenPype/pull/3240) +- Unreal: Fixed Render creation in UE5 [\#3239](https://github.com/pypeclub/OpenPype/pull/3239) +- Unreal: Fixed Camera loading in UE5 [\#3238](https://github.com/pypeclub/OpenPype/pull/3238) +- Flame: debugging [\#3224](https://github.com/pypeclub/OpenPype/pull/3224) +- add silent audio to slate [\#3162](https://github.com/pypeclub/OpenPype/pull/3162) +- Add timecode to slate [\#2929](https://github.com/pypeclub/OpenPype/pull/2929) + +**πŸ”€ Refactored code** + +- Blender: Use client query functions [\#3331](https://github.com/pypeclub/OpenPype/pull/3331) +- General: Define query functions [\#3288](https://github.com/pypeclub/OpenPype/pull/3288) + +**Merged pull requests:** + +- Maya: add pointcache family to gpu cache loader [\#3318](https://github.com/pypeclub/OpenPype/pull/3318) +- Maya look: skip empty file attributes [\#3274](https://github.com/pypeclub/OpenPype/pull/3274) ## [3.10.0](https://github.com/pypeclub/OpenPype/tree/3.10.0) (2022-05-26) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.10.0-nightly.6...3.10.0) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.9.8...3.10.0) + +### πŸ“– Documentation + +- Docs: add all-contributors config and initial list [\#3094](https://github.com/pypeclub/OpenPype/pull/3094) +- Nuke docs with videos [\#3052](https://github.com/pypeclub/OpenPype/pull/3052) + +**πŸ†• New features** + +- General: OpenPype modules publish plugins are registered in host [\#3180](https://github.com/pypeclub/OpenPype/pull/3180) +- General: Creator plugins from addons can be registered [\#3179](https://github.com/pypeclub/OpenPype/pull/3179) +- Ftrack: Single image reviewable [\#3157](https://github.com/pypeclub/OpenPype/pull/3157) +- Nuke: Expose write attributes to settings [\#3123](https://github.com/pypeclub/OpenPype/pull/3123) +- Hiero: Initial frame publish support [\#3106](https://github.com/pypeclub/OpenPype/pull/3106) +- Unreal: Render Publishing [\#2917](https://github.com/pypeclub/OpenPype/pull/2917) +- AfterEffects: Implemented New Publisher [\#2838](https://github.com/pypeclub/OpenPype/pull/2838) +- Unreal: Rendering implementation [\#2410](https://github.com/pypeclub/OpenPype/pull/2410) + +**πŸš€ Enhancements** + +- Maya: FBX camera export [\#3253](https://github.com/pypeclub/OpenPype/pull/3253) +- General: updating common vendor `scriptmenu` to 1.5.2 [\#3246](https://github.com/pypeclub/OpenPype/pull/3246) +- Project Manager: Allow to paste Tasks into multiple assets at the same time [\#3226](https://github.com/pypeclub/OpenPype/pull/3226) +- Project manager: Sped up project load [\#3216](https://github.com/pypeclub/OpenPype/pull/3216) +- Loader UI: Speed issues of loader with sync server [\#3199](https://github.com/pypeclub/OpenPype/pull/3199) +- Looks: add basic support for Renderman [\#3190](https://github.com/pypeclub/OpenPype/pull/3190) +- Maya: added clean\_import option to Import loader [\#3181](https://github.com/pypeclub/OpenPype/pull/3181) +- Add the scripts menu definition to nuke [\#3168](https://github.com/pypeclub/OpenPype/pull/3168) +- Maya: add maya 2023 to default applications [\#3167](https://github.com/pypeclub/OpenPype/pull/3167) +- Compressed bgeo publishing in SAP and Houdini loader [\#3153](https://github.com/pypeclub/OpenPype/pull/3153) +- General: Add 'dataclasses' to required python modules [\#3149](https://github.com/pypeclub/OpenPype/pull/3149) +- Hooks: Tweak logging grammar [\#3147](https://github.com/pypeclub/OpenPype/pull/3147) +- Nuke: settings for reformat node in CreateWriteRender node [\#3143](https://github.com/pypeclub/OpenPype/pull/3143) +- Houdini: Add loader for alembic through Alembic Archive node [\#3140](https://github.com/pypeclub/OpenPype/pull/3140) +- Publisher: UI Modifications and fixes [\#3139](https://github.com/pypeclub/OpenPype/pull/3139) +- General: Simplified OP modules/addons import [\#3137](https://github.com/pypeclub/OpenPype/pull/3137) +- Terminal: Tweak coloring of TrayModuleManager logging enabled states [\#3133](https://github.com/pypeclub/OpenPype/pull/3133) +- General: Cleanup some Loader docstrings [\#3131](https://github.com/pypeclub/OpenPype/pull/3131) +- Nuke: render instance with subset name filtered overrides [\#3117](https://github.com/pypeclub/OpenPype/pull/3117) +- Unreal: Layout and Camera update and remove functions reimplemented and improvements [\#3116](https://github.com/pypeclub/OpenPype/pull/3116) +- Settings: Remove environment groups from settings [\#3115](https://github.com/pypeclub/OpenPype/pull/3115) +- TVPaint: Match renderlayer key with other hosts [\#3110](https://github.com/pypeclub/OpenPype/pull/3110) +- Ftrack: AssetVersion status on publish [\#3108](https://github.com/pypeclub/OpenPype/pull/3108) +- Tray publisher: Simple families from settings [\#3105](https://github.com/pypeclub/OpenPype/pull/3105) +- Local Settings UI: Overlay messages on save and reset [\#3104](https://github.com/pypeclub/OpenPype/pull/3104) +- General: Remove repos related logic [\#3087](https://github.com/pypeclub/OpenPype/pull/3087) +- Standalone publisher: add support for bgeo and vdb [\#3080](https://github.com/pypeclub/OpenPype/pull/3080) +- Houdini: Fix FPS + outdated content pop-ups [\#3079](https://github.com/pypeclub/OpenPype/pull/3079) +- General: Add global log verbose arguments [\#3070](https://github.com/pypeclub/OpenPype/pull/3070) +- Flame: extract presets distribution [\#3063](https://github.com/pypeclub/OpenPype/pull/3063) +- Update collect\_render.py [\#3055](https://github.com/pypeclub/OpenPype/pull/3055) +- SiteSync: Added compute\_resource\_sync\_sites to sync\_server\_module [\#2983](https://github.com/pypeclub/OpenPype/pull/2983) +- Maya: Implement Hardware Renderer 2.0 support for Render Products [\#2611](https://github.com/pypeclub/OpenPype/pull/2611) + +**πŸ› Bug fixes** + +- nuke: use framerange issue [\#3254](https://github.com/pypeclub/OpenPype/pull/3254) +- Ftrack: Chunk sizes for queries has minimal condition [\#3244](https://github.com/pypeclub/OpenPype/pull/3244) +- Maya: renderman displays needs to be filtered [\#3242](https://github.com/pypeclub/OpenPype/pull/3242) +- Ftrack: Validate that the user exists on ftrack [\#3237](https://github.com/pypeclub/OpenPype/pull/3237) +- Maya: Fix support for multiple resolutions [\#3236](https://github.com/pypeclub/OpenPype/pull/3236) +- TVPaint: Look for more groups than 12 [\#3228](https://github.com/pypeclub/OpenPype/pull/3228) +- Hiero: debugging frame range and other 3.10 [\#3222](https://github.com/pypeclub/OpenPype/pull/3222) +- Project Manager: Fix persistent editors on project change [\#3218](https://github.com/pypeclub/OpenPype/pull/3218) +- Deadline: instance data overwrite fix [\#3214](https://github.com/pypeclub/OpenPype/pull/3214) +- Ftrack: Push hierarchical attributes action works [\#3210](https://github.com/pypeclub/OpenPype/pull/3210) +- Standalone Publisher: Always create new representation for thumbnail [\#3203](https://github.com/pypeclub/OpenPype/pull/3203) +- Photoshop: skip collector when automatic testing [\#3202](https://github.com/pypeclub/OpenPype/pull/3202) +- Nuke: render/workfile version sync doesn't work on farm [\#3185](https://github.com/pypeclub/OpenPype/pull/3185) +- Ftrack: Review image only if there are no mp4 reviews [\#3183](https://github.com/pypeclub/OpenPype/pull/3183) +- Ftrack: Locations deepcopy issue [\#3177](https://github.com/pypeclub/OpenPype/pull/3177) +- General: Avoid creating multiple thumbnails [\#3176](https://github.com/pypeclub/OpenPype/pull/3176) +- General/Hiero: better clip duration calculation [\#3169](https://github.com/pypeclub/OpenPype/pull/3169) +- General: Oiio conversion for ffmpeg checks for invalid characters [\#3166](https://github.com/pypeclub/OpenPype/pull/3166) +- Fix for attaching render to subset [\#3164](https://github.com/pypeclub/OpenPype/pull/3164) +- Harmony: fixed missing task name in render instance [\#3163](https://github.com/pypeclub/OpenPype/pull/3163) +- Ftrack: Action delete old versions formatting works [\#3152](https://github.com/pypeclub/OpenPype/pull/3152) +- Deadline: fix the output directory [\#3144](https://github.com/pypeclub/OpenPype/pull/3144) +- General: New Session schema [\#3141](https://github.com/pypeclub/OpenPype/pull/3141) +- General: Missing version on headless mode crash properly [\#3136](https://github.com/pypeclub/OpenPype/pull/3136) +- TVPaint: Composite layers in reversed order [\#3135](https://github.com/pypeclub/OpenPype/pull/3135) +- Nuke: fixing default settings for workfile builder loaders [\#3120](https://github.com/pypeclub/OpenPype/pull/3120) +- Nuke: fix anatomy imageio regex default [\#3119](https://github.com/pypeclub/OpenPype/pull/3119) +- General: Python 3 compatibility in queries [\#3112](https://github.com/pypeclub/OpenPype/pull/3112) +- General: TemplateResult can be copied [\#3099](https://github.com/pypeclub/OpenPype/pull/3099) +- General: Collect loaded versions skips not existing representations [\#3095](https://github.com/pypeclub/OpenPype/pull/3095) +- RoyalRender Control Submission - AVALON\_APP\_NAME default [\#3091](https://github.com/pypeclub/OpenPype/pull/3091) +- Ftrack: Update Create Folders action [\#3089](https://github.com/pypeclub/OpenPype/pull/3089) +- Maya: Collect Render fix any render cameras check [\#3088](https://github.com/pypeclub/OpenPype/pull/3088) +- Project Manager: Avoid unnecessary updates of asset documents [\#3083](https://github.com/pypeclub/OpenPype/pull/3083) +- Standalone publisher: Fix plugins install [\#3077](https://github.com/pypeclub/OpenPype/pull/3077) +- General: Extract review sequence is not converted with same names [\#3076](https://github.com/pypeclub/OpenPype/pull/3076) +- Webpublisher: Use variant value [\#3068](https://github.com/pypeclub/OpenPype/pull/3068) +- Nuke: Add aov matching even for remainder and prerender [\#3060](https://github.com/pypeclub/OpenPype/pull/3060) +- Fix support for Renderman in Maya [\#3006](https://github.com/pypeclub/OpenPype/pull/3006) + +**πŸ”€ Refactored code** + +- Avalon repo removed from Jobs workflow [\#3193](https://github.com/pypeclub/OpenPype/pull/3193) +- General: Remove remaining imports from avalon [\#3130](https://github.com/pypeclub/OpenPype/pull/3130) +- General: Move mongo db logic and remove avalon repository [\#3066](https://github.com/pypeclub/OpenPype/pull/3066) +- General: Move host install [\#3009](https://github.com/pypeclub/OpenPype/pull/3009) + +**Merged pull requests:** + +- Harmony: message length in 21.1 [\#3257](https://github.com/pypeclub/OpenPype/pull/3257) +- Harmony: 21.1 fix [\#3249](https://github.com/pypeclub/OpenPype/pull/3249) +- Maya: added jpg to filter for Image Plane Loader [\#3223](https://github.com/pypeclub/OpenPype/pull/3223) +- Webpublisher: replace space by underscore in subset names [\#3160](https://github.com/pypeclub/OpenPype/pull/3160) +- StandalonePublisher: removed Extract Background plugins [\#3093](https://github.com/pypeclub/OpenPype/pull/3093) +- Nuke: added suspend\_publish knob [\#3078](https://github.com/pypeclub/OpenPype/pull/3078) +- Bump async from 2.6.3 to 2.6.4 in /website [\#3065](https://github.com/pypeclub/OpenPype/pull/3065) +- SiteSync: Download all workfile inputs [\#2966](https://github.com/pypeclub/OpenPype/pull/2966) +- Photoshop: New Publisher [\#2933](https://github.com/pypeclub/OpenPype/pull/2933) +- Bump pillow from 9.0.0 to 9.0.1 [\#2880](https://github.com/pypeclub/OpenPype/pull/2880) +- AfterEffects: Allow configuration of default variant via Settings [\#2856](https://github.com/pypeclub/OpenPype/pull/2856) ## [3.9.8](https://github.com/pypeclub/OpenPype/tree/3.9.8) (2022-05-19) @@ -158,87 +778,1085 @@ ## [3.9.5](https://github.com/pypeclub/OpenPype/tree/3.9.5) (2022-04-25) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.10.0-nightly.2...3.9.5) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.9.4...3.9.5) ## [3.9.4](https://github.com/pypeclub/OpenPype/tree/3.9.4) (2022-04-15) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.9.4-nightly.2...3.9.4) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.9.3...3.9.4) + +### πŸ“– Documentation + +- Documentation: more info about Tasks [\#3062](https://github.com/pypeclub/OpenPype/pull/3062) +- Documentation: Python requirements to 3.7.9 [\#3035](https://github.com/pypeclub/OpenPype/pull/3035) +- Website Docs: Remove unused pages [\#2974](https://github.com/pypeclub/OpenPype/pull/2974) + +**πŸ†• New features** + +- General: Local overrides for environment variables [\#3045](https://github.com/pypeclub/OpenPype/pull/3045) +- Flame: Flare integration preparation [\#2928](https://github.com/pypeclub/OpenPype/pull/2928) + +**πŸš€ Enhancements** + +- TVPaint: Added init file for worker to triggers missing sound file dialog [\#3053](https://github.com/pypeclub/OpenPype/pull/3053) +- Ftrack: Custom attributes can be filled in slate values [\#3036](https://github.com/pypeclub/OpenPype/pull/3036) +- Resolve environment variable in google drive credential path [\#3008](https://github.com/pypeclub/OpenPype/pull/3008) + +**πŸ› Bug fixes** + +- GitHub: Updated push-protected action in github workflow [\#3064](https://github.com/pypeclub/OpenPype/pull/3064) +- Nuke: Typos in imports from Nuke implementation [\#3061](https://github.com/pypeclub/OpenPype/pull/3061) +- Hotfix: fixing deadline job publishing [\#3059](https://github.com/pypeclub/OpenPype/pull/3059) +- General: Extract Review handle invalid characters for ffmpeg [\#3050](https://github.com/pypeclub/OpenPype/pull/3050) +- Slate Review: Support to keep format on slate concatenation [\#3049](https://github.com/pypeclub/OpenPype/pull/3049) +- Webpublisher: fix processing of workfile [\#3048](https://github.com/pypeclub/OpenPype/pull/3048) +- Ftrack: Integrate ftrack api fix [\#3044](https://github.com/pypeclub/OpenPype/pull/3044) +- Webpublisher - removed wrong hardcoded family [\#3043](https://github.com/pypeclub/OpenPype/pull/3043) +- LibraryLoader: Use current project for asset query in families filter [\#3042](https://github.com/pypeclub/OpenPype/pull/3042) +- SiteSync: Providers ignore that site is disabled [\#3041](https://github.com/pypeclub/OpenPype/pull/3041) +- Unreal: Creator import fixes [\#3040](https://github.com/pypeclub/OpenPype/pull/3040) +- SiteSync: fix transitive alternate sites, fix dropdown in Local Settings [\#3018](https://github.com/pypeclub/OpenPype/pull/3018) +- Maya: invalid review flag on rendered AOVs [\#2915](https://github.com/pypeclub/OpenPype/pull/2915) + +**Merged pull requests:** + +- Deadline: reworked pools assignment [\#3051](https://github.com/pypeclub/OpenPype/pull/3051) +- Houdini: Avoid ImportError on `hdefereval` when Houdini runs without UI [\#2987](https://github.com/pypeclub/OpenPype/pull/2987) ## [3.9.3](https://github.com/pypeclub/OpenPype/tree/3.9.3) (2022-04-07) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.9.3-nightly.2...3.9.3) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.9.2...3.9.3) + +### πŸ“– Documentation + +- Documentation: Added mention of adding My Drive as a root [\#2999](https://github.com/pypeclub/OpenPype/pull/2999) +- Website Docs: Manager Ftrack fix broken links [\#2979](https://github.com/pypeclub/OpenPype/pull/2979) +- Docs: Added MongoDB requirements [\#2951](https://github.com/pypeclub/OpenPype/pull/2951) +- Documentation: New publisher develop docs [\#2896](https://github.com/pypeclub/OpenPype/pull/2896) + +**πŸ†• New features** + +- Ftrack: Add description integrator [\#3027](https://github.com/pypeclub/OpenPype/pull/3027) +- nuke: bypass baking [\#2992](https://github.com/pypeclub/OpenPype/pull/2992) +- Publishing textures for Unreal [\#2988](https://github.com/pypeclub/OpenPype/pull/2988) +- Maya to Unreal: Static and Skeletal Meshes [\#2978](https://github.com/pypeclub/OpenPype/pull/2978) +- Multiverse: Initial Support [\#2908](https://github.com/pypeclub/OpenPype/pull/2908) + +**πŸš€ Enhancements** + +- General: default workfile subset name for workfile [\#3011](https://github.com/pypeclub/OpenPype/pull/3011) +- Ftrack: Add more options for note text of integrate ftrack note [\#3025](https://github.com/pypeclub/OpenPype/pull/3025) +- Console Interpreter: Changed how console splitter size are reused on show [\#3016](https://github.com/pypeclub/OpenPype/pull/3016) +- Deadline: Use more suitable name for sequence review logic [\#3015](https://github.com/pypeclub/OpenPype/pull/3015) +- Nuke: add concurrency attr to deadline job [\#3005](https://github.com/pypeclub/OpenPype/pull/3005) +- Photoshop: create image without instance [\#3001](https://github.com/pypeclub/OpenPype/pull/3001) +- TVPaint: Render scene family [\#3000](https://github.com/pypeclub/OpenPype/pull/3000) +- Deadline: priority configurable in Maya jobs [\#2995](https://github.com/pypeclub/OpenPype/pull/2995) +- Nuke: ReviewDataMov Read RAW attribute [\#2985](https://github.com/pypeclub/OpenPype/pull/2985) +- General: `METADATA_KEYS` constant as `frozenset` for optimal immutable lookup [\#2980](https://github.com/pypeclub/OpenPype/pull/2980) +- General: Tools with host filters [\#2975](https://github.com/pypeclub/OpenPype/pull/2975) +- Hero versions: Use custom templates [\#2967](https://github.com/pypeclub/OpenPype/pull/2967) +- Slack: Added configurable maximum file size of review upload to Slack [\#2945](https://github.com/pypeclub/OpenPype/pull/2945) +- NewPublisher: Prepared implementation of optional pyblish plugin [\#2943](https://github.com/pypeclub/OpenPype/pull/2943) +- TVPaint: Extractor to convert PNG into EXR [\#2942](https://github.com/pypeclub/OpenPype/pull/2942) +- Workfiles tool: Save as published workfiles [\#2937](https://github.com/pypeclub/OpenPype/pull/2937) +- Workfiles: Open published workfiles [\#2925](https://github.com/pypeclub/OpenPype/pull/2925) +- General: Default modules loaded dynamically [\#2923](https://github.com/pypeclub/OpenPype/pull/2923) +- CI: change the version bump logic [\#2919](https://github.com/pypeclub/OpenPype/pull/2919) +- Deadline: Add headless argument [\#2916](https://github.com/pypeclub/OpenPype/pull/2916) +- Nuke: Add no-audio Tag [\#2911](https://github.com/pypeclub/OpenPype/pull/2911) +- Ftrack: Fill workfile in custom attribute [\#2906](https://github.com/pypeclub/OpenPype/pull/2906) +- Nuke: improving readability [\#2903](https://github.com/pypeclub/OpenPype/pull/2903) +- Settings UI: Add simple tooltips for settings entities [\#2901](https://github.com/pypeclub/OpenPype/pull/2901) + +**πŸ› Bug fixes** + +- General: Fix validate asset docs plug-in filename and class name [\#3029](https://github.com/pypeclub/OpenPype/pull/3029) +- Deadline: Fixed default value of use sequence for review [\#3033](https://github.com/pypeclub/OpenPype/pull/3033) +- Settings UI: Version column can be extended so version are visible [\#3032](https://github.com/pypeclub/OpenPype/pull/3032) +- General: Fix import after movements [\#3028](https://github.com/pypeclub/OpenPype/pull/3028) +- Harmony: Added creating subset name for workfile from template [\#3024](https://github.com/pypeclub/OpenPype/pull/3024) +- AfterEffects: Added creating subset name for workfile from template [\#3023](https://github.com/pypeclub/OpenPype/pull/3023) +- General: Add example addons to ignored [\#3022](https://github.com/pypeclub/OpenPype/pull/3022) +- Maya: Remove missing import [\#3017](https://github.com/pypeclub/OpenPype/pull/3017) +- Ftrack: multiple reviewable componets [\#3012](https://github.com/pypeclub/OpenPype/pull/3012) +- Tray publisher: Fixes after code movement [\#3010](https://github.com/pypeclub/OpenPype/pull/3010) +- Hosts: Remove path existence checks in 'add\_implementation\_envs' [\#3004](https://github.com/pypeclub/OpenPype/pull/3004) +- Nuke: fixing unicode type detection in effect loaders [\#3002](https://github.com/pypeclub/OpenPype/pull/3002) +- Fix - remove doubled dot in workfile created from template [\#2998](https://github.com/pypeclub/OpenPype/pull/2998) +- Nuke: removing redundant Ftrack asset when farm publishing [\#2996](https://github.com/pypeclub/OpenPype/pull/2996) +- PS: fix renaming subset incorrectly in PS [\#2991](https://github.com/pypeclub/OpenPype/pull/2991) +- Fix: Disable setuptools auto discovery [\#2990](https://github.com/pypeclub/OpenPype/pull/2990) +- AEL: fix opening existing workfile if no scene opened [\#2989](https://github.com/pypeclub/OpenPype/pull/2989) +- Maya: Don't do hardlinks on windows for look publishing [\#2986](https://github.com/pypeclub/OpenPype/pull/2986) +- Settings UI: Fix version completer on linux [\#2981](https://github.com/pypeclub/OpenPype/pull/2981) +- Photoshop: Fix creation of subset names in PS review and workfile [\#2969](https://github.com/pypeclub/OpenPype/pull/2969) +- Slack: Added default for review\_upload\_limit for Slack [\#2965](https://github.com/pypeclub/OpenPype/pull/2965) +- General: OIIO conversion for ffmeg can handle sequences [\#2958](https://github.com/pypeclub/OpenPype/pull/2958) +- Settings: Conditional dictionary avoid invalid logs [\#2956](https://github.com/pypeclub/OpenPype/pull/2956) +- General: Smaller fixes and typos [\#2950](https://github.com/pypeclub/OpenPype/pull/2950) +- LogViewer: Don't refresh on initialization [\#2949](https://github.com/pypeclub/OpenPype/pull/2949) +- nuke: python3 compatibility issue with `iteritems` [\#2948](https://github.com/pypeclub/OpenPype/pull/2948) +- General: anatomy data with correct task short key [\#2947](https://github.com/pypeclub/OpenPype/pull/2947) +- SceneInventory: Fix imports in UI [\#2944](https://github.com/pypeclub/OpenPype/pull/2944) +- Slack: add generic exception [\#2941](https://github.com/pypeclub/OpenPype/pull/2941) +- General: Python specific vendor paths on env injection [\#2939](https://github.com/pypeclub/OpenPype/pull/2939) +- General: More fail safe delete old versions [\#2936](https://github.com/pypeclub/OpenPype/pull/2936) +- Settings UI: Collapsed of collapsible wrapper works as expected [\#2934](https://github.com/pypeclub/OpenPype/pull/2934) +- Maya: Do not pass `set` to maya commands \(fixes support for older maya versions\) [\#2932](https://github.com/pypeclub/OpenPype/pull/2932) +- General: Don't print log record on OSError [\#2926](https://github.com/pypeclub/OpenPype/pull/2926) +- Hiero: Fix import of 'register\_event\_callback' [\#2924](https://github.com/pypeclub/OpenPype/pull/2924) +- Flame: centos related debugging [\#2922](https://github.com/pypeclub/OpenPype/pull/2922) +- Ftrack: Missing Ftrack id after editorial publish [\#2905](https://github.com/pypeclub/OpenPype/pull/2905) +- AfterEffects: Fix rendering for single frame in DL [\#2875](https://github.com/pypeclub/OpenPype/pull/2875) + +**πŸ”€ Refactored code** + +- General: Move plugins register and discover [\#2935](https://github.com/pypeclub/OpenPype/pull/2935) +- General: Move Attribute Definitions from pipeline [\#2931](https://github.com/pypeclub/OpenPype/pull/2931) +- General: Removed silo references and terminal splash [\#2927](https://github.com/pypeclub/OpenPype/pull/2927) +- General: Move pipeline constants to OpenPype [\#2918](https://github.com/pypeclub/OpenPype/pull/2918) +- General: Move formatting and workfile functions [\#2914](https://github.com/pypeclub/OpenPype/pull/2914) +- General: Move remaining plugins from avalon [\#2912](https://github.com/pypeclub/OpenPype/pull/2912) + +**Merged pull requests:** + +- Maya: Allow to select invalid camera contents if no cameras found [\#3030](https://github.com/pypeclub/OpenPype/pull/3030) +- Bump paramiko from 2.9.2 to 2.10.1 [\#2973](https://github.com/pypeclub/OpenPype/pull/2973) +- Bump minimist from 1.2.5 to 1.2.6 in /website [\#2954](https://github.com/pypeclub/OpenPype/pull/2954) +- Bump node-forge from 1.2.1 to 1.3.0 in /website [\#2953](https://github.com/pypeclub/OpenPype/pull/2953) +- Maya - added transparency into review creator [\#2952](https://github.com/pypeclub/OpenPype/pull/2952) ## [3.9.2](https://github.com/pypeclub/OpenPype/tree/3.9.2) (2022-04-04) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.9.2-nightly.4...3.9.2) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.9.1...3.9.2) ## [3.9.1](https://github.com/pypeclub/OpenPype/tree/3.9.1) (2022-03-18) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.9.1-nightly.3...3.9.1) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.9.0...3.9.1) + +**πŸš€ Enhancements** + +- General: Change how OPENPYPE\_DEBUG value is handled [\#2907](https://github.com/pypeclub/OpenPype/pull/2907) +- nuke: imageio adding ocio config version 1.2 [\#2897](https://github.com/pypeclub/OpenPype/pull/2897) +- Flame: support for comment with xml attribute overrides [\#2892](https://github.com/pypeclub/OpenPype/pull/2892) +- Nuke: ExtractReviewSlate can handle more codes and profiles [\#2879](https://github.com/pypeclub/OpenPype/pull/2879) +- Flame: sequence used for reference video [\#2869](https://github.com/pypeclub/OpenPype/pull/2869) + +**πŸ› Bug fixes** + +- General: Fix use of Anatomy roots [\#2904](https://github.com/pypeclub/OpenPype/pull/2904) +- Fixing gap detection in extract review [\#2902](https://github.com/pypeclub/OpenPype/pull/2902) +- Pyblish Pype - ensure current state is correct when entering new group order [\#2899](https://github.com/pypeclub/OpenPype/pull/2899) +- SceneInventory: Fix import of load function [\#2894](https://github.com/pypeclub/OpenPype/pull/2894) +- Harmony - fixed creator issue [\#2891](https://github.com/pypeclub/OpenPype/pull/2891) +- General: Remove forgotten use of avalon Creator [\#2885](https://github.com/pypeclub/OpenPype/pull/2885) +- General: Avoid circular import [\#2884](https://github.com/pypeclub/OpenPype/pull/2884) +- Fixes for attaching loaded containers \(\#2837\) [\#2874](https://github.com/pypeclub/OpenPype/pull/2874) +- Maya: Deformer node ids validation plugin [\#2826](https://github.com/pypeclub/OpenPype/pull/2826) +- Flame Babypublisher optimalization [\#2806](https://github.com/pypeclub/OpenPype/pull/2806) +- hotfix: OIIO tool path - add extension on windows [\#2618](https://github.com/pypeclub/OpenPype/pull/2618) + +**πŸ”€ Refactored code** + +- General: Reduce style usage to OpenPype repository [\#2889](https://github.com/pypeclub/OpenPype/pull/2889) +- General: Move loader logic from avalon to openpype [\#2886](https://github.com/pypeclub/OpenPype/pull/2886) ## [3.9.0](https://github.com/pypeclub/OpenPype/tree/3.9.0) (2022-03-14) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.9.0-nightly.9...3.9.0) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.8.2...3.9.0) + +**Deprecated:** + +- Houdini: Remove unused code [\#2779](https://github.com/pypeclub/OpenPype/pull/2779) +- Loader: Remove default family states for hosts from code [\#2706](https://github.com/pypeclub/OpenPype/pull/2706) +- AssetCreator: Remove the tool [\#2845](https://github.com/pypeclub/OpenPype/pull/2845) + +### πŸ“– Documentation + +- Documentation: fixed broken links [\#2799](https://github.com/pypeclub/OpenPype/pull/2799) +- Documentation: broken link fix [\#2785](https://github.com/pypeclub/OpenPype/pull/2785) +- Documentation: link fixes [\#2772](https://github.com/pypeclub/OpenPype/pull/2772) +- Update docusaurus to latest version [\#2760](https://github.com/pypeclub/OpenPype/pull/2760) +- Various testing updates [\#2726](https://github.com/pypeclub/OpenPype/pull/2726) +- documentation: add example to `repack-version` command [\#2669](https://github.com/pypeclub/OpenPype/pull/2669) +- Update docusaurus [\#2639](https://github.com/pypeclub/OpenPype/pull/2639) +- Documentation: Fixed relative links [\#2621](https://github.com/pypeclub/OpenPype/pull/2621) +- Documentation: Change Photoshop & AfterEffects plugin path [\#2878](https://github.com/pypeclub/OpenPype/pull/2878) + +**πŸ†• New features** + +- Flame: loading clips to reels [\#2622](https://github.com/pypeclub/OpenPype/pull/2622) +- General: Store settings by OpenPype version [\#2570](https://github.com/pypeclub/OpenPype/pull/2570) + +**πŸš€ Enhancements** + +- New: Validation exceptions [\#2841](https://github.com/pypeclub/OpenPype/pull/2841) +- General: Set context environments for non host applications [\#2803](https://github.com/pypeclub/OpenPype/pull/2803) +- Houdini: Remove duplicate ValidateOutputNode plug-in [\#2780](https://github.com/pypeclub/OpenPype/pull/2780) +- Tray publisher: New Tray Publisher host \(beta\) [\#2778](https://github.com/pypeclub/OpenPype/pull/2778) +- Slack: Added regex for filtering on subset names [\#2775](https://github.com/pypeclub/OpenPype/pull/2775) +- Houdini: Implement Reset Frame Range [\#2770](https://github.com/pypeclub/OpenPype/pull/2770) +- Pyblish Pype: Remove redundant new line in installed fonts printing [\#2758](https://github.com/pypeclub/OpenPype/pull/2758) +- Flame: use Shot Name on segment for asset name [\#2751](https://github.com/pypeclub/OpenPype/pull/2751) +- Flame: adding validator source clip [\#2746](https://github.com/pypeclub/OpenPype/pull/2746) +- Work Files: Preserve subversion comment of current filename by default [\#2734](https://github.com/pypeclub/OpenPype/pull/2734) +- Maya: set Deadline job/batch name to original source workfile name instead of published workfile [\#2733](https://github.com/pypeclub/OpenPype/pull/2733) +- Ftrack: Disable ftrack module by default [\#2732](https://github.com/pypeclub/OpenPype/pull/2732) +- Project Manager: Disable add task, add asset and save button when not in a project [\#2727](https://github.com/pypeclub/OpenPype/pull/2727) +- dropbox handle big file [\#2718](https://github.com/pypeclub/OpenPype/pull/2718) +- Fusion Move PR: Minor tweaks to Fusion integration [\#2716](https://github.com/pypeclub/OpenPype/pull/2716) +- RoyalRender: Minor enhancements [\#2700](https://github.com/pypeclub/OpenPype/pull/2700) +- Nuke: prerender with review knob [\#2691](https://github.com/pypeclub/OpenPype/pull/2691) +- Maya configurable unit validator [\#2680](https://github.com/pypeclub/OpenPype/pull/2680) +- General: Add settings for CleanUpFarm and disable the plugin by default [\#2679](https://github.com/pypeclub/OpenPype/pull/2679) +- Project Manager: Only allow scroll wheel edits when spinbox is active [\#2678](https://github.com/pypeclub/OpenPype/pull/2678) +- Ftrack: Sync description to assets [\#2670](https://github.com/pypeclub/OpenPype/pull/2670) +- Houdini: Moved to OpenPype [\#2658](https://github.com/pypeclub/OpenPype/pull/2658) +- Maya: Move implementation to OpenPype [\#2649](https://github.com/pypeclub/OpenPype/pull/2649) +- General: FFmpeg conversion also check attribute string length [\#2635](https://github.com/pypeclub/OpenPype/pull/2635) +- Houdini: Load Arnold .ass procedurals into Houdini [\#2606](https://github.com/pypeclub/OpenPype/pull/2606) +- Deadline: Simplify GlobalJobPreLoad logic [\#2605](https://github.com/pypeclub/OpenPype/pull/2605) +- Houdini: Implement Arnold .ass standin extraction from Houdini \(also support .ass.gz\) [\#2603](https://github.com/pypeclub/OpenPype/pull/2603) +- New Publisher: New features and preparations for new standalone publisher [\#2556](https://github.com/pypeclub/OpenPype/pull/2556) +- Fix Maya 2022 Python 3 compatibility [\#2445](https://github.com/pypeclub/OpenPype/pull/2445) +- TVPaint: Use new publisher exceptions in validators [\#2435](https://github.com/pypeclub/OpenPype/pull/2435) +- Harmony: Added new style validations for New Publisher [\#2434](https://github.com/pypeclub/OpenPype/pull/2434) +- Aftereffects: New style validations for New publisher [\#2430](https://github.com/pypeclub/OpenPype/pull/2430) +- Farm publishing: New cleanup plugin for Maya renders on farm [\#2390](https://github.com/pypeclub/OpenPype/pull/2390) +- General: Subset name filtering in ExtractReview outpus [\#2872](https://github.com/pypeclub/OpenPype/pull/2872) +- NewPublisher: Descriptions and Icons in creator dialog [\#2867](https://github.com/pypeclub/OpenPype/pull/2867) +- NewPublisher: Changing task on publishing instance [\#2863](https://github.com/pypeclub/OpenPype/pull/2863) +- TrayPublisher: Choose project widget is more clear [\#2859](https://github.com/pypeclub/OpenPype/pull/2859) +- Maya: add loaded containers to published instance [\#2837](https://github.com/pypeclub/OpenPype/pull/2837) +- Ftrack: Can sync fps as string [\#2836](https://github.com/pypeclub/OpenPype/pull/2836) +- General: Custom function for find executable [\#2822](https://github.com/pypeclub/OpenPype/pull/2822) +- General: Color dialog UI fixes [\#2817](https://github.com/pypeclub/OpenPype/pull/2817) +- global: letter box calculated on output as last process [\#2812](https://github.com/pypeclub/OpenPype/pull/2812) +- Nuke: adding Reformat to baking mov plugin [\#2811](https://github.com/pypeclub/OpenPype/pull/2811) +- Manager: Update all to latest button [\#2805](https://github.com/pypeclub/OpenPype/pull/2805) +- Houdini: Move Houdini Save Current File to beginning of ExtractorOrder [\#2747](https://github.com/pypeclub/OpenPype/pull/2747) +- Global: adding studio name/code to anatomy template formatting data [\#2630](https://github.com/pypeclub/OpenPype/pull/2630) + +**πŸ› Bug fixes** + +- Settings UI: Search case sensitivity [\#2810](https://github.com/pypeclub/OpenPype/pull/2810) +- resolve: fixing fusion module loading [\#2802](https://github.com/pypeclub/OpenPype/pull/2802) +- Ftrack: Unset task ids from asset versions before tasks are removed [\#2800](https://github.com/pypeclub/OpenPype/pull/2800) +- Slack: fail gracefully if slack exception [\#2798](https://github.com/pypeclub/OpenPype/pull/2798) +- Flame: Fix version string in default settings [\#2783](https://github.com/pypeclub/OpenPype/pull/2783) +- After Effects: Fix typo in name `afftereffects` -\> `aftereffects` [\#2768](https://github.com/pypeclub/OpenPype/pull/2768) +- Houdini: Fix open last workfile [\#2767](https://github.com/pypeclub/OpenPype/pull/2767) +- Avoid renaming udim indexes [\#2765](https://github.com/pypeclub/OpenPype/pull/2765) +- Maya: Fix `unique_namespace` when in an namespace that is empty [\#2759](https://github.com/pypeclub/OpenPype/pull/2759) +- Loader UI: Fix right click in representation widget [\#2757](https://github.com/pypeclub/OpenPype/pull/2757) +- Harmony: Rendering in Deadline didn't work in other machines than submitter [\#2754](https://github.com/pypeclub/OpenPype/pull/2754) +- Aftereffects 2022 and Deadline [\#2748](https://github.com/pypeclub/OpenPype/pull/2748) +- Flame: bunch of bugs [\#2745](https://github.com/pypeclub/OpenPype/pull/2745) +- Maya: Save current scene on workfile publish [\#2744](https://github.com/pypeclub/OpenPype/pull/2744) +- Version Up: Preserve parts of filename after version number \(like subversion\) on version\_up [\#2741](https://github.com/pypeclub/OpenPype/pull/2741) +- Loader UI: Multiple asset selection and underline colors fixed [\#2731](https://github.com/pypeclub/OpenPype/pull/2731) +- General: Fix loading of unused chars in xml format [\#2729](https://github.com/pypeclub/OpenPype/pull/2729) +- TVPaint: Set objectName with members [\#2725](https://github.com/pypeclub/OpenPype/pull/2725) +- General: Don't use 'objectName' from loaded references [\#2715](https://github.com/pypeclub/OpenPype/pull/2715) +- Settings: Studio Project anatomy is queried using right keys [\#2711](https://github.com/pypeclub/OpenPype/pull/2711) +- Local Settings: Additional applications don't break UI [\#2710](https://github.com/pypeclub/OpenPype/pull/2710) +- Maya: Remove some unused code [\#2709](https://github.com/pypeclub/OpenPype/pull/2709) +- Houdini: Fix refactor of Houdini host move for CreateArnoldAss [\#2704](https://github.com/pypeclub/OpenPype/pull/2704) +- LookAssigner: Fix imports after moving code to OpenPype repository [\#2701](https://github.com/pypeclub/OpenPype/pull/2701) +- Multiple hosts: unify menu style across hosts [\#2693](https://github.com/pypeclub/OpenPype/pull/2693) +- Maya Redshift fixes [\#2692](https://github.com/pypeclub/OpenPype/pull/2692) +- Maya: fix fps validation popup [\#2685](https://github.com/pypeclub/OpenPype/pull/2685) +- Houdini Explicitly collect correct frame name even in case of single frame render when `frameStart` is provided [\#2676](https://github.com/pypeclub/OpenPype/pull/2676) +- hiero: fix effect collector name and order [\#2673](https://github.com/pypeclub/OpenPype/pull/2673) +- Maya: Fix menu callbacks [\#2671](https://github.com/pypeclub/OpenPype/pull/2671) +- hiero: removing obsolete unsupported plugin [\#2667](https://github.com/pypeclub/OpenPype/pull/2667) +- Launcher: Fix access to 'data' attribute on actions [\#2659](https://github.com/pypeclub/OpenPype/pull/2659) +- Maya `vrscene` loader fixes [\#2633](https://github.com/pypeclub/OpenPype/pull/2633) +- Houdini: fix usd family in loader and integrators [\#2631](https://github.com/pypeclub/OpenPype/pull/2631) +- Maya: Add only reference node to look family container like with other families [\#2508](https://github.com/pypeclub/OpenPype/pull/2508) +- General: Missing time function [\#2877](https://github.com/pypeclub/OpenPype/pull/2877) +- Deadline: Fix plugin name for tile assemble [\#2868](https://github.com/pypeclub/OpenPype/pull/2868) +- Nuke: gizmo precollect fix [\#2866](https://github.com/pypeclub/OpenPype/pull/2866) +- General: Fix hardlink for windows [\#2864](https://github.com/pypeclub/OpenPype/pull/2864) +- General: ffmpeg was crashing on slate merge [\#2860](https://github.com/pypeclub/OpenPype/pull/2860) +- WebPublisher: Video file was published with one too many frame [\#2858](https://github.com/pypeclub/OpenPype/pull/2858) +- New Publisher: Error dialog got right styles [\#2857](https://github.com/pypeclub/OpenPype/pull/2857) +- General: Fix getattr clalback on dynamic modules [\#2855](https://github.com/pypeclub/OpenPype/pull/2855) +- Nuke: slate resolution to input video resolution [\#2853](https://github.com/pypeclub/OpenPype/pull/2853) +- WebPublisher: Fix username stored in DB [\#2852](https://github.com/pypeclub/OpenPype/pull/2852) +- WebPublisher: Fix wrong number of frames for video file [\#2851](https://github.com/pypeclub/OpenPype/pull/2851) +- Nuke: Fix family test in validate\_write\_legacy to work with stillImage [\#2847](https://github.com/pypeclub/OpenPype/pull/2847) +- Nuke: fix multiple baking profile farm publishing [\#2842](https://github.com/pypeclub/OpenPype/pull/2842) +- Blender: Fixed parameters for FBX export of the camera [\#2840](https://github.com/pypeclub/OpenPype/pull/2840) +- Maya: Stop creation of reviews for Cryptomattes [\#2832](https://github.com/pypeclub/OpenPype/pull/2832) +- Deadline: Remove recreated event [\#2828](https://github.com/pypeclub/OpenPype/pull/2828) +- Deadline: Added missing events folder [\#2827](https://github.com/pypeclub/OpenPype/pull/2827) +- Settings: Missing document with OP versions may break start of OpenPype [\#2825](https://github.com/pypeclub/OpenPype/pull/2825) +- Deadline: more detailed temp file name for environment json [\#2824](https://github.com/pypeclub/OpenPype/pull/2824) +- General: Host name was formed from obsolete code [\#2821](https://github.com/pypeclub/OpenPype/pull/2821) +- Settings UI: Fix "Apply from" action [\#2820](https://github.com/pypeclub/OpenPype/pull/2820) +- Ftrack: Job killer with missing user [\#2819](https://github.com/pypeclub/OpenPype/pull/2819) +- Nuke: Use AVALON\_APP to get value for "app" key [\#2818](https://github.com/pypeclub/OpenPype/pull/2818) +- StandalonePublisher: use dynamic groups in subset names [\#2816](https://github.com/pypeclub/OpenPype/pull/2816) + +**πŸ”€ Refactored code** + +- Ftrack: Moved module one hierarchy level higher [\#2792](https://github.com/pypeclub/OpenPype/pull/2792) +- SyncServer: Moved module one hierarchy level higher [\#2791](https://github.com/pypeclub/OpenPype/pull/2791) +- Royal render: Move module one hierarchy level higher [\#2790](https://github.com/pypeclub/OpenPype/pull/2790) +- Deadline: Move module one hierarchy level higher [\#2789](https://github.com/pypeclub/OpenPype/pull/2789) +- Refactor: move webserver tool to openpype [\#2876](https://github.com/pypeclub/OpenPype/pull/2876) +- General: Move create logic from avalon to OpenPype [\#2854](https://github.com/pypeclub/OpenPype/pull/2854) +- General: Add vendors from avalon [\#2848](https://github.com/pypeclub/OpenPype/pull/2848) +- General: Basic event system [\#2846](https://github.com/pypeclub/OpenPype/pull/2846) +- General: Move change context functions [\#2839](https://github.com/pypeclub/OpenPype/pull/2839) +- Tools: Don't use avalon tools code [\#2829](https://github.com/pypeclub/OpenPype/pull/2829) +- Move Unreal Implementation to OpenPype [\#2823](https://github.com/pypeclub/OpenPype/pull/2823) +- General: Extract template formatting from anatomy [\#2766](https://github.com/pypeclub/OpenPype/pull/2766) + +**Merged pull requests:** + +- Fusion: Moved implementation into OpenPype [\#2713](https://github.com/pypeclub/OpenPype/pull/2713) +- TVPaint: Plugin build without dependencies [\#2705](https://github.com/pypeclub/OpenPype/pull/2705) +- Webpublisher: Photoshop create a beauty png [\#2689](https://github.com/pypeclub/OpenPype/pull/2689) +- Ftrack: Hierarchical attributes are queried properly [\#2682](https://github.com/pypeclub/OpenPype/pull/2682) +- Maya: Add Validate Frame Range settings [\#2661](https://github.com/pypeclub/OpenPype/pull/2661) +- Harmony: move to Openpype [\#2657](https://github.com/pypeclub/OpenPype/pull/2657) +- Maya: cleanup duplicate rendersetup code [\#2642](https://github.com/pypeclub/OpenPype/pull/2642) +- Deadline: Be able to pass Mongo url to job [\#2616](https://github.com/pypeclub/OpenPype/pull/2616) ## [3.8.2](https://github.com/pypeclub/OpenPype/tree/3.8.2) (2022-02-07) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.8.2-nightly.3...3.8.2) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.8.1...3.8.2) + +### πŸ“– Documentation + +- Cosmetics: Fix common typos in openpype/website [\#2617](https://github.com/pypeclub/OpenPype/pull/2617) + +**πŸš€ Enhancements** + +- TVPaint: Image loaders also work on review family [\#2638](https://github.com/pypeclub/OpenPype/pull/2638) +- General: Project backup tools [\#2629](https://github.com/pypeclub/OpenPype/pull/2629) +- nuke: adding clear button to write nodes [\#2627](https://github.com/pypeclub/OpenPype/pull/2627) +- Ftrack: Family to Asset type mapping is in settings [\#2602](https://github.com/pypeclub/OpenPype/pull/2602) +- Nuke: load color space from representation data [\#2576](https://github.com/pypeclub/OpenPype/pull/2576) + +**πŸ› Bug fixes** + +- Fix pulling of cx\_freeze 6.10 [\#2628](https://github.com/pypeclub/OpenPype/pull/2628) +- Global: fix broken otio review extractor [\#2590](https://github.com/pypeclub/OpenPype/pull/2590) + +**Merged pull requests:** + +- WebPublisher: fix instance duplicates [\#2641](https://github.com/pypeclub/OpenPype/pull/2641) +- Fix - safer pulling of task name for webpublishing from PS [\#2613](https://github.com/pypeclub/OpenPype/pull/2613) ## [3.8.1](https://github.com/pypeclub/OpenPype/tree/3.8.1) (2022-02-01) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.8.1-nightly.3...3.8.1) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.8.0...3.8.1) + +**πŸš€ Enhancements** + +- Webpublisher: Thumbnail extractor [\#2600](https://github.com/pypeclub/OpenPype/pull/2600) +- Loader: Allow to toggle default family filters between "include" or "exclude" filtering [\#2541](https://github.com/pypeclub/OpenPype/pull/2541) +- Launcher: Added context menu to to skip opening last workfile [\#2536](https://github.com/pypeclub/OpenPype/pull/2536) +- Unreal: JSON Layout Loading support [\#2066](https://github.com/pypeclub/OpenPype/pull/2066) + +**πŸ› Bug fixes** + +- Release/3.8.0 [\#2619](https://github.com/pypeclub/OpenPype/pull/2619) +- Settings: Enum does not store empty string if has single item to select [\#2615](https://github.com/pypeclub/OpenPype/pull/2615) +- switch distutils to sysconfig for `get_platform()` [\#2594](https://github.com/pypeclub/OpenPype/pull/2594) +- Fix poetry index and speedcopy update [\#2589](https://github.com/pypeclub/OpenPype/pull/2589) +- Webpublisher: Fix - subset names from processed .psd used wrong value for task [\#2586](https://github.com/pypeclub/OpenPype/pull/2586) +- `vrscene` creator Deadline webservice URL handling [\#2580](https://github.com/pypeclub/OpenPype/pull/2580) +- global: track name was failing if duplicated root word in name [\#2568](https://github.com/pypeclub/OpenPype/pull/2568) +- Validate Maya Rig produces no cycle errors [\#2484](https://github.com/pypeclub/OpenPype/pull/2484) + +**Merged pull requests:** + +- Bump pillow from 8.4.0 to 9.0.0 [\#2595](https://github.com/pypeclub/OpenPype/pull/2595) +- Webpublisher: Skip version collect [\#2591](https://github.com/pypeclub/OpenPype/pull/2591) +- build\(deps\): bump pillow from 8.4.0 to 9.0.0 [\#2523](https://github.com/pypeclub/OpenPype/pull/2523) ## [3.8.0](https://github.com/pypeclub/OpenPype/tree/3.8.0) (2022-01-24) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.8.0-nightly.7...3.8.0) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.7.0...3.8.0) + +### πŸ“– Documentation + +- Variable in docs renamed to proper name [\#2546](https://github.com/pypeclub/OpenPype/pull/2546) + +**πŸ†• New features** + +- Flame: extracting segments with trans-coding [\#2547](https://github.com/pypeclub/OpenPype/pull/2547) +- Maya : V-Ray Proxy - load all ABC files via proxy [\#2544](https://github.com/pypeclub/OpenPype/pull/2544) +- Maya to Unreal: Extended static mesh workflow [\#2537](https://github.com/pypeclub/OpenPype/pull/2537) +- Flame: collecting publishable instances [\#2519](https://github.com/pypeclub/OpenPype/pull/2519) +- Flame: create publishable clips [\#2495](https://github.com/pypeclub/OpenPype/pull/2495) +- Flame: OpenTimelineIO Export Modul [\#2398](https://github.com/pypeclub/OpenPype/pull/2398) + +**πŸš€ Enhancements** + +- Webpublisher: Moved error at the beginning of the log [\#2559](https://github.com/pypeclub/OpenPype/pull/2559) +- Ftrack: Use ApplicationManager to get DJV path [\#2558](https://github.com/pypeclub/OpenPype/pull/2558) +- Webpublisher: Added endpoint to reprocess batch through UI [\#2555](https://github.com/pypeclub/OpenPype/pull/2555) +- Settings: PathInput strip passed string [\#2550](https://github.com/pypeclub/OpenPype/pull/2550) +- Global: Exctract Review anatomy fill data with output name [\#2548](https://github.com/pypeclub/OpenPype/pull/2548) +- Cosmetics: Clean up some cosmetics / typos [\#2542](https://github.com/pypeclub/OpenPype/pull/2542) +- General: Validate if current process OpenPype version is requested version [\#2529](https://github.com/pypeclub/OpenPype/pull/2529) +- General: Be able to use anatomy data in ffmpeg output arguments [\#2525](https://github.com/pypeclub/OpenPype/pull/2525) +- Expose toggle publish plug-in settings for Maya Look Shading Engine Naming [\#2521](https://github.com/pypeclub/OpenPype/pull/2521) +- Photoshop: Move implementation to OpenPype [\#2510](https://github.com/pypeclub/OpenPype/pull/2510) +- TimersManager: Move module one hierarchy higher [\#2501](https://github.com/pypeclub/OpenPype/pull/2501) +- Slack: notifications are sent with Openpype logo and bot name [\#2499](https://github.com/pypeclub/OpenPype/pull/2499) +- Slack: Add review to notification message [\#2498](https://github.com/pypeclub/OpenPype/pull/2498) +- Ftrack: Event handlers settings [\#2496](https://github.com/pypeclub/OpenPype/pull/2496) +- Tools: Fix style and modality of errors in loader and creator [\#2489](https://github.com/pypeclub/OpenPype/pull/2489) +- Maya: Collect 'fps' animation data only for "review" instances [\#2486](https://github.com/pypeclub/OpenPype/pull/2486) +- Project Manager: Remove project button cleanup [\#2482](https://github.com/pypeclub/OpenPype/pull/2482) +- Tools: Be able to change models of tasks and assets widgets [\#2475](https://github.com/pypeclub/OpenPype/pull/2475) +- Publish pype: Reduce publish process defering [\#2464](https://github.com/pypeclub/OpenPype/pull/2464) +- Maya: Improve speed of Collect History logic [\#2460](https://github.com/pypeclub/OpenPype/pull/2460) +- Maya: Validate Rig Controllers - fix Error: in script editor [\#2459](https://github.com/pypeclub/OpenPype/pull/2459) +- Maya: Validate NGONs simplify and speed-up [\#2458](https://github.com/pypeclub/OpenPype/pull/2458) +- Maya: Optimize Validate Locked Normals speed for dense polymeshes [\#2457](https://github.com/pypeclub/OpenPype/pull/2457) +- Maya: Refactor missing \_get\_reference\_node method [\#2455](https://github.com/pypeclub/OpenPype/pull/2455) +- Houdini: Remove broken unique name counter [\#2450](https://github.com/pypeclub/OpenPype/pull/2450) +- Maya: Improve lib.polyConstraint performance when Select tool is not the active tool context [\#2447](https://github.com/pypeclub/OpenPype/pull/2447) +- General: Validate third party before build [\#2425](https://github.com/pypeclub/OpenPype/pull/2425) +- Maya : add option to not group reference in ReferenceLoader [\#2383](https://github.com/pypeclub/OpenPype/pull/2383) + +**πŸ› Bug fixes** + +- AfterEffects: Fix - removed obsolete import [\#2577](https://github.com/pypeclub/OpenPype/pull/2577) +- General: OpenPype version updates [\#2575](https://github.com/pypeclub/OpenPype/pull/2575) +- Ftrack: Delete action revision [\#2563](https://github.com/pypeclub/OpenPype/pull/2563) +- Webpublisher: ftrack shows incorrect user names [\#2560](https://github.com/pypeclub/OpenPype/pull/2560) +- General: Do not validate version if build does not support it [\#2557](https://github.com/pypeclub/OpenPype/pull/2557) +- Webpublisher: Fixed progress reporting [\#2553](https://github.com/pypeclub/OpenPype/pull/2553) +- Fix Maya AssProxyLoader version switch [\#2551](https://github.com/pypeclub/OpenPype/pull/2551) +- General: Fix install thread in igniter [\#2549](https://github.com/pypeclub/OpenPype/pull/2549) +- Houdini: vdbcache family preserve frame numbers on publish integration + enable validate version for Houdini [\#2535](https://github.com/pypeclub/OpenPype/pull/2535) +- Maya: Fix Load VDB to V-Ray [\#2533](https://github.com/pypeclub/OpenPype/pull/2533) +- Maya: ReferenceLoader fix not unique group name error for attach to root [\#2532](https://github.com/pypeclub/OpenPype/pull/2532) +- Maya: namespaced context go back to original namespace when started from inside a namespace [\#2531](https://github.com/pypeclub/OpenPype/pull/2531) +- Fix create zip tool - path argument [\#2522](https://github.com/pypeclub/OpenPype/pull/2522) +- Maya: Fix Extract Look with space in names [\#2518](https://github.com/pypeclub/OpenPype/pull/2518) +- Fix published frame content for sequence starting with 0 [\#2513](https://github.com/pypeclub/OpenPype/pull/2513) +- Maya: reset empty string attributes correctly to "" instead of "None" [\#2506](https://github.com/pypeclub/OpenPype/pull/2506) +- Improve FusionPreLaunch hook errors [\#2505](https://github.com/pypeclub/OpenPype/pull/2505) +- General: Settings work if OpenPypeVersion is available [\#2494](https://github.com/pypeclub/OpenPype/pull/2494) +- General: PYTHONPATH may break OpenPype dependencies [\#2493](https://github.com/pypeclub/OpenPype/pull/2493) +- General: Modules import function output fix [\#2492](https://github.com/pypeclub/OpenPype/pull/2492) +- AE: fix hiding of alert window below Publish [\#2491](https://github.com/pypeclub/OpenPype/pull/2491) +- Workfiles tool: Files widget show files on first show [\#2488](https://github.com/pypeclub/OpenPype/pull/2488) +- General: Custom template paths filter fix [\#2483](https://github.com/pypeclub/OpenPype/pull/2483) +- Loader: Remove always on top flag in tray [\#2480](https://github.com/pypeclub/OpenPype/pull/2480) +- General: Anatomy does not return root envs as unicode [\#2465](https://github.com/pypeclub/OpenPype/pull/2465) +- Maya: Validate Shape Zero do not keep fixed geometry vertices selected/active after repair [\#2456](https://github.com/pypeclub/OpenPype/pull/2456) + +**Merged pull requests:** + +- AfterEffects: Move implementation to OpenPype [\#2543](https://github.com/pypeclub/OpenPype/pull/2543) +- Maya: Remove Maya Look Assigner check on startup [\#2540](https://github.com/pypeclub/OpenPype/pull/2540) +- build\(deps\): bump shelljs from 0.8.4 to 0.8.5 in /website [\#2538](https://github.com/pypeclub/OpenPype/pull/2538) +- build\(deps\): bump follow-redirects from 1.14.4 to 1.14.7 in /website [\#2534](https://github.com/pypeclub/OpenPype/pull/2534) +- Nuke: Merge avalon's implementation into OpenPype [\#2514](https://github.com/pypeclub/OpenPype/pull/2514) +- Maya: Vray fix proxies look assignment [\#2392](https://github.com/pypeclub/OpenPype/pull/2392) +- Bump algoliasearch-helper from 3.4.4 to 3.6.2 in /website [\#2297](https://github.com/pypeclub/OpenPype/pull/2297) ## [3.7.0](https://github.com/pypeclub/OpenPype/tree/3.7.0) (2022-01-04) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.7.0-nightly.14...3.7.0) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.6.4...3.7.0) + +**Deprecated:** + +- General: Default modules hierarchy n2 [\#2368](https://github.com/pypeclub/OpenPype/pull/2368) + +### πŸ“– Documentation + +- docs\[website\]: Add Ellipse Studio \(logo\) as an OpenPype contributor [\#2324](https://github.com/pypeclub/OpenPype/pull/2324) + +**πŸ†• New features** + +- Settings UI use OpenPype styles [\#2296](https://github.com/pypeclub/OpenPype/pull/2296) +- Store typed version dependencies for workfiles [\#2192](https://github.com/pypeclub/OpenPype/pull/2192) +- OpenPypeV3: add key task type, task shortname and user to path templating construction [\#2157](https://github.com/pypeclub/OpenPype/pull/2157) +- Nuke: Alembic model workflow [\#2140](https://github.com/pypeclub/OpenPype/pull/2140) +- TVPaint: Load workfile from published. [\#1980](https://github.com/pypeclub/OpenPype/pull/1980) + +**πŸš€ Enhancements** + +- General: Workdir extra folders [\#2462](https://github.com/pypeclub/OpenPype/pull/2462) +- Photoshop: New style validations for New publisher [\#2429](https://github.com/pypeclub/OpenPype/pull/2429) +- General: Environment variables groups [\#2424](https://github.com/pypeclub/OpenPype/pull/2424) +- Unreal: Dynamic menu created in Python [\#2422](https://github.com/pypeclub/OpenPype/pull/2422) +- Settings UI: Hyperlinks to settings [\#2420](https://github.com/pypeclub/OpenPype/pull/2420) +- Modules: JobQueue module moved one hierarchy level higher [\#2419](https://github.com/pypeclub/OpenPype/pull/2419) +- TimersManager: Start timer post launch hook [\#2418](https://github.com/pypeclub/OpenPype/pull/2418) +- General: Run applications as separate processes under linux [\#2408](https://github.com/pypeclub/OpenPype/pull/2408) +- Ftrack: Check existence of object type on recreation [\#2404](https://github.com/pypeclub/OpenPype/pull/2404) +- Enhancement: Global cleanup plugin that explicitly remove paths from context [\#2402](https://github.com/pypeclub/OpenPype/pull/2402) +- General: MongoDB ability to specify replica set groups [\#2401](https://github.com/pypeclub/OpenPype/pull/2401) +- Flame: moving `utility_scripts` to api folder also with `scripts` [\#2385](https://github.com/pypeclub/OpenPype/pull/2385) +- Centos 7 dependency compatibility [\#2384](https://github.com/pypeclub/OpenPype/pull/2384) +- Enhancement: Settings: Use project settings values from another project [\#2382](https://github.com/pypeclub/OpenPype/pull/2382) +- Blender 3: Support auto install for new blender version [\#2377](https://github.com/pypeclub/OpenPype/pull/2377) +- Maya add render image path to settings [\#2375](https://github.com/pypeclub/OpenPype/pull/2375) +- Settings: Webpublisher in hosts enum [\#2367](https://github.com/pypeclub/OpenPype/pull/2367) +- Hiero: python3 compatibility [\#2365](https://github.com/pypeclub/OpenPype/pull/2365) +- Burnins: Be able recognize mxf OPAtom format [\#2361](https://github.com/pypeclub/OpenPype/pull/2361) +- Maya: Add is\_static\_image\_plane and is\_in\_all\_views option in imagePlaneLoader [\#2356](https://github.com/pypeclub/OpenPype/pull/2356) +- Local settings: Copyable studio paths [\#2349](https://github.com/pypeclub/OpenPype/pull/2349) +- Assets Widget: Clear model on project change [\#2345](https://github.com/pypeclub/OpenPype/pull/2345) +- General: OpenPype default modules hierarchy [\#2338](https://github.com/pypeclub/OpenPype/pull/2338) +- TVPaint: Move implementation to OpenPype [\#2336](https://github.com/pypeclub/OpenPype/pull/2336) +- General: FFprobe error exception contain original error message [\#2328](https://github.com/pypeclub/OpenPype/pull/2328) +- Resolve: Add experimental button to menu [\#2325](https://github.com/pypeclub/OpenPype/pull/2325) +- Hiero: Add experimental tools action [\#2323](https://github.com/pypeclub/OpenPype/pull/2323) +- Input links: Cleanup and unification of differences [\#2322](https://github.com/pypeclub/OpenPype/pull/2322) +- General: Don't validate vendor bin with executing them [\#2317](https://github.com/pypeclub/OpenPype/pull/2317) +- General: Multilayer EXRs support [\#2315](https://github.com/pypeclub/OpenPype/pull/2315) +- General: Run process log stderr as info log level [\#2309](https://github.com/pypeclub/OpenPype/pull/2309) +- General: Reduce vendor imports [\#2305](https://github.com/pypeclub/OpenPype/pull/2305) +- Tools: Cleanup of unused classes [\#2304](https://github.com/pypeclub/OpenPype/pull/2304) +- Project Manager: Added ability to delete project [\#2298](https://github.com/pypeclub/OpenPype/pull/2298) +- Ftrack: Synchronize input links [\#2287](https://github.com/pypeclub/OpenPype/pull/2287) +- StandalonePublisher: Remove unused plugin ExtractHarmonyZip [\#2277](https://github.com/pypeclub/OpenPype/pull/2277) +- Ftrack: Support multiple reviews [\#2271](https://github.com/pypeclub/OpenPype/pull/2271) +- Ftrack: Remove unused clean component plugin [\#2269](https://github.com/pypeclub/OpenPype/pull/2269) +- Royal Render: Support for rr channels in separate dirs [\#2268](https://github.com/pypeclub/OpenPype/pull/2268) +- Houdini: Add experimental tools action [\#2267](https://github.com/pypeclub/OpenPype/pull/2267) +- Nuke: extract baked review videos presets [\#2248](https://github.com/pypeclub/OpenPype/pull/2248) +- TVPaint: Workers rendering [\#2209](https://github.com/pypeclub/OpenPype/pull/2209) +- OpenPypeV3: Add key parent asset to path templating construction [\#2186](https://github.com/pypeclub/OpenPype/pull/2186) + +**πŸ› Bug fixes** + +- TVPaint: Create render layer dialog is in front [\#2471](https://github.com/pypeclub/OpenPype/pull/2471) +- Short Pyblish plugin path [\#2428](https://github.com/pypeclub/OpenPype/pull/2428) +- PS: Introduced settings for invalid characters to use in ValidateNaming plugin [\#2417](https://github.com/pypeclub/OpenPype/pull/2417) +- Settings UI: Breadcrumbs path does not create new entities [\#2416](https://github.com/pypeclub/OpenPype/pull/2416) +- AfterEffects: Variant 2022 is in defaults but missing in schemas [\#2412](https://github.com/pypeclub/OpenPype/pull/2412) +- Nuke: baking representations was not additive [\#2406](https://github.com/pypeclub/OpenPype/pull/2406) +- General: Fix access to environments from default settings [\#2403](https://github.com/pypeclub/OpenPype/pull/2403) +- Fix: Placeholder Input color set fix [\#2399](https://github.com/pypeclub/OpenPype/pull/2399) +- Settings: Fix state change of wrapper label [\#2396](https://github.com/pypeclub/OpenPype/pull/2396) +- Flame: fix ftrack publisher [\#2381](https://github.com/pypeclub/OpenPype/pull/2381) +- hiero: solve custom ocio path [\#2379](https://github.com/pypeclub/OpenPype/pull/2379) +- hiero: fix workio and flatten [\#2378](https://github.com/pypeclub/OpenPype/pull/2378) +- Nuke: fixing menu re-drawing during context change [\#2374](https://github.com/pypeclub/OpenPype/pull/2374) +- Webpublisher: Fix assignment of families of TVpaint instances [\#2373](https://github.com/pypeclub/OpenPype/pull/2373) +- Nuke: fixing node name based on switched asset name [\#2369](https://github.com/pypeclub/OpenPype/pull/2369) +- JobQueue: Fix loading of settings [\#2362](https://github.com/pypeclub/OpenPype/pull/2362) +- Tools: Placeholder color [\#2359](https://github.com/pypeclub/OpenPype/pull/2359) +- Launcher: Minimize button on MacOs [\#2355](https://github.com/pypeclub/OpenPype/pull/2355) +- StandalonePublisher: Fix import of constant [\#2354](https://github.com/pypeclub/OpenPype/pull/2354) +- Houdini: Fix HDA creation [\#2350](https://github.com/pypeclub/OpenPype/pull/2350) +- Adobe products show issue [\#2347](https://github.com/pypeclub/OpenPype/pull/2347) +- Maya Look Assigner: Fix Python 3 compatibility [\#2343](https://github.com/pypeclub/OpenPype/pull/2343) +- Remove wrongly used host for hook [\#2342](https://github.com/pypeclub/OpenPype/pull/2342) +- Tools: Use Qt context on tools show [\#2340](https://github.com/pypeclub/OpenPype/pull/2340) +- Flame: Fix default argument value in custom dictionary [\#2339](https://github.com/pypeclub/OpenPype/pull/2339) +- Timers Manager: Disable auto stop timer on linux platform [\#2334](https://github.com/pypeclub/OpenPype/pull/2334) +- nuke: bake preset single input exception [\#2331](https://github.com/pypeclub/OpenPype/pull/2331) +- Hiero: fixing multiple templates at a hierarchy parent [\#2330](https://github.com/pypeclub/OpenPype/pull/2330) +- Fix - provider icons are pulled from a folder [\#2326](https://github.com/pypeclub/OpenPype/pull/2326) +- InputLinks: Typo in "inputLinks" key [\#2314](https://github.com/pypeclub/OpenPype/pull/2314) +- Deadline timeout and logging [\#2312](https://github.com/pypeclub/OpenPype/pull/2312) +- nuke: do not multiply representation on class method [\#2311](https://github.com/pypeclub/OpenPype/pull/2311) +- Workfiles tool: Fix task formatting [\#2306](https://github.com/pypeclub/OpenPype/pull/2306) +- Delivery: Fix delivery paths created on windows [\#2302](https://github.com/pypeclub/OpenPype/pull/2302) +- Maya: Deadline - fix limit groups [\#2295](https://github.com/pypeclub/OpenPype/pull/2295) +- Royal Render: Fix plugin order and OpenPype auto-detection [\#2291](https://github.com/pypeclub/OpenPype/pull/2291) +- New Publisher: Fix mapping of indexes [\#2285](https://github.com/pypeclub/OpenPype/pull/2285) +- Alternate site for site sync doesnt work for sequences [\#2284](https://github.com/pypeclub/OpenPype/pull/2284) +- FFmpeg: Execute ffprobe using list of arguments instead of string command [\#2281](https://github.com/pypeclub/OpenPype/pull/2281) +- Nuke: Anatomy fill data use task as dictionary [\#2278](https://github.com/pypeclub/OpenPype/pull/2278) +- Bug: fix variable name \_asset\_id in workfiles application [\#2274](https://github.com/pypeclub/OpenPype/pull/2274) +- Version handling fixes [\#2272](https://github.com/pypeclub/OpenPype/pull/2272) + +**Merged pull requests:** + +- Maya: Replaced PATH usage with vendored oiio path for maketx utility [\#2405](https://github.com/pypeclub/OpenPype/pull/2405) +- \[Fix\]\[MAYA\] Handle message type attribute within CollectLook [\#2394](https://github.com/pypeclub/OpenPype/pull/2394) +- Add validator to check correct version of extension for PS and AE [\#2387](https://github.com/pypeclub/OpenPype/pull/2387) +- Maya: configurable model top level validation [\#2321](https://github.com/pypeclub/OpenPype/pull/2321) +- Create test publish class for After Effects [\#2270](https://github.com/pypeclub/OpenPype/pull/2270) ## [3.6.4](https://github.com/pypeclub/OpenPype/tree/3.6.4) (2021-11-23) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.7.0-nightly.1...3.6.4) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.6.3...3.6.4) + +**πŸ› Bug fixes** + +- Nuke: inventory update removes all loaded read nodes [\#2294](https://github.com/pypeclub/OpenPype/pull/2294) ## [3.6.3](https://github.com/pypeclub/OpenPype/tree/3.6.3) (2021-11-19) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.6.3-nightly.1...3.6.3) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.6.2...3.6.3) + +**πŸ› Bug fixes** + +- Deadline: Fix publish targets [\#2280](https://github.com/pypeclub/OpenPype/pull/2280) ## [3.6.2](https://github.com/pypeclub/OpenPype/tree/3.6.2) (2021-11-18) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.6.2-nightly.2...3.6.2) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.6.1...3.6.2) + +**πŸš€ Enhancements** + +- Tools: Assets widget [\#2265](https://github.com/pypeclub/OpenPype/pull/2265) +- SceneInventory: Choose loader in asset switcher [\#2262](https://github.com/pypeclub/OpenPype/pull/2262) +- Style: New fonts in OpenPype style [\#2256](https://github.com/pypeclub/OpenPype/pull/2256) +- Tools: SceneInventory in OpenPype [\#2255](https://github.com/pypeclub/OpenPype/pull/2255) +- Tools: Tasks widget [\#2251](https://github.com/pypeclub/OpenPype/pull/2251) +- Tools: Creator in OpenPype [\#2244](https://github.com/pypeclub/OpenPype/pull/2244) +- Added endpoint for configured extensions [\#2221](https://github.com/pypeclub/OpenPype/pull/2221) + +**πŸ› Bug fixes** + +- Tools: Parenting of tools in Nuke and Hiero [\#2266](https://github.com/pypeclub/OpenPype/pull/2266) +- limiting validator to specific editorial hosts [\#2264](https://github.com/pypeclub/OpenPype/pull/2264) +- Tools: Select Context dialog attribute fix [\#2261](https://github.com/pypeclub/OpenPype/pull/2261) +- Maya: Render publishing fails on linux [\#2260](https://github.com/pypeclub/OpenPype/pull/2260) +- LookAssigner: Fix tool reopen [\#2259](https://github.com/pypeclub/OpenPype/pull/2259) +- Standalone: editorial not publishing thumbnails on all subsets [\#2258](https://github.com/pypeclub/OpenPype/pull/2258) +- Burnins: Support mxf metadata [\#2247](https://github.com/pypeclub/OpenPype/pull/2247) +- Maya: Support for configurable AOV separator characters [\#2197](https://github.com/pypeclub/OpenPype/pull/2197) +- Maya: texture colorspace modes in looks [\#2195](https://github.com/pypeclub/OpenPype/pull/2195) ## [3.6.1](https://github.com/pypeclub/OpenPype/tree/3.6.1) (2021-11-16) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.6.1-nightly.1...3.6.1) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.6.0...3.6.1) + +**πŸ› Bug fixes** + +- Loader doesn't allow changing of version before loading [\#2254](https://github.com/pypeclub/OpenPype/pull/2254) ## [3.6.0](https://github.com/pypeclub/OpenPype/tree/3.6.0) (2021-11-15) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.6.0-nightly.6...3.6.0) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.5.0...3.6.0) + +### πŸ“– Documentation + +- Add alternative sites for Site Sync [\#2206](https://github.com/pypeclub/OpenPype/pull/2206) +- Add command line way of running site sync server [\#2188](https://github.com/pypeclub/OpenPype/pull/2188) + +**πŸ†• New features** + +- Add validate active site button to sync queue on a project [\#2176](https://github.com/pypeclub/OpenPype/pull/2176) +- Maya : Colorspace configuration [\#2170](https://github.com/pypeclub/OpenPype/pull/2170) +- Blender: Added support for audio [\#2168](https://github.com/pypeclub/OpenPype/pull/2168) +- Flame: a host basic integration [\#2165](https://github.com/pypeclub/OpenPype/pull/2165) +- Houdini: simple HDA workflow [\#2072](https://github.com/pypeclub/OpenPype/pull/2072) +- Basic Royal Render Integration ✨ [\#2061](https://github.com/pypeclub/OpenPype/pull/2061) +- Camera handling between Blender and Unreal [\#1988](https://github.com/pypeclub/OpenPype/pull/1988) +- switch PyQt5 for PySide2 [\#1744](https://github.com/pypeclub/OpenPype/pull/1744) + +**πŸš€ Enhancements** + +- Tools: Subset manager in OpenPype [\#2243](https://github.com/pypeclub/OpenPype/pull/2243) +- General: Skip module directories without init file [\#2239](https://github.com/pypeclub/OpenPype/pull/2239) +- General: Static interfaces [\#2238](https://github.com/pypeclub/OpenPype/pull/2238) +- Style: Fix transparent image in style [\#2235](https://github.com/pypeclub/OpenPype/pull/2235) +- Add a "following workfile versioning" option on publish [\#2225](https://github.com/pypeclub/OpenPype/pull/2225) +- Modules: Module can add cli commands [\#2224](https://github.com/pypeclub/OpenPype/pull/2224) +- Webpublisher: Separate webpublisher logic [\#2222](https://github.com/pypeclub/OpenPype/pull/2222) +- Add both side availability on Site Sync sites to Loader [\#2220](https://github.com/pypeclub/OpenPype/pull/2220) +- Tools: Center loader and library loader on show [\#2219](https://github.com/pypeclub/OpenPype/pull/2219) +- Maya : Validate shape zero [\#2212](https://github.com/pypeclub/OpenPype/pull/2212) +- Maya : validate unique names [\#2211](https://github.com/pypeclub/OpenPype/pull/2211) +- Tools: OpenPype stylesheet in workfiles tool [\#2208](https://github.com/pypeclub/OpenPype/pull/2208) +- Ftrack: Replace Queue with deque in event handlers logic [\#2204](https://github.com/pypeclub/OpenPype/pull/2204) +- Tools: New select context dialog [\#2200](https://github.com/pypeclub/OpenPype/pull/2200) +- Maya : Validate mesh ngons [\#2199](https://github.com/pypeclub/OpenPype/pull/2199) +- Dirmap in Nuke [\#2198](https://github.com/pypeclub/OpenPype/pull/2198) +- Delivery: Check 'frame' key in template for sequence delivery [\#2196](https://github.com/pypeclub/OpenPype/pull/2196) +- Settings: Site sync project settings improvement [\#2193](https://github.com/pypeclub/OpenPype/pull/2193) +- Usage of tools code [\#2185](https://github.com/pypeclub/OpenPype/pull/2185) +- Settings: Dictionary based on project roots [\#2184](https://github.com/pypeclub/OpenPype/pull/2184) +- Subset name: Be able to pass asset document to get subset name [\#2179](https://github.com/pypeclub/OpenPype/pull/2179) +- Tools: Experimental tools [\#2167](https://github.com/pypeclub/OpenPype/pull/2167) +- Loader: Refactor and use OpenPype stylesheets [\#2166](https://github.com/pypeclub/OpenPype/pull/2166) +- Add loader for linked smart objects in photoshop [\#2149](https://github.com/pypeclub/OpenPype/pull/2149) +- Burnins: DNxHD profiles handling [\#2142](https://github.com/pypeclub/OpenPype/pull/2142) +- Tools: Single access point for host tools [\#2139](https://github.com/pypeclub/OpenPype/pull/2139) + +**πŸ› Bug fixes** + +- Ftrack: Sync project ftrack id cache issue [\#2250](https://github.com/pypeclub/OpenPype/pull/2250) +- Ftrack: Session creation and Prepare project [\#2245](https://github.com/pypeclub/OpenPype/pull/2245) +- Added queue for studio processing in PS [\#2237](https://github.com/pypeclub/OpenPype/pull/2237) +- Python 2: Unicode to string conversion [\#2236](https://github.com/pypeclub/OpenPype/pull/2236) +- Fix - enum for color coding in PS [\#2234](https://github.com/pypeclub/OpenPype/pull/2234) +- Pyblish Tool: Fix targets handling [\#2232](https://github.com/pypeclub/OpenPype/pull/2232) +- Ftrack: Base event fix of 'get\_project\_from\_entity' method [\#2214](https://github.com/pypeclub/OpenPype/pull/2214) +- Maya : multiple subsets review broken [\#2210](https://github.com/pypeclub/OpenPype/pull/2210) +- Fix - different command used for Linux and Mac OS [\#2207](https://github.com/pypeclub/OpenPype/pull/2207) +- Tools: Workfiles tool don't use avalon widgets [\#2205](https://github.com/pypeclub/OpenPype/pull/2205) +- Ftrack: Fill missing ftrack id on mongo project [\#2203](https://github.com/pypeclub/OpenPype/pull/2203) +- Project Manager: Fix copying of tasks [\#2191](https://github.com/pypeclub/OpenPype/pull/2191) +- StandalonePublisher: Source validator don't expect representations [\#2190](https://github.com/pypeclub/OpenPype/pull/2190) +- Blender: Fix trying to pack an image when the shader node has no texture [\#2183](https://github.com/pypeclub/OpenPype/pull/2183) +- Maya: review viewport settings [\#2177](https://github.com/pypeclub/OpenPype/pull/2177) +- MacOS: Launching of applications may cause Permissions error [\#2175](https://github.com/pypeclub/OpenPype/pull/2175) +- Maya: Aspect ratio [\#2174](https://github.com/pypeclub/OpenPype/pull/2174) +- Blender: Fix 'Deselect All' with object not in 'Object Mode' [\#2163](https://github.com/pypeclub/OpenPype/pull/2163) +- Tools: Stylesheets are applied after tool show [\#2161](https://github.com/pypeclub/OpenPype/pull/2161) +- Maya: Collect render - fix UNC path support πŸ› [\#2158](https://github.com/pypeclub/OpenPype/pull/2158) +- Maya: Fix hotbox broken by scriptsmenu [\#2151](https://github.com/pypeclub/OpenPype/pull/2151) +- Ftrack: Ignore save warnings exception in Prepare project action [\#2150](https://github.com/pypeclub/OpenPype/pull/2150) +- Loader thumbnails with smooth edges [\#2147](https://github.com/pypeclub/OpenPype/pull/2147) +- Added validator for source files for Standalone Publisher [\#2138](https://github.com/pypeclub/OpenPype/pull/2138) + +**Merged pull requests:** + +- Bump pillow from 8.2.0 to 8.3.2 [\#2162](https://github.com/pypeclub/OpenPype/pull/2162) +- Bump axios from 0.21.1 to 0.21.4 in /website [\#2059](https://github.com/pypeclub/OpenPype/pull/2059) ## [3.5.0](https://github.com/pypeclub/OpenPype/tree/3.5.0) (2021-10-17) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.5.0-nightly.8...3.5.0) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.4.1...3.5.0) + +**Deprecated:** + +- Maya: Change mayaAscii family to mayaScene [\#2106](https://github.com/pypeclub/OpenPype/pull/2106) + +**πŸ†• New features** + +- Added project and task into context change message in Maya [\#2131](https://github.com/pypeclub/OpenPype/pull/2131) +- Add ExtractBurnin to photoshop review [\#2124](https://github.com/pypeclub/OpenPype/pull/2124) +- PYPE-1218 - changed namespace to contain subset name in Maya [\#2114](https://github.com/pypeclub/OpenPype/pull/2114) +- Added running configurable disk mapping command before start of OP [\#2091](https://github.com/pypeclub/OpenPype/pull/2091) +- SFTP provider [\#2073](https://github.com/pypeclub/OpenPype/pull/2073) +- Maya: Validate setdress top group [\#2068](https://github.com/pypeclub/OpenPype/pull/2068) +- Maya: Enable publishing render attrib sets \(e.g. V-Ray Displacement\) with model [\#1955](https://github.com/pypeclub/OpenPype/pull/1955) + +**πŸš€ Enhancements** + +- Maya: make rig validators configurable in settings [\#2137](https://github.com/pypeclub/OpenPype/pull/2137) +- Settings: Updated readme for entity types in settings [\#2132](https://github.com/pypeclub/OpenPype/pull/2132) +- Nuke: unified clip loader [\#2128](https://github.com/pypeclub/OpenPype/pull/2128) +- Settings UI: Project model refreshing and sorting [\#2104](https://github.com/pypeclub/OpenPype/pull/2104) +- Create Read From Rendered - Disable Relative paths by default [\#2093](https://github.com/pypeclub/OpenPype/pull/2093) +- Added choosing different dirmap mapping if workfile synched locally [\#2088](https://github.com/pypeclub/OpenPype/pull/2088) +- General: Remove IdleManager module [\#2084](https://github.com/pypeclub/OpenPype/pull/2084) +- Tray UI: Message box about missing settings defaults [\#2080](https://github.com/pypeclub/OpenPype/pull/2080) +- Tray UI: Show menu where first click happened [\#2079](https://github.com/pypeclub/OpenPype/pull/2079) +- Global: add global validators to settings [\#2078](https://github.com/pypeclub/OpenPype/pull/2078) +- Use CRF for burnin when available [\#2070](https://github.com/pypeclub/OpenPype/pull/2070) +- Project manager: Filter first item after selection of project [\#2069](https://github.com/pypeclub/OpenPype/pull/2069) +- Nuke: Adding `still` image family workflow [\#2064](https://github.com/pypeclub/OpenPype/pull/2064) +- Maya: validate authorized loaded plugins [\#2062](https://github.com/pypeclub/OpenPype/pull/2062) +- Tools: add support for pyenv on windows [\#2051](https://github.com/pypeclub/OpenPype/pull/2051) +- SyncServer: Dropbox Provider [\#1979](https://github.com/pypeclub/OpenPype/pull/1979) +- Burnin: Get data from context with defined keys. [\#1897](https://github.com/pypeclub/OpenPype/pull/1897) +- Timers manager: Get task time [\#1896](https://github.com/pypeclub/OpenPype/pull/1896) +- TVPaint: Option to stop timer on application exit. [\#1887](https://github.com/pypeclub/OpenPype/pull/1887) + +**πŸ› Bug fixes** + +- Maya: fix model publishing [\#2130](https://github.com/pypeclub/OpenPype/pull/2130) +- Fix - oiiotool wasn't recognized even if present [\#2129](https://github.com/pypeclub/OpenPype/pull/2129) +- General: Disk mapping group [\#2120](https://github.com/pypeclub/OpenPype/pull/2120) +- Hiero: publishing effect first time makes wrong resources path [\#2115](https://github.com/pypeclub/OpenPype/pull/2115) +- Add startup script for Houdini Core. [\#2110](https://github.com/pypeclub/OpenPype/pull/2110) +- TVPaint: Behavior name of loop also accept repeat [\#2109](https://github.com/pypeclub/OpenPype/pull/2109) +- Ftrack: Project settings save custom attributes skip unknown attributes [\#2103](https://github.com/pypeclub/OpenPype/pull/2103) +- Blender: Fix NoneType error when animation\_data is missing for a rig [\#2101](https://github.com/pypeclub/OpenPype/pull/2101) +- Fix broken import in sftp provider [\#2100](https://github.com/pypeclub/OpenPype/pull/2100) +- Global: Fix docstring on publish plugin extract review [\#2097](https://github.com/pypeclub/OpenPype/pull/2097) +- Delivery Action Files Sequence fix [\#2096](https://github.com/pypeclub/OpenPype/pull/2096) +- General: Cloud mongo ca certificate issue [\#2095](https://github.com/pypeclub/OpenPype/pull/2095) +- TVPaint: Creator use context from workfile [\#2087](https://github.com/pypeclub/OpenPype/pull/2087) +- Blender: fix texture missing when publishing blend files [\#2085](https://github.com/pypeclub/OpenPype/pull/2085) +- General: Startup validations oiio tool path fix on linux [\#2083](https://github.com/pypeclub/OpenPype/pull/2083) +- Deadline: Collect deadline server does not check existence of deadline key [\#2082](https://github.com/pypeclub/OpenPype/pull/2082) +- Blender: fixed Curves with modifiers in Rigs [\#2081](https://github.com/pypeclub/OpenPype/pull/2081) +- Nuke UI scaling [\#2077](https://github.com/pypeclub/OpenPype/pull/2077) +- Maya: Fix multi-camera renders [\#2065](https://github.com/pypeclub/OpenPype/pull/2065) +- Fix Sync Queue when project disabled [\#2063](https://github.com/pypeclub/OpenPype/pull/2063) + +**Merged pull requests:** + +- Bump pywin32 from 300 to 301 [\#2086](https://github.com/pypeclub/OpenPype/pull/2086) ## [3.4.1](https://github.com/pypeclub/OpenPype/tree/3.4.1) (2021-09-23) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.4.1-nightly.1...3.4.1) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.4.0...3.4.1) + +**πŸ†• New features** + +- Settings: Flag project as deactivated and hide from tools' view [\#2008](https://github.com/pypeclub/OpenPype/pull/2008) + +**πŸš€ Enhancements** + +- General: Startup validations [\#2054](https://github.com/pypeclub/OpenPype/pull/2054) +- Nuke: proxy mode validator [\#2052](https://github.com/pypeclub/OpenPype/pull/2052) +- Ftrack: Removed ftrack interface [\#2049](https://github.com/pypeclub/OpenPype/pull/2049) +- Settings UI: Deffered set value on entity [\#2044](https://github.com/pypeclub/OpenPype/pull/2044) +- Loader: Families filtering [\#2043](https://github.com/pypeclub/OpenPype/pull/2043) +- Settings UI: Project view enhancements [\#2042](https://github.com/pypeclub/OpenPype/pull/2042) +- Settings for Nuke IncrementScriptVersion [\#2039](https://github.com/pypeclub/OpenPype/pull/2039) +- Loader & Library loader: Use tools from OpenPype [\#2038](https://github.com/pypeclub/OpenPype/pull/2038) +- Adding predefined project folders creation in PM [\#2030](https://github.com/pypeclub/OpenPype/pull/2030) +- WebserverModule: Removed interface of webserver module [\#2028](https://github.com/pypeclub/OpenPype/pull/2028) +- TimersManager: Removed interface of timers manager [\#2024](https://github.com/pypeclub/OpenPype/pull/2024) +- Feature Maya import asset from scene inventory [\#2018](https://github.com/pypeclub/OpenPype/pull/2018) + +**πŸ› Bug fixes** + +- Timers manger: Typo fix [\#2058](https://github.com/pypeclub/OpenPype/pull/2058) +- Hiero: Editorial fixes [\#2057](https://github.com/pypeclub/OpenPype/pull/2057) +- Differentiate jpg sequences from thumbnail [\#2056](https://github.com/pypeclub/OpenPype/pull/2056) +- FFmpeg: Split command to list does not work [\#2046](https://github.com/pypeclub/OpenPype/pull/2046) +- Removed shell flag in subprocess call [\#2045](https://github.com/pypeclub/OpenPype/pull/2045) + +**Merged pull requests:** + +- Bump prismjs from 1.24.0 to 1.25.0 in /website [\#2050](https://github.com/pypeclub/OpenPype/pull/2050) ## [3.4.0](https://github.com/pypeclub/OpenPype/tree/3.4.0) (2021-09-17) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.4.0-nightly.6...3.4.0) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.3.1...3.4.0) + +### πŸ“– Documentation + +- Documentation: Ftrack launch argsuments update [\#2014](https://github.com/pypeclub/OpenPype/pull/2014) +- Nuke Quick Start / Tutorial [\#1952](https://github.com/pypeclub/OpenPype/pull/1952) +- Houdini: add Camera, Point Cache, Composite, Redshift ROP and VDB Cache support [\#1821](https://github.com/pypeclub/OpenPype/pull/1821) + +**πŸ†• New features** + +- Nuke: Compatibility with Nuke 13 [\#2003](https://github.com/pypeclub/OpenPype/pull/2003) +- Maya: Add Xgen family support [\#1947](https://github.com/pypeclub/OpenPype/pull/1947) +- Feature/webpublisher backend [\#1876](https://github.com/pypeclub/OpenPype/pull/1876) +- Blender: Improved assets handling [\#1615](https://github.com/pypeclub/OpenPype/pull/1615) + +**πŸš€ Enhancements** + +- Added possibility to configure of synchronization of workfile version… [\#2041](https://github.com/pypeclub/OpenPype/pull/2041) +- General: Task types in profiles [\#2036](https://github.com/pypeclub/OpenPype/pull/2036) +- Console interpreter: Handle invalid sizes on initialization [\#2022](https://github.com/pypeclub/OpenPype/pull/2022) +- Ftrack: Show OpenPype versions in event server status [\#2019](https://github.com/pypeclub/OpenPype/pull/2019) +- General: Staging icon [\#2017](https://github.com/pypeclub/OpenPype/pull/2017) +- Ftrack: Sync to avalon actions have jobs [\#2015](https://github.com/pypeclub/OpenPype/pull/2015) +- Modules: Connect method is not required [\#2009](https://github.com/pypeclub/OpenPype/pull/2009) +- Settings UI: Number with configurable steps [\#2001](https://github.com/pypeclub/OpenPype/pull/2001) +- Moving project folder structure creation out of ftrack module \#1989 [\#1996](https://github.com/pypeclub/OpenPype/pull/1996) +- Configurable items for providers without Settings [\#1987](https://github.com/pypeclub/OpenPype/pull/1987) +- Global: Example addons [\#1986](https://github.com/pypeclub/OpenPype/pull/1986) +- Standalone Publisher: Extract harmony zip handle workfile template [\#1982](https://github.com/pypeclub/OpenPype/pull/1982) +- Settings UI: Number sliders [\#1978](https://github.com/pypeclub/OpenPype/pull/1978) +- Workfiles: Support more workfile templates [\#1966](https://github.com/pypeclub/OpenPype/pull/1966) +- Launcher: Fix crashes on action click [\#1964](https://github.com/pypeclub/OpenPype/pull/1964) +- Settings: Minor fixes in UI and missing default values [\#1963](https://github.com/pypeclub/OpenPype/pull/1963) +- Blender: Toggle system console works on windows [\#1962](https://github.com/pypeclub/OpenPype/pull/1962) +- Global: Settings defined by Addons/Modules [\#1959](https://github.com/pypeclub/OpenPype/pull/1959) +- CI: change release numbering triggers [\#1954](https://github.com/pypeclub/OpenPype/pull/1954) +- Global: Avalon Host name collector [\#1949](https://github.com/pypeclub/OpenPype/pull/1949) +- Global: Define hosts in CollectSceneVersion [\#1948](https://github.com/pypeclub/OpenPype/pull/1948) +- Add face sets to exported alembics [\#1942](https://github.com/pypeclub/OpenPype/pull/1942) +- OpenPype: Add version validation and `--headless` mode and update progress πŸ”„ [\#1939](https://github.com/pypeclub/OpenPype/pull/1939) +- \#1894 - adds host to template\_name\_profiles for filtering [\#1915](https://github.com/pypeclub/OpenPype/pull/1915) +- Environments: Tool environments in alphabetical order [\#1910](https://github.com/pypeclub/OpenPype/pull/1910) +- Disregard publishing time. [\#1888](https://github.com/pypeclub/OpenPype/pull/1888) +- Dynamic modules [\#1872](https://github.com/pypeclub/OpenPype/pull/1872) + +**πŸ› Bug fixes** + +- Workfiles tool: Task selection [\#2040](https://github.com/pypeclub/OpenPype/pull/2040) +- Ftrack: Delete old versions missing settings key [\#2037](https://github.com/pypeclub/OpenPype/pull/2037) +- Nuke: typo on a button [\#2034](https://github.com/pypeclub/OpenPype/pull/2034) +- Hiero: Fix "none" named tags [\#2033](https://github.com/pypeclub/OpenPype/pull/2033) +- FFmpeg: Subprocess arguments as list [\#2032](https://github.com/pypeclub/OpenPype/pull/2032) +- General: Fix Python 2 breaking line [\#2016](https://github.com/pypeclub/OpenPype/pull/2016) +- Bugfix/webpublisher task type [\#2006](https://github.com/pypeclub/OpenPype/pull/2006) +- Nuke thumbnails generated from middle of the sequence [\#1992](https://github.com/pypeclub/OpenPype/pull/1992) +- Nuke: last version from path gets correct version [\#1990](https://github.com/pypeclub/OpenPype/pull/1990) +- nuke, resolve, hiero: precollector order lest then 0.5 [\#1984](https://github.com/pypeclub/OpenPype/pull/1984) +- Last workfile with multiple work templates [\#1981](https://github.com/pypeclub/OpenPype/pull/1981) +- Collectors order [\#1977](https://github.com/pypeclub/OpenPype/pull/1977) +- Stop timer was within validator order range. [\#1975](https://github.com/pypeclub/OpenPype/pull/1975) +- Ftrack: arrow submodule has https url source [\#1974](https://github.com/pypeclub/OpenPype/pull/1974) +- Ftrack: Fix hosts attribute in collect ftrack username [\#1972](https://github.com/pypeclub/OpenPype/pull/1972) +- Deadline: Houdini plugins in different hierarchy [\#1970](https://github.com/pypeclub/OpenPype/pull/1970) +- Removed deprecated submodules [\#1967](https://github.com/pypeclub/OpenPype/pull/1967) +- Global: ExtractJpeg can handle filepaths with spaces [\#1961](https://github.com/pypeclub/OpenPype/pull/1961) +- Resolve path when adding to zip [\#1960](https://github.com/pypeclub/OpenPype/pull/1960) + +**Merged pull requests:** + +- Bump url-parse from 1.5.1 to 1.5.3 in /website [\#1958](https://github.com/pypeclub/OpenPype/pull/1958) +- Bump path-parse from 1.0.6 to 1.0.7 in /website [\#1933](https://github.com/pypeclub/OpenPype/pull/1933) ## [3.3.1](https://github.com/pypeclub/OpenPype/tree/3.3.1) (2021-08-20) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.3.1-nightly.1...3.3.1) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.3.0...3.3.1) + +**πŸ› Bug fixes** + +- TVPaint: Fixed rendered frame indexes [\#1946](https://github.com/pypeclub/OpenPype/pull/1946) +- Maya: Menu actions fix [\#1945](https://github.com/pypeclub/OpenPype/pull/1945) +- standalone: editorial shared object problem [\#1941](https://github.com/pypeclub/OpenPype/pull/1941) +- Bugfix nuke deadline app name [\#1928](https://github.com/pypeclub/OpenPype/pull/1928) ## [3.3.0](https://github.com/pypeclub/OpenPype/tree/3.3.0) (2021-08-17) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.3.0-nightly.11...3.3.0) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.2.0...3.3.0) + +### πŸ“– Documentation + +- Standalone Publish of textures family [\#1834](https://github.com/pypeclub/OpenPype/pull/1834) + +**πŸ†• New features** + +- Settings UI: Breadcrumbs in settings [\#1932](https://github.com/pypeclub/OpenPype/pull/1932) +- Maya: Scene patching 🩹on submission to Deadline [\#1923](https://github.com/pypeclub/OpenPype/pull/1923) +- Feature AE local render [\#1901](https://github.com/pypeclub/OpenPype/pull/1901) + +**πŸš€ Enhancements** + +- Python console interpreter [\#1940](https://github.com/pypeclub/OpenPype/pull/1940) +- Global: Updated logos and Default settings [\#1927](https://github.com/pypeclub/OpenPype/pull/1927) +- Check for missing ✨ Python when using `pyenv` [\#1925](https://github.com/pypeclub/OpenPype/pull/1925) +- Settings: Default values for enum [\#1920](https://github.com/pypeclub/OpenPype/pull/1920) +- Settings UI: Modifiable dict view enhance [\#1919](https://github.com/pypeclub/OpenPype/pull/1919) +- submodules: avalon-core update [\#1911](https://github.com/pypeclub/OpenPype/pull/1911) +- Ftrack: Where I run action enhancement [\#1900](https://github.com/pypeclub/OpenPype/pull/1900) +- Ftrack: Private project server actions [\#1899](https://github.com/pypeclub/OpenPype/pull/1899) +- Support nested studio plugins paths. [\#1898](https://github.com/pypeclub/OpenPype/pull/1898) +- Settings: global validators with options [\#1892](https://github.com/pypeclub/OpenPype/pull/1892) +- Settings: Conditional dict enum positioning [\#1891](https://github.com/pypeclub/OpenPype/pull/1891) +- Expose stop timer through rest api. [\#1886](https://github.com/pypeclub/OpenPype/pull/1886) +- TVPaint: Increment workfile [\#1885](https://github.com/pypeclub/OpenPype/pull/1885) +- Allow Multiple Notes to run on tasks. [\#1882](https://github.com/pypeclub/OpenPype/pull/1882) +- Prepare for pyside2 [\#1869](https://github.com/pypeclub/OpenPype/pull/1869) +- Filter hosts in settings host-enum [\#1868](https://github.com/pypeclub/OpenPype/pull/1868) +- Local actions with process identifier [\#1867](https://github.com/pypeclub/OpenPype/pull/1867) +- Workfile tool start at host launch support [\#1865](https://github.com/pypeclub/OpenPype/pull/1865) +- Anatomy schema validation [\#1864](https://github.com/pypeclub/OpenPype/pull/1864) +- Ftrack prepare project structure [\#1861](https://github.com/pypeclub/OpenPype/pull/1861) +- Maya: support for configurable `dirmap` πŸ—ΊοΈ [\#1859](https://github.com/pypeclub/OpenPype/pull/1859) +- Independent general environments [\#1853](https://github.com/pypeclub/OpenPype/pull/1853) +- TVPaint Start Frame [\#1844](https://github.com/pypeclub/OpenPype/pull/1844) +- Ftrack push attributes action adds traceback to job [\#1843](https://github.com/pypeclub/OpenPype/pull/1843) +- Prepare project action enhance [\#1838](https://github.com/pypeclub/OpenPype/pull/1838) +- nuke: settings create missing default subsets [\#1829](https://github.com/pypeclub/OpenPype/pull/1829) +- Update poetry lock [\#1823](https://github.com/pypeclub/OpenPype/pull/1823) +- Settings: settings for plugins [\#1819](https://github.com/pypeclub/OpenPype/pull/1819) +- Settings list can use template or schema as object type [\#1815](https://github.com/pypeclub/OpenPype/pull/1815) +- Maya: Deadline custom settings [\#1797](https://github.com/pypeclub/OpenPype/pull/1797) +- Maya: Shader name validation [\#1762](https://github.com/pypeclub/OpenPype/pull/1762) + +**πŸ› Bug fixes** + +- Fix - ftrack family was added incorrectly in some cases [\#1935](https://github.com/pypeclub/OpenPype/pull/1935) +- Fix - Deadline publish on Linux started Tray instead of headless publishing [\#1930](https://github.com/pypeclub/OpenPype/pull/1930) +- Maya: Validate Model Name - repair accident deletion in settings defaults [\#1929](https://github.com/pypeclub/OpenPype/pull/1929) +- Nuke: submit to farm failed due `ftrack` family remove [\#1926](https://github.com/pypeclub/OpenPype/pull/1926) +- Fix - validate takes repre\["files"\] as list all the time [\#1922](https://github.com/pypeclub/OpenPype/pull/1922) +- standalone: validator asset parents [\#1917](https://github.com/pypeclub/OpenPype/pull/1917) +- Nuke: update video file crassing [\#1916](https://github.com/pypeclub/OpenPype/pull/1916) +- Fix - texture validators for workfiles triggers only for textures workfiles [\#1914](https://github.com/pypeclub/OpenPype/pull/1914) +- Settings UI: List order works as expected [\#1906](https://github.com/pypeclub/OpenPype/pull/1906) +- Hiero: loaded clip was not set colorspace from version data [\#1904](https://github.com/pypeclub/OpenPype/pull/1904) +- Pyblish UI: Fix collecting stage processing [\#1903](https://github.com/pypeclub/OpenPype/pull/1903) +- Burnins: Use input's bitrate in h624 [\#1902](https://github.com/pypeclub/OpenPype/pull/1902) +- Bug: fixed python detection [\#1893](https://github.com/pypeclub/OpenPype/pull/1893) +- global: integrate name missing default template [\#1890](https://github.com/pypeclub/OpenPype/pull/1890) +- publisher: editorial plugins fixes [\#1889](https://github.com/pypeclub/OpenPype/pull/1889) +- Normalize path returned from Workfiles. [\#1880](https://github.com/pypeclub/OpenPype/pull/1880) +- Workfiles tool event arguments fix [\#1862](https://github.com/pypeclub/OpenPype/pull/1862) +- imageio: fix grouping [\#1856](https://github.com/pypeclub/OpenPype/pull/1856) +- Maya: don't add reference members as connections to the container set πŸ“¦ [\#1855](https://github.com/pypeclub/OpenPype/pull/1855) +- publisher: missing version in subset prop [\#1849](https://github.com/pypeclub/OpenPype/pull/1849) +- Ftrack type error fix in sync to avalon event handler [\#1845](https://github.com/pypeclub/OpenPype/pull/1845) +- Nuke: updating effects subset fail [\#1841](https://github.com/pypeclub/OpenPype/pull/1841) +- nuke: write render node skipped with crop [\#1836](https://github.com/pypeclub/OpenPype/pull/1836) +- Project folder structure overrides [\#1813](https://github.com/pypeclub/OpenPype/pull/1813) +- Maya: fix yeti settings path in extractor [\#1809](https://github.com/pypeclub/OpenPype/pull/1809) +- Failsafe for cross project containers. [\#1806](https://github.com/pypeclub/OpenPype/pull/1806) +- Houdini colector formatting keys fix [\#1802](https://github.com/pypeclub/OpenPype/pull/1802) +- Settings error dialog on show [\#1798](https://github.com/pypeclub/OpenPype/pull/1798) +- Application launch stdout/stderr in GUI build [\#1684](https://github.com/pypeclub/OpenPype/pull/1684) +- Nuke: re-use instance nodes output path [\#1577](https://github.com/pypeclub/OpenPype/pull/1577) + +**Merged pull requests:** + +- Fix - make AE workfile publish to Ftrack configurable [\#1937](https://github.com/pypeclub/OpenPype/pull/1937) +- Add support for multiple Deadline β˜ οΈβž– servers [\#1905](https://github.com/pypeclub/OpenPype/pull/1905) +- Maya: add support for `RedshiftNormalMap` node, fix `tx` linear space πŸš€ [\#1863](https://github.com/pypeclub/OpenPype/pull/1863) +- Maya: expected files -\> render products βš™οΈ overhaul [\#1812](https://github.com/pypeclub/OpenPype/pull/1812) +- PS, AE - send actual context when another webserver is running [\#1811](https://github.com/pypeclub/OpenPype/pull/1811) ## [3.2.0](https://github.com/pypeclub/OpenPype/tree/3.2.0) (2021-07-13) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.2.0-nightly.7...3.2.0) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/2.18.4...3.2.0) + +### πŸ“– Documentation + +- Fix: staging and `--use-version` option [\#1786](https://github.com/pypeclub/OpenPype/pull/1786) +- Subset template and TVPaint subset template docs [\#1717](https://github.com/pypeclub/OpenPype/pull/1717) +- Overscan color extract review [\#1701](https://github.com/pypeclub/OpenPype/pull/1701) + +**πŸš€ Enhancements** + +- Nuke: ftrack family plugin settings preset [\#1805](https://github.com/pypeclub/OpenPype/pull/1805) +- Standalone publisher last project [\#1799](https://github.com/pypeclub/OpenPype/pull/1799) +- Ftrack Multiple notes as server action [\#1795](https://github.com/pypeclub/OpenPype/pull/1795) +- Settings conditional dict [\#1777](https://github.com/pypeclub/OpenPype/pull/1777) +- Settings application use python 2 only where needed [\#1776](https://github.com/pypeclub/OpenPype/pull/1776) +- Settings UI copy/paste [\#1769](https://github.com/pypeclub/OpenPype/pull/1769) +- Workfile tool widths [\#1766](https://github.com/pypeclub/OpenPype/pull/1766) +- Push hierarchical attributes care about task parent changes [\#1763](https://github.com/pypeclub/OpenPype/pull/1763) +- Application executables with environment variables [\#1757](https://github.com/pypeclub/OpenPype/pull/1757) +- Deadline: Nuke submission additional attributes [\#1756](https://github.com/pypeclub/OpenPype/pull/1756) +- Settings schema without prefill [\#1753](https://github.com/pypeclub/OpenPype/pull/1753) +- Settings Hosts enum [\#1739](https://github.com/pypeclub/OpenPype/pull/1739) +- Validate containers settings [\#1736](https://github.com/pypeclub/OpenPype/pull/1736) +- PS - added loader from sequence [\#1726](https://github.com/pypeclub/OpenPype/pull/1726) +- Autoupdate launcher [\#1725](https://github.com/pypeclub/OpenPype/pull/1725) +- Toggle Ftrack upload in StandalonePublisher [\#1708](https://github.com/pypeclub/OpenPype/pull/1708) +- Nuke: Prerender Frame Range by default [\#1699](https://github.com/pypeclub/OpenPype/pull/1699) +- Smoother edges of color triangle [\#1695](https://github.com/pypeclub/OpenPype/pull/1695) + +**πŸ› Bug fixes** + +- nuke: fixing wrong name of family folder when `used existing frames` [\#1803](https://github.com/pypeclub/OpenPype/pull/1803) +- Collect ftrack family bugs [\#1801](https://github.com/pypeclub/OpenPype/pull/1801) +- Invitee email can be None which break the Ftrack commit. [\#1788](https://github.com/pypeclub/OpenPype/pull/1788) +- Otio unrelated error on import [\#1782](https://github.com/pypeclub/OpenPype/pull/1782) +- FFprobe streams order [\#1775](https://github.com/pypeclub/OpenPype/pull/1775) +- Fix - single file files are str only, cast it to list to count properly [\#1772](https://github.com/pypeclub/OpenPype/pull/1772) +- Environments in app executable for MacOS [\#1768](https://github.com/pypeclub/OpenPype/pull/1768) +- Project specific environments [\#1767](https://github.com/pypeclub/OpenPype/pull/1767) +- Settings UI with refresh button [\#1764](https://github.com/pypeclub/OpenPype/pull/1764) +- Standalone publisher thumbnail extractor fix [\#1761](https://github.com/pypeclub/OpenPype/pull/1761) +- Anatomy others templates don't cause crash [\#1758](https://github.com/pypeclub/OpenPype/pull/1758) +- Backend acre module commit update [\#1745](https://github.com/pypeclub/OpenPype/pull/1745) +- hiero: precollect instances failing when audio selected [\#1743](https://github.com/pypeclub/OpenPype/pull/1743) +- Hiero: creator instance error [\#1742](https://github.com/pypeclub/OpenPype/pull/1742) +- Nuke: fixing render creator for no selection format failing [\#1741](https://github.com/pypeclub/OpenPype/pull/1741) +- StandalonePublisher: failing collector for editorial [\#1738](https://github.com/pypeclub/OpenPype/pull/1738) +- Local settings UI crash on missing defaults [\#1737](https://github.com/pypeclub/OpenPype/pull/1737) +- TVPaint white background on thumbnail [\#1735](https://github.com/pypeclub/OpenPype/pull/1735) +- Ftrack missing custom attribute message [\#1734](https://github.com/pypeclub/OpenPype/pull/1734) +- Launcher project changes [\#1733](https://github.com/pypeclub/OpenPype/pull/1733) +- Ftrack sync status [\#1732](https://github.com/pypeclub/OpenPype/pull/1732) +- TVPaint use layer name for default variant [\#1724](https://github.com/pypeclub/OpenPype/pull/1724) +- Default subset template for TVPaint review and workfile families [\#1716](https://github.com/pypeclub/OpenPype/pull/1716) +- Maya: Extract review hotfix [\#1714](https://github.com/pypeclub/OpenPype/pull/1714) +- Settings: Imageio improving granularity [\#1711](https://github.com/pypeclub/OpenPype/pull/1711) +- Application without executables [\#1679](https://github.com/pypeclub/OpenPype/pull/1679) +- Unreal: launching on Linux [\#1672](https://github.com/pypeclub/OpenPype/pull/1672) + +**Merged pull requests:** + +- Bump prismjs from 1.23.0 to 1.24.0 in /website [\#1773](https://github.com/pypeclub/OpenPype/pull/1773) +- TVPaint ftrack family [\#1755](https://github.com/pypeclub/OpenPype/pull/1755) ## [2.18.4](https://github.com/pypeclub/OpenPype/tree/2.18.4) (2021-06-24) @@ -246,7 +1864,7 @@ ## [2.18.3](https://github.com/pypeclub/OpenPype/tree/2.18.3) (2021-06-23) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.2.0-nightly.2...2.18.3) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/2.18.2...2.18.3) ## [2.18.2](https://github.com/pypeclub/OpenPype/tree/2.18.2) (2021-06-16) @@ -254,9 +1872,47 @@ ## [3.1.0](https://github.com/pypeclub/OpenPype/tree/3.1.0) (2021-06-15) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.1.0-nightly.4...3.1.0) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.0.0...3.1.0) -# Changelog +### πŸ“– Documentation + +- Feature Slack integration [\#1657](https://github.com/pypeclub/OpenPype/pull/1657) + +**πŸš€ Enhancements** + +- Log Viewer with OpenPype style [\#1703](https://github.com/pypeclub/OpenPype/pull/1703) +- Scrolling in OpenPype info widget [\#1702](https://github.com/pypeclub/OpenPype/pull/1702) +- OpenPype style in modules [\#1694](https://github.com/pypeclub/OpenPype/pull/1694) +- Sort applications and tools alphabetically in Settings UI [\#1689](https://github.com/pypeclub/OpenPype/pull/1689) +- \#683 - Validate Frame Range in Standalone Publisher [\#1683](https://github.com/pypeclub/OpenPype/pull/1683) +- Hiero: old container versions identify with red color [\#1682](https://github.com/pypeclub/OpenPype/pull/1682) +- Project Manger: Default name column width [\#1669](https://github.com/pypeclub/OpenPype/pull/1669) +- Remove outline in stylesheet [\#1667](https://github.com/pypeclub/OpenPype/pull/1667) +- TVPaint: Creator take layer name as default value for subset variant [\#1663](https://github.com/pypeclub/OpenPype/pull/1663) +- TVPaint custom subset template [\#1662](https://github.com/pypeclub/OpenPype/pull/1662) +- Editorial: conform assets validator [\#1659](https://github.com/pypeclub/OpenPype/pull/1659) +- Nuke - Publish simplification [\#1653](https://github.com/pypeclub/OpenPype/pull/1653) +- \#1333 - added tooltip hints to Pyblish buttons [\#1649](https://github.com/pypeclub/OpenPype/pull/1649) + +**πŸ› Bug fixes** + +- Nuke: broken publishing rendered frames [\#1707](https://github.com/pypeclub/OpenPype/pull/1707) +- Standalone publisher Thumbnail export args [\#1705](https://github.com/pypeclub/OpenPype/pull/1705) +- Bad zip can break OpenPype start [\#1691](https://github.com/pypeclub/OpenPype/pull/1691) +- Hiero: published whole edit mov [\#1687](https://github.com/pypeclub/OpenPype/pull/1687) +- Ftrack subprocess handle of stdout/stderr [\#1675](https://github.com/pypeclub/OpenPype/pull/1675) +- Settings list race condifiton and mutable dict list conversion [\#1671](https://github.com/pypeclub/OpenPype/pull/1671) +- Mac launch arguments fix [\#1660](https://github.com/pypeclub/OpenPype/pull/1660) +- Fix missing dbm python module [\#1652](https://github.com/pypeclub/OpenPype/pull/1652) +- Transparent branches in view on Mac [\#1648](https://github.com/pypeclub/OpenPype/pull/1648) +- Add asset on task item [\#1646](https://github.com/pypeclub/OpenPype/pull/1646) +- Project manager save and queue [\#1645](https://github.com/pypeclub/OpenPype/pull/1645) +- New project anatomy values [\#1644](https://github.com/pypeclub/OpenPype/pull/1644) +- Farm publishing: check if published items do exist [\#1573](https://github.com/pypeclub/OpenPype/pull/1573) + +**Merged pull requests:** + +- Bump normalize-url from 4.5.0 to 4.5.1 in /website [\#1686](https://github.com/pypeclub/OpenPype/pull/1686) ## [3.0.0](https://github.com/pypeclub/openpype/tree/3.0.0) @@ -269,12 +1925,12 @@ - Easy to add Application versions. - Per Project Environment and plugin management. - Robust profile system for creating reviewables and burnins, with filtering based on Application, Task and data family. -- Configurable publish plugins. +- Configurable publish plugins. - Options to make any validator or extractor, optional or disabled. - Color Management is now unified under anatomy settings. - Subset naming and grouping is fully configurable. - All project attributes can now be set directly in OpenPype settings. -- Studio Setting can be locked to prevent unwanted artist changes. +- Studio Setting can be locked to prevent unwanted artist changes. - You can now add per project and per task type templates for workfile initialization in most hosts. - Too many other individual configurable option to list in this changelog :) @@ -1032,8 +2688,6 @@ - Standalone Publisher: getting fps from context instead of nonexistent entity [\#729](https://github.com/pypeclub/pype/pull/729) -# Changelog - ## [2.13.6](https://github.com/pypeclub/pype/tree/2.13.6) (2020-11-15) [Full Changelog](https://github.com/pypeclub/pype/compare/2.13.5...2.13.6) @@ -1823,9 +3477,4 @@ A large cleanup release. Most of the change are under the hood. - _(avalon)_ subsets in maya 2019 weren't behaving correctly in the outliner - - - - - - +\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* From 589ba87baa0446ed745af3cc11fbcf28cfe7f05a Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 19 Oct 2022 12:33:20 +0200 Subject: [PATCH 185/201] updating worlfows --- .github/workflows/prerelease.yml | 44 +++++++++++++-------------- .github/workflows/release.yml | 52 ++++++++++++++++---------------- 2 files changed, 48 insertions(+), 48 deletions(-) diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index 81d5f05b17..078f6c85bb 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -37,27 +37,27 @@ jobs: echo ::set-output name=next_tag::$RESULT - - name: "✏️ Generate full changelog" - if: steps.version_type.outputs.type != 'skip' - id: generate-full-changelog - uses: heinrichreimer/github-changelog-generator-action@v2.3 - with: - token: ${{ secrets.ADMIN_TOKEN }} - addSections: '{"documentation":{"prefix":"### πŸ“– Documentation","labels":["type: documentation"]},"tests":{"prefix":"### βœ… Testing","labels":["tests"]},"feature":{"prefix":"**πŸ†• New features**", "labels":["type: feature"]},"breaking":{"prefix":"**πŸ’₯ Breaking**", "labels":["breaking"]},"enhancements":{"prefix":"**πŸš€ Enhancements**", "labels":["type: enhancement"]},"bugs":{"prefix":"**πŸ› Bug fixes**", "labels":["type: bug"]},"deprecated":{"prefix":"**⚠️ Deprecations**", "labels":["depreciated"]}, "refactor":{"prefix":"**πŸ”€ Refactored code**", "labels":["refactor"]}}' - issues: false - issuesWoLabels: false - sinceTag: "3.12.0" - maxIssues: 100 - pullRequests: true - prWoLabels: false - author: false - unreleased: true - compareLink: true - stripGeneratorNotice: true - verbose: true - unreleasedLabel: ${{ steps.version.outputs.next_tag }} - excludeTagsRegex: "CI/.+" - releaseBranch: "main" + # - name: "✏️ Generate full changelog" + # if: steps.version_type.outputs.type != 'skip' + # id: generate-full-changelog + # uses: heinrichreimer/github-changelog-generator-action@v2.3 + # with: + # token: ${{ secrets.ADMIN_TOKEN }} + # addSections: '{"documentation":{"prefix":"### πŸ“– Documentation","labels":["type: documentation"]},"tests":{"prefix":"### βœ… Testing","labels":["tests"]},"feature":{"prefix":"**πŸ†• New features**", "labels":["type: feature"]},"breaking":{"prefix":"**πŸ’₯ Breaking**", "labels":["breaking"]},"enhancements":{"prefix":"**πŸš€ Enhancements**", "labels":["type: enhancement"]},"bugs":{"prefix":"**πŸ› Bug fixes**", "labels":["type: bug"]},"deprecated":{"prefix":"**⚠️ Deprecations**", "labels":["depreciated"]}, "refactor":{"prefix":"**πŸ”€ Refactored code**", "labels":["refactor"]}}' + # issues: false + # issuesWoLabels: false + # sinceTag: "3.12.0" + # maxIssues: 100 + # pullRequests: true + # prWoLabels: false + # author: false + # unreleased: true + # compareLink: true + # stripGeneratorNotice: true + # verbose: true + # unreleasedLabel: ${{ steps.version.outputs.next_tag }} + # excludeTagsRegex: "CI/.+" + # releaseBranch: "main" - name: "πŸ–¨οΈ Print changelog to console" if: steps.version_type.outputs.type != 'skip' @@ -85,7 +85,7 @@ jobs: tags: true unprotect_reviews: true - - name: πŸ”¨ Merge main back to develop + - name: πŸ”¨ Merge main back to develop uses: everlytic/branch-merge@1.1.0 if: steps.version_type.outputs.type != 'skip' with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cc69e1643a..754f3d32d6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,7 +2,7 @@ name: Stable Release on: release: - types: + types: - prereleased jobs: @@ -13,7 +13,7 @@ jobs: steps: - name: πŸš› Checkout Code uses: actions/checkout@v2 - with: + with: fetch-depth: 0 - name: Set up Python @@ -33,27 +33,27 @@ jobs: echo ::set-output name=last_release::$LASTRELEASE echo ::set-output name=release_tag::$RESULT - - name: "✏️ Generate full changelog" - if: steps.version.outputs.release_tag != 'skip' - id: generate-full-changelog - uses: heinrichreimer/github-changelog-generator-action@v2.3 - with: - token: ${{ secrets.ADMIN_TOKEN }} - addSections: '{"documentation":{"prefix":"### πŸ“– Documentation","labels":["type: documentation"]},"tests":{"prefix":"### βœ… Testing","labels":["tests"]},"feature":{"prefix":"**πŸ†• New features**", "labels":["type: feature"]},"breaking":{"prefix":"**πŸ’₯ Breaking**", "labels":["breaking"]},"enhancements":{"prefix":"**πŸš€ Enhancements**", "labels":["type: enhancement"]},"bugs":{"prefix":"**πŸ› Bug fixes**", "labels":["type: bug"]},"deprecated":{"prefix":"**⚠️ Deprecations**", "labels":["depreciated"]}, "refactor":{"prefix":"**πŸ”€ Refactored code**", "labels":["refactor"]}}' - issues: false - issuesWoLabels: false - sinceTag: "3.12.0" - maxIssues: 100 - pullRequests: true - prWoLabels: false - author: false - unreleased: true - compareLink: true - stripGeneratorNotice: true - verbose: true - futureRelease: ${{ steps.version.outputs.release_tag }} - excludeTagsRegex: "CI/.+" - releaseBranch: "main" + # - name: "✏️ Generate full changelog" + # if: steps.version.outputs.release_tag != 'skip' + # id: generate-full-changelog + # uses: heinrichreimer/github-changelog-generator-action@v2.3 + # with: + # token: ${{ secrets.ADMIN_TOKEN }} + # addSections: '{"documentation":{"prefix":"### πŸ“– Documentation","labels":["type: documentation"]},"tests":{"prefix":"### βœ… Testing","labels":["tests"]},"feature":{"prefix":"**πŸ†• New features**", "labels":["type: feature"]},"breaking":{"prefix":"**πŸ’₯ Breaking**", "labels":["breaking"]},"enhancements":{"prefix":"**πŸš€ Enhancements**", "labels":["type: enhancement"]},"bugs":{"prefix":"**πŸ› Bug fixes**", "labels":["type: bug"]},"deprecated":{"prefix":"**⚠️ Deprecations**", "labels":["depreciated"]}, "refactor":{"prefix":"**πŸ”€ Refactored code**", "labels":["refactor"]}}' + # issues: false + # issuesWoLabels: false + # sinceTag: "3.12.0" + # maxIssues: 100 + # pullRequests: true + # prWoLabels: false + # author: false + # unreleased: true + # compareLink: true + # stripGeneratorNotice: true + # verbose: true + # futureRelease: ${{ steps.version.outputs.release_tag }} + # excludeTagsRegex: "CI/.+" + # releaseBranch: "main" - name: πŸ’Ύ Commit and Tag id: git_commit @@ -73,8 +73,8 @@ jobs: token: ${{ secrets.ADMIN_TOKEN }} branch: main tags: true - unprotect_reviews: true - + unprotect_reviews: true + - name: "✏️ Generate last changelog" if: steps.version.outputs.release_tag != 'skip' id: generate-last-changelog @@ -114,7 +114,7 @@ jobs: with: tag: "${{ steps.version.outputs.current_version }}" - - name: πŸ” Merge main back to develop + - name: πŸ” Merge main back to develop if: steps.version.outputs.release_tag != 'skip' uses: everlytic/branch-merge@1.1.0 with: From 268724b584f04b3861a77c8bb293b357e3127358 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 19 Oct 2022 12:36:32 +0200 Subject: [PATCH 186/201] rename release label --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d841eb9747..c4f1dcf314 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [Unreleased](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.14.4](https://github.com/pypeclub/OpenPype/tree/HEAD) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.3...HEAD) From e5dacd20010a75e89007f265d6ed294443a66aef Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 19 Oct 2022 13:30:50 +0200 Subject: [PATCH 187/201] OP-4180 - removed unneeded check Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/pipeline/anatomy.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/pipeline/anatomy.py b/openpype/pipeline/anatomy.py index fd32a16bb2..908dc2b187 100644 --- a/openpype/pipeline/anatomy.py +++ b/openpype/pipeline/anatomy.py @@ -333,8 +333,6 @@ class BaseAnatomy(object): Probably should fill missing keys and values. """ - if not project_doc: - return {} output = copy.deepcopy(project_doc["config"]) output["attributes"] = copy.deepcopy(project_doc["data"]) From 92a58ba0e5dc1122eef135862f4f5be6fc586813 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Wed, 19 Oct 2022 12:06:11 +0000 Subject: [PATCH 188/201] [Automated] Bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index 3a0c538daf..6769bb8467 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.14.4-nightly.3" +__version__ = "3.14.4-nightly.4" From b15eb445c6336421bb8b81acda98c9ce8266c40c Mon Sep 17 00:00:00 2001 From: OpenPype Date: Wed, 19 Oct 2022 12:09:55 +0000 Subject: [PATCH 189/201] [Automated] Release --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index 6769bb8467..fd3606e9f2 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.14.4-nightly.4" +__version__ = "3.14.4" From 1054ab6010e84d82392941e932d97d979cca6c06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Wed, 19 Oct 2022 16:12:33 +0200 Subject: [PATCH 190/201] :bug: fix order of includes --- .../UE_5.0/Source/OpenPype/Private/OpenPypeStyle.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/OpenPypeStyle.cpp b/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/OpenPypeStyle.cpp index 4a53af26b5..49e805da4d 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/OpenPypeStyle.cpp +++ b/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/OpenPypeStyle.cpp @@ -1,5 +1,5 @@ -#include "OpenPype.h" #include "OpenPypeStyle.h" +#include "OpenPype.h" #include "Framework/Application/SlateApplication.h" #include "Styling/SlateStyleRegistry.h" #include "Slate/SlateGameResources.h" From bb25d7cfdf7555a1495b6a384ac17101de85527b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Wed, 19 Oct 2022 16:34:54 +0200 Subject: [PATCH 191/201] :bug: add `uproject` extension to unreal project template --- openpype/settings/defaults/project_anatomy/templates.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_anatomy/templates.json b/openpype/settings/defaults/project_anatomy/templates.json index caf399a903..221a87eb99 100644 --- a/openpype/settings/defaults/project_anatomy/templates.json +++ b/openpype/settings/defaults/project_anatomy/templates.json @@ -29,7 +29,7 @@ "delivery": {}, "unreal": { "folder": "{root[work]}/{project[name]}/unreal/{task[name]}", - "file": "{project[code]}_{asset}", + "file": "{project[code]}_{asset}.uproject", "path": "{@folder}/{@file}" }, "others": { From 232780f58abb6c90ac80d48366e9ac9a70c6c2c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Wed, 19 Oct 2022 16:47:15 +0200 Subject: [PATCH 192/201] :recycle: use extension defined by addon --- openpype/settings/defaults/project_anatomy/templates.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_anatomy/templates.json b/openpype/settings/defaults/project_anatomy/templates.json index 221a87eb99..9ac0bf2ec5 100644 --- a/openpype/settings/defaults/project_anatomy/templates.json +++ b/openpype/settings/defaults/project_anatomy/templates.json @@ -29,7 +29,7 @@ "delivery": {}, "unreal": { "folder": "{root[work]}/{project[name]}/unreal/{task[name]}", - "file": "{project[code]}_{asset}.uproject", + "file": "{project[code]}_{asset}{ext}", "path": "{@folder}/{@file}" }, "others": { From 48382d2bc7ef87141473e0e586f3d7f522057ca2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 19 Oct 2022 16:48:42 +0200 Subject: [PATCH 193/201] change import of RepairAction in houdini validator --- .../hosts/houdini/plugins/publish/validate_workfile_paths.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/validate_workfile_paths.py b/openpype/hosts/houdini/plugins/publish/validate_workfile_paths.py index 0bd78ff38a..560b355e21 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_workfile_paths.py +++ b/openpype/hosts/houdini/plugins/publish/validate_workfile_paths.py @@ -1,8 +1,9 @@ # -*- coding: utf-8 -*- -import openpype.api import pyblish.api import hou +from openpype.pipeline.publish import RepairAction + class ValidateWorkfilePaths(pyblish.api.InstancePlugin): """Validate workfile paths so they are absolute.""" @@ -11,7 +12,7 @@ class ValidateWorkfilePaths(pyblish.api.InstancePlugin): families = ["workfile"] hosts = ["houdini"] label = "Validate Workfile Paths" - actions = [openpype.api.RepairAction] + actions = [RepairAction] optional = True node_types = ["file", "alembic"] From b6c3f86840ed9464f4b9def514e2b4afe670766b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Wed, 19 Oct 2022 17:02:43 +0200 Subject: [PATCH 194/201] Update openpype/settings/defaults/project_anatomy/templates.json Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/settings/defaults/project_anatomy/templates.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_anatomy/templates.json b/openpype/settings/defaults/project_anatomy/templates.json index 9ac0bf2ec5..3415c4451f 100644 --- a/openpype/settings/defaults/project_anatomy/templates.json +++ b/openpype/settings/defaults/project_anatomy/templates.json @@ -29,7 +29,7 @@ "delivery": {}, "unreal": { "folder": "{root[work]}/{project[name]}/unreal/{task[name]}", - "file": "{project[code]}_{asset}{ext}", + "file": "{project[code]}_{asset}.{ext}", "path": "{@folder}/{@file}" }, "others": { From b4ab7bb324133ad4cb3daabe620d5408d196c442 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 19 Oct 2022 17:31:13 +0200 Subject: [PATCH 195/201] change import of 'get_workfile_template_key' in unreal prelaunch hook --- openpype/hosts/unreal/hooks/pre_workfile_preparation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/unreal/hooks/pre_workfile_preparation.py b/openpype/hosts/unreal/hooks/pre_workfile_preparation.py index 50b34bd573..4ae72593e9 100644 --- a/openpype/hosts/unreal/hooks/pre_workfile_preparation.py +++ b/openpype/hosts/unreal/hooks/pre_workfile_preparation.py @@ -8,8 +8,8 @@ from openpype.lib import ( PreLaunchHook, ApplicationLaunchFailed, ApplicationNotFound, - get_workfile_template_key ) +from openpype.pipeline.workfile import get_workfile_template_key import openpype.hosts.unreal.lib as unreal_lib From 84d03eac53ac4287a7e660fbd863f8923bb23b17 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 19 Oct 2022 17:49:37 +0200 Subject: [PATCH 196/201] removed unused imports and change imports from openpype.api --- .../hosts/maya/plugins/create/create_render.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_render.py b/openpype/hosts/maya/plugins/create/create_render.py index 2b2c978d3c..a3e1272652 100644 --- a/openpype/hosts/maya/plugins/create/create_render.py +++ b/openpype/hosts/maya/plugins/create/create_render.py @@ -13,22 +13,14 @@ from openpype.settings import ( get_system_settings, get_project_settings, ) +from openpype.lib import requests_get +from openpype.modules import ModulesManager +from openpype.pipeline import legacy_io from openpype.hosts.maya.api import ( lib, lib_rendersettings, plugin ) -from openpype.lib import requests_get -from openpype.api import ( - get_system_settings, - get_project_settings) -from openpype.modules import ModulesManager -from openpype.pipeline import legacy_io -from openpype.pipeline import ( - CreatorError, - legacy_io, -) -from openpype.pipeline.context_tools import get_current_project_asset class CreateRender(plugin.Creator): From a23dfcea7909331b6db1d7b6301a7658aface47d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 19 Oct 2022 23:19:51 +0200 Subject: [PATCH 197/201] implemented main function for publish report viewer --- .../tools/publisher/publish_report_viewer/__init__.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/openpype/tools/publisher/publish_report_viewer/__init__.py b/openpype/tools/publisher/publish_report_viewer/__init__.py index ce1cc3729c..bf77a6d30b 100644 --- a/openpype/tools/publisher/publish_report_viewer/__init__.py +++ b/openpype/tools/publisher/publish_report_viewer/__init__.py @@ -1,3 +1,5 @@ +from Qt import QtWidgets + from .report_items import ( PublishReport ) @@ -16,4 +18,13 @@ __all__ = ( "PublishReportViewerWidget", "PublishReportViewerWindow", + + "main", ) + + +def main(): + app = QtWidgets.QApplication([]) + window = PublishReportViewerWindow() + window.show() + return app.exec_() From a7747eb1acf121c692d17fa2ae86010e1ea4ba47 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 19 Oct 2022 23:20:04 +0200 Subject: [PATCH 198/201] added launch function for publish report viewer --- openpype/cli.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openpype/cli.py b/openpype/cli.py index 398d1a94c0..d24cd4a872 100644 --- a/openpype/cli.py +++ b/openpype/cli.py @@ -277,6 +277,13 @@ def projectmanager(): PypeCommands().launch_project_manager() +@main.command(context_settings={"ignore_unknown_options": True}) +def publish_report_viewer(): + from openpype.tools.publisher.publish_report_viewer import main + + sys.exit(main()) + + @main.command() @click.argument("output_path") @click.option("--project", help="Define project context") From 8fb9dd41d1dfd353ac906f6febcb2922202ed6c5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 19 Oct 2022 23:20:27 +0200 Subject: [PATCH 199/201] added ps1 script to launch publish report viewer --- tools/run_publish_report_viewer.ps1 | 40 +++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 tools/run_publish_report_viewer.ps1 diff --git a/tools/run_publish_report_viewer.ps1 b/tools/run_publish_report_viewer.ps1 new file mode 100644 index 0000000000..3ff40e64e3 --- /dev/null +++ b/tools/run_publish_report_viewer.ps1 @@ -0,0 +1,40 @@ +<# +.SYNOPSIS + Helper script OpenPype Tray. + +.DESCRIPTION + + +.EXAMPLE + +PS> .\run_tray.ps1 + +#> +$current_dir = Get-Location +$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent +$openpype_root = (Get-Item $script_dir).parent.FullName + +# Install PSWriteColor to support colorized output to terminal +$env:PSModulePath = $env:PSModulePath + ";$($openpype_root)\tools\modules\powershell" + +$env:_INSIDE_OPENPYPE_TOOL = "1" + +# make sure Poetry is in PATH +if (-not (Test-Path 'env:POETRY_HOME')) { + $env:POETRY_HOME = "$openpype_root\.poetry" +} +$env:PATH = "$($env:PATH);$($env:POETRY_HOME)\bin" + +Set-Location -Path $openpype_root + +Write-Color -Text ">>> ", "Reading Poetry ... " -Color Green, Gray -NoNewline +if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) { + Write-Color -Text "NOT FOUND" -Color Yellow + Write-Color -Text "*** ", "We need to install Poetry create virtual env first ..." -Color Yellow, Gray + & "$openpype_root\tools\create_env.ps1" +} else { + Write-Color -Text "OK" -Color Green +} + +& "$($env:POETRY_HOME)\bin\poetry" run python "$($openpype_root)\start.py" publish-report-viewer --debug +Set-Location -Path $current_dir From e35e3e0299622bd600d59574857f27ad700890dd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 20 Oct 2022 12:49:39 +0200 Subject: [PATCH 200/201] fix python 2 compatibility of ffmpeg and oiio tools discovery --- openpype/lib/vendor_bin_utils.py | 48 ++++++++++++++++---------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/openpype/lib/vendor_bin_utils.py b/openpype/lib/vendor_bin_utils.py index eb7987c8a1..099f9a34ba 100644 --- a/openpype/lib/vendor_bin_utils.py +++ b/openpype/lib/vendor_bin_utils.py @@ -195,6 +195,28 @@ def find_tool_in_custom_paths(paths, tool, validation_func=None): return None +def _check_args_returncode(args): + try: + # Python 2 compatibility where DEVNULL is not available + if hasattr(subprocess, "DEVNULL"): + proc = subprocess.Popen( + args, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + proc.wait() + else: + with open(os.devnull, "w") as devnull: + proc = subprocess.Popen( + args, stdout=devnull, stderr=devnull, + ) + proc.wait() + + except Exception: + return False + return proc.returncode == 0 + + def _oiio_executable_validation(filepath): """Validate oiio tool executable if can be executed. @@ -223,18 +245,7 @@ def _oiio_executable_validation(filepath): if not filepath: return False - try: - proc = subprocess.Popen( - [filepath, "--help"], - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - ) - proc.wait() - return proc.returncode == 0 - - except Exception: - pass - return False + return _check_args_returncode([filepath, "--help"]) def get_oiio_tools_path(tool="oiiotool"): @@ -302,18 +313,7 @@ def _ffmpeg_executable_validation(filepath): if not filepath: return False - try: - proc = subprocess.Popen( - [filepath, "-version"], - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - ) - proc.wait() - return proc.returncode == 0 - - except Exception: - pass - return False + return _check_args_returncode([filepath, "-version"]) def get_ffmpeg_tool_path(tool="ffmpeg"): From 5d476f8f8b8136e20c88a6b5229c74fec2f2b1d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 20 Oct 2022 12:53:25 +0200 Subject: [PATCH 201/201] :recycle: remove redundant Creator stub this caused empty item in creator dialog --- openpype/hosts/unreal/api/__init__.py | 7 ++----- openpype/hosts/unreal/api/plugin.py | 11 +---------- openpype/hosts/unreal/plugins/create/create_camera.py | 4 ++-- openpype/hosts/unreal/plugins/create/create_layout.py | 4 ++-- openpype/hosts/unreal/plugins/create/create_look.py | 3 ++- openpype/hosts/unreal/plugins/create/create_render.py | 4 ++-- .../unreal/plugins/create/create_staticmeshfbx.py | 4 ++-- 7 files changed, 13 insertions(+), 24 deletions(-) diff --git a/openpype/hosts/unreal/api/__init__.py b/openpype/hosts/unreal/api/__init__.py index 870982f5f9..3f96d8ac6f 100644 --- a/openpype/hosts/unreal/api/__init__.py +++ b/openpype/hosts/unreal/api/__init__.py @@ -1,10 +1,8 @@ # -*- coding: utf-8 -*- """Unreal Editor OpenPype host API.""" -from .plugin import ( - Loader, - Creator -) +from .plugin import Loader + from .pipeline import ( install, uninstall, @@ -25,7 +23,6 @@ from .pipeline import ( __all__ = [ "install", "uninstall", - "Creator", "Loader", "ls", "publish", diff --git a/openpype/hosts/unreal/api/plugin.py b/openpype/hosts/unreal/api/plugin.py index d8d2f2420d..6fc00cb71c 100644 --- a/openpype/hosts/unreal/api/plugin.py +++ b/openpype/hosts/unreal/api/plugin.py @@ -1,16 +1,7 @@ # -*- coding: utf-8 -*- from abc import ABC -from openpype.pipeline import ( - LegacyCreator, - LoaderPlugin, -) - - -class Creator(LegacyCreator): - """This serves as skeleton for future OpenPype specific functionality""" - defaults = ['Main'] - maintain_selection = False +from openpype.pipeline import LoaderPlugin class Loader(LoaderPlugin, ABC): diff --git a/openpype/hosts/unreal/plugins/create/create_camera.py b/openpype/hosts/unreal/plugins/create/create_camera.py index 2842900834..bf1489d688 100644 --- a/openpype/hosts/unreal/plugins/create/create_camera.py +++ b/openpype/hosts/unreal/plugins/create/create_camera.py @@ -2,11 +2,11 @@ import unreal from unreal import EditorAssetLibrary as eal from unreal import EditorLevelLibrary as ell -from openpype.hosts.unreal.api import plugin from openpype.hosts.unreal.api.pipeline import instantiate +from openpype.pipeline import LegacyCreator -class CreateCamera(plugin.Creator): +class CreateCamera(LegacyCreator): """Layout output for character rigs""" name = "layoutMain" diff --git a/openpype/hosts/unreal/plugins/create/create_layout.py b/openpype/hosts/unreal/plugins/create/create_layout.py index 5fef08ce2a..c1067b00d9 100644 --- a/openpype/hosts/unreal/plugins/create/create_layout.py +++ b/openpype/hosts/unreal/plugins/create/create_layout.py @@ -1,11 +1,11 @@ # -*- coding: utf-8 -*- from unreal import EditorLevelLibrary -from openpype.hosts.unreal.api import plugin +from openpype.pipeline import LegacyCreator from openpype.hosts.unreal.api.pipeline import instantiate -class CreateLayout(plugin.Creator): +class CreateLayout(LegacyCreator): """Layout output for character rigs.""" name = "layoutMain" diff --git a/openpype/hosts/unreal/plugins/create/create_look.py b/openpype/hosts/unreal/plugins/create/create_look.py index 12f6b70ae6..4abf3f6095 100644 --- a/openpype/hosts/unreal/plugins/create/create_look.py +++ b/openpype/hosts/unreal/plugins/create/create_look.py @@ -2,9 +2,10 @@ """Create look in Unreal.""" import unreal # noqa from openpype.hosts.unreal.api import pipeline, plugin +from openpype.pipeline import LegacyCreator -class CreateLook(plugin.Creator): +class CreateLook(LegacyCreator): """Shader connections defining shape look.""" name = "unrealLook" diff --git a/openpype/hosts/unreal/plugins/create/create_render.py b/openpype/hosts/unreal/plugins/create/create_render.py index 950799cc10..a85d17421b 100644 --- a/openpype/hosts/unreal/plugins/create/create_render.py +++ b/openpype/hosts/unreal/plugins/create/create_render.py @@ -1,10 +1,10 @@ import unreal from openpype.hosts.unreal.api import pipeline -from openpype.hosts.unreal.api.plugin import Creator +from openpype.pipeline import LegacyCreator -class CreateRender(Creator): +class CreateRender(LegacyCreator): """Create instance for sequence for rendering""" name = "unrealRender" diff --git a/openpype/hosts/unreal/plugins/create/create_staticmeshfbx.py b/openpype/hosts/unreal/plugins/create/create_staticmeshfbx.py index 601c2fae06..45d517d27d 100644 --- a/openpype/hosts/unreal/plugins/create/create_staticmeshfbx.py +++ b/openpype/hosts/unreal/plugins/create/create_staticmeshfbx.py @@ -1,13 +1,13 @@ # -*- coding: utf-8 -*- """Create Static Meshes as FBX geometry.""" import unreal # noqa -from openpype.hosts.unreal.api import plugin from openpype.hosts.unreal.api.pipeline import ( instantiate, ) +from openpype.pipeline import LegacyCreator -class CreateStaticMeshFBX(plugin.Creator): +class CreateStaticMeshFBX(LegacyCreator): """Static FBX geometry.""" name = "unrealStaticMeshMain"