From 7402662161441640ba10df0d0d47556a3310eae4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 30 Oct 2023 23:12:14 +0100 Subject: [PATCH 01/92] Remove `shelf` class and shelf build on maya `userSetup.py` --- openpype/hosts/maya/api/lib.py | 113 ----------------------- openpype/hosts/maya/startup/userSetup.py | 19 ---- 2 files changed, 132 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 7c49c837e9..f7eaf358fe 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -2921,119 +2921,6 @@ def fix_incompatible_containers(): "ReferenceLoader", type="string") -def _null(*args): - pass - - -class shelf(): - '''A simple class to build shelves in maya. Since the build method is empty, - it should be extended by the derived class to build the necessary shelf - elements. By default it creates an empty shelf called "customShelf".''' - - ########################################################################### - '''This is an example shelf.''' - # class customShelf(_shelf): - # def build(self): - # self.addButon(label="button1") - # self.addButon("button2") - # self.addButon("popup") - # p = cmds.popupMenu(b=1) - # self.addMenuItem(p, "popupMenuItem1") - # self.addMenuItem(p, "popupMenuItem2") - # sub = self.addSubMenu(p, "subMenuLevel1") - # self.addMenuItem(sub, "subMenuLevel1Item1") - # sub2 = self.addSubMenu(sub, "subMenuLevel2") - # self.addMenuItem(sub2, "subMenuLevel2Item1") - # self.addMenuItem(sub2, "subMenuLevel2Item2") - # self.addMenuItem(sub, "subMenuLevel1Item2") - # self.addMenuItem(p, "popupMenuItem3") - # self.addButon("button3") - # customShelf() - ########################################################################### - - def __init__(self, name="customShelf", iconPath="", preset={}): - self.name = name - - self.iconPath = iconPath - - self.labelBackground = (0, 0, 0, 0) - self.labelColour = (.9, .9, .9) - - self.preset = preset - - self._cleanOldShelf() - cmds.setParent(self.name) - self.build() - - def build(self): - '''This method should be overwritten in derived classes to actually - build the shelf elements. Otherwise, nothing is added to the shelf.''' - for item in self.preset['items']: - if not item.get('command'): - item['command'] = self._null - if item['type'] == 'button': - self.addButon(item['name'], - command=item['command'], - icon=item['icon']) - if item['type'] == 'menuItem': - self.addMenuItem(item['parent'], - item['name'], - command=item['command'], - icon=item['icon']) - if item['type'] == 'subMenu': - self.addMenuItem(item['parent'], - item['name'], - command=item['command'], - icon=item['icon']) - - def addButon(self, label, icon="commandButton.png", - command=_null, doubleCommand=_null): - ''' - Adds a shelf button with the specified label, command, - double click command and image. - ''' - cmds.setParent(self.name) - if icon: - icon = os.path.join(self.iconPath, icon) - print(icon) - cmds.shelfButton(width=37, height=37, image=icon, label=label, - command=command, dcc=doubleCommand, - imageOverlayLabel=label, olb=self.labelBackground, - olc=self.labelColour) - - def addMenuItem(self, parent, label, command=_null, icon=""): - ''' - Adds a shelf button with the specified label, command, - double click command and image. - ''' - if icon: - icon = os.path.join(self.iconPath, icon) - print(icon) - return cmds.menuItem(p=parent, label=label, c=command, i="") - - def addSubMenu(self, parent, label, icon=None): - ''' - Adds a sub menu item with the specified label and icon to - the specified parent popup menu. - ''' - if icon: - icon = os.path.join(self.iconPath, icon) - print(icon) - return cmds.menuItem(p=parent, label=label, i=icon, subMenu=1) - - def _cleanOldShelf(self): - ''' - Checks if the shelf exists and empties it if it does - or creates it if it does not. - ''' - if cmds.shelfLayout(self.name, ex=1): - if cmds.shelfLayout(self.name, q=1, ca=1): - for each in cmds.shelfLayout(self.name, q=1, ca=1): - cmds.deleteUI(each) - else: - cmds.shelfLayout(self.name, p="ShelfLayout") - - def _get_render_instances(): """Return all 'render-like' instances. diff --git a/openpype/hosts/maya/startup/userSetup.py b/openpype/hosts/maya/startup/userSetup.py index f2899cdb37..417f72a59f 100644 --- a/openpype/hosts/maya/startup/userSetup.py +++ b/openpype/hosts/maya/startup/userSetup.py @@ -46,24 +46,5 @@ if bool(int(os.environ.get(key, "0"))): lowestPriority=True ) -# Build a shelf. -shelf_preset = settings['maya'].get('project_shelf') -if shelf_preset: - icon_path = os.path.join( - os.environ['OPENPYPE_PROJECT_SCRIPTS'], - project_name, - "icons") - icon_path = os.path.abspath(icon_path) - - for i in shelf_preset['imports']: - import_string = "from {} import {}".format(project_name, i) - print(import_string) - exec(import_string) - - cmds.evalDeferred( - "mlib.shelf(name=shelf_preset['name'], iconPath=icon_path," - " preset=shelf_preset)" - ) - print("Finished OpenPype usersetup.") From 6f2191128dc6b8fc14a6ba419c4f5ffa848aa132 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 11 Dec 2023 10:42:34 +0100 Subject: [PATCH 02/92] Refactor create_editorial.py for better track handling - Refactored the code to filter tracks based on their kind being "Video" - Added a comment to clarify the purpose of media_data variable - Added a comment to explain setting track name - Removed an unnecessary blank line --- .../plugins/create/create_editorial.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index dce4a051fd..e6f29af40f 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -381,15 +381,19 @@ or updating already created. Publishing will create OTIO file. """ self.asset_name_check = [] - tracks = otio_timeline.each_child( - descended_from_type=otio.schema.Track - ) + tracks = [ + track for track in otio_timeline.each_child( + descended_from_type=otio.schema.Track) + if track.kind == "Video" + ] - # media data for audio sream and reference solving + # media data for audio stream and reference solving media_data = self._get_media_source_metadata(media_path) for track in tracks: + # set track name track.name = f"{sequence_file_name} - {otio_timeline.name}" + try: track_start_frame = ( abs(track.source_range.start_time.value) @@ -398,7 +402,6 @@ or updating already created. Publishing will create OTIO file. except AttributeError: track_start_frame = 0 - for clip in track.each_child(): if not self._validate_clip_for_processing(clip): continue From fc179befc24469b39325e0ec12517263cb313410 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 11 Dec 2023 10:43:11 +0100 Subject: [PATCH 03/92] improving plugin label so it is easier to read --- openpype/plugins/publish/validate_resources.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/validate_resources.py b/openpype/plugins/publish/validate_resources.py index 7911c70c2d..ce03515400 100644 --- a/openpype/plugins/publish/validate_resources.py +++ b/openpype/plugins/publish/validate_resources.py @@ -17,7 +17,7 @@ class ValidateResources(pyblish.api.InstancePlugin): """ order = ValidateContentsOrder - label = "Resources" + label = "Validate Resources" def process(self, instance): From 0fc23ce2e7600c335bd05ccc642f742497a36282 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 21 Dec 2023 15:06:31 +0000 Subject: [PATCH 04/92] Changed logic for creation of output node --- openpype/hosts/blender/api/render_lib.py | 70 ++++++++++++++++-------- 1 file changed, 48 insertions(+), 22 deletions(-) diff --git a/openpype/hosts/blender/api/render_lib.py b/openpype/hosts/blender/api/render_lib.py index b437078ad8..cdccf13805 100644 --- a/openpype/hosts/blender/api/render_lib.py +++ b/openpype/hosts/blender/api/render_lib.py @@ -135,71 +135,95 @@ def set_render_passes(settings): return aov_list, custom_passes -def set_node_tree(output_path, name, aov_sep, ext, multilayer): +def set_node_tree(output_path, render_product, name, aov_sep, ext, multilayer): # Set the scene to use the compositor node tree to render bpy.context.scene.use_nodes = True tree = bpy.context.scene.node_tree + comp_layer_type = "CompositorNodeRLayers" + output_type = "CompositorNodeOutputFile" + # Get the Render Layers node rl_node = None for node in tree.nodes: - if node.bl_idname == "CompositorNodeRLayers": + if node.bl_idname == comp_layer_type: rl_node = node break # If there's not a Render Layers node, we create it if not rl_node: - rl_node = tree.nodes.new("CompositorNodeRLayers") + rl_node = tree.nodes.new(comp_layer_type) # Get the enabled output sockets, that are the active passes for the # render. # We also exclude some layers. - exclude_sockets = ["Image", "Alpha", "Noisy Image"] + exclude_sockets = ["Alpha", "Noisy Image"] passes = [ socket for socket in rl_node.outputs if socket.enabled and socket.name not in exclude_sockets ] - # Remove all output nodes + old_output = None + + # Remove all output nodes that inlcude "AYON" in the name. + # There should be only one. for node in tree.nodes: - if node.bl_idname == "CompositorNodeOutputFile": - tree.nodes.remove(node) + if node.bl_idname == output_type and "AYON" in node.name: + old_output = node + break # Create a new output node - output = tree.nodes.new("CompositorNodeOutputFile") + output = tree.nodes.new(output_type) image_settings = bpy.context.scene.render.image_settings output.format.file_format = image_settings.file_format + slots = None + # In case of a multilayer exr, we don't need to use the output node, # because the blender render already outputs a multilayer exr. - if ext == "exr" and multilayer: - output.layer_slots.clear() - return [] + multi_exr = ext == "exr" and multilayer + slots = output.layer_slots if multi_exr else output.file_slots + output.base_path = render_product if multi_exr else str(output_path) - output.file_slots.clear() - output.base_path = str(output_path) + slots.clear() aov_file_products = [] # For each active render pass, we add a new socket to the output node # and link it - for render_pass in passes: - filepath = f"{name}{aov_sep}{render_pass.name}.####" + for rpass in passes: + if rpass.name == "Image": + pass_name = "rgba" if multi_exr else "beauty" + else: + pass_name = rpass.name + filepath = f"{name}{aov_sep}{pass_name}.####" - output.file_slots.new(filepath) + slots.new(pass_name if multi_exr else filepath) filename = str(output_path / filepath.lstrip("/")) - aov_file_products.append((render_pass.name, filename)) + aov_file_products.append((rpass.name, filename)) - node_input = output.inputs[-1] + if not old_output: + node_input = output.inputs[-1] + tree.links.new(rpass, node_input) - tree.links.new(render_pass, node_input) + for link in tree.links: + if link.to_node == old_output: + socket_name = link.to_socket.name + new_socket = output.inputs.get(socket_name) + if new_socket: + tree.links.new(link.from_socket, new_socket) - return aov_file_products + if old_output: + output.location = old_output.location + tree.nodes.remove(old_output) + output.name = "AYON File Output" + + return [] if multi_exr else aov_file_products def imprint_render_settings(node, data): @@ -236,9 +260,11 @@ def prepare_rendering(asset_group): render_product = get_render_product(output_path, name, aov_sep) aov_file_product = set_node_tree( - output_path, name, aov_sep, ext, multilayer) + output_path, render_product, name, aov_sep, ext, multilayer) - bpy.context.scene.render.filepath = render_product + # Clear the render filepath, so that the output is handled only by the + # output node in the compositor. + bpy.context.scene.render.filepath = "" render_settings = { "render_folder": render_folder, From fa5b9777a98cca34f5cd5412500f34825a6ff3d8 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 21 Dec 2023 15:07:00 +0000 Subject: [PATCH 05/92] Create a new version of the workfile instead of overwriting current one --- openpype/hosts/blender/plugins/create/create_render.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/blender/plugins/create/create_render.py b/openpype/hosts/blender/plugins/create/create_render.py index 7fb3e5eb00..e728579286 100644 --- a/openpype/hosts/blender/plugins/create/create_render.py +++ b/openpype/hosts/blender/plugins/create/create_render.py @@ -1,8 +1,10 @@ """Create render.""" import bpy +from openpype.lib import version_up from openpype.hosts.blender.api import plugin from openpype.hosts.blender.api.render_lib import prepare_rendering +from openpype.hosts.blender.api.workio import save_file class CreateRenderlayer(plugin.BaseCreator): @@ -37,6 +39,7 @@ class CreateRenderlayer(plugin.BaseCreator): # settings. Even the validator to check that the file is saved will # detect the file as saved, even if it isn't. The only solution for # now it is to force the file to be saved. - bpy.ops.wm.save_as_mainfile(filepath=bpy.data.filepath) + filepath = version_up(bpy.data.filepath) + save_file(filepath, copy=False) return collection From 44128f77e5eda00ebf91f0d31a631f687424944b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 4 Jan 2024 16:34:58 +0100 Subject: [PATCH 06/92] implemented the same logic to keep version on switch in ayon switch tool --- .../switch_dialog/dialog.py | 45 ++++++++++++++----- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/openpype/tools/ayon_sceneinventory/switch_dialog/dialog.py b/openpype/tools/ayon_sceneinventory/switch_dialog/dialog.py index 2ebed7f89b..fade09305a 100644 --- a/openpype/tools/ayon_sceneinventory/switch_dialog/dialog.py +++ b/openpype/tools/ayon_sceneinventory/switch_dialog/dialog.py @@ -1212,12 +1212,11 @@ class SwitchAssetDialog(QtWidgets.QDialog): )) version_ids = set() - version_docs_by_parent_id = {} + version_docs_by_parent_id_and_name = collections.defaultdict(dict) for version_doc in version_docs: - parent_id = version_doc["parent"] - if parent_id not in version_docs_by_parent_id: - version_ids.add(version_doc["_id"]) - version_docs_by_parent_id[parent_id] = version_doc + subset_id = version_doc["parent"] + name = version_doc["name"] + version_docs_by_parent_id_and_name[subset_id][name] = version_doc hero_version_docs_by_parent_id = {} for hero_version_doc in hero_version_docs: @@ -1242,7 +1241,7 @@ class SwitchAssetDialog(QtWidgets.QDialog): selected_product_name, selected_representation, product_docs_by_parent_and_name, - version_docs_by_parent_id, + version_docs_by_parent_id_and_name, hero_version_docs_by_parent_id, repre_docs_by_parent_id_by_name, ) @@ -1256,10 +1255,10 @@ class SwitchAssetDialog(QtWidgets.QDialog): container, loader, selected_folder_id, - product_name, + selected_product_name, selected_representation, product_docs_by_parent_and_name, - version_docs_by_parent_id, + version_docs_by_parent_id_and_name, hero_version_docs_by_parent_id, repre_docs_by_parent_id_by_name, ): @@ -1272,15 +1271,18 @@ class SwitchAssetDialog(QtWidgets.QDialog): container_product_id = container_version["parent"] container_product = self._product_docs_by_id[container_product_id] + container_product_name = container_product["name"] + + container_folder_id = container_product["parent"] if selected_folder_id: folder_id = selected_folder_id else: - folder_id = container_product["parent"] + folder_id = container_folder_id products_by_name = product_docs_by_parent_and_name[folder_id] - if product_name: - product_doc = products_by_name[product_name] + if selected_product_name: + product_doc = products_by_name[selected_product_name] else: product_doc = products_by_name[container_product["name"]] @@ -1300,7 +1302,26 @@ class SwitchAssetDialog(QtWidgets.QDialog): repre_doc = _repres.get(container_repre_name) if not repre_doc: - version_doc = version_docs_by_parent_id[product_id] + version_docs_by_name = ( + version_docs_by_parent_id_and_name[product_id] + ) + # If asset or subset are selected for switching, we use latest + # version else we try to keep the current container version. + version_name = None + if ( + selected_folder_id in (None, container_folder_id) + and selected_product_name in (None, container_product_name) + ): + version_name = container_version.get("name") + + version_doc = None + if version_name is not None: + version_doc = version_docs_by_name.get(version_name) + + if version_doc is None: + version_name = max(version_docs_by_name) + version_doc = version_docs_by_name[version_name] + version_id = version_doc["_id"] repres_by_name = repre_docs_by_parent_id_by_name[version_id] if selected_representation: From 4444f17892948748d67453132ed81ef2a27ae930 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 4 Jan 2024 16:35:11 +0100 Subject: [PATCH 07/92] make version doc resolving a little bit more safe --- openpype/tools/sceneinventory/switch_dialog.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/openpype/tools/sceneinventory/switch_dialog.py b/openpype/tools/sceneinventory/switch_dialog.py index 150e369678..695f47b4d4 100644 --- a/openpype/tools/sceneinventory/switch_dialog.py +++ b/openpype/tools/sceneinventory/switch_dialog.py @@ -1299,15 +1299,21 @@ class SwitchAssetDialog(QtWidgets.QDialog): # If asset or subset are selected for switching, we use latest # version else we try to keep the current container version. + version_name = None if ( - selected_asset not in (None, container_asset_name) - or selected_subset not in (None, container_subset_name) + selected_asset in (None, container_asset_name) + and selected_subset in (None, container_subset_name) ): - version_name = max(version_docs_by_name) - else: - version_name = container_version["name"] + version_name = container_version.get("name") + + version_doc = None + if version_name is not None: + version_doc = version_docs_by_name.get(version_name) + + if version_doc is None: + version_name = max(version_docs_by_name) + version_doc = version_docs_by_name[version_name] - version_doc = version_docs_by_name[version_name] version_id = version_doc["_id"] repres_docs_by_name = repre_docs_by_parent_id_by_name[ version_id From f6b2b00d10c16c0e42bd5fe736d65c8008783518 Mon Sep 17 00:00:00 2001 From: jrsndlr Date: Fri, 12 Jan 2024 11:46:32 +0100 Subject: [PATCH 08/92] Swapping version fix offset, copy IDT --- openpype/hosts/resolve/api/lib.py | 5 +++++ openpype/hosts/resolve/api/plugin.py | 10 ++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/resolve/api/lib.py b/openpype/hosts/resolve/api/lib.py index 3866477c77..96caacf8d1 100644 --- a/openpype/hosts/resolve/api/lib.py +++ b/openpype/hosts/resolve/api/lib.py @@ -713,6 +713,11 @@ def swap_clips(from_clip, to_clip, to_in_frame, to_out_frame): bool: True if successfully replaced """ + # copy ACES input transform from timeline clip to new media item + mediapool_item_from_timeline = from_clip.GetMediaPoolItem() + _idt = mediapool_item_from_timeline.GetClipProperty('IDT') + to_clip.SetClipProperty('IDT', _idt) + _clip_prop = to_clip.GetClipProperty to_clip_name = _clip_prop("File Name") # add clip item as take to timeline diff --git a/openpype/hosts/resolve/api/plugin.py b/openpype/hosts/resolve/api/plugin.py index a00933405f..2346739e20 100644 --- a/openpype/hosts/resolve/api/plugin.py +++ b/openpype/hosts/resolve/api/plugin.py @@ -477,14 +477,16 @@ class ClipLoader: ) _clip_property = media_pool_item.GetClipProperty - source_in = int(_clip_property("Start")) - source_out = int(_clip_property("End")) + # Read trimming from timeline item + timeline_item_in = timeline_item.GetLeftOffset() + timeline_item_len = timeline_item.GetDuration() + timeline_item_out = timeline_item_in + timeline_item_len lib.swap_clips( timeline_item, media_pool_item, - source_in, - source_out + timeline_item_in, + timeline_item_out ) print("Loading clips: `{}`".format(self.data["clip_name"])) From 0534c9c55d9eb5d0f193b2c64c3a870b6082a092 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 18 Jan 2024 10:13:24 +0000 Subject: [PATCH 09/92] Fix error when getting setting value in Ayon --- openpype/hosts/blender/api/render_lib.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/blender/api/render_lib.py b/openpype/hosts/blender/api/render_lib.py index cdccf13805..904cabfc05 100644 --- a/openpype/hosts/blender/api/render_lib.py +++ b/openpype/hosts/blender/api/render_lib.py @@ -2,6 +2,7 @@ from pathlib import Path import bpy +from openpype import AYON_SERVER_ENABLED from openpype.settings import get_project_settings from openpype.pipeline import get_current_project_name @@ -124,13 +125,14 @@ def set_render_passes(settings): aovs_names = [aov.name for aov in vl.aovs] for cp in custom_passes: - cp_name = cp[0] + cp_name = cp["attribute"] if AYON_SERVER_ENABLED else cp[0] if cp_name not in aovs_names: aov = vl.aovs.add() aov.name = cp_name else: aov = vl.aovs[cp_name] - aov.type = cp[1].get("type", "VALUE") + aov.type = (cp["value"] + if AYON_SERVER_ENABLED else cp[1].get("type", "VALUE")) return aov_list, custom_passes From 1eb07bcedb97edef9bbf57fa7c605bf85794a33f Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 18 Jan 2024 10:16:05 +0000 Subject: [PATCH 10/92] Added setting to choose render engine --- openpype/hosts/blender/api/render_lib.py | 11 ++++++++++- .../blender/server/settings/render_settings.py | 13 +++++++++++++ server_addon/blender/server/version.py | 2 +- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/blender/api/render_lib.py b/openpype/hosts/blender/api/render_lib.py index 904cabfc05..06353f8e8b 100644 --- a/openpype/hosts/blender/api/render_lib.py +++ b/openpype/hosts/blender/api/render_lib.py @@ -48,6 +48,14 @@ def get_multilayer(settings): ["multilayer_exr"]) +def get_renderer(settings): + """Get renderer from blender settings.""" + + return (settings["blender"] + ["RenderSettings"] + ["renderer"]) + + def get_render_product(output_path, name, aov_sep): """ Generate the path to the render product. Blender interprets the `#` @@ -254,9 +262,10 @@ def prepare_rendering(asset_group): aov_sep = get_aov_separator(settings) ext = get_image_format(settings) multilayer = get_multilayer(settings) + renderer = get_renderer(settings) set_render_format(ext, multilayer) - aov_list, custom_passes = set_render_passes(settings) + bpy.context.scene.render.engine = renderer output_path = Path.joinpath(dirpath, render_folder, file_name) diff --git a/server_addon/blender/server/settings/render_settings.py b/server_addon/blender/server/settings/render_settings.py index f62013982e..53cefd145d 100644 --- a/server_addon/blender/server/settings/render_settings.py +++ b/server_addon/blender/server/settings/render_settings.py @@ -25,6 +25,13 @@ def image_format_enum(): ] +def renderers_enum(): + return [ + {"value": "CYCLES", "label": "Cycles"}, + {"value": "BLENDER_EEVEE", "label": "Eevee"}, + ] + + def aov_list_enum(): return [ {"value": "empty", "label": "< none >"}, @@ -83,6 +90,11 @@ class RenderSettingsModel(BaseSettingsModel): multilayer_exr: bool = Field( title="Multilayer (EXR)" ) + renderer: str = Field( + "CYCLES", + title="Renderer", + enum_resolver=renderers_enum + ) aov_list: list[str] = Field( default_factory=list, enum_resolver=aov_list_enum, @@ -104,6 +116,7 @@ DEFAULT_RENDER_SETTINGS = { "aov_separator": "underscore", "image_format": "exr", "multilayer_exr": True, + "renderer": "CYCLES", "aov_list": [], "custom_passes": [] } diff --git a/server_addon/blender/server/version.py b/server_addon/blender/server/version.py index 1276d0254f..0a8da88258 100644 --- a/server_addon/blender/server/version.py +++ b/server_addon/blender/server/version.py @@ -1 +1 @@ -__version__ = "0.1.5" +__version__ = "0.1.6" From 2f58708f584a63a68ffcbe2c429704dca566f43b Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 18 Jan 2024 10:16:31 +0000 Subject: [PATCH 11/92] Updated aov list --- openpype/hosts/blender/api/render_lib.py | 62 ++++++++++++++----- .../server/settings/render_settings.py | 50 ++++++++++++--- 2 files changed, 90 insertions(+), 22 deletions(-) diff --git a/openpype/hosts/blender/api/render_lib.py b/openpype/hosts/blender/api/render_lib.py index 06353f8e8b..44ee2be208 100644 --- a/openpype/hosts/blender/api/render_lib.py +++ b/openpype/hosts/blender/api/render_lib.py @@ -100,36 +100,69 @@ def set_render_format(ext, multilayer): image_settings.file_format = "TIFF" -def set_render_passes(settings): - aov_list = (settings["blender"] - ["RenderSettings"] - ["aov_list"]) - - custom_passes = (settings["blender"] - ["RenderSettings"] - ["custom_passes"]) +def set_render_passes(settings, renderer): + aov_list = settings["blender"]["RenderSettings"]["aov_list"] + custom_passes = settings["blender"]["RenderSettings"]["custom_passes"] + # Common passes for both renderers vl = bpy.context.view_layer + # Data Passes vl.use_pass_combined = "combined" in aov_list vl.use_pass_z = "z" in aov_list vl.use_pass_mist = "mist" in aov_list vl.use_pass_normal = "normal" in aov_list + + # Light Passes vl.use_pass_diffuse_direct = "diffuse_light" in aov_list vl.use_pass_diffuse_color = "diffuse_color" in aov_list vl.use_pass_glossy_direct = "specular_light" in aov_list vl.use_pass_glossy_color = "specular_color" in aov_list - vl.eevee.use_pass_volume_direct = "volume_light" in aov_list vl.use_pass_emit = "emission" in aov_list vl.use_pass_environment = "environment" in aov_list - vl.use_pass_shadow = "shadow" in aov_list vl.use_pass_ambient_occlusion = "ao" in aov_list - cycles = vl.cycles + # Cryptomatte Passes + vl.use_pass_cryptomatte_object = "cryptomatte_object" in aov_list + vl.use_pass_cryptomatte_material = "cryptomatte_material" in aov_list + vl.use_pass_cryptomatte_asset = "cryptomatte_asset" in aov_list - cycles.denoising_store_passes = "denoising" in aov_list - cycles.use_pass_volume_direct = "volume_direct" in aov_list - cycles.use_pass_volume_indirect = "volume_indirect" in aov_list + if renderer == "BLENDER_EEVEE": + # Eevee exclusive passes + eevee = vl.eevee + + # Light Passes + vl.use_pass_shadow = "shadow" in aov_list + eevee.use_pass_volume_direct = "volume_light" in aov_list + + # Effects Passes + eevee.use_pass_bloom = "bloom" in aov_list + eevee.use_pass_transparent = "transparent" in aov_list + + # Cryptomatte Passes + vl.use_pass_cryptomatte_accurate = "cryptomatte_accurate" in aov_list + elif renderer == "CYCLES": + # Cycles exclusive passes + cycles = vl.cycles + + # Data Passes + vl.use_pass_position = "position" in aov_list + vl.use_pass_vector = "vector" in aov_list + vl.use_pass_uv = "uv" in aov_list + cycles.denoising_store_passes = "denoising" in aov_list + vl.use_pass_object_index = "object_index" in aov_list + vl.use_pass_material_index = "material_index" in aov_list + cycles.pass_debug_sample_count = "sample_count" in aov_list + + # Light Passes + vl.use_pass_diffuse_indirect = "diffuse_indirect" in aov_list + vl.use_pass_glossy_indirect = "specular_indirect" in aov_list + vl.use_pass_transmission_direct = "transmission_direct" in aov_list + vl.use_pass_transmission_indirect = "transmission_indirect" in aov_list + vl.use_pass_transmission_color = "transmission_color" in aov_list + cycles.use_pass_volume_direct = "volume_light" in aov_list + cycles.use_pass_volume_indirect = "volume_indirect" in aov_list + cycles.use_pass_shadow_catcher = "shadow" in aov_list aovs_names = [aov.name for aov in vl.aovs] for cp in custom_passes: @@ -266,6 +299,7 @@ def prepare_rendering(asset_group): set_render_format(ext, multilayer) bpy.context.scene.render.engine = renderer + aov_list, custom_passes = set_render_passes(settings, renderer) output_path = Path.joinpath(dirpath, render_folder, file_name) diff --git a/server_addon/blender/server/settings/render_settings.py b/server_addon/blender/server/settings/render_settings.py index 53cefd145d..3ab720fc6a 100644 --- a/server_addon/blender/server/settings/render_settings.py +++ b/server_addon/blender/server/settings/render_settings.py @@ -39,18 +39,52 @@ def aov_list_enum(): {"value": "z", "label": "Z"}, {"value": "mist", "label": "Mist"}, {"value": "normal", "label": "Normal"}, - {"value": "diffuse_light", "label": "Diffuse Light"}, + {"value": "position", "label": "Position (Cycles Only)"}, + {"value": "vector", "label": "Vector (Cycles Only)"}, + {"value": "uv", "label": "UV (Cycles Only)"}, + {"value": "denoising", "label": "Denoising Data (Cycles Only)"}, + {"value": "object_index", "label": "Object Index (Cycles Only)"}, + {"value": "material_index", "label": "Material Index (Cycles Only)"}, + {"value": "sample_count", "label": "Sample Count (Cycles Only)"}, + {"value": "diffuse_light", "label": "Diffuse Light/Direct"}, + { + "value": "diffuse_indirect", + "label": "Diffuse Indirect (Cycles Only)" + }, {"value": "diffuse_color", "label": "Diffuse Color"}, - {"value": "specular_light", "label": "Specular Light"}, - {"value": "specular_color", "label": "Specular Color"}, - {"value": "volume_light", "label": "Volume Light"}, + {"value": "specular_light", "label": "Specular (Glossy) Light/Direct"}, + { + "value": "specular_indirect", + "label": "Specular (Glossy) Indirect (Cycles Only)" + }, + {"value": "specular_color", "label": "Specular (Glossy) Color"}, + { + "value": "transmission_light", + "label": "Transmission Light/Direct (Cycles Only)" + }, + { + "value": "transmission_indirect", + "label": "Transmission Indirect (Cycles Only)" + }, + { + "value": "transmission_color", + "label": "Transmission Color (Cycles Only)" + }, + {"value": "volume_light", "label": "Volume Light/Direct"}, + {"value": "volume_indirect", "label": "Volume Indirect (Cycles Only)"}, {"value": "emission", "label": "Emission"}, {"value": "environment", "label": "Environment"}, - {"value": "shadow", "label": "Shadow"}, + {"value": "shadow", "label": "Shadow/Shadow Catcher"}, {"value": "ao", "label": "Ambient Occlusion"}, - {"value": "denoising", "label": "Denoising"}, - {"value": "volume_direct", "label": "Direct Volumetric Scattering"}, - {"value": "volume_indirect", "label": "Indirect Volumetric Scattering"} + {"value": "bloom", "label": "Bloom (Eevee Only)"}, + {"value": "transparent", "label": "Transparent (Eevee Only)"}, + {"value": "cryptomatte_object", "label": "Cryptomatte Object"}, + {"value": "cryptomatte_material", "label": "Cryptomatte Material"}, + {"value": "cryptomatte_asset", "label": "Cryptomatte Asset"}, + { + "value": "cryptomatte_accurate", + "label": "Cryptomatte Accurate Mode (Eevee Only)" + }, ] From 91f67ca9b0a199e110650fd899d562eae3cdcd16 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Fri, 19 Jan 2024 10:00:58 +0000 Subject: [PATCH 12/92] Updated default settings for render passes --- server_addon/blender/server/settings/render_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/blender/server/settings/render_settings.py b/server_addon/blender/server/settings/render_settings.py index 3ab720fc6a..580547e510 100644 --- a/server_addon/blender/server/settings/render_settings.py +++ b/server_addon/blender/server/settings/render_settings.py @@ -151,6 +151,6 @@ DEFAULT_RENDER_SETTINGS = { "image_format": "exr", "multilayer_exr": True, "renderer": "CYCLES", - "aov_list": [], + "aov_list": ["combined"], "custom_passes": [] } From dd0533ecb36b19988ccb67b0b2e83a50f7090be1 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Fri, 19 Jan 2024 10:03:05 +0000 Subject: [PATCH 13/92] Added composite output --- openpype/hosts/blender/api/render_lib.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/blender/api/render_lib.py b/openpype/hosts/blender/api/render_lib.py index 44ee2be208..fc47f5a659 100644 --- a/openpype/hosts/blender/api/render_lib.py +++ b/openpype/hosts/blender/api/render_lib.py @@ -186,12 +186,17 @@ def set_node_tree(output_path, render_product, name, aov_sep, ext, multilayer): comp_layer_type = "CompositorNodeRLayers" output_type = "CompositorNodeOutputFile" + compositor_type = "CompositorNodeComposite" - # Get the Render Layers node + # Get the Render Layer and Composite nodes rl_node = None + comp_node = None for node in tree.nodes: if node.bl_idname == comp_layer_type: rl_node = node + elif node.bl_idname == compositor_type: + comp_node = node + if rl_node and comp_node: break # If there's not a Render Layers node, we create it @@ -242,29 +247,38 @@ def set_node_tree(output_path, render_product, name, aov_sep, ext, multilayer): pass_name = "rgba" if multi_exr else "beauty" else: pass_name = rpass.name - filepath = f"{name}{aov_sep}{pass_name}.####" + filename = f"{name}{aov_sep}{pass_name}.####" - slots.new(pass_name if multi_exr else filepath) + slots.new(pass_name if multi_exr else filename) - filename = str(output_path / filepath.lstrip("/")) + filepath = str(output_path / filename.lstrip("/")) - aov_file_products.append((rpass.name, filename)) + aov_file_products.append((rpass.name, filepath)) if not old_output: node_input = output.inputs[-1] tree.links.new(rpass, node_input) + # Create a new socket for the composite output + pass_name = "composite" + filename = f"{name}{aov_sep}{pass_name}.####" + comp_socket = slots.new(pass_name if multi_exr else filename) + aov_file_products.append(("Composite", filepath)) + for link in tree.links: if link.to_node == old_output: socket_name = link.to_socket.name new_socket = output.inputs.get(socket_name) if new_socket: tree.links.new(link.from_socket, new_socket) + elif comp_node and link.to_node == comp_node: + tree.links.new(link.from_socket, comp_socket) if old_output: output.location = old_output.location tree.nodes.remove(old_output) output.name = "AYON File Output" + output.label = "AYON File Output" return [] if multi_exr else aov_file_products From c911c67dae109a3c769fa4fe584f925f3afe7a2e Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Fri, 19 Jan 2024 11:04:25 +0000 Subject: [PATCH 14/92] Updated OpenPype Settings --- .../defaults/project_settings/blender.json | 3 +- .../schema_project_blender.json | 43 +++++++++++++++---- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/openpype/settings/defaults/project_settings/blender.json b/openpype/settings/defaults/project_settings/blender.json index 385e97ef91..48f3ef8ef0 100644 --- a/openpype/settings/defaults/project_settings/blender.json +++ b/openpype/settings/defaults/project_settings/blender.json @@ -22,7 +22,8 @@ "aov_separator": "underscore", "image_format": "exr", "multilayer_exr": true, - "aov_list": [], + "renderer": "CYCLES", + "aov_list": ["combined"], "custom_passes": [] }, "workfile_builder": { diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json b/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json index 535d9434a3..bbed881ab0 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json @@ -103,6 +103,17 @@ "type": "label", "label": "Note: Multilayer EXR is only used when output format type set to EXR." }, + { + "key": "renderer", + "label": "Renderer", + "type": "enum", + "multiselection": false, + "defaults": "CYCLES", + "enum_items": [ + {"CYCLES": "Cycles"}, + {"BLENDER_EEVEE": "Eevee"} + ] + }, { "key": "aov_list", "label": "AOVs to create", @@ -115,18 +126,34 @@ {"z": "Z"}, {"mist": "Mist"}, {"normal": "Normal"}, - {"diffuse_light": "Diffuse Light"}, + {"position": "Position (Cycles Only)"}, + {"vector": "Vector (Cycles Only)"}, + {"uv": "UV (Cycles Only)"}, + {"denoising": "Denoising Data (Cycles Only)"}, + {"object_index": "Object Index (Cycles Only)"}, + {"material_index": "Material Index (Cycles Only)"}, + {"sample_count": "Sample Count (Cycles Only)"}, + {"diffuse_light": "Diffuse Light/Direct"}, + {"diffuse_indirect": "Diffuse Indirect (Cycles Only)"}, {"diffuse_color": "Diffuse Color"}, - {"specular_light": "Specular Light"}, - {"specular_color": "Specular Color"}, - {"volume_light": "Volume Light"}, + {"specular_light": "Specular (Glossy) Light/Direct"}, + {"specular_indirect": "Specular (Glossy) Indirect (Cycles Only)"}, + {"specular_color": "Specular (Glossy) Color"}, + {"transmission_light": "Transmission Light/Direct (Cycles Only)"}, + {"transmission_indirect": "Transmission Indirect (Cycles Only)"}, + {"transmission_color": "Transmission Color (Cycles Only)"}, + {"volume_light": "Volume Light/Direct"}, + {"volume_indirect": "Volume Indirect (Cycles Only)"}, {"emission": "Emission"}, {"environment": "Environment"}, - {"shadow": "Shadow"}, + {"shadow": "Shadow/Shadow Catcher"}, {"ao": "Ambient Occlusion"}, - {"denoising": "Denoising"}, - {"volume_direct": "Direct Volumetric Scattering"}, - {"volume_indirect": "Indirect Volumetric Scattering"} + {"bloom": "Bloom (Eevee Only)"}, + {"transparent": "Transparent (Eevee Only)"}, + {"cryptomatte_object": "Cryptomatte Object"}, + {"cryptomatte_material": "Cryptomatte Material"}, + {"cryptomatte_asset": "Cryptomatte Asset"}, + {"cryptomatte_accurate": "Cryptomatte Accurate Mode (Eevee Only)"} ] }, { From 90fc4e27167052b0fd091b038ebc4593870c0668 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Nov 2023 15:21:30 +0100 Subject: [PATCH 15/92] implemented base of click wrapper --- openpype/click_wrap.py | 371 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 371 insertions(+) create mode 100644 openpype/click_wrap.py diff --git a/openpype/click_wrap.py b/openpype/click_wrap.py new file mode 100644 index 0000000000..218825bf04 --- /dev/null +++ b/openpype/click_wrap.py @@ -0,0 +1,371 @@ +"""Simplified wrapper for 'click' python module. + +Module 'click' is used as main cli handler in AYON/OpenPype. Addons can +register their own subcommands with options. This wrapper allows to define +commands and options as with 'click', but without any dependency. + +Why not to use 'click' directly? Version of 'click' used in AYON/OpenPype +is not compatible with 'click' version used in some DCCs (e.g. Houdini 20+). +And updating 'click' would break other DCCs. + +How to use it? If you already have cli commands defined in addon, just replace +'click' with 'click_wrap' and it should work and modify your addon's cli +method to convert 'click_wrap' object to 'click' object. + +# Before +```python +import click +from openpype.modules import OpenPypeModule + + +class ExampleAddon(OpenPypeModule): + name = "example" + + def cli(self, click_group): + click_group.add_command(cli_main) + + +@click.group(ExampleAddon.name, help="Example addon") +def cli_main(): + pass + + +@cli_main.command(help="Example command") +@click.option("--arg1", help="Example argument 1", default="default1") +@click.option("--arg2", help="Example argument 2", is_flag=True) +def mycommand(arg1, arg2): + print(arg1, arg2) +``` + +# Now +``` +from openpype import click_wrap +from openpype.modules import OpenPypeModule + + +class ExampleAddon(OpenPypeModule): + name = "example" + + def cli(self, click_group): + click_group.add_command(cli_main.to_click_obj()) + + +@click_wrap.group(ExampleAddon.name, help="Example addon") +def cli_main(): + pass + + +@cli_main.command(help="Example command") +@click_wrap.option("--arg1", help="Example argument 1", default="default1") +@click_wrap.option("--arg2", help="Example argument 2", is_flag=True) +def mycommand(arg1, arg2): + print(arg1, arg2) +``` + + +Added small enhancements: +- most of the methods can be used as chained calls +- functions/methods 'command' and 'group' can be used in a way that + first argument is callback function and the rest are arguments + for click + +Example: + ```python + from openpype import click_wrap + from openpype.modules import OpenPypeModule + + + class ExampleAddon(OpenPypeModule): + name = "example" + + def cli(self, click_group): + # Define main command (name 'example') + main = click_wrap.group( + self._cli_main, name=self.name, help="Example addon" + ) + # Add subcommand (name 'mycommand') + ( + main.command( + self._cli_command, name="mycommand", help="Example command" + ) + .option( + "--arg1", help="Example argument 1", default="default1" + ) + .option( + "--arg2", help="Example argument 2", is_flag=True, + ) + ) + # Convert main command to click object and add it to parent group + click_group.add_command(main.to_click_obj()) + + def _cli_main(self): + pass + + def _cli_command(self, arg1, arg2): + print(arg1, arg2) + ``` + + ```shell + openpype_console addon example mycommand --arg1 value1 --arg2 + ``` +""" + +import collections + +FUNC_ATTR_NAME = "__ayon_cli_options__" + + +class Command(object): + def __init__(self, func, *args, **kwargs): + # Command function + self._func = func + # Command definition arguments + self._args = args + # Command definition kwargs + self._kwargs = kwargs + # Both 'options' and 'arguments' are stored to the same variable + # - keep order of options and arguments + self._options = getattr(func, FUNC_ATTR_NAME, []) + + def to_click_obj(self): + """Converts this object to click object. + + Returns: + click.Command: Click command object. + """ + + return convert_to_click(self) + + # --- Methods for 'convert_to_click' function --- + def get_args(self): + """ + Returns: + tuple: Command definition arguments. + """ + + return self._args + + def get_kwargs(self): + """ + Returns: + dict[str, Any]: Command definition kwargs. + """ + + return self._kwargs + + def get_func(self): + """ + Returns: + Function: Function to invoke on command trigger. + """ + + return self._func + + def iter_options(self): + """ + Yields: + tuple[str, tuple, dict]: Option type name with args and kwargs. + """ + + for item in self._options: + yield item + # ----------------------------------------------- + + def add_option(self, *args, **kwargs): + return self.add_option_by_type("option", *args, **kwargs) + + def add_argument(self, *args, **kwargs): + return self.add_option_by_type("argument", *args, **kwargs) + + option = add_option + argument = add_argument + + def add_option_by_type(self, option_name, *args, **kwargs): + self._options.append((option_name, args, kwargs)) + return self + + +class Group(Command): + def __init__(self, func, *args, **kwargs): + super(Group, self).__init__(func, *args, **kwargs) + # Store sub-groupd and sub-commands to the same variable + self._commands = [] + + # --- Methods for 'convert_to_click' function --- + def iter_commands(self): + for command in self._commands: + yield command + # ----------------------------------------------- + + def add_command(self, command): + """Add prepared command object as child. + + Args: + command (Command): Prepared command object. + """ + + if command not in self._commands: + self._commands.append(command) + + def add_group(self, group): + """Add prepared group object as child. + + Args: + group (Group): Prepared group object. + """ + + if group not in self._commands: + self._commands.append(group) + + def command(self, *args, **kwargs): + """Add child command. + + Returns: + Union[Command, Function]: New command object, or wrapper function. + """ + + return self._add_new(Command, *args, **kwargs) + + def group(self, *args, **kwargs): + """Add child group. + + Returns: + Union[Group, Function]: New group object, or wrapper function. + """ + + return self._add_new(Group, *args, **kwargs) + + def _add_new(self, target_cls, *args, **kwargs): + func = None + if args and callable(args[0]): + args = list(args) + func = args.pop(0) + args = tuple(args) + + def decorator(_func): + out = target_cls(_func, *args, **kwargs) + self._commands.append(out) + return out + + if func is not None: + return decorator(func) + return decorator + + +def convert_to_click(obj_to_convert): + """Convert wrapped object to click object. + + Args: + obj_to_convert (Command): Object to convert to click object. + + Returns: + click.Command: Click command object. + """ + + import click + + commands_queue = collections.deque() + commands_queue.append((obj_to_convert, None)) + top_obj = None + while commands_queue: + item = commands_queue.popleft() + command_obj, parent_obj = item + if not isinstance(command_obj, Command): + raise TypeError( + "Invalid type '{}' expected 'Command'".format(type(command_obj)) + ) + + if isinstance(command_obj, Group): + click_obj = ( + click.group( + *command_obj.get_args(), + **command_obj.get_kwargs() + )(command_obj.get_func()) + ) + + else: + click_obj = ( + click.command( + *command_obj.get_args(), + **command_obj.get_kwargs() + )(command_obj.get_func()) + ) + + for item in command_obj.iter_options(): + option_name, args, kwargs = item + if option_name == "option": + click.option(*args, **kwargs)(click_obj) + elif option_name == "argument": + click.argument(*args, **kwargs)(click_obj) + else: + raise ValueError("Invalid option name '{}'".format(option_name)) + + if top_obj is None: + top_obj = click_obj + + if parent_obj is not None: + parent_obj.add_command(click_obj) + + if isinstance(command_obj, Group): + for command in command_obj.iter_commands(): + commands_queue.append((command, click_obj)) + + return top_obj + + +def group(*args, **kwargs): + func = None + if args and callable(args[0]): + args = list(args) + func = args.pop(0) + args = tuple(args) + + def decorator(_func): + return Group(_func, *args, **kwargs) + + if func is not None: + return decorator(func) + return decorator + + +def command(*args, **kwargs): + func = None + if args and callable(args[0]): + args = list(args) + func = args.pop(0) + args = tuple(args) + + def decorator(_func): + return Command(_func, *args, **kwargs) + + if func is not None: + return decorator(func) + return decorator + + +def argument(*args, **kwargs): + def decorator(func): + return _add_option_to_func( + func, "argument", *args, **kwargs + ) + return decorator + + +def option(*args, **kwargs): + def decorator(func): + return _add_option_to_func( + func, "option", *args, **kwargs + ) + return decorator + + +def _add_option_to_func(func, option_name, *args, **kwargs): + if isinstance(func, Command): + func.add_option_by_type(option_name, *args, **kwargs) + return func + + if not hasattr(func, FUNC_ATTR_NAME): + setattr(func, FUNC_ATTR_NAME, []) + cli_options = getattr(func, FUNC_ATTR_NAME) + cli_options.append((option_name, args, kwargs)) + return func From 83c5e7d0de4a0779ce3e1ea3a2c86064f750cd94 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Nov 2023 15:22:24 +0100 Subject: [PATCH 16/92] use 'click_wrap' in ftrack --- openpype/modules/ftrack/ftrack_module.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/openpype/modules/ftrack/ftrack_module.py b/openpype/modules/ftrack/ftrack_module.py index b5152ff9c4..c7df45d6a4 100644 --- a/openpype/modules/ftrack/ftrack_module.py +++ b/openpype/modules/ftrack/ftrack_module.py @@ -3,8 +3,7 @@ import json import collections import platform -import click - +from openpype import click_wrap from openpype.modules import ( OpenPypeModule, ITrayModule, @@ -489,7 +488,7 @@ class FtrackModule( return cred.get("username"), cred.get("api_key") def cli(self, click_group): - click_group.add_command(cli_main) + click_group.add_command(main.to_click_obj()) def _check_ftrack_url(url): @@ -540,24 +539,24 @@ def resolve_ftrack_url(url, logger=None): return ftrack_url -@click.group(FtrackModule.name, help="Ftrack module related commands.") +@click_wrap.group(FtrackModule.name, help="Ftrack module related commands.") def cli_main(): pass @cli_main.command() -@click.option("-d", "--debug", is_flag=True, help="Print debug messages") -@click.option("--ftrack-url", envvar="FTRACK_SERVER", +@click_wrap.option("-d", "--debug", is_flag=True, help="Print debug messages") +@click_wrap.option("--ftrack-url", envvar="FTRACK_SERVER", help="Ftrack server url") -@click.option("--ftrack-user", envvar="FTRACK_API_USER", +@click_wrap.option("--ftrack-user", envvar="FTRACK_API_USER", help="Ftrack api user") -@click.option("--ftrack-api-key", envvar="FTRACK_API_KEY", +@click_wrap.option("--ftrack-api-key", envvar="FTRACK_API_KEY", help="Ftrack api key") -@click.option("--legacy", is_flag=True, +@click_wrap.option("--legacy", is_flag=True, help="run event server without mongo storing") -@click.option("--clockify-api-key", envvar="CLOCKIFY_API_KEY", +@click_wrap.option("--clockify-api-key", envvar="CLOCKIFY_API_KEY", help="Clockify API key.") -@click.option("--clockify-workspace", envvar="CLOCKIFY_WORKSPACE", +@click_wrap.option("--clockify-workspace", envvar="CLOCKIFY_WORKSPACE", help="Clockify workspace") def eventserver( debug, From ade8d4d47cfa45780a63b9d376bdb1fe059a0c83 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Nov 2023 16:28:29 +0100 Subject: [PATCH 17/92] small fixes --- openpype/click_wrap.py | 8 ++++++-- openpype/modules/ftrack/ftrack_module.py | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/openpype/click_wrap.py b/openpype/click_wrap.py index 218825bf04..3db5037b2f 100644 --- a/openpype/click_wrap.py +++ b/openpype/click_wrap.py @@ -272,7 +272,9 @@ def convert_to_click(obj_to_convert): command_obj, parent_obj = item if not isinstance(command_obj, Command): raise TypeError( - "Invalid type '{}' expected 'Command'".format(type(command_obj)) + "Invalid type '{}' expected 'Command'".format( + type(command_obj) + ) ) if isinstance(command_obj, Group): @@ -298,7 +300,9 @@ def convert_to_click(obj_to_convert): elif option_name == "argument": click.argument(*args, **kwargs)(click_obj) else: - raise ValueError("Invalid option name '{}'".format(option_name)) + raise ValueError( + "Invalid option name '{}'".format(option_name) + ) if top_obj is None: top_obj = click_obj diff --git a/openpype/modules/ftrack/ftrack_module.py b/openpype/modules/ftrack/ftrack_module.py index c7df45d6a4..ed48b170a1 100644 --- a/openpype/modules/ftrack/ftrack_module.py +++ b/openpype/modules/ftrack/ftrack_module.py @@ -488,7 +488,7 @@ class FtrackModule( return cred.get("username"), cred.get("api_key") def cli(self, click_group): - click_group.add_command(main.to_click_obj()) + click_group.add_command(cli_main.to_click_obj()) def _check_ftrack_url(url): From eb7d264900b61892b843a68ae5238c6538795c60 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 22 Jan 2024 17:48:58 +0100 Subject: [PATCH 18/92] moved 'click_wrap.py' to './modules' --- openpype/modules/__init__.py | 3 +++ openpype/{ => modules}/click_wrap.py | 0 2 files changed, 3 insertions(+) rename openpype/{ => modules}/click_wrap.py (100%) diff --git a/openpype/modules/__init__.py b/openpype/modules/__init__.py index 3097805353..87f3233afc 100644 --- a/openpype/modules/__init__.py +++ b/openpype/modules/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from . import click_wrap from .interfaces import ( ILaunchHookPaths, IPluginPaths, @@ -28,6 +29,8 @@ from .base import ( __all__ = ( + "click_wrap", + "ILaunchHookPaths", "IPluginPaths", "ITrayModule", diff --git a/openpype/click_wrap.py b/openpype/modules/click_wrap.py similarity index 100% rename from openpype/click_wrap.py rename to openpype/modules/click_wrap.py From 159a2d1dbc83f82f2c7c7fec507b5d436e25bddd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 22 Jan 2024 17:49:52 +0100 Subject: [PATCH 19/92] use new click_wrap in existing openpype modules --- openpype/hosts/standalonepublisher/addon.py | 13 ++++--- openpype/hosts/traypublisher/addon.py | 15 +++++--- openpype/hosts/webpublisher/addon.py | 34 +++++++++---------- .../example_addons/example_addon/addon.py | 6 ++-- openpype/modules/ftrack/ftrack_module.py | 2 +- openpype/modules/job_queue/module.py | 15 ++++---- openpype/modules/kitsu/kitsu_module.py | 18 +++++----- .../modules/sync_server/sync_server_module.py | 16 ++++++--- 8 files changed, 65 insertions(+), 54 deletions(-) diff --git a/openpype/hosts/standalonepublisher/addon.py b/openpype/hosts/standalonepublisher/addon.py index 67204b581b..607c4ecdae 100644 --- a/openpype/hosts/standalonepublisher/addon.py +++ b/openpype/hosts/standalonepublisher/addon.py @@ -1,10 +1,13 @@ import os -import click - from openpype.lib import get_openpype_execute_args from openpype.lib.execute import run_detached_process -from openpype.modules import OpenPypeModule, ITrayAction, IHostAddon +from openpype.modules import ( + click_wrap, + OpenPypeModule, + ITrayAction, + IHostAddon, +) STANDALONEPUBLISH_ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -37,10 +40,10 @@ class StandAlonePublishAddon(OpenPypeModule, ITrayAction, IHostAddon): run_detached_process(args) def cli(self, click_group): - click_group.add_command(cli_main) + click_group.add_command(cli_main.to_click_obj()) -@click.group( +@click_wrap.group( StandAlonePublishAddon.name, help="StandalonePublisher related commands.") def cli_main(): diff --git a/openpype/hosts/traypublisher/addon.py b/openpype/hosts/traypublisher/addon.py index 3b34f9e6e8..ca60760bab 100644 --- a/openpype/hosts/traypublisher/addon.py +++ b/openpype/hosts/traypublisher/addon.py @@ -1,10 +1,13 @@ import os -import click - from openpype.lib import get_openpype_execute_args from openpype.lib.execute import run_detached_process -from openpype.modules import OpenPypeModule, ITrayAction, IHostAddon +from openpype.modules import ( + click_wrap, + OpenPypeModule, + ITrayAction, + IHostAddon, +) TRAYPUBLISH_ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -38,10 +41,12 @@ class TrayPublishAddon(OpenPypeModule, IHostAddon, ITrayAction): run_detached_process(args) def cli(self, click_group): - click_group.add_command(cli_main) + click_group.add_command(cli_main.to_click_obj()) -@click.group(TrayPublishAddon.name, help="TrayPublisher related commands.") +@click_wrap.group( + TrayPublishAddon.name, + help="TrayPublisher related commands.") def cli_main(): pass diff --git a/openpype/hosts/webpublisher/addon.py b/openpype/hosts/webpublisher/addon.py index 4438775b03..810d9aa6c3 100644 --- a/openpype/hosts/webpublisher/addon.py +++ b/openpype/hosts/webpublisher/addon.py @@ -1,8 +1,6 @@ import os -import click - -from openpype.modules import OpenPypeModule, IHostAddon +from openpype.modules import click_wrap, OpenPypeModule, IHostAddon WEBPUBLISHER_ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -38,10 +36,10 @@ class WebpublisherAddon(OpenPypeModule, IHostAddon): ) def cli(self, click_group): - click_group.add_command(cli_main) + click_group.add_command(cli_main.to_click_obj()) -@click.group( +@click_wrap.group( WebpublisherAddon.name, help="Webpublisher related commands.") def cli_main(): @@ -49,10 +47,10 @@ def cli_main(): @cli_main.command() -@click.argument("path") -@click.option("-u", "--user", help="User email address") -@click.option("-p", "--project", help="Project") -@click.option("-t", "--targets", help="Targets", default=None, +@click_wrap.argument("path") +@click_wrap.option("-u", "--user", help="User email address") +@click_wrap.option("-p", "--project", help="Project") +@click_wrap.option("-t", "--targets", help="Targets", default=None, multiple=True) def publish(project, path, user=None, targets=None): """Start publishing (Inner command). @@ -67,11 +65,11 @@ def publish(project, path, user=None, targets=None): @cli_main.command() -@click.argument("path") -@click.option("-p", "--project", help="Project") -@click.option("-h", "--host", help="Host") -@click.option("-u", "--user", help="User email address") -@click.option("-t", "--targets", help="Targets", default=None, +@click_wrap.argument("path") +@click_wrap.option("-p", "--project", help="Project") +@click_wrap.option("-h", "--host", help="Host") +@click_wrap.option("-u", "--user", help="User email address") +@click_wrap.option("-t", "--targets", help="Targets", default=None, multiple=True) def publishfromapp(project, path, host, user=None, targets=None): """Start publishing through application (Inner command). @@ -86,10 +84,10 @@ def publishfromapp(project, path, host, user=None, targets=None): @cli_main.command() -@click.option("-e", "--executable", help="Executable") -@click.option("-u", "--upload_dir", help="Upload dir") -@click.option("-h", "--host", help="Host", default=None) -@click.option("-p", "--port", help="Port", default=None) +@click_wrap.option("-e", "--executable", help="Executable") +@click_wrap.option("-u", "--upload_dir", help="Upload dir") +@click_wrap.option("-h", "--host", help="Host", default=None) +@click_wrap.option("-p", "--port", help="Port", default=None) def webserver(executable, upload_dir, host=None, port=None): """Start service for communication with Webpublish Front end. diff --git a/openpype/modules/example_addons/example_addon/addon.py b/openpype/modules/example_addons/example_addon/addon.py index be1d3ff920..e9bcee85bb 100644 --- a/openpype/modules/example_addons/example_addon/addon.py +++ b/openpype/modules/example_addons/example_addon/addon.py @@ -8,9 +8,9 @@ in global space here until are required or used. """ import os -import click from openpype.modules import ( + click_wrap, JsonFilesSettingsDef, OpenPypeAddOn, ModulesManager, @@ -115,10 +115,10 @@ class ExampleAddon(OpenPypeAddOn, IPluginPaths, ITrayAction): } def cli(self, click_group): - click_group.add_command(cli_main) + click_group.add_command(cli_main.to_click_obj()) -@click.group(ExampleAddon.name, help="Example addon dynamic cli commands.") +@click_wrap.group(ExampleAddon.name, help="Example addon dynamic cli commands.") def cli_main(): pass diff --git a/openpype/modules/ftrack/ftrack_module.py b/openpype/modules/ftrack/ftrack_module.py index ed48b170a1..2042367a7e 100644 --- a/openpype/modules/ftrack/ftrack_module.py +++ b/openpype/modules/ftrack/ftrack_module.py @@ -3,8 +3,8 @@ import json import collections import platform -from openpype import click_wrap from openpype.modules import ( + click_wrap, OpenPypeModule, ITrayModule, IPluginPaths, diff --git a/openpype/modules/job_queue/module.py b/openpype/modules/job_queue/module.py index 7075fcea14..c267329a61 100644 --- a/openpype/modules/job_queue/module.py +++ b/openpype/modules/job_queue/module.py @@ -41,8 +41,7 @@ import json import copy import platform -import click -from openpype.modules import OpenPypeModule +from openpype.modules import OpenPypeModule, click_wrap from openpype.settings import get_system_settings @@ -153,7 +152,7 @@ class JobQueueModule(OpenPypeModule): return requests.get(api_path).json() def cli(self, click_group): - click_group.add_command(cli_main) + click_group.add_command(cli_main.to_click_obj()) @classmethod def get_server_url_from_settings(cls): @@ -213,7 +212,7 @@ class JobQueueModule(OpenPypeModule): return main(str(executable), server_url) -@click.group( +@click_wrap.group( JobQueueModule.name, help="Application job server. Can be used as render farm." ) @@ -225,8 +224,8 @@ def cli_main(): "start_server", help="Start server handling workers and their jobs." ) -@click.option("--port", help="Server port") -@click.option("--host", help="Server host (ip address)") +@click_wrap.option("--port", help="Server port") +@click_wrap.option("--host", help="Server host (ip address)") def cli_start_server(port, host): JobQueueModule.start_server(port, host) @@ -236,7 +235,7 @@ def cli_start_server(port, host): "Start a worker for a specific application. (e.g. \"tvpaint/11.5\")" ) ) -@click.argument("app_name") -@click.option("--server_url", help="Server url which handle workers and jobs.") +@click_wrap.argument("app_name") +@click_wrap.option("--server_url", help="Server url which handle workers and jobs.") def cli_start_worker(app_name, server_url): JobQueueModule.start_worker(app_name, server_url) diff --git a/openpype/modules/kitsu/kitsu_module.py b/openpype/modules/kitsu/kitsu_module.py index 8d2d5ccd60..0ab627ba75 100644 --- a/openpype/modules/kitsu/kitsu_module.py +++ b/openpype/modules/kitsu/kitsu_module.py @@ -1,9 +1,9 @@ """Kitsu module.""" -import click import os from openpype.modules import ( + click_wrap, OpenPypeModule, IPluginPaths, ITrayAction, @@ -98,17 +98,17 @@ class KitsuModule(OpenPypeModule, IPluginPaths, ITrayAction): } def cli(self, click_group): - click_group.add_command(cli_main) + click_group.add_command(cli_main.to_click_obj()) -@click.group(KitsuModule.name, help="Kitsu dynamic cli commands.") +@click_wrap.group(KitsuModule.name, help="Kitsu dynamic cli commands.") def cli_main(): pass @cli_main.command() -@click.option("--login", envvar="KITSU_LOGIN", help="Kitsu login") -@click.option( +@click_wrap.option("--login", envvar="KITSU_LOGIN", help="Kitsu login") +@click_wrap.option( "--password", envvar="KITSU_PWD", help="Password for kitsu username" ) def push_to_zou(login, password): @@ -124,11 +124,11 @@ def push_to_zou(login, password): @cli_main.command() -@click.option("-l", "--login", envvar="KITSU_LOGIN", help="Kitsu login") -@click.option( +@click_wrap.option("-l", "--login", envvar="KITSU_LOGIN", help="Kitsu login") +@click_wrap.option( "-p", "--password", envvar="KITSU_PWD", help="Password for kitsu username" ) -@click.option( +@click_wrap.option( "-prj", "--project", "projects", @@ -136,7 +136,7 @@ def push_to_zou(login, password): default=[], help="Sync specific kitsu projects", ) -@click.option( +@click_wrap.option( "-lo", "--listen-only", "listen_only", diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index 8a92697920..3d6f76ad55 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -7,7 +7,6 @@ import copy import signal from collections import deque, defaultdict -import click from bson.objectid import ObjectId from openpype.client import ( @@ -15,7 +14,12 @@ from openpype.client import ( get_representations, get_representation_by_id, ) -from openpype.modules import OpenPypeModule, ITrayModule, IPluginPaths +from openpype.modules import ( + OpenPypeModule, + ITrayModule, + IPluginPaths, + click_wrap, +) from openpype.settings import ( get_project_settings, get_system_settings, @@ -2405,7 +2409,7 @@ class SyncServerModule(OpenPypeModule, ITrayModule, IPluginPaths): return presets[project_name]['sites'][site_name]['root'] def cli(self, click_group): - click_group.add_command(cli_main) + click_group.add_command(cli_main.to_click_obj()) # Webserver module implementation def webserver_initialization(self, server_manager): @@ -2417,13 +2421,15 @@ class SyncServerModule(OpenPypeModule, ITrayModule, IPluginPaths): ) -@click.group(SyncServerModule.name, help="SyncServer module related commands.") +@click_wrap.group( + SyncServerModule.name, + help="SyncServer module related commands.") def cli_main(): pass @cli_main.command() -@click.option( +@click_wrap.option( "-a", "--active_site", required=True, From 6f5be5ab7ff369f040413638b7afaade2829a61b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 23 Jan 2024 10:55:07 +0100 Subject: [PATCH 20/92] fix line length --- openpype/modules/example_addons/example_addon/addon.py | 4 +++- openpype/modules/job_queue/module.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/openpype/modules/example_addons/example_addon/addon.py b/openpype/modules/example_addons/example_addon/addon.py index e9bcee85bb..e9de0c1bf5 100644 --- a/openpype/modules/example_addons/example_addon/addon.py +++ b/openpype/modules/example_addons/example_addon/addon.py @@ -118,7 +118,9 @@ class ExampleAddon(OpenPypeAddOn, IPluginPaths, ITrayAction): click_group.add_command(cli_main.to_click_obj()) -@click_wrap.group(ExampleAddon.name, help="Example addon dynamic cli commands.") +@click_wrap.group( + ExampleAddon.name, + help="Example addon dynamic cli commands.") def cli_main(): pass diff --git a/openpype/modules/job_queue/module.py b/openpype/modules/job_queue/module.py index c267329a61..6792cd2aca 100644 --- a/openpype/modules/job_queue/module.py +++ b/openpype/modules/job_queue/module.py @@ -236,6 +236,8 @@ def cli_start_server(port, host): ) ) @click_wrap.argument("app_name") -@click_wrap.option("--server_url", help="Server url which handle workers and jobs.") +@click_wrap.option( + "--server_url", + help="Server url which handle workers and jobs.") def cli_start_worker(app_name, server_url): JobQueueModule.start_worker(app_name, server_url) From 94702cc2cda1177cd8973e64fbd62e4635a442fb Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Jan 2024 10:56:09 +0100 Subject: [PATCH 21/92] reset loader window on reopen --- openpype/tools/ayon_loader/ui/window.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openpype/tools/ayon_loader/ui/window.py b/openpype/tools/ayon_loader/ui/window.py index a6d40d52e7..d0455c901d 100644 --- a/openpype/tools/ayon_loader/ui/window.py +++ b/openpype/tools/ayon_loader/ui/window.py @@ -322,6 +322,7 @@ class LoaderWindow(QtWidgets.QWidget): ) def refresh(self): + self._reset_on_show = False self._controller.reset() def showEvent(self, event): @@ -332,6 +333,10 @@ class LoaderWindow(QtWidgets.QWidget): self._show_timer.start() + def closeEvent(self, event): + super(LoaderWindow, self).closeEvent(event) + self._reset_on_show = True + def keyPressEvent(self, event): modifiers = event.modifiers() ctrl_pressed = QtCore.Qt.ControlModifier & modifiers @@ -378,8 +383,7 @@ class LoaderWindow(QtWidgets.QWidget): self._show_timer.stop() if self._reset_on_show: - self._reset_on_show = False - self._controller.reset() + self.refresh() def _show_group_dialog(self): project_name = self._projects_combobox.get_selected_project_name() From d2ee1b91f55449f43b0783f7b6ab59532352606c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Jan 2024 11:15:08 +0100 Subject: [PATCH 22/92] deselect project on close --- openpype/tools/ayon_loader/ui/window.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/tools/ayon_loader/ui/window.py b/openpype/tools/ayon_loader/ui/window.py index d0455c901d..8982d92c0f 100644 --- a/openpype/tools/ayon_loader/ui/window.py +++ b/openpype/tools/ayon_loader/ui/window.py @@ -335,6 +335,9 @@ class LoaderWindow(QtWidgets.QWidget): def closeEvent(self, event): super(LoaderWindow, self).closeEvent(event) + # Deselect project so current context will be selected + # on next 'showEvent' + self._controller.set_selected_project(None) self._reset_on_show = True def keyPressEvent(self, event): From dadf8989be620616cdebc0bc3ee99dc3ba9d9ebd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Jan 2024 16:45:44 +0100 Subject: [PATCH 23/92] Use QDialog as super class and pass parent to init --- openpype/tools/publisher/window.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index 5dd6998b24..de1a6249ad 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -42,7 +42,7 @@ from .widgets import ( ) -class PublisherWindow(QtWidgets.QWidget): +class PublisherWindow(QtWidgets.QDialog): """Main window of publisher.""" default_width = 1300 default_height = 800 @@ -50,7 +50,7 @@ class PublisherWindow(QtWidgets.QWidget): publish_footer_spacer = 2 def __init__(self, parent=None, controller=None, reset_on_show=None): - super(PublisherWindow, self).__init__() + super(PublisherWindow, self).__init__(parent) self.setObjectName("PublishWindow") From 34b42c132d56a69c190681b6b3aad0c4c8f06369 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Jan 2024 16:46:03 +0100 Subject: [PATCH 24/92] ignore enter and return event key --- openpype/tools/publisher/window.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index de1a6249ad..ab337c9b32 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -328,7 +328,6 @@ class PublisherWindow(QtWidgets.QDialog): "copy_report.request", self._copy_report ) - # Store extra header widget for TrayPublisher # - can be used to add additional widgets to header between context # label and help button @@ -492,7 +491,11 @@ class PublisherWindow(QtWidgets.QDialog): def keyPressEvent(self, event): # Ignore escape button to close window - if event.key() == QtCore.Qt.Key_Escape: + if event.key() in { + QtCore.Qt.Key_Escape, + QtCore.Qt.Key_Enter, + QtCore.Qt.Key_Return, + }: event.accept() return From 87b4b6e483de6dfc5db677a8fbc848f24cedda3a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Jan 2024 17:43:09 +0100 Subject: [PATCH 25/92] removed '_make_sure_on_top' --- openpype/tools/publisher/window.py | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index ab337c9b32..5b56802055 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -294,12 +294,6 @@ class PublisherWindow(QtWidgets.QDialog): controller.event_system.add_callback( "publish.process.stopped", self._on_publish_stop ) - controller.event_system.add_callback( - "publish.process.instance.changed", self._on_instance_change - ) - controller.event_system.add_callback( - "publish.process.plugin.changed", self._on_plugin_change - ) controller.event_system.add_callback( "show.card.message", self._on_overlay_message ) @@ -561,18 +555,6 @@ class PublisherWindow(QtWidgets.QDialog): self._reset_on_show = False self.reset() - def _make_sure_on_top(self): - """Raise window to top and activate it. - - This may not work for some DCCs without Qt. - """ - - if not self._window_is_visible: - self.show() - - self.setWindowState(QtCore.Qt.WindowActive) - self.raise_() - def _checks_before_save(self, explicit_save): """Save of changes may trigger some issues. @@ -885,12 +867,6 @@ class PublisherWindow(QtWidgets.QDialog): if self._is_on_create_tab(): self._go_to_publish_tab() - def _on_instance_change(self): - self._make_sure_on_top() - - def _on_plugin_change(self): - self._make_sure_on_top() - def _on_publish_validated_change(self, event): if event["value"]: self._validate_btn.setEnabled(False) @@ -901,7 +877,6 @@ class PublisherWindow(QtWidgets.QDialog): self._comment_input.setText("") def _on_publish_stop(self): - self._make_sure_on_top() self._set_publish_overlay_visibility(False) self._reset_btn.setEnabled(True) self._stop_btn.setEnabled(False) From 781bd615a6506dd36d5c69c90393596a3a35e299 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Jan 2024 17:53:30 +0100 Subject: [PATCH 26/92] add comments to enter and return keys --- openpype/tools/publisher/window.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index 5b56802055..82c7b167b7 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -484,9 +484,11 @@ class PublisherWindow(QtWidgets.QDialog): app.removeEventFilter(self) def keyPressEvent(self, event): - # Ignore escape button to close window if event.key() in { + # Ignore escape button to close window QtCore.Qt.Key_Escape, + # Ignore enter keyboard event which by default triggers + # first available button in QDialog QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return, }: From 0bbe25fab1b7259c118c03f37748a4bc570eb530 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 29 Jan 2024 16:57:15 +0100 Subject: [PATCH 27/92] Refactor code for creating and publishing editorial clips - renaming variable to specify correctly value type - Added a condition in `collect_shot_instances.py` to check if the parent kind is "Video" before collecting shot instances. --- .../plugins/create/create_editorial.py | 13 +++++++------ .../plugins/publish/collect_shot_instances.py | 1 + 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index e6f29af40f..3898635254 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -402,18 +402,19 @@ or updating already created. Publishing will create OTIO file. except AttributeError: track_start_frame = 0 - for clip in track.each_child(): - if not self._validate_clip_for_processing(clip): + for otio_clip in track.each_child(): + if not self._validate_clip_for_processing(otio_clip): continue + # get available frames info to clip data - self._create_otio_reference(clip, media_path, media_data) + self._create_otio_reference(otio_clip, media_path, media_data) # convert timeline range to source range - self._restore_otio_source_range(clip) + self._restore_otio_source_range(otio_clip) base_instance_data = self._get_base_instance_data( - clip, + otio_clip, instance_data, track_start_frame ) @@ -432,7 +433,7 @@ or updating already created. Publishing will create OTIO file. continue instance = self._make_subset_instance( - clip, + otio_clip, _fpreset, deepcopy(base_instance_data), parenting_data diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py b/openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py index b99b634da1..43f6518374 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py @@ -79,6 +79,7 @@ class CollectShotInstance(pyblish.api.InstancePlugin): clip for clip in otio_timeline.each_child( descended_from_type=otio.schema.Clip) if clip.name == otio_clip.name + if clip.parent().kind == "Video" ] otio_clip = clips.pop() From 449e601a5fe37617d7c663b47f9d0bd45b083aec Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 29 Jan 2024 17:39:28 +0000 Subject: [PATCH 28/92] Show message with error on action failure. --- openpype/tools/publisher/control.py | 15 +++++++++++++++ openpype/tools/publisher/window.py | 15 +++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 47e374edf2..0137d5c95f 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -2329,6 +2329,21 @@ class PublisherController(BasePublisherController): result = pyblish.plugin.process( plugin, self._publish_context, None, action.id ) + exception = result.get("error") + if exception: + self._emit_event( + "action.failed", + { + "title": "Action failed", + "message": "Action failed.", + "traceback": "".join( + traceback.format_exception(exception) + ), + "label": "", + "identifier": action.__name__ + } + ) + self._publish_report.add_action_result(action, result) def _publish_next_process(self): diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index 5dd6998b24..e5b47f4309 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -321,6 +321,9 @@ class PublisherWindow(QtWidgets.QWidget): controller.event_system.add_callback( "convertors.find.failed", self._on_convertor_error ) + controller.event_system.add_callback( + "action.failed", self._on_action_error + ) controller.event_system.add_callback( "export_report.request", self._export_report ) @@ -1012,6 +1015,18 @@ class PublisherWindow(QtWidgets.QWidget): event["title"], new_failed_info, "Convertor:" ) + def _on_action_error(self, event): + self.add_error_message_dialog( + event["title"], + [{ + "message": event["message"], + "traceback": event["traceback"], + "label": event["label"], + "identifier": event["identifier"] + }], + "Action:" + ) + def _update_create_overlay_size(self): metrics = self._create_overlay_button.fontMetrics() height = int(metrics.height()) From 2b3160773332c53cc4e923e897ea532e06d7c968 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 30 Jan 2024 10:57:59 +0000 Subject: [PATCH 29/92] Feedback action finished. --- 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 0137d5c95f..61528c7c88 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -2346,6 +2346,8 @@ class PublisherController(BasePublisherController): self._publish_report.add_action_result(action, result) + self.emit_card_message("Action finished.") + def _publish_next_process(self): # Validations of progress before using iterator # - same conditions may be inside iterator but they may be used From d0a09bcad47299e341152cb47a600abc3d269f43 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 31 Jan 2024 07:15:36 +0000 Subject: [PATCH 30/92] Illicit suggestion --- openpype/tools/publisher/control.py | 2 +- openpype/tools/publisher/window.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 61528c7c88..9bae0deac8 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -2332,7 +2332,7 @@ class PublisherController(BasePublisherController): exception = result.get("error") if exception: self._emit_event( - "action.failed", + "publish.action.failed", { "title": "Action failed", "message": "Action failed.", diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index e5b47f4309..193b6948a5 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -322,7 +322,7 @@ class PublisherWindow(QtWidgets.QWidget): "convertors.find.failed", self._on_convertor_error ) controller.event_system.add_callback( - "action.failed", self._on_action_error + "publish.action.failed", self._on_action_error ) controller.event_system.add_callback( "export_report.request", self._export_report From 149f53daff8a4267fc0a45370bcd414db00d7981 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 31 Jan 2024 09:15:11 +0000 Subject: [PATCH 31/92] identifier > name --- openpype/tools/publisher/control.py | 2 +- openpype/tools/publisher/window.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 9bae0deac8..23ef6ae6c1 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -2340,7 +2340,7 @@ class PublisherController(BasePublisherController): traceback.format_exception(exception) ), "label": "", - "identifier": action.__name__ + "name": action.__name__ } ) diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index 193b6948a5..f4de3a08f7 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -1022,7 +1022,7 @@ class PublisherWindow(QtWidgets.QWidget): "message": event["message"], "traceback": event["traceback"], "label": event["label"], - "identifier": event["identifier"] + "identifier": event["name"] }], "Action:" ) From b39c41f98a5ece76b6466ab08e8e55e7462df8a3 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 31 Jan 2024 10:01:02 +0000 Subject: [PATCH 32/92] Use label --- openpype/tools/publisher/control.py | 4 ++-- openpype/tools/publisher/window.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 23ef6ae6c1..13d007dd35 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -2339,8 +2339,8 @@ class PublisherController(BasePublisherController): "traceback": "".join( traceback.format_exception(exception) ), - "label": "", - "name": action.__name__ + "label": action.__name__, + "identifier": action.id } ) diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index f4de3a08f7..193b6948a5 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -1022,7 +1022,7 @@ class PublisherWindow(QtWidgets.QWidget): "message": event["message"], "traceback": event["traceback"], "label": event["label"], - "identifier": event["name"] + "identifier": event["identifier"] }], "Action:" ) From 44282f86df721a271a291bc005abd07f896acb49 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 1 Feb 2024 10:42:34 +0000 Subject: [PATCH 33/92] Fixed deadline validator to check compositor tree output --- .../publish/validate_deadline_publish.py | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/validate_deadline_publish.py b/openpype/hosts/blender/plugins/publish/validate_deadline_publish.py index bb243f08cc..f7860dd75d 100644 --- a/openpype/hosts/blender/plugins/publish/validate_deadline_publish.py +++ b/openpype/hosts/blender/plugins/publish/validate_deadline_publish.py @@ -28,15 +28,27 @@ class ValidateDeadlinePublish(pyblish.api.InstancePlugin, def process(self, instance): if not self.is_active(instance.data): return + + tree = bpy.context.scene.node_tree + output_type = "CompositorNodeOutputFile" + output_node = None + # Remove all output nodes that inlcude "AYON" in the name. + # There should be only one. + for node in tree.nodes: + if node.bl_idname == output_type and "AYON" in node.name: + output_node = node + break + if not output_node: + raise PublishValidationError( + "No output node found in the compositor tree." + ) filepath = bpy.data.filepath file = os.path.basename(filepath) filename, ext = os.path.splitext(file) - if filename not in bpy.context.scene.render.filepath: + if filename not in output_node.base_path: raise PublishValidationError( - "Render output folder " - "doesn't match the blender scene name! " - "Use Repair action to " - "fix the folder file path." + "Render output folder doesn't match the blender scene name! " + "Use Repair action to fix the folder file path." ) @classmethod From 27f869e204662ba9a0dd78400269499145a60ec1 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 1 Feb 2024 11:49:25 +0000 Subject: [PATCH 34/92] Fix error report --- openpype/hosts/nuke/plugins/publish/validate_write_nodes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py b/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py index f490b580d6..648025adad 100644 --- a/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py +++ b/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py @@ -129,7 +129,7 @@ class ValidateNukeWriteNode( and key != "file" and key != "tile_color" ): - check.append([key, node_value, write_node[key].value()]) + check.append([key, fixed_values, write_node[key].value()]) if check: self._make_error(check) @@ -137,7 +137,7 @@ class ValidateNukeWriteNode( def _make_error(self, check): # sourcery skip: merge-assign-and-aug-assign, move-assign-in-block dbg_msg = "Write node's knobs values are not correct!\n" - msg_add = "Knob '{0}' > Correct: `{1}` > Wrong: `{2}`" + msg_add = "Knob '{0}' > Expected: `{1}` > Current: `{2}`" details = [ msg_add.format(item[0], item[1], item[2]) From b26ea7a62039ba766085377bf51b8b521f8014a9 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 1 Feb 2024 12:18:46 +0000 Subject: [PATCH 35/92] Fix getting node and node name. --- openpype/hosts/nuke/plugins/load/load_camera_abc.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/nuke/plugins/load/load_camera_abc.py b/openpype/hosts/nuke/plugins/load/load_camera_abc.py index 898c5e4e7b..7f0452d6d4 100644 --- a/openpype/hosts/nuke/plugins/load/load_camera_abc.py +++ b/openpype/hosts/nuke/plugins/load/load_camera_abc.py @@ -112,8 +112,6 @@ class AlembicCameraLoader(load.LoaderPlugin): project_name = get_current_project_name() version_doc = get_version_by_id(project_name, representation["parent"]) - object_name = container["node"] - # get main variables version_data = version_doc.get("data", {}) vname = version_doc.get("name", None) @@ -139,7 +137,7 @@ class AlembicCameraLoader(load.LoaderPlugin): file = get_representation_path(representation).replace("\\", "/") with maintained_selection(): - camera_node = nuke.toNode(object_name) + camera_node = container["node"] camera_node['selected'].setValue(True) # collect input output dependencies @@ -154,9 +152,10 @@ class AlembicCameraLoader(load.LoaderPlugin): xpos = camera_node.xpos() ypos = camera_node.ypos() nuke.nodeCopy("%clipboard%") + camera_name = camera_node.name() nuke.delete(camera_node) nuke.nodePaste("%clipboard%") - camera_node = nuke.toNode(object_name) + camera_node = nuke.toNode(camera_name) camera_node.setXYpos(xpos, ypos) # link to original input nodes From d64eb3ccc2eba2a622631bac8041df66722648a1 Mon Sep 17 00:00:00 2001 From: Jack P Date: Thu, 1 Feb 2024 13:26:38 +0000 Subject: [PATCH 36/92] refactor: replaced legacy function has_unsaved_changes seems to be legacy as indicated by the base class. It was already unused/implemented. Replaced with working version workfiles_has_unsaved_changes --- openpype/hosts/max/api/pipeline.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/max/api/pipeline.py b/openpype/hosts/max/api/pipeline.py index d0ae854dc8..ce4afd2e8b 100644 --- a/openpype/hosts/max/api/pipeline.py +++ b/openpype/hosts/max/api/pipeline.py @@ -60,9 +60,8 @@ class MaxHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): rt.callbacks.addScript(rt.Name('filePostOpen'), lib.check_colorspace) - def has_unsaved_changes(self): - # TODO: how to get it from 3dsmax? - return True + def workfiles_has_unsaved_changes(self): + return rt.getSaveRequired() def get_workfile_extensions(self): return [".max"] From 895ca1b57221e395d0f205e1ed8c45832c55e2a2 Mon Sep 17 00:00:00 2001 From: Jack P Date: Thu, 1 Feb 2024 13:28:08 +0000 Subject: [PATCH 37/92] feat: implemented new work_file_has_unsaved_changes function on host class mirrored Houdini implementation --- .../hosts/max/plugins/publish/save_scene.py | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/save_scene.py b/openpype/hosts/max/plugins/publish/save_scene.py index a40788ab41..54f9f8d8eb 100644 --- a/openpype/hosts/max/plugins/publish/save_scene.py +++ b/openpype/hosts/max/plugins/publish/save_scene.py @@ -1,21 +1,24 @@ import pyblish.api -import os +from openpype.pipeline import registered_host class SaveCurrentScene(pyblish.api.ContextPlugin): - """Save current scene - - """ + """Save current scene""" label = "Save current file" order = pyblish.api.ExtractorOrder - 0.49 hosts = ["max"] families = ["maxrender", "workfile"] - + def process(self, context): - from pymxs import runtime as rt - folder = rt.maxFilePath - file = rt.maxFileName - current = os.path.join(folder, file) - assert context.data["currentFile"] == current - rt.saveMaxFile(current) + host = registered_host() + current_file = host.get_current_workfile() + + assert context.data["currentFile"] == current_file + + if host.workfile_has_unsaved_changes(): + self.log.info(f"Saving current file: {current_file}") + host.save_workfile(current_file) + else: + self.log.debug("No unsaved changes, skipping file save..") + From 14baeb65c8820c90e15f90295bd32e238e13c60c Mon Sep 17 00:00:00 2001 From: Jack P Date: Thu, 1 Feb 2024 13:29:59 +0000 Subject: [PATCH 38/92] feat: added line to restore menu to 3dsmax default --- openpype/hosts/max/startup/startup.ms | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/max/startup/startup.ms b/openpype/hosts/max/startup/startup.ms index b80ead4b74..3a4e76b3cf 100644 --- a/openpype/hosts/max/startup/startup.ms +++ b/openpype/hosts/max/startup/startup.ms @@ -7,6 +7,9 @@ local pythonpath = systemTools.getEnvVariable "MAX_PYTHONPATH" systemTools.setEnvVariable "PYTHONPATH" pythonpath + + # opens the create menu on startup to ensure users are presented with a useful default view. + max create mode python.ExecuteFile startup -) \ No newline at end of file +) From 4999550167e3668b7be617c2842cddaee100a9a2 Mon Sep 17 00:00:00 2001 From: Jack P Date: Thu, 1 Feb 2024 13:50:04 +0000 Subject: [PATCH 39/92] fix: hound --- openpype/hosts/max/plugins/publish/save_scene.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/save_scene.py b/openpype/hosts/max/plugins/publish/save_scene.py index 54f9f8d8eb..fa571be835 100644 --- a/openpype/hosts/max/plugins/publish/save_scene.py +++ b/openpype/hosts/max/plugins/publish/save_scene.py @@ -9,16 +9,15 @@ class SaveCurrentScene(pyblish.api.ContextPlugin): order = pyblish.api.ExtractorOrder - 0.49 hosts = ["max"] families = ["maxrender", "workfile"] - + def process(self, context): host = registered_host() current_file = host.get_current_workfile() assert context.data["currentFile"] == current_file - + if host.workfile_has_unsaved_changes(): self.log.info(f"Saving current file: {current_file}") host.save_workfile(current_file) else: self.log.debug("No unsaved changes, skipping file save..") - From f709a86db49274d33a41d9b5b182fa711275d942 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 1 Feb 2024 15:43:48 +0100 Subject: [PATCH 40/92] use bundle name as variant in dev mode --- openpype/settings/ayon_settings.py | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/openpype/settings/ayon_settings.py b/openpype/settings/ayon_settings.py index 4948f2431c..2c851c054d 100644 --- a/openpype/settings/ayon_settings.py +++ b/openpype/settings/ayon_settings.py @@ -1458,7 +1458,7 @@ class _AyonSettingsCache: variant = "production" if is_dev_mode_enabled(): - variant = cls._get_dev_mode_settings_variant() + variant = cls._get_bundle_name() elif is_staging_enabled(): variant = "staging" @@ -1474,28 +1474,6 @@ class _AyonSettingsCache: def _get_bundle_name(cls): return os.environ["AYON_BUNDLE_NAME"] - @classmethod - def _get_dev_mode_settings_variant(cls): - """Develop mode settings variant. - - Returns: - str: Name of settings variant. - """ - - con = get_ayon_server_api_connection() - bundles = con.get_bundles() - user = con.get_user() - username = user["name"] - for bundle in bundles["bundles"]: - if ( - bundle.get("isDev") - and bundle.get("activeUser") == username - ): - return bundle["name"] - # Return fake variant - distribution logic will tell user that he - # does not have set any dev bundle - return "dev" - @classmethod def get_value_by_project(cls, project_name): cache_item = _AyonSettingsCache.cache_by_project_name[project_name] From 7be9463e5169f260686b214b616820df6d70c5f7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 1 Feb 2024 15:48:43 +0100 Subject: [PATCH 41/92] removed djvview group from default applications the djvview does not have model and is unused, probably forgotten --- .../applications/server/applications.json | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/server_addon/applications/server/applications.json b/server_addon/applications/server/applications.json index b0b12b2003..82dfd3b8d3 100644 --- a/server_addon/applications/server/applications.json +++ b/server_addon/applications/server/applications.json @@ -1175,30 +1175,6 @@ } ] }, - "djvview": { - "enabled": true, - "label": "DJV View", - "icon": "{}/app_icons/djvView.png", - "host_name": "", - "environment": "{}", - "variants": [ - { - "name": "1-1", - "label": "1.1", - "executables": { - "windows": [], - "darwin": [], - "linux": [] - }, - "arguments": { - "windows": [], - "darwin": [], - "linux": [] - }, - "environment": "{}" - } - ] - }, "wrap": { "enabled": true, "label": "Wrap", From 1ee89a0afacc8779cac51cafdabd984922d6acc5 Mon Sep 17 00:00:00 2001 From: Ynbot Date: Thu, 1 Feb 2024 15:47:50 +0000 Subject: [PATCH 42/92] [Automated] Release --- CHANGELOG.md | 126 ++++++++++++++++++++++++++++++++++++++++++++ openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 128 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 14f0bc469f..009150ae7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,132 @@ # Changelog +## [3.18.6](https://github.com/ynput/OpenPype/tree/3.18.6) + + +[Full Changelog](https://github.com/ynput/OpenPype/compare/3.18.5...3.18.6) + +### **🚀 Enhancements** + + +
+AYON: Use `SettingsField` from ayon server #6173 + +This is preparation for new version of pydantic which will require to customize the field class for AYON purposes as raw pydantic Field could not be used. + + +___ + +
+ + +
+Nuke: Expose write knobs - OP-7592 #6137 + +This PR adds `exposed_knobs` to the creator plugins settings at `ayon+settings://nuke/create/CreateWriteRender/exposed_knobs`.When exposed knobs will be linked from the write node to the outside publish group, for users to adjust. + + +___ + +
+ + +
+AYON: Remove kitsu addon #6172 + +Removed kitsu addon from server addons because already has own repository. + + +___ + +
+ +### **🐛 Bug fixes** + + +
+Fusion: provide better logging for validate saver crash due type error #6082 + +Handles reported issue for `NoneType` error thrown in conversion `int(tool["Comments"][frame])`. It is most likely happening when saver node has no input connections.There is a validator for that, but it might be not obvious, that this error is caused by missing input connections and it has been already reported by `"Validate Saver Has Input"`. + + +___ + +
+ + +
+Workfile Template Builder: Use correct variable in create placeholder #6141 + +Use correct variable where failed instances are stored for validation. + + +___ + +
+ + +
+ExtractOIIOTranscode: Missing product_names to subsets conversion #6159 + +The `Product Names` filtering should be fixed with this. + + +___ + +
+ + +
+Blender: Fix missing animation data when updating blend assets #6165 + +Fix missing animation data when updating blend assets. + + +___ + +
+ + +
+TrayPublisher: Pre-fill of version works in AYON #6180 + +Use `folderPath` instead of `asset` in AYON mode to calculate next available version. + + +___ + +
+ +### **🔀 Refactored code** + + +
+Chore: remove Muster #6085 + +Muster isn't maintained for a long time and it wasn't working anyway. This is removing related code from the code base. If there is renewed interest in Muster, it needs to be re-implemented in modern AYON compatible way. + + +___ + +
+ +### **Merged pull requests** + + +
+Maya: change label in the render settings to be more readable #6134 + +AYON replacement for #5713. + + +___ + +
+ + + + ## [3.18.5](https://github.com/ynput/OpenPype/tree/3.18.5) diff --git a/openpype/version.py b/openpype/version.py index 6cbe5ba6cd..078500cd3e 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.18.6-nightly.2" +__version__ = "3.18.6" diff --git a/pyproject.toml b/pyproject.toml index 24172aa77f..453833aae2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.18.5" # OpenPype +version = "3.18.6" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From 56772fe3f755786c5d4083cf0682f09d0170af9f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 1 Feb 2024 15:48:42 +0000 Subject: [PATCH 43/92] chore(): update bug report / version --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index cd81171b73..e5dd558409 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -35,6 +35,7 @@ body: label: Version description: What version are you running? Look to OpenPype Tray options: + - 3.18.6 - 3.18.6-nightly.2 - 3.18.6-nightly.1 - 3.18.5 @@ -134,7 +135,6 @@ body: - 3.15.9-nightly.2 - 3.15.9-nightly.1 - 3.15.8 - - 3.15.8-nightly.3 validations: required: true - type: dropdown From 86cebe84365180d8a1ddbaf4a8cdd511cdf7eb40 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 1 Feb 2024 19:32:39 +0100 Subject: [PATCH 44/92] use 'get_openpype_qt_app' to create Qt application 'get_openpype_qt_app' prepares QApplication a little bit better for scaling issues --- openpype/hosts/photoshop/api/lib.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/photoshop/api/lib.py b/openpype/hosts/photoshop/api/lib.py index ff520348f0..d4d4995e6d 100644 --- a/openpype/hosts/photoshop/api/lib.py +++ b/openpype/hosts/photoshop/api/lib.py @@ -3,12 +3,11 @@ import sys import contextlib import traceback -from qtpy import QtWidgets - from openpype.lib import env_value_to_bool, Logger from openpype.modules import ModulesManager from openpype.pipeline import install_host from openpype.tools.utils import host_tools +from openpype.tools.utils import get_openpype_qt_app from openpype.tests.lib import is_in_tests from .launch_logic import ProcessLauncher, stub @@ -30,7 +29,7 @@ def main(*subprocess_args): # coloring in StdOutBroker os.environ["OPENPYPE_LOG_NO_COLORS"] = "False" - app = QtWidgets.QApplication([]) + app = get_openpype_qt_app() app.setQuitOnLastWindowClosed(False) launcher = ProcessLauncher(subprocess_args) From be70b52285c1d0c1d4542c20b0d91de7b889c174 Mon Sep 17 00:00:00 2001 From: Sponge96 Date: Fri, 2 Feb 2024 09:26:30 +0000 Subject: [PATCH 45/92] fix: comment using wrong syntax Co-authored-by: Kayla Man <64118225+moonyuet@users.noreply.github.com> --- openpype/hosts/max/startup/startup.ms | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/startup/startup.ms b/openpype/hosts/max/startup/startup.ms index 3a4e76b3cf..5e79901cdd 100644 --- a/openpype/hosts/max/startup/startup.ms +++ b/openpype/hosts/max/startup/startup.ms @@ -8,7 +8,7 @@ local pythonpath = systemTools.getEnvVariable "MAX_PYTHONPATH" systemTools.setEnvVariable "PYTHONPATH" pythonpath - # opens the create menu on startup to ensure users are presented with a useful default view. + /*opens the create menu on startup to ensure users are presented with a useful default view.*/ max create mode python.ExecuteFile startup From 2c3761ca37fd9ed7b815f03d086de70e84e83b57 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 2 Feb 2024 17:16:55 +0000 Subject: [PATCH 46/92] Make values for project_settings/ftrack/events/status_update case insensitive --- openpype/modules/ftrack/launch_hooks/post_ftrack_changes.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/modules/ftrack/launch_hooks/post_ftrack_changes.py b/openpype/modules/ftrack/launch_hooks/post_ftrack_changes.py index ac4e499e41..5c780a51c4 100644 --- a/openpype/modules/ftrack/launch_hooks/post_ftrack_changes.py +++ b/openpype/modules/ftrack/launch_hooks/post_ftrack_changes.py @@ -131,6 +131,8 @@ class PostFtrackHook(PostLaunchHook): for key, value in status_mapping.items(): if key in already_tested: continue + + value = value.lower() if actual_status in value or "__any__" in value: if key != "__ignore__": next_status_name = key From a939593c14d23918d0dfe84beee6dafeee9d3ac5 Mon Sep 17 00:00:00 2001 From: Ynbot Date: Sat, 3 Feb 2024 03:25:40 +0000 Subject: [PATCH 47/92] [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 078500cd3e..db6da9f656 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.18.6" +__version__ = "3.18.7-nightly.1" From 29d169e2b124aa9171698e540fc2b52f704af299 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 3 Feb 2024 03:26:23 +0000 Subject: [PATCH 48/92] chore(): update bug report / version --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index e5dd558409..54a9d69bdc 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -35,6 +35,7 @@ body: label: Version description: What version are you running? Look to OpenPype Tray options: + - 3.18.7-nightly.1 - 3.18.6 - 3.18.6-nightly.2 - 3.18.6-nightly.1 @@ -134,7 +135,6 @@ body: - 3.15.9 - 3.15.9-nightly.2 - 3.15.9-nightly.1 - - 3.15.8 validations: required: true - type: dropdown From ed339ed516391071948271d49a55a8e6564cceb8 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 5 Feb 2024 10:26:55 +0100 Subject: [PATCH 49/92] General: added fallback for broken ffprobe return (#6189) * OP-8090 - added fallback for ffprobe issue Customer provided .exr returned width and height equal to 0 which caused error in extract_thumbnail. This tries to use oiiotool to get metadata about file, in our case it read it correctly. * OP-8090 - extract logic `get_rescaled_command_arguments` is long enough right now, new method is better testable too. * Update openpype/lib/transcoding.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --------- Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/lib/transcoding.py | 50 ++++++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index c8ddbde061..1cfe9ac14b 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1227,12 +1227,8 @@ def get_rescaled_command_arguments( target_par = target_par or 1.0 input_par = 1.0 - # ffmpeg command - input_file_metadata = get_ffprobe_data(input_path, logger=log) - stream = input_file_metadata["streams"][0] - input_width = int(stream["width"]) - input_height = int(stream["height"]) - stream_input_par = stream.get("sample_aspect_ratio") + input_height, input_width, stream_input_par = _get_image_dimensions( + application, input_path, log) if stream_input_par: input_par = ( float(stream_input_par.split(":")[0]) @@ -1345,6 +1341,48 @@ def get_rescaled_command_arguments( return command_args +def _get_image_dimensions(application, input_path, log): + """Uses 'ffprobe' first and then 'oiiotool' if available to get dim. + + Args: + application (str): "oiiotool"|"ffmpeg" + input_path (str): path to image file + log (Optional[logging.Logger]): Logger used for logging. + Returns: + (tuple) (int, int, dict) - (height, width, sample_aspect_ratio) + Raises: + RuntimeError if image dimensions couldn't be parsed out. + """ + # ffmpeg command + input_file_metadata = get_ffprobe_data(input_path, logger=log) + input_width = input_height = 0 + stream = next( + ( + s for s in input_file_metadata["streams"] + if s.get("codec_type") == "video" + ), + {} + ) + if stream: + input_width = int(stream["width"]) + input_height = int(stream["height"]) + + # fallback for weird files with width=0, height=0 + if (input_width == 0 or input_height == 0) and application == "oiiotool": + # Load info about file from oiio tool + input_info = get_oiio_info_for_input(input_path, logger=log) + if input_info: + input_width = int(input_info["width"]) + input_height = int(input_info["height"]) + + if input_width == 0 or input_height == 0: + raise RuntimeError("Couldn't read {} either " + "with ffprobe or oiiotool".format(input_path)) + + stream_input_par = stream.get("sample_aspect_ratio") + return input_height, input_width, stream_input_par + + def convert_color_values(application, color_value): """Get color mapping for ffmpeg and oiiotool. Args: From d377b28f9e45036e1cc4892838c85f7d5196d5d6 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 5 Feb 2024 10:32:18 +0100 Subject: [PATCH 50/92] OP-8104 - fix unwanted change to field name (#6193) It should be image_format but in previous refactoring PR it fell back to original output_formats --- server_addon/fusion/server/settings.py | 8 +++++--- server_addon/fusion/server/version.py | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/server_addon/fusion/server/settings.py b/server_addon/fusion/server/settings.py index b157ce9e40..a913db16da 100644 --- a/server_addon/fusion/server/settings.py +++ b/server_addon/fusion/server/settings.py @@ -57,9 +57,9 @@ class CreateSaverPluginModel(BaseSettingsModel): enum_resolver=_create_saver_instance_attributes_enum, title="Instance attributes" ) - output_formats: list[str] = SettingsField( - default_factory=list, - title="Output formats" + image_format: str = SettingsField( + enum_resolver=_image_format_enum, + title="Output Image Format" ) @@ -90,6 +90,8 @@ class CreateImageSaverModel(CreateSaverPluginModel): 0, title="Default rendered frame" ) + + class CreatPluginsModel(BaseSettingsModel): CreateSaver: CreateSaverModel = SettingsField( default_factory=CreateSaverModel, diff --git a/server_addon/fusion/server/version.py b/server_addon/fusion/server/version.py index ae7362549b..bbab0242f6 100644 --- a/server_addon/fusion/server/version.py +++ b/server_addon/fusion/server/version.py @@ -1 +1 @@ -__version__ = "0.1.3" +__version__ = "0.1.4" From 907b5fb606675c5758ede110cfbb2ad64bafbccb Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 5 Feb 2024 10:38:57 +0100 Subject: [PATCH 51/92] modified docstrings for sphinx --- openpype/modules/click_wrap.py | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/openpype/modules/click_wrap.py b/openpype/modules/click_wrap.py index 3db5037b2f..ed67035ec8 100644 --- a/openpype/modules/click_wrap.py +++ b/openpype/modules/click_wrap.py @@ -12,7 +12,7 @@ How to use it? If you already have cli commands defined in addon, just replace 'click' with 'click_wrap' and it should work and modify your addon's cli method to convert 'click_wrap' object to 'click' object. -# Before +Before ```python import click from openpype.modules import OpenPypeModule @@ -37,7 +37,7 @@ def mycommand(arg1, arg2): print(arg1, arg2) ``` -# Now +Now ``` from openpype import click_wrap from openpype.modules import OpenPypeModule @@ -133,7 +133,6 @@ class Command(object): Returns: click.Command: Click command object. """ - return convert_to_click(self) # --- Methods for 'convert_to_click' function --- @@ -142,7 +141,6 @@ class Command(object): Returns: tuple: Command definition arguments. """ - return self._args def get_kwargs(self): @@ -150,7 +148,6 @@ class Command(object): Returns: dict[str, Any]: Command definition kwargs. """ - return self._kwargs def get_func(self): @@ -158,7 +155,6 @@ class Command(object): Returns: Function: Function to invoke on command trigger. """ - return self._func def iter_options(self): @@ -166,7 +162,6 @@ class Command(object): Yields: tuple[str, tuple, dict]: Option type name with args and kwargs. """ - for item in self._options: yield item # ----------------------------------------------- @@ -203,7 +198,6 @@ class Group(Command): Args: command (Command): Prepared command object. """ - if command not in self._commands: self._commands.append(command) @@ -213,7 +207,6 @@ class Group(Command): Args: group (Group): Prepared group object. """ - if group not in self._commands: self._commands.append(group) @@ -223,7 +216,6 @@ class Group(Command): Returns: Union[Command, Function]: New command object, or wrapper function. """ - return self._add_new(Command, *args, **kwargs) def group(self, *args, **kwargs): @@ -232,7 +224,6 @@ class Group(Command): Returns: Union[Group, Function]: New group object, or wrapper function. """ - return self._add_new(Group, *args, **kwargs) def _add_new(self, target_cls, *args, **kwargs): @@ -261,7 +252,6 @@ def convert_to_click(obj_to_convert): Returns: click.Command: Click command object. """ - import click commands_queue = collections.deque() From fe5ef4aa8c39b851b548064b9219027d0741311f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 5 Feb 2024 11:23:44 +0100 Subject: [PATCH 52/92] store version id to versions set --- openpype/tools/ayon_sceneinventory/switch_dialog/dialog.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/tools/ayon_sceneinventory/switch_dialog/dialog.py b/openpype/tools/ayon_sceneinventory/switch_dialog/dialog.py index fade09305a..1d1bd1adbc 100644 --- a/openpype/tools/ayon_sceneinventory/switch_dialog/dialog.py +++ b/openpype/tools/ayon_sceneinventory/switch_dialog/dialog.py @@ -1214,9 +1214,10 @@ class SwitchAssetDialog(QtWidgets.QDialog): version_ids = set() version_docs_by_parent_id_and_name = collections.defaultdict(dict) for version_doc in version_docs: - subset_id = version_doc["parent"] + version_ids.add(version_doc["_id"]) + product_id = version_doc["parent"] name = version_doc["name"] - version_docs_by_parent_id_and_name[subset_id][name] = version_doc + version_docs_by_parent_id_and_name[product_id][name] = version_doc hero_version_docs_by_parent_id = {} for hero_version_doc in hero_version_docs: From f5c38eb8d794fc6f3f3517322a796d18d8036d82 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 5 Feb 2024 10:25:01 +0000 Subject: [PATCH 53/92] Thumbnail subset filtering --- openpype/plugins/publish/extract_thumbnail.py | 30 +++++++++++++++++-- .../defaults/project_settings/global.json | 1 + .../schemas/schema_global_publish.json | 6 ++++ .../core/server/settings/publish_plugins.py | 5 ++++ server_addon/core/server/version.py | 2 +- 5 files changed, 40 insertions(+), 4 deletions(-) diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index 10eb261482..6008b8323d 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -2,6 +2,7 @@ import copy import os import subprocess import tempfile +import re import pyblish.api from openpype.lib import ( @@ -14,9 +15,10 @@ from openpype.lib import ( path_to_subprocess_arg, run_subprocess, ) -from openpype.lib.transcoding import convert_colorspace - -from openpype.lib.transcoding import VIDEO_EXTENSIONS +from openpype.lib.transcoding import ( + convert_colorspace, + VIDEO_EXTENSIONS, +) class ExtractThumbnail(pyblish.api.InstancePlugin): @@ -49,6 +51,8 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # attribute presets from settings oiiotool_defaults = None ffmpeg_args = None + subsets = [] + product_names = [] def process(self, instance): # run main process @@ -103,6 +107,26 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): self.log.debug("Skipping crypto passes.") return + # We only want to process the subsets needed from settings. + def validate_string_against_patterns(input_str, patterns): + for pattern in patterns: + if re.match(pattern, input_str): + return True + return False + + product_names = self.subsets + self.product_names + if product_names: + result = validate_string_against_patterns( + instance.data["subset"], product_names + ) + if not result: + self.log.debug( + "Subset \"{}\" did not match any valid subsets: {}".format( + instance.data["subset"], product_names + ) + ) + return + # first check for any explicitly marked representations for thumbnail explicit_repres = self._get_explicit_repres_for_thumbnail(instance) if explicit_repres: diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index bb7e3266bd..782fff1052 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -70,6 +70,7 @@ }, "ExtractThumbnail": { "enabled": true, + "subsets": [], "integrate_thumbnail": false, "background_color": [ 0, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json index 64f292a140..226a190dd4 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json @@ -202,6 +202,12 @@ "key": "enabled", "label": "Enabled" }, + { + "type": "list", + "object_type": "text", + "key": "subsets", + "label": "Subsets" + }, { "type": "boolean", "key": "integrate_thumbnail", diff --git a/server_addon/core/server/settings/publish_plugins.py b/server_addon/core/server/settings/publish_plugins.py index 7aa86aafa6..8506801e7e 100644 --- a/server_addon/core/server/settings/publish_plugins.py +++ b/server_addon/core/server/settings/publish_plugins.py @@ -176,6 +176,10 @@ class ExtractThumbnailOIIODefaultsModel(BaseSettingsModel): class ExtractThumbnailModel(BaseSettingsModel): _isGroup = True enabled: bool = SettingsField(True) + product_names: list[str] = SettingsField( + default_factory=list, + title="Product names" + ) integrate_thumbnail: bool = SettingsField( True, title="Integrate Thumbnail Representation" @@ -844,6 +848,7 @@ DEFAULT_PUBLISH_VALUES = { }, "ExtractThumbnail": { "enabled": True, + "product_names": [], "integrate_thumbnail": True, "target_size": { "type": "source" diff --git a/server_addon/core/server/version.py b/server_addon/core/server/version.py index bbab0242f6..1276d0254f 100644 --- a/server_addon/core/server/version.py +++ b/server_addon/core/server/version.py @@ -1 +1 @@ -__version__ = "0.1.4" +__version__ = "0.1.5" From 7a7a4b1e9a084956d6923658848eeb896f3d88c2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 5 Feb 2024 12:04:55 +0100 Subject: [PATCH 54/92] handle empty project in 'get_project_product_types' --- openpype/tools/ayon_loader/abstract.py | 3 +++ openpype/tools/ayon_loader/models/products.py | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/openpype/tools/ayon_loader/abstract.py b/openpype/tools/ayon_loader/abstract.py index bf3e81d485..1d93716e07 100644 --- a/openpype/tools/ayon_loader/abstract.py +++ b/openpype/tools/ayon_loader/abstract.py @@ -531,6 +531,9 @@ class FrontendLoaderController(_BaseLoaderController): Product types have defined if are checked for filtering or not. + Args: + project_name (Union[str, None]): Project name. + Returns: list[ProductTypeItem]: List of product type items for a project. """ diff --git a/openpype/tools/ayon_loader/models/products.py b/openpype/tools/ayon_loader/models/products.py index 135f28df97..40b6474d12 100644 --- a/openpype/tools/ayon_loader/models/products.py +++ b/openpype/tools/ayon_loader/models/products.py @@ -179,12 +179,15 @@ class ProductsModel: """Product type items for project. Args: - project_name (str): Project name. + project_name (Union[str, None]): Project name. Returns: list[ProductTypeItem]: Product type items. """ + if not project_name: + return [] + cache = self._product_type_items_cache[project_name] if not cache.is_valid: product_types = ayon_api.get_project_product_types(project_name) From 6290343ce7b9b1facebcb7148fc41f775d9ae5fe Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 5 Feb 2024 12:24:48 +0100 Subject: [PATCH 55/92] Use better resolution of Ayon apps on 4k display Special get_qt_app is used instead of shared get_openpype_qt_app as we don't want to set application icon. --- openpype/hosts/fusion/api/menu.py | 32 ++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/fusion/api/menu.py b/openpype/hosts/fusion/api/menu.py index 0b9ad1a43b..53ff5af767 100644 --- a/openpype/hosts/fusion/api/menu.py +++ b/openpype/hosts/fusion/api/menu.py @@ -173,8 +173,38 @@ class OpenPypeMenu(QtWidgets.QWidget): set_asset_framerange() +def get_qt_app(): + """Main Qt application.""" + + app = QtWidgets.QApplication.instance() + if app is None: + for attr_name in ( + "AA_EnableHighDpiScaling", + "AA_UseHighDpiPixmaps", + ): + attr = getattr(QtCore.Qt, attr_name, None) + if attr is not None: + QtWidgets.QApplication.setAttribute(attr) + + policy = os.getenv("QT_SCALE_FACTOR_ROUNDING_POLICY") + if ( + hasattr( + QtWidgets.QApplication, "setHighDpiScaleFactorRoundingPolicy" + ) + and not policy + ): + QtWidgets.QApplication.setHighDpiScaleFactorRoundingPolicy( + QtCore.Qt.HighDpiScaleFactorRoundingPolicy.PassThrough + ) + + app = QtWidgets.QApplication(sys.argv) + + return app + + def launch_openpype_menu(): - app = QtWidgets.QApplication(sys.argv) + + app = get_qt_app() pype_menu = OpenPypeMenu() From 78a3ec2118c9963e31ea0b4ca2907d0b84a14b39 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Mon, 5 Feb 2024 12:06:19 +0000 Subject: [PATCH 56/92] Fixed issue with double entries in the json manifest --- openpype/hosts/blender/api/render_lib.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/blender/api/render_lib.py b/openpype/hosts/blender/api/render_lib.py index fc47f5a659..c2792103e5 100644 --- a/openpype/hosts/blender/api/render_lib.py +++ b/openpype/hosts/blender/api/render_lib.py @@ -244,7 +244,9 @@ def set_node_tree(output_path, render_product, name, aov_sep, ext, multilayer): # and link it for rpass in passes: if rpass.name == "Image": - pass_name = "rgba" if multi_exr else "beauty" + if not multi_exr: + continue + pass_name = "rgba" else: pass_name = rpass.name filename = f"{name}{aov_sep}{pass_name}.####" @@ -263,6 +265,7 @@ def set_node_tree(output_path, render_product, name, aov_sep, ext, multilayer): pass_name = "composite" filename = f"{name}{aov_sep}{pass_name}.####" comp_socket = slots.new(pass_name if multi_exr else filename) + filepath = str(output_path / filename.lstrip("/")) aov_file_products.append(("Composite", filepath)) for link in tree.links: From 9452cdb06d289aced41d4bffb0104f6fc24d2ab5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 5 Feb 2024 13:45:04 +0100 Subject: [PATCH 57/92] define 'get_qt_app' in tools utils --- openpype/hosts/fusion/api/menu.py | 30 +----------------------------- openpype/tools/utils/__init__.py | 1 + openpype/tools/utils/lib.py | 23 +++++++++++++++++++---- 3 files changed, 21 insertions(+), 33 deletions(-) diff --git a/openpype/hosts/fusion/api/menu.py b/openpype/hosts/fusion/api/menu.py index 53ff5af767..ff4a734928 100644 --- a/openpype/hosts/fusion/api/menu.py +++ b/openpype/hosts/fusion/api/menu.py @@ -15,6 +15,7 @@ from openpype.hosts.fusion.api.lib import ( ) from openpype.pipeline import get_current_asset_name from openpype.resources import get_openpype_icon_filepath +from openpype.tools.utils import get_qt_app from .pipeline import FusionEventHandler from .pulse import FusionPulse @@ -173,35 +174,6 @@ class OpenPypeMenu(QtWidgets.QWidget): set_asset_framerange() -def get_qt_app(): - """Main Qt application.""" - - app = QtWidgets.QApplication.instance() - if app is None: - for attr_name in ( - "AA_EnableHighDpiScaling", - "AA_UseHighDpiPixmaps", - ): - attr = getattr(QtCore.Qt, attr_name, None) - if attr is not None: - QtWidgets.QApplication.setAttribute(attr) - - policy = os.getenv("QT_SCALE_FACTOR_ROUNDING_POLICY") - if ( - hasattr( - QtWidgets.QApplication, "setHighDpiScaleFactorRoundingPolicy" - ) - and not policy - ): - QtWidgets.QApplication.setHighDpiScaleFactorRoundingPolicy( - QtCore.Qt.HighDpiScaleFactorRoundingPolicy.PassThrough - ) - - app = QtWidgets.QApplication(sys.argv) - - return app - - def launch_openpype_menu(): app = get_qt_app() diff --git a/openpype/tools/utils/__init__.py b/openpype/tools/utils/__init__.py index 50d50f467a..74702a2a10 100644 --- a/openpype/tools/utils/__init__.py +++ b/openpype/tools/utils/__init__.py @@ -32,6 +32,7 @@ from .lib import ( set_style_property, DynamicQThread, qt_app_context, + get_qt_app, get_openpype_qt_app, get_asset_icon, get_asset_icon_by_name, diff --git a/openpype/tools/utils/lib.py b/openpype/tools/utils/lib.py index 365caaafd9..c7f92dd26e 100644 --- a/openpype/tools/utils/lib.py +++ b/openpype/tools/utils/lib.py @@ -154,11 +154,15 @@ def qt_app_context(): yield app -def get_openpype_qt_app(): - """Main Qt application initialized for OpenPype processed. +def get_qt_app(): + """Get Qt application. - This function should be used only inside OpenPype process and never inside - other processes. + The function initializes new Qt application if it is not already + initialized. It also sets some attributes to the application to + ensure that it will work properly on high DPI displays. + + Returns: + QtWidgets.QApplication: Current Qt application. """ app = QtWidgets.QApplication.instance() @@ -184,6 +188,17 @@ def get_openpype_qt_app(): app = QtWidgets.QApplication(sys.argv) + return app + + +def get_openpype_qt_app(): + """Main Qt application initialized for OpenPype processed. + + This function should be used only inside OpenPype process and never inside + other processes. + """ + + app = get_qt_app() app.setWindowIcon(QtGui.QIcon(get_app_icon_path())) return app From f83d0f974958ffef089e4fb362cf6f13514fe104 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 6 Feb 2024 14:25:12 +0800 Subject: [PATCH 58/92] AYON menu would be registered when the workspace has been changed --- openpype/hosts/max/api/pipeline.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/hosts/max/api/pipeline.py b/openpype/hosts/max/api/pipeline.py index d0ae854dc8..18e287266a 100644 --- a/openpype/hosts/max/api/pipeline.py +++ b/openpype/hosts/max/api/pipeline.py @@ -59,6 +59,8 @@ class MaxHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): rt.callbacks.addScript(rt.Name('filePostOpen'), lib.check_colorspace) + rt.callbacks.addScript(rt.Name('postWorkspaceChange'), + self._deferred_menu_creation) def has_unsaved_changes(self): # TODO: how to get it from 3dsmax? From f2429680ff5aa68b358ececeba02d24e9d86ad0c Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Tue, 6 Feb 2024 15:43:18 +0000 Subject: [PATCH 59/92] Fix old links and duplicated entries in the manifest --- openpype/hosts/blender/api/render_lib.py | 114 ++++++++++++----------- 1 file changed, 62 insertions(+), 52 deletions(-) diff --git a/openpype/hosts/blender/api/render_lib.py b/openpype/hosts/blender/api/render_lib.py index c2792103e5..17b9d926ec 100644 --- a/openpype/hosts/blender/api/render_lib.py +++ b/openpype/hosts/blender/api/render_lib.py @@ -178,6 +178,14 @@ def set_render_passes(settings, renderer): return aov_list, custom_passes +def _create_aov_slot(name, aov_sep, slots, rpass_name, multi_exr, output_path): + filename = f"{name}{aov_sep}{rpass_name}.####" + slot = slots.new(rpass_name if multi_exr else filename) + filepath = str(output_path / filename.lstrip("/")) + + return slot, filepath + + def set_node_tree(output_path, render_product, name, aov_sep, ext, multilayer): # Set the scene to use the compositor node tree to render bpy.context.scene.use_nodes = True @@ -188,40 +196,34 @@ def set_node_tree(output_path, render_product, name, aov_sep, ext, multilayer): output_type = "CompositorNodeOutputFile" compositor_type = "CompositorNodeComposite" - # Get the Render Layer and Composite nodes - rl_node = None - comp_node = None + # Get the Render Layer, Composite and the previous output nodes + render_layer_node = None + composite_node = None + old_output_node = None for node in tree.nodes: if node.bl_idname == comp_layer_type: - rl_node = node + render_layer_node = node elif node.bl_idname == compositor_type: - comp_node = node - if rl_node and comp_node: + composite_node = node + elif node.bl_idname == output_type and "AYON" in node.name: + old_output_node = node + if render_layer_node and composite_node and old_output_node: break # If there's not a Render Layers node, we create it - if not rl_node: - rl_node = tree.nodes.new(comp_layer_type) + if not render_layer_node: + render_layer_node = tree.nodes.new(comp_layer_type) # Get the enabled output sockets, that are the active passes for the # render. # We also exclude some layers. - exclude_sockets = ["Alpha", "Noisy Image"] + exclude_sockets = ["Image", "Alpha", "Noisy Image"] passes = [ socket - for socket in rl_node.outputs + for socket in render_layer_node.outputs if socket.enabled and socket.name not in exclude_sockets ] - old_output = None - - # Remove all output nodes that inlcude "AYON" in the name. - # There should be only one. - for node in tree.nodes: - if node.bl_idname == output_type and "AYON" in node.name: - old_output = node - break - # Create a new output node output = tree.nodes.new(output_type) @@ -240,46 +242,54 @@ def set_node_tree(output_path, render_product, name, aov_sep, ext, multilayer): aov_file_products = [] - # For each active render pass, we add a new socket to the output node - # and link it - for rpass in passes: - if rpass.name == "Image": - if not multi_exr: - continue - pass_name = "rgba" - else: - pass_name = rpass.name - filename = f"{name}{aov_sep}{pass_name}.####" + old_links = { + link.from_socket.name: link for link in tree.links + if link.to_node == old_output_node} - slots.new(pass_name if multi_exr else filename) - - filepath = str(output_path / filename.lstrip("/")) - - aov_file_products.append((rpass.name, filepath)) - - if not old_output: - node_input = output.inputs[-1] - tree.links.new(rpass, node_input) + # Create a new socket for the beauty output + pass_name = "rgba" if multi_exr else "beauty" + slot, _ = _create_aov_slot( + name, aov_sep, slots, pass_name, multi_exr, output_path) + tree.links.new(render_layer_node.outputs["Image"], slot) # Create a new socket for the composite output pass_name = "composite" - filename = f"{name}{aov_sep}{pass_name}.####" - comp_socket = slots.new(pass_name if multi_exr else filename) - filepath = str(output_path / filename.lstrip("/")) + comp_socket, filepath = _create_aov_slot( + name, aov_sep, slots, pass_name, multi_exr, output_path) aov_file_products.append(("Composite", filepath)) - for link in tree.links: - if link.to_node == old_output: - socket_name = link.to_socket.name - new_socket = output.inputs.get(socket_name) - if new_socket: - tree.links.new(link.from_socket, new_socket) - elif comp_node and link.to_node == comp_node: - tree.links.new(link.from_socket, comp_socket) + # For each active render pass, we add a new socket to the output node + # and link it + for rpass in passes: + slot, filepath = _create_aov_slot( + name, aov_sep, slots, rpass.name, multi_exr, output_path) + aov_file_products.append((rpass.name, filepath)) + + # If the rpass was not connected with the old output node, we connect + # it with the new one. + if not old_links.get(rpass.name): + tree.links.new(rpass, slot) + + for link in list(old_links.values()): + # Check if the socket is still available in the new output node. + socket = output.inputs.get(link.to_socket.name) + # If it is, we connect it with the new output node. + if socket: + tree.links.new(link.from_socket, socket) + # Then, we remove the old link. + tree.links.remove(link) + + # If there's a composite node, we connect its input with the new output + if composite_node: + for link in tree.links: + if link.to_node == composite_node: + tree.links.new(link.from_socket, comp_socket) + break + + if old_output_node: + output.location = old_output_node.location + tree.nodes.remove(old_output_node) - if old_output: - output.location = old_output.location - tree.nodes.remove(old_output) output.name = "AYON File Output" output.label = "AYON File Output" From 1c9c125dc2dcb6662c69cff95f881263b49711c5 Mon Sep 17 00:00:00 2001 From: Braden Jennings Date: Wed, 7 Feb 2024 11:00:33 +1300 Subject: [PATCH 60/92] enhancement/OP-8033 --- openpype/modules/timers_manager/timers_manager.py | 5 +++-- openpype/modules/timers_manager/widget_user_idle.py | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/modules/timers_manager/timers_manager.py b/openpype/modules/timers_manager/timers_manager.py index 674d834a1d..e684737d5e 100644 --- a/openpype/modules/timers_manager/timers_manager.py +++ b/openpype/modules/timers_manager/timers_manager.py @@ -162,6 +162,7 @@ class TimersManager( def tray_start(self, *_a, **_kw): if self._idle_manager: self._idle_manager.start() + self.show_message() def tray_exit(self): if self._idle_manager: @@ -373,8 +374,8 @@ class TimersManager( ).format(module.name)) def show_message(self): - if self.is_running is False: - return + # if self.is_running is False: + # return if not self._widget_user_idle.is_showed(): self._widget_user_idle.reset_countdown() self._widget_user_idle.show() diff --git a/openpype/modules/timers_manager/widget_user_idle.py b/openpype/modules/timers_manager/widget_user_idle.py index 9df328e6b2..8cc78cf102 100644 --- a/openpype/modules/timers_manager/widget_user_idle.py +++ b/openpype/modules/timers_manager/widget_user_idle.py @@ -17,6 +17,7 @@ class WidgetUserIdle(QtWidgets.QWidget): self.setWindowFlags( QtCore.Qt.WindowCloseButtonHint | QtCore.Qt.WindowMinimizeButtonHint + | QtCore.Qt.WindowStaysOnTopHint ) self._is_showed = False From 1d3c1c7c6c0bb5a0a4bd3c62e91c7a58e1a72c86 Mon Sep 17 00:00:00 2001 From: Braden Jennings Date: Wed, 7 Feb 2024 11:06:28 +1300 Subject: [PATCH 61/92] enhancement/OP-8033 --- openpype/modules/timers_manager/timers_manager.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/modules/timers_manager/timers_manager.py b/openpype/modules/timers_manager/timers_manager.py index e684737d5e..674d834a1d 100644 --- a/openpype/modules/timers_manager/timers_manager.py +++ b/openpype/modules/timers_manager/timers_manager.py @@ -162,7 +162,6 @@ class TimersManager( def tray_start(self, *_a, **_kw): if self._idle_manager: self._idle_manager.start() - self.show_message() def tray_exit(self): if self._idle_manager: @@ -374,8 +373,8 @@ class TimersManager( ).format(module.name)) def show_message(self): - # if self.is_running is False: - # return + if self.is_running is False: + return if not self._widget_user_idle.is_showed(): self._widget_user_idle.reset_countdown() self._widget_user_idle.show() From bac4f6d9bd2b53b97c01eb7d40bda7415b062e0b Mon Sep 17 00:00:00 2001 From: Ynbot Date: Wed, 7 Feb 2024 03:25:17 +0000 Subject: [PATCH 62/92] [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 db6da9f656..d105b0169e 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.18.7-nightly.1" +__version__ = "3.18.7-nightly.2" From 4243ee427cd24d9eb67fdd38c051c2388eb45e27 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 7 Feb 2024 03:25:56 +0000 Subject: [PATCH 63/92] chore(): update bug report / version --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 54a9d69bdc..f751a54116 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -35,6 +35,7 @@ body: label: Version description: What version are you running? Look to OpenPype Tray options: + - 3.18.7-nightly.2 - 3.18.7-nightly.1 - 3.18.6 - 3.18.6-nightly.2 @@ -134,7 +135,6 @@ body: - 3.15.10-nightly.1 - 3.15.9 - 3.15.9-nightly.2 - - 3.15.9-nightly.1 validations: required: true - type: dropdown From 5b9b26050d071b48c5141faffa46d439473f5f20 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 7 Feb 2024 10:47:41 +0100 Subject: [PATCH 64/92] feat: Add settings category for Tray Publisher This commit adds a new settings category for the Tray Publisher plugin in order to organize its configuration options more effectively. --- openpype/hosts/traypublisher/api/plugin.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/hosts/traypublisher/api/plugin.py b/openpype/hosts/traypublisher/api/plugin.py index 6859b85a46..a6075f0eb5 100644 --- a/openpype/hosts/traypublisher/api/plugin.py +++ b/openpype/hosts/traypublisher/api/plugin.py @@ -32,6 +32,7 @@ SHARED_DATA_KEY = "openpype.traypublisher.instances" class HiddenTrayPublishCreator(HiddenCreator): host_name = "traypublisher" + settings_category = "traypublisher" def collect_instances(self): instances_by_identifier = cache_and_get_instances( @@ -68,6 +69,7 @@ class HiddenTrayPublishCreator(HiddenCreator): class TrayPublishCreator(Creator): create_allow_context_change = True host_name = "traypublisher" + settings_category = "traypublisher" def collect_instances(self): instances_by_identifier = cache_and_get_instances( From 1fcdde0a9cc3a8b31697540ce320286d83d30e99 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 8 Feb 2024 13:46:56 +0100 Subject: [PATCH 65/92] AfterEffects: added toggle for applying values from DB during creation (#6204) * OP-8130 - After Effects added flag to force values from Asset to composition during creation This allows controlling setting of values (resolution, duration) from Asset (DB) to the created instance. Default is to set it automatically. * OP-8130 - Ayon version of Settings for AE creator --- openpype/hosts/aftereffects/plugins/create/create_render.py | 6 +++++- .../settings/defaults/project_settings/aftereffects.json | 3 ++- .../projects_schema/schema_project_aftereffects.json | 6 ++++++ .../aftereffects/server/settings/creator_plugins.py | 2 ++ server_addon/aftereffects/server/version.py | 2 +- 5 files changed, 16 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/aftereffects/plugins/create/create_render.py b/openpype/hosts/aftereffects/plugins/create/create_render.py index fadfc0c206..b4fb20f922 100644 --- a/openpype/hosts/aftereffects/plugins/create/create_render.py +++ b/openpype/hosts/aftereffects/plugins/create/create_render.py @@ -29,6 +29,7 @@ class RenderCreator(Creator): # Settings mark_for_review = True + force_setting_values = True def create(self, subset_name_from_ui, data, pre_create_data): stub = api.get_stub() # only after After Effects is up @@ -96,7 +97,9 @@ class RenderCreator(Creator): self._add_instance_to_context(new_instance) stub.rename_item(comp.id, subset_name) - set_settings(True, True, [comp.id], print_msg=False) + + if self.force_setting_values: + set_settings(True, True, [comp.id], print_msg=False) def get_pre_create_attr_defs(self): output = [ @@ -173,6 +176,7 @@ class RenderCreator(Creator): ) self.mark_for_review = plugin_settings["mark_for_review"] + self.force_setting_values = plugin_settings["force_setting_values"] self.default_variants = plugin_settings.get( "default_variants", plugin_settings.get("defaults") or [] diff --git a/openpype/settings/defaults/project_settings/aftereffects.json b/openpype/settings/defaults/project_settings/aftereffects.json index 77ccb74410..9e2ab7334b 100644 --- a/openpype/settings/defaults/project_settings/aftereffects.json +++ b/openpype/settings/defaults/project_settings/aftereffects.json @@ -15,7 +15,8 @@ "default_variants": [ "Main" ], - "mark_for_review": true + "mark_for_review": true, + "force_setting_values": true } }, "publish": { diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json b/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json index 72f09a641d..b0f8a7357f 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json @@ -42,6 +42,12 @@ "key": "mark_for_review", "label": "Review", "default": true + }, + { + "type": "boolean", + "key": "force_setting_values", + "label": "Force resolution and duration values from Asset", + "default": true } ] } diff --git a/server_addon/aftereffects/server/settings/creator_plugins.py b/server_addon/aftereffects/server/settings/creator_plugins.py index 988a036589..5d4ba30cd0 100644 --- a/server_addon/aftereffects/server/settings/creator_plugins.py +++ b/server_addon/aftereffects/server/settings/creator_plugins.py @@ -7,6 +7,8 @@ class CreateRenderPlugin(BaseSettingsModel): default_factory=list, title="Default Variants" ) + force_setting_values: bool = SettingsField( + True, title="Force resolution and duration values from Asset") class AfterEffectsCreatorPlugins(BaseSettingsModel): diff --git a/server_addon/aftereffects/server/version.py b/server_addon/aftereffects/server/version.py index df0c92f1e2..e57ad00718 100644 --- a/server_addon/aftereffects/server/version.py +++ b/server_addon/aftereffects/server/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring addon version.""" -__version__ = "0.1.2" +__version__ = "0.1.3" From b70b418dee0a08b320eed557490edc237a48a0ed Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 8 Feb 2024 14:34:41 +0100 Subject: [PATCH 66/92] update ayon unreal plugin --- openpype/hosts/unreal/integration | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/unreal/integration b/openpype/hosts/unreal/integration index 63266607ce..a4755d2869 160000 --- a/openpype/hosts/unreal/integration +++ b/openpype/hosts/unreal/integration @@ -1 +1 @@ -Subproject commit 63266607ceb972a61484f046634ddfc9eb0b5757 +Subproject commit a4755d2869694fcf58c98119298cde8d204e2ce4 From d502a26bfe55b8062dc383a00b61c8f21b490c99 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Thu, 8 Feb 2024 17:47:37 +0100 Subject: [PATCH 67/92] Update CONTRIBUTING.md --- CONTRIBUTING.md | 55 +++++++------------------------------------------ 1 file changed, 7 insertions(+), 48 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 644a74c1f7..2898c13acd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,53 +1,12 @@ -## How to contribute to Pype +## How to contribute to OpenPype -We are always happy for any contributions for OpenPype improvements. Before making a PR and starting working on an issue, please read these simple guidelines. +OpenPype has reached the end of its life and is now in a limited maintenance mode (read more at https://community.ynput.io/t/openpype-end-of-life-timeline/877). As such we're no longer accepting contributions unless they are also ported to AYON a the same time. -#### **Did you find a bug?** +## Getting my PR merged during this period -1. Check in the issues and our [bug triage[(https://github.com/pypeclub/pype/projects/2) to make sure it wasn't reported already. -2. Ask on our [discord](http://pype.community/chat) Often, what appears as a bug, might be the intended behaviour for someone else. -3. Create a new issue. -4. Use the issue template for you PR please. +- Each OpenPype PR MUST have a corresponding AYON PR in github. Without AYON compatibility features will not be merged! Luckily most of the code is compatible, albeit sometimes in a different place after refactor. Porting from OpenPype to AYON should be really easy. +- Please keep the corresponding OpenPype and AYON PR names the same so they can be easily identified. +Inside each PR, put a link to the corresponding PR from the other product. OpenPype PRs should point to AYON PR and vice versa. -#### **Did you write a patch that fixes a bug?** - -- Open a new GitHub pull request with the patch. -- Ensure the PR description clearly describes the problem and solution. Include the relevant issue number if applicable. - -#### **Do you intend to add a new feature or change an existing one?** - -- Open a new thread in the [github discussions](https://github.com/pypeclub/pype/discussions/new) -- Do not open issue until the suggestion is discussed. We will convert accepted suggestions into backlog and point them to the relevant discussion thread to keep the context. -- If you are already working on a new feature and you'd like it eventually merged to the main codebase, please consider making a DRAFT PR as soon as possible. This makes it a lot easier to give feedback, discuss the code and functionalit, plus it prevents multiple people tackling the same problem independently. - -#### **Do you have questions about the source code?** - -Open a new question on [github discussions](https://github.com/pypeclub/pype/discussions/new) - -## Branching Strategy - -As we move to 3.x as the primary supported version of pype and only keep 2.15 on bug bugfixes and client sponsored feature requests, we need to be very careful with merging strategy. - -We also use this opportunity to switch the branch naming. 3.0 production branch will no longer be called MASTER, but will be renamed to MAIN. Develop will stay as it is. - -A few important notes about 2.x and 3.x development: - -- 3.x features are not backported to 2.x unless specifically requested -- 3.x bugs and hotfixes can be ported to 2.x if they are relevant or severe -- 2.x features and bugs MUST be ported to 3.x at the same time - -## Pull Requests - -- Each 2.x PR MUST have a corresponding 3.x PR in github. Without 3.x PR, 2.x features will not be merged! Luckily most of the code is compatible, albeit sometimes in a different place after refactor. Porting from 2.x to 3.x should be really easy. -- Please keep the corresponding 2 and 3 PR names the same so they can be easily identified from the PR list page. -- Each 2.x PR should be labeled with `2.x-dev` label. - -Inside each PR, put a link to the corresponding PR for the other version - -Of course if you want to contribute, feel free to make a PR to only 2.x/develop or develop, based on what you are using. While reviewing the PRs, we might convert the code to corresponding PR for the other release ourselves. - -We might also change the target of you PR to and intermediate branch, rather than `develop` if we feel it requires some extra work on our end. That way, we preserve all your commits so you don't loose out on the contribution credits. - - -If a PR is targeted at 2.x release it must be labelled with 2x-dev label in Github. +AYON repository structure is a lot more granular compared to OpenPype. If you're unsure what repository you AYON equivalent PR should target, feel free to make OpenPype PR first and ask. From 39f3f777e5f05a40f7b31c7036c1ac79beae71d1 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Thu, 8 Feb 2024 17:55:31 +0100 Subject: [PATCH 68/92] Update CONTRIBUTING.md Co-authored-by: Petr Kalis --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2898c13acd..5f3fb90dfa 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ ## How to contribute to OpenPype -OpenPype has reached the end of its life and is now in a limited maintenance mode (read more at https://community.ynput.io/t/openpype-end-of-life-timeline/877). As such we're no longer accepting contributions unless they are also ported to AYON a the same time. +OpenPype has reached the end of its life and is now in a limited maintenance mode (read more at https://community.ynput.io/t/openpype-end-of-life-timeline/877). As such we're no longer accepting contributions unless they are also ported to AYON at the same time. ## Getting my PR merged during this period From f2add8f7f158e1d8237dd21816e08a018118832b Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Thu, 8 Feb 2024 17:55:36 +0100 Subject: [PATCH 69/92] Update CONTRIBUTING.md Co-authored-by: Petr Kalis --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5f3fb90dfa..27294b19be 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,4 +9,4 @@ OpenPype has reached the end of its life and is now in a limited maintenance mod Inside each PR, put a link to the corresponding PR from the other product. OpenPype PRs should point to AYON PR and vice versa. -AYON repository structure is a lot more granular compared to OpenPype. If you're unsure what repository you AYON equivalent PR should target, feel free to make OpenPype PR first and ask. +AYON repository structure is a lot more granular compared to OpenPype. If you're unsure what repository your AYON equivalent PR should target, feel free to make OpenPype PR first and ask. From 91917f20c21324e5be8fc69534d4a3dad23f50fe Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Thu, 8 Feb 2024 19:02:45 +0100 Subject: [PATCH 70/92] Update README.md --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a79b9f2582..5b8d3692dc 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,13 @@ OpenPype ## Important Notice! -OpenPype as a standalone product has reach end of it's life and this repository is now used as a pipeline core code for [AYON](https://ynput.io/ayon/). You can read more details about the end of life process here https://community.ynput.io/t/openpype-end-of-life-timeline/877 +OpenPype as a standalone product has reach end of it's life and this repository is now being phased out in favour of [ayon-core](https://github.com/ynput/ayon-core). You can read more details about the end of life process here https://community.ynput.io/t/openpype-end-of-life-timeline/877 +As such, we no longer accept Pull Requests that are not ported to AYON at the same time! + +``` +Please refer to https://github.com/ynput/OpenPype/blob/develop/CONTRIBUTING.md for more information about the current PR process. +``` Introduction ------------ From f17588b51d59f71eaffdbaf317773794a70608ff Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 9 Feb 2024 15:44:08 +0000 Subject: [PATCH 71/92] Fix getting non-existent settings --- openpype/hosts/nuke/api/plugin.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/api/plugin.py b/openpype/hosts/nuke/api/plugin.py index c8301b81fd..fe89a79096 100644 --- a/openpype/hosts/nuke/api/plugin.py +++ b/openpype/hosts/nuke/api/plugin.py @@ -1348,7 +1348,9 @@ def _remove_old_knobs(node): def exposed_write_knobs(settings, plugin_name, instance_node): - exposed_knobs = settings["nuke"]["create"][plugin_name]["exposed_knobs"] + exposed_knobs = settings["nuke"]["create"][plugin_name].get( + "exposed_knobs", [] + ) if exposed_knobs: instance_node.addKnob(nuke.Text_Knob('', 'Write Knobs')) write_node = nuke.allNodes(group=instance_node, filter="Write")[0] From f49781aae15060be76838016be437e74cb1441bd Mon Sep 17 00:00:00 2001 From: JackP Date: Fri, 9 Feb 2024 16:00:57 +0000 Subject: [PATCH 72/92] fix: typo in class function --- openpype/hosts/max/api/pipeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/api/pipeline.py b/openpype/hosts/max/api/pipeline.py index ce4afd2e8b..1b74b8131c 100644 --- a/openpype/hosts/max/api/pipeline.py +++ b/openpype/hosts/max/api/pipeline.py @@ -60,7 +60,7 @@ class MaxHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): rt.callbacks.addScript(rt.Name('filePostOpen'), lib.check_colorspace) - def workfiles_has_unsaved_changes(self): + def workfile_has_unsaved_changes(self): return rt.getSaveRequired() def get_workfile_extensions(self): From 960de700dd3ba6fb9c75fbff7abda39d60ab7c56 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 9 Feb 2024 17:33:30 +0100 Subject: [PATCH 73/92] OP-8165 - fix AE local render doesnt push thumbnail to Ftrack (#6212) Without thumbnail review is not clickable from main Versions list --- openpype/plugins/publish/extract_thumbnail.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index 10eb261482..291345abb1 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -35,6 +35,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): "traypublisher", "substancepainter", "nuke", + "aftereffects" ] enabled = False From 20e12d613e4c700d981f381044818c1ce6b746e5 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 9 Feb 2024 16:33:48 +0000 Subject: [PATCH 74/92] Fix exposed knobs validator --- openpype/hosts/nuke/plugins/publish/validate_exposed_knobs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/plugins/publish/validate_exposed_knobs.py b/openpype/hosts/nuke/plugins/publish/validate_exposed_knobs.py index fe5644f0c9..f592fc4a44 100644 --- a/openpype/hosts/nuke/plugins/publish/validate_exposed_knobs.py +++ b/openpype/hosts/nuke/plugins/publish/validate_exposed_knobs.py @@ -65,7 +65,7 @@ class ValidateExposedKnobs( group_node = instance.data["transientData"]["node"] nuke_settings = instance.context.data["project_settings"]["nuke"] create_settings = nuke_settings["create"][plugin] - exposed_knobs = create_settings["exposed_knobs"] + exposed_knobs = create_settings.get("exposed_knobs", []) unexposed_knobs = [] for knob in exposed_knobs: if knob not in group_node.knobs(): From 0e58b3efc665e7e4e5581c4238164cb3a95bac7b Mon Sep 17 00:00:00 2001 From: Ynbot Date: Sat, 10 Feb 2024 03:24:54 +0000 Subject: [PATCH 75/92] [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 d105b0169e..39fb10bb6e 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.18.7-nightly.2" +__version__ = "3.18.7-nightly.3" From ecaf8a6f6b1ebd256ad7dc45d934eaa4ee10ef40 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 10 Feb 2024 03:25:29 +0000 Subject: [PATCH 76/92] chore(): update bug report / version --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index f751a54116..7cf51713e4 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -35,6 +35,7 @@ body: label: Version description: What version are you running? Look to OpenPype Tray options: + - 3.18.7-nightly.3 - 3.18.7-nightly.2 - 3.18.7-nightly.1 - 3.18.6 @@ -134,7 +135,6 @@ body: - 3.15.10-nightly.2 - 3.15.10-nightly.1 - 3.15.9 - - 3.15.9-nightly.2 validations: required: true - type: dropdown From cf4bb19ad6dbf7221bc514d32422b609d3ccb489 Mon Sep 17 00:00:00 2001 From: Braden Jennings Date: Mon, 12 Feb 2024 11:30:35 +1300 Subject: [PATCH 77/92] enhancement/OP-7723_hidden_jonts_validator --- .../hosts/maya/plugins/publish/validate_rig_joints_hidden.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_joints_hidden.py b/openpype/hosts/maya/plugins/publish/validate_rig_joints_hidden.py index 30d95128a2..24762a4232 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_joints_hidden.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_joints_hidden.py @@ -7,6 +7,7 @@ from openpype.hosts.maya.api import lib from openpype.pipeline.publish import ( RepairAction, ValidateContentsOrder, + PublishValidationError ) @@ -38,7 +39,7 @@ class ValidateRigJointsHidden(pyblish.api.InstancePlugin): invalid = self.get_invalid(instance) if invalid: - raise ValueError("Visible joints found: {0}".format(invalid)) + raise PublishValidationError("Visible joints found: {0}".format(invalid)) @classmethod def repair(cls, instance): From 7f040bd32240198a988b027fa144dbf2bcbef1a9 Mon Sep 17 00:00:00 2001 From: Braden Jennings Date: Mon, 12 Feb 2024 11:36:06 +1300 Subject: [PATCH 78/92] enhancement/OP-7723_hidden_jonts_validator --- .../hosts/maya/plugins/publish/validate_rig_joints_hidden.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_joints_hidden.py b/openpype/hosts/maya/plugins/publish/validate_rig_joints_hidden.py index 24762a4232..2bb5036f8b 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_joints_hidden.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_joints_hidden.py @@ -39,7 +39,8 @@ class ValidateRigJointsHidden(pyblish.api.InstancePlugin): invalid = self.get_invalid(instance) if invalid: - raise PublishValidationError("Visible joints found: {0}".format(invalid)) + raise PublishValidationError( + "Visible joints found: {0}".format(invalid)) @classmethod def repair(cls, instance): From 858a90335fc792b20a34179b281f1859af7d94b6 Mon Sep 17 00:00:00 2001 From: Ynbot Date: Wed, 14 Feb 2024 03:25:48 +0000 Subject: [PATCH 79/92] [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 39fb10bb6e..9e1bd39b3a 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.18.7-nightly.3" +__version__ = "3.18.7-nightly.4" From b15643e2cd8d64f06a7cfa238aed705eaea4de2a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 14 Feb 2024 03:26:23 +0000 Subject: [PATCH 80/92] chore(): update bug report / version --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 7cf51713e4..bc0e00f740 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -35,6 +35,7 @@ body: label: Version description: What version are you running? Look to OpenPype Tray options: + - 3.18.7-nightly.4 - 3.18.7-nightly.3 - 3.18.7-nightly.2 - 3.18.7-nightly.1 @@ -134,7 +135,6 @@ body: - 3.15.10 - 3.15.10-nightly.2 - 3.15.10-nightly.1 - - 3.15.9 validations: required: true - type: dropdown From bcd216946859bfdb38b2d88df4541c9fe6319da7 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 14 Feb 2024 10:11:37 +0000 Subject: [PATCH 81/92] Cast aov_list to set to improve performance --- openpype/hosts/blender/api/render_lib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/blender/api/render_lib.py b/openpype/hosts/blender/api/render_lib.py index 17b9d926ec..bb35dd44ea 100644 --- a/openpype/hosts/blender/api/render_lib.py +++ b/openpype/hosts/blender/api/render_lib.py @@ -101,7 +101,7 @@ def set_render_format(ext, multilayer): def set_render_passes(settings, renderer): - aov_list = settings["blender"]["RenderSettings"]["aov_list"] + aov_list = set(settings["blender"]["RenderSettings"]["aov_list"]) custom_passes = settings["blender"]["RenderSettings"]["custom_passes"] # Common passes for both renderers @@ -175,7 +175,7 @@ def set_render_passes(settings, renderer): aov.type = (cp["value"] if AYON_SERVER_ENABLED else cp[1].get("type", "VALUE")) - return aov_list, custom_passes + return list(aov_list), custom_passes def _create_aov_slot(name, aov_sep, slots, rpass_name, multi_exr, output_path): From 6002da8235c32ac74eeb5e7c40f5153b8326f59d Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 14 Feb 2024 10:18:52 +0000 Subject: [PATCH 82/92] Added an option to disable composite output --- openpype/hosts/blender/api/render_lib.py | 29 ++++++++++++++----- .../defaults/project_settings/blender.json | 1 + .../schema_project_blender.json | 5 ++++ .../server/settings/render_settings.py | 4 +++ 4 files changed, 31 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/blender/api/render_lib.py b/openpype/hosts/blender/api/render_lib.py index bb35dd44ea..c09cfce502 100644 --- a/openpype/hosts/blender/api/render_lib.py +++ b/openpype/hosts/blender/api/render_lib.py @@ -56,6 +56,14 @@ def get_renderer(settings): ["renderer"]) +def get_compositing(settings): + """Get compositing from blender settings.""" + + return (settings["blender"] + ["RenderSettings"] + ["compositing"]) + + def get_render_product(output_path, name, aov_sep): """ Generate the path to the render product. Blender interprets the `#` @@ -186,7 +194,9 @@ def _create_aov_slot(name, aov_sep, slots, rpass_name, multi_exr, output_path): return slot, filepath -def set_node_tree(output_path, render_product, name, aov_sep, ext, multilayer): +def set_node_tree( + output_path, render_product, name, aov_sep, ext, multilayer, compositing +): # Set the scene to use the compositor node tree to render bpy.context.scene.use_nodes = True @@ -252,11 +262,12 @@ def set_node_tree(output_path, render_product, name, aov_sep, ext, multilayer): name, aov_sep, slots, pass_name, multi_exr, output_path) tree.links.new(render_layer_node.outputs["Image"], slot) - # Create a new socket for the composite output - pass_name = "composite" - comp_socket, filepath = _create_aov_slot( - name, aov_sep, slots, pass_name, multi_exr, output_path) - aov_file_products.append(("Composite", filepath)) + if compositing: + # Create a new socket for the composite output + pass_name = "composite" + comp_socket, filepath = _create_aov_slot( + name, aov_sep, slots, pass_name, multi_exr, output_path) + aov_file_products.append(("Composite", filepath)) # For each active render pass, we add a new socket to the output node # and link it @@ -280,7 +291,7 @@ def set_node_tree(output_path, render_product, name, aov_sep, ext, multilayer): tree.links.remove(link) # If there's a composite node, we connect its input with the new output - if composite_node: + if compositing and composite_node: for link in tree.links: if link.to_node == composite_node: tree.links.new(link.from_socket, comp_socket) @@ -323,6 +334,7 @@ def prepare_rendering(asset_group): ext = get_image_format(settings) multilayer = get_multilayer(settings) renderer = get_renderer(settings) + compositing = get_compositing(settings) set_render_format(ext, multilayer) bpy.context.scene.render.engine = renderer @@ -332,7 +344,8 @@ def prepare_rendering(asset_group): render_product = get_render_product(output_path, name, aov_sep) aov_file_product = set_node_tree( - output_path, render_product, name, aov_sep, ext, multilayer) + output_path, render_product, name, aov_sep, + ext, multilayer, compositing) # Clear the render filepath, so that the output is handled only by the # output node in the compositor. diff --git a/openpype/settings/defaults/project_settings/blender.json b/openpype/settings/defaults/project_settings/blender.json index 48f3ef8ef0..03a5400ced 100644 --- a/openpype/settings/defaults/project_settings/blender.json +++ b/openpype/settings/defaults/project_settings/blender.json @@ -23,6 +23,7 @@ "image_format": "exr", "multilayer_exr": true, "renderer": "CYCLES", + "compositing": true, "aov_list": ["combined"], "custom_passes": [] }, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json b/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json index bbed881ab0..13e460b74c 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json @@ -114,6 +114,11 @@ {"BLENDER_EEVEE": "Eevee"} ] }, + { + "key": "compositing", + "type": "boolean", + "label": "Enable Compositing" + }, { "key": "aov_list", "label": "AOVs to create", diff --git a/server_addon/blender/server/settings/render_settings.py b/server_addon/blender/server/settings/render_settings.py index a1f6d3114a..f992ea6fcc 100644 --- a/server_addon/blender/server/settings/render_settings.py +++ b/server_addon/blender/server/settings/render_settings.py @@ -127,6 +127,9 @@ class RenderSettingsModel(BaseSettingsModel): title="Renderer", enum_resolver=renderers_enum ) + compositing: bool = SettingsField( + title="Enable Compositing" + ) aov_list: list[str] = SettingsField( default_factory=list, enum_resolver=aov_list_enum, @@ -149,6 +152,7 @@ DEFAULT_RENDER_SETTINGS = { "image_format": "exr", "multilayer_exr": True, "renderer": "CYCLES", + "compositing": True, "aov_list": ["combined"], "custom_passes": [] } From ea00109d86dd5f40422ee0eabe046ed65d50586a Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 16 Feb 2024 12:35:05 +0000 Subject: [PATCH 83/92] Expose families transfer setting. --- openpype/settings/defaults/project_settings/deadline.json | 1 + .../schemas/projects_schema/schema_project_deadline.json | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/openpype/settings/defaults/project_settings/deadline.json b/openpype/settings/defaults/project_settings/deadline.json index b02cfa8207..0c4b282d10 100644 --- a/openpype/settings/defaults/project_settings/deadline.json +++ b/openpype/settings/defaults/project_settings/deadline.json @@ -129,6 +129,7 @@ "deadline_priority": 50, "publishing_script": "", "skip_integration_repre_list": [], + "families_transfer": ["render3d", "render2d", "ftrack", "slate"], "aov_filter": { "maya": [ ".*([Bb]eauty).*" diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json b/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json index 42dea33ef9..bb8e0b5cd4 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json @@ -693,6 +693,14 @@ "type": "text" } }, + { + "type": "list", + "key": "families_transfer", + "label": "List of family names to transfer\nto generated instances (AOVs for example).", + "object_type": { + "type": "text" + } + }, { "type": "dict-modifiable", "docstring": "Regular expression to filter for which subset review should be created in publish job.", From 89d69f7c94db6558b8da06014ef0a9861130b585 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 16 Feb 2024 16:02:50 +0000 Subject: [PATCH 84/92] Remove redundant instance_skeleton_data code. --- .../plugins/publish/submit_publish_job.py | 146 ------------------ 1 file changed, 146 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 82971daee5..4e9df976cd 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -321,7 +321,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, return deadline_publish_job_id - def process(self, instance): # type: (pyblish.api.Instance) -> None """Process plugin. @@ -338,151 +337,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, self.log.debug("Skipping local instance.") return - data = instance.data.copy() - context = instance.context - self.context = context - self.anatomy = instance.context.data["anatomy"] - - asset = data.get("asset") or context.data["asset"] - subset = data.get("subset") - - start = instance.data.get("frameStart") - if start is None: - start = context.data["frameStart"] - - end = instance.data.get("frameEnd") - if end is None: - end = context.data["frameEnd"] - - handle_start = instance.data.get("handleStart") - if handle_start is None: - handle_start = context.data["handleStart"] - - handle_end = instance.data.get("handleEnd") - if handle_end is None: - handle_end = context.data["handleEnd"] - - fps = instance.data.get("fps") - if fps is None: - fps = context.data["fps"] - - if data.get("extendFrames", False): - start, end = self._extend_frames( - asset, - subset, - start, - end, - data["overrideExistingFrame"]) - - try: - source = data["source"] - except KeyError: - source = context.data["currentFile"] - - success, rootless_path = ( - self.anatomy.find_root_template_from_path(source) - ) - if success: - source = rootless_path - - else: - # `rootless_path` is not set to `source` if none of roots match - self.log.warning(( - "Could not find root path for remapping \"{}\"." - " This may cause issues." - ).format(source)) - - family = "render" - if ("prerender" in instance.data["families"] or - "prerender.farm" in instance.data["families"]): - family = "prerender" - families = [family] - - # pass review to families if marked as review - do_not_add_review = False - if data.get("review"): - families.append("review") - elif data.get("review") is False: - self.log.debug("Instance has review explicitly disabled.") - do_not_add_review = True - - instance_skeleton_data = { - "family": family, - "subset": subset, - "families": families, - "asset": asset, - "frameStart": start, - "frameEnd": end, - "handleStart": handle_start, - "handleEnd": handle_end, - "frameStartHandle": start - handle_start, - "frameEndHandle": end + handle_end, - "comment": instance.data["comment"], - "fps": fps, - "source": source, - "extendFrames": data.get("extendFrames"), - "overrideExistingFrame": data.get("overrideExistingFrame"), - "pixelAspect": data.get("pixelAspect", 1), - "resolutionWidth": data.get("resolutionWidth", 1920), - "resolutionHeight": data.get("resolutionHeight", 1080), - "multipartExr": data.get("multipartExr", False), - "jobBatchName": data.get("jobBatchName", ""), - "useSequenceForReview": data.get("useSequenceForReview", True), - # map inputVersions `ObjectId` -> `str` so json supports it - "inputVersions": list(map(str, data.get("inputVersions", []))), - "colorspace": instance.data.get("colorspace"), - "stagingDir_persistent": instance.data.get( - "stagingDir_persistent", False - ) - } - - # skip locking version if we are creating v01 - instance_version = instance.data.get("version") # take this if exists - if instance_version != 1: - instance_skeleton_data["version"] = instance_version - - # transfer specific families from original instance to new render - for item in self.families_transfer: - if item in instance.data.get("families", []): - instance_skeleton_data["families"] += [item] - - # transfer specific properties from original instance based on - # mapping dictionary `instance_transfer` - for key, values in self.instance_transfer.items(): - if key in instance.data.get("families", []): - for v in values: - instance_skeleton_data[v] = instance.data.get(v) - - # look into instance data if representations are not having any - # which are having tag `publish_on_farm` and include them - for repre in instance.data.get("representations", []): - staging_dir = repre.get("stagingDir") - if staging_dir: - success, rootless_staging_dir = ( - self.anatomy.find_root_template_from_path( - staging_dir - ) - ) - if success: - repre["stagingDir"] = rootless_staging_dir - else: - self.log.warning(( - "Could not find root path for remapping \"{}\"." - " This may cause issues on farm." - ).format(staging_dir)) - repre["stagingDir"] = staging_dir - - if "publish_on_farm" in repre.get("tags"): - # create representations attribute of not there - if "representations" not in instance_skeleton_data.keys(): - instance_skeleton_data["representations"] = [] - - instance_skeleton_data["representations"].append(repre) - - instances = None - assert data.get("expectedFiles"), ("Submission from old Pype version" - " - missing expectedFiles") - anatomy = instance.context.data["anatomy"] instance_skeleton_data = create_skeleton_instance( From 9d612e1f0f9c5deb442931ba2cf633c112088518 Mon Sep 17 00:00:00 2001 From: Ynbot Date: Sat, 17 Feb 2024 03:25:05 +0000 Subject: [PATCH 85/92] [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 9e1bd39b3a..64d8075c4a 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.18.7-nightly.4" +__version__ = "3.18.7-nightly.5" From 6657b847b8ffd91e77b37e9639cef0b839dc720c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 17 Feb 2024 03:25:42 +0000 Subject: [PATCH 86/92] chore(): update bug report / version --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index bc0e00f740..4d48212d4a 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -35,6 +35,7 @@ body: label: Version description: What version are you running? Look to OpenPype Tray options: + - 3.18.7-nightly.5 - 3.18.7-nightly.4 - 3.18.7-nightly.3 - 3.18.7-nightly.2 @@ -134,7 +135,6 @@ body: - 3.15.11-nightly.1 - 3.15.10 - 3.15.10-nightly.2 - - 3.15.10-nightly.1 validations: required: true - type: dropdown From 6aa534dbc4af1a8f59381617332c7db16c8dd40c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 19 Feb 2024 16:44:17 +0100 Subject: [PATCH 87/92] fix value lowering in postlaunch hook --- openpype/modules/ftrack/launch_hooks/post_ftrack_changes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/ftrack/launch_hooks/post_ftrack_changes.py b/openpype/modules/ftrack/launch_hooks/post_ftrack_changes.py index 5c780a51c4..1876ff20eb 100644 --- a/openpype/modules/ftrack/launch_hooks/post_ftrack_changes.py +++ b/openpype/modules/ftrack/launch_hooks/post_ftrack_changes.py @@ -132,7 +132,7 @@ class PostFtrackHook(PostLaunchHook): if key in already_tested: continue - value = value.lower() + value = [i.lower() for i in value] if actual_status in value or "__any__" in value: if key != "__ignore__": next_status_name = key From e1f5bdb5a9a2c7225583ba7b2a164f548d76d8d4 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Tue, 20 Feb 2024 11:27:12 +0000 Subject: [PATCH 88/92] Removed redundant option in aov list --- .../entities/schemas/projects_schema/schema_project_blender.json | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json b/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json index 13e460b74c..2ffdc6070d 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json @@ -126,7 +126,6 @@ "multiselection": true, "defaults": "empty", "enum_items": [ - {"empty": "< empty >"}, {"combined": "Combined"}, {"z": "Z"}, {"mist": "Mist"}, From b0499edb1a53f17ffa4785909cefd444b3702d3a Mon Sep 17 00:00:00 2001 From: Ynbot Date: Tue, 20 Feb 2024 13:05:15 +0000 Subject: [PATCH 89/92] [Automated] Release --- CHANGELOG.md | 401 ++++++++++++++++++++++++++++++++++++++++++++ openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 403 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 009150ae7d..4ec3448570 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,407 @@ # Changelog +## [3.18.7](https://github.com/ynput/OpenPype/tree/3.18.7) + + +[Full Changelog](https://github.com/ynput/OpenPype/compare/3.18.6...3.18.7) + +### **🆕 New features** + + +
+Chore: Wrapper for click proposal #5928 + +This is a proposal how to resolve issues with `click` python module. Issue https://github.com/ynput/OpenPype/issues/5921 reported that in Houdini 20+ is our click clashing with click in houdini, where is expected higher version. We can't update our version to support older pythons (NOTE older Python 3). + + +___ + +
+ +### **🚀 Enhancements** + + +
+Maya: Add repair action to hidden joints validator #6214 + +Joints Hidden is missing repair action, this adds it back + + +___ + +
+ + +
+Blender: output node and EXR #6086 + +Output node now works correctly for Multilayer EXR and keeps existing links. The output now is handled entirely by the compositor node tree. + + +___ + +
+ + +
+AYON Switch tool: Keep version after switch #6104 + +Keep version if only representation did change. The AYON variant of https://github.com/ynput/OpenPype/pull/4629 + + +___ + +
+ + +
+Loader AYON: Reset loader window on open #6170 + +Make sure loader tool is reset on each show. + + +___ + +
+ + +
+Publisher: Show message with error on action failure #6179 + +This PR adds support for the publisher to show error message from running actions.Errors from actions will otherwise be hidden from user in various console outputs.Also include card for when action is finished. + + +___ + +
+ + +
+AYON Applications: Remove djvview group from default applications #6188 + +The djv does not have group defined in models so the values are not used anywhere. + + +___ + +
+ + +
+General: added fallback for broken ffprobe return #6189 + +Customer provided .exr returned width and height equal to 0 which caused error in `extract_thumbnail`. This tries to use oiiotool to get metadata about file, in our case it read it correctly. + + +___ + +
+ + +
+Photoshop: High scaling in UIs #6190 + +Use `get_openpype_qt_app` to create `QApplication` in Photoshop. + + +___ + +
+ + +
+Ftrack: Status update settings are not case insensitive. #6195 + +Make values for project_settings/ftrack/events/status_update case insensitive. + + +___ + +
+ + +
+Thumbnail product filtering #6197 + +This PR introduces subset filtering for thumbnail extraction. This is to skip passes like zdepth which is not needed and can cause issues with extraction. Also speeds up publishing. + + +___ + +
+ + +
+TimersManager: Idle dialog always on top #6201 + +Make stop timer dialog always on tophttps://app.clickup.com/t/6658547/OP-8033 + + +___ + +
+ + +
+AfterEffects: added toggle for applying values from DB during creation #6204 + +Previously values (resolution, duration) from Asset (eg. DB) were applied explicitly when instance of `render` product type was created. This PR adds toggle to Settings to disable this. (This allows artist to publish non standard length of composition, disabling of `Validate Scene Settings` is still required.) + + +___ + +
+ + +
+Unreal: Update plugin commit #6208 + +Updated unreal plugin to latest main. + + +___ + +
+ +### **🐛 Bug fixes** + + +
+Traypublisher: editorial avoid audio tracks processing #6038 + +Avoiding audio tracks from EDL editorial publishing. + + +___ + +
+ + +
+Resolve Inventory offsets clips when swapping versions #6128 + +Swapped version retain the offset and IDT of the timelime clip.closes: https://github.com/ynput/OpenPype/issues/6125 + + +___ + +
+ + +
+Publisher window as dialog #6176 + +Changing back Publisher window to QDialog. + + +___ + +
+ + +
+Nuke: Validate write node fix error report - OP-8088 #6183 + +Report error was not printing the expected values from settings, but instead the values on the write node, leading to confusing messages like: +``` +Traceback (most recent call last): + File "C:\Users\tokejepsen\AppData\Local\Ynput\AYON\dependency_packages\ayon_2310271602_windows.zip\dependencies\pyblish\plugin.py", line 527, in __explicit_process + runner(*args) + File "C:\Users\tokejepsen\OpenPype\openpype\hosts\nuke\plugins\publish\validate_write_nodes.py", line 135, in process + self._make_error(check) + File "C:\Users\tokejepsen\OpenPype\openpype\hosts\nuke\plugins\publish\validate_write_nodes.py", line 149, in _make_error + raise PublishXmlValidationError( +openpype.pipeline.publish.publish_plugins.PublishXmlValidationError: Write node's knobs values are not correct! +Knob 'channels' > Correct: `rgb` > Wrong: `rgb` +``` +This PR changes the error report to: +``` +Traceback (most recent call last): + File "C:\Users\tokejepsen\AppData\Local\Ynput\AYON\dependency_packages\ayon_2310271602_windows.zip\dependencies\pyblish\plugin.py", line 527, in __explicit_process + runner(*args) + File "C:\Users\tokejepsen\OpenPype\openpype\hosts\nuke\plugins\publish\validate_write_nodes.py", line 135, in process + self._make_error(check) + File "C:\Users\tokejepsen\OpenPype\openpype\hosts\nuke\plugins\publish\validate_write_nodes.py", line 149, in _make_error + raise PublishXmlValidationError( +openpype.pipeline.publish.publish_plugins.PublishXmlValidationError: Write node's knobs values are not correct! +Knob 'channels' > Expected: `['rg']` > Current: `rgb` +``` + + + +___ + +
+ + +
+Nuke: Camera product type loaded is not updating - OP-7973 #6184 + +When updating the camera this error would appear: +``` +(...)openpype/hosts/nuke/plugins/load/load_camera_abc.py", line 142, in update + camera_node = nuke.toNode(object_name) +TypeError: toNode() argument 1 must be str, not Node +``` + + + +___ + +
+ + +
+AYON settings: Use bundle name as variant in dev mode #6187 + +Make sure the bundle name is used in dev mode for settings variant. + + +___ + +
+ + +
+Fusion: fix unwanted change to field name in Settings #6193 + +It should be `image_format` but in previous refactoring PR it fell back to original `output_formats` which caused enum not to show up and propagate into plugin. + + +___ + +
+ + +
+Bugfix: AYON menu disappeared when the workspace has been changed in 3dsMax #6200 + +AYON plugins are not correctly registered when switching to different workspaces. + + +___ + +
+ + +
+TrayPublisher: adding settings category to base creator classes #6202 + +Settings are resolving correctly as they suppose to. + + +___ + +
+ + +
+Nuke: expose knobs backward compatibility fix - OP-8164 #6211 + +Fix backwards compatibility for settings `project_settings/nuke/create/CreateWriteRender/exposed_knobs`. + + +___ + +
+ + +
+AE: fix local render doesn't push thumbnail to Ftrack #6212 + +Without thumbnail review is not clickable from main Versions list + + +___ + +
+ + +
+Nuke: openpype expose knobs validator - OP-8166 #6213 + +Fix exposed knobs validator for backwards compatibility with missing settings. + + +___ + +
+ + +
+Ftrack: Post-launch hook fix value lowering #6221 + +Fix lowerin of values in status mapping. + + +___ + +
+ +### **🔀 Refactored code** + + +
+Maya: Remove `shelf` class and shelf build on maya `userSetup.py` #5837 + +Remove shelf builder logic. It appeared to be unused and had bugs. + + +___ + +
+ +### **Merged pull requests** + + +
+Max: updated implementation of save_scene + small QOL improvements to host #6186 + +- Removed `has_unsaved_changes` from Max host as it looks to have been unused and unimplemented. +- Added and implemented `workfile_has_unsaved_changes` to Max host. +- Mirrored the Houdini host to implement the above into `save_scene` publish for Max. +- Added a line to `startup.ms` which opens the usual 'default' menu inside of Max (see screenshots).Current (Likely opens this menu due to one or more of the startup scripts used to insert OP menu):New: + + +___ + +
+ + +
+Fusion: Use better resolution of Ayon apps on 4k display #6199 + +Changes size (makes it smaller) of Ayon apps (Workfiles, Loader) in Fusion on high definitions displays. + + +___ + +
+ + +
+Update CONTRIBUTING.md #6210 + +Updating contributing guidelines to reflect the EOL state of repository +___ + +
+ + +
+Deadline: Remove redundant instance_skeleton_data code - OP-8269 #6219 + +This PR https://github.com/ynput/OpenPype/pull/5186 re-introduced code about for the `instance_skeleton_data` but its actually not used since this variable gets overwritten later. + + +___ + +
+ + + + ## [3.18.6](https://github.com/ynput/OpenPype/tree/3.18.6) diff --git a/openpype/version.py b/openpype/version.py index 64d8075c4a..a389280775 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.18.7-nightly.5" +__version__ = "3.18.7" diff --git a/pyproject.toml b/pyproject.toml index 453833aae2..eef6a2e978 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.18.6" # OpenPype +version = "3.18.7" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From f06658af081435a879f336b6d962dbc669aafe13 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 20 Feb 2024 13:06:09 +0000 Subject: [PATCH 90/92] chore(): update bug report / version --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 4d48212d4a..7fe6c79259 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -35,6 +35,7 @@ body: label: Version description: What version are you running? Look to OpenPype Tray options: + - 3.18.7 - 3.18.7-nightly.5 - 3.18.7-nightly.4 - 3.18.7-nightly.3 @@ -134,7 +135,6 @@ body: - 3.15.11-nightly.2 - 3.15.11-nightly.1 - 3.15.10 - - 3.15.10-nightly.2 validations: required: true - type: dropdown From 7ea09acb3307507576ea248eb996f5431ce71c2c Mon Sep 17 00:00:00 2001 From: Ynbot Date: Wed, 21 Feb 2024 03:24:33 +0000 Subject: [PATCH 91/92] [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 a389280775..95203e17c9 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.18.7" +__version__ = "3.18.8-nightly.1" From 51026fbca803ec1281fdc30bc4c55314f36825fd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 21 Feb 2024 03:25:06 +0000 Subject: [PATCH 92/92] chore(): update bug report / version --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 7fe6c79259..c01ab5122c 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -35,6 +35,7 @@ body: label: Version description: What version are you running? Look to OpenPype Tray options: + - 3.18.8-nightly.1 - 3.18.7 - 3.18.7-nightly.5 - 3.18.7-nightly.4 @@ -134,7 +135,6 @@ body: - 3.15.11-nightly.3 - 3.15.11-nightly.2 - 3.15.11-nightly.1 - - 3.15.10 validations: required: true - type: dropdown