From 059589890e0d91eea4bd89095b02fe5de9f44fc8 Mon Sep 17 00:00:00 2001 From: Guilhem Compain Date: Wed, 15 May 2024 19:29:27 +0200 Subject: [PATCH 01/19] #419: enhancement: add custom format set from custom settings for Nuke --- openpype/hosts/nuke/api/lib.py | 72 ++++++++++++++++++++++++++++- openpype/hosts/nuke/api/pipeline.py | 6 ++- 2 files changed, 76 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index c06263a8b77..50a9930e156 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -2597,13 +2597,83 @@ def make_format_string(self, **kwargs): def set_context_settings(self): os.environ["OP_NUKE_SKIP_SAVE_EVENT"] = "True" # replace reset resolution from avalon core to pype's - #self.reset_resolution() + self.reset_resolution() # replace reset resolution from avalon core to pype's self.reset_frame_range_handles() # add colorspace menu item self.set_colorspace() del os.environ["OP_NUKE_SKIP_SAVE_EVENT"] + def set_custom_settings(self,): + project_name = get_current_project_name() + project_settings = get_project_settings(project_name) + + custom_settings = project_settings.get('fix_custom_settings') + if not custom_settings: + log.warning("Can't access to quad custom settings. Custom settings will not be applied.") + return + + self.set_workfile_overrides(custom_settings, project_name) + + def set_workfile_overrides(self, custom_settings, project_name): + resolution_overrides = custom_settings.get("general", []).get("working_resolution_overrides", None) + if not resolution_overrides: + log.warning("Can't retrieve resolution overrides for workfiles. Will not be applied.") + return + + import re + regex_str = f'(nuke)[\/.]({nuke.NUKE_VERSION_MAJOR})[\\\/\.\-\_\:]({nuke.NUKE_VERSION_MINOR})' + application_regex = re.compile(regex_str) + + overrides_group = None + for resolution_overrides_set in resolution_overrides: + for application in resolution_overrides_set.get('applications', []): + match = application_regex.search(application) + if match: + overrides_group = resolution_overrides_set + + if not overrides_group: + log.warning("Can't find overrides group that fit application. Abort.") + + asset_data = self._asset_entity["data"] + format_data = {"419-enhancement-dissocier-res-de-travail-et-res-final" + "width": int(overrides_group.get('working_resolution_width')), + "height": int(overrides_group.get('working_resolution_height')), + "pixel_aspect": asset_data.get( + 'pixelAspect', + asset_data.get('pixel_aspect', 1)), + "name": f"{project_name}_{match.group()}" + } + + if any(x_ for x_ in format_data.values() if x_ is None): + msg = ("Missing override from custom settings." + "\nContact your supervisor!." + "\n\nWidth: `{width}`" + "\nHeight: `{height}`" + "\nPixel Aspect: `{pixel_aspect}`").format(**format_data) + log.error(msg) + nuke.message(msg) + + existing_format = None + for format in nuke.formats(): + if format_data["name"] == format.name(): + existing_format = format + break + + if existing_format: + # Enforce existing format to be correct. + existing_format.setWidth(format_data["width"]) + existing_format.setHeight(format_data["height"]) + existing_format.setPixelAspect(format_data["pixel_aspect"]) + else: + format_string = self.make_format_string(**format_data) + log.info("Creating new format: {}".format(format_string)) + nuke.addFormat(format_string) + + nuke.root()["format"].setValue(format_data["name"]) + log.info(f"Format is set with values : {format_data}") + + def set_favorites(self): from .utils import set_context_favorites diff --git a/openpype/hosts/nuke/api/pipeline.py b/openpype/hosts/nuke/api/pipeline.py index 8d185edf17b..420c9d0d32b 100644 --- a/openpype/hosts/nuke/api/pipeline.py +++ b/openpype/hosts/nuke/api/pipeline.py @@ -175,11 +175,13 @@ def add_nuke_callbacks(): # set checker for last versions on loaded containers nuke.addOnScriptLoad(check_inventory_versions) - nuke.addOnScriptSave(check_inventory_versions) # set apply all workfile settings on script load and save nuke.addOnScriptLoad(WorkfileSettings().set_context_settings) + # set apply all custom settings on script load and save + nuke.addOnScriptLoad(workfile_settings.set_custom_settings) + # Emit events nuke.addOnCreate(_on_scene_open, nodeClass="Root") nuke.addOnScriptSave(_on_scene_save) @@ -191,6 +193,8 @@ def add_nuke_callbacks(): log.info("Added Nuke callbacks ...") +def test(): + nuke.message('test') def reload_config(): """Attempt to reload pipeline at run-time. From 21396cf83d755e2e2a38ab795af9cc2dff41634e Mon Sep 17 00:00:00 2001 From: Guilhem Compain Date: Thu, 16 May 2024 10:52:32 +0200 Subject: [PATCH 02/19] #419: bugfix: fix typo issue --- openpype/hosts/nuke/api/lib.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 50a9930e156..b05fffba14c 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -2543,6 +2543,8 @@ def reset_resolution(self): "name": project_name } + log.info(format_data) + if any(x_ for x_ in format_data.values() if x_ is None): msg = ("Missing set shot attributes in DB." "\nContact your supervisor!." @@ -2636,7 +2638,7 @@ def set_workfile_overrides(self, custom_settings, project_name): log.warning("Can't find overrides group that fit application. Abort.") asset_data = self._asset_entity["data"] - format_data = {"419-enhancement-dissocier-res-de-travail-et-res-final" + format_data = { "width": int(overrides_group.get('working_resolution_width')), "height": int(overrides_group.get('working_resolution_height')), "pixel_aspect": asset_data.get( From 97eadd82a613bf2e302e962a2b007ec6635e0e59 Mon Sep 17 00:00:00 2001 From: Guilhem Compain Date: Thu, 16 May 2024 11:32:50 +0200 Subject: [PATCH 03/19] #419: enhancement: add resolution override for After Effects --- openpype/hosts/aftereffects/api/lib.py | 44 +++++++++++++++++++++++++- openpype/hosts/nuke/api/lib.py | 3 +- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/aftereffects/api/lib.py b/openpype/hosts/aftereffects/api/lib.py index e8352c382b5..62123c5b5cf 100644 --- a/openpype/hosts/aftereffects/api/lib.py +++ b/openpype/hosts/aftereffects/api/lib.py @@ -4,7 +4,7 @@ import contextlib import logging -from openpype.pipeline.context_tools import get_current_context +from openpype.pipeline.context_tools import get_current_context, get_project_settings from openpype.client import get_asset_by_name from .ws_stub import get_stub @@ -114,6 +114,38 @@ def get_asset_settings(asset_doc): } +def get_custom_settings(project_name): + project_settings = get_project_settings(project_name) + custom_settings = project_settings.get('fix_custom_settings') + if not custom_settings: + log.warning("Can't access to quad custom settings. Custom settings will not be applied.") + return + + return custom_settings + +def get_workfile_overrides(custom_settings): + resolution_overrides = custom_settings.get("general", []).get("working_resolution_overrides", None) + if not resolution_overrides: + log.warning("Can't retrieve resolution overrides for workfiles. Will not be applied.") + return + + app_year = f"20{get_stub().get_app_version().split('.')[0]}" + regex_str = f'(aftereffects)[\\\/\.\-\_\:]({app_year}$)' + application_regex = re.compile(regex_str) + + overrides_group = None + for resolution_overrides_set in resolution_overrides: + for application in resolution_overrides_set.get('applications', []): + match = application_regex.search(application) + if match: + overrides_group = resolution_overrides_set + + if not overrides_group: + log.warning("Can't find overrides group that fit application. Abort.") + + return overrides_group + + def set_settings(frames, resolution, comp_ids=None, print_msg=True): """Sets number of frames and resolution to selected comps. @@ -130,6 +162,7 @@ def set_settings(frames, resolution, comp_ids=None, print_msg=True): asset_doc = get_asset_by_name(current_context["project_name"], current_context["asset_name"]) settings = get_asset_settings(asset_doc) + custom_settings = get_custom_settings(current_context["project_name"]) msg = '' if frames: @@ -138,7 +171,16 @@ def set_settings(frames, resolution, comp_ids=None, print_msg=True): fps = settings["fps"] msg += f"frame start:{frame_start}, duration:{frames_duration}, "\ f"fps:{fps}" + if resolution: + + workfile_overrides = get_workfile_overrides(custom_settings) + if workfile_overrides: + override_width = workfile_overrides.get('working_resolution_width') + if override_width: settings["resolutionWidth"] = override_width + override_height = workfile_overrides.get('working_resolution_height') + if override_height: settings["resolutionHeight"] = override_height + width = settings["resolutionWidth"] height = settings["resolutionHeight"] msg += f"width:{width} and height:{height}" diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index b05fffba14c..09f1b1e1b0f 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -2623,8 +2623,7 @@ def set_workfile_overrides(self, custom_settings, project_name): log.warning("Can't retrieve resolution overrides for workfiles. Will not be applied.") return - import re - regex_str = f'(nuke)[\/.]({nuke.NUKE_VERSION_MAJOR})[\\\/\.\-\_\:]({nuke.NUKE_VERSION_MINOR})' + regex_str = f'(nuke)[\/\.]({nuke.NUKE_VERSION_MAJOR})[\\\/\.\-\_\:]({nuke.NUKE_VERSION_MINOR})' application_regex = re.compile(regex_str) overrides_group = None From aafef3e639c3b29c77e251e9c2eee79f185089e4 Mon Sep 17 00:00:00 2001 From: Guilhem Compain Date: Thu, 16 May 2024 11:36:45 +0200 Subject: [PATCH 04/19] #419: enhancement: update regex for nuke --- openpype/hosts/nuke/api/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 09f1b1e1b0f..159fe7f5898 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -2623,7 +2623,7 @@ def set_workfile_overrides(self, custom_settings, project_name): log.warning("Can't retrieve resolution overrides for workfiles. Will not be applied.") return - regex_str = f'(nuke)[\/\.]({nuke.NUKE_VERSION_MAJOR})[\\\/\.\-\_\:]({nuke.NUKE_VERSION_MINOR})' + regex_str = f'(nuke)[\\\/\.\-\_\:]({nuke.NUKE_VERSION_MAJOR})[\\\/\.\-\_\:]({nuke.NUKE_VERSION_MINOR})' application_regex = re.compile(regex_str) overrides_group = None From 9edab19179bd18632cfba7bccdf777cf43543c90 Mon Sep 17 00:00:00 2001 From: Guilhem Compain Date: Thu, 16 May 2024 14:18:47 +0200 Subject: [PATCH 05/19] #419: enhancement: add missing break --- openpype/hosts/aftereffects/api/lib.py | 1 + openpype/hosts/nuke/api/lib.py | 1 + 2 files changed, 2 insertions(+) diff --git a/openpype/hosts/aftereffects/api/lib.py b/openpype/hosts/aftereffects/api/lib.py index 62123c5b5cf..9e9d4be3e01 100644 --- a/openpype/hosts/aftereffects/api/lib.py +++ b/openpype/hosts/aftereffects/api/lib.py @@ -139,6 +139,7 @@ def get_workfile_overrides(custom_settings): match = application_regex.search(application) if match: overrides_group = resolution_overrides_set + break if not overrides_group: log.warning("Can't find overrides group that fit application. Abort.") diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 159fe7f5898..ac8e6d6ceb7 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -2632,6 +2632,7 @@ def set_workfile_overrides(self, custom_settings, project_name): match = application_regex.search(application) if match: overrides_group = resolution_overrides_set + break if not overrides_group: log.warning("Can't find overrides group that fit application. Abort.") From ea745e225163f58d47d4fdac7790526a2a9e3176 Mon Sep 17 00:00:00 2001 From: Guilhem Compain Date: Thu, 16 May 2024 14:30:49 +0200 Subject: [PATCH 06/19] #419: bugfix: moved override group getter in separate function --- openpype/hosts/aftereffects/api/lib.py | 18 ++++++++++-------- openpype/hosts/nuke/api/lib.py | 17 +++++++++-------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/openpype/hosts/aftereffects/api/lib.py b/openpype/hosts/aftereffects/api/lib.py index 9e9d4be3e01..d4843109bf3 100644 --- a/openpype/hosts/aftereffects/api/lib.py +++ b/openpype/hosts/aftereffects/api/lib.py @@ -133,20 +133,22 @@ def get_workfile_overrides(custom_settings): regex_str = f'(aftereffects)[\\\/\.\-\_\:]({app_year}$)' application_regex = re.compile(regex_str) - overrides_group = None - for resolution_overrides_set in resolution_overrides: - for application in resolution_overrides_set.get('applications', []): - match = application_regex.search(application) - if match: - overrides_group = resolution_overrides_set - break - + overrides_group = _get_override_group(resolution_overrides, application_regex) if not overrides_group: log.warning("Can't find overrides group that fit application. Abort.") return overrides_group +def _get_override_group(resolution_overrides, application_regex): + for resolution_overrides_set in resolution_overrides: + for application in resolution_overrides_set.get('applications', []): + match = application_regex.search(application) + if match: return resolution_overrides_set + + return None, None + + def set_settings(frames, resolution, comp_ids=None, print_msg=True): """Sets number of frames and resolution to selected comps. diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index ac8e6d6ceb7..4a88d483c0f 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -2626,13 +2626,7 @@ def set_workfile_overrides(self, custom_settings, project_name): regex_str = f'(nuke)[\\\/\.\-\_\:]({nuke.NUKE_VERSION_MAJOR})[\\\/\.\-\_\:]({nuke.NUKE_VERSION_MINOR})' application_regex = re.compile(regex_str) - overrides_group = None - for resolution_overrides_set in resolution_overrides: - for application in resolution_overrides_set.get('applications', []): - match = application_regex.search(application) - if match: - overrides_group = resolution_overrides_set - break + overrides_group, application_name = self._get_override_group(resolution_overrides, application_regex) if not overrides_group: log.warning("Can't find overrides group that fit application. Abort.") @@ -2644,7 +2638,7 @@ def set_workfile_overrides(self, custom_settings, project_name): "pixel_aspect": asset_data.get( 'pixelAspect', asset_data.get('pixel_aspect', 1)), - "name": f"{project_name}_{match.group()}" + "name": f"{project_name}_{application_name}" } if any(x_ for x_ in format_data.values() if x_ is None): @@ -2675,6 +2669,13 @@ def set_workfile_overrides(self, custom_settings, project_name): nuke.root()["format"].setValue(format_data["name"]) log.info(f"Format is set with values : {format_data}") + def _get_override_group(self, resolution_overrides, application_regex): + for resolution_overrides_set in resolution_overrides: + for application in resolution_overrides_set.get('applications', []): + match = application_regex.search(application) + if match: return resolution_overrides_set, match.group() + + return None, None def set_favorites(self): from .utils import set_context_favorites From d7ba7ec306ae5768d038a66f9f0a27516a21b30c Mon Sep 17 00:00:00 2001 From: Guilhem Compain Date: Thu, 16 May 2024 14:56:19 +0200 Subject: [PATCH 07/19] #419: bugfix: check for nuke environment variables to determine correct application --- openpype/hosts/nuke/api/lib.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 4a88d483c0f..ac00af1afe8 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -2623,7 +2623,10 @@ def set_workfile_overrides(self, custom_settings, project_name): log.warning("Can't retrieve resolution overrides for workfiles. Will not be applied.") return - regex_str = f'(nuke)[\\\/\.\-\_\:]({nuke.NUKE_VERSION_MAJOR})[\\\/\.\-\_\:]({nuke.NUKE_VERSION_MINOR})' + application_name = self._get_application_name() + major_version = nuke.NUKE_VERSION_MAJOR + minor_version = nuke.NUKE_VERSION_MINOR + regex_str = f'({application_name})[\\\/\.\-\_\:]({major_version})[\\\/\.\-\_\:]({minor_version})' application_regex = re.compile(regex_str) overrides_group, application_name = self._get_override_group(resolution_overrides, application_regex) @@ -2669,6 +2672,11 @@ def set_workfile_overrides(self, custom_settings, project_name): nuke.root()["format"].setValue(format_data["name"]) log.info(f"Format is set with values : {format_data}") + def _get_application_name(self): + return "nukex" if nuke.env['nukex'] \ + else "studio" if nuke.env["studio"] \ + else "nuke" + def _get_override_group(self, resolution_overrides, application_regex): for resolution_overrides_set in resolution_overrides: for application in resolution_overrides_set.get('applications', []): From 2b480eab4629658fc25662b11416bec5a8c69694 Mon Sep 17 00:00:00 2001 From: Guilhem Compain Date: Fri, 17 May 2024 15:48:49 +0200 Subject: [PATCH 08/19] #419: enhancement: add custom resolution set button in nuke menu --- openpype/hosts/nuke/api/lib.py | 20 +++++++++++++------- openpype/hosts/nuke/api/pipeline.py | 6 +++++- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index ac00af1afe8..3d19206b403 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -2606,18 +2606,24 @@ def set_context_settings(self): self.set_colorspace() del os.environ["OP_NUKE_SKIP_SAVE_EVENT"] - def set_custom_settings(self,): - project_name = get_current_project_name() - project_settings = get_project_settings(project_name) - - custom_settings = project_settings.get('fix_custom_settings') + def set_custom_resolution(self): + custom_settings = self.get_custom_settings() if not custom_settings: log.warning("Can't access to quad custom settings. Custom settings will not be applied.") return - self.set_workfile_overrides(custom_settings, project_name) + self.set_workfile_overrides( + custom_settings=custom_settings + ) - def set_workfile_overrides(self, custom_settings, project_name): + def get_custom_settings(self): + project_name = get_current_project_name() + project_settings = get_project_settings(project_name) + + return project_settings.get('fix_custom_settings') + + def set_workfile_overrides(self, custom_settings): + project_name = get_current_project_name() resolution_overrides = custom_settings.get("general", []).get("working_resolution_overrides", None) if not resolution_overrides: log.warning("Can't retrieve resolution overrides for workfiles. Will not be applied.") diff --git a/openpype/hosts/nuke/api/pipeline.py b/openpype/hosts/nuke/api/pipeline.py index 420c9d0d32b..9d24c931be8 100644 --- a/openpype/hosts/nuke/api/pipeline.py +++ b/openpype/hosts/nuke/api/pipeline.py @@ -180,7 +180,7 @@ def add_nuke_callbacks(): nuke.addOnScriptLoad(WorkfileSettings().set_context_settings) # set apply all custom settings on script load and save - nuke.addOnScriptLoad(workfile_settings.set_custom_settings) + nuke.addOnScriptLoad(workfile_settings.set_custom_resolution) # Emit events nuke.addOnCreate(_on_scene_open, nodeClass="Root") @@ -306,6 +306,10 @@ def _install_menu(): "Set Resolution", lambda: WorkfileSettings().reset_resolution() ) + menu.addCommand( + "Set Custom Resolution", + lambda: WorkfileSettings().set_custom_resolution() + ) menu.addCommand( "Set Frame Range", lambda: WorkfileSettings().reset_frame_range_handles() From 006a59b450b6ceb8a7ad909da45ab38898204215 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Wed, 22 May 2024 14:43:40 +0200 Subject: [PATCH 09/19] return correct node in _update_renderlayer_instance method (quadproduction/issues#287) --- openpype/hosts/maya/api/plugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/api/plugin.py b/openpype/hosts/maya/api/plugin.py index aefafa90918..21d54a11eb1 100644 --- a/openpype/hosts/maya/api/plugin.py +++ b/openpype/hosts/maya/api/plugin.py @@ -524,9 +524,9 @@ def _update_renderlayer_instance(self, node, layer_name, subset): # Rename the set to match the new layer name set_layer_name = node.split(":")[-1] new_set_name = node.replace(set_layer_name, layer_name) - cmds.rename(node, new_set_name) + renamed_node = cmds.rename(node, new_set_name) - return node + return renamed_node def _create_layer_instance_node(self, layer): # We only collect if a CreateRender instance exists From dbd75c67c72432223736208fb97a9eb05718b3f6 Mon Sep 17 00:00:00 2001 From: ccaillot Date: Wed, 22 May 2024 16:52:23 +0200 Subject: [PATCH 10/19] Add post_placeholder_process when no representation --- openpype/hosts/maya/api/workfile_template_builder.py | 3 ++- openpype/pipeline/workfile/workfile_template_builder.py | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/api/workfile_template_builder.py b/openpype/hosts/maya/api/workfile_template_builder.py index a59ede5828e..c41560dc0ac 100644 --- a/openpype/hosts/maya/api/workfile_template_builder.py +++ b/openpype/hosts/maya/api/workfile_template_builder.py @@ -475,7 +475,8 @@ def post_placeholder_process(self, placeholder, failed): """ node = placeholder.scene_identifier - cmds.sets(node, addElement=PLACEHOLDER_SET) + if cmds.objExists(PLACEHOLDER_SET): + cmds.sets(node, addElement=PLACEHOLDER_SET) cmds.hide(node) cmds.setAttr(node + ".hiddenInOutliner", True) update_instances_frame_range() diff --git a/openpype/pipeline/workfile/workfile_template_builder.py b/openpype/pipeline/workfile/workfile_template_builder.py index eba36418cdd..3e4247eccd0 100644 --- a/openpype/pipeline/workfile/workfile_template_builder.py +++ b/openpype/pipeline/workfile/workfile_template_builder.py @@ -1609,6 +1609,9 @@ def populate_load_placeholder(self, placeholder, ignore_repre_ids=None): self.log.info(( "There's no representation for this placeholder: {}" ).format(placeholder.scene_identifier)) + self.post_placeholder_process(placeholder, failed=True) + if not placeholder.data.get("keep_placeholder", True): + self.delete_placeholder(placeholder) return repre_load_contexts = get_contexts_for_repre_docs( From de918043793fa8008ba63cc97b45bc97bf184cf5 Mon Sep 17 00:00:00 2001 From: BenSouchet Date: Thu, 23 May 2024 15:07:56 +0200 Subject: [PATCH 11/19] Bump version to 3.16.9-quad-1.13.0 --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index fb4e15cf467..fceea89bbfc 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.16.9-quad-1.12.0" +__version__ = "3.16.9-quad-1.13.0" From 1c984dc5b80aa001ffbcdde7418d980b634d7b31 Mon Sep 17 00:00:00 2001 From: Guilhem Compain Date: Tue, 4 Jun 2024 17:12:49 +0200 Subject: [PATCH 12/19] #419: enhancement: aftereffects: deport custom resolution set to dedicated button --- openpype/hosts/aftereffects/api/extension.zxp | Bin 104030 -> 104050 bytes .../aftereffects/api/extension/index.html | 12 +++++++++ .../hosts/aftereffects/api/launch_logic.py | 17 +++++++++++- openpype/hosts/aftereffects/api/lib.py | 25 +++++++++++------- 4 files changed, 43 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/aftereffects/api/extension.zxp b/openpype/hosts/aftereffects/api/extension.zxp index 3ead44ef694f29e90e499a8b65197e59691de4d2..a95b1050f70bc9a2789ea44f6f34ef4599676c1a 100644 GIT binary patch delta 7683 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 SjwS!`Te}AUxJ3eg{QEzxtJU`a delta 7671 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 + +