From f2b6e954a1ddb4662835f8d786e34e70100645fc Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Sep 2022 00:16:06 +0200 Subject: [PATCH 001/243] Avoid name conflict where `group_name != group_node` due to maya auto renaming new node --- openpype/hosts/maya/plugins/load/load_yeti_cache.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_yeti_cache.py b/openpype/hosts/maya/plugins/load/load_yeti_cache.py index 8435ba2493..abc0e6003c 100644 --- a/openpype/hosts/maya/plugins/load/load_yeti_cache.py +++ b/openpype/hosts/maya/plugins/load/load_yeti_cache.py @@ -73,8 +73,8 @@ class YetiCacheLoader(load.LoaderPlugin): c = colors.get(family) if c is not None: - cmds.setAttr(group_name + ".useOutlinerColor", 1) - cmds.setAttr(group_name + ".outlinerColor", + cmds.setAttr(group_node + ".useOutlinerColor", 1) + cmds.setAttr(group_node + ".outlinerColor", (float(c[0])/255), (float(c[1])/255), (float(c[2])/255) From 57b81b4b5b5cd4ab98bfb9d73a8a69ba208bb061 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 28 Oct 2022 14:41:58 +0200 Subject: [PATCH 002/243] hiero: loading effects --- .../hosts/hiero/plugins/load/load_effects.py | 259 ++++++++++++++++++ 1 file changed, 259 insertions(+) create mode 100644 openpype/hosts/hiero/plugins/load/load_effects.py diff --git a/openpype/hosts/hiero/plugins/load/load_effects.py b/openpype/hosts/hiero/plugins/load/load_effects.py new file mode 100644 index 0000000000..40f8d66d0c --- /dev/null +++ b/openpype/hosts/hiero/plugins/load/load_effects.py @@ -0,0 +1,259 @@ +import json +from collections import OrderedDict +from pprint import pprint +import six + +from openpype.pipeline import ( + AVALON_CONTAINER_ID, + load +) +from openpype.hosts.hiero import api as phiero +from openpype.hosts.hiero.api import tags + + +class LoadEffects(load.LoaderPlugin): + """Loading colorspace soft effect exported from nukestudio""" + + representations = ["effectJson"] + families = ["effect"] + + label = "Load Effects" + order = 0 + icon = "cc" + color = "white" + ignore_attr = ["useLifetime"] + + def load(self, context, name, namespace, data): + """ + Loading function to get the soft effects to particular read node + + Arguments: + context (dict): context of version + name (str): name of the version + namespace (str): asset name + data (dict): compulsory attribute > not used + + Returns: + nuke node: containerised nuke node object + """ + active_sequence = phiero.get_current_sequence() + active_track = phiero.get_current_track( + active_sequence, "LoadedEffects") + + # get main variables + version = context['version'] + version_data = version.get("data", {}) + vname = version.get("name", None) + namespace = namespace or context['asset']['name'] + object_name = "{}_{}".format(name, namespace) + + data_imprint = { + "source": version_data["source"], + "version": vname, + "author": version_data["author"], + } + + # getting file path + file = self.fname.replace("\\", "/") + + # getting data from json file with unicode conversion + with open(file, "r") as f: + json_f = {self.byteify(key): self.byteify(value) + for key, value in json.load(f).items()} + + # get correct order of nodes by positions on track and subtrack + nodes_order = self.reorder_nodes(json_f) + + used_subtracks = { + stitem.name(): stitem + for stitem in phiero.flatten(active_track.subTrackItems()) + } + + for ef_name, ef_val in nodes_order.items(): + pprint("_" * 100) + pprint(ef_name) + pprint(ef_val) + new_name = "{}_loaded".format(ef_name) + if new_name not in used_subtracks: + effect_track_item = active_track.createEffect( + effectType=ef_val["class"], + timelineIn=ef_val["timelineIn"], + timelineOut=ef_val["timelineOut"] + ) + effect_track_item.setName(new_name) + node = effect_track_item.node() + for knob_name, knob_value in ef_val["node"].items(): + if ( + not knob_value + or knob_name == "name" + ): + continue + node[knob_name].setValue(knob_value) + + self.containerise( + active_track, + name=name, + namespace=namespace, + object_name=object_name, + context=context, + loader=self.__class__.__name__, + data=data_imprint) + return + + def update(self, container, representation): + """Update the Loader's path + + Nuke automatically tries to reset some variables when changing + the loader's path to a new file. These automatic changes are to its + inputs: + + """ + pass + + def reorder_nodes(self, data): + new_order = OrderedDict() + trackNums = [v["trackIndex"] for k, v in data.items() + if isinstance(v, dict)] + subTrackNums = [v["subTrackIndex"] for k, v in data.items() + if isinstance(v, dict)] + + for trackIndex in range( + min(trackNums), max(trackNums) + 1): + for subTrackIndex in range( + min(subTrackNums), max(subTrackNums) + 1): + item = self.get_item(data, trackIndex, subTrackIndex) + if item is not {}: + new_order.update(item) + return new_order + + def get_item(self, data, trackIndex, subTrackIndex): + return {key: val for key, val in data.items() + if isinstance(val, dict) + if subTrackIndex == val["subTrackIndex"] + if trackIndex == val["trackIndex"]} + + def byteify(self, input): + """ + Converts unicode strings to strings + It goes through all dictionary + + Arguments: + input (dict/str): input + + Returns: + dict: with fixed values and keys + + """ + + if isinstance(input, dict): + return {self.byteify(key): self.byteify(value) + for key, value in input.items()} + elif isinstance(input, list): + return [self.byteify(element) for element in input] + elif isinstance(input, six.text_type): + return str(input) + else: + return input + + def switch(self, container, representation): + self.update(container, representation) + + def remove(self, container): + pass + + def containerise( + self, + track, + name, + namespace, + object_name, + context, + loader=None, + data=None + ): + """Bundle Hiero's object into an assembly and imprint it with metadata + + Containerisation enables a tracking of version, author and origin + for loaded assets. + + Arguments: + track_item (hiero.core.TrackItem): object to imprint as container + name (str): Name of resulting assembly + namespace (str): Namespace under which to host container + context (dict): Asset information + loader (str, optional): Name of node used to produce this container. + + Returns: + track_item (hiero.core.TrackItem): containerised object + + """ + + data_imprint = { + object_name: { + "schema": "openpype:container-2.0", + "id": AVALON_CONTAINER_ID, + "name": str(name), + "namespace": str(namespace), + "loader": str(loader), + "representation": str(context["representation"]["_id"]), + } + } + + if data: + for k, v in data.items(): + data_imprint[object_name].update({k: v}) + + self.log.debug("_ data_imprint: {}".format(data_imprint)) + self.set_track_openpype_tag(track, data_imprint) + + def set_track_openpype_tag(self, track, data=None): + """ + Set pype track item tag to input track_item. + + Attributes: + trackItem (hiero.core.TrackItem): hiero object + + Returns: + hiero.core.Tag + """ + data = data or {} + + # basic Tag's attribute + tag_data = { + "editable": "0", + "note": "OpenPype data container", + "icon": "openpype_icon.png", + "metadata": dict(data.items()) + } + # get available pype tag if any + _tag = self.get_track_openpype_tag(track) + + if _tag: + # it not tag then create one + tag = tags.update_tag(_tag, tag_data) + else: + # if pype tag available then update with input data + tag = tags.create_tag(phiero.pype_tag_name, tag_data) + # add it to the input track item + track.addTag(tag) + + return tag + + def get_track_openpype_tag(self, track): + """ + Get pype track item tag created by creator or loader plugin. + + Attributes: + trackItem (hiero.core.TrackItem): hiero object + + Returns: + hiero.core.Tag: hierarchy, orig clip attributes + """ + # get all tags from track item + _tags = track.tags() + if not _tags: + return None + for tag in _tags: + # return only correct tag defined by global name + if tag.name() == phiero.pype_tag_name: + return tag From b04fc48fbc475f671c0876c6d05cfca79c6d95c0 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 28 Oct 2022 16:49:38 +0200 Subject: [PATCH 003/243] hiero: fix - skip audio in collect effects --- openpype/hosts/hiero/plugins/publish/collect_clip_effects.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/hosts/hiero/plugins/publish/collect_clip_effects.py b/openpype/hosts/hiero/plugins/publish/collect_clip_effects.py index 8d2ed9a9c2..9489b1c4fb 100644 --- a/openpype/hosts/hiero/plugins/publish/collect_clip_effects.py +++ b/openpype/hosts/hiero/plugins/publish/collect_clip_effects.py @@ -16,6 +16,9 @@ class CollectClipEffects(pyblish.api.InstancePlugin): review_track_index = instance.context.data.get("reviewTrackIndex") item = instance.data["item"] + if "audio" in instance.data["family"]: + return + # frame range self.handle_start = instance.data["handleStart"] self.handle_end = instance.data["handleEnd"] From 21a3d2067e1732a14c3273a8ba6c2429ac8f7a19 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 28 Oct 2022 16:50:30 +0200 Subject: [PATCH 004/243] hiero: load effects update - adding order - adding clip in out definition --- .../hosts/hiero/plugins/load/load_effects.py | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/hiero/plugins/load/load_effects.py b/openpype/hosts/hiero/plugins/load/load_effects.py index 40f8d66d0c..3158f29d93 100644 --- a/openpype/hosts/hiero/plugins/load/load_effects.py +++ b/openpype/hosts/hiero/plugins/load/load_effects.py @@ -41,11 +41,13 @@ class LoadEffects(load.LoaderPlugin): active_sequence, "LoadedEffects") # get main variables - version = context['version'] + version = context["version"] version_data = version.get("data", {}) vname = version.get("name", None) - namespace = namespace or context['asset']['name'] + namespace = namespace or context["asset"]["name"] object_name = "{}_{}".format(name, namespace) + clip_in = context["asset"]["data"]["clipIn"] + clip_out = context["asset"]["data"]["clipOut"] data_imprint = { "source": version_data["source"], @@ -69,7 +71,8 @@ class LoadEffects(load.LoaderPlugin): for stitem in phiero.flatten(active_track.subTrackItems()) } - for ef_name, ef_val in nodes_order.items(): + loaded = False + for index_order, (ef_name, ef_val) in enumerate(nodes_order.items()): pprint("_" * 100) pprint(ef_name) pprint(ef_val) @@ -77,8 +80,10 @@ class LoadEffects(load.LoaderPlugin): if new_name not in used_subtracks: effect_track_item = active_track.createEffect( effectType=ef_val["class"], - timelineIn=ef_val["timelineIn"], - timelineOut=ef_val["timelineOut"] + timelineIn=clip_in, + timelineOut=clip_out, + subTrackIndex=index_order + ) effect_track_item.setName(new_name) node = effect_track_item.node() @@ -90,6 +95,12 @@ class LoadEffects(load.LoaderPlugin): continue node[knob_name].setValue(knob_value) + # make sure containerisation will happen + loaded = True + + if not loaded: + return + self.containerise( active_track, name=name, @@ -98,7 +109,6 @@ class LoadEffects(load.LoaderPlugin): context=context, loader=self.__class__.__name__, data=data_imprint) - return def update(self, container, representation): """Update the Loader's path From 49ebb5aa0118a8535250743400789efdf952ba90 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 31 Oct 2022 11:05:19 +0100 Subject: [PATCH 005/243] hiero: abstraction for effect loader tag operations --- openpype/hosts/hiero/api/lib.py | 62 +++++++++++++++++-- openpype/hosts/hiero/api/pipeline.py | 18 ++++-- .../hosts/hiero/plugins/load/load_effects.py | 52 ---------------- 3 files changed, 70 insertions(+), 62 deletions(-) diff --git a/openpype/hosts/hiero/api/lib.py b/openpype/hosts/hiero/api/lib.py index e5d35945af..9e626270f8 100644 --- a/openpype/hosts/hiero/api/lib.py +++ b/openpype/hosts/hiero/api/lib.py @@ -321,13 +321,67 @@ def get_track_item_pype_tag(track_item): return tag -def set_track_item_pype_tag(track_item, data=None): +def set_track_openpype_tag(track, data=None): """ - Set pype track item tag to input track_item. + Set openpype track tag to input track object. + + Attributes: + track (hiero.core.VideoTrack): hiero object + + Returns: + hiero.core.Tag + """ + data = data or {} + + # basic Tag's attribute + tag_data = { + "editable": "0", + "note": "OpenPype data container", + "icon": "openpype_icon.png", + "metadata": dict(data.items()) + } + # get available pype tag if any + _tag = get_track_openpype_tag(track) + + if _tag: + # it not tag then create one + tag = tags.update_tag(_tag, tag_data) + else: + # if pype tag available then update with input data + tag = tags.create_tag(self.pype_tag_name, tag_data) + # add it to the input track item + track.addTag(tag) + + return tag + + +def get_track_openpype_tag(track): + """ + Get pype track item tag created by creator or loader plugin. Attributes: trackItem (hiero.core.TrackItem): hiero object + Returns: + hiero.core.Tag: hierarchy, orig clip attributes + """ + # get all tags from track item + _tags = track.tags() + if not _tags: + return None + for tag in _tags: + # return only correct tag defined by global name + if tag.name() == self.pype_tag_name: + return tag + + +def set_trackitem_openpype_tag(track_item, data=None): + """ + Set openpype track tag to input track object. + + Attributes: + track (hiero.core.VideoTrack): hiero object + Returns: hiero.core.Tag """ @@ -1083,10 +1137,10 @@ def check_inventory_versions(track_items=None): project_name = legacy_io.active_project() filter_result = filter_containers(containers, project_name) for container in filter_result.latest: - set_track_color(container["_track_item"], clip_color) + set_track_color(container["_item"], clip_color) for container in filter_result.outdated: - set_track_color(container["_track_item"], clip_color_last) + set_track_color(container["_item"], clip_color_last) def selection_changed_timeline(event): diff --git a/openpype/hosts/hiero/api/pipeline.py b/openpype/hosts/hiero/api/pipeline.py index ea61dc4785..1b78159e04 100644 --- a/openpype/hosts/hiero/api/pipeline.py +++ b/openpype/hosts/hiero/api/pipeline.py @@ -17,6 +17,7 @@ from openpype.pipeline import ( ) from openpype.tools.utils import host_tools from . import lib, menu, events +import hiero log = Logger.get_logger(__name__) @@ -131,11 +132,12 @@ def ls(): yield container -def parse_container(track_item, validate=True): +def parse_container(item, validate=True): """Return container data from track_item's pype tag. Args: - track_item (hiero.core.TrackItem): A containerised track item. + item (hiero.core.TrackItem or hiero.core.VideoTrack): + A containerised track item. validate (bool)[optional]: validating with avalon scheme Returns: @@ -143,7 +145,11 @@ def parse_container(track_item, validate=True): """ # convert tag metadata to normal keys names - data = lib.get_track_item_pype_data(track_item) + if type(item) == hiero.core.VideoTrack: + data = lib.set_track_openpype_data(item) + else: + data = lib.set_track_item_pype_data(item) + if ( not data or data.get("id") != "pyblish.avalon.container" @@ -160,15 +166,15 @@ def parse_container(track_item, validate=True): required = ['schema', 'id', 'name', 'namespace', 'loader', 'representation'] - if not all(key in data for key in required): + if any(key not in data for key in required): return container = {key: data[key] for key in required} - container["objectName"] = track_item.name() + container["objectName"] = item.name() # Store reference to the node object - container["_track_item"] = track_item + container["_item"] = item return container diff --git a/openpype/hosts/hiero/plugins/load/load_effects.py b/openpype/hosts/hiero/plugins/load/load_effects.py index 3158f29d93..947655b4c8 100644 --- a/openpype/hosts/hiero/plugins/load/load_effects.py +++ b/openpype/hosts/hiero/plugins/load/load_effects.py @@ -215,55 +215,3 @@ class LoadEffects(load.LoaderPlugin): self.log.debug("_ data_imprint: {}".format(data_imprint)) self.set_track_openpype_tag(track, data_imprint) - - def set_track_openpype_tag(self, track, data=None): - """ - Set pype track item tag to input track_item. - - Attributes: - trackItem (hiero.core.TrackItem): hiero object - - Returns: - hiero.core.Tag - """ - data = data or {} - - # basic Tag's attribute - tag_data = { - "editable": "0", - "note": "OpenPype data container", - "icon": "openpype_icon.png", - "metadata": dict(data.items()) - } - # get available pype tag if any - _tag = self.get_track_openpype_tag(track) - - if _tag: - # it not tag then create one - tag = tags.update_tag(_tag, tag_data) - else: - # if pype tag available then update with input data - tag = tags.create_tag(phiero.pype_tag_name, tag_data) - # add it to the input track item - track.addTag(tag) - - return tag - - def get_track_openpype_tag(self, track): - """ - Get pype track item tag created by creator or loader plugin. - - Attributes: - trackItem (hiero.core.TrackItem): hiero object - - Returns: - hiero.core.Tag: hierarchy, orig clip attributes - """ - # get all tags from track item - _tags = track.tags() - if not _tags: - return None - for tag in _tags: - # return only correct tag defined by global name - if tag.name() == phiero.pype_tag_name: - return tag From dcf4688e1c8802510e04ac95f74c0968500a8c52 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 31 Oct 2022 11:16:47 +0100 Subject: [PATCH 006/243] hiero: renaming functions, with backward compatibility --- openpype/hosts/hiero/api/__init__.py | 12 ++-- openpype/hosts/hiero/api/lib.py | 69 +++++++++++-------- openpype/hosts/hiero/api/pipeline.py | 10 +-- .../plugins/publish/precollect_instances.py | 2 +- 4 files changed, 54 insertions(+), 39 deletions(-) diff --git a/openpype/hosts/hiero/api/__init__.py b/openpype/hosts/hiero/api/__init__.py index 781f846bbe..d0fb24b654 100644 --- a/openpype/hosts/hiero/api/__init__.py +++ b/openpype/hosts/hiero/api/__init__.py @@ -30,9 +30,9 @@ from .lib import ( get_timeline_selection, get_current_track, get_track_item_tags, - get_track_item_pype_tag, - set_track_item_pype_tag, - get_track_item_pype_data, + get_trackitem_openpype_tag, + set_trackitem_openpype_tag, + get_trackitem_openpype_data, set_publish_attribute, get_publish_attribute, imprint, @@ -85,9 +85,9 @@ __all__ = [ "get_timeline_selection", "get_current_track", "get_track_item_tags", - "get_track_item_pype_tag", - "set_track_item_pype_tag", - "get_track_item_pype_data", + "get_trackitem_openpype_tag", + "set_trackitem_openpype_tag", + "get_trackitem_openpype_data", "set_publish_attribute", "get_publish_attribute", "imprint", diff --git a/openpype/hosts/hiero/api/lib.py b/openpype/hosts/hiero/api/lib.py index 9e626270f8..b0da4ce7b3 100644 --- a/openpype/hosts/hiero/api/lib.py +++ b/openpype/hosts/hiero/api/lib.py @@ -301,26 +301,6 @@ def get_track_item_tags(track_item): return returning_tag_data -def get_track_item_pype_tag(track_item): - """ - Get pype track item tag created by creator or loader plugin. - - Attributes: - trackItem (hiero.core.TrackItem): hiero object - - Returns: - hiero.core.Tag: hierarchy, orig clip attributes - """ - # get all tags from track item - _tags = track_item.tags() - if not _tags: - return None - for tag in _tags: - # return only correct tag defined by global name - if tag.name() == self.pype_tag_name: - return tag - - def set_track_openpype_tag(track, data=None): """ Set openpype track tag to input track object. @@ -375,6 +355,41 @@ def get_track_openpype_tag(track): return tag +def get_track_item_pype_tag(track_item): + # backward compatibility alias + return get_trackitem_openpype_tag(track_item) + + +def set_track_item_pype_tag(track_item, data=None): + # backward compatibility alias + return set_trackitem_openpype_tag(track_item, data) + + +def get_track_item_pype_data(track_item): + # backward compatibility alias + return get_trackitem_openpype_data(track_item) + + +def get_trackitem_openpype_tag(track_item): + """ + Get pype track item tag created by creator or loader plugin. + + Attributes: + trackItem (hiero.core.TrackItem): hiero object + + Returns: + hiero.core.Tag: hierarchy, orig clip attributes + """ + # get all tags from track item + _tags = track_item.tags() + if not _tags: + return None + for tag in _tags: + # return only correct tag defined by global name + if tag.name() == self.pype_tag_name: + return tag + + def set_trackitem_openpype_tag(track_item, data=None): """ Set openpype track tag to input track object. @@ -395,7 +410,7 @@ def set_trackitem_openpype_tag(track_item, data=None): "metadata": dict(data.items()) } # get available pype tag if any - _tag = get_track_item_pype_tag(track_item) + _tag = get_trackitem_openpype_tag(track_item) if _tag: # it not tag then create one @@ -409,7 +424,7 @@ def set_trackitem_openpype_tag(track_item, data=None): return tag -def get_track_item_pype_data(track_item): +def get_trackitem_openpype_data(track_item): """ Get track item's pype tag data. @@ -421,7 +436,7 @@ def get_track_item_pype_data(track_item): """ data = {} # get pype data tag from track item - tag = get_track_item_pype_tag(track_item) + tag = get_trackitem_openpype_tag(track_item) if not tag: return None @@ -474,7 +489,7 @@ def imprint(track_item, data=None): """ data = data or {} - tag = set_track_item_pype_tag(track_item, data) + tag = set_trackitem_openpype_tag(track_item, data) # add publish attribute set_publish_attribute(tag, True) @@ -1084,7 +1099,7 @@ def sync_clip_name_to_data_asset(track_items_list): # get name and data ti_name = track_item.name() - data = get_track_item_pype_data(track_item) + data = get_trackitem_openpype_data(track_item) # ignore if no data on the clip or not publish instance if not data: @@ -1096,10 +1111,10 @@ def sync_clip_name_to_data_asset(track_items_list): if data["asset"] != ti_name: data["asset"] = ti_name # remove the original tag - tag = get_track_item_pype_tag(track_item) + tag = get_trackitem_openpype_tag(track_item) track_item.removeTag(tag) # create new tag with updated data - set_track_item_pype_tag(track_item, data) + set_trackitem_openpype_tag(track_item, data) print("asset was changed in clip: {}".format(ti_name)) diff --git a/openpype/hosts/hiero/api/pipeline.py b/openpype/hosts/hiero/api/pipeline.py index 1b78159e04..0c11f7072f 100644 --- a/openpype/hosts/hiero/api/pipeline.py +++ b/openpype/hosts/hiero/api/pipeline.py @@ -107,7 +107,7 @@ def containerise(track_item, data_imprint.update({k: v}) log.debug("_ data_imprint: {}".format(data_imprint)) - lib.set_track_item_pype_tag(track_item, data_imprint) + lib.set_trackitem_openpype_tag(track_item, data_imprint) return track_item @@ -192,7 +192,7 @@ def update_container(track_item, data=None): """ data = data or dict() - container = lib.get_track_item_pype_data(track_item) + container = lib.get_trackitem_openpype_data(track_item) for _key, _value in container.items(): try: @@ -201,7 +201,7 @@ def update_container(track_item, data=None): pass log.info("Updating container: `{}`".format(track_item.name())) - return bool(lib.set_track_item_pype_tag(track_item, container)) + return bool(lib.set_trackitem_openpype_tag(track_item, container)) def launch_workfiles_app(*args): @@ -278,11 +278,11 @@ def on_pyblish_instance_toggled(instance, old_value, new_value): instance, old_value, new_value)) from openpype.hosts.hiero.api import ( - get_track_item_pype_tag, + get_trackitem_openpype_tag, set_publish_attribute ) # Whether instances should be passthrough based on new value track_item = instance.data["item"] - tag = get_track_item_pype_tag(track_item) + tag = get_trackitem_openpype_tag(track_item) set_publish_attribute(tag, new_value) diff --git a/openpype/hosts/hiero/plugins/publish/precollect_instances.py b/openpype/hosts/hiero/plugins/publish/precollect_instances.py index 1fc4b1f696..bb02919b35 100644 --- a/openpype/hosts/hiero/plugins/publish/precollect_instances.py +++ b/openpype/hosts/hiero/plugins/publish/precollect_instances.py @@ -48,7 +48,7 @@ class PrecollectInstances(pyblish.api.ContextPlugin): self.log.debug("clip_name: {}".format(clip_name)) # get openpype tag data - tag_data = phiero.get_track_item_pype_data(track_item) + tag_data = phiero.get_trackitem_openpype_data(track_item) self.log.debug("__ tag_data: {}".format(pformat(tag_data))) if not tag_data: From a9ab5baac9903c5a307d737201b12e14ecdbbf85 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 31 Oct 2022 11:26:48 +0100 Subject: [PATCH 007/243] hiero: improving bckw compatibility after rename --- openpype/hosts/hiero/api/__init__.py | 7 +++ openpype/hosts/hiero/api/lib.py | 75 +++++++++++++++++++++------- 2 files changed, 65 insertions(+), 17 deletions(-) diff --git a/openpype/hosts/hiero/api/__init__.py b/openpype/hosts/hiero/api/__init__.py index d0fb24b654..f457d791f5 100644 --- a/openpype/hosts/hiero/api/__init__.py +++ b/openpype/hosts/hiero/api/__init__.py @@ -30,6 +30,9 @@ from .lib import ( get_timeline_selection, get_current_track, get_track_item_tags, + get_track_item_pype_tag, + set_track_item_pype_tag, + get_track_item_pype_data, get_trackitem_openpype_tag, set_trackitem_openpype_tag, get_trackitem_openpype_data, @@ -99,6 +102,10 @@ __all__ = [ "apply_colorspace_project", "apply_colorspace_clips", "get_sequence_pattern_and_padding", + # depricated + "get_track_item_pype_tag", + "set_track_item_pype_tag", + "get_track_item_pype_data", # plugins "CreatorWidget", diff --git a/openpype/hosts/hiero/api/lib.py b/openpype/hosts/hiero/api/lib.py index b0da4ce7b3..f4b80aea4e 100644 --- a/openpype/hosts/hiero/api/lib.py +++ b/openpype/hosts/hiero/api/lib.py @@ -7,11 +7,13 @@ import os import re import sys import platform +import functools +import warnings import ast import shutil import hiero -from Qt import QtWidgets +from Qt import QtWidgets, QtCore, QtXml from openpype.client import get_project from openpype.settings import get_project_settings @@ -20,15 +22,51 @@ from openpype.pipeline.load import filter_containers from openpype.lib import Logger from . import tags -try: - from PySide.QtCore import QFile, QTextStream - from PySide.QtXml import QDomDocument -except ImportError: - from PySide2.QtCore import QFile, QTextStream - from PySide2.QtXml import QDomDocument -# from opentimelineio import opentime -# from pprint import pformat +class DeprecatedWarning(DeprecationWarning): + pass + + +def deprecated(new_destination): + """Mark functions as deprecated. + + It will result in a warning being emitted when the function is used. + """ + + func = None + if callable(new_destination): + func = new_destination + new_destination = None + + def _decorator(decorated_func): + if new_destination is None: + warning_message = ( + " Please check content of deprecated function to figure out" + " possible replacement." + ) + else: + warning_message = " Please replace your usage with '{}'.".format( + new_destination + ) + + @functools.wraps(decorated_func) + def wrapper(*args, **kwargs): + warnings.simplefilter("always", DeprecatedWarning) + warnings.warn( + ( + "Call to deprecated function '{}'" + "\nFunction was moved or removed.{}" + ).format(decorated_func.__name__, warning_message), + category=DeprecatedWarning, + stacklevel=4 + ) + return decorated_func(*args, **kwargs) + return wrapper + + if func is None: + return _decorator + return _decorator(func) + log = Logger.get_logger(__name__) @@ -355,16 +393,19 @@ def get_track_openpype_tag(track): return tag +@deprecated("openpype.hosts.hiero.api.lib.get_trackitem_openpype_tag") def get_track_item_pype_tag(track_item): # backward compatibility alias return get_trackitem_openpype_tag(track_item) +@deprecated("openpype.hosts.hiero.api.lib.set_trackitem_openpype_tag") def set_track_item_pype_tag(track_item, data=None): # backward compatibility alias return set_trackitem_openpype_tag(track_item, data) +@deprecated("openpype.hosts.hiero.api.lib.get_trackitem_openpype_data") def get_track_item_pype_data(track_item): # backward compatibility alias return get_trackitem_openpype_data(track_item) @@ -901,22 +942,22 @@ def set_selected_track_items(track_items_list, sequence=None): def _read_doc_from_path(path): - # reading QDomDocument from HROX path - hrox_file = QFile(path) - if not hrox_file.open(QFile.ReadOnly): + # reading QtXml.QDomDocument from HROX path + hrox_file = QtCore.QFile(path) + if not hrox_file.open(QtCore.QFile.ReadOnly): raise RuntimeError("Failed to open file for reading") - doc = QDomDocument() + doc = QtXml.QDomDocument() doc.setContent(hrox_file) hrox_file.close() return doc def _write_doc_to_path(doc, path): - # write QDomDocument to path as HROX - hrox_file = QFile(path) - if not hrox_file.open(QFile.WriteOnly): + # write QtXml.QDomDocument to path as HROX + hrox_file = QtCore.QFile(path) + if not hrox_file.open(QtCore.QFile.WriteOnly): raise RuntimeError("Failed to open file for writing") - stream = QTextStream(hrox_file) + stream = QtCore.QTextStream(hrox_file) doc.save(stream, 1) hrox_file.close() From 04d1016dfa71d5630e7b920371d3b4ea42e2fcff Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 31 Oct 2022 11:43:34 +0100 Subject: [PATCH 008/243] hiero: update api --- openpype/hosts/hiero/api/__init__.py | 6 ++++ openpype/hosts/hiero/api/lib.py | 51 ++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/openpype/hosts/hiero/api/__init__.py b/openpype/hosts/hiero/api/__init__.py index f457d791f5..1fa40c9f74 100644 --- a/openpype/hosts/hiero/api/__init__.py +++ b/openpype/hosts/hiero/api/__init__.py @@ -30,6 +30,9 @@ from .lib import ( get_timeline_selection, get_current_track, get_track_item_tags, + get_track_openpype_tag, + set_track_openpype_tag, + get_track_openpype_data, get_track_item_pype_tag, set_track_item_pype_tag, get_track_item_pype_data, @@ -88,6 +91,9 @@ __all__ = [ "get_timeline_selection", "get_current_track", "get_track_item_tags", + "get_track_openpype_tag", + "set_track_openpype_tag", + "get_track_openpype_data", "get_trackitem_openpype_tag", "set_trackitem_openpype_tag", "get_trackitem_openpype_data", diff --git a/openpype/hosts/hiero/api/lib.py b/openpype/hosts/hiero/api/lib.py index f4b80aea4e..3c1d500e46 100644 --- a/openpype/hosts/hiero/api/lib.py +++ b/openpype/hosts/hiero/api/lib.py @@ -393,6 +393,57 @@ def get_track_openpype_tag(track): return tag +def get_track_openpype_data(track): + """ + Get track's openpype tag data. + + Attributes: + trackItem (hiero.core.VideoTrack): hiero object + + Returns: + dict: data found on pype tag + """ + return_data = {} + # get pype data tag from track item + tag = get_track_openpype_tag(track) + + if not tag: + return None + + # get tag metadata attribute + tag_data = deepcopy(dict(tag.metadata())) + + for obj_name, obj_data in tag_data.items(): + return_data[obj_name] = {} + + # convert tag metadata to normal keys names and values to correct types + for k, v in obj_data.items(): + + key = k.replace("tag.", "") + + try: + # capture exceptions which are related to strings only + if re.match(r"^[\d]+$", v): + value = int(v) + elif re.match(r"^True$", v): + value = True + elif re.match(r"^False$", v): + value = False + elif re.match(r"^None$", v): + value = None + elif re.match(r"^[\w\d_]+$", v): + value = v + else: + value = ast.literal_eval(v) + except (ValueError, SyntaxError) as msg: + log.warning(msg) + value = v + + return_data[obj_name][key] = value + + return return_data + + @deprecated("openpype.hosts.hiero.api.lib.get_trackitem_openpype_tag") def get_track_item_pype_tag(track_item): # backward compatibility alias From 66571cc8cded1b6329f839cd9425da2631531a67 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 31 Oct 2022 12:16:08 +0100 Subject: [PATCH 009/243] hiero: update parse_container and ls to new functionality accepting track containers --- openpype/hosts/hiero/api/pipeline.py | 85 +++++++++++++++++----------- 1 file changed, 51 insertions(+), 34 deletions(-) diff --git a/openpype/hosts/hiero/api/pipeline.py b/openpype/hosts/hiero/api/pipeline.py index 0c11f7072f..1ce8e4e1c5 100644 --- a/openpype/hosts/hiero/api/pipeline.py +++ b/openpype/hosts/hiero/api/pipeline.py @@ -124,11 +124,20 @@ def ls(): """ # get all track items from current timeline - all_track_items = lib.get_track_items() + all_items = lib.get_track_items() - for track_item in all_track_items: - container = parse_container(track_item) - if container: + # append all video tracks + for track in lib.get_current_sequence(): + if type(track) != hiero.core.VideoTrack: + continue + all_items.append(track) + + for item in all_items: + container = parse_container(item) + if isinstance(container, list): + for _c in container: + yield _c + elif container: yield container @@ -144,39 +153,47 @@ def parse_container(item, validate=True): dict: The container schema data for input containerized track item. """ + def data_to_container(item, data): + if ( + not data + or data.get("id") != "pyblish.avalon.container" + ): + return + + if validate and data and data.get("schema"): + schema.validate(data) + + if not isinstance(data, dict): + return + + # If not all required data return the empty container + required = ['schema', 'id', 'name', + 'namespace', 'loader', 'representation'] + + if any(key not in data for key in required): + return + + container = {key: data[key] for key in required} + + container["objectName"] = item.name() + + # Store reference to the node object + container["_item"] = item + + return container + # convert tag metadata to normal keys names if type(item) == hiero.core.VideoTrack: - data = lib.set_track_openpype_data(item) + return_list = [] + _data = lib.get_track_openpype_data(item) + # convert the data to list and validate them + for _, obj_data in _data.items(): + cotnainer = data_to_container(item, obj_data) + return_list.append(cotnainer) + return return_list else: - data = lib.set_track_item_pype_data(item) - - if ( - not data - or data.get("id") != "pyblish.avalon.container" - ): - return - - if validate and data and data.get("schema"): - schema.validate(data) - - if not isinstance(data, dict): - return - - # If not all required data return the empty container - required = ['schema', 'id', 'name', - 'namespace', 'loader', 'representation'] - - if any(key not in data for key in required): - return - - container = {key: data[key] for key in required} - - container["objectName"] = item.name() - - # Store reference to the node object - container["_item"] = item - - return container + _data = lib.get_track_item_pype_data(item) + return data_to_container(item, _data) def update_container(track_item, data=None): From 25b61d3fdf657f38db35c741456d680cb1c24b59 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 31 Oct 2022 12:17:34 +0100 Subject: [PATCH 010/243] hiero: refactor plugin to new abstracted functionality --- openpype/hosts/hiero/plugins/load/load_effects.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/hiero/plugins/load/load_effects.py b/openpype/hosts/hiero/plugins/load/load_effects.py index 947655b4c8..fa78684838 100644 --- a/openpype/hosts/hiero/plugins/load/load_effects.py +++ b/openpype/hosts/hiero/plugins/load/load_effects.py @@ -8,7 +8,6 @@ from openpype.pipeline import ( load ) from openpype.hosts.hiero import api as phiero -from openpype.hosts.hiero.api import tags class LoadEffects(load.LoaderPlugin): @@ -53,6 +52,7 @@ class LoadEffects(load.LoaderPlugin): "source": version_data["source"], "version": vname, "author": version_data["author"], + "children_names": [] } # getting file path @@ -95,6 +95,8 @@ class LoadEffects(load.LoaderPlugin): continue node[knob_name].setValue(knob_value) + # register all loaded children + data_imprint["children_names"].append(new_name) # make sure containerisation will happen loaded = True @@ -187,11 +189,13 @@ class LoadEffects(load.LoaderPlugin): for loaded assets. Arguments: - track_item (hiero.core.TrackItem): object to imprint as container + track (hiero.core.VideoTrack): object to imprint as container name (str): Name of resulting assembly namespace (str): Namespace under which to host container + object_name (str): name of container context (dict): Asset information - loader (str, optional): Name of node used to produce this container. + loader (str, optional): Name of node used to produce this + container. Returns: track_item (hiero.core.TrackItem): containerised object @@ -214,4 +218,4 @@ class LoadEffects(load.LoaderPlugin): data_imprint[object_name].update({k: v}) self.log.debug("_ data_imprint: {}".format(data_imprint)) - self.set_track_openpype_tag(track, data_imprint) + phiero.set_track_openpype_tag(track, data_imprint) From 3d55d4d9554c5b844da7697e7f1efea6f3ffa303 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 31 Oct 2022 19:36:44 +0800 Subject: [PATCH 011/243] Alembic Loader as Arnold Standin --- .../maya/plugins/load/load_abc_to_standin.py | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 openpype/hosts/maya/plugins/load/load_abc_to_standin.py diff --git a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py new file mode 100644 index 0000000000..defed4bd73 --- /dev/null +++ b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py @@ -0,0 +1,115 @@ +import os +import clique + +from openpype.pipeline import ( + load, + get_representation_path +) +from openpype.settings import get_project_settings + + +class AlembicStandinLoader(load.LoaderPlugin): + """Load Alembic as Arnold Standin""" + + families = ["model", "pointcache"] + representations = ["abc"] + + label = "Import Alembic as Standin" + order = -5 + icon = "code-fork" + color = "orange" + + def load(self, context, name, namespace, options): + + import maya.cmds as cmds + import pymel.core as pm + import mtoa.ui.arnoldmenu + from openpype.hosts.maya.api.pipeline import containerise + from openpype.hosts.maya.api.lib import unique_namespace + + version = context["version"] + version_data = version.get("data", {}) + + self.log.info("version_data: {}\n".format(version_data)) + + frameStart = version_data.get("frameStart", None) + + asset = context["asset"]["name"] + namespace = namespace or unique_namespace( + asset + "_", + prefix="_" if asset[0].isdigit() else "", + suffix="_", + ) + + #Root group + label = "{}:{}".format(namespace, name) + root = pm.group(name=label, empty=True) + + settings = get_project_settings(os.environ['AVALON_PROJECT']) + colors = settings["maya"]["load"]["colors"] + + c = colors.get('ass') + if c is not None: + cmds.setAttr(root + ".useOutlinerColor", 1) + cmds.setAttr(root + ".outlinerColor", + c[0], c[1], c[2]) + + transform_name = label + "_ABC" + + standinShape = pm.PyNode(mtoa.ui.arnoldmenu.createStandIn()) + standin = standinShape.getParent() + standin.rename(transform_name) + + pm.parent(standin, root) + + # Set the standin filepath + standinShape.dso.set(self.fname) + if frameStart is not None: + standinShape.useFrameExtension.set(1) + + nodes = [root, standin] + self[:] = nodes + + return containerise( + name=name, + namespace=namespace, + nodes=nodes, + context=context, + loader=self.__class__.__name__) + + def update(self, container, representation): + + import pymel.core as pm + + path = get_representation_path(representation) + + # Update the standin + standins = list() + members = pm.sets(container['objectName'], query=True) + for member in members: + shape = member.getShape() + if (shape and shape.type() == "aiStandIn"): + standins.append(shape) + + for standin in standins: + standin.dso.set(path) + standin.useFrameExtension.set(1) + + container = pm.PyNode(container["objectName"]) + container.representation.set(str(representation["_id"])) + + def switch(self, container, representation): + self.update(container, representation) + + def remove(self, container): + import maya.cmds as cmds + members = cmds.sets(container['objectName'], query=True) + cmds.lockNode(members, lock=False) + cmds.delete([container['objectName']] + members) + + # Clean up the namespace + try: + cmds.namespace(removeNamespace=container['namespace'], + deleteNamespaceContent=True) + except RuntimeError: + pass From 7f88049d2a38e46fb933cbf23859529d46976915 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 31 Oct 2022 19:56:10 +0800 Subject: [PATCH 012/243] Alembic Loader as Arnold Standin --- openpype/hosts/maya/plugins/load/load_abc_to_standin.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py index defed4bd73..f39aa56650 100644 --- a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py +++ b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py @@ -1,5 +1,4 @@ import os -import clique from openpype.pipeline import ( load, @@ -41,7 +40,7 @@ class AlembicStandinLoader(load.LoaderPlugin): suffix="_", ) - #Root group + # Root group label = "{}:{}".format(namespace, name) root = pm.group(name=label, empty=True) From ac2f268575327c2e82d1cc9ca0a231caf54ea322 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Mon, 31 Oct 2022 13:28:50 +0100 Subject: [PATCH 013/243] Feature: Auto download last published workfile as first workfile --- .../hooks/pre_copy_last_published_workfile.py | 124 ++++++++++++++++++ openpype/modules/sync_server/sync_server.py | 9 +- 2 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 openpype/hooks/pre_copy_last_published_workfile.py diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py new file mode 100644 index 0000000000..004f9d25e7 --- /dev/null +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -0,0 +1,124 @@ +import gc +import os +import shutil +from openpype.client.entities import ( + get_last_version_by_subset_id, + get_representations, + get_subsets, +) +from openpype.lib import PreLaunchHook +from openpype.modules.base import ModulesManager +from openpype.pipeline.load.utils import get_representation_path + + +class CopyLastPublishedWorkfile(PreLaunchHook): + """Copy last published workfile as first workfile. + + Prelaunch hook works only if last workfile leads to not existing file. + - That is possible only if it's first version. + """ + + # Before `AddLastWorkfileToLaunchArgs` + order = -1 + app_groups = ["blender", "photoshop", "tvpaint", "aftereffects"] + + def execute(self): + """Check if local workfile doesn't exist, else copy it. + + 1- Check if setting for this feature is enabled + 2- Check if workfile in work area doesn't exist + 3- Check if published workfile exists and is copied locally in publish + + Returns: + None: This is a void method. + """ + # TODO setting + self.log.info("Trying to fetch last published workfile...") + + last_workfile = self.data.get("last_workfile_path") + if os.path.exists(last_workfile): + self.log.debug( + "Last workfile exists. Skipping {} process.".format( + self.__class__.__name__ + ) + ) + return + + project_name = self.data["project_name"] + task_name = self.data["task_name"] + + project_doc = self.data.get("project_doc") + asset_doc = self.data.get("asset_doc") + anatomy = self.data.get("anatomy") + if project_doc and asset_doc: + # Get subset id + subset_id = next( + ( + subset["_id"] + for subset in get_subsets( + project_name, + asset_ids=[asset_doc["_id"]], + fields=["_id", "data.family"], + ) + if subset["data"]["family"] == "workfile" + ), + None, + ) + if not subset_id: + return + + # Get workfile representation + workfile_representation = next( + ( + representation + for representation in get_representations( + project_name, + version_ids=[ + get_last_version_by_subset_id( + project_name, subset_id, fields=["_id"] + )["_id"] + ], + ) + if representation["context"]["task"]["name"] == task_name + ), + None, + ) + + if workfile_representation: # TODO add setting + # Get sync server from Tray, which handles the asynchronous thread instance + sync_server = next( + ( + t["sync_server"] + for t in [ + obj + for obj in gc.get_objects() + if isinstance(obj, ModulesManager) + ] + if t["sync_server"].sync_server_thread + ), + None, + ) + + # Add site and reset timer + active_site = sync_server.get_active_site(project_name) + sync_server.add_site( + project_name, + workfile_representation["_id"], + active_site, + force=True, + ) + sync_server.reset_timer() + + # Wait for the download loop to end + sync_server.sync_server_thread.files_processed.wait() + + # Get paths + published_workfile_path = get_representation_path( + workfile_representation, root=anatomy.roots + ) + local_workfile_dir = os.path.dirname(last_workfile) + + # Copy file and substitute path + self.data["last_workfile_path"] = shutil.copy( + published_workfile_path, local_workfile_dir + ) diff --git a/openpype/modules/sync_server/sync_server.py b/openpype/modules/sync_server/sync_server.py index 8b11055e65..def9e6cfd8 100644 --- a/openpype/modules/sync_server/sync_server.py +++ b/openpype/modules/sync_server/sync_server.py @@ -236,7 +236,11 @@ class SyncServerThread(threading.Thread): """ def __init__(self, module): self.log = Logger.get_logger(self.__class__.__name__) - super(SyncServerThread, self).__init__() + + # Event to trigger files have been processed + self.files_processed = threading.Event() + + super(SyncServerThread, self).__init__(args=(self.files_processed,)) self.module = module self.loop = None self.is_running = False @@ -396,6 +400,8 @@ class SyncServerThread(threading.Thread): representation, site, error) + # Trigger files are processed + self.files_processed.set() duration = time.time() - start_time self.log.debug("One loop took {:.2f}s".format(duration)) @@ -454,6 +460,7 @@ class SyncServerThread(threading.Thread): async def run_timer(self, delay): """Wait for 'delay' seconds to start next loop""" + self.files_processed.clear() await asyncio.sleep(delay) def reset_timer(self): From b12bb8723040d3c76cff92f6fd1c7b1bef9a5549 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 31 Oct 2022 13:57:03 +0100 Subject: [PATCH 014/243] hiero: refactor update container function --- openpype/hosts/hiero/api/pipeline.py | 41 ++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/hiero/api/pipeline.py b/openpype/hosts/hiero/api/pipeline.py index 1ce8e4e1c5..1e4158261c 100644 --- a/openpype/hosts/hiero/api/pipeline.py +++ b/openpype/hosts/hiero/api/pipeline.py @@ -196,29 +196,46 @@ def parse_container(item, validate=True): return data_to_container(item, _data) -def update_container(track_item, data=None): - """Update container data to input track_item's pype tag. +def update_container(item, data=None): + """Update container data to input track_item or track's + openpype tag. Args: - track_item (hiero.core.TrackItem): A containerised track item. + item (hiero.core.TrackItem or hiero.core.VideoTrack): + A containerised track item. data (dict)[optional]: dictionery with data to be updated Returns: bool: True if container was updated correctly """ - data = data or dict() + def update_container_data(container, data): + for key in container: + try: + container[key] = data[key] + except KeyError: + pass + return container - container = lib.get_trackitem_openpype_data(track_item) + data = data or {} - for _key, _value in container.items(): - try: - container[_key] = data[_key] - except KeyError: - pass + if type(item) == hiero.core.VideoTrack: + object_name = "{}_{}".format( + data["name"], data["namespace"]) + containers = lib.get_track_openpype_data(item) + for obj_name, container in containers.items(): + if object_name != obj_name: + continue + updated_container = update_container_data(container, data) + containers.update(updated_container) - log.info("Updating container: `{}`".format(track_item.name())) - return bool(lib.set_trackitem_openpype_tag(track_item, container)) + return bool(lib.set_track_openpype_tag(item, containers)) + else: + container = lib.get_trackitem_openpype_data(item) + updated_container = update_container_data(container, data) + + log.info("Updating container: `{}`".format(item.name())) + return bool(lib.set_trackitem_openpype_tag(item, updated_container)) def launch_workfiles_app(*args): From 4ab8fd1a822d6c2e9f60d3eb4933eee61e381208 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 31 Oct 2022 14:01:44 +0100 Subject: [PATCH 015/243] hiero: updating doc strings --- openpype/hosts/hiero/api/pipeline.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openpype/hosts/hiero/api/pipeline.py b/openpype/hosts/hiero/api/pipeline.py index 1e4158261c..e9e16ef5b1 100644 --- a/openpype/hosts/hiero/api/pipeline.py +++ b/openpype/hosts/hiero/api/pipeline.py @@ -220,13 +220,19 @@ def update_container(item, data=None): data = data or {} if type(item) == hiero.core.VideoTrack: + # form object data for test object_name = "{}_{}".format( data["name"], data["namespace"]) + + # get all available containers containers = lib.get_track_openpype_data(item) for obj_name, container in containers.items(): + # ignore all which are not the same object if object_name != obj_name: continue + # update data in container updated_container = update_container_data(container, data) + # merge updated container back to containers containers.update(updated_container) return bool(lib.set_track_openpype_tag(item, containers)) From 65e7c45e94ed1fb19dc512ce8ced91506dd2efec Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 31 Oct 2022 17:13:09 +0100 Subject: [PATCH 016/243] hiero: wip updating effect containers --- openpype/hosts/hiero/api/lib.py | 32 ++++--------------- openpype/hosts/hiero/api/pipeline.py | 12 ++++--- openpype/hosts/hiero/api/tags.py | 22 ++++++++----- .../hosts/hiero/plugins/load/load_effects.py | 1 + 4 files changed, 29 insertions(+), 38 deletions(-) diff --git a/openpype/hosts/hiero/api/lib.py b/openpype/hosts/hiero/api/lib.py index 3c1d500e46..d04a710df1 100644 --- a/openpype/hosts/hiero/api/lib.py +++ b/openpype/hosts/hiero/api/lib.py @@ -9,6 +9,7 @@ import sys import platform import functools import warnings +import json import ast import shutil import hiero @@ -414,32 +415,11 @@ def get_track_openpype_data(track): tag_data = deepcopy(dict(tag.metadata())) for obj_name, obj_data in tag_data.items(): - return_data[obj_name] = {} - - # convert tag metadata to normal keys names and values to correct types - for k, v in obj_data.items(): - - key = k.replace("tag.", "") - - try: - # capture exceptions which are related to strings only - if re.match(r"^[\d]+$", v): - value = int(v) - elif re.match(r"^True$", v): - value = True - elif re.match(r"^False$", v): - value = False - elif re.match(r"^None$", v): - value = None - elif re.match(r"^[\w\d_]+$", v): - value = v - else: - value = ast.literal_eval(v) - except (ValueError, SyntaxError) as msg: - log.warning(msg) - value = v - - return_data[obj_name][key] = value + obj_name = obj_name.replace("tag.", "") + print(obj_name) + if obj_name in ["applieswhole", "note", "label"]: + continue + return_data[obj_name] = json.loads(obj_data) return return_data diff --git a/openpype/hosts/hiero/api/pipeline.py b/openpype/hosts/hiero/api/pipeline.py index e9e16ef5b1..26c8ebe6d3 100644 --- a/openpype/hosts/hiero/api/pipeline.py +++ b/openpype/hosts/hiero/api/pipeline.py @@ -133,12 +133,13 @@ def ls(): all_items.append(track) for item in all_items: - container = parse_container(item) + container_data = parse_container(item) if isinstance(container, list): - for _c in container: + if isinstance(container_data, list): + for _c in container_data: yield _c - elif container: - yield container + elif container_data: + yield container_data def parse_container(item, validate=True): @@ -186,6 +187,9 @@ def parse_container(item, validate=True): if type(item) == hiero.core.VideoTrack: return_list = [] _data = lib.get_track_openpype_data(item) + log.info("_data: {}".format(_data)) + if not _data: + return # convert the data to list and validate them for _, obj_data in _data.items(): cotnainer = data_to_container(item, obj_data) diff --git a/openpype/hosts/hiero/api/tags.py b/openpype/hosts/hiero/api/tags.py index fac26da03a..918af3dc1f 100644 --- a/openpype/hosts/hiero/api/tags.py +++ b/openpype/hosts/hiero/api/tags.py @@ -1,3 +1,4 @@ +import json import re import os import hiero @@ -85,17 +86,22 @@ def update_tag(tag, data): # get metadata key from data data_mtd = data.get("metadata", {}) - # due to hiero bug we have to make sure keys which are not existent in - # data are cleared of value by `None` - for _mk in mtd.dict().keys(): - if _mk.replace("tag.", "") not in data_mtd.keys(): - mtd.setValue(_mk, str(None)) + # # due to hiero bug we have to make sure keys which are not existent in + # # data are cleared of value by `None` + # for _mk in mtd.dict().keys(): + # if _mk.replace("tag.", "") not in data_mtd.keys(): + # mtd.setValue(_mk, str(None)) # set all data metadata to tag metadata - for k, v in data_mtd.items(): + for _k, _v in data_mtd.items(): + value = str(_v) + if type(_v) == dict: + value = json.dumps(_v) + + # set the value mtd.setValue( - "tag.{}".format(str(k)), - str(v) + "tag.{}".format(str(_k)), + value ) # set note description of tag diff --git a/openpype/hosts/hiero/plugins/load/load_effects.py b/openpype/hosts/hiero/plugins/load/load_effects.py index fa78684838..16c9187ad9 100644 --- a/openpype/hosts/hiero/plugins/load/load_effects.py +++ b/openpype/hosts/hiero/plugins/load/load_effects.py @@ -52,6 +52,7 @@ class LoadEffects(load.LoaderPlugin): "source": version_data["source"], "version": vname, "author": version_data["author"], + "objectName": object_name, "children_names": [] } From 2c4d37d1bfef0b83134bd00775b00815493366cc Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 31 Oct 2022 19:23:22 +0100 Subject: [PATCH 017/243] added create next overlay widget --- openpype/tools/publisher/widgets/__init__.py | 2 + openpype/tools/publisher/widgets/widgets.py | 201 +++++++++++++++++++ 2 files changed, 203 insertions(+) diff --git a/openpype/tools/publisher/widgets/__init__.py b/openpype/tools/publisher/widgets/__init__.py index a02c69d5e0..042985b007 100644 --- a/openpype/tools/publisher/widgets/__init__.py +++ b/openpype/tools/publisher/widgets/__init__.py @@ -8,6 +8,7 @@ from .widgets import ( ResetBtn, ValidateBtn, PublishBtn, + CreateNextPageOverlay, ) from .help_widget import ( HelpButton, @@ -28,6 +29,7 @@ __all__ = ( "ResetBtn", "ValidateBtn", "PublishBtn", + "CreateNextPageOverlay", "HelpButton", "HelpDialog", diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index d4c2623790..507ecedb0f 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -1652,3 +1652,204 @@ class ThumbnailWidget(QtWidgets.QWidget): self.thumbnail_label = thumbnail_label self.default_pix = default_pix self.current_pix = None + + +class CreateNextPageOverlay(QtWidgets.QWidget): + max_value = 100.0 + clicked = QtCore.Signal() + + def __init__(self, parent): + super(CreateNextPageOverlay, self).__init__(parent) + + self._bg_color = QtGui.QColor(127, 127, 255) + self._arrow_color = QtGui.QColor(255, 255, 255) + + change_anim = QtCore.QVariantAnimation() + change_anim.setStartValue(0.0) + change_anim.setEndValue(self.max_value) + change_anim.setDuration(200) + change_anim.setEasingCurve(QtCore.QEasingCurve.InOutQuad) + + change_anim.valueChanged.connect(self._on_anim) + + self._change_anim = change_anim + self._is_visible = None + self._anim_value = 0.0 + self._increasing = False + self._under_mouse = None + self._handle_show_on_own = True + self._mouse_pressed = False + self.set_visible(True) + + def set_increasing(self, increasing): + if self._increasing is increasing: + return + self._increasing = increasing + if increasing: + self._change_anim.setDirection(self._change_anim.Forward) + else: + self._change_anim.setDirection(self._change_anim.Backward) + + if self._change_anim.state() != self._change_anim.Running: + self._change_anim.start() + + def set_visible(self, visible): + if self._is_visible is visible: + return + + self._is_visible = visible + if not visible: + self.set_increasing(False) + if not self._is_anim_finished(): + return + + self.setVisible(visible) + self._check_anim_timer() + + def _is_anim_finished(self): + if self._increasing: + return self._anim_value == self.max_value + return self._anim_value == 0.0 + + def _on_anim(self, value): + self._check_anim_timer() + + self._anim_value = value + + self.update() + + if not self._is_anim_finished(): + return + + if not self._is_visible: + self.setVisible(False) + + def set_handle_show_on_own(self, handle): + if self._handle_show_on_own is handle: + return + self._handle_show_on_own = handle + self._under_mouse = None + self._check_anim_timer() + + def set_under_mouse(self, under_mouse): + if self._under_mouse is under_mouse: + return + + if self._handle_show_on_own: + self._handle_show_on_own = False + self._under_mouse = under_mouse + self.set_increasing(under_mouse) + + def _is_under_mouse(self): + mouse_pos = self.mapFromGlobal(QtGui.QCursor.pos()) + under_mouse = self.rect().contains(mouse_pos) + return under_mouse + + def _check_anim_timer(self): + if not self.isVisible(): + return + + if self._handle_show_on_own: + under_mouse = self._is_under_mouse() + else: + under_mouse = self._under_mouse + + self.set_increasing(under_mouse) + + def enterEvent(self, event): + super(CreateNextPageOverlay, self).enterEvent(event) + if self._handle_show_on_own: + self._check_anim_timer() + + def leaveEvent(self, event): + super(CreateNextPageOverlay, self).leaveEvent(event) + if self._handle_show_on_own: + self._check_anim_timer() + + def mousePressEvent(self, event): + if event.button() == QtCore.Qt.LeftButton: + self._mouse_pressed = True + super(CreateNextPageOverlay, self).mousePressEvent(event) + + def mouseReleaseEvent(self, event): + if self._mouse_pressed: + self._mouse_pressed = False + if self.rect().contains(event.pos()): + self.clicked.emit() + + super(CreateNextPageOverlay, self).mouseReleaseEvent(event) + + def paintEvent(self, event): + painter = QtGui.QPainter() + painter.begin(self) + if self._anim_value == 0.0: + painter.end() + return + painter.setRenderHints( + painter.Antialiasing + | painter.SmoothPixmapTransform + ) + + pen = QtGui.QPen() + pen.setWidth(0) + painter.setPen(pen) + rect = QtCore.QRect(self.rect()) + + offset = rect.width() - int( + float(rect.width()) * 0.01 * self._anim_value + ) + + pos_y = rect.center().y() + left = rect.left() + offset + right = rect.right() + top = rect.top() + bottom = rect.bottom() + width = right - left + height = bottom - top + + q_height = height * 0.15 + + arrow_half_height = width * 0.2 + arrow_x_start = left + (width * 0.4) + arrow_x_end = arrow_x_start + arrow_half_height + arrow_top_y_boundry = arrow_half_height + q_height + arrow_bottom_y_boundry = height - (arrow_half_height + q_height) + offset = 0 + if pos_y < arrow_top_y_boundry: + pos_y = arrow_top_y_boundry + elif pos_y > arrow_bottom_y_boundry: + pos_y = arrow_bottom_y_boundry + + top_cubic_y = pos_y - q_height + bottom_cubic_y = pos_y + q_height + + path = QtGui.QPainterPath() + path.moveTo(right, top) + path.lineTo(right, bottom) + + path.cubicTo( + right, bottom, + left, bottom_cubic_y, + left, pos_y + ) + path.cubicTo( + left, top_cubic_y, + right, top, + right, top + ) + path.closeSubpath() + + painter.fillPath(path, self._bg_color) + + src_arrow_path = QtGui.QPainterPath() + src_arrow_path.moveTo(arrow_x_start, pos_y - arrow_half_height) + src_arrow_path.lineTo(arrow_x_end, pos_y) + src_arrow_path.lineTo(arrow_x_start, pos_y + arrow_half_height) + + arrow_stroker = QtGui.QPainterPathStroker() + arrow_stroker.setWidth(min(4, arrow_half_height * 0.2)) + arrow_path = arrow_stroker.createStroke(src_arrow_path) + + painter.fillPath(arrow_path, self._arrow_color) + + painter.end() From 30789058b34e0445da3c6a4a1bb12fafb073c3b9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 31 Oct 2022 19:23:38 +0100 Subject: [PATCH 018/243] overview widget can return global geo of subset view widget --- openpype/tools/publisher/widgets/overview_widget.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/openpype/tools/publisher/widgets/overview_widget.py b/openpype/tools/publisher/widgets/overview_widget.py index be3839b90b..1c924d1631 100644 --- a/openpype/tools/publisher/widgets/overview_widget.py +++ b/openpype/tools/publisher/widgets/overview_widget.py @@ -195,6 +195,16 @@ class OverviewWidget(QtWidgets.QFrame): self._subset_views_widget.setMaximumWidth(view_width) self._change_anim.start() + def get_subset_views_geo(self): + parent = self._subset_views_widget.parent() + global_pos = parent.mapToGlobal(self._subset_views_widget.pos()) + return QtCore.QRect( + global_pos.x(), + global_pos.y(), + self._subset_views_widget.width(), + self._subset_views_widget.height() + ) + def _on_create_clicked(self): """Pass signal to parent widget which should care about changing state. From 90d0dd718bce3a4537ffe8d2301484369cd67e84 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 31 Oct 2022 19:24:35 +0100 Subject: [PATCH 019/243] prepared methods for set/check current tab --- openpype/tools/publisher/window.py | 31 +++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index d8a69bbeb0..7a0c34e298 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -403,7 +403,7 @@ class PublisherWindow(QtWidgets.QDialog): self._context_label.setText(label) def _update_publish_details_widget(self, force=False): - if not force and self._tabs_widget.current_tab() != "details": + if not force and not self._is_current_tab("details"): return report_data = self.controller.get_publish_report() @@ -434,7 +434,7 @@ class PublisherWindow(QtWidgets.QDialog): ) def _on_tab_change(self, old_tab, new_tab): - if old_tab == "details": + if old_tab != "details": self._publish_details_widget.close_details_popup() if new_tab in ("create", "publish"): @@ -463,14 +463,23 @@ class PublisherWindow(QtWidgets.QDialog): def _on_create_request(self): self._go_to_create_tab() + def _set_current_tab(self, identifier): + self._tabs_widget.set_current_tab(identifier) + + def _is_current_tab(self, identifier): + return self._tabs_widget.is_current_tab(identifier) + def _go_to_create_tab(self): - self._tabs_widget.set_current_tab("create") + self._set_current_tab("create") + + def _go_to_publish_tab(self): + self._set_current_tab("publish") def _go_to_details_tab(self): - self._tabs_widget.set_current_tab("details") + self._set_current_tab("details") def _go_to_report_tab(self): - self._tabs_widget.set_current_tab("report") + self._set_current_tab("report") def _set_publish_overlay_visibility(self, visible): if visible: @@ -523,10 +532,10 @@ class PublisherWindow(QtWidgets.QDialog): self._set_footer_enabled(False) self._update_publish_details_widget() if ( - not self._tabs_widget.is_current_tab("create") - and not self._tabs_widget.is_current_tab("publish") + not self._is_current_tab("create") + and not self._is_current_tab("publish") ): - self._tabs_widget.set_current_tab("publish") + self._set_current_tab("publish") def _on_publish_start(self): self._create_tab.setEnabled(False) @@ -542,8 +551,8 @@ class PublisherWindow(QtWidgets.QDialog): self._publish_details_widget.close_details_popup() - if self._tabs_widget.is_current_tab(self._create_tab): - self._tabs_widget.set_current_tab("publish") + if self._is_current_tab(self._create_tab): + self._set_current_tab("publish") def _on_publish_validated_change(self, event): if event["value"]: @@ -556,7 +565,7 @@ class PublisherWindow(QtWidgets.QDialog): publish_has_crashed = self._controller.publish_has_crashed validate_enabled = not publish_has_crashed publish_enabled = not publish_has_crashed - if self._tabs_widget.is_current_tab("publish"): + if self._is_current_tab("publish"): self._go_to_report_tab() if validate_enabled: From ee94f7c46c707846277a8faf4fb3bcf6087f1edf Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 31 Oct 2022 19:24:56 +0100 Subject: [PATCH 020/243] added overlay widget and necessary parts to window --- openpype/tools/publisher/window.py | 85 +++++++++++++++++++++++++++--- 1 file changed, 78 insertions(+), 7 deletions(-) diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index 7a0c34e298..ddac19f2e5 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -29,6 +29,8 @@ from .widgets import ( HelpButton, HelpDialog, + + CreateNextPageOverlay, ) @@ -225,8 +227,9 @@ class PublisherWindow(QtWidgets.QDialog): # Floating publish frame publish_frame = PublishFrame(controller, self.footer_border, self) - # Timer started on show -> connected to timer counter - # - helps to deffer on show logic by 3 event loops + create_overlay_button = CreateNextPageOverlay(self) + create_overlay_button.set_handle_show_on_own(False) + show_timer = QtCore.QTimer() show_timer.setInterval(1) show_timer.timeout.connect(self._on_show_timer) @@ -255,6 +258,7 @@ class PublisherWindow(QtWidgets.QDialog): publish_btn.clicked.connect(self._on_publish_clicked) publish_frame.details_page_requested.connect(self._go_to_details_tab) + create_overlay_button.clicked.connect(self._go_to_publish_tab) controller.event_system.add_callback( "instances.refresh.finished", self._on_instances_refresh @@ -310,6 +314,7 @@ class PublisherWindow(QtWidgets.QDialog): self._publish_overlay = publish_overlay self._publish_frame = publish_frame + self._content_widget = content_widget self._content_stacked_layout = content_stacked_layout self._overview_widget = overview_widget @@ -342,6 +347,9 @@ class PublisherWindow(QtWidgets.QDialog): self._set_publish_visibility(False) + self._create_overlay_button = create_overlay_button + self._app_event_listener_installed = False + self._show_timer = show_timer self._show_counter = 0 @@ -355,11 +363,38 @@ class PublisherWindow(QtWidgets.QDialog): self._first_show = False self._on_first_show() + self._show_counter = 0 self._show_timer.start() def resizeEvent(self, event): super(PublisherWindow, self).resizeEvent(event) self._update_publish_frame_rect() + self._update_create_overlay_size() + + def closeEvent(self, event): + self._uninstall_app_event_listener() + self.save_changes() + self._reset_on_show = True + super(PublisherWindow, self).closeEvent(event) + + def eventFilter(self, obj, event): + if event.type() == QtCore.QEvent.MouseMove: + self._update_create_overlay_visibility(event.globalPos()) + return super(PublisherWindow, self).eventFilter(obj, event) + + def _install_app_event_listener(self): + if self._app_event_listener_installed: + return + self._app_event_listener_installed = True + app = QtWidgets.QApplication.instance() + app.installEventFilter(self) + + def _uninstall_app_event_listener(self): + if not self._app_event_listener_installed: + return + self._app_event_listener_installed = False + app = QtWidgets.QApplication.instance() + app.removeEventFilter(self) def _on_overlay_message(self, event): self._overlay_object.add_message( @@ -383,16 +418,16 @@ class PublisherWindow(QtWidgets.QDialog): # Reset counter when done for next show event self._show_counter = 0 + self._update_create_overlay_size() + self._update_create_overlay_visibility() + if self._is_current_tab("create"): + self._install_app_event_listener() + # Reset if requested if self._reset_on_show: self._reset_on_show = False self.reset() - def closeEvent(self, event): - self.save_changes() - self._reset_on_show = True - super(PublisherWindow, self).closeEvent(event) - def save_changes(self): self._controller.save_changes() @@ -457,6 +492,13 @@ class PublisherWindow(QtWidgets.QDialog): self._report_widget ) + is_create = new_tab == "create" + if is_create: + self._install_app_event_listener() + else: + self._uninstall_app_event_listener() + self._create_overlay_button.set_visible(is_create) + def _on_context_or_active_change(self): self._validate_create_instances() @@ -669,6 +711,35 @@ class PublisherWindow(QtWidgets.QDialog): event["title"], new_failed_info, "Convertor:" ) + def _update_create_overlay_size(self): + height = self._content_widget.height() + metrics = self._create_overlay_button.fontMetrics() + width = int(metrics.height() * 3) + pos_x = self.width() - width + + tab_pos = self._tabs_widget.parent().mapTo( + self, self._tabs_widget.pos() + ) + tab_height = self._tabs_widget.height() + pos_y = tab_pos.y() + tab_height + + self._create_overlay_button.setGeometry( + pos_x, pos_y, + width, height + ) + + def _update_create_overlay_visibility(self, global_pos=None): + if global_pos is None: + global_pos = QtGui.QCursor.pos() + + under_mouse = False + my_pos = self.mapFromGlobal(global_pos) + if self.rect().contains(my_pos): + widget_geo = self._overview_widget.get_subset_views_geo() + widget_x = widget_geo.left() + (widget_geo.width() * 0.5) + under_mouse = widget_x < global_pos.x() + self._create_overlay_button.set_under_mouse(under_mouse) + class ErrorsMessageBox(ErrorMessageBox): def __init__(self, error_title, failed_info, message_start, parent): From ea6e924dd95b86053092af0f08790b8e8a77be83 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 31 Oct 2022 19:31:53 +0100 Subject: [PATCH 021/243] use gradient and different color --- openpype/tools/publisher/widgets/widgets.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index 507ecedb0f..975a1faa06 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -1661,7 +1661,6 @@ class CreateNextPageOverlay(QtWidgets.QWidget): def __init__(self, parent): super(CreateNextPageOverlay, self).__init__(parent) - self._bg_color = QtGui.QColor(127, 127, 255) self._arrow_color = QtGui.QColor(255, 255, 255) change_anim = QtCore.QVariantAnimation() @@ -1839,7 +1838,11 @@ class CreateNextPageOverlay(QtWidgets.QWidget): ) path.closeSubpath() - painter.fillPath(path, self._bg_color) + gradient = QtGui.QLinearGradient(left, pos_y, right, pos_y) + gradient.setColorAt(0, QtGui.QColor(22, 25, 29)) + gradient.setColorAt(1, QtGui.QColor(33, 37, 43)) + + painter.fillPath(path, gradient) src_arrow_path = QtGui.QPainterPath() src_arrow_path.moveTo(arrow_x_start, pos_y - arrow_half_height) From 42b1012e7c320ec783df7ce5c76b76a24e18896e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 31 Oct 2022 19:41:12 +0100 Subject: [PATCH 022/243] use radial gradient --- openpype/tools/publisher/widgets/widgets.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index 975a1faa06..c4481d4d9d 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -1838,7 +1838,10 @@ class CreateNextPageOverlay(QtWidgets.QWidget): ) path.closeSubpath() - gradient = QtGui.QLinearGradient(left, pos_y, right, pos_y) + radius = height * 0.7 + focal = QtCore.QPointF(left, pos_y) + start_p = QtCore.QPointF(right - (width * 0.5), pos_y) + gradient = QtGui.QRadialGradient(start_p, radius, focal) gradient.setColorAt(0, QtGui.QColor(22, 25, 29)) gradient.setColorAt(1, QtGui.QColor(33, 37, 43)) From 17125a62edec43fe4c144485e584be964398aa41 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 31 Oct 2022 21:03:16 +0100 Subject: [PATCH 023/243] hiero: adding fallback if incompatible knobs from version to version --- openpype/hosts/hiero/api/pipeline.py | 6 +++--- openpype/hosts/hiero/plugins/load/load_effects.py | 7 ++++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/hiero/api/pipeline.py b/openpype/hosts/hiero/api/pipeline.py index 26c8ebe6d3..c48d404ede 100644 --- a/openpype/hosts/hiero/api/pipeline.py +++ b/openpype/hosts/hiero/api/pipeline.py @@ -134,7 +134,7 @@ def ls(): for item in all_items: container_data = parse_container(item) - if isinstance(container, list): + if isinstance(container_data, list): for _c in container_data: yield _c @@ -187,7 +187,7 @@ def parse_container(item, validate=True): if type(item) == hiero.core.VideoTrack: return_list = [] _data = lib.get_track_openpype_data(item) - log.info("_data: {}".format(_data)) + if not _data: return # convert the data to list and validate them @@ -196,7 +196,7 @@ def parse_container(item, validate=True): return_list.append(cotnainer) return return_list else: - _data = lib.get_track_item_pype_data(item) + _data = lib.get_trackitem_openpype_data(item) return data_to_container(item, _data) diff --git a/openpype/hosts/hiero/plugins/load/load_effects.py b/openpype/hosts/hiero/plugins/load/load_effects.py index 16c9187ad9..d8a388c6ed 100644 --- a/openpype/hosts/hiero/plugins/load/load_effects.py +++ b/openpype/hosts/hiero/plugins/load/load_effects.py @@ -94,7 +94,12 @@ class LoadEffects(load.LoaderPlugin): or knob_name == "name" ): continue - node[knob_name].setValue(knob_value) + + try: + node[knob_name].setValue(knob_value) + except NameError: + self.log.warning("Knob: {} cannot be set".format( + knob_name)) # register all loaded children data_imprint["children_names"].append(new_name) From 576903575e78e20ba0c7401061741b3c4cc50218 Mon Sep 17 00:00:00 2001 From: Felix David Date: Tue, 1 Nov 2022 10:00:26 +0100 Subject: [PATCH 024/243] Project setting --- .../hooks/pre_copy_last_published_workfile.py | 119 ++++++++++++------ .../defaults/project_settings/global.json | 3 +- .../schemas/schema_global_tools.json | 5 + 3 files changed, 88 insertions(+), 39 deletions(-) diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index 004f9d25e7..312548d2db 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -7,8 +7,10 @@ from openpype.client.entities import ( get_subsets, ) from openpype.lib import PreLaunchHook +from openpype.lib.profiles_filtering import filter_profiles from openpype.modules.base import ModulesManager from openpype.pipeline.load.utils import get_representation_path +from openpype.settings.lib import get_project_settings class CopyLastPublishedWorkfile(PreLaunchHook): @@ -32,9 +34,45 @@ class CopyLastPublishedWorkfile(PreLaunchHook): Returns: None: This is a void method. """ - # TODO setting + project_name = self.data["project_name"] + task_name = self.data["task_name"] + task_type = self.data["task_type"] + host_name = self.application.host_name + + # Check settings has enabled it + project_settings = get_project_settings(project_name) + profiles = project_settings["global"]["tools"]["Workfiles"][ + "last_workfile_on_startup" + ] + filter_data = { + "tasks": task_name, + "task_types": task_type, + "hosts": host_name, + } + last_workfile_settings = filter_profiles(profiles, filter_data) + use_last_published_workfile = last_workfile_settings.get( + "use_last_published_workfile" + ) + if use_last_published_workfile is None: + self.log.info( + ( + "Seems like old version of settings is used." + ' Can\'t access custom templates in host "{}".' + ).format(host_name) + ) + return + elif use_last_published_workfile is False: + self.log.info( + ( + 'Project "{}" has turned off to use last published workfile' + ' as first workfile for host "{}"' + ).format(project_name, host_name) + ) + return + self.log.info("Trying to fetch last published workfile...") + # Check there is no workfile available last_workfile = self.data.get("last_workfile_path") if os.path.exists(last_workfile): self.log.debug( @@ -44,9 +82,6 @@ class CopyLastPublishedWorkfile(PreLaunchHook): ) return - project_name = self.data["project_name"] - task_name = self.data["task_name"] - project_doc = self.data.get("project_doc") asset_doc = self.data.get("asset_doc") anatomy = self.data.get("anatomy") @@ -65,6 +100,9 @@ class CopyLastPublishedWorkfile(PreLaunchHook): None, ) if not subset_id: + self.log.debug('No any workfile for asset "{}".').format( + asset_doc["name"] + ) return # Get workfile representation @@ -84,41 +122,46 @@ class CopyLastPublishedWorkfile(PreLaunchHook): None, ) - if workfile_representation: # TODO add setting - # Get sync server from Tray, which handles the asynchronous thread instance - sync_server = next( - ( - t["sync_server"] - for t in [ - obj - for obj in gc.get_objects() - if isinstance(obj, ModulesManager) - ] - if t["sync_server"].sync_server_thread - ), - None, - ) + if not workfile_representation: + self.log.debug( + 'No published workfile for task "{}" and host "{}".' + ).format(task_name, host_name) + return - # Add site and reset timer - active_site = sync_server.get_active_site(project_name) - sync_server.add_site( - project_name, - workfile_representation["_id"], - active_site, - force=True, - ) - sync_server.reset_timer() + # Get sync server from Tray, which handles the asynchronous thread instance + sync_server = next( + ( + t["sync_server"] + for t in [ + obj + for obj in gc.get_objects() + if isinstance(obj, ModulesManager) + ] + if t["sync_server"].sync_server_thread + ), + None, + ) - # Wait for the download loop to end - sync_server.sync_server_thread.files_processed.wait() + # Add site and reset timer + active_site = sync_server.get_active_site(project_name) + sync_server.add_site( + project_name, + workfile_representation["_id"], + active_site, + force=True, + ) + sync_server.reset_timer() - # Get paths - published_workfile_path = get_representation_path( - workfile_representation, root=anatomy.roots - ) - local_workfile_dir = os.path.dirname(last_workfile) + # Wait for the download loop to end + sync_server.sync_server_thread.files_processed.wait() - # Copy file and substitute path - self.data["last_workfile_path"] = shutil.copy( - published_workfile_path, local_workfile_dir - ) + # Get paths + published_workfile_path = get_representation_path( + workfile_representation, root=anatomy.roots + ) + local_workfile_dir = os.path.dirname(last_workfile) + + # Copy file and substitute path + self.data["last_workfile_path"] = shutil.copy( + published_workfile_path, local_workfile_dir + ) diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index b128564bc2..5b1c750bf4 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -401,7 +401,8 @@ "hosts": [], "task_types": [], "tasks": [], - "enabled": true + "enabled": true, + "use_last_published_workfile": false } ], "open_workfile_tool_on_startup": [ diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json index ba446135e2..962008d476 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json @@ -149,6 +149,11 @@ "type": "boolean", "key": "enabled", "label": "Enabled" + }, + { + "type": "boolean", + "key": "use_last_published_workfile", + "label": "Use last published workfile" } ] } From 10fb9a141159302d4321d1acb8409fa5f341d7c9 Mon Sep 17 00:00:00 2001 From: Felix David Date: Tue, 1 Nov 2022 10:10:12 +0100 Subject: [PATCH 025/243] docstring --- openpype/hooks/pre_copy_last_published_workfile.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index 312548d2db..b1b2fe2366 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -30,6 +30,7 @@ class CopyLastPublishedWorkfile(PreLaunchHook): 1- Check if setting for this feature is enabled 2- Check if workfile in work area doesn't exist 3- Check if published workfile exists and is copied locally in publish + 4- Substitute copied published workfile as first workfile Returns: None: This is a void method. From 895bfbaae5aaebaf97c30233ae407f12ad52ca7d Mon Sep 17 00:00:00 2001 From: Felix David Date: Tue, 1 Nov 2022 10:14:58 +0100 Subject: [PATCH 026/243] comment length --- openpype/hooks/pre_copy_last_published_workfile.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index b1b2fe2366..d342151823 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -129,7 +129,8 @@ class CopyLastPublishedWorkfile(PreLaunchHook): ).format(task_name, host_name) return - # Get sync server from Tray, which handles the asynchronous thread instance + # Get sync server from Tray, + # which handles the asynchronous thread instance sync_server = next( ( t["sync_server"] From c49017e6718ee169f052a75689b5624dc36705dc Mon Sep 17 00:00:00 2001 From: Felix David Date: Tue, 1 Nov 2022 10:50:30 +0100 Subject: [PATCH 027/243] lint --- openpype/hooks/pre_copy_last_published_workfile.py | 4 ++-- openpype/modules/sync_server/sync_server.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index d342151823..cf4edeac9b 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -65,8 +65,8 @@ class CopyLastPublishedWorkfile(PreLaunchHook): elif use_last_published_workfile is False: self.log.info( ( - 'Project "{}" has turned off to use last published workfile' - ' as first workfile for host "{}"' + 'Project "{}" has turned off to use last published' + ' workfile as first workfile for host "{}"' ).format(project_name, host_name) ) return diff --git a/openpype/modules/sync_server/sync_server.py b/openpype/modules/sync_server/sync_server.py index def9e6cfd8..353b39c4e1 100644 --- a/openpype/modules/sync_server/sync_server.py +++ b/openpype/modules/sync_server/sync_server.py @@ -239,7 +239,7 @@ class SyncServerThread(threading.Thread): # Event to trigger files have been processed self.files_processed = threading.Event() - + super(SyncServerThread, self).__init__(args=(self.files_processed,)) self.module = module self.loop = None From e4e6044198a7240e21387c2931926f7d0cffdbc2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 1 Nov 2022 11:56:27 +0100 Subject: [PATCH 028/243] fix last pixel --- openpype/tools/publisher/widgets/widgets.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index c4481d4d9d..b8fb2d38b9 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -1800,9 +1800,10 @@ class CreateNextPageOverlay(QtWidgets.QWidget): pos_y = rect.center().y() left = rect.left() + offset - right = rect.right() top = rect.top() - bottom = rect.bottom() + # Right and bootm is pixel index + right = rect.right() + 1 + bottom = rect.bottom() + 1 width = right - left height = bottom - top From 049de296240198cdf296d0ff411c2601f1568589 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 1 Nov 2022 11:57:32 +0100 Subject: [PATCH 029/243] handle leave event --- openpype/tools/publisher/window.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index ddac19f2e5..2063cdab96 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -377,6 +377,10 @@ class PublisherWindow(QtWidgets.QDialog): self._reset_on_show = True super(PublisherWindow, self).closeEvent(event) + def leaveEvent(self, event): + super(PublisherWindow, self).leaveEvent(event) + self._update_create_overlay_visibility() + def eventFilter(self, obj, event): if event.type() == QtCore.QEvent.MouseMove: self._update_create_overlay_visibility(event.globalPos()) From 02fb9561d7f25a1146d9c59dd2306bce7e166edf Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 1 Nov 2022 20:36:58 +0800 Subject: [PATCH 030/243] Alembic Loader as Arnold Standin --- .../maya/plugins/load/load_abc_to_standin.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py index f39aa56650..68aeb24069 100644 --- a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py +++ b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py @@ -13,7 +13,7 @@ class AlembicStandinLoader(load.LoaderPlugin): families = ["model", "pointcache"] representations = ["abc"] - label = "Import Alembic as Standin" + label = "Import Alembic as Arnold Standin" order = -5 icon = "code-fork" color = "orange" @@ -21,7 +21,6 @@ class AlembicStandinLoader(load.LoaderPlugin): def load(self, context, name, namespace, options): import maya.cmds as cmds - import pymel.core as pm import mtoa.ui.arnoldmenu from openpype.hosts.maya.api.pipeline import containerise from openpype.hosts.maya.api.lib import unique_namespace @@ -42,7 +41,7 @@ class AlembicStandinLoader(load.LoaderPlugin): # Root group label = "{}:{}".format(namespace, name) - root = pm.group(name=label, empty=True) + root = cmds.group(name=label, empty=True) settings = get_project_settings(os.environ['AVALON_PROJECT']) colors = settings["maya"]["load"]["colors"] @@ -55,16 +54,17 @@ class AlembicStandinLoader(load.LoaderPlugin): transform_name = label + "_ABC" - standinShape = pm.PyNode(mtoa.ui.arnoldmenu.createStandIn()) - standin = standinShape.getParent() - standin.rename(transform_name) + standinShape = cmds.ls(mtoa.ui.arnoldmenu.createStandIn())[0] + standin = cmds.listRelatives(standinShape, parent=True, typ="transform") + standin = cmds.rename(standin, transform_name) + standinShape = cmds.listRelatives(standin, children=True)[0] - pm.parent(standin, root) + cmds.parent(standin, root) # Set the standin filepath - standinShape.dso.set(self.fname) + cmds.setAttr(standinShape + ".dso", self.fname, type="string") if frameStart is not None: - standinShape.useFrameExtension.set(1) + cmds.setAttr(standinShape + ".useFrameExtension", 1) nodes = [root, standin] self[:] = nodes From 6b0d25cb7c6f3c0dd084bbccf0b5f06fe8fe1341 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 1 Nov 2022 20:38:51 +0800 Subject: [PATCH 031/243] Alembic Loader as Arnold Standin --- openpype/hosts/maya/plugins/load/load_abc_to_standin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py index 68aeb24069..5d6c52eac9 100644 --- a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py +++ b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py @@ -55,7 +55,8 @@ class AlembicStandinLoader(load.LoaderPlugin): transform_name = label + "_ABC" standinShape = cmds.ls(mtoa.ui.arnoldmenu.createStandIn())[0] - standin = cmds.listRelatives(standinShape, parent=True, typ="transform") + standin = cmds.listRelatives(standinShape, parent=True, + typ="transform") standin = cmds.rename(standin, transform_name) standinShape = cmds.listRelatives(standin, children=True)[0] From 72ce97a6285e1e31782b8c9a3c5e0d6bb49ab56c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 1 Nov 2022 16:07:06 +0100 Subject: [PATCH 032/243] general: fixing loader for multiselection --- openpype/tools/loader/widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index d37ce500e0..826c7110da 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -515,7 +515,7 @@ class SubsetWidget(QtWidgets.QWidget): if not one_item_selected: # Filter loaders from first subset by intersected combinations for repre, loader in first_loaders: - if (repre["name"], loader) not in found_combinations: + if (repre["name"].lower(), loader) not in found_combinations: continue loaders.append((repre, loader)) From 3dd115feef6c02e0effe7b44874c63056ed8a775 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 1 Nov 2022 16:38:41 +0100 Subject: [PATCH 033/243] hiero: return specific container name --- openpype/hosts/hiero/api/lib.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/hiero/api/lib.py b/openpype/hosts/hiero/api/lib.py index d04a710df1..e340209207 100644 --- a/openpype/hosts/hiero/api/lib.py +++ b/openpype/hosts/hiero/api/lib.py @@ -394,7 +394,7 @@ def get_track_openpype_tag(track): return tag -def get_track_openpype_data(track): +def get_track_openpype_data(track, container_name=None): """ Get track's openpype tag data. @@ -416,12 +416,16 @@ def get_track_openpype_data(track): for obj_name, obj_data in tag_data.items(): obj_name = obj_name.replace("tag.", "") - print(obj_name) + if obj_name in ["applieswhole", "note", "label"]: continue return_data[obj_name] = json.loads(obj_data) - return return_data + return ( + return_data[container_name] + if container_name + else return_data + ) @deprecated("openpype.hosts.hiero.api.lib.get_trackitem_openpype_tag") From 393692559e3f57b5ed4db333e8c5e2c997801437 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 1 Nov 2022 16:39:08 +0100 Subject: [PATCH 034/243] hiero: deep copy dicts --- openpype/hosts/hiero/api/pipeline.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/hiero/api/pipeline.py b/openpype/hosts/hiero/api/pipeline.py index c48d404ede..3475bc62e4 100644 --- a/openpype/hosts/hiero/api/pipeline.py +++ b/openpype/hosts/hiero/api/pipeline.py @@ -1,6 +1,7 @@ """ Basic avalon integration """ +from copy import deepcopy import os import contextlib from collections import OrderedDict @@ -225,19 +226,19 @@ def update_container(item, data=None): if type(item) == hiero.core.VideoTrack: # form object data for test - object_name = "{}_{}".format( - data["name"], data["namespace"]) + object_name = data["objectName"] # get all available containers containers = lib.get_track_openpype_data(item) - for obj_name, container in containers.items(): - # ignore all which are not the same object - if object_name != obj_name: - continue - # update data in container - updated_container = update_container_data(container, data) - # merge updated container back to containers - containers.update(updated_container) + container = lib.get_track_openpype_data(item, object_name) + + containers = deepcopy(containers) + container = deepcopy(container) + + # update data in container + updated_container = update_container_data(container, data) + # merge updated container back to containers + containers.update({object_name: updated_container}) return bool(lib.set_track_openpype_tag(item, containers)) else: From 5b77f92d0bbf9cedb2f6c7b2a81964c45ccabd73 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 1 Nov 2022 16:39:23 +0100 Subject: [PATCH 035/243] hiero: removing obsolete code --- openpype/hosts/hiero/api/tags.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/openpype/hosts/hiero/api/tags.py b/openpype/hosts/hiero/api/tags.py index 918af3dc1f..cb7bc14edb 100644 --- a/openpype/hosts/hiero/api/tags.py +++ b/openpype/hosts/hiero/api/tags.py @@ -86,12 +86,6 @@ def update_tag(tag, data): # get metadata key from data data_mtd = data.get("metadata", {}) - # # due to hiero bug we have to make sure keys which are not existent in - # # data are cleared of value by `None` - # for _mk in mtd.dict().keys(): - # if _mk.replace("tag.", "") not in data_mtd.keys(): - # mtd.setValue(_mk, str(None)) - # set all data metadata to tag metadata for _k, _v in data_mtd.items(): value = str(_v) From 8c715a98aaa8bf4343b35f565400197ced021b0a Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 1 Nov 2022 16:39:59 +0100 Subject: [PATCH 036/243] hiero: update effects finish --- .../hosts/hiero/plugins/load/load_effects.py | 145 ++++++++++++------ 1 file changed, 101 insertions(+), 44 deletions(-) diff --git a/openpype/hosts/hiero/plugins/load/load_effects.py b/openpype/hosts/hiero/plugins/load/load_effects.py index d8a388c6ed..3e5225ba22 100644 --- a/openpype/hosts/hiero/plugins/load/load_effects.py +++ b/openpype/hosts/hiero/plugins/load/load_effects.py @@ -1,11 +1,16 @@ import json from collections import OrderedDict -from pprint import pprint import six +from openpype.client import ( + get_version_by_id +) + from openpype.pipeline import ( AVALON_CONTAINER_ID, - load + load, + legacy_io, + get_representation_path ) from openpype.hosts.hiero import api as phiero @@ -40,18 +45,12 @@ class LoadEffects(load.LoaderPlugin): active_sequence, "LoadedEffects") # get main variables - version = context["version"] - version_data = version.get("data", {}) - vname = version.get("name", None) namespace = namespace or context["asset"]["name"] object_name = "{}_{}".format(name, namespace) clip_in = context["asset"]["data"]["clipIn"] clip_out = context["asset"]["data"]["clipOut"] data_imprint = { - "source": version_data["source"], - "version": vname, - "author": version_data["author"], "objectName": object_name, "children_names": [] } @@ -59,6 +58,31 @@ class LoadEffects(load.LoaderPlugin): # getting file path file = self.fname.replace("\\", "/") + if self._shared_loading( + file, + active_track, + clip_in, + clip_out, + data_imprint + ): + self.containerise( + active_track, + name=name, + namespace=namespace, + object_name=object_name, + context=context, + loader=self.__class__.__name__, + data=data_imprint) + + def _shared_loading( + self, + file, + active_track, + clip_in, + clip_out, + data_imprint, + update=False + ): # getting data from json file with unicode conversion with open(file, "r") as f: json_f = {self.byteify(key): self.byteify(value) @@ -74,9 +98,6 @@ class LoadEffects(load.LoaderPlugin): loaded = False for index_order, (ef_name, ef_val) in enumerate(nodes_order.items()): - pprint("_" * 100) - pprint(ef_name) - pprint(ef_val) new_name = "{}_loaded".format(ef_name) if new_name not in used_subtracks: effect_track_item = active_track.createEffect( @@ -87,46 +108,82 @@ class LoadEffects(load.LoaderPlugin): ) effect_track_item.setName(new_name) - node = effect_track_item.node() - for knob_name, knob_value in ef_val["node"].items(): - if ( - not knob_value - or knob_name == "name" - ): - continue + else: + effect_track_item = used_subtracks[new_name] - try: - node[knob_name].setValue(knob_value) - except NameError: - self.log.warning("Knob: {} cannot be set".format( - knob_name)) + node = effect_track_item.node() + for knob_name, knob_value in ef_val["node"].items(): + if ( + not knob_value + or knob_name == "name" + ): + continue - # register all loaded children - data_imprint["children_names"].append(new_name) - # make sure containerisation will happen - loaded = True + try: + node[knob_name].setValue(knob_value) + except NameError: + self.log.warning("Knob: {} cannot be set".format( + knob_name)) - if not loaded: - return + # register all loaded children + data_imprint["children_names"].append(new_name) - self.containerise( - active_track, - name=name, - namespace=namespace, - object_name=object_name, - context=context, - loader=self.__class__.__name__, - data=data_imprint) + # make sure containerisation will happen + loaded = True + + return loaded def update(self, container, representation): - """Update the Loader's path - - Nuke automatically tries to reset some variables when changing - the loader's path to a new file. These automatic changes are to its - inputs: - + """ Updating previously loaded effects """ - pass + active_track = container["_item"] + file = get_representation_path(representation).replace("\\", "/") + + # get main variables + name = container['name'] + namespace = container['namespace'] + + # get timeline in out data + project_name = legacy_io.active_project() + version_doc = get_version_by_id(project_name, representation["parent"]) + version_data = version_doc["data"] + clip_in = version_data["clipIn"] + clip_out = version_data["clipOut"] + + object_name = "{}_{}".format(name, namespace) + + # Disable previously created nodes + used_subtracks = { + stitem.name(): stitem + for stitem in phiero.flatten(active_track.subTrackItems()) + } + container = phiero.get_track_openpype_data( + active_track, object_name + ) + + loaded_subtrack_items = container["children_names"] + for loaded_stitem in loaded_subtrack_items: + if loaded_stitem not in used_subtracks: + continue + item_to_remove = used_subtracks.pop(loaded_stitem) + item_to_remove.node()["enable"].setValue(0) + + data_imprint = { + "objectName": object_name, + "name": name, + "representation": str(representation["_id"]), + "children_names": [] + } + + if self._shared_loading( + file, + active_track, + clip_in, + clip_out, + data_imprint, + update=True + ): + return phiero.update_container(active_track, data_imprint) def reorder_nodes(self, data): new_order = OrderedDict() From f3b038ec7df4e77be2a251d3c84722736dc832cc Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 1 Nov 2022 16:45:27 +0100 Subject: [PATCH 037/243] hiero: removing unused attribute --- openpype/hosts/hiero/plugins/load/load_effects.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/hiero/plugins/load/load_effects.py b/openpype/hosts/hiero/plugins/load/load_effects.py index 3e5225ba22..fab426e58d 100644 --- a/openpype/hosts/hiero/plugins/load/load_effects.py +++ b/openpype/hosts/hiero/plugins/load/load_effects.py @@ -25,7 +25,6 @@ class LoadEffects(load.LoaderPlugin): order = 0 icon = "cc" color = "white" - ignore_attr = ["useLifetime"] def load(self, context, name, namespace, data): """ From d655a53136e724179da0889d0e508b607d9d173c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 1 Nov 2022 18:45:07 +0100 Subject: [PATCH 038/243] use objected colors from styles --- openpype/tools/publisher/widgets/widgets.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index b8fb2d38b9..444ad4c7dc 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -1661,7 +1661,15 @@ class CreateNextPageOverlay(QtWidgets.QWidget): def __init__(self, parent): super(CreateNextPageOverlay, self).__init__(parent) - self._arrow_color = QtGui.QColor(255, 255, 255) + self._arrow_color = ( + get_objected_colors("bg-buttons").get_qcolor() + ) + self._gradient_start_color = ( + get_objected_colors("publisher", "tab-bg").get_qcolor() + ) + self._gradient_end_color = ( + get_objected_colors("bg-inputs").get_qcolor() + ) change_anim = QtCore.QVariantAnimation() change_anim.setStartValue(0.0) @@ -1843,8 +1851,8 @@ class CreateNextPageOverlay(QtWidgets.QWidget): focal = QtCore.QPointF(left, pos_y) start_p = QtCore.QPointF(right - (width * 0.5), pos_y) gradient = QtGui.QRadialGradient(start_p, radius, focal) - gradient.setColorAt(0, QtGui.QColor(22, 25, 29)) - gradient.setColorAt(1, QtGui.QColor(33, 37, 43)) + gradient.setColorAt(0, self._gradient_start_color) + gradient.setColorAt(1, self._gradient_end_color) painter.fillPath(path, gradient) From ed96f1d5b33649e8f5f21e5598e4ee56436f63df Mon Sep 17 00:00:00 2001 From: Felix David Date: Thu, 3 Nov 2022 11:41:59 +0100 Subject: [PATCH 039/243] requested cosmetic changes --- .../hooks/pre_copy_last_published_workfile.py | 172 +++++++++--------- 1 file changed, 90 insertions(+), 82 deletions(-) diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index cf4edeac9b..7a835507f7 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -35,6 +35,17 @@ class CopyLastPublishedWorkfile(PreLaunchHook): Returns: None: This is a void method. """ + # Check there is no workfile available + last_workfile = self.data.get("last_workfile_path") + if os.path.exists(last_workfile): + self.log.debug( + "Last workfile exists. Skipping {} process.".format( + self.__class__.__name__ + ) + ) + return + + # Get data project_name = self.data["project_name"] task_name = self.data["task_name"] task_type = self.data["task_type"] @@ -73,97 +84,94 @@ class CopyLastPublishedWorkfile(PreLaunchHook): self.log.info("Trying to fetch last published workfile...") - # Check there is no workfile available - last_workfile = self.data.get("last_workfile_path") - if os.path.exists(last_workfile): - self.log.debug( - "Last workfile exists. Skipping {} process.".format( - self.__class__.__name__ - ) - ) - return - project_doc = self.data.get("project_doc") asset_doc = self.data.get("asset_doc") anatomy = self.data.get("anatomy") - if project_doc and asset_doc: - # Get subset id - subset_id = next( - ( - subset["_id"] - for subset in get_subsets( - project_name, - asset_ids=[asset_doc["_id"]], - fields=["_id", "data.family"], - ) - if subset["data"]["family"] == "workfile" - ), - None, - ) - if not subset_id: - self.log.debug('No any workfile for asset "{}".').format( - asset_doc["name"] - ) - return - # Get workfile representation - workfile_representation = next( - ( - representation - for representation in get_representations( - project_name, - version_ids=[ + # Check it can proceed + if not project_doc and not asset_doc: + return + + # Get subset id + subset_id = next( + ( + subset["_id"] + for subset in get_subsets( + project_name, + asset_ids=[asset_doc["_id"]], + fields=["_id", "data.family"], + ) + if subset["data"]["family"] == "workfile" + ), + None, + ) + if not subset_id: + self.log.debug('No any workfile for asset "{}".').format( + asset_doc["name"] + ) + return + + # Get workfile representation + workfile_representation = next( + ( + representation + for representation in get_representations( + project_name, + version_ids=[ + ( get_last_version_by_subset_id( project_name, subset_id, fields=["_id"] - )["_id"] - ], - ) - if representation["context"]["task"]["name"] == task_name - ), - None, - ) + ) + or {} + ).get("_id") + ], + ) + if representation["context"]["task"]["name"] == task_name + ), + None, + ) - if not workfile_representation: - self.log.debug( - 'No published workfile for task "{}" and host "{}".' - ).format(task_name, host_name) - return + if not workfile_representation: + self.log.debug( + 'No published workfile for task "{}" and host "{}".' + ).format(task_name, host_name) + return - # Get sync server from Tray, - # which handles the asynchronous thread instance - sync_server = next( - ( - t["sync_server"] - for t in [ - obj - for obj in gc.get_objects() - if isinstance(obj, ModulesManager) - ] - if t["sync_server"].sync_server_thread - ), - None, - ) + # Get sync server from Tray, + # which handles the asynchronous thread instance + sync_server = next( + ( + t["sync_server"] + for t in [ + obj + for obj in gc.get_objects() + if isinstance(obj, ModulesManager) + ] + if t["sync_server"].sync_server_thread + ), + None, + ) - # Add site and reset timer - active_site = sync_server.get_active_site(project_name) - sync_server.add_site( - project_name, - workfile_representation["_id"], - active_site, - force=True, - ) - sync_server.reset_timer() + # Add site and reset timer + active_site = sync_server.get_active_site(project_name) + sync_server.add_site( + project_name, + workfile_representation["_id"], + active_site, + force=True, + ) + sync_server.reset_timer() - # Wait for the download loop to end - sync_server.sync_server_thread.files_processed.wait() + # Wait for the download loop to end + sync_server.sync_server_thread.files_processed.wait() - # Get paths - published_workfile_path = get_representation_path( - workfile_representation, root=anatomy.roots - ) - local_workfile_dir = os.path.dirname(last_workfile) + # Get paths + published_workfile_path = get_representation_path( + workfile_representation, root=anatomy.roots + ) + local_workfile_dir = os.path.dirname(last_workfile) - # Copy file and substitute path - self.data["last_workfile_path"] = shutil.copy( - published_workfile_path, local_workfile_dir - ) + # Copy file and substitute path + self.data["last_workfile_path"] = shutil.copy( + published_workfile_path, local_workfile_dir + ) From 82be7ce8d053eefcb430a6b7d26948821ea6ea11 Mon Sep 17 00:00:00 2001 From: Felix David Date: Thu, 3 Nov 2022 18:33:54 +0100 Subject: [PATCH 040/243] Change to REST API using web server --- .../hooks/pre_copy_last_published_workfile.py | 54 ++++++++------- openpype/modules/sync_server/rest_api.py | 68 +++++++++++++++++++ openpype/modules/sync_server/sync_server.py | 12 ++-- .../modules/sync_server/sync_server_module.py | 9 +++ openpype/modules/timers_manager/rest_api.py | 2 +- 5 files changed, 112 insertions(+), 33 deletions(-) create mode 100644 openpype/modules/sync_server/rest_api.py diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index 7a835507f7..cefc7e5d40 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -1,14 +1,14 @@ -import gc import os import shutil +from time import sleep from openpype.client.entities import ( get_last_version_by_subset_id, get_representations, get_subsets, ) from openpype.lib import PreLaunchHook +from openpype.lib.local_settings import get_local_site_id from openpype.lib.profiles_filtering import filter_profiles -from openpype.modules.base import ModulesManager from openpype.pipeline.load.utils import get_representation_path from openpype.settings.lib import get_project_settings @@ -137,33 +137,37 @@ class CopyLastPublishedWorkfile(PreLaunchHook): ).format(task_name, host_name) return - # Get sync server from Tray, - # which handles the asynchronous thread instance - sync_server = next( - ( - t["sync_server"] - for t in [ - obj - for obj in gc.get_objects() - if isinstance(obj, ModulesManager) - ] - if t["sync_server"].sync_server_thread - ), - None, - ) + # POST to webserver sites to add to representations + webserver_url = os.environ.get("OPENPYPE_WEBSERVER_URL") + if not webserver_url: + self.log.warning("Couldn't find webserver url") + return - # Add site and reset timer - active_site = sync_server.get_active_site(project_name) - sync_server.add_site( - project_name, - workfile_representation["_id"], - active_site, - force=True, + entry_point_url = "{}/sync_server".format(webserver_url) + rest_api_url = "{}/add_sites_to_representations".format( + entry_point_url + ) + try: + import requests + except Exception: + self.log.warning( + "Couldn't add sites to representations ('requests' is not available)" + ) + return + + requests.post( + rest_api_url, + json={ + "project_name": project_name, + "sites": [get_local_site_id()], + "representations": [str(workfile_representation["_id"])], + }, ) - sync_server.reset_timer() # Wait for the download loop to end - sync_server.sync_server_thread.files_processed.wait() + rest_api_url = "{}/files_are_processed".format(entry_point_url) + while requests.get(rest_api_url).content: + sleep(5) # Get paths published_workfile_path = get_representation_path( diff --git a/openpype/modules/sync_server/rest_api.py b/openpype/modules/sync_server/rest_api.py new file mode 100644 index 0000000000..b7c5d26d15 --- /dev/null +++ b/openpype/modules/sync_server/rest_api.py @@ -0,0 +1,68 @@ +from aiohttp.web_response import Response +from openpype.lib import Logger + + +class SyncServerModuleRestApi: + """ + REST API endpoint used for calling from hosts when context change + happens in Workfile app. + """ + + def __init__(self, user_module, server_manager): + self._log = None + self.module = user_module + self.server_manager = server_manager + + self.prefix = "/sync_server" + + self.register() + + @property + def log(self): + if self._log is None: + self._log = Logger.get_logger(self.__class__.__name__) + return self._log + + def register(self): + self.server_manager.add_route( + "POST", + self.prefix + "/add_sites_to_representations", + self.add_sites_to_representations, + ) + self.server_manager.add_route( + "GET", + self.prefix + "/files_are_processed", + self.files_are_processed, + ) + + async def add_sites_to_representations(self, request): + # Extract data from request + data = await request.json() + try: + project_name = data["project_name"] + sites = data["sites"] + representations = data["representations"] + except KeyError: + msg = ( + "Payload must contain fields 'project_name," + " 'sites' (list of names) and 'representations' (list of IDs)" + ) + self.log.error(msg) + return Response(status=400, message=msg) + + # Add all sites to each representation + for representation_id in representations: + for site in sites: + self.module.add_site( + project_name, representation_id, site, force=True + ) + + # Force timer to run immediately + self.module.reset_timer() + + return Response(status=200) + + async def files_are_processed(self, _request): + return Response( + body=bytes(self.module.sync_server_thread.files_are_processed) + ) diff --git a/openpype/modules/sync_server/sync_server.py b/openpype/modules/sync_server/sync_server.py index 353b39c4e1..7fd2311c2d 100644 --- a/openpype/modules/sync_server/sync_server.py +++ b/openpype/modules/sync_server/sync_server.py @@ -237,15 +237,13 @@ class SyncServerThread(threading.Thread): def __init__(self, module): self.log = Logger.get_logger(self.__class__.__name__) - # Event to trigger files have been processed - self.files_processed = threading.Event() - - super(SyncServerThread, self).__init__(args=(self.files_processed,)) + super(SyncServerThread, self).__init__() self.module = module self.loop = None self.is_running = False self.executor = concurrent.futures.ThreadPoolExecutor(max_workers=3) self.timer = None + self.files_are_processed = False def run(self): self.is_running = True @@ -400,8 +398,8 @@ class SyncServerThread(threading.Thread): representation, site, error) - # Trigger files are processed - self.files_processed.set() + # Trigger files process finished + self.files_are_processed = False duration = time.time() - start_time self.log.debug("One loop took {:.2f}s".format(duration)) @@ -460,7 +458,6 @@ class SyncServerThread(threading.Thread): async def run_timer(self, delay): """Wait for 'delay' seconds to start next loop""" - self.files_processed.clear() await asyncio.sleep(delay) def reset_timer(self): @@ -469,6 +466,7 @@ class SyncServerThread(threading.Thread): if self.timer: self.timer.cancel() self.timer = None + self.files_are_processed = True def _working_sites(self, project_name): if self.module.is_project_paused(project_name): diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index a478faa9ef..7aaf42006c 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -2089,6 +2089,15 @@ class SyncServerModule(OpenPypeModule, ITrayModule): def cli(self, click_group): click_group.add_command(cli_main) + # Webserver module implementation + def webserver_initialization(self, server_manager): + """Add routes for syncs.""" + if self.tray_initialized: + from .rest_api import SyncServerModuleRestApi + self.rest_api_obj = SyncServerModuleRestApi( + self, server_manager + ) + @click.group(SyncServerModule.name, help="SyncServer module related commands.") def cli_main(): diff --git a/openpype/modules/timers_manager/rest_api.py b/openpype/modules/timers_manager/rest_api.py index 4a2e9e6575..979db9075b 100644 --- a/openpype/modules/timers_manager/rest_api.py +++ b/openpype/modules/timers_manager/rest_api.py @@ -21,7 +21,7 @@ class TimersManagerModuleRestApi: @property def log(self): if self._log is None: - self._log = Logger.get_logger(self.__ckass__.__name__) + self._log = Logger.get_logger(self.__class__.__name__) return self._log def register(self): From bca965cf9cae5eff205edd0e191288731770fa1a Mon Sep 17 00:00:00 2001 From: Felix David Date: Thu, 3 Nov 2022 18:37:13 +0100 Subject: [PATCH 041/243] lint --- openpype/hooks/pre_copy_last_published_workfile.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index cefc7e5d40..6bec4f7d2c 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -151,7 +151,8 @@ class CopyLastPublishedWorkfile(PreLaunchHook): import requests except Exception: self.log.warning( - "Couldn't add sites to representations ('requests' is not available)" + "Couldn't add sites to representations " + "('requests' is not available)" ) return From e359fb3d8451949ccba65204d69adb39d4a711cf Mon Sep 17 00:00:00 2001 From: Felix David Date: Fri, 4 Nov 2022 10:06:59 +0100 Subject: [PATCH 042/243] legacy compatibility --- openpype/hooks/pre_copy_last_published_workfile.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index 6bec4f7d2c..f3293fa511 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -99,9 +99,11 @@ class CopyLastPublishedWorkfile(PreLaunchHook): for subset in get_subsets( project_name, asset_ids=[asset_doc["_id"]], - fields=["_id", "data.family"], + fields=["_id", "data.family", "data.families"], ) - if subset["data"]["family"] == "workfile" + if subset["data"].get("family") == "workfile" + # Legacy compatibility + or "workfile" in subset["data"].get("families", {}) ), None, ) From befd6889ccf35216e1153eec5742d0b16edcceed Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 4 Nov 2022 21:25:12 +0100 Subject: [PATCH 043/243] use much simpler UI for the button --- openpype/tools/publisher/widgets/widgets.py | 112 +++++--------------- openpype/tools/publisher/window.py | 19 ++-- 2 files changed, 35 insertions(+), 96 deletions(-) diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index 444ad4c7dc..a180107380 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -1660,22 +1660,19 @@ class CreateNextPageOverlay(QtWidgets.QWidget): def __init__(self, parent): super(CreateNextPageOverlay, self).__init__(parent) - + self.setCursor(QtCore.Qt.PointingHandCursor) self._arrow_color = ( get_objected_colors("bg-buttons").get_qcolor() ) - self._gradient_start_color = ( + self._bg_color = ( get_objected_colors("publisher", "tab-bg").get_qcolor() ) - self._gradient_end_color = ( - get_objected_colors("bg-inputs").get_qcolor() - ) change_anim = QtCore.QVariantAnimation() change_anim.setStartValue(0.0) change_anim.setEndValue(self.max_value) - change_anim.setDuration(200) - change_anim.setEasingCurve(QtCore.QEasingCurve.InOutQuad) + change_anim.setDuration(400) + change_anim.setEasingCurve(QtCore.QEasingCurve.OutBounce) change_anim.valueChanged.connect(self._on_anim) @@ -1731,19 +1728,10 @@ class CreateNextPageOverlay(QtWidgets.QWidget): if not self._is_visible: self.setVisible(False) - def set_handle_show_on_own(self, handle): - if self._handle_show_on_own is handle: - return - self._handle_show_on_own = handle - self._under_mouse = None - self._check_anim_timer() - def set_under_mouse(self, under_mouse): if self._under_mouse is under_mouse: return - if self._handle_show_on_own: - self._handle_show_on_own = False self._under_mouse = under_mouse self.set_increasing(under_mouse) @@ -1756,22 +1744,7 @@ class CreateNextPageOverlay(QtWidgets.QWidget): if not self.isVisible(): return - if self._handle_show_on_own: - under_mouse = self._is_under_mouse() - else: - under_mouse = self._under_mouse - - self.set_increasing(under_mouse) - - def enterEvent(self, event): - super(CreateNextPageOverlay, self).enterEvent(event) - if self._handle_show_on_own: - self._check_anim_timer() - - def leaveEvent(self, event): - super(CreateNextPageOverlay, self).leaveEvent(event) - if self._handle_show_on_own: - self._check_anim_timer() + self.set_increasing(self._under_mouse) def mousePressEvent(self, event): if event.button() == QtCore.Qt.LeftButton: @@ -1792,74 +1765,41 @@ class CreateNextPageOverlay(QtWidgets.QWidget): if self._anim_value == 0.0: painter.end() return + + painter.setClipRect(event.rect()) painter.setRenderHints( painter.Antialiasing | painter.SmoothPixmapTransform ) - pen = QtGui.QPen() - pen.setWidth(0) - painter.setPen(pen) + painter.setPen(QtCore.Qt.NoPen) + rect = QtCore.QRect(self.rect()) + rect_width = rect.width() + rect_height = rect.height() - offset = rect.width() - int( - float(rect.width()) * 0.01 * self._anim_value - ) + size = rect_width * 0.9 - pos_y = rect.center().y() - left = rect.left() + offset - top = rect.top() - # Right and bootm is pixel index - right = rect.right() + 1 - bottom = rect.bottom() + 1 - width = right - left - height = bottom - top + x_offset = (rect_width - size) * 0.5 + y_offset = (rect_height - size) * 0.5 + if self._anim_value != self.max_value: + x_offset += rect_width - (rect_width * 0.01 * self._anim_value) - q_height = height * 0.15 - - arrow_half_height = width * 0.2 - arrow_x_start = left + (width * 0.4) + arrow_half_height = size * 0.2 + arrow_x_start = x_offset + (size * 0.4) arrow_x_end = arrow_x_start + arrow_half_height - arrow_top_y_boundry = arrow_half_height + q_height - arrow_bottom_y_boundry = height - (arrow_half_height + q_height) - offset = 0 - if pos_y < arrow_top_y_boundry: - pos_y = arrow_top_y_boundry - elif pos_y > arrow_bottom_y_boundry: - pos_y = arrow_bottom_y_boundry + center_y = rect.center().y() - top_cubic_y = pos_y - q_height - bottom_cubic_y = pos_y + q_height - - path = QtGui.QPainterPath() - path.moveTo(right, top) - path.lineTo(right, bottom) - - path.cubicTo( - right, bottom, - left, bottom_cubic_y, - left, pos_y + painter.setBrush(self._bg_color) + painter.drawEllipse( + x_offset, y_offset, + size, size ) - path.cubicTo( - left, top_cubic_y, - right, top, - right, top - ) - path.closeSubpath() - - radius = height * 0.7 - focal = QtCore.QPointF(left, pos_y) - start_p = QtCore.QPointF(right - (width * 0.5), pos_y) - gradient = QtGui.QRadialGradient(start_p, radius, focal) - gradient.setColorAt(0, self._gradient_start_color) - gradient.setColorAt(1, self._gradient_end_color) - - painter.fillPath(path, gradient) src_arrow_path = QtGui.QPainterPath() - src_arrow_path.moveTo(arrow_x_start, pos_y - arrow_half_height) - src_arrow_path.lineTo(arrow_x_end, pos_y) - src_arrow_path.lineTo(arrow_x_start, pos_y + arrow_half_height) + src_arrow_path.moveTo(arrow_x_start, center_y - arrow_half_height) + src_arrow_path.lineTo(arrow_x_end, center_y) + src_arrow_path.lineTo(arrow_x_start, center_y + arrow_half_height) arrow_stroker = QtGui.QPainterPathStroker() arrow_stroker.setWidth(min(4, arrow_half_height * 0.2)) diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index 2063cdab96..82a2576ff4 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -228,7 +228,6 @@ class PublisherWindow(QtWidgets.QDialog): publish_frame = PublishFrame(controller, self.footer_border, self) create_overlay_button = CreateNextPageOverlay(self) - create_overlay_button.set_handle_show_on_own(False) show_timer = QtCore.QTimer() show_timer.setInterval(1) @@ -716,20 +715,20 @@ class PublisherWindow(QtWidgets.QDialog): ) def _update_create_overlay_size(self): - height = self._content_widget.height() metrics = self._create_overlay_button.fontMetrics() - width = int(metrics.height() * 3) - pos_x = self.width() - width + size = int(metrics.height() * 3) + end_pos_x = self.width() + start_pos_x = end_pos_x - size - tab_pos = self._tabs_widget.parent().mapTo( - self, self._tabs_widget.pos() + center = self._content_widget.parent().mapTo( + self, + self._content_widget.rect().center() ) - tab_height = self._tabs_widget.height() - pos_y = tab_pos.y() + tab_height + pos_y = center.y() - (size * 0.5) self._create_overlay_button.setGeometry( - pos_x, pos_y, - width, height + start_pos_x, pos_y, + size, size ) def _update_create_overlay_visibility(self, global_pos=None): From 9ec78651547738a2d2ed3cf266ebb9428b44a6b6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 4 Nov 2022 21:32:09 +0100 Subject: [PATCH 044/243] removred unnecessary restart --- openpype/tools/publisher/window.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index 7cf3ae0da8..0daa31938d 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -362,7 +362,6 @@ class PublisherWindow(QtWidgets.QDialog): self._first_show = False self._on_first_show() - self._show_counter = 0 self._show_timer.start() def resizeEvent(self, event): From a852973e1139e5f2bba380f5c1e103ab3a817a54 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 4 Nov 2022 21:32:56 +0100 Subject: [PATCH 045/243] fix details dialog close --- openpype/tools/publisher/window.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index 0daa31938d..281c7ad2a1 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -472,7 +472,7 @@ class PublisherWindow(QtWidgets.QDialog): ) def _on_tab_change(self, old_tab, new_tab): - if old_tab != "details": + if old_tab == "details": self._publish_details_widget.close_details_popup() if new_tab in ("create", "publish"): From 017ec79552eeb000edc6159960867dc781275655 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 7 Nov 2022 23:20:29 +0100 Subject: [PATCH 046/243] change colors --- openpype/tools/publisher/widgets/widgets.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index 6c8ee3b332..ece27cd8cc 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -1718,10 +1718,10 @@ class CreateNextPageOverlay(QtWidgets.QWidget): super(CreateNextPageOverlay, self).__init__(parent) self.setCursor(QtCore.Qt.PointingHandCursor) self._arrow_color = ( - get_objected_colors("bg-buttons").get_qcolor() + get_objected_colors("font").get_qcolor() ) self._bg_color = ( - get_objected_colors("publisher", "tab-bg").get_qcolor() + get_objected_colors("bg-buttons").get_qcolor() ) change_anim = QtCore.QVariantAnimation() From b75356d631f26048330e65ff24e78107dc0bbd0c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 7 Nov 2022 23:20:35 +0100 Subject: [PATCH 047/243] change easing curve --- openpype/tools/publisher/widgets/widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index ece27cd8cc..f170992c1a 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -1728,7 +1728,7 @@ class CreateNextPageOverlay(QtWidgets.QWidget): change_anim.setStartValue(0.0) change_anim.setEndValue(self.max_value) change_anim.setDuration(400) - change_anim.setEasingCurve(QtCore.QEasingCurve.OutBounce) + change_anim.setEasingCurve(QtCore.QEasingCurve.OutCubic) change_anim.valueChanged.connect(self._on_anim) From 94114d5ed0ecb4c785403c99e55a94f9b2f3cb6b Mon Sep 17 00:00:00 2001 From: clement hector Date: Tue, 8 Nov 2022 11:21:52 +0100 Subject: [PATCH 048/243] add instance name and extension checks to filter only reviewMain file --- .../kitsu/plugins/publish/integrate_kitsu_review.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py index bf80095225..61d5a13660 100644 --- a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +import os import gazu import pyblish.api @@ -31,9 +32,13 @@ class IntegrateKitsuReview(pyblish.api.InstancePlugin): continue review_path = representation.get("published_path") + file_name, file_extension = os.path.splitext(review_path) + + if instance.data.get('name') != 'reviewMain' \ + or file_extension != '.mp4': + continue self.log.debug("Found review at: {}".format(review_path)) - gazu.task.add_preview( task, comment, review_path, normalize_movie=True ) From 3dbfa8ee5143d411adf6bbe2357966078cb819e4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 8 Nov 2022 16:30:40 +0100 Subject: [PATCH 049/243] removed max value and use 1.0 --- openpype/tools/publisher/widgets/widgets.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index f170992c1a..7ab6294817 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -1711,7 +1711,6 @@ class SubsetAttributesWidget(QtWidgets.QWidget): class CreateNextPageOverlay(QtWidgets.QWidget): - max_value = 100.0 clicked = QtCore.Signal() def __init__(self, parent): @@ -1726,7 +1725,7 @@ class CreateNextPageOverlay(QtWidgets.QWidget): change_anim = QtCore.QVariantAnimation() change_anim.setStartValue(0.0) - change_anim.setEndValue(self.max_value) + change_anim.setEndValue(1.0) change_anim.setDuration(400) change_anim.setEasingCurve(QtCore.QEasingCurve.OutCubic) @@ -1768,7 +1767,7 @@ class CreateNextPageOverlay(QtWidgets.QWidget): def _is_anim_finished(self): if self._increasing: - return self._anim_value == self.max_value + return self._anim_value == 1.0 return self._anim_value == 0.0 def _on_anim(self, value): @@ -1838,8 +1837,8 @@ class CreateNextPageOverlay(QtWidgets.QWidget): x_offset = (rect_width - size) * 0.5 y_offset = (rect_height - size) * 0.5 - if self._anim_value != self.max_value: - x_offset += rect_width - (rect_width * 0.01 * self._anim_value) + if self._anim_value != 1.0: + x_offset += rect_width - (rect_width * self._anim_value) arrow_half_height = size * 0.2 arrow_x_start = x_offset + (size * 0.4) From a76ad6035110e917e44b36e799222787bf87fd9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Tue, 8 Nov 2022 16:41:19 +0100 Subject: [PATCH 050/243] use 'created_dt' of representation --- openpype/client/entities.py | 28 +++++++++++++++++++ .../hooks/pre_copy_last_published_workfile.py | 19 +++++++++++-- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 43afccf2f1..43c2874f57 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -6,6 +6,7 @@ that has project name as a context (e.g. on 'ProjectEntity'?). + We will need more specific functions doing wery specific queires really fast. """ +from datetime import datetime import re import collections @@ -1367,6 +1368,33 @@ def get_representation_parents(project_name, representation): return parents_by_repre_id[repre_id] +def get_representation_last_created_time_on_site( + representation: dict, site_name: str +) -> datetime: + """Get `created_dt` value for representation on site. + + Args: + representation (dict): Representation to get creation date of + site_name (str): Site from which to get the creation date + + Returns: + datetime: Created time of representation on site + """ + created_time = next( + ( + site.get("created_dt") + for site in representation["files"][0].get("sites", []) + if site["name"] == site_name + ), + None, + ) + if created_time: + return created_time + else: + # Use epoch as 'zero' time + return datetime.utcfromtimestamp(0) + + def get_thumbnail_id_from_source(project_name, src_type, src_id): """Receive thumbnail id from source entity. diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index f3293fa511..4eb66f6f85 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -3,6 +3,8 @@ import shutil from time import sleep from openpype.client.entities import ( get_last_version_by_subset_id, + get_representation_by_id, + get_representation_last_created_time_on_site, get_representations, get_subsets, ) @@ -158,18 +160,29 @@ class CopyLastPublishedWorkfile(PreLaunchHook): ) return + local_site_id = get_local_site_id() requests.post( rest_api_url, json={ "project_name": project_name, - "sites": [get_local_site_id()], + "sites": [local_site_id], "representations": [str(workfile_representation["_id"])], }, ) # Wait for the download loop to end - rest_api_url = "{}/files_are_processed".format(entry_point_url) - while requests.get(rest_api_url).content: + last_created_time = get_representation_last_created_time_on_site( + workfile_representation, local_site_id + ) + while ( + last_created_time + >= get_representation_last_created_time_on_site( + get_representation_by_id( + project_name, workfile_representation["_id"] + ), + local_site_id, + ) + ): sleep(5) # Get paths From 7b1069f708dc9c0d8153fc669a1106bc6d79d030 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 9 Nov 2022 00:32:22 +0800 Subject: [PATCH 051/243] Alembic Loader as Arnold Standin --- .../hosts/maya/plugins/load/load_abc_to_standin.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py index 5d6c52eac9..94bb974917 100644 --- a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py +++ b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py @@ -10,7 +10,7 @@ from openpype.settings import get_project_settings class AlembicStandinLoader(load.LoaderPlugin): """Load Alembic as Arnold Standin""" - families = ["model", "pointcache"] + families = ["animation", "model", "pointcache"] representations = ["abc"] label = "Import Alembic as Arnold Standin" @@ -31,6 +31,7 @@ class AlembicStandinLoader(load.LoaderPlugin): self.log.info("version_data: {}\n".format(version_data)) frameStart = version_data.get("frameStart", None) + frameEnd = version_data.get("frameEnd", None) asset = context["asset"]["name"] namespace = namespace or unique_namespace( @@ -64,7 +65,13 @@ class AlembicStandinLoader(load.LoaderPlugin): # Set the standin filepath cmds.setAttr(standinShape + ".dso", self.fname, type="string") - if frameStart is not None: + cmds.setAttr(standinShape + ".abcFPS", 25) + + if frameStart is None: + cmds.setAttr(standinShape + ".useFrameExtension", 0) + elif frameStart == 1 and frameEnd == 1: + cmds.setAttr(standinShape + ".useFrameExtension", 0) + else: cmds.setAttr(standinShape + ".useFrameExtension", 1) nodes = [root, standin] @@ -93,7 +100,8 @@ class AlembicStandinLoader(load.LoaderPlugin): for standin in standins: standin.dso.set(path) - standin.useFrameExtension.set(1) + standin.useFrameExtension.set(0) + standin.abcFPS.set(25) container = pm.PyNode(container["objectName"]) container.representation.set(str(representation["_id"])) From 3ad8e95ca436c4229ae8f3eadbee5b41c8326a68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Tue, 8 Nov 2022 18:57:02 +0100 Subject: [PATCH 052/243] add priority to add_site --- openpype/modules/sync_server/rest_api.py | 17 ++++++----------- openpype/modules/sync_server/sync_server.py | 4 ---- .../modules/sync_server/sync_server_module.py | 13 ++++++++++--- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/openpype/modules/sync_server/rest_api.py b/openpype/modules/sync_server/rest_api.py index b7c5d26d15..e92ddc8eee 100644 --- a/openpype/modules/sync_server/rest_api.py +++ b/openpype/modules/sync_server/rest_api.py @@ -1,4 +1,5 @@ from aiohttp.web_response import Response +from openpype.client.entities import get_representation_by_id from openpype.lib import Logger @@ -29,11 +30,6 @@ class SyncServerModuleRestApi: self.prefix + "/add_sites_to_representations", self.add_sites_to_representations, ) - self.server_manager.add_route( - "GET", - self.prefix + "/files_are_processed", - self.files_are_processed, - ) async def add_sites_to_representations(self, request): # Extract data from request @@ -54,15 +50,14 @@ class SyncServerModuleRestApi: for representation_id in representations: for site in sites: self.module.add_site( - project_name, representation_id, site, force=True + project_name, + representation_id, + site, + force=True, + priority=99, ) # Force timer to run immediately self.module.reset_timer() return Response(status=200) - - async def files_are_processed(self, _request): - return Response( - body=bytes(self.module.sync_server_thread.files_are_processed) - ) diff --git a/openpype/modules/sync_server/sync_server.py b/openpype/modules/sync_server/sync_server.py index 7fd2311c2d..d0a40a60ff 100644 --- a/openpype/modules/sync_server/sync_server.py +++ b/openpype/modules/sync_server/sync_server.py @@ -243,7 +243,6 @@ class SyncServerThread(threading.Thread): self.is_running = False self.executor = concurrent.futures.ThreadPoolExecutor(max_workers=3) self.timer = None - self.files_are_processed = False def run(self): self.is_running = True @@ -398,8 +397,6 @@ class SyncServerThread(threading.Thread): representation, site, error) - # Trigger files process finished - self.files_are_processed = False duration = time.time() - start_time self.log.debug("One loop took {:.2f}s".format(duration)) @@ -466,7 +463,6 @@ class SyncServerThread(threading.Thread): if self.timer: self.timer.cancel() self.timer = None - self.files_are_processed = True def _working_sites(self, project_name): if self.module.is_project_paused(project_name): diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index 7aaf42006c..788032180e 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -136,7 +136,7 @@ class SyncServerModule(OpenPypeModule, ITrayModule): """ Start of Public API """ def add_site(self, project_name, representation_id, site_name=None, - force=False): + force=False, priority=None): """ Adds new site to representation to be synced. @@ -152,6 +152,7 @@ class SyncServerModule(OpenPypeModule, ITrayModule): representation_id (string): MongoDB _id value site_name (string): name of configured and active site force (bool): reset site if exists + priority (int): set priority Throws: SiteAlreadyPresentError - if adding already existing site and @@ -167,7 +168,8 @@ class SyncServerModule(OpenPypeModule, ITrayModule): self.reset_site_on_representation(project_name, representation_id, site_name=site_name, - force=force) + force=force, + priority=priority) def remove_site(self, project_name, representation_id, site_name, remove_local_files=False): @@ -1655,7 +1657,7 @@ class SyncServerModule(OpenPypeModule, ITrayModule): def reset_site_on_representation(self, project_name, representation_id, side=None, file_id=None, site_name=None, - remove=False, pause=None, force=False): + remove=False, pause=None, force=False, priority=None): """ Reset information about synchronization for particular 'file_id' and provider. @@ -1678,6 +1680,7 @@ class SyncServerModule(OpenPypeModule, ITrayModule): remove (bool): if True remove site altogether pause (bool or None): if True - pause, False - unpause force (bool): hard reset - currently only for add_site + priority (int): set priority Raises: SiteAlreadyPresentError - if adding already existing site and @@ -1705,6 +1708,10 @@ class SyncServerModule(OpenPypeModule, ITrayModule): elem = {"name": site_name} + # Add priority + if priority: + elem["priority"] = priority + if file_id: # reset site for particular file self._reset_site_for_file(project_name, representation_id, elem, file_id, site_name) From f19c2b3a7936b5ca0c3742ddc98f1d0fc38555ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Tue, 8 Nov 2022 18:58:19 +0100 Subject: [PATCH 053/243] clean --- openpype/modules/sync_server/rest_api.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/modules/sync_server/rest_api.py b/openpype/modules/sync_server/rest_api.py index e92ddc8eee..0c3b914833 100644 --- a/openpype/modules/sync_server/rest_api.py +++ b/openpype/modules/sync_server/rest_api.py @@ -1,5 +1,4 @@ from aiohttp.web_response import Response -from openpype.client.entities import get_representation_by_id from openpype.lib import Logger From 255d5d8b9b0c34d10b2ac17913e1369d41cc1108 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Tue, 8 Nov 2022 18:59:03 +0100 Subject: [PATCH 054/243] clean --- openpype/modules/sync_server/sync_server_module.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index 788032180e..94a97e9f37 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -1657,7 +1657,8 @@ class SyncServerModule(OpenPypeModule, ITrayModule): def reset_site_on_representation(self, project_name, representation_id, side=None, file_id=None, site_name=None, - remove=False, pause=None, force=False, priority=None): + remove=False, pause=None, force=False, + priority=None): """ Reset information about synchronization for particular 'file_id' and provider. From 4408ea9b02a00a0c0ff5adf4b45b2cb1f5168793 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Tue, 8 Nov 2022 19:02:29 +0100 Subject: [PATCH 055/243] sort fields --- openpype/hooks/pre_copy_last_published_workfile.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index 4eb66f6f85..acbc9ec1c7 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -178,7 +178,9 @@ class CopyLastPublishedWorkfile(PreLaunchHook): last_created_time >= get_representation_last_created_time_on_site( get_representation_by_id( - project_name, workfile_representation["_id"] + project_name, + workfile_representation["_id"], + fields=["files"], ), local_site_id, ) From bf6af7f7175ff1536b81e6ac8ae3f4c233271a26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Tue, 8 Nov 2022 19:04:03 +0100 Subject: [PATCH 056/243] clean --- openpype/modules/sync_server/sync_server_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index 94a97e9f37..4d848958e8 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -1657,7 +1657,7 @@ class SyncServerModule(OpenPypeModule, ITrayModule): def reset_site_on_representation(self, project_name, representation_id, side=None, file_id=None, site_name=None, - remove=False, pause=None, force=False, + remove=False, pause=None, force=False, priority=None): """ Reset information about synchronization for particular 'file_id' From cf50722e1fee7c6ab227dedefc74a479713264fb Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 8 Nov 2022 21:42:26 +0100 Subject: [PATCH 057/243] flame: load with native colorspace resolved from mapping --- openpype/hosts/flame/api/plugin.py | 13 +++++++++++++ openpype/hosts/flame/plugins/load/load_clip.py | 4 ++-- .../hosts/flame/plugins/load/load_clip_batch.py | 4 ++-- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/flame/api/plugin.py b/openpype/hosts/flame/api/plugin.py index 092ce9d106..45fa7fd9a4 100644 --- a/openpype/hosts/flame/api/plugin.py +++ b/openpype/hosts/flame/api/plugin.py @@ -690,6 +690,19 @@ class ClipLoader(LoaderPlugin): ) ] + _mapping = None + + def get_native_colorspace(self, input_colorspace): + if not self._mapping: + settings = get_current_project_settings()["flame"] + mapping = settings["imageio"]["profilesMapping"]["inputs"] + self._mapping = { + input["ocioName"]: input["flameName"] + for input in mapping + } + + return self._mapping.get(input_colorspace) + class OpenClipSolver(flib.MediaInfoFile): create_new_clip = False diff --git a/openpype/hosts/flame/plugins/load/load_clip.py b/openpype/hosts/flame/plugins/load/load_clip.py index 0843dde76a..23879b923e 100644 --- a/openpype/hosts/flame/plugins/load/load_clip.py +++ b/openpype/hosts/flame/plugins/load/load_clip.py @@ -40,10 +40,10 @@ class LoadClip(opfapi.ClipLoader): clip_name = StringTemplate(self.clip_name_template).format( context["representation"]["context"]) - # TODO: settings in imageio # convert colorspace with ocio to flame mapping # in imageio flame section - colorspace = colorspace + colorspace = self.get_native_colorspace(colorspace) + self.log.info("Loading with colorspace: `{}`".format(colorspace)) # create workfile path workfile_dir = os.environ["AVALON_WORKDIR"] diff --git a/openpype/hosts/flame/plugins/load/load_clip_batch.py b/openpype/hosts/flame/plugins/load/load_clip_batch.py index 3b049b861b..2de75df116 100644 --- a/openpype/hosts/flame/plugins/load/load_clip_batch.py +++ b/openpype/hosts/flame/plugins/load/load_clip_batch.py @@ -43,10 +43,10 @@ class LoadClipBatch(opfapi.ClipLoader): clip_name = StringTemplate(self.clip_name_template).format( context["representation"]["context"]) - # TODO: settings in imageio # convert colorspace with ocio to flame mapping # in imageio flame section - colorspace = colorspace + colorspace = self.get_native_colorspace(colorspace) + self.log.info("Loading with colorspace: `{}`".format(colorspace)) # create workfile path workfile_dir = options.get("workdir") or os.environ["AVALON_WORKDIR"] From ff565317d0a1abe63671ab5e4b62ce599a000ff7 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 9 Nov 2022 08:39:52 +0800 Subject: [PATCH 058/243] Alembic Loader as Arnold Standin --- .../hosts/maya/plugins/load/load_abc_to_standin.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py index 94bb974917..19e60d33da 100644 --- a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py +++ b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py @@ -1,6 +1,7 @@ import os from openpype.pipeline import ( + legacy_io, load, get_representation_path ) @@ -46,6 +47,7 @@ class AlembicStandinLoader(load.LoaderPlugin): settings = get_project_settings(os.environ['AVALON_PROJECT']) colors = settings["maya"]["load"]["colors"] + fps = legacy_io.Session["AVALON_FPS"] c = colors.get('ass') if c is not None: @@ -65,12 +67,14 @@ class AlembicStandinLoader(load.LoaderPlugin): # Set the standin filepath cmds.setAttr(standinShape + ".dso", self.fname, type="string") - cmds.setAttr(standinShape + ".abcFPS", 25) + cmds.setAttr(standinShape + ".abcFPS", float(fps)) if frameStart is None: cmds.setAttr(standinShape + ".useFrameExtension", 0) + elif frameStart == 1 and frameEnd == 1: cmds.setAttr(standinShape + ".useFrameExtension", 0) + else: cmds.setAttr(standinShape + ".useFrameExtension", 1) @@ -89,7 +93,7 @@ class AlembicStandinLoader(load.LoaderPlugin): import pymel.core as pm path = get_representation_path(representation) - + fps = legacy_io.Session["AVALON_FPS"] # Update the standin standins = list() members = pm.sets(container['objectName'], query=True) @@ -101,7 +105,7 @@ class AlembicStandinLoader(load.LoaderPlugin): for standin in standins: standin.dso.set(path) standin.useFrameExtension.set(0) - standin.abcFPS.set(25) + standin.abcFPS.set(float(fps)) container = pm.PyNode(container["objectName"]) container.representation.set(str(representation["_id"])) From 8feedcbc155387958b325e8eccc4c08bfad8a18e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 9 Nov 2022 11:18:23 +0100 Subject: [PATCH 059/243] fix last version check --- .../hooks/pre_copy_last_published_workfile.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index acbc9ec1c7..96b5ccadb2 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -116,19 +116,19 @@ class CopyLastPublishedWorkfile(PreLaunchHook): return # Get workfile representation + last_version_doc = get_last_version_by_subset_id( + project_name, subset_id, fields=["_id"] + ) + if not last_version_doc: + self.log.debug("Subset does not have any versions") + return + workfile_representation = next( ( representation for representation in get_representations( project_name, - version_ids=[ - ( - get_last_version_by_subset_id( - project_name, subset_id, fields=["_id"] - ) - or {} - ).get("_id") - ], + version_ids=[last_version_doc["_id"]] ) if representation["context"]["task"]["name"] == task_name ), From 50afec52223e415c6118999f7f9ac465ed721d3e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 9 Nov 2022 11:19:03 +0100 Subject: [PATCH 060/243] replaced 'add_sites_to_representations' with 'reset_timer' in rest api --- openpype/modules/sync_server/rest_api.py | 31 +++--------------------- 1 file changed, 3 insertions(+), 28 deletions(-) diff --git a/openpype/modules/sync_server/rest_api.py b/openpype/modules/sync_server/rest_api.py index 0c3b914833..51769cd4fb 100644 --- a/openpype/modules/sync_server/rest_api.py +++ b/openpype/modules/sync_server/rest_api.py @@ -26,36 +26,11 @@ class SyncServerModuleRestApi: def register(self): self.server_manager.add_route( "POST", - self.prefix + "/add_sites_to_representations", - self.add_sites_to_representations, + self.prefix + "/reset_timer", + self.reset_timer, ) - async def add_sites_to_representations(self, request): - # Extract data from request - data = await request.json() - try: - project_name = data["project_name"] - sites = data["sites"] - representations = data["representations"] - except KeyError: - msg = ( - "Payload must contain fields 'project_name," - " 'sites' (list of names) and 'representations' (list of IDs)" - ) - self.log.error(msg) - return Response(status=400, message=msg) - - # Add all sites to each representation - for representation_id in representations: - for site in sites: - self.module.add_site( - project_name, - representation_id, - site, - force=True, - priority=99, - ) - + async def reset_timer(self, request): # Force timer to run immediately self.module.reset_timer() From 0ca0173e9b48b8cddc6b42dc4360d7bad8649d0f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 9 Nov 2022 11:19:15 +0100 Subject: [PATCH 061/243] added ability to rese timer from add_site --- openpype/modules/sync_server/sync_server_module.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index 4d848958e8..7dc1e15322 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -136,7 +136,7 @@ class SyncServerModule(OpenPypeModule, ITrayModule): """ Start of Public API """ def add_site(self, project_name, representation_id, site_name=None, - force=False, priority=None): + force=False, priority=None, reset_timer=False): """ Adds new site to representation to be synced. @@ -171,6 +171,9 @@ class SyncServerModule(OpenPypeModule, ITrayModule): force=force, priority=priority) + if reset_timer: + self.reset_timer() + def remove_site(self, project_name, representation_id, site_name, remove_local_files=False): """ From a137258b1b2a24c503b9db5e47735c9ac9f293d3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 9 Nov 2022 11:19:32 +0100 Subject: [PATCH 062/243] 'reset_timer' can reset timer via rest api endpoint --- .../modules/sync_server/sync_server_module.py | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index 7dc1e15322..26a6abbbf4 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -916,7 +916,42 @@ class SyncServerModule(OpenPypeModule, ITrayModule): In case of user's involvement (reset site), start that right away. """ - self.sync_server_thread.reset_timer() + + if not self.enabled: + return + + if self.sync_server_thread is None: + self._reset_timer_with_rest_api() + else: + self.sync_server_thread.reset_timer() + + def is_representaion_on_site( + self, project_name, representation_id, site_id + ): + # TODO implement + return False + + def _reset_timer_with_rest_api(self): + # POST to webserver sites to add to representations + webserver_url = os.environ.get("OPENPYPE_WEBSERVER_URL") + if not webserver_url: + self.log.warning("Couldn't find webserver url") + return + + rest_api_url = "{}/sync_server/reset_timer".format( + webserver_url + ) + + try: + import requests + except Exception: + self.log.warning( + "Couldn't add sites to representations " + "('requests' is not available)" + ) + return + + requests.post(rest_api_url) def get_enabled_projects(self): """Returns list of projects which have SyncServer enabled.""" From 109c52809c1f599a4d93b8dc15d99f4836c03e26 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 9 Nov 2022 11:20:21 +0100 Subject: [PATCH 063/243] updated prelaunch hook with new abilities of sync server --- .../hooks/pre_copy_last_published_workfile.py | 52 +++++-------------- 1 file changed, 12 insertions(+), 40 deletions(-) diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index 96b5ccadb2..6fd50a64d6 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -141,49 +141,21 @@ class CopyLastPublishedWorkfile(PreLaunchHook): ).format(task_name, host_name) return - # POST to webserver sites to add to representations - webserver_url = os.environ.get("OPENPYPE_WEBSERVER_URL") - if not webserver_url: - self.log.warning("Couldn't find webserver url") - return - - entry_point_url = "{}/sync_server".format(webserver_url) - rest_api_url = "{}/add_sites_to_representations".format( - entry_point_url - ) - try: - import requests - except Exception: - self.log.warning( - "Couldn't add sites to representations " - "('requests' is not available)" - ) - return - local_site_id = get_local_site_id() - requests.post( - rest_api_url, - json={ - "project_name": project_name, - "sites": [local_site_id], - "representations": [str(workfile_representation["_id"])], - }, + sync_server = self.modules_manager.get("sync_server") + sync_server.add_site( + project_name, + workfile_representation["_id"], + local_site_id, + force=True, + priority=99, + reset_timer=True ) - # Wait for the download loop to end - last_created_time = get_representation_last_created_time_on_site( - workfile_representation, local_site_id - ) - while ( - last_created_time - >= get_representation_last_created_time_on_site( - get_representation_by_id( - project_name, - workfile_representation["_id"], - fields=["files"], - ), - local_site_id, - ) + while not sync_server.is_representaion_on_site( + project_name, + workfile_representation["_id"], + local_site_id ): sleep(5) From e89051466efa9d12b0700b95711b049ab3b944ca Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 9 Nov 2022 11:22:56 +0100 Subject: [PATCH 064/243] check if is sync server enabled --- openpype/hooks/pre_copy_last_published_workfile.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index 6fd50a64d6..69e3d6efe4 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -37,6 +37,12 @@ class CopyLastPublishedWorkfile(PreLaunchHook): Returns: None: This is a void method. """ + + sync_server = self.modules_manager.get("sync_server") + if not sync_server or not sync_server.enabled: + self.log.deubg("Sync server module is not enabled or available") + return + # Check there is no workfile available last_workfile = self.data.get("last_workfile_path") if os.path.exists(last_workfile): @@ -142,7 +148,6 @@ class CopyLastPublishedWorkfile(PreLaunchHook): return local_site_id = get_local_site_id() - sync_server = self.modules_manager.get("sync_server") sync_server.add_site( project_name, workfile_representation["_id"], From 4b1a19f3bf868813f5f2a1749eda8277cd2882ba Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 9 Nov 2022 11:36:03 +0100 Subject: [PATCH 065/243] removed 'get_representation_last_created_time_on_site' function --- openpype/client/entities.py | 27 ------------------- .../hooks/pre_copy_last_published_workfile.py | 2 -- 2 files changed, 29 deletions(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 43c2874f57..91d4b499b0 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -1368,33 +1368,6 @@ def get_representation_parents(project_name, representation): return parents_by_repre_id[repre_id] -def get_representation_last_created_time_on_site( - representation: dict, site_name: str -) -> datetime: - """Get `created_dt` value for representation on site. - - Args: - representation (dict): Representation to get creation date of - site_name (str): Site from which to get the creation date - - Returns: - datetime: Created time of representation on site - """ - created_time = next( - ( - site.get("created_dt") - for site in representation["files"][0].get("sites", []) - if site["name"] == site_name - ), - None, - ) - if created_time: - return created_time - else: - # Use epoch as 'zero' time - return datetime.utcfromtimestamp(0) - - def get_thumbnail_id_from_source(project_name, src_type, src_id): """Receive thumbnail id from source entity. diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index 69e3d6efe4..884b0f54b6 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -3,8 +3,6 @@ import shutil from time import sleep from openpype.client.entities import ( get_last_version_by_subset_id, - get_representation_by_id, - get_representation_last_created_time_on_site, get_representations, get_subsets, ) From cc7a3e8581293e7fa2c3a678a43ca9c579c40e0e Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 9 Nov 2022 18:41:48 +0800 Subject: [PATCH 066/243] adding the switching on off for multipart and force muiltilayer options --- openpype/hosts/maya/api/lib_rendersettings.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/openpype/hosts/maya/api/lib_rendersettings.py b/openpype/hosts/maya/api/lib_rendersettings.py index 2b996702c3..2fc7547c8c 100644 --- a/openpype/hosts/maya/api/lib_rendersettings.py +++ b/openpype/hosts/maya/api/lib_rendersettings.py @@ -154,6 +154,16 @@ class RenderSettings(object): self._set_global_output_settings() cmds.setAttr("redshiftOptions.imageFormat", img_ext) + if redshift_render_presets["multilayer_exr"]: + cmds.setAttr("redshiftOptions.exrMultipart", 1) + else: + cmds.setAttr("redshiftOptions.exrMultipart", 0) + + if redshift_render_presets["force_combine"]: + cmds.setAttr("redshiftOptions.exrForceMultilayer", 1) + else: + cmds.setAttr("redshiftOptions.exrForceMultilayer", 0) + cmds.setAttr("defaultResolution.width", width) cmds.setAttr("defaultResolution.height", height) self._additional_attribs_setter(additional_options) From 3ee386543bc6b91ee7a0ab3c95424ac7955d7d98 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 9 Nov 2022 11:43:55 +0100 Subject: [PATCH 067/243] hiero: adding animated knobs also making track per subset --- .../hosts/hiero/plugins/load/load_effects.py | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/hiero/plugins/load/load_effects.py b/openpype/hosts/hiero/plugins/load/load_effects.py index fab426e58d..0819d1d1b7 100644 --- a/openpype/hosts/hiero/plugins/load/load_effects.py +++ b/openpype/hosts/hiero/plugins/load/load_effects.py @@ -13,6 +13,7 @@ from openpype.pipeline import ( get_representation_path ) from openpype.hosts.hiero import api as phiero +from openpype.lib import Logger class LoadEffects(load.LoaderPlugin): @@ -26,6 +27,8 @@ class LoadEffects(load.LoaderPlugin): icon = "cc" color = "white" + log = Logger.get_logger(__name__) + def load(self, context, name, namespace, data): """ Loading function to get the soft effects to particular read node @@ -41,7 +44,7 @@ class LoadEffects(load.LoaderPlugin): """ active_sequence = phiero.get_current_sequence() active_track = phiero.get_current_track( - active_sequence, "LoadedEffects") + active_sequence, "Loaded_{}".format(name)) # get main variables namespace = namespace or context["asset"]["name"] @@ -119,7 +122,27 @@ class LoadEffects(load.LoaderPlugin): continue try: - node[knob_name].setValue(knob_value) + # assume list means animation + # except 4 values could be RGBA or vector + if isinstance(knob_value, list) and len(knob_value) > 4: + node[knob_name].setAnimated() + for i, value in enumerate(knob_value): + if isinstance(value, list): + # list can have vector animation + for ci, cv in enumerate(value): + node[knob_name].setValueAt( + cv, + (clip_in + i), + ci + ) + else: + # list is single values + node[knob_name].setValueAt( + value, + (clip_in + i) + ) + else: + node[knob_name].setValue(knob_value) except NameError: self.log.warning("Knob: {} cannot be set".format( knob_name)) From 189dc17ce3440f9a45d68c631b19be6ddb017907 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 9 Nov 2022 11:51:00 +0100 Subject: [PATCH 068/243] fix typo --- openpype/hooks/pre_copy_last_published_workfile.py | 2 +- openpype/modules/sync_server/sync_server_module.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index 884b0f54b6..0e561334e1 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -155,7 +155,7 @@ class CopyLastPublishedWorkfile(PreLaunchHook): reset_timer=True ) - while not sync_server.is_representaion_on_site( + while not sync_server.is_representation_on_site( project_name, workfile_representation["_id"], local_site_id diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index 26a6abbbf4..7228f43f84 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -925,7 +925,7 @@ class SyncServerModule(OpenPypeModule, ITrayModule): else: self.sync_server_thread.reset_timer() - def is_representaion_on_site( + def is_representation_on_site( self, project_name, representation_id, site_id ): # TODO implement From 21833283b833327b3b079604e389efe23af7f3c8 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 9 Nov 2022 12:27:07 +0100 Subject: [PATCH 069/243] added method to check if representation has all files on site --- .../modules/sync_server/sync_server_module.py | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index 7228f43f84..dc20e37a12 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -926,10 +926,27 @@ class SyncServerModule(OpenPypeModule, ITrayModule): self.sync_server_thread.reset_timer() def is_representation_on_site( - self, project_name, representation_id, site_id + self, project_name, representation_id, site_name ): - # TODO implement - return False + """Checks if 'representation_id' has all files avail. on 'site_name'""" + representation = get_representation_by_id(project_name, + representation_id, + fields=["_id", "files"]) + if not representation: + return False + + on_site = False + for file_info in representation.get("files", []): + for site in file_info.get("sites", []): + if site["name"] != site_name: + continue + + if (site.get("progress") or site.get("error") or + not site.get("created_dt")): + return False + on_site = True + + return on_site def _reset_timer_with_rest_api(self): # POST to webserver sites to add to representations From d8c7ff2d15b912f00588771cd7aff8effef7f6df Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 9 Nov 2022 12:48:52 +0100 Subject: [PATCH 070/243] small updates to docstrings --- openpype/modules/sync_server/sync_server_module.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index dc20e37a12..9a2ff97ed6 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -143,7 +143,7 @@ class SyncServerModule(OpenPypeModule, ITrayModule): 'project_name' must have synchronization enabled (globally or project only) - Used as a API endpoint from outside applications (Loader etc). + Used as an API endpoint from outside applications (Loader etc). Use 'force' to reset existing site. @@ -153,6 +153,8 @@ class SyncServerModule(OpenPypeModule, ITrayModule): site_name (string): name of configured and active site force (bool): reset site if exists priority (int): set priority + reset_timer (bool): if delay timer should be reset, eg. user mark + some representation to be synced manually Throws: SiteAlreadyPresentError - if adding already existing site and @@ -1601,12 +1603,12 @@ class SyncServerModule(OpenPypeModule, ITrayModule): Args: project_name (string): name of project - force to db connection as each file might come from different collection - new_file_id (string): + new_file_id (string): only present if file synced successfully file (dictionary): info about processed file (pulled from DB) representation (dictionary): parent repr of file (from DB) site (string): label ('gdrive', 'S3') error (string): exception message - progress (float): 0-1 of progress of upload/download + progress (float): 0-0.99 of progress of upload/download priority (int): 0-100 set priority Returns: From 756bb9d85acf7d8d286eb21ca205185d0a18eed1 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 9 Nov 2022 16:26:44 +0100 Subject: [PATCH 071/243] hiero: improving management of versions --- openpype/hosts/hiero/api/lib.py | 10 ++++++++-- openpype/hosts/hiero/api/pipeline.py | 21 ++++++++++++--------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/hiero/api/lib.py b/openpype/hosts/hiero/api/lib.py index e340209207..2829fe2bf5 100644 --- a/openpype/hosts/hiero/api/lib.py +++ b/openpype/hosts/hiero/api/lib.py @@ -11,6 +11,7 @@ import functools import warnings import json import ast +import secrets import shutil import hiero @@ -350,6 +351,8 @@ def set_track_openpype_tag(track, data=None): Returns: hiero.core.Tag """ + hash = secrets.token_hex(nbytes=4) + data = data or {} # basic Tag's attribute @@ -367,7 +370,10 @@ def set_track_openpype_tag(track, data=None): tag = tags.update_tag(_tag, tag_data) else: # if pype tag available then update with input data - tag = tags.create_tag(self.pype_tag_name, tag_data) + tag = tags.create_tag( + "{}_{}".format(self.pype_tag_name, hash), + tag_data + ) # add it to the input track item track.addTag(tag) @@ -390,7 +396,7 @@ def get_track_openpype_tag(track): return None for tag in _tags: # return only correct tag defined by global name - if tag.name() == self.pype_tag_name: + if self.pype_tag_name in tag.name(): return tag diff --git a/openpype/hosts/hiero/api/pipeline.py b/openpype/hosts/hiero/api/pipeline.py index 3475bc62e4..4ab73e7d19 100644 --- a/openpype/hosts/hiero/api/pipeline.py +++ b/openpype/hosts/hiero/api/pipeline.py @@ -201,6 +201,15 @@ def parse_container(item, validate=True): return data_to_container(item, _data) +def _update_container_data(container, data): + for key in container: + try: + container[key] = data[key] + except KeyError: + pass + return container + + def update_container(item, data=None): """Update container data to input track_item or track's openpype tag. @@ -214,15 +223,9 @@ def update_container(item, data=None): bool: True if container was updated correctly """ - def update_container_data(container, data): - for key in container: - try: - container[key] = data[key] - except KeyError: - pass - return container data = data or {} + data = deepcopy(data) if type(item) == hiero.core.VideoTrack: # form object data for test @@ -236,14 +239,14 @@ def update_container(item, data=None): container = deepcopy(container) # update data in container - updated_container = update_container_data(container, data) + updated_container = _update_container_data(container, data) # merge updated container back to containers containers.update({object_name: updated_container}) return bool(lib.set_track_openpype_tag(item, containers)) else: container = lib.get_trackitem_openpype_data(item) - updated_container = update_container_data(container, data) + updated_container = _update_container_data(container, data) log.info("Updating container: `{}`".format(item.name())) return bool(lib.set_trackitem_openpype_tag(item, updated_container)) From f111fc3763a2ae2b43330a08c4bf2064ef646cbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Wed, 9 Nov 2022 17:09:38 +0100 Subject: [PATCH 072/243] clean --- openpype/client/entities.py | 1 - openpype/modules/sync_server/rest_api.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 91d4b499b0..43afccf2f1 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -6,7 +6,6 @@ that has project name as a context (e.g. on 'ProjectEntity'?). + We will need more specific functions doing wery specific queires really fast. """ -from datetime import datetime import re import collections diff --git a/openpype/modules/sync_server/rest_api.py b/openpype/modules/sync_server/rest_api.py index 51769cd4fb..a7d9dd80b7 100644 --- a/openpype/modules/sync_server/rest_api.py +++ b/openpype/modules/sync_server/rest_api.py @@ -30,8 +30,8 @@ class SyncServerModuleRestApi: self.reset_timer, ) - async def reset_timer(self, request): - # Force timer to run immediately + async def reset_timer(self, _request): + """Force timer to run immediately.""" self.module.reset_timer() return Response(status=200) From 9996c3f1afbe2e2b3adb110382586ffefd82a3ae Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 10 Nov 2022 13:44:20 +0800 Subject: [PATCH 073/243] AOV Filtering --- openpype/hosts/maya/api/lib_renderproducts.py | 3 ++- openpype/hosts/maya/api/lib_rendersettings.py | 10 ---------- .../deadline/plugins/publish/submit_publish_job.py | 10 ++++++---- vendor/configs/OpenColorIO-Configs | 1 + 4 files changed, 9 insertions(+), 15 deletions(-) create mode 160000 vendor/configs/OpenColorIO-Configs diff --git a/openpype/hosts/maya/api/lib_renderproducts.py b/openpype/hosts/maya/api/lib_renderproducts.py index cd204445b7..ef75391638 100644 --- a/openpype/hosts/maya/api/lib_renderproducts.py +++ b/openpype/hosts/maya/api/lib_renderproducts.py @@ -1016,7 +1016,8 @@ class RenderProductsRedshift(ARenderProducts): # due to some AOVs still being written into separate files, # like Cryptomatte. # AOVs are merged in multi-channel file - multipart = bool(self._get_attr("redshiftOptions.exrForceMultilayer")) + multipart = bool(self._get_attr("redshiftOptions.exrForceMultilayer")) or \ + bool(self._get_attr("redshiftOptions.exrMultipart")) # Get Redshift Extension from image format image_format = self._get_attr("redshiftOptions.imageFormat") # integer diff --git a/openpype/hosts/maya/api/lib_rendersettings.py b/openpype/hosts/maya/api/lib_rendersettings.py index 2fc7547c8c..2b996702c3 100644 --- a/openpype/hosts/maya/api/lib_rendersettings.py +++ b/openpype/hosts/maya/api/lib_rendersettings.py @@ -154,16 +154,6 @@ class RenderSettings(object): self._set_global_output_settings() cmds.setAttr("redshiftOptions.imageFormat", img_ext) - if redshift_render_presets["multilayer_exr"]: - cmds.setAttr("redshiftOptions.exrMultipart", 1) - else: - cmds.setAttr("redshiftOptions.exrMultipart", 0) - - if redshift_render_presets["force_combine"]: - cmds.setAttr("redshiftOptions.exrForceMultilayer", 1) - else: - cmds.setAttr("redshiftOptions.exrForceMultilayer", 0) - cmds.setAttr("defaultResolution.width", width) cmds.setAttr("defaultResolution.height", height) self._additional_attribs_setter(additional_options) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 35f2532c16..615be78794 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -494,12 +494,14 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): else: render_file_name = os.path.basename(col) aov_patterns = self.aov_filter - preview = match_aov_pattern(app, aov_patterns, render_file_name) - + self.log.info("aov_pattern:{}".format(aov_patterns)) # toggle preview on if multipart is on - if instance_data.get("multipartExr"): + preview = match_aov_pattern(app, aov_patterns, render_file_name) + #if instance_data.get("multipartExr"): + if "Cryptomatte" in render_file_name: # for redshift preview = True + self.log.info("preview:{}".format(preview)) new_instance = deepcopy(instance_data) new_instance["subset"] = subset_name new_instance["subsetGroup"] = group_name @@ -542,7 +544,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): if new_instance.get("extendFrames", False): self._copy_extend_frames(new_instance, rep) instances.append(new_instance) - + self.log.info("instances:{}".format(instances)) return instances def _get_representations(self, instance, exp_files): diff --git a/vendor/configs/OpenColorIO-Configs b/vendor/configs/OpenColorIO-Configs new file mode 160000 index 0000000000..0bb079c08b --- /dev/null +++ b/vendor/configs/OpenColorIO-Configs @@ -0,0 +1 @@ +Subproject commit 0bb079c08be410030669cbf5f19ff869b88af953 From 7e2ba84911dec742654ab07f28062c0ccbf0a731 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 10 Nov 2022 13:57:15 +0800 Subject: [PATCH 074/243] AOV Filtering --- openpype/hosts/maya/api/lib_renderproducts.py | 3 ++- .../modules/deadline/plugins/publish/submit_publish_job.py | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/api/lib_renderproducts.py b/openpype/hosts/maya/api/lib_renderproducts.py index ef75391638..f89441cfc7 100644 --- a/openpype/hosts/maya/api/lib_renderproducts.py +++ b/openpype/hosts/maya/api/lib_renderproducts.py @@ -1016,7 +1016,8 @@ class RenderProductsRedshift(ARenderProducts): # due to some AOVs still being written into separate files, # like Cryptomatte. # AOVs are merged in multi-channel file - multipart = bool(self._get_attr("redshiftOptions.exrForceMultilayer")) or \ + multipart = bool(self._get_attr("redshiftOptions.exrForceMultilayer")) \ + or \ bool(self._get_attr("redshiftOptions.exrMultipart")) # Get Redshift Extension from image format diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 615be78794..18fc769d49 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -497,7 +497,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): self.log.info("aov_pattern:{}".format(aov_patterns)) # toggle preview on if multipart is on preview = match_aov_pattern(app, aov_patterns, render_file_name) - #if instance_data.get("multipartExr"): if "Cryptomatte" in render_file_name: # for redshift preview = True From 252859ce0206a011828a1314e1530dbc12db5ea7 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 10 Nov 2022 18:03:46 +0800 Subject: [PATCH 075/243] AOV Filtering --- openpype/hosts/maya/api/lib_renderproducts.py | 6 ++++-- .../modules/deadline/plugins/publish/submit_publish_job.py | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/maya/api/lib_renderproducts.py b/openpype/hosts/maya/api/lib_renderproducts.py index f89441cfc7..a95c1c4932 100644 --- a/openpype/hosts/maya/api/lib_renderproducts.py +++ b/openpype/hosts/maya/api/lib_renderproducts.py @@ -536,6 +536,7 @@ class RenderProductsArnold(ARenderProducts): products = [] aov_name = self._get_attr(aov, "name") + multipart = bool(self._get_attr("defaultArnoldDriver.multipart")) ai_drivers = cmds.listConnections("{}.outputs".format(aov), source=True, destination=False, @@ -589,6 +590,7 @@ class RenderProductsArnold(ARenderProducts): ext=ext, aov=aov_name, driver=ai_driver, + multipart=multipart, camera=camera) products.append(product) @@ -1016,9 +1018,9 @@ class RenderProductsRedshift(ARenderProducts): # due to some AOVs still being written into separate files, # like Cryptomatte. # AOVs are merged in multi-channel file + multipart = bool(self._get_attr("redshiftOptions.exrForceMultilayer")) \ - or \ - bool(self._get_attr("redshiftOptions.exrMultipart")) + or bool(self._get_attr("redshiftOptions.exrMultipart")) # Get Redshift Extension from image format image_format = self._get_attr("redshiftOptions.imageFormat") # integer diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 18fc769d49..27400bb269 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -494,13 +494,13 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): else: render_file_name = os.path.basename(col) aov_patterns = self.aov_filter - self.log.info("aov_pattern:{}".format(aov_patterns)) + # toggle preview on if multipart is on preview = match_aov_pattern(app, aov_patterns, render_file_name) - if "Cryptomatte" in render_file_name: # for redshift + + if instance_data.get("multipartExr"): preview = True - self.log.info("preview:{}".format(preview)) new_instance = deepcopy(instance_data) new_instance["subset"] = subset_name new_instance["subsetGroup"] = group_name From 3cd1918f04ef5c13ab10e003064699b1659f8fb0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 10 Nov 2022 11:23:32 +0100 Subject: [PATCH 076/243] shorter animation --- openpype/tools/publisher/widgets/widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index a33e6e7565..71f476c4ef 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -1726,7 +1726,7 @@ class CreateNextPageOverlay(QtWidgets.QWidget): change_anim = QtCore.QVariantAnimation() change_anim.setStartValue(0.0) change_anim.setEndValue(1.0) - change_anim.setDuration(400) + change_anim.setDuration(200) change_anim.setEasingCurve(QtCore.QEasingCurve.OutCubic) change_anim.valueChanged.connect(self._on_anim) From 8eb704aeb2703c5809f6b236c7ec8f6b24fd2941 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 10 Nov 2022 19:09:13 +0800 Subject: [PATCH 077/243] Alembic Loader as Arnold Standin --- .../maya/plugins/load/load_abc_to_standin.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py index 19e60d33da..a192d9c357 100644 --- a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py +++ b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py @@ -28,11 +28,10 @@ class AlembicStandinLoader(load.LoaderPlugin): version = context["version"] version_data = version.get("data", {}) - + family = version["data"]["families"] self.log.info("version_data: {}\n".format(version_data)) - + self.log.info("family: {}\n".format(family)) frameStart = version_data.get("frameStart", None) - frameEnd = version_data.get("frameEnd", None) asset = context["asset"]["name"] namespace = namespace or unique_namespace( @@ -48,12 +47,14 @@ class AlembicStandinLoader(load.LoaderPlugin): settings = get_project_settings(os.environ['AVALON_PROJECT']) colors = settings["maya"]["load"]["colors"] fps = legacy_io.Session["AVALON_FPS"] - - c = colors.get('ass') + c = colors.get(family[0]) if c is not None: cmds.setAttr(root + ".useOutlinerColor", 1) cmds.setAttr(root + ".outlinerColor", - c[0], c[1], c[2]) + (float(c[0])/255), + (float(c[1])/255), + (float(c[2])/255) + ) transform_name = label + "_ABC" @@ -72,7 +73,7 @@ class AlembicStandinLoader(load.LoaderPlugin): if frameStart is None: cmds.setAttr(standinShape + ".useFrameExtension", 0) - elif frameStart == 1 and frameEnd == 1: + elif "model" in family: cmds.setAttr(standinShape + ".useFrameExtension", 0) else: From fe47deca3ce1832f00f336051c27f5a4627964d1 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 10 Nov 2022 19:10:41 +0800 Subject: [PATCH 078/243] Alembic Loader as Arnold Standin --- openpype/hosts/maya/plugins/load/load_abc_to_standin.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py index a192d9c357..8ce1aee3ac 100644 --- a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py +++ b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py @@ -30,7 +30,6 @@ class AlembicStandinLoader(load.LoaderPlugin): version_data = version.get("data", {}) family = version["data"]["families"] self.log.info("version_data: {}\n".format(version_data)) - self.log.info("family: {}\n".format(family)) frameStart = version_data.get("frameStart", None) asset = context["asset"]["name"] @@ -51,10 +50,10 @@ class AlembicStandinLoader(load.LoaderPlugin): if c is not None: cmds.setAttr(root + ".useOutlinerColor", 1) cmds.setAttr(root + ".outlinerColor", - (float(c[0])/255), - (float(c[1])/255), - (float(c[2])/255) - ) + (float(c[0])/255), + (float(c[1])/255), + (float(c[2])/255) + ) transform_name = label + "_ABC" From bb924595c88fa0268eb3c6e3615ced6af5d6c755 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 10 Nov 2022 19:11:56 +0800 Subject: [PATCH 079/243] Alembic Loader as Arnold Standin --- openpype/hosts/maya/plugins/load/load_abc_to_standin.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py index 8ce1aee3ac..d93c85f8a4 100644 --- a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py +++ b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py @@ -50,10 +50,10 @@ class AlembicStandinLoader(load.LoaderPlugin): if c is not None: cmds.setAttr(root + ".useOutlinerColor", 1) cmds.setAttr(root + ".outlinerColor", - (float(c[0])/255), - (float(c[1])/255), - (float(c[2])/255) - ) + (float(c[0])/255), + (float(c[1])/255), + (float(c[2])/255) + ) transform_name = label + "_ABC" From 17c3b1f96ae5fefc1bcec3bc43fcbdc8bf8bb4fc Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 10 Nov 2022 19:13:04 +0800 Subject: [PATCH 080/243] Alembic Loader as Arnold Standin --- openpype/hosts/maya/plugins/load/load_abc_to_standin.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py index d93c85f8a4..dafe999d9d 100644 --- a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py +++ b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py @@ -50,9 +50,9 @@ class AlembicStandinLoader(load.LoaderPlugin): if c is not None: cmds.setAttr(root + ".useOutlinerColor", 1) cmds.setAttr(root + ".outlinerColor", - (float(c[0])/255), - (float(c[1])/255), - (float(c[2])/255) + (float(c[0])/255), + (float(c[1])/255), + (float(c[2])/255) ) transform_name = label + "_ABC" From 98244c77b08989959f04293a1b54aa43d3b2c67f Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 10 Nov 2022 19:15:11 +0800 Subject: [PATCH 081/243] Alembic Loader as Arnold Standin --- openpype/hosts/maya/plugins/load/load_abc_to_standin.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py index dafe999d9d..d93c85f8a4 100644 --- a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py +++ b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py @@ -50,9 +50,9 @@ class AlembicStandinLoader(load.LoaderPlugin): if c is not None: cmds.setAttr(root + ".useOutlinerColor", 1) cmds.setAttr(root + ".outlinerColor", - (float(c[0])/255), - (float(c[1])/255), - (float(c[2])/255) + (float(c[0])/255), + (float(c[1])/255), + (float(c[2])/255) ) transform_name = label + "_ABC" From c5547766074b7999ea880b76705c14fba2828cd3 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 10 Nov 2022 19:16:10 +0800 Subject: [PATCH 082/243] Alembic Loader as Arnold Standin --- openpype/hosts/maya/plugins/load/load_abc_to_standin.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py index d93c85f8a4..8ce1aee3ac 100644 --- a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py +++ b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py @@ -50,10 +50,10 @@ class AlembicStandinLoader(load.LoaderPlugin): if c is not None: cmds.setAttr(root + ".useOutlinerColor", 1) cmds.setAttr(root + ".outlinerColor", - (float(c[0])/255), - (float(c[1])/255), - (float(c[2])/255) - ) + (float(c[0])/255), + (float(c[1])/255), + (float(c[2])/255) + ) transform_name = label + "_ABC" From d73ac24f59490554c20eb89af7675ae3bbcb0496 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 10 Nov 2022 19:19:11 +0800 Subject: [PATCH 083/243] Alembic Loader as Arnold Standin --- openpype/hosts/maya/plugins/load/load_abc_to_standin.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py index 8ce1aee3ac..9583063c7e 100644 --- a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py +++ b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py @@ -30,6 +30,7 @@ class AlembicStandinLoader(load.LoaderPlugin): version_data = version.get("data", {}) family = version["data"]["families"] self.log.info("version_data: {}\n".format(version_data)) + self.log.info("family: {}\n".format(family)) frameStart = version_data.get("frameStart", None) asset = context["asset"]["name"] @@ -48,12 +49,12 @@ class AlembicStandinLoader(load.LoaderPlugin): fps = legacy_io.Session["AVALON_FPS"] c = colors.get(family[0]) if c is not None: + r = (float(c[0]) / 255) + g = (float(c[1]) / 255) + b = (float(c[2]) / 255) cmds.setAttr(root + ".useOutlinerColor", 1) cmds.setAttr(root + ".outlinerColor", - (float(c[0])/255), - (float(c[1])/255), - (float(c[2])/255) - ) + r, g, b) transform_name = label + "_ABC" From ddd4e653919adfe58b10caa857948ccf98066868 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 10 Nov 2022 13:39:01 +0100 Subject: [PATCH 084/243] hiero: unification of openpype tags --- openpype/hosts/hiero/api/lib.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/hiero/api/lib.py b/openpype/hosts/hiero/api/lib.py index 2829fe2bf5..7f0cf8149a 100644 --- a/openpype/hosts/hiero/api/lib.py +++ b/openpype/hosts/hiero/api/lib.py @@ -341,6 +341,11 @@ def get_track_item_tags(track_item): return returning_tag_data +def _get_tag_unique_hash(): + # sourcery skip: avoid-builtin-shadow + return secrets.token_hex(nbytes=4) + + def set_track_openpype_tag(track, data=None): """ Set openpype track tag to input track object. @@ -351,8 +356,6 @@ def set_track_openpype_tag(track, data=None): Returns: hiero.core.Tag """ - hash = secrets.token_hex(nbytes=4) - data = data or {} # basic Tag's attribute @@ -371,7 +374,10 @@ def set_track_openpype_tag(track, data=None): else: # if pype tag available then update with input data tag = tags.create_tag( - "{}_{}".format(self.pype_tag_name, hash), + "{}_{}".format( + self.pype_tag_name, + _get_tag_unique_hash() + ), tag_data ) # add it to the input track item @@ -468,7 +474,7 @@ def get_trackitem_openpype_tag(track_item): return None for tag in _tags: # return only correct tag defined by global name - if tag.name() == self.pype_tag_name: + if self.pype_tag_name in tag.name(): return tag @@ -493,13 +499,18 @@ def set_trackitem_openpype_tag(track_item, data=None): } # get available pype tag if any _tag = get_trackitem_openpype_tag(track_item) - if _tag: # it not tag then create one tag = tags.update_tag(_tag, tag_data) else: # if pype tag available then update with input data - tag = tags.create_tag(self.pype_tag_name, tag_data) + tag = tags.create_tag( + "{}_{}".format( + self.pype_tag_name, + _get_tag_unique_hash() + ), + tag_data + ) # add it to the input track item track_item.addTag(tag) From c5d3e8a45788ce03c996096f5af89df967e735a0 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 10 Nov 2022 13:39:25 +0100 Subject: [PATCH 085/243] hiero: loading effects not able delete previous nodes --- openpype/hosts/hiero/plugins/load/load_effects.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/hiero/plugins/load/load_effects.py b/openpype/hosts/hiero/plugins/load/load_effects.py index 0819d1d1b7..a3fcd63b5b 100644 --- a/openpype/hosts/hiero/plugins/load/load_effects.py +++ b/openpype/hosts/hiero/plugins/load/load_effects.py @@ -188,7 +188,9 @@ class LoadEffects(load.LoaderPlugin): if loaded_stitem not in used_subtracks: continue item_to_remove = used_subtracks.pop(loaded_stitem) - item_to_remove.node()["enable"].setValue(0) + # TODO: find a way to erase nodes + self.log.debug( + "This node needs to be removed: {}".format(item_to_remove)) data_imprint = { "objectName": object_name, From 79eb997e4b7d49510615606cb6fa1c05ddec67d7 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 10 Nov 2022 14:19:49 +0100 Subject: [PATCH 086/243] flame: convert color mapping to classmethod --- openpype/hosts/flame/api/plugin.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/flame/api/plugin.py b/openpype/hosts/flame/api/plugin.py index 45fa7fd9a4..9efbd5c1bc 100644 --- a/openpype/hosts/flame/api/plugin.py +++ b/openpype/hosts/flame/api/plugin.py @@ -692,16 +692,17 @@ class ClipLoader(LoaderPlugin): _mapping = None - def get_native_colorspace(self, input_colorspace): - if not self._mapping: + @classmethod + def get_native_colorspace(cls, input_colorspace): + if not cls._mapping: settings = get_current_project_settings()["flame"] mapping = settings["imageio"]["profilesMapping"]["inputs"] - self._mapping = { + cls._mapping = { input["ocioName"]: input["flameName"] for input in mapping } - return self._mapping.get(input_colorspace) + return cls._mapping.get(input_colorspace) class OpenClipSolver(flib.MediaInfoFile): From 0f392dd99455eec17b81a73ac7894d3286d7fa17 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 10 Nov 2022 17:11:42 +0100 Subject: [PATCH 087/243] falame: better colorspace loading --- openpype/hosts/flame/api/plugin.py | 38 ++++++++++++++++++- .../hosts/flame/plugins/load/load_clip.py | 3 +- .../flame/plugins/load/load_clip_batch.py | 2 +- 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/flame/api/plugin.py b/openpype/hosts/flame/api/plugin.py index 9efbd5c1bc..26129ebaa6 100644 --- a/openpype/hosts/flame/api/plugin.py +++ b/openpype/hosts/flame/api/plugin.py @@ -4,13 +4,13 @@ import shutil from copy import deepcopy from xml.etree import ElementTree as ET +import qargparse from Qt import QtCore, QtWidgets -import qargparse from openpype import style -from openpype.settings import get_current_project_settings from openpype.lib import Logger from openpype.pipeline import LegacyCreator, LoaderPlugin +from openpype.settings import get_current_project_settings from . import constants from . import lib as flib @@ -692,8 +692,42 @@ class ClipLoader(LoaderPlugin): _mapping = None + def get_colorspace(self, context): + """Get colorspace name + + Look either to version data or representation data. + + Args: + context (dict): version context data + + Returns: + str: colorspace name or None + """ + version = context['version'] + version_data = version.get("data", {}) + colorspace = version_data.get( + "colorspace", None + ) + + if ( + not colorspace + or colorspace == "Unknown" + ): + colorspace = context["representation"]["data"].get( + "colorspace", None) + + return colorspace + @classmethod def get_native_colorspace(cls, input_colorspace): + """Return native colorspace name. + + Args: + input_colorspace (str | None): colorspace name + + Returns: + str: native colorspace name defined in mapping or None + """ if not cls._mapping: settings = get_current_project_settings()["flame"] mapping = settings["imageio"]["profilesMapping"]["inputs"] diff --git a/openpype/hosts/flame/plugins/load/load_clip.py b/openpype/hosts/flame/plugins/load/load_clip.py index 23879b923e..f8cb7b3e11 100644 --- a/openpype/hosts/flame/plugins/load/load_clip.py +++ b/openpype/hosts/flame/plugins/load/load_clip.py @@ -36,7 +36,8 @@ class LoadClip(opfapi.ClipLoader): version = context['version'] version_data = version.get("data", {}) version_name = version.get("name", None) - colorspace = version_data.get("colorspace", None) + colorspace = self.get_colorspace(context) + clip_name = StringTemplate(self.clip_name_template).format( context["representation"]["context"]) diff --git a/openpype/hosts/flame/plugins/load/load_clip_batch.py b/openpype/hosts/flame/plugins/load/load_clip_batch.py index 19c0ed1ef0..048ac19431 100644 --- a/openpype/hosts/flame/plugins/load/load_clip_batch.py +++ b/openpype/hosts/flame/plugins/load/load_clip_batch.py @@ -35,7 +35,7 @@ class LoadClipBatch(opfapi.ClipLoader): version = context['version'] version_data = version.get("data", {}) version_name = version.get("name", None) - colorspace = version_data.get("colorspace", None) + colorspace = self.get_colorspace(context) # in case output is not in context replace key to representation if not context["representation"]["context"].get("output"): From 9a722cb8bb8acd5deb744acfd11fab3528ae6289 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 10 Nov 2022 17:30:10 +0100 Subject: [PATCH 088/243] :art: creator for online family --- .../plugins/create/create_online.py | 98 +++++++++++++++++++ .../plugins/publish/collect_online_file.py | 24 +++++ 2 files changed, 122 insertions(+) create mode 100644 openpype/hosts/traypublisher/plugins/create/create_online.py create mode 100644 openpype/hosts/traypublisher/plugins/publish/collect_online_file.py diff --git a/openpype/hosts/traypublisher/plugins/create/create_online.py b/openpype/hosts/traypublisher/plugins/create/create_online.py new file mode 100644 index 0000000000..e8092e8eaf --- /dev/null +++ b/openpype/hosts/traypublisher/plugins/create/create_online.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +"""Creator of online files. + +Online file retain their original name and use it as subset name. To +avoid conflicts, this creator checks if subset with this name already +exists under selected asset. +""" +import copy +import os +import re +from pathlib import Path + +from openpype.client import get_subset_by_name, get_asset_by_name +from openpype.lib.attribute_definitions import FileDef +from openpype.pipeline import ( + CreatedInstance, + CreatorError +) +from openpype.pipeline.create import ( + get_subset_name, + TaskNotSetError, +) + +from openpype.hosts.traypublisher.api.plugin import TrayPublishCreator + + +class OnlineCreator(TrayPublishCreator): + """Creates instance from file and retains its original name.""" + + identifier = "io.openpype.creators.traypublisher.online" + label = "Online" + family = "online" + description = "Publish file retaining its original file name" + extensions = [".mov", ".mp4", ".mfx", ".m4v", ".mpg"] + + def get_detail_description(self): + return """# Publish batch of .mov to multiple assets. + + File names must then contain only asset name, or asset name + version. + (eg. 'chair.mov', 'chair_v001.mov', not really safe `my_chair_v001.mov` + """ + + def get_icon(self): + return "fa.file" + + def create(self, subset_name, instance_data, pre_create_data): + if not pre_create_data.get("representation_file")["filenames"]: + raise CreatorError("No files specified") + + asset = get_asset_by_name(self.project_name, instance_data["asset"]) + origin_basename = Path(pre_create_data.get( + "representation_file")["filenames"][0]).stem + + if get_subset_by_name( + self.project_name, origin_basename, asset["_id"]): + raise CreatorError(f"subset with {origin_basename} already " + "exists in selected asset") + + instance_data["originalBasename"] = origin_basename + subset_name = origin_basename + path = (Path( + pre_create_data.get( + "representation_file")["directory"] + ) / pre_create_data.get( + "representation_file")["filenames"][0]).as_posix() + + instance_data["creator_attributes"] = {"path": path} + + # Create new instance + new_instance = CreatedInstance(self.family, subset_name, + instance_data, self) + self._store_new_instance(new_instance) + + def get_pre_create_attr_defs(self): + return [ + FileDef( + "representation_file", + folders=False, + extensions=self.extensions, + allow_sequences=False, + single_item=True, + label="Representation", + ) + ] + + def get_subset_name( + self, + variant, + task_name, + asset_doc, + project_name, + host_name=None, + instance=None + ): + if instance is None: + return "{originalBasename}" + + return instance.data["subset"] diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_online_file.py b/openpype/hosts/traypublisher/plugins/publish/collect_online_file.py new file mode 100644 index 0000000000..1d173c326b --- /dev/null +++ b/openpype/hosts/traypublisher/plugins/publish/collect_online_file.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +import pyblish.api +from pathlib import Path + + +class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin): + """Collect online file and retain its file name.""" + label = "Collect online file" + families = ["online"] + hosts = ["traypublisher"] + + def process(self, instance): + file = Path(instance.data["creator_attributes"]["path"]) + + if not instance.data.get("representations"): + instance.data["representations"] = [ + { + "name": file.suffix.lstrip("."), + "ext": file.suffix.lstrip("."), + "files": file.name, + "stagingDir": file.parent.as_posix() + } + ] + From 2b8846766f8cb65f9a6f7528c15ae840849097e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 10 Nov 2022 17:30:57 +0100 Subject: [PATCH 089/243] :art: defaults for online family --- .../defaults/project_anatomy/templates.json | 8 +++++++- .../settings/defaults/project_settings/global.json | 14 +++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/openpype/settings/defaults/project_anatomy/templates.json b/openpype/settings/defaults/project_anatomy/templates.json index 3415c4451f..0ac56a4dad 100644 --- a/openpype/settings/defaults/project_anatomy/templates.json +++ b/openpype/settings/defaults/project_anatomy/templates.json @@ -48,10 +48,16 @@ "file": "{originalBasename}_{@version}.{ext}", "path": "{@folder}/{@file}" }, + "online": { + "folder": "{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/{@version}", + "file": "{originalBasename}<.{@frame}><_{udim}>.{ext}", + "path": "{@folder}/{@file}" + }, "__dynamic_keys_labels__": { "maya2unreal": "Maya to Unreal", "simpleUnrealTextureHero": "Simple Unreal Texture - Hero", - "simpleUnrealTexture": "Simple Unreal Texture" + "simpleUnrealTexture": "Simple Unreal Texture", + "online": "online" } } } \ No newline at end of file diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 9c3f2f1e1b..0409ce802c 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -483,7 +483,19 @@ ] }, "publish": { - "template_name_profiles": [], + "template_name_profiles": [ + { + "families": [ + "online" + ], + "hosts": [ + "traypublisher" + ], + "task_types": [], + "task_names": [], + "template_name": "online" + } + ], "hero_template_name_profiles": [] } }, From 7ca8b4ddf476644847f84d4690a69a81dd2bba41 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 10 Nov 2022 17:36:58 +0100 Subject: [PATCH 090/243] OP-4361 - addd support for multiple composition in AE There cannot be multiple Render items for same composition in Render Queue. There cannot be multiple output modules for same Render item. --- openpype/hosts/aftereffects/api/extension.zxp | Bin 100943 -> 101272 bytes .../aftereffects/api/extension/js/main.js | 4 +- .../api/extension/jsx/hostscript.jsx | 96 +++++++++++++----- openpype/hosts/aftereffects/api/ws_stub.py | 10 +- .../plugins/publish/collect_render.py | 15 ++- .../plugins/publish/extract_local_render.py | 12 ++- 6 files changed, 93 insertions(+), 44 deletions(-) diff --git a/openpype/hosts/aftereffects/api/extension.zxp b/openpype/hosts/aftereffects/api/extension.zxp index 217d06098f856d8daa2e50f4dcdb56149fe71b52..e7b3bcac8ae26be2d55a122a77701e43e265fa06 100644 GIT binary patch delta 10790 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 F{|{^Kuk`=` diff --git a/openpype/hosts/aftereffects/api/extension/js/main.js b/openpype/hosts/aftereffects/api/extension/js/main.js index 2105ea82dc..bb0f3b1f0c 100644 --- a/openpype/hosts/aftereffects/api/extension/js/main.js +++ b/openpype/hosts/aftereffects/api/extension/js/main.js @@ -237,7 +237,7 @@ function main(websocket_url){ RPC.addRoute('AfterEffects.get_render_info', function (data) { log.warn('Server called client route "get_render_info":', data); - return runEvalScript("getRenderInfo()") + return runEvalScript("getRenderInfo(" + data.comp_id +")") .then(function(result){ log.warn("get_render_info: " + result); return result; @@ -289,7 +289,7 @@ function main(websocket_url){ RPC.addRoute('AfterEffects.render', function (data) { log.warn('Server called client route "render":', data); var escapedPath = EscapeStringForJSX(data.folder_url); - return runEvalScript("render('" + escapedPath +"')") + return runEvalScript("render('" + escapedPath +"', " + data.comp_id + ")") .then(function(result){ log.warn("render: " + result); return result; diff --git a/openpype/hosts/aftereffects/api/extension/jsx/hostscript.jsx b/openpype/hosts/aftereffects/api/extension/jsx/hostscript.jsx index 91df433908..c72b423af8 100644 --- a/openpype/hosts/aftereffects/api/extension/jsx/hostscript.jsx +++ b/openpype/hosts/aftereffects/api/extension/jsx/hostscript.jsx @@ -395,25 +395,52 @@ function saveAs(path){ app.project.save(fp = new File(path)); } -function getRenderInfo(){ +function getRenderInfo(comp_id){ /*** Get info from render queue. Currently pulls only file name to parse extension and if it is sequence in Python **/ - try{ - var render_item = app.project.renderQueue.item(1); - if (render_item.status == RQItemStatus.DONE){ - render_item.duplicate(); // create new, cannot change status if DONE - render_item.remove(); // remove existing to limit duplications - render_item = app.project.renderQueue.item(1); - } + var item = app.project.itemByID(comp_id); + if (!item){ + return _prepareError("Composition with '" + comp_id + "' wasn't found! Recreate publishable instance(s)") + } - render_item.render = true; // always set render queue to render - var item = render_item.outputModule(1); + var comp_name = item.name; + try{ + var comp_id_count = 0; + for (i = 1; i <= app.project.renderQueue.numItems; ++i){ + var render_item = app.project.renderQueue.item(i); + if (render_item.comp.id != comp_id){ + continue; + } + comp_id_count += 1; + + if (render_item.status == RQItemStatus.DONE){ + var new_item = render_item.duplicate(); // create new, cannot change status if DONE + render_item.remove(); // remove existing to limit duplications + render_item = new_item; + } + + render_item.render = true; // always set render queue to render + var item = render_item.outputModule(1); + } } catch (error) { return _prepareError("There is no render queue, create one"); } + + if (comp_id_count > 1){ + return _prepareError("There cannot be more items in Render Queue for '" + comp_name + "'!") + } + + if (comp_id_count == 0){ + return _prepareError("There is no item in Render Queue for '" + comp_name + "'! Add composition to Render Queue.") + } + + if (render_item.numOutputModules !=1){ + return _prepareError("There must be just 1 Output Module in Render Queue for '" + comp_name + "'! Keep only correct one.") + } + var file_url = item.file.toString(); return JSON.stringify({ @@ -689,30 +716,42 @@ function isFileSequence (item){ return false; } -function render(target_folder){ +function render(target_folder, comp_id){ var out_dir = new Folder(target_folder); var out_dir = out_dir.fsName; for (i = 1; i <= app.project.renderQueue.numItems; ++i){ var render_item = app.project.renderQueue.item(i); - var om1 = app.project.renderQueue.item(i).outputModule(1); - var file_name = File.decode( om1.file.name ).replace('℗', ''); // Name contains special character, space? + var composition = render_item.comp; + if (composition.id == comp_id){ + if (render_item.status == RQItemStatus.DONE){ + var new_item = render_item.duplicate(); + render_item.remove(); + render_item = new_item; + } + + render_item.render = true; + + var om1 = app.project.renderQueue.item(i).outputModule(1); + var file_name = File.decode( om1.file.name ).replace('℗', ''); // Name contains special character, space? + + var omItem1_settable_str = app.project.renderQueue.item(i).outputModule(1).getSettings( GetSettingsFormat.STRING_SETTABLE ); + + var targetFolder = new Folder(target_folder); + if (!targetFolder.exists) { + targetFolder.create(); + } + + om1.file = new File(targetFolder.fsName + '/' + file_name); + }else{ + if (render_item.status != RQItemStatus.DONE){ + render_item.render = false; + } + } - var omItem1_settable_str = app.project.renderQueue.item(i).outputModule(1).getSettings( GetSettingsFormat.STRING_SETTABLE ); - - if (render_item.status == RQItemStatus.DONE){ - render_item.duplicate(); - render_item.remove(); - continue; - } - - var targetFolder = new Folder(target_folder); - if (!targetFolder.exists) { - targetFolder.create(); - } - - om1.file = new File(targetFolder.fsName + '/' + file_name); } + app.beginSuppressDialogs(); app.project.renderQueue.render(); + app.endSuppressDialogs(false); } function close(){ @@ -730,3 +769,6 @@ function _prepareSingleValue(value){ function _prepareError(error_msg){ return JSON.stringify({"error": error_msg}) } + +// render("c:/projects/test", 2); +getRenderInfo(1); \ No newline at end of file diff --git a/openpype/hosts/aftereffects/api/ws_stub.py b/openpype/hosts/aftereffects/api/ws_stub.py index 8719a8f46e..32125a7d99 100644 --- a/openpype/hosts/aftereffects/api/ws_stub.py +++ b/openpype/hosts/aftereffects/api/ws_stub.py @@ -418,14 +418,15 @@ class AfterEffectsServerStub(): return self._handle_return(res) - def get_render_info(self): + def get_render_info(self, comp_id): """ Get render queue info for render purposes Returns: (AEItem): with 'file_name' field """ res = self.websocketserver.call(self.client.call - ('AfterEffects.get_render_info')) + ('AfterEffects.get_render_info', + comp_id=comp_id)) records = self._to_records(self._handle_return(res)) if records: @@ -522,7 +523,7 @@ class AfterEffectsServerStub(): if records: return records.pop() - def render(self, folder_url): + def render(self, folder_url, comp_id): """ Render all renderqueueitem to 'folder_url' Args: @@ -531,7 +532,8 @@ class AfterEffectsServerStub(): """ res = self.websocketserver.call(self.client.call ('AfterEffects.render', - folder_url=folder_url)) + folder_url=folder_url, + comp_id=comp_id)) return self._handle_return(res) def get_extension_version(self): diff --git a/openpype/hosts/aftereffects/plugins/publish/collect_render.py b/openpype/hosts/aftereffects/plugins/publish/collect_render.py index d444ead6dc..2b37c1f101 100644 --- a/openpype/hosts/aftereffects/plugins/publish/collect_render.py +++ b/openpype/hosts/aftereffects/plugins/publish/collect_render.py @@ -64,14 +64,13 @@ class CollectAERender(publish.AbstractCollectRender): if family not in ["render", "renderLocal"]: # legacy continue - item_id = inst.data["members"][0] + comp_id = int(inst.data["members"][0]) - work_area_info = CollectAERender.get_stub().get_work_area( - int(item_id)) + work_area_info = CollectAERender.get_stub().get_work_area(comp_id) if not work_area_info: self.log.warning("Orphaned instance, deleting metadata") - inst_id = inst.get("instance_id") or item_id + inst_id = inst.get("instance_id") or str(comp_id) CollectAERender.get_stub().remove_instance(inst_id) continue @@ -84,7 +83,7 @@ class CollectAERender(publish.AbstractCollectRender): task_name = inst.data.get("task") # legacy - render_q = CollectAERender.get_stub().get_render_info() + render_q = CollectAERender.get_stub().get_render_info(comp_id) if not render_q: raise ValueError("No file extension set in Render Queue") @@ -118,13 +117,13 @@ class CollectAERender(publish.AbstractCollectRender): file_name=render_q.file_name ) - comp = compositions_by_id.get(int(item_id)) + comp = compositions_by_id.get(comp_id) if not comp: raise ValueError("There is no composition for item {}". - format(item_id)) + format(comp_id)) instance.outputDir = self._get_output_dir(instance) instance.comp_name = comp.name - instance.comp_id = item_id + instance.comp_id = comp_id is_local = "renderLocal" in inst.data["family"] # legacy if inst.data.get("creator_attributes"): diff --git a/openpype/hosts/aftereffects/plugins/publish/extract_local_render.py b/openpype/hosts/aftereffects/plugins/publish/extract_local_render.py index dc65cee61d..f2ae91c341 100644 --- a/openpype/hosts/aftereffects/plugins/publish/extract_local_render.py +++ b/openpype/hosts/aftereffects/plugins/publish/extract_local_render.py @@ -24,22 +24,28 @@ class ExtractLocalRender(publish.Extractor): self.log.info("staging_dir::{}".format(staging_dir)) # pull file name from Render Queue Output module - render_q = stub.get_render_info() - stub.render(staging_dir) + comp_id = instance.data['comp_id'] + render_q = stub.get_render_info(comp_id) # re queue render item if not render_q: raise ValueError("No file extension set in Render Queue") + + stub.render(staging_dir, comp_id) + _, ext = os.path.splitext(os.path.basename(render_q.file_name)) ext = ext[1:] first_file_path = None files = [] - self.log.info("files::{}".format(os.listdir(staging_dir))) for file_name in os.listdir(staging_dir): files.append(file_name) if first_file_path is None: first_file_path = os.path.join(staging_dir, file_name) + self.log.debug("files::{}".format(os.listdir(staging_dir))) + if not files: + raise ValueError("Nothing rendered!") + resulting_files = files if len(files) == 1: resulting_files = files[0] From d0f8233b8bedf5a3742d35a090f58ec99c2825a2 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 10 Nov 2022 17:40:33 +0100 Subject: [PATCH 091/243] OP-4361 - updated documentation --- website/docs/artist_hosts_aftereffects.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/website/docs/artist_hosts_aftereffects.md b/website/docs/artist_hosts_aftereffects.md index d235cf44f0..ede2f7e86e 100644 --- a/website/docs/artist_hosts_aftereffects.md +++ b/website/docs/artist_hosts_aftereffects.md @@ -38,8 +38,6 @@ In AfterEffects you'll find the tools in the `OpenPype` extension: You can show the extension panel by going to `Window` > `Extensions` > `OpenPype`. -Because of current rendering limitations, it is expected that only single composition will be marked for publishing! - ### Publish When you are ready to share some work, you will need to publish it. This is done by opening the `Publisher` through the `Publish...` button. @@ -69,7 +67,8 @@ Publisher allows publishing into different context, just click on any instance, #### RenderQueue -AE's Render Queue is required for publishing locally or on a farm. Artist needs to configure expected result format (extension, resolution) in the Render Queue in an Output module. Currently its expected to have only single render item and single output module in the Render Queue. +AE's Render Queue is required for publishing locally or on a farm. Artist needs to configure expected result format (extension, resolution) in the Render Queue in an Output module. +Currently its expected to have only single render item and single output module per composition in the Render Queue. AE might throw some warning windows during publishing locally, so please pay attention to them in a case publishing seems to be stuck in a `Extract Local Render`. From 104fc1fd83af7b5993db2ef59f2eac63a3726e07 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 10 Nov 2022 17:49:35 +0100 Subject: [PATCH 092/243] OP-4361 - removed forgotten debug method call --- openpype/hosts/aftereffects/api/extension.zxp | Bin 101272 -> 101245 bytes .../api/extension/jsx/hostscript.jsx | 3 --- 2 files changed, 3 deletions(-) diff --git a/openpype/hosts/aftereffects/api/extension.zxp b/openpype/hosts/aftereffects/api/extension.zxp index e7b3bcac8ae26be2d55a122a77701e43e265fa06..339560fa387be6b792d2da0da71c879111b40b40 100644 GIT binary patch delta 7977 zcmZvBWl$VpwCpbK?(PsYxVyUq_u#g;Ew)%7Sa5d<=(I$Mx?f!0XN`Tye($#ntD%2K;_5cnf7r4>0Sv->Cwja<2iSKqECdurc_(o4 zz-**<^8FJq6!D!f837kypz*%hrNWp2J!u^N$^563nj$m|4gdf^0Q@`F+QK=^X}|>l zmi1F}%z$2S|F<)h*c`|PQ3$1>!x^EKTg~{nc}{x%axP!p0gMA6ytrhIq>^g!W6I}E zA3D0fP1zh@T>8e-=?zsEYLL{GS3bny+Ok}znhZZ@lsr<6r3%gB)8<~UN*MY7Wf(Eb z(ULti33nh|zp589d%H~%7I{9N-rfOcs1_u>eEw;?Z~IHJ^J!8Z!lQhW)?iFP){P%- z%Yk_T$@%g}`p{4uY(Fdku`3$Aykv;y*`Q=81tLO-6;}Z5-wRy`QuLAAf{$i5< zv=}3686Kijg)&_+BM&o7`u|4Ui%ibu-9=h=!WheBG5igy zDCcj_QYwOZYVa<)^l7M)bcR7Edd5)6=JPwM@LSSui5>2B-5MPBefyhwi)oyLk3BZt9m3K58M8o;*Z5@J`k!vjQ945|dEGHR4+h zIolqMJdhhA=b2(5L`I;nIz6tf^TR0%O`LpTI)1%ERP9l{Jn^kydpoIjVK*axnY0Dn zCFKHR7@)z>(`!Sp!5TGY^BAN9T^O6jde7k_92`)N&UoO_xLfeG__>7lKpb7*_Lsa=v!twN__+ zN@y%=J8saXhsO&C|4E9LoOW4gPt<&D>#PIl0wOl9XI8kV4XS6#J&P&t+(mB|au#)S znIq!z;~Emv>>coP9-Qi^x6SFZD|4USWVyZ8XI{=+a&VHY!dF&%kRs1r@q>jQPND0$ z>aB&a4~$yLkFp`<1f^>s=f-SH_Cay?1`2#UTpC96AI7ozlCZ64Ln}}AhYPYRDvM_| zWV+?jLw|9W(etx}n*sr=W)qV)Cr$TP9w}?!SDyx552pNso(d}KhO}; zYpQ^MxD$VfE~d19?1@t@^j>KS2b&zvT#rN>&AqG3S@kQZ?~|t`$F+B1r4Kbgc7uJ|&$l zaESBEr!5OBw;bJ}n|3!}o{!}ZyQ?0r(kK_W&Y#y%=NIN{5Vu+{Am}v8Am>A*&h7bu z=X~kv-Farb(~+d4L_av84}nBp6bTDE1YVC6hXHS`sHH_)G}u~6%KT|*O5iL(pT_nv z#5<#PCjQn@HHmZo&J&PkXl}d}*kL<##ap+I`%=wVMSRwPQ2f)Ov-s`h2j;C|ZPMt= zy72pIFR8`QzhOL<&o>FWEpiaEO*aeOCW`$(4z7R^-r?k-3U*w^)T7q@B} z)ZgYBT#VyTpS1h=o7Cd?K&j+|Ci8CrcJ+eRzV)kqP``_jlN!*gat!({phnCSd+EJQ6` zUdw--P#wyD$ChxqEzb0Egt>`|Gv6Q77o&i`jbFCPW{$^}R`}*d$v?2ZUbjk+oBNfi z&G9U~Q_Ep=xXWunTkIM#&%1gvP2NE_iH0uTID_k@L(zsG5<1Zd_c9wr&h`X-OX_?4 zh~itGe~yOzF�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 BB$faG delta 8041 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 diff --git a/openpype/hosts/aftereffects/api/extension/jsx/hostscript.jsx b/openpype/hosts/aftereffects/api/extension/jsx/hostscript.jsx index c72b423af8..cfb72bacf5 100644 --- a/openpype/hosts/aftereffects/api/extension/jsx/hostscript.jsx +++ b/openpype/hosts/aftereffects/api/extension/jsx/hostscript.jsx @@ -769,6 +769,3 @@ function _prepareSingleValue(value){ function _prepareError(error_msg){ return JSON.stringify({"error": error_msg}) } - -// render("c:/projects/test", 2); -getRenderInfo(1); \ No newline at end of file From 81451300611b4eb7aab753ad1267848ec1965e72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Fri, 11 Nov 2022 10:00:16 +0100 Subject: [PATCH 093/243] :label: fix type hint --- openpype/pipeline/create/creator_plugins.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index 782534d589..bb5ce00452 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -393,8 +393,9 @@ class BaseCreator: asset_doc(dict): Asset document for which subset is created. project_name(str): Project name. host_name(str): Which host creates subset. - instance(str|None): Object of 'CreatedInstance' for which is - subset name updated. Passed only on subset name update. + instance(CreatedInstance|None): Object of 'CreatedInstance' for + which is subset name updated. Passed only on subset name + update. """ dynamic_data = self.get_dynamic_data( From 2edcb15fbb1640dd57286d83828d9bb05e908c42 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 11 Nov 2022 17:55:26 +0800 Subject: [PATCH 094/243] fixing te multipart boolean option --- openpype/hosts/maya/api/lib_renderproducts.py | 20 ++++++++++++------- .../plugins/publish/submit_publish_job.py | 2 +- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/maya/api/lib_renderproducts.py b/openpype/hosts/maya/api/lib_renderproducts.py index a95c1c4932..78a0a89472 100644 --- a/openpype/hosts/maya/api/lib_renderproducts.py +++ b/openpype/hosts/maya/api/lib_renderproducts.py @@ -536,7 +536,11 @@ class RenderProductsArnold(ARenderProducts): products = [] aov_name = self._get_attr(aov, "name") - multipart = bool(self._get_attr("defaultArnoldDriver.multipart")) + multipart = False + multilayer = bool(self._get_attr("defaultArnoldDriver.multipart")) + merge_AOVs = bool(self._get_attr("defaultArnoldDriver.mergeAOVs")) + if multilayer or merge_AOVs: + multipart = True ai_drivers = cmds.listConnections("{}.outputs".format(aov), source=True, destination=False, @@ -1018,9 +1022,11 @@ class RenderProductsRedshift(ARenderProducts): # due to some AOVs still being written into separate files, # like Cryptomatte. # AOVs are merged in multi-channel file - - multipart = bool(self._get_attr("redshiftOptions.exrForceMultilayer")) \ - or bool(self._get_attr("redshiftOptions.exrMultipart")) + multipart = False + force_layer = bool(self._get_attr("redshiftOptions.exrForceMultilayer")) + exMultipart = bool(self._get_attr("redshiftOptions.exrMultipart")) + if exMultipart or force_layer: + multipart = True # Get Redshift Extension from image format image_format = self._get_attr("redshiftOptions.imageFormat") # integer @@ -1048,7 +1054,7 @@ class RenderProductsRedshift(ARenderProducts): # Any AOVs that still get processed, like Cryptomatte # by themselves are not multipart files. - aov_multipart = not multipart + # aov_multipart = not multipart # Redshift skips rendering of masterlayer without AOV suffix # when a Beauty AOV is rendered. It overrides the main layer. @@ -1079,7 +1085,7 @@ class RenderProductsRedshift(ARenderProducts): productName=aov_light_group_name, aov=aov_name, ext=ext, - multipart=aov_multipart, + multipart=multipart, camera=camera) products.append(product) @@ -1093,7 +1099,7 @@ class RenderProductsRedshift(ARenderProducts): product = RenderProduct(productName=aov_name, aov=aov_name, ext=ext, - multipart=aov_multipart, + multipart=multipart, camera=camera) products.append(product) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 27400bb269..e87cc6beeb 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -500,7 +500,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): if instance_data.get("multipartExr"): preview = True - + self.log.info("preview:{}".format(preview)) new_instance = deepcopy(instance_data) new_instance["subset"] = subset_name new_instance["subsetGroup"] = group_name From 81d09b98ffa87983d08ee8fb6e5ef83f23f231d2 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 11 Nov 2022 17:58:26 +0800 Subject: [PATCH 095/243] fixing te multipart boolean option --- openpype/hosts/maya/api/lib_renderproducts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/api/lib_renderproducts.py b/openpype/hosts/maya/api/lib_renderproducts.py index 78a0a89472..58fcd2d281 100644 --- a/openpype/hosts/maya/api/lib_renderproducts.py +++ b/openpype/hosts/maya/api/lib_renderproducts.py @@ -1023,7 +1023,7 @@ class RenderProductsRedshift(ARenderProducts): # like Cryptomatte. # AOVs are merged in multi-channel file multipart = False - force_layer = bool(self._get_attr("redshiftOptions.exrForceMultilayer")) + force_layer = bool(self._get_attr("redshiftOptions.exrForceMultilayer")) # noqa exMultipart = bool(self._get_attr("redshiftOptions.exrMultipart")) if exMultipart or force_layer: multipart = True From 9324bf25383a773d8789a7d6debeea200b179b6f Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 11 Nov 2022 18:05:42 +0800 Subject: [PATCH 096/243] fixing te multipart boolean option --- openpype/modules/deadline/plugins/publish/submit_publish_job.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index e87cc6beeb..c1e9dd4015 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -495,8 +495,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): render_file_name = os.path.basename(col) aov_patterns = self.aov_filter - # toggle preview on if multipart is on preview = match_aov_pattern(app, aov_patterns, render_file_name) + # toggle preview on if multipart is on if instance_data.get("multipartExr"): preview = True From 5f302eb95c970618497ed85b9e1ebbf713423963 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 11 Nov 2022 12:20:19 +0100 Subject: [PATCH 097/243] OP-4361 - streamline local render Do not do get render queue again, should be already collected. --- .../plugins/publish/extract_local_render.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/aftereffects/plugins/publish/extract_local_render.py b/openpype/hosts/aftereffects/plugins/publish/extract_local_render.py index f2ae91c341..309855f1c7 100644 --- a/openpype/hosts/aftereffects/plugins/publish/extract_local_render.py +++ b/openpype/hosts/aftereffects/plugins/publish/extract_local_render.py @@ -21,30 +21,32 @@ class ExtractLocalRender(publish.Extractor): def process(self, instance): stub = get_stub() staging_dir = instance.data["stagingDir"] - self.log.info("staging_dir::{}".format(staging_dir)) + self.log.debug("staging_dir::{}".format(staging_dir)) - # pull file name from Render Queue Output module - comp_id = instance.data['comp_id'] - render_q = stub.get_render_info(comp_id) # re queue render item - if not render_q: + # pull file name collected value from Render Queue Output module + if not instance.data["file_name"]: raise ValueError("No file extension set in Render Queue") + comp_id = instance.data['comp_id'] stub.render(staging_dir, comp_id) - _, ext = os.path.splitext(os.path.basename(render_q.file_name)) + _, ext = os.path.splitext(os.path.basename(instance.data["file_name"])) ext = ext[1:] first_file_path = None files = [] for file_name in os.listdir(staging_dir): + if not file_name.endswith(ext): + continue + files.append(file_name) if first_file_path is None: first_file_path = os.path.join(staging_dir, file_name) - self.log.debug("files::{}".format(os.listdir(staging_dir))) if not files: - raise ValueError("Nothing rendered!") + self.log.info("no files") + return resulting_files = files if len(files) == 1: From 9ba1028e615b4d39388dd2f39f4ed9cf473d98d6 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 11 Nov 2022 12:53:22 +0100 Subject: [PATCH 098/243] OP-4361 - fixed issue in render info Weird synchronization issue was happening in getRenderInfo when re-publishing published workfile. This implementation is ugly, but seems to work. --- openpype/hosts/aftereffects/api/extension.zxp | Bin 101245 -> 101376 bytes .../api/extension/jsx/hostscript.jsx | 120 ++++++++++-------- 2 files changed, 65 insertions(+), 55 deletions(-) diff --git a/openpype/hosts/aftereffects/api/extension.zxp b/openpype/hosts/aftereffects/api/extension.zxp index 339560fa387be6b792d2da0da71c879111b40b40..f80787fef8a989c46d4fda489a39b7792ff9500b 100644 GIT binary patch delta 8538 zcmZvBWl&sQmvuMpuA#Bu?levU!GpWI1b2tVf=kfG3GPmC4ek)!-2y=Z!DW&>^S#e} zQ&abzx@(`kYVA|!$E{U+Kcyf9q#}F@gB1tc*G5hPVc&u66ZK%y{>1gSFlrcofg&~z z%wJ%JD~=9(V{VgJM&Uth!fS8(n{FzF4sW6)2MdP>0058xf3gCUfrx*ye?-1g&cpsy z&7jHtt06?Iit`to%YMU7G?R_^??C&X-S_&8{{SzCN$kJ1z3 zzX(wVNP_aGD3nGO_@AmxMXJDH~7KT2#!Ijyw9krOP^N zvJw))g;ImCx=r55M3+JyFmC*Bs2A3_V8_r9@a*{SoJcf=X#>-;vrn_L9a>5Fe6JMD zJ{Z&=m*7ZMRL%R2jo7lBE50b)@=RXEwQE5x^j=DCgEmsWFC63)dVRgT4}Tg>t<2Lk zP?tI}%&CzI%YnGxTE*kn)0=jY)E`KsoTF4fyQB{;32fP7g@&E=8K+FZJsR$A33Bi~ zKXRQ6D%3|OA8V%!oLv2+$*8_;R*1@sFt44!G0HX2u&pEf=ePFi;9PHp=M)P)P^P^N$EG_H z;^V6Ok@R5|y-Ad#b_3SKx zo_81&y-wcPGrdEl8RDUC8Ij~+Z_(y8jTW(8-D_Eh(Kp*$ zJMNvOPvfc>DH>zMCD)`x<4QHjd2?3_hEs4~+MN{C$;=74PVlp7`=ehLJfO}A1pz$L z?%Df7#Kf{~#Tk`VnHzkhL6jBwRBa261y}iMbT1OUy$pes!#v{kg7yyE%v-dvzyc-z zz-jh&{bZ=}BW|^nBsn(Q9iumdC(yqPlkq^kVLLC{7x-oKnx$Fb6wN3ib15+Z=}zg%XrkI)=52RX*Vr_MHP;kb$(WVsC%5(5^e2F9m+)BKT15ty z1cvn=hUa>iJH_`}m%7btFkjE;FfU~sHZ|4GN70t}=EW)6bp{2Sj(*GWGFnVEHoxTF zA*7k?8T(#1IdEQN6!^;M9VwA3$RsS+6y;K7_?*8XfjaJt2eRAshG2isYS*c&`20t@ zDAP6%1rkp61~xc5Rcj2AOUfD$?p^=R%^}?;MVVo?r!E?cO}h$?Ew!l4RBVyE??S50 zuiHxI2PZ5H_6$t~+{2^4>KeH7mK7{Vn$y zY`eWXG;jQv+c0C_+Y(bL{w9kN7Uk1W>MxymE?QT2sU$AO1rr&MHrNeq1W{iuvpDr) z4~cX{s#|_v!K*iDMoxLr+dFoozrw9;$K=YQy6+0{*li^sclY2nyOV3=rAqT4=LGh8 zOzyR!4bgzR<$*S~FtQn8{f#2+SLzwpzNE+zf)XEc!26IodeCzWdC)QvIzY`>3&JRZ3LuSr$kjBu@gcENz&WnPkHXV)0|w4P9? z4I?|7Ut1ShxF#bqn#~UePJT?>@UNy$vSP_N#$_P3fqF8)SddKR|}poMhKX!vvO5z2zaA2}Yk%y&s&SC%TCwHY~Wi@lt6oiF6}I zEBJ;C$pu@^JSUO>pE!4k_-hHUh5|b*8?}p>cHhM(?C42PoV$9Q@D4l_Z_8Hc`p`Py zW&FI}taQVh@I*=`%%3ilh&ZWigywk~Pw zg?n!yM(w!z!|R6fy--(kvw)w|Gld9VOjunIWF{@UqqDhP2*UexebqH-s7S_47Pc;~ z-R|pmad25*j#yi0b*p7OrdWHo*mNc|&v?5rpQ>}NQ)XF~pt*)h^^p8=RcNQ#%0a!= zGRwW|)0NbE@)y*Mx``sT;sRJg&YjmOD5_7gq_&ULmb#Lvff~?(8 z$ikY+Obsh-Lkrf=_wwWfBc7p-`NSSIs(Z>QcB{(W4vq0>VZ(yi$QVCfN}*Xm>xfJt zyQsJ=8-v-Jh00565TBER+N+TXJjUUK>(NB_Ga>iW$;^11zH8veahajoF;1^6Ra(Fak}q@oEH6fX?jwrCk5P|cvt~%|_#yI^# zBziJvC=22RtRRMxdO9uk`VG?6D3IWRE8*%mlu1%gcE`CAotQ4~vC;C6IN96;*4A!7 zQ8&szjV7{I%w1BCFQ7SAiX3wg#$geoheiHdsS3}>$>VN)&49WO+ibhY-S!^MfOe{a zwCO|fIR|MGof|t&SwVu%;{x0Q3Qx5_YZb15L9Jfg%H{ZazO#BCo~aH4?X+mJW6fW~ z$eX##Dj{IvtLXH=jJ1ZKv&i%g7;r9+vVG>`WRiYA%yMEwI%)nm7cSnUr8^{xpN5TWcPjZv}>!D(612OUtiNmU>=*p zEqr4(t=9;x$p#$%q%LSJIkVHX+9dIiic4^5Uz}bJI5PG zLqzc2M2bSUj|Lr^!3Etys+RGO3C56d*aI>3*?nyB{NWDyI(uSpZVO_|bP%T#)B^Qtp)SihlfGRGSuv2GwJJV1<+Y;@ zL^gkVT9`9({W00AxEXpV*Fr*H%fE?}Yf9nl>2`4&b`T zQ3x8FL`CrYVIAw=DRet)SX`sF8O+tyD26NV0fx4Oo zmkg+xA*cTkE4JMPq1i*xZGP!cstShsq~Gln9>HKB-lsxNJr99FmSw zt&U0jcbUWL(%8@Qy;|1kzoA9-E-S@Rwbhibo zp0kn?j%P&Ba{c_KkO40M%cqg{2?_VE+cl*MkpFcUTOQta3Wl4Ls7yk9w;T57af4Qr z?T&UFqo*|*qoG`f2vj>@RglLn@Kej6l|G=n~8M#(L_27)p70v6zIAH;gWU8VtHO*a=~#HFcVxF4MHhe+*^0=n{WQIZj_ZS=#gKaPpK~I zGMj|1-26)FYh|9-Z6ck|o@YE4TWcoc)o>AXD@&YDO^AmFAt#xQJjxbsG5bS6= z%1+Qc1UX!D{*Di$`;CzI#c4~=j%C#hc~+7%W=#j!;gZK^Srur^wsp$O-^{q_S~_`` zS33Nv+<{SL%IOj@_@OSyf7s!)rvQYW+T zVm9!?&oZ$ln!)_?58u1P>+q7TWopTb!4%C3kj9Q`YBW)wbfAAtND5ImGWP>KYaKWh z*L#%g<_a~LiC2gtL#i8$P~h`%rRMasq!J{oEv7AsKqmf5e6y9E9FJSGzCtsxK-@?B zyW*sEkuu4l#hC_NZSukmYJ|=Z`dc2h8QttG6ruSmH*Wz8!zao|u$P6|Xo>7L%)|{c zBuDDFg5Y6HZSUfK|A^L%MRBpXVS=Iz$9k4$-{9VT#vlY_{rgQd8eG^L)G6vTsp9Q8 zx(G-*vt|CeAohcrNRD&=nL|P!fAIYhUGSUgh|KfTdyyp7a-?dZMAgk^*j>+9=<7gB zM;Zd(Dpj#;cOP0fs_n?1v_xjFTF>nvi+IM8+By#Y;@ml)X-_#N!6|sZLB&kydjvS+ z*O9T{!waC+z5z&_#N2X*m-GFPvSX_W0q(W4ZVx;;q8%RAy%T@8Z^oG4U=CkXp7RA&;s3w5?8-O|T5n+H4tp)|P43CeM*DnWzE~u2b8D z2cpADiFwqnJ>Q$q&D*2yb5;^R>CDzJ6nxL=+owR4E-Rj|m*Wt^F&7dD^_0tPyUYBQ zT3-5xKoV|$N=!nla}$;v_m0Q1Hy6FRd4qCF6+Tj-t$rdJbdedmHGMYX5P9<)YqT|u zU|iqT`u;bLrij_b>vd>?!?R=3y~w>16NLm~wb?TK@QMTD{meMO<9KRV1 z={gEIGLwBe?e%3JccW^nO*&F-_|nvW@!qc2A{HsmdBkfI`MvR5Qpl5awoz?+N}$3( zRvzI4HZ(ubTf@|G;g&J6_0A~1)3$LX&A@ucCCkJXf7LaCU1P5(E1FY&%XWCHMw&*R z6C;YaKXS1DwIXBy{GI*jHYh*KC!5pE(3kS&PCaQKS9VK>C7$04{}maW!VEipqQH2a zvbY6t1o^iR-49k?`;fAjAjQOxG=Bo~l=&WZQ0&(_`lmA*rKEs&q-@g+0X?h}4x>g` z?~dFX8Jp@e>@xRPn(tAb?HgtE>=J!z<-aZQGB`A~ z9=U(+QC~^G^|-Ub7`2J{%yyt#E8PTxG%wRyNjTkzpkNJp7KmtE$Xj23_V4UidmNLx zeJ;mqRCjlTS>ZXkKP8rRXwd3BX2S83=W$@W^x7)L^qNJvh1d86-jl0S!KiOPrhS6X z;d)|4)PP|UqlHlG6u`=9iijmq7R=8!c;%bTRc#lo zk$)6c-_xo=F(G0lI)avzZcQ)BP}i{&c|XE`F;%|be}FJ0u@g8m?1h{3ALCt*ade*K zZL5JI2$#?m%R?gM$zVgpMDz3#9Idu(hR=Dxqpv@VQ18$ObD>4mWmr*d!q{f$aqu8~ zU#@0)6TG8C?F3aP2$kqBWM142pbuf^=$r6RBI4|Z0bN->{Q`%EYm25Oq4@e8II2o~ z4~Jt);D<1cJbF^ZnA7;p_EdfU(p}tT@B)9pox8DBAEkNloSCrTZkCew1;jTX!5=j2 zNxX+z1K>wNiw*HewEG??U$XozC?gU}xbo0ND(e$^!*k9f z&nWZ?{jx;Y1tIz=o)0YOyD2l$Ay*&K6Zf&|}MnCV|3U+VQbT2Nh>c@bQHq z(G`+P-SC3Cjb8byj=+{*+g8x-Hqz>zLA53qCnwOSCB4^nzrJuS009bK}1G z`YSqq^&1Dj91X9rM2mzgdFK*ir!w;dxws&az+(f$o9=Pt141E@iB+bX)n|8JRYlr6 z8?6&m-U~g+4|C!N`Y(DF+3VHOq^!C#9GKrdYfy1x;n9E?SiEdAruqA|om(%@PmoYk zUQjGOICNg}0RyxS=!Oa@od_BLA)zb)kv%4zahXK^RxS;oSg8N zu*-)pi5dEXNdg3U>L!v_EMm?VJAxS)VT?@E!{fK#yS?XUdZYrz2b_Nm&!oj4l;eM} z$zIB;kQ=LwvNTeJFHxP$oup0*+*wMCFJj_*`7`e-P6cb;KTo@H)z-AlktvKCdd zRuv`AkWUm_LInJNZTp(U-Z#1F_>e94A##oUL^RmCYa;%ht?6Pgx}QB3TnwAsC=ep3 z%!V}}(2E|eQyx<$KJV%4-ij0T5UwKrdC67D^#$`!KOW_GKOO*Z{N0QO0HC{`Ku_{N z4d_1|`#;}*JNADVA^AC-olqYyU?AiV;s255Qng(&-V=nL>F>RKa%NTMEpII4Q@0hP z0K!lrH`{z(0>7pD?Af83_NpeABre8K)-qJ8@%LaI$G3G0ZHZO^6~dO=AtlMaHmd-=hIY&+ZTn1#o-sQ z*T6fE`3K@o`)*TQ;P#3N0-6yzDyBc@B>rR89SZ$6fcJ1~X5v#exyXwtLKQO>v(lwC zd_95$Zj#%&uhkP|@@s7z>ZKE3z+(|M5?Zn0J2Vqf%=_Vs-IeqWf~DY!kA}KRg~+(} zkLD`pU-BypId%EF1tT>+RA0ug$-cF4kZd*(f%L3T`UXDOk1othDc<^)X_p-^SMVndVdb@Pk}XkSBur%xltb>=Z;Hs`uhOpWUb0f)2n(^F%q9 zfWRgnjCpzpMrg^K^YYg+Cx>61#@-(AWg#Z|AqrLLXAnx7_g)bd*7BV>rc7tR_tt0T zfk}&39t{>Gaue?!+QeU1;M3w0o6tFqoqwYDp)_{oRl8wUWz6i46?R%!Hd9=Ts8QG2 zcVnJ$t7{r-eGs%1R9M{;(a>7nmM>MkJ+DX|(`qSzgtW4Si4(Of$9Gw5a?B?fAN0)ZPfUD%knMa5ovzj>V{vz?&7xS6)I}z^x`|q z)+vRSDT36*X&d3A$r!sp1bpsfSJU2w0U_trmJMXK>Wz8q-(L48jH{2TsTVj9zfL#;p zZhl=ovkJ_w^~~dqH78jV>FBkV^hM8V+lZ5Etn`-jaT`1t!HU*-7SSDuX@)Oyr@ zhO{7GO^vSUzVgdK5!sz0PrKup+;_s5{**`F`TVirhVrQ8R*h-7#nNIsl25G|i=}~C zTFN4njig$gh8vp{E+dBLf#Ik_(M$AFtSVIlB5yz* zV?XNxy=B1}>Q-}G(7es7F_s;)d3D~*Ey&E&^TT2NrB+`|JZoD(Hi@2ij;wXMqHA_; zU$F4L7{y4r9*mS~3r@$|12o>ITrzELQM6S3Mo#F@%t^v*pGESgnX5NbX38%SGL zhdp>UHP&nkg>-d(+2l^N_K_R#=;!E-B-%_pBy!O&uEJ(X`8Hg}sb<%%)wc5T7K_n2 zTR-|b9X7n9&Z-8=B$}#EDpLC9S`8l@zW7bw-MLcU&32So-z5kkACY;Fl+HFX*MbKB z(#%DBfz~T#OY|p+c}g;!)`n!@D8$y~4FZaGvmixIIJUTaOdokalu_5g+oK+Sc@t>R z*-Y!W!4OWeX)*QkQqWa+Rw1d%(x|BH7=G*B)+kmlt;HQ9o}Kent^_{`F@85|N8pL- zdgXpRlkrzaZ&7R_21P1?D zDy|zMr;Q$`#%S=8lfGc0m+Jmld@ZNpXhj1l`S8aJm%D4htKr&@zwY9Fzvc`rEX=Y{ zr%t?OhH4O!Eur@?A6Cw=(LmhYmhPmnRfV=)i3SFI$?|PL&ut+^?dZHe4%%ww7=|}+ zznhy0l>Vr#_s3NloSF&99}nU_>?<96L3Y;&B^K&AvCvqk@l;k~PDb%L;aj)84ohpP zjxG`MFyc{H_lDlWmydfLD}st`zK^saH=#!@W++kXUraRPTt{2mYCw2`zt8j=kEELS z!WW*Z%G+0YeevJzOdg-f%yse#o1tJ#=Hb_8iPp#YTJJxhaUzC7r~4`kPn2cJW+XOH z7!mwhgl>x=y!|mD%~v>&$VLF_3P=0H+!%4FLGdz@y(Fe{p~}@6uLpzyzoi5-52g&O z=tPgE458O?R?Pk$R|{E(saOUihYW75RPeEyH8>tV3j8FC8bRPOAl0-2oV|$Lg;Yh; z&_(d1>15|09BcFqZ<@VltX!#(4s{^t?mx-ZX?h{yzt*ttBG?rzJ12jsGSEGHMtdRo z|2+ZnbGrz`Z-4M-ApvXvb|37_UA-L5p+5qFhNyoW!hf$O%^+Yq@b6HZAOy&P`)BB` zBnN~c1N>*;57h|)3d8AGAbkPG);&VNh~G(HkY0;A;xt%?K^ z5&z?O{yp@+o@XNxhz~r0{)z$_B2D7zO@`3Hk_We`5VLf;#qJpp1u) z@E4d7NMa#MfUOhCXg$cta&1k2(@bTtkWEw+5RnK0001iBPga;N0OeoV`X0IPtO z8I0L~*8nlS$NvjX`VW9OjN<)ms1V@wze^4U93vq0 z%Gs(XbOgI%4kNLd{CgV-IE>^8+96JV8xDnJ{$15Jw2JvJpsZ?g{sUmMv_OC2zeraA zXp8(8eFFhOn12z99FPJXF)`RCh4DSm3*{p@*z`|P(R*MJ+Ft^@0dN`#k@%BM3X%!X zox$$+8T@+#|2c=>X^l0)-RycI0AOA>CEEn(0sU|0k^WETMZx$dpVD*>e>XjP*732y zrDemc_B4)|4YPIu0&n`!A%o9qtn$GOOoW4sZx_?QtsYYyKODhC>BYCtzo&qplO&8e=ZkL) ze1EYFnq+Ip9vX$&ku9IqflQt*6Gg@D_s7>aAnD5ai4PyX8E#t-DYV^<%JC~5q}Cgf zQg@JqS--+Nfo6yNl>T{LE;|>KRrrE!UoKs7esD`DV|u!Was zr|)aY->q09YFKWfQa~9{XS|5rbfcbM;n!lLlet$BmTgFeGMOyDLMsGxHw6w!vfq7h zx8vmy8d$7nA@b?^m|#XsnVD$bfG`0`(kc%O6I@-4ocH&=-^U9Y4c$3}e0S@Lk9esw zi7Za*Eu)mTK1rtl;jbn>Y1d_Vo~ShdKF~2lNHU$?P%dk3@jL5-GA;YXlU=-ta420D zxxBw|>5r^#zGqoi!wCv$y)3sm9p*Xb_UV*NYixlkH)Y~P(dNQ>v=iLDL^7(RbJ}SO@nm3*!!#iRK;%BT81*|qrfP&GVu2kA z9O26$u@`VM;k6)o{1*L3-Pmukq&xch$q-<2y>P3N6wz*8XndFP8awM+J)lgXKj?UA z&ts|9@{r6>)_Pd4S%+BQ8OnPpMjFO>(3ZIA_qC%|pc9zFu#R2+q$Z$_E$1k@tZftL z3&>I2#c2vk&g;Y5|60AP*T=DMthLTMyW6JJb#j&C@?4jFK4Z?#L9&uWN##b0CTGD1 z8D$`usq?I>hPxMTAqRhno*j^;fsqrvF4>L1*X1wFwL3R}?K?nd5fZHpEIUfazf-8NbE#Z-4)6t06# z6_P`0QLFizIo`fglttez9i46SG4Cw4ygUyN+Lai zzk3K2peVbW(6On(yVJw$!r>&9)CIug@b{BK>L!j;=frPAsx)(�j3y*|tVkd!*Ct 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 TKf9p%eQl!x00Du(-*5i|E_3Gr diff --git a/openpype/hosts/aftereffects/api/extension/jsx/hostscript.jsx b/openpype/hosts/aftereffects/api/extension/jsx/hostscript.jsx index cfb72bacf5..cdaa2e3b83 100644 --- a/openpype/hosts/aftereffects/api/extension/jsx/hostscript.jsx +++ b/openpype/hosts/aftereffects/api/extension/jsx/hostscript.jsx @@ -395,61 +395,6 @@ function saveAs(path){ app.project.save(fp = new File(path)); } -function getRenderInfo(comp_id){ - /*** - Get info from render queue. - Currently pulls only file name to parse extension and - if it is sequence in Python - **/ - var item = app.project.itemByID(comp_id); - if (!item){ - return _prepareError("Composition with '" + comp_id + "' wasn't found! Recreate publishable instance(s)") - } - - var comp_name = item.name; - try{ - var comp_id_count = 0; - for (i = 1; i <= app.project.renderQueue.numItems; ++i){ - var render_item = app.project.renderQueue.item(i); - if (render_item.comp.id != comp_id){ - continue; - } - comp_id_count += 1; - - if (render_item.status == RQItemStatus.DONE){ - var new_item = render_item.duplicate(); // create new, cannot change status if DONE - render_item.remove(); // remove existing to limit duplications - render_item = new_item; - } - - render_item.render = true; // always set render queue to render - var item = render_item.outputModule(1); - } - } catch (error) { - return _prepareError("There is no render queue, create one"); - } - - if (comp_id_count > 1){ - return _prepareError("There cannot be more items in Render Queue for '" + comp_name + "'!") - } - - if (comp_id_count == 0){ - return _prepareError("There is no item in Render Queue for '" + comp_name + "'! Add composition to Render Queue.") - } - - if (render_item.numOutputModules !=1){ - return _prepareError("There must be just 1 Output Module in Render Queue for '" + comp_name + "'! Keep only correct one.") - } - - var file_url = item.file.toString(); - - return JSON.stringify({ - "file_name": file_url, - "width": render_item.comp.width, - "height": render_item.comp.height - }) -} - function getAudioUrlForComp(comp_id){ /** * Searches composition for audio layer @@ -716,6 +661,71 @@ function isFileSequence (item){ return false; } +function getRenderInfo(comp_id){ + /*** + Get info from render queue. + Currently pulls only file name to parse extension and + if it is sequence in Python + **/ + var item = app.project.itemByID(comp_id); + if (!item){ + return _prepareError("Composition with '" + comp_id + "' wasn't found! Recreate publishable instance(s)") + } + + var comp_name = item.name; + try{ + // render_item.duplicate() should create new item on renderQueue + // BUT it works only sometimes, there are some weird synchronization issue + // this method will be called always before render, so prepare items here + // for render to spare the hassle + for (i = 1; i <= app.project.renderQueue.numItems; ++i){ + var render_item = app.project.renderQueue.item(i); + if (render_item.comp.id != comp_id){ + continue; + } + + if (render_item.status == RQItemStatus.DONE){ + render_item.duplicate(); // create new, cannot change status if DONE + render_item.remove(); // remove existing to limit duplications + continue; + } + } + + // properly validate as `numItems` won't change magically + var comp_id_count = 0; + for (i = 1; i <= app.project.renderQueue.numItems; ++i){ + var render_item = app.project.renderQueue.item(i); + if (render_item.comp.id != comp_id){ + continue; + } + comp_id_count += 1; + var item = render_item.outputModule(1); + } + } catch (error) { + return _prepareError("There is no render queue, create one"); + } + + if (comp_id_count > 1){ + return _prepareError("There cannot be more items in Render Queue for '" + comp_name + "'!") + } + + if (comp_id_count == 0){ + return _prepareError("There is no item in Render Queue for '" + comp_name + "'! Add composition to Render Queue.") + } + + if (render_item.numOutputModules !=1){ + return _prepareError("There must be just 1 Output Module in Render Queue for '" + comp_name + "'! Keep only correct one.") + } + + var file_url = item.file.toString(); + + return JSON.stringify({ + "file_name": file_url, + "width": render_item.comp.width, + "height": render_item.comp.height + }) +} + function render(target_folder, comp_id){ var out_dir = new Folder(target_folder); var out_dir = out_dir.fsName; From a09ab62eb7ab9c06dd99fb1b44d6946a30bf3d12 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 11 Nov 2022 15:07:51 +0100 Subject: [PATCH 099/243] :recycle: some tweaks --- .../plugins/create/create_online.py | 20 +++++++------------ .../plugins/publish/collect_online_file.py | 6 +++--- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_online.py b/openpype/hosts/traypublisher/plugins/create/create_online.py index e8092e8eaf..91016dc794 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_online.py +++ b/openpype/hosts/traypublisher/plugins/create/create_online.py @@ -5,9 +5,6 @@ Online file retain their original name and use it as subset name. To avoid conflicts, this creator checks if subset with this name already exists under selected asset. """ -import copy -import os -import re from pathlib import Path from openpype.client import get_subset_by_name, get_asset_by_name @@ -16,11 +13,6 @@ from openpype.pipeline import ( CreatedInstance, CreatorError ) -from openpype.pipeline.create import ( - get_subset_name, - TaskNotSetError, -) - from openpype.hosts.traypublisher.api.plugin import TrayPublishCreator @@ -31,14 +23,16 @@ class OnlineCreator(TrayPublishCreator): label = "Online" family = "online" description = "Publish file retaining its original file name" - extensions = [".mov", ".mp4", ".mfx", ".m4v", ".mpg"] + extensions = [".mov", ".mp4", ".mxf", ".m4v", ".mpg"] def get_detail_description(self): - return """# Publish batch of .mov to multiple assets. + return """# Create file retaining its original file name. - File names must then contain only asset name, or asset name + version. - (eg. 'chair.mov', 'chair_v001.mov', not really safe `my_chair_v001.mov` - """ + This will publish files using template helping to retain original + file name and that file name is used as subset name. + + Bz default it tries to guard against multiple publishes of the same + file.""" def get_icon(self): return "fa.file" diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_online_file.py b/openpype/hosts/traypublisher/plugins/publish/collect_online_file.py index 1d173c326b..459ee463aa 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_online_file.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_online_file.py @@ -3,9 +3,10 @@ import pyblish.api from pathlib import Path -class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin): +class CollectOnlineFile(pyblish.api.InstancePlugin): """Collect online file and retain its file name.""" - label = "Collect online file" + label = "Collect Online File" + order = pyblish.api.CollectorOrder families = ["online"] hosts = ["traypublisher"] @@ -21,4 +22,3 @@ class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin): "stagingDir": file.parent.as_posix() } ] - From b8b184b1b6c90fefcba386886554ebb32f99798c Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 11 Nov 2022 15:08:02 +0100 Subject: [PATCH 100/243] :art: add validator --- .../plugins/publish/validate_online_file.py | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 openpype/hosts/traypublisher/plugins/publish/validate_online_file.py diff --git a/openpype/hosts/traypublisher/plugins/publish/validate_online_file.py b/openpype/hosts/traypublisher/plugins/publish/validate_online_file.py new file mode 100644 index 0000000000..86b9334184 --- /dev/null +++ b/openpype/hosts/traypublisher/plugins/publish/validate_online_file.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +import pyblish.api + +from openpype.pipeline.publish import ( + ValidateContentsOrder, + PublishValidationError, + OptionalPyblishPluginMixin, +) +from openpype.client import get_subset_by_name, get_asset_by_name + + +class ValidateOnlineFile(OptionalPyblishPluginMixin, + pyblish.api.InstancePlugin): + """Validate that subset doesn't exist yet.""" + label = "Validate Existing Online Files" + hosts = ["traypublisher"] + families = ["online"] + order = ValidateContentsOrder + + optional = True + + def process(self, instance): + project_name = instance.context.data["projectName"] + asset_id = instance.data["assetEntity"]["_id"] + subset = get_subset_by_name( + project_name, instance.data["subset"], asset_id) + + if subset: + raise PublishValidationError( + "Subset to be published already exists.", + title=self.label + ) From 9d304f07da447f9a5686be702d6a930c0dc774dd Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 11 Nov 2022 15:08:29 +0100 Subject: [PATCH 101/243] :art: add family to integrator --- openpype/plugins/publish/integrate.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 0998e643e6..401270a788 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -129,7 +129,8 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "mvUsd", "mvUsdComposition", "mvUsdOverride", - "simpleUnrealTexture" + "simpleUnrealTexture", + "online" ] default_template_name = "publish" From cae09e0002ba379bbd5b39ce6720e6a2ff07b1ca Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 11 Nov 2022 15:08:55 +0100 Subject: [PATCH 102/243] :label: fix docstring hints --- openpype/client/entities.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 43afccf2f1..bbef8dc65e 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -389,10 +389,11 @@ def get_subset_by_name(project_name, subset_name, asset_id, fields=None): returned if 'None' is passed. Returns: - None: If subset with specified filters was not found. - Dict: Subset document which can be reduced to specified 'fields'. - """ + Union[str, Dict]: None if subset with specified filters was not found. + or dict subset document which can be reduced to + specified 'fields'. + """ if not subset_name: return None From deac4a33d41d9914a41437e21db6ac0af81d797c Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 11 Nov 2022 15:19:40 +0100 Subject: [PATCH 103/243] :rotating_light: fix hound :dog: --- .../traypublisher/plugins/create/create_online.py | 12 ++++++------ .../plugins/publish/validate_online_file.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_online.py b/openpype/hosts/traypublisher/plugins/create/create_online.py index 91016dc794..22d4b73aee 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_online.py +++ b/openpype/hosts/traypublisher/plugins/create/create_online.py @@ -30,7 +30,7 @@ class OnlineCreator(TrayPublishCreator): This will publish files using template helping to retain original file name and that file name is used as subset name. - + Bz default it tries to guard against multiple publishes of the same file.""" @@ -52,11 +52,11 @@ class OnlineCreator(TrayPublishCreator): instance_data["originalBasename"] = origin_basename subset_name = origin_basename - path = (Path( - pre_create_data.get( - "representation_file")["directory"] - ) / pre_create_data.get( - "representation_file")["filenames"][0]).as_posix() + path = ( + Path( + pre_create_data.get("representation_file")["directory"] + ) / pre_create_data.get("representation_file")["filenames"][0] + ).as_posix() instance_data["creator_attributes"] = {"path": path} diff --git a/openpype/hosts/traypublisher/plugins/publish/validate_online_file.py b/openpype/hosts/traypublisher/plugins/publish/validate_online_file.py index 86b9334184..12b2e72ced 100644 --- a/openpype/hosts/traypublisher/plugins/publish/validate_online_file.py +++ b/openpype/hosts/traypublisher/plugins/publish/validate_online_file.py @@ -6,7 +6,7 @@ from openpype.pipeline.publish import ( PublishValidationError, OptionalPyblishPluginMixin, ) -from openpype.client import get_subset_by_name, get_asset_by_name +from openpype.client import get_subset_by_name class ValidateOnlineFile(OptionalPyblishPluginMixin, From dbd00b3751eb6e9ffa378eb0b0c5985afbfdf41e Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 11 Nov 2022 15:22:29 +0100 Subject: [PATCH 104/243] :rotating_light: hound fix 2 --- .../hosts/traypublisher/plugins/create/create_online.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_online.py b/openpype/hosts/traypublisher/plugins/create/create_online.py index 22d4b73aee..5a6373730d 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_online.py +++ b/openpype/hosts/traypublisher/plugins/create/create_online.py @@ -52,11 +52,7 @@ class OnlineCreator(TrayPublishCreator): instance_data["originalBasename"] = origin_basename subset_name = origin_basename - path = ( - Path( - pre_create_data.get("representation_file")["directory"] - ) / pre_create_data.get("representation_file")["filenames"][0] - ).as_posix() + path = (Path(pre_create_data.get("representation_file")["directory"]) / pre_create_data.get("representation_file")["filenames"][0]).as_posix() # noqa instance_data["creator_attributes"] = {"path": path} From cf0cba1dba0d14b60ca1bff0f9d9170aff88bb43 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 11 Nov 2022 15:43:38 +0100 Subject: [PATCH 105/243] fix variable check in collect anatomy instance data --- openpype/plugins/publish/collect_anatomy_instance_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/collect_anatomy_instance_data.py b/openpype/plugins/publish/collect_anatomy_instance_data.py index f67d3373d9..909b49a07d 100644 --- a/openpype/plugins/publish/collect_anatomy_instance_data.py +++ b/openpype/plugins/publish/collect_anatomy_instance_data.py @@ -188,7 +188,7 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin): for subset_doc in subset_docs: subset_id = subset_doc["_id"] last_version_doc = last_version_docs_by_subset_id.get(subset_id) - if last_version_docs_by_subset_id is None: + if last_version_doc is None: continue asset_id = subset_doc["parent"] From 88bf8840bd7757a19a20a460091800c0fc2741bb Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 14 Nov 2022 17:57:32 +0800 Subject: [PATCH 106/243] Alembic Loader as Arnold Standin --- openpype/hosts/maya/plugins/load/load_abc_to_standin.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py index 9583063c7e..605a492e4d 100644 --- a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py +++ b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py @@ -98,6 +98,7 @@ class AlembicStandinLoader(load.LoaderPlugin): # Update the standin standins = list() members = pm.sets(container['objectName'], query=True) + self.log.info("container:{}".format(container)) for member in members: shape = member.getShape() if (shape and shape.type() == "aiStandIn"): @@ -105,8 +106,11 @@ class AlembicStandinLoader(load.LoaderPlugin): for standin in standins: standin.dso.set(path) - standin.useFrameExtension.set(0) standin.abcFPS.set(float(fps)) + if "modelMain" in container['objectName']: + standin.useFrameExtension.set(0) + else: + standin.useFrameExtension.set(1) container = pm.PyNode(container["objectName"]) container.representation.set(str(representation["_id"])) From ae8342c57932806f05b7e13a7d82ad7d0b5c4d0b Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 14 Nov 2022 18:40:20 +0800 Subject: [PATCH 107/243] aov Filtering --- vendor/configs/OpenColorIO-Configs | 1 - 1 file changed, 1 deletion(-) delete mode 160000 vendor/configs/OpenColorIO-Configs diff --git a/vendor/configs/OpenColorIO-Configs b/vendor/configs/OpenColorIO-Configs deleted file mode 160000 index 0bb079c08b..0000000000 --- a/vendor/configs/OpenColorIO-Configs +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0bb079c08be410030669cbf5f19ff869b88af953 From f6495ca956c709cf33654d12c80cadedb5a272d3 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 14 Nov 2022 13:30:41 +0100 Subject: [PATCH 108/243] OP-4394 - extension is lowercased in Setting and in uploaded files --- .../webpublisher/plugins/publish/collect_published_files.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py b/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py index 2bf097de41..ac4ade4e48 100644 --- a/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py +++ b/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py @@ -86,6 +86,7 @@ class CollectPublishedFiles(pyblish.api.ContextPlugin): first_file = task_data["files"][0] _, extension = os.path.splitext(first_file) + extension = extension.lower() family, families, tags = self._get_family( self.task_type_to_family, task_type, @@ -244,7 +245,10 @@ class CollectPublishedFiles(pyblish.api.ContextPlugin): for config in families_config: if is_sequence != config["is_sequence"]: continue - if (extension in config["extensions"] or + + lower_extensions = [ext.lower() + for ext in config.get("extensions", [])] + if (extension.lower() in lower_extensions or '' in config["extensions"]): # all extensions setting found_family = config["result_family"] break From 93b9dd7224e669c4f453dc0578ebc57ce0812c6f Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 14 Nov 2022 13:40:28 +0100 Subject: [PATCH 109/243] OP-4394 - extension is lowercased in Setting and in uploaded files --- .../webpublisher/plugins/publish/collect_published_files.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py b/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py index ac4ade4e48..40f4da9403 100644 --- a/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py +++ b/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py @@ -247,9 +247,9 @@ class CollectPublishedFiles(pyblish.api.ContextPlugin): continue lower_extensions = [ext.lower() - for ext in config.get("extensions", [])] + for ext in config.get("extensions", [''])] if (extension.lower() in lower_extensions or - '' in config["extensions"]): # all extensions setting + lower_extensions[0] == ''): # all extensions setting found_family = config["result_family"] break From 1e995ea6d921611f221c3352a958cc1d960e8884 Mon Sep 17 00:00:00 2001 From: clement hector Date: Mon, 14 Nov 2022 15:06:12 +0100 Subject: [PATCH 110/243] remove reviewMain checks --- .../kitsu/plugins/publish/integrate_kitsu_review.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py index 61d5a13660..bf77f2c892 100644 --- a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py @@ -32,13 +32,8 @@ class IntegrateKitsuReview(pyblish.api.InstancePlugin): continue review_path = representation.get("published_path") - file_name, file_extension = os.path.splitext(review_path) - - if instance.data.get('name') != 'reviewMain' \ - or file_extension != '.mp4': - continue - self.log.debug("Found review at: {}".format(review_path)) + gazu.task.add_preview( task, comment, review_path, normalize_movie=True ) From 6934b3e0ef92101871909d4b643c444001a4c478 Mon Sep 17 00:00:00 2001 From: clement hector Date: Mon, 14 Nov 2022 15:09:18 +0100 Subject: [PATCH 111/243] add an option to chose which families will be uploaded to kitsu --- .../settings/defaults/project_settings/tvpaint.json | 6 ++++++ .../projects_schema/schema_project_tvpaint.json | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/openpype/settings/defaults/project_settings/tvpaint.json b/openpype/settings/defaults/project_settings/tvpaint.json index 88b5a598cd..2e413f50cd 100644 --- a/openpype/settings/defaults/project_settings/tvpaint.json +++ b/openpype/settings/defaults/project_settings/tvpaint.json @@ -11,6 +11,12 @@ 255, 255, 255 + ], + "families_to_upload": [ + "review", + "renderpass", + "renderlayer", + "renderscene" ] }, "ValidateProjectSettings": { diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json b/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json index 20fe5b0855..0392c9089b 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json @@ -56,6 +56,18 @@ "key": "review_bg", "label": "Review BG color", "use_alpha": false + }, + { + "type": "enum", + "key": "families_to_upload", + "label": "Families to upload", + "multiselection": true, + "enum_items": [ + {"review": "review"}, + {"renderpass": "renderPass"}, + {"renderlayer": "renderLayer"}, + {"renderscene": "renderScene"} + ] } ] }, From 4c1d1f961511e6fe9a0a87d84bc16b1b3b710011 Mon Sep 17 00:00:00 2001 From: clement hector Date: Mon, 14 Nov 2022 15:10:12 +0100 Subject: [PATCH 112/243] add review tag to the selected families in the tvpaint project settings --- openpype/hosts/tvpaint/plugins/publish/extract_sequence.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py b/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py index 77712347bd..d8aef1ab6b 100644 --- a/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py +++ b/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py @@ -127,9 +127,9 @@ class ExtractSequence(pyblish.api.Extractor): output_frame_start ) - # Fill tags and new families + # Fill tags and new families from project settings tags = [] - if family_lowered in ("review", "renderlayer", "renderscene"): + if family_lowered in self.families_to_upload: tags.append("review") # Sequence of one frame From fd08bbf17026aa3be3045804503342ce5f9a02c7 Mon Sep 17 00:00:00 2001 From: clement hector Date: Mon, 14 Nov 2022 15:13:11 +0100 Subject: [PATCH 113/243] remove useless import --- openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py index bf77f2c892..e5e6439439 100644 --- a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -import os import gazu import pyblish.api From 20ea1c8212a5d9226d162ce5532ad641ad9a2b73 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 14 Nov 2022 18:27:11 +0100 Subject: [PATCH 114/243] ignore case sensitivity of extension in files widget --- openpype/lib/attribute_definitions.py | 7 +++++++ openpype/tools/attribute_defs/files_widget.py | 6 +++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/openpype/lib/attribute_definitions.py b/openpype/lib/attribute_definitions.py index 589a4ef9ab..6baeaec045 100644 --- a/openpype/lib/attribute_definitions.py +++ b/openpype/lib/attribute_definitions.py @@ -541,6 +541,13 @@ class FileDefItem(object): return ext return None + @property + def lower_ext(self): + ext = self.ext + if ext is not None: + return ext.lower() + return ext + @property def is_dir(self): if self.is_empty: diff --git a/openpype/tools/attribute_defs/files_widget.py b/openpype/tools/attribute_defs/files_widget.py index 3f1e6a34e1..738e50ba07 100644 --- a/openpype/tools/attribute_defs/files_widget.py +++ b/openpype/tools/attribute_defs/files_widget.py @@ -349,7 +349,7 @@ class FilesModel(QtGui.QStandardItemModel): item.setData(file_item.filenames, FILENAMES_ROLE) item.setData(file_item.directory, DIRPATH_ROLE) item.setData(icon_pixmap, ITEM_ICON_ROLE) - item.setData(file_item.ext, EXT_ROLE) + item.setData(file_item.lower_ext, EXT_ROLE) item.setData(file_item.is_dir, IS_DIR_ROLE) item.setData(file_item.is_sequence, IS_SEQUENCE_ROLE) @@ -463,7 +463,7 @@ class FilesProxyModel(QtCore.QSortFilterProxyModel): for filepath in filepaths: if os.path.isfile(filepath): _, ext = os.path.splitext(filepath) - if ext in self._allowed_extensions: + if ext.lower() in self._allowed_extensions: return True elif self._allow_folders: @@ -475,7 +475,7 @@ class FilesProxyModel(QtCore.QSortFilterProxyModel): for filepath in filepaths: if os.path.isfile(filepath): _, ext = os.path.splitext(filepath) - if ext in self._allowed_extensions: + if ext.lower() in self._allowed_extensions: filtered_paths.append(filepath) elif self._allow_folders: From 088e7507b3144a7deb3cafe4b51286649e446079 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Tue, 15 Nov 2022 09:06:09 +0100 Subject: [PATCH 115/243] logging format --- .../hooks/pre_copy_last_published_workfile.py | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index 0e561334e1..44144e5fff 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -75,16 +75,20 @@ class CopyLastPublishedWorkfile(PreLaunchHook): self.log.info( ( "Seems like old version of settings is used." - ' Can\'t access custom templates in host "{}".' - ).format(host_name) + ' Can\'t access custom templates in host "{}".'.format( + host_name + ) + ) ) return elif use_last_published_workfile is False: self.log.info( ( 'Project "{}" has turned off to use last published' - ' workfile as first workfile for host "{}"' - ).format(project_name, host_name) + ' workfile as first workfile for host "{}"'.format( + project_name, host_name + ) + ) ) return @@ -114,8 +118,8 @@ class CopyLastPublishedWorkfile(PreLaunchHook): None, ) if not subset_id: - self.log.debug('No any workfile for asset "{}".').format( - asset_doc["name"] + self.log.debug( + 'No any workfile for asset "{}".'.format(asset_doc["name"]) ) return @@ -131,8 +135,7 @@ class CopyLastPublishedWorkfile(PreLaunchHook): ( representation for representation in get_representations( - project_name, - version_ids=[last_version_doc["_id"]] + project_name, version_ids=[last_version_doc["_id"]] ) if representation["context"]["task"]["name"] == task_name ), @@ -141,8 +144,10 @@ class CopyLastPublishedWorkfile(PreLaunchHook): if not workfile_representation: self.log.debug( - 'No published workfile for task "{}" and host "{}".' - ).format(task_name, host_name) + 'No published workfile for task "{}" and host "{}".'.format( + task_name, host_name + ) + ) return local_site_id = get_local_site_id() @@ -152,13 +157,11 @@ class CopyLastPublishedWorkfile(PreLaunchHook): local_site_id, force=True, priority=99, - reset_timer=True + reset_timer=True, ) while not sync_server.is_representation_on_site( - project_name, - workfile_representation["_id"], - local_site_id + project_name, workfile_representation["_id"], local_site_id ): sleep(5) From 6a18fa0fdaef4ed2d85a91f2f74b0bd7bccdd611 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 15 Nov 2022 12:11:26 +0100 Subject: [PATCH 116/243] OP-4361 - updated Creator to use composition name There is new flag to allow use composition name Updated subset template. Removed obsolete 'renderLocal', now it is only 'render' with a flag. --- .../plugins/create/create_render.py | 68 +++++++++++++------ .../defaults/project_settings/global.json | 6 +- 2 files changed, 51 insertions(+), 23 deletions(-) diff --git a/openpype/hosts/aftereffects/plugins/create/create_render.py b/openpype/hosts/aftereffects/plugins/create/create_render.py index 65a4bc4500..7010696df3 100644 --- a/openpype/hosts/aftereffects/plugins/create/create_render.py +++ b/openpype/hosts/aftereffects/plugins/create/create_render.py @@ -1,3 +1,5 @@ +import re + from openpype import resources from openpype.lib import BoolDef, UISeparatorDef from openpype.hosts.aftereffects import api @@ -8,6 +10,7 @@ from openpype.pipeline import ( legacy_io, ) from openpype.hosts.aftereffects.api.pipeline import cache_and_get_instances +from openpype.lib import prepare_template_data class RenderCreator(Creator): @@ -50,7 +53,7 @@ class RenderCreator(Creator): self.host.remove_instance(instance) self._remove_instance_from_context(instance) - def create(self, subset_name, data, pre_create_data): + def create(self, subset_name_from_ui, data, pre_create_data): stub = api.get_stub() # only after After Effects is up if pre_create_data.get("use_selection"): items = stub.get_selected_items( @@ -59,31 +62,40 @@ class RenderCreator(Creator): else: items = stub.get_items(comps=True, folders=False, footages=False) - if len(items) > 1: - raise CreatorError( - "Please select only single composition at time." - ) if not items: - raise CreatorError(( + raise CreatorError( "Nothing to create. Select composition " "if 'useSelection' or create at least " "one composition." - )) + ) - for inst in self.create_context.instances: - if subset_name == inst.subset_name: - raise CreatorError("{} already exists".format( - inst.subset_name)) + for item in items: + if pre_create_data.get("use_composition_name"): + composition_name = item.name + dynamic_fill = prepare_template_data({"composition": + composition_name}) + subset_name = subset_name_from_ui.format(**dynamic_fill) + data["composition_name"] = composition_name + else: + subset_name = subset_name_from_ui + re.sub(subset_name, r"{composition}", '', + flags=re.IGNORECASE) - data["members"] = [items[0].id] - new_instance = CreatedInstance(self.family, subset_name, data, self) - if "farm" in pre_create_data: - use_farm = pre_create_data["farm"] - new_instance.creator_attributes["farm"] = use_farm + for inst in self.create_context.instances: + if subset_name == inst.subset_name: + raise CreatorError("{} already exists".format( + inst.subset_name)) - api.get_stub().imprint(new_instance.id, - new_instance.data_to_store()) - self._add_instance_to_context(new_instance) + data["members"] = [items[0].id] + new_instance = CreatedInstance(self.family, subset_name, data, + self) + if "farm" in pre_create_data: + use_farm = pre_create_data["farm"] + new_instance.creator_attributes["farm"] = use_farm + + api.get_stub().imprint(new_instance.id, + new_instance.data_to_store()) + self._add_instance_to_context(new_instance) def get_default_variants(self): return self._default_variants @@ -94,6 +106,8 @@ class RenderCreator(Creator): def get_pre_create_attr_defs(self): output = [ BoolDef("use_selection", default=True, label="Use selection"), + BoolDef("use_composition_name", + label="Use composition name in subset"), UISeparatorDef(), BoolDef("farm", label="Render on farm") ] @@ -102,6 +116,22 @@ class RenderCreator(Creator): def get_detail_description(self): return """Creator for Render instances""" + def get_dynamic_data(self, variant, task_name, asset_doc, + project_name, host_name, instance): + dynamic_data = super(RenderCreator, self).get_dynamic_data( + variant, task_name, asset_doc, project_name, host_name, + instance + ) + + if instance is not None: + composition_name = instance.get("composition_name") + if composition_name: + dynamic_data["composition"] = composition_name + else: + dynamic_data["composition"] = "{composition}" + + return dynamic_data + def _handle_legacy(self, instance_data): """Converts old instances to new format.""" if not instance_data.get("members"): diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index b128564bc2..9ad90af63a 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -347,15 +347,13 @@ "template": "{family}{Task}" }, { - "families": [ - "renderLocal" - ], + "families": ["render"], "hosts": [ "aftereffects" ], "task_types": [], "tasks": [], - "template": "render{Task}{Variant}" + "template": "{family}{Task}{Composition}{Variant}" }, { "families": [ From 702edb0efe0e268f1a92d0b7e020bfda2b05d694 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 15 Nov 2022 12:17:27 +0100 Subject: [PATCH 117/243] OP-4361 - rename composition after create Previously it was renaming composition with subset name, this was missing in new creator. Renamed items to more descriptive 'comps'. --- .../aftereffects/plugins/create/create_render.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/aftereffects/plugins/create/create_render.py b/openpype/hosts/aftereffects/plugins/create/create_render.py index 7010696df3..518f3070af 100644 --- a/openpype/hosts/aftereffects/plugins/create/create_render.py +++ b/openpype/hosts/aftereffects/plugins/create/create_render.py @@ -56,22 +56,22 @@ class RenderCreator(Creator): def create(self, subset_name_from_ui, data, pre_create_data): stub = api.get_stub() # only after After Effects is up if pre_create_data.get("use_selection"): - items = stub.get_selected_items( + comps = stub.get_selected_items( comps=True, folders=False, footages=False ) else: - items = stub.get_items(comps=True, folders=False, footages=False) + comps = stub.get_items(comps=True, folders=False, footages=False) - if not items: + if not comps: raise CreatorError( "Nothing to create. Select composition " "if 'useSelection' or create at least " "one composition." ) - for item in items: + for comp in comps: if pre_create_data.get("use_composition_name"): - composition_name = item.name + composition_name = comp.name dynamic_fill = prepare_template_data({"composition": composition_name}) subset_name = subset_name_from_ui.format(**dynamic_fill) @@ -86,7 +86,7 @@ class RenderCreator(Creator): raise CreatorError("{} already exists".format( inst.subset_name)) - data["members"] = [items[0].id] + data["members"] = [comp.id] new_instance = CreatedInstance(self.family, subset_name, data, self) if "farm" in pre_create_data: @@ -97,6 +97,8 @@ class RenderCreator(Creator): new_instance.data_to_store()) self._add_instance_to_context(new_instance) + stub.rename_item(comp.id, subset_name) + def get_default_variants(self): return self._default_variants From 3b9662f97cfb19be8a44b95e16fa50785e22ea21 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 15 Nov 2022 13:26:22 +0100 Subject: [PATCH 118/243] added settings for validate frame range in tray publisher --- .../project_settings/traypublisher.json | 7 +++++ .../schema_project_traypublisher.json | 18 +++++++++++++ .../schemas/template_validate_plugin.json | 26 +++++++++++++++++++ 3 files changed, 51 insertions(+) create mode 100644 openpype/settings/entities/schemas/projects_schema/schemas/template_validate_plugin.json diff --git a/openpype/settings/defaults/project_settings/traypublisher.json b/openpype/settings/defaults/project_settings/traypublisher.json index 5db2a79772..e99b96b8c4 100644 --- a/openpype/settings/defaults/project_settings/traypublisher.json +++ b/openpype/settings/defaults/project_settings/traypublisher.json @@ -303,5 +303,12 @@ "extensions": [ ".mov" ] + }, + "publish": { + "ValidateFrameRange": { + "enabled": true, + "optional": true, + "active": true + } } } \ No newline at end of file diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json index 7c61aeed50..faa5033d2a 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json @@ -311,6 +311,24 @@ "object_type": "text" } ] + }, + { + "type": "dict", + "collapsible": true, + "key": "publish", + "label": "Publish plugins", + "children": [ + { + "type": "schema_template", + "name": "template_validate_plugin", + "template_data": [ + { + "key": "ValidateFrameRange", + "label": "Validate frame range" + } + ] + } + ] } ] } diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/template_validate_plugin.json b/openpype/settings/entities/schemas/projects_schema/schemas/template_validate_plugin.json new file mode 100644 index 0000000000..b57cad6719 --- /dev/null +++ b/openpype/settings/entities/schemas/projects_schema/schemas/template_validate_plugin.json @@ -0,0 +1,26 @@ +[ + { + "type": "dict", + "collapsible": true, + "key": "{key}", + "label": "{label}", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "optional", + "label": "Optional" + }, + { + "type": "boolean", + "key": "active", + "label": "Active" + } + ] + } +] From 2e1004c618f136999d02728471c4fa9c4acb98dd Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 15 Nov 2022 16:03:45 +0100 Subject: [PATCH 119/243] OP-4361 - rename composition after update, delete --- .../aftereffects/plugins/create/create_render.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/aftereffects/plugins/create/create_render.py b/openpype/hosts/aftereffects/plugins/create/create_render.py index 518f3070af..5684657bf9 100644 --- a/openpype/hosts/aftereffects/plugins/create/create_render.py +++ b/openpype/hosts/aftereffects/plugins/create/create_render.py @@ -47,11 +47,25 @@ class RenderCreator(Creator): for created_inst, _changes in update_list: api.get_stub().imprint(created_inst.get("instance_id"), created_inst.data_to_store()) + subset_change = _changes.get("subset") + if subset_change: + api.get_stub().rename_item(created_inst.data["members"][0], + subset_change[1]) def remove_instances(self, instances): for instance in instances: - self.host.remove_instance(instance) self._remove_instance_from_context(instance) + self.host.remove_instance(instance) + + subset = instance.data["subset"] + comp_id = instance.data["members"][0] + comp = api.get_stub().get_item(comp_id) + if comp: + new_comp_name = comp.name.replace(subset, '') + if not new_comp_name: + new_comp_name = "dummyCompName" + api.get_stub().rename_item(comp_id, + new_comp_name) def create(self, subset_name_from_ui, data, pre_create_data): stub = api.get_stub() # only after After Effects is up From fb154ad4f18b43b6d7e872f239889bd98381d00a Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 15 Nov 2022 16:11:55 +0100 Subject: [PATCH 120/243] OP-4361 - implemented multiple output modules per composition Working version for local rendering. --- openpype/hosts/aftereffects/api/extension.zxp | Bin 101376 -> 101426 bytes .../api/extension/jsx/hostscript.jsx | 176 +++++++++--------- openpype/hosts/aftereffects/api/ws_stub.py | 5 +- .../plugins/publish/collect_render.py | 43 +++-- .../plugins/publish/extract_local_render.py | 64 ++++--- 5 files changed, 151 insertions(+), 137 deletions(-) diff --git a/openpype/hosts/aftereffects/api/extension.zxp b/openpype/hosts/aftereffects/api/extension.zxp index f80787fef8a989c46d4fda489a39b7792ff9500b..b436f0ca0b67313cf4509d1ec5e7d4a1dfd2d106 100644 GIT binary patch delta 8603 zcmZ8{Wmp}}((T?j1b5c}!QB$vod6-Y2e;sC2n-h7-QC??g9LXC1P|`++$85c^4({o z*L1C_?&%-X_0$^gB)FO+xTrwrlv2m)ka6&zHdY5J`A=JW3-tl@FHpk3g!&6UVTqwY z^Mu;QmXNy=AX+;Z|E3vBp}-owmxG4E27y2bpg-2P6h83(SZlvg%t8P0jZLM__#1-kTQ=D$qxy7d175W8`VzXQtoxc?{Qh|e(;bee%(Y`ecR zvbuw#@xNygi$+ZttMS$TPq5gLuW5gSS_M?l{sqJpO%8uR<2*Uo8}~2L;sIO1{zU`) zV1DGk2wnzEg!CtTBDE^m9sZyAjRmS;Kg7QTMqThM477@sT_TJT*yZ(?um7FEf0yC6 zSxr&X<@E;%~Ab*VIV!+Nmc@am(H zg#vXmLt^N=E$^*`p5(>u_n5}}OYJ@Fs{(`0JkQA9-BB^Wwt8$ds6C#uh{7dv&%|=s z0?}4N8Aq*L2-G)%W@l3(HImC=pZ6a8_0Q#3xB5bWo%@1~S6;d$tt_3=7ydg}7!UIp z+I{nFB@qjq0!C=4osS!*-INRk_WjSSMl2zW>o|f>JDiy&2mZBs*d!fzftqh|PJw8t z6N!`CVV0oP=nce(dawe+-6< zJ?3>f8meLm{R-$Hhk0LNkjnbxYVpf}pw}I&pZ<4`>%3_9uBnvDB#Lgb-3(>rH@D`! z>=`Wk)H?ISAH>g(o9F~s=+_*|-gKz-%|7)2fy@WniDN!Ud>#3|CkwH(N^y+Y8|Q^_ za@Yw7Jfr#aXII7#Tso^+V<{aWJixZ*N8|w62Hs6IfnBCEid6N!kaWFyekI{63(cDV zCe&AYncIob@6`yEI4bUn_FvGuJ#0JC*lvC1?J?)8qkk_YH1!p9{|Y)o_mEpGjy)6* zZu)exNjH9Po#9YbXEc^&wdyx{39C(~7oew~g=pz%oLa&^HdY{wlm5Cd?qvsUrzUy^ zY&CJnKMG;5duxN1yrj$5d5!?*i7zF^tOgX_OF5@M;-R3W!Zgr)N3K6^f&`cQCu+b~ zi9Vr}`A%My@JE`Fxe3&n_gW9CpB(^f6Lf@!Y4c|xq3juFWUFn2m)=!o)1QjW$h+{Q z@|^e{>nW8Z-q1DiR)%0QnmM|shqz~z63W2!Wb+gWk6|`d#383NNw_Z8nB|2rZ(5HM zwe#WffZ#yL1GZ+i8F+ac7@xkPF%s35 zS!b9&mTmDRX|^LT;&)(MXVK$hf}y_&VqOZUCMC)SvSh`5g0#&)DnCO>BqjD3c0FZu z%~At;BXi#5((Q&)*8FV2Nn!vWF^EmMRy;a&L+jHaHsxnVs&7&e4BhYzd&lPJxZzdL z>$YT{wbUfIcH5cbR7Bvd8!AAaGmA9*iOnr;)nhfNaW)_*05|jen_vxQSTLj<2XaF z7abB6x3J{3P9rkZ`2r6loL{G(vj=@rbvwnMV0f70QlT%KWbwFVy+Vnrvhv=xxgTQ} z;XHxFJGjuC^F~{>syUN^eEIViAQI_X=PlgmqBFCCo{AC4g6By-$%Hk zpwq6YE4{cI4w>2u_-&tPDZOdBM>dX!kY&AP{}9intBEFH(u5lBYhRIcE|Kz*MB6u9 zqLJvAap(ky?wZ>CXm_B>(w8Krq8$T42~D%qIxXyt6 zLBm!#rfSD=yJG^BRz-(yLFDrS55&a{p(MVdNdRiFU4zkS%XtEDx$c1g10BYZUozLZ zxu&)zK-x(N1jLPWcE^;xp|!Q4v5AE@F=A&;m2ZHBw82^)vz?v7aoW|)$N9a~v-a7| zv)0O>9lNn-hx@|~JtI0&3T6CPj+{a_R|;KPurCjUjkvxEgyto2YO02xIvt|4vq;H~ z%4LD_=@Gn`7gpXH7em$SEyRZa&nq?U4OVu~OIx7=dvVE|;B^%GMMO8BBh-8zJzN0` zD@TB8*S6UF1DKjhEJiaDVJ0{a(!WFzx6vaz)Cn6hhP~=dFo5jsU|=vH@g| zeJ=9eh3Q%25|yj7D#12{`nm34rAvdU2$iaBAWiMFSK9lkI^4Xjx5f(f*jKRr!$VbJ zpa)4eTI*S&O5m$5Tj(BCU*5y#j!h1Y!dD>*bUsF5voJKlz7HdxmWSHB;CfhJU#Qp> zK3rs&olz))N17d5I76FJfxF~w>{dXVddWkc>>b04KStyWEJ@P|(t947XVDDMq08^! zJfbNCvn%ROp68P(bBAw^@NYX}6{Gyt>|u3de}MV>!{j+F16S6T&2CC^+VwP(!zvDV zt_Bg;vVZIat(i_Z(!<%hOuy+Ux4b6&BHlOIt=wBb(EY}W4*%Y*scV@)(+m*%s&f>f z@aWk+p1&JX88U3j`^ZhGcY42*bN^Z8Y{Bgt$=NAqCf=;<@m=q=-~WyPFuca+UT5SS#MqpBb`GS|5U&sUzwU@K*D!Q*fiv)I(p15 z)fx6O{97L?Ms8j$(h<2wM};&bK;@^0NYe^&a~pUf`&9}&PiJ{}a6@=_Qm+5@M;=rfNhTY3 zTwL*oO^;ndiTqNQP(Db5_XsdpDYiC8a9Kq&SWWvitm_6f%LEiDJ=zo zqPQ77{HtMKupUopu66}T5XCmVlfv~H$(nhMos84-3=_|~pFJPz6Xer!dXpJmR#d;!%6y}_Q5tH4 z#gOIck9N}%^vJ!a8LW0qOPyUSwZ>w)%ia6?^<71qshvit{Z3>8LLQ!kB?Ub$1G!oW z9KOuM_6q#k{dZ7|!gEmZRGUAoQBc+TLtMi?ZhMja zyqlhysTNQdx^Bm_+W-h~1cKS;*kK#06d)<9?ows5_LoL@`DtikdFKjAmo%U64W!DX z;1yb+c^{Os!xbfeM0e$kL=#>r)&AztkYlQPHd+Tk{*Xk1){j{R#z6PfXHfDe+V@UdM%3EiTNu3` z0(l}uqNh%)0{&$@_LE_q-2_aaJc;@mhA%+%J(6H)PyYH@Z`zW30%A%PZ--^vQq=9j zijtM@jri7(qxn|_m)Ifty?#NT7XF*<9YVv2oMN}u_N%o^o-Zfp#{t&+Zp|}XrV~Xb|~WuGLf*yQ8G6QpE7Pa5`!3jY$?SjvoAp>uZMg*_WH2K77Sy8v7r zmE?BqMk(F)hD(?igD7RKOF4Xtm~b@v~giqV_6lj(KAQ=WN5?()3Uh>5+G5puL0 z7u3~jz>8}lIo~8C>k0k1K9!2aj&8E^sOO+vSk=wt3f}8xyu?tQC`nb1U z*~nes+*@5(=wZB4CoC;=RDb|h8!uBBBnYcv<&Vhv6oFWC9`PI z0*FIqZPz_?Px54;2en1|PJj5R27>HBYub!4QG)R^Q<6vk4s&9F-(D;3BkQSvI>Pf} zj|TGu^}zs1Yt*q2AQ{F38|7=B^aq+Iqx$WxWgQ2OJ~ob)8#so&>7)&odx z*G)K<74Ot7PQ(nE;)CcYanm~su6xx8#1F)dm4Zae5V1q?&k<+ij08~`d1`yL%hGgJHKl2DbY|V(%_4skUS&`u&9Zpnvd@;MxQNL9e#`ZvcD>79 zgkaAWEAf8$*!@*iTJU=5MH<${8g9~9v^ok%fs~g$YE3H0H)ICb;UKmQ#LYz)ej$M^ zK(ri?U%U1zFXEqgA!P!rSHhLCy_2( zw)pg60!D~r3PkQ!4Lw?A5K@_1^@%WV(*GC5P7M}ib%rb#3TgOA4<8%gXtAB z8|2Ie$rB@*BkW@MLlYm0Uh1&ozNxh9f=x{JBjHT}B`wB^99I4ChuwbfGv5xKnVBcc2NGbQ&q z^)X&ne+-Vp zXE82sGDNU?G4j!LOY$gkh17)$g+a1Pe&C3ST49W{P(E`KWWs_BAQi zTAMF08lS1Ad^SteWNxs0cQR>Yc)}dYE_Wazw2zis^QK3?Q6DdwK~h4thV%8-#XT*MPcZGiKQ<={er2pqQQV{$wb9wS`!RR z+q}&L>{b(rUvN*yjaun@U0~%M1Xpo|KPaj%VO9)lq5$(t>^=uh0l+E961<%+-9XJ+ zxxx3SYg5{X{c_tdWOVEIGJN-uajF~?;2dLnAkkJX5<^(pDZg=)U(`7N!oI3!=d~{O z7-ETpMgPtX3X5yx=6OR#soD*J5q<94NQ@7#jGMHpIlDxl+*}y@x||a~DqA~n~aF&U|7u+rr5 zKzaXOBV5jgtt-vb^TJp0t;A+1Po-XGT9^_Gvtou9F!bXL948UMU8}dmgKCq@UuAKH zu`t1>QkaHJ(kNeSLeV0381LI7jS6_li@rN7?XzbPNB;pi#8-mPj2f_JRDTeul<<** zKTmcX*mMq#);l9UEcQO0vDsxu{AGA#blYwc8OoP)&|>E7er4Q>bhl0LTq;y?`e=QA ze`v%mc(>-+wH8j-2eBk-g6Y&-yeD%eQB3;7@rt&d%z2?Xpx_NTi3 z@9Vz>?%!rLM)+W{w!{|**a!GG;ZHl8B;%N>NE&cbaqcS3Cp82Iqm>k*I?E_Z4mOGw zUPkA9pyc+sz4S}(#CjK-62d-d$V}g|_X`3L{k&I^5;^9yfjy|aan-+UoGTQRySTrj zuuhrod|!9aQW)VzUjj-uzz;COev5kAKbT1-h`4gmGY8mX(R#1$2(^{N2~gaGCgN^x zNsu-x5~3(#;WWdfR-Fp^aK0%iLaf!dF*Rp-STE{li`)!|WLDgcWD&skQ6if_8|bWE z89!A!;-Aa*1GHuR1j`JLzbWRhc&SstBa}SWQGRC=-5%R;T3SQs#=2x1<+j+w z=gZ(>NZ}sAD^*O5Ho^ouyw!CcVCFKeKXJpMzt*vIUVp`g+0lCFe9N-Ab2ZvCPsK6{ z1*obv2MtM*@~EmXY*Po;9!wDzJRx>DGLef+0wkkcBEeAkHAH=3E{RQ}GI{Nt2dp>9 z-I(dZyuX-RVKeA{*hY`Ki3f|@?Tu2)9L0w8;I+4X#Pb_R#asHhcq}O8fHHIAsu9sB zq8R`on@ak~H$eQ_`rQ@P6LtYH&X)Lxng&M-&C^GPJv7+dAIc|$dMkO_s$oXy{SXgR z17KGCISB{lksc8$92pr}KHl)dT=LPo?ix zjd!_mZ4eBPEcJXnnb$`sN?YA6!QSr(3V=8- zD_6e{ma=eP38R;$q(&bYh<(|v-d$yi-?bFQhL_A zgHLO=?*06YYhMH!niPaUu4Jju@>rw2gJvq6pZ1oo7XN1t*x>P7ZElUQ3GS$}8j?OiHl~ARwY8^o4ro-eioa7y4_jf~iINn^j!Q6wLP>dd(M(OIb|@c1 zrwym5M05O%w1V_F?=&B#kr=s>K1L)huECi7ytt6dc_E&*^R#1&>==gqg)as)LLlx8 zxhHbnfrR(D?45Iqj6((-3A=%nK=z^M8+R$|iY=yJgmj(#qtkrm39|dNLD5E~KWt30 zlh0hB56bIri+9^uAVL>{kkbV?o-7kI+;92ei`vyh^?Hk4;ZqbeM`C)oIaI20iIP8e zbedaz=gO@vHN@%46!&&}3af#qn@PMxx=4#4dQy^@siYKsyk7p)Vn%Ye3sw`TK)m_n zwYUH$YWJTyDjKT+gw@h6#8voC(J~tS#*R1U$8sn9JoXNlhU>xY=Fw*G{19qq)wJbr z7Ni0iw3`>XJ0AvBaep;VV!%voI22>mrpu@Ga>y%Hvb#2W!HxAt_!D29}$a$+l60XTX__GkPF>Mm@$BY$4;l@!_ao8YN zz{@}-j)hj5p)(PzRF)Yq)|*IkXTK9$fwL^9>=`o0xJV%xAaL;NA$(^raK>_wE^Vm{rxTC* zc6KG1Ttj7^dToh9Q<#$%K|Wy01sgu8wpsmM zLS_`6OM6$q;*ch&uKX$28+o`aMnU}6ZfkeR`Qb|Rc@y_}v$J%5t`!J)LHWPwfVwZ( z?bZM6^o>7!!EN9_`g~%tKbQ^ckLLeg4h%&K`p>@ri9h|p0?@w=jU->eFaP3_1%kQ% zhx@N|`WrY6%)dYx z2N&)yFvpX`fWNk|O)R7IBr)T2c>A0FRt5vnOho}60UrPWpaA|v1*w9N|3v?Ye5IO$ z|EroooBdZqm`)w{FF2F`hLdP6|KVQ~-G5f!7&83>yd5WS{^n8$^7*&gXwV7V-^KP& zcl7_B=HeHsb6Q2A_hlQfO^r@`5KW4QrmnUj{Q8QmWYVZA)F7Kkv zMnr@VMgzv~GJ7Q#T?)O&ybidgnP26GA4NyPw->l|Ce<3E3rfq*KFQ8@Y$4_MyHqlN zXHD;rUN}UcrLjKUQhWxzn@d+{q^E5;&CLkGEd(~ zQ|8z>r$#0`2kLQS9Zyit@V1k*eqSo(47K{nHGN=FaMKPuEc~?BG-Vv&!FXp=h?D>6 zf%|wssXjXSNH?Yb`0^)hM)gIrQdH&#i`sEqlUyThf%}V{tyv((uUjis-7t&3{Wj>9 z_ox&@o^1fAQ@IZ!6^|G~B)m^>#pAvL1DpNSbyZ zT|5W^R12qAw$o!@Jb6;B?C<$g1f6=3(W;X18h}N83Yr6Y7i?eB>X6viy_AQVe6zc; z=h`?3FZy}j6Xudo5XdX* zk-aBOLL%Qm3%Rje3MK=Dv`Vl|#>!TB~CcBbmkYCYO2^ z2UN_WXeLp)3#oo+S4wwA6ZO^-Uz?kz)`ls(g|_H&#*9=Sg`M|C03md%gxC7UIx?sv zD7^0=BG=QxIljlH)O~uLjFHYIMBRJ&k$hRDClZ8}MiwmA@ zBHD@W(eH&5{bxlcK`%^RlM%at&BAlvqF$&Coe4A~(8QhcLU%e}5$^3;?>KiBpZ!P| zW8UJWM8U0I$AM(0>Wo5j$=CuTeCl7jJEq&Fs4~uU*F|Hq>sBGKrxvxol~~~EJ(p?q z@AB3p(B$yaWXKIh*J-N)_|Gi%cfuCyygJ6~M0Rk-UdLD3j;l~ruw7Cv3HWiPsfbO( z$>O^96&|v^6Oj(p+S@ePu*WM4b=E?$uk)47WciVZc2D~Wj01o+Fe91>O5|J?mgFN6 ziKf|ppe{o|GO-3;QolrLj3_{j-%_5G&*pi2mDLye+qNL7<*!08In@nn7zR!|#(1e~mS3_1O zf*}J3Q-5EYnCKK5RM_TS-iH2i5uZ&M@pAadnv{`u?f@ndklBJHzc4$!uXs1dAO<4m&C6?H#1m{`d;CSZNXLlE6`q z#j{$pE*5yZ)ZfY;PChMSxL&0DLNnvaj||jK%JezN*D}UQ%C?~iU$Vz*4e3Iq`_c@rrUBmbBb*)AtCULsW-ES9|T#O14`_O(1_ z6<5+n0HEapZ!^5GE@Yae=QYg96}K~d2##D|5l5Y9C$>~)OT z1BhvRts;q72+tJjw_Z;Q5-rwjI>Q8WhR2!-BB;e zyr(1iEJ2T95M01KiC6yRm1EQz6T5?SGtbLufF1(WMe7_z4{wb-^MX7(yT;hJ<(N`q z2;^dMWm9D7mW<3~J~t3F@jh`qpqeJhnl|72_fQUu;v zIW^Ru!@^l(faB3{AB3nq!J(Cg1uUZ$=OvyF!I)IP8<=DuzK$d@F1Wq+R%puj3Q84P?j9+&qu@`tOUkW~bi}qyOl(hCB zys?y^aa#G|eNFX7xU;!g(BJuqQWQTXye=3zotE9+(cC5s<$Juk?3^%GCTAfJUz5~r z^YcI7zo;)qt}V2_(J>uWuDxAoIu)K{x>=t~)jQKGvnor_UPYt6Pkz55yxnZ=sM%tb z<?Njq&K=Lqgdg%pcFCuq>d>hfHDn zsJKmAqnVod$_pAWzq69Yi{Wv6rlEwZkwlMEVULreh@DP~{A#}-MiyIbt|=u+r;08N zLusE>}V*$7%h-?@@^X zDa3q+VRhHB@FgeU0&kIoL3EB_S82@|w|shI(Z`VCvZ-8H^NILlc2^H112%DaIVv(Q z(vA~5Gi;N}-x%|**c+=V1+2^tSU|;NtZB(d^W-zJ z*ztg|JeUu#j2uSh<-E}2KR{ojM2ZioM5yCbB~3lv8RJfLX1=(?LC-(rVs{r@UA+cJ zU90{y8P8g_a7{frhvir+bIwAUhD1&77X)r(D!d*hj=BuB1MA*xvG0Jo96Xx=ZPW#6 zQwNf>j0I+P{WC z8@Wqrp%9YG==7kB)rR2H$nY$db@~sv_%+A_#Jo)+Iyx#8 z(v9JkjOUCxVX}B<5tm2ahEnIs$AU30M(z+B)~41q<=Ljww`!rwMv61m#rr3G_B25t zi^s?LSrfM(6FtfsVFwCdNEvDcHgI#_Qo4A#pC3zXK{k$xn11%922zm~dnes^@e{Nc zf=4INki34_#0GQ--^>^n*C>74x@Yn=(jcx{--XaOI8*C~&2VOuQemXQowihB$ELc3H^EKkm>)I+aQ7Muv zStAMWA@oM#n?+>{8GBiwKwrYHI9Kv!?zg6$9N#=(ib+rv!Y7T78r7(-o6)=nWFyoo zGw8o`O2=q$Scbba=|~CM;O-%H5EkpR9QhwzE)FdUWb;?}!MR&z{4Je#y{(pAts&}X zY-B`Z86W7le|}TSKoo@M*Gl_{f_K~HmQn@C|2l-Dh-fzn$HPTjCMCJk1^@G?K_|*? zTQ`o$%LX0VwtCVoxT>;({K;7HYfeXT1R2{m?Bl{A5Fg!<0KvLrHLC|j$x3U_IC~qe zAIX>_Aen_Jsh#BBwxkrD{tkt+GOF~rfw@n>76moUsMm!_dK15aaMWXI6C79BnF+a4 zyvP^1e9@I8{HQnm9*J{TW^)Z4+h;{3_s*m{ID-e;J-L`r9A&|($B#UYt2L@@Krl5r zEk84+=TI^UzA8bwU>metniHIucbWlAhm=NxQHvIK*F1XXnm@0ZWTgvv=GW&_X^Oed zBw^@wL#SLkXC^D+^05|#1X{@RW(hKl_ZN*(++XG;PqWn2b!{Pp704YGi|t)PTb~| z4!tOMWKx@SzCaGSZ%ZuV`Q;_6+ZvU<1}}3Qn}v{w<;Z}jBO;Re3Fo0I%(AT1+5EeN zEuzSiT&$UPh=Ah#_pXRK{A3%sT8d%_WwR2rvAvoGUCcKf7*G?MLfi%7xkqHHgQVj5 zjF4Ynq9rr)33F!1bU}~`eBUqEoSc+Yf<<&Cbj6U!C0|Hxv~W=1^Jv#sXeSm(`s#jH zp0FuWB|We_)k3IEp1(%>pf`vi&dWZnpPhv&Ja_5tBWP*-Nc8~mwlp6pk>7$FzXm~b zWR5Bb??*Lu&+qmQ>C9P`7m6FkDa&weW_b6E?mVWALcun_&s39v`P~7%q7JhvzV^fO zz@$??KWK;*xb~hnr3?uM-Yn9GysG|?d3JIqnxtNiQZ1aQzR?W7;}r{g8D!-| zOXydnE|KlwONT(c75S5n*!)GynFDkI-&9&x&oMxfCkH&`rJy1-iReF|oC$k_gkbtI zG8S@h4%FE*0!xxwSWWYBz4=jgWIZm(vwGU)iLXGs&C9miBkh`Lji>O++x8+ep{|&V z#PXH2GN}SLH@S>S)^}&^DsnH28@sngArqZ@pW0$3&*POiWEJw(&fP*|5z?UcKU5|+ zbN71qBP8&mutuM27-9u(!gLc~TZxF%81q*1)EK`1F6t~#JBXc7UQuN|HYXz3<44YR z6>em%#;;@crv~Bl^kVaOPdw2x~jgLyy3cSB>p`?u)JxOs0VNI`3SF~fqH=| z3ewVQhG+IVZj}TJ{~QVB%ceB+A+?XL#o4L}o-taNJ)_sgD$U01DH1LdO%Td`VwZ4F zd{8Mhht|34cOABIbJ%spM(Qt{*%FRQ;5Bvg7^K!|&HMFY3`#WWN(!Z!biHYFox9Y? zOMe$c$`e3^MPz+u#+u{N{!sSnyeBtrKtZL#S0=2*UsQ`eGGnKv*H#j$Xpv)szN!_1 z=eJVd_sZ!la<=JO9lFra%&2S+s7GqNkWivFTTTE`xqqyW1^1W2?hum6%y|AFMpl)q zSg(?vKvQsls)>^`D9WbRE_*T;7oywsiHbrBdwM0Zt7+ssV3FCJfSC4kzoG9v>Px?& z2%!3|Qbm~y%|;e0SWyLfF6>hARdf_t7vLi9*0Zn`w<4mXT@!}C(C6w z(e(~yfhX3DoIVfDh##mCJANIkpVS5P?G)Vd#9uaf6 zAK8$#;Fu-opfq{~@Cw?Z67v#Sd|>J^&dMpDjADBe$JP~qu-ehX6udmIU~}iGsTh4f z-)n>Fe8ad!lR<}=zT>?Ka~VdoLBtpHSval1#5X!;XJMO7M%+&=4zO;J$1qqAL6-!G(_4D6%QkVK3 zfxw&~0A(J2@S=>dp!J{WuKNDDtGLtX8F8N{cYU)yN_+n)GhyDtJSFcln7?02Ab7}& zWEZUlAb^S<8|s;8|2;^tWa)KqMkKaK%lTGBd+%m4I=aR;wDhjf>U@^I8sx7zoi>Wk z1kqrk()0-?y#nhbQr~UWyR`nABwGph=k=Ho2dKsP6X$rtNZZ!Z4iK|^x8xC7nfk@Gc6qA)h)#xe{DnBZu(kQkEI4!JuBtn!J)~ZbAlbPg;hu^*>mx?PQ_chL z2>_WaXU$@Fr&rYRSQpU1j;!<6a#z|UD~(K&J*tSX+m0Pw3YDX@{e6=j8t$sl!!u)| z8#I-s;Tiu4509(?tq*YniOJ-Qqsln6a2U^`lymEABl&surx?z1aaUYoFt0|;*<>JB zPYCs#t{ay{1vvrP;AKs9R$Knwq<3<<9r@tPn9^8FegzUM^eDUZf@I9EgXk@@V}ALD z%X-)c)jIXf8ibN{;W)ov4GxLPFxuKE3BO~MMt})epB4N?-73S-eCl5Y#Wx88i zonth~yHlLp!pe;$0;V&Y363FZoh0GWYOLWTVa7CX(;j+O9?;d)cEQ#qi0Ef}ujH z?AZN+Js8n?SM4b9#z@y2&tC^LWx++~uDruyHkN4X2KA7L{-Bt~tq|)@_*VDbl{?AGZ0!CTdLs#ojzUoXP#nq2PmdDTt$VPC&i%G49lG zlGs@>@hxzCDWli{eHOW|@ROhD>Ci?5rB$EG$KGsPh{16qoH#F>O;I0gofje(N1Vf7 z0dGC$?nycvy58aex0cnA&`mJVumZRy2p+O-Q5m)Xd+CU2*PuC zuuwbuoL^bUr7zGW6sh&D`XYW+Ufj}Ay4gq++Pya67j*A1GCwDyd=p?^-2*)ot5zoO zDC-p@&9I#rv%lg2rOJwu=K0}TsdwglbcbcWcKK4T?fVIeaP@vI9!L^zA%CGcdx;>@ z*;$WZfg_ndtZ1HQtqWh1h|y%AX07|FWukW$S3^ z+~$pSrl-=cZ`D{5kFD)}n@{5_0Cq(MjR*Hy)TCeAD~UnX?(OE<`eWchh3>;45h3tM8BXlP+m3$LwO zCY8QS5~e0j*@_%a#MlQR5%46d7V7j1FDM`8%k_R)O^K}nmb`tC8pt#uXAR;ZV2wjf zap{YL&Mg|KABAup8GiZF$(_cbtsqyNXhfF)3UTW4nDQdD_LhroNmia=WAp4th?#x= zS*sqN@Cd2iuvJTh?N_(`a2D}mRf{bC>JfK4K&#VGbx|)%Z7V4QV}P0?`Zg+&Ea|{fY63-GN$P;VEQ)}jjZFteg0vyp>btUFyiXp*3X`gM;iVe|z5xNC%N+l^%G0s^TF=@~ z&@Z6N$&pq47ydb@qC1lmX}7!+d(N0sAM+?Wp58ZHQyqS}(O_QsVr97%$*)n2&Dy{s zD`OePPFk%=%Y#FPkP*Xs&v@9b>@9X7dOK;~Lpu4hkr$WC?Vz$ZO8+@xL+?l$nJ+Mp zsgG@*!K&aCZL_&Gc+U34DC;)*oF-r9CUko8>HeVpLZ>$-o~<=7o76xuN8Tn~*)2P_ zH$-Glf^xXr08U2z3vRpkK004hF1ap`7<#HlzrdSgIkJXz8mf4FX8S!Z$+?mr6x(W6ZP&?OGNT|BaLXgCVfXA&w9kA4WLm+ zGo8~qV+84j<>d2oL1*DwVB#8DxJXO9h(&_B3h*NbIqr$`gp<(ZESjsls<)py($D47lqHXN7pu8d9r~6EYQ!RQB z3s2M)9jd%P2kdktk4rFl4aP*=RDqj=P=!}? zU_?`eGU&M|XMc~YwOoU%SOO%64s0%0@UxjWI2}9){v?kYM&dOh)3yejK8xOlRz=g& zf8b5i%g*0F(&`=BFn_~Txm+O|=1ADpcbu!&^h_#nrRC5`xFc3}M)6!_q<{K^{!IE` zH395HFwhI_Pa*KHs|h^>*arOTeUF3!Ir08@>naLBIC8+hZG9Nld!PvX??99Cd*Jgw zT-9(O-+yrbsX6`_=;`4=_CH+MZ!THWVK|WGuWt_{js)`kU000+8X^3#%3)CP$gzaKgNUmcjF4-jAQKXm?-O8@VE qKu>L=jK81ybD{i~j(kcqkod(vxe<|mhXG{2k3AFs03ihUd-gx0B^Eyb diff --git a/openpype/hosts/aftereffects/api/extension/jsx/hostscript.jsx b/openpype/hosts/aftereffects/api/extension/jsx/hostscript.jsx index cdaa2e3b83..9b211207de 100644 --- a/openpype/hosts/aftereffects/api/extension/jsx/hostscript.jsx +++ b/openpype/hosts/aftereffects/api/extension/jsx/hostscript.jsx @@ -395,13 +395,84 @@ function saveAs(path){ app.project.save(fp = new File(path)); } +function getRenderInfo(comp_id){ + /*** + Get info from render queue. + Currently pulls only file name to parse extension and + if it is sequence in Python + Args: + comp_id (int): id of composition + Return: + (list) [{file_name:"xx.png", width:00, height:00}] + **/ + var item = app.project.itemByID(comp_id); + if (!item){ + return _prepareError("Composition with '" + comp_id + "' wasn't found! Recreate publishable instance(s)") + } + + var comp_name = item.name; + var output_metadata = [] + try{ + // render_item.duplicate() should create new item on renderQueue + // BUT it works only sometimes, there are some weird synchronization issue + // this method will be called always before render, so prepare items here + // for render to spare the hassle + for (i = 1; i <= app.project.renderQueue.numItems; ++i){ + var render_item = app.project.renderQueue.item(i); + if (render_item.comp.id != comp_id){ + continue; + } + + if (render_item.status == RQItemStatus.DONE){ + render_item.duplicate(); // create new, cannot change status if DONE + render_item.remove(); // remove existing to limit duplications + continue; + } + } + + // properly validate as `numItems` won't change magically + var comp_id_count = 0; + for (i = 1; i <= app.project.renderQueue.numItems; ++i){ + var render_item = app.project.renderQueue.item(i); + if (render_item.comp.id != comp_id){ + continue; + } + comp_id_count += 1; + var item = render_item.outputModule(1); + + for (j = 1; j<= render_item.numOutputModules; ++j){ + var file_url = item.file.toString(); + output_metadata.push( + JSON.stringify({ + "file_name": file_url, + "width": render_item.comp.width, + "height": render_item.comp.height + }) + ); + } + } + } catch (error) { + return _prepareError("There is no render queue, create one"); + } + + if (comp_id_count > 1){ + return _prepareError("There cannot be more items in Render Queue for '" + comp_name + "'!") + } + + if (comp_id_count == 0){ + return _prepareError("There is no item in Render Queue for '" + comp_name + "'! Add composition to Render Queue.") + } + + return '[' + output_metadata.join() + ']'; +} + function getAudioUrlForComp(comp_id){ /** * Searches composition for audio layer - * + * * Only single AVLayer is expected! * Used for collecting Audio - * + * * Args: * comp_id (int): id of composition * Return: @@ -429,7 +500,7 @@ function addItemAsLayerToComp(comp_id, item_id, found_comp){ /** * Adds already imported FootageItem ('item_id') as a new * layer to composition ('comp_id'). - * + * * Args: * comp_id (int): id of target composition * item_id (int): FootageItem.id @@ -452,17 +523,17 @@ function addItemAsLayerToComp(comp_id, item_id, found_comp){ function importBackground(comp_id, composition_name, files_to_import){ /** * Imports backgrounds images to existing or new composition. - * + * * If comp_id is not provided, new composition is created, basic * values (width, heights, frameRatio) takes from first imported * image. - * + * * Args: * comp_id (int): id of existing composition (null if new) - * composition_name (str): used when new composition + * composition_name (str): used when new composition * files_to_import (list): list of absolute paths to import and * add as layers - * + * * Returns: * (str): json representation (id, name, members) */ @@ -484,7 +555,7 @@ function importBackground(comp_id, composition_name, files_to_import){ } } } - + if (files_to_import){ for (i = 0; i < files_to_import.length; ++i){ item = _importItem(files_to_import[i]); @@ -496,8 +567,8 @@ function importBackground(comp_id, composition_name, files_to_import){ if (!comp){ folder = app.project.items.addFolder(composition_name); imported_ids.push(folder.id); - comp = app.project.items.addComp(composition_name, item.width, - item.height, item.pixelAspect, + comp = app.project.items.addComp(composition_name, item.width, + item.height, item.pixelAspect, 1, 26.7); // hardcode defaults imported_ids.push(comp.id); comp.parentFolder = folder; @@ -506,7 +577,7 @@ function importBackground(comp_id, composition_name, files_to_import){ item.parentFolder = folder; addItemAsLayerToComp(comp.id, item.id, comp); - } + } } var item = {"name": comp.name, "id": folder.id, @@ -517,19 +588,19 @@ function importBackground(comp_id, composition_name, files_to_import){ function reloadBackground(comp_id, composition_name, files_to_import){ /** * Reloads existing composition. - * + * * It deletes complete composition with encompassing folder, recreates * from scratch via 'importBackground' functionality. - * + * * Args: * comp_id (int): id of existing composition (null if new) - * composition_name (str): used when new composition + * composition_name (str): used when new composition * files_to_import (list): list of absolute paths to import and * add as layers - * + * * Returns: * (str): json representation (id, name, members) - * + * */ var imported_ids = []; // keep track of members of composition comp = app.project.itemByID(comp_id); @@ -592,7 +663,7 @@ function reloadBackground(comp_id, composition_name, files_to_import){ function _get_file_name(file_url){ /** * Returns file name without extension from 'file_url' - * + * * Args: * file_url (str): full absolute url * Returns: @@ -607,7 +678,7 @@ function _delete_obsolete_items(folder, new_filenames){ /*** * Goes through 'folder' and removes layers not in new * background - * + * * Args: * folder (FolderItem) * new_filenames (array): list of layer names in new bg @@ -632,14 +703,14 @@ function _delete_obsolete_items(folder, new_filenames){ function _importItem(file_url){ /** * Imports 'file_url' as new FootageItem - * + * * Args: * file_url (str): file url with content * Returns: * (FootageItem) */ file_name = _get_file_name(file_url); - + //importFile prepared previously to return json item_json = importFile(file_url, file_name, JSON.stringify({"ImportAsType":"FOOTAGE"})); item_json = JSON.parse(item_json); @@ -661,71 +732,6 @@ function isFileSequence (item){ return false; } -function getRenderInfo(comp_id){ - /*** - Get info from render queue. - Currently pulls only file name to parse extension and - if it is sequence in Python - **/ - var item = app.project.itemByID(comp_id); - if (!item){ - return _prepareError("Composition with '" + comp_id + "' wasn't found! Recreate publishable instance(s)") - } - - var comp_name = item.name; - try{ - // render_item.duplicate() should create new item on renderQueue - // BUT it works only sometimes, there are some weird synchronization issue - // this method will be called always before render, so prepare items here - // for render to spare the hassle - for (i = 1; i <= app.project.renderQueue.numItems; ++i){ - var render_item = app.project.renderQueue.item(i); - if (render_item.comp.id != comp_id){ - continue; - } - - if (render_item.status == RQItemStatus.DONE){ - render_item.duplicate(); // create new, cannot change status if DONE - render_item.remove(); // remove existing to limit duplications - continue; - } - } - - // properly validate as `numItems` won't change magically - var comp_id_count = 0; - for (i = 1; i <= app.project.renderQueue.numItems; ++i){ - var render_item = app.project.renderQueue.item(i); - if (render_item.comp.id != comp_id){ - continue; - } - comp_id_count += 1; - var item = render_item.outputModule(1); - } - } catch (error) { - return _prepareError("There is no render queue, create one"); - } - - if (comp_id_count > 1){ - return _prepareError("There cannot be more items in Render Queue for '" + comp_name + "'!") - } - - if (comp_id_count == 0){ - return _prepareError("There is no item in Render Queue for '" + comp_name + "'! Add composition to Render Queue.") - } - - if (render_item.numOutputModules !=1){ - return _prepareError("There must be just 1 Output Module in Render Queue for '" + comp_name + "'! Keep only correct one.") - } - - var file_url = item.file.toString(); - - return JSON.stringify({ - "file_name": file_url, - "width": render_item.comp.width, - "height": render_item.comp.height - }) -} - function render(target_folder, comp_id){ var out_dir = new Folder(target_folder); var out_dir = out_dir.fsName; diff --git a/openpype/hosts/aftereffects/api/ws_stub.py b/openpype/hosts/aftereffects/api/ws_stub.py index 32125a7d99..e5d6d9ed89 100644 --- a/openpype/hosts/aftereffects/api/ws_stub.py +++ b/openpype/hosts/aftereffects/api/ws_stub.py @@ -422,15 +422,14 @@ class AfterEffectsServerStub(): """ Get render queue info for render purposes Returns: - (AEItem): with 'file_name' field + (list) of (AEItem): with 'file_name' field """ res = self.websocketserver.call(self.client.call ('AfterEffects.get_render_info', comp_id=comp_id)) records = self._to_records(self._handle_return(res)) - if records: - return records.pop() + return records def get_audio_url(self, item_id): """ Get audio layer absolute url for comp diff --git a/openpype/hosts/aftereffects/plugins/publish/collect_render.py b/openpype/hosts/aftereffects/plugins/publish/collect_render.py index 2b37c1f101..6153a426cf 100644 --- a/openpype/hosts/aftereffects/plugins/publish/collect_render.py +++ b/openpype/hosts/aftereffects/plugins/publish/collect_render.py @@ -22,7 +22,7 @@ class AERenderInstance(RenderInstance): stagingDir = attr.ib(default=None) app_version = attr.ib(default=None) publish_attributes = attr.ib(default={}) - file_name = attr.ib(default=None) + file_names = attr.ib(default=[]) class CollectAERender(publish.AbstractCollectRender): @@ -86,6 +86,7 @@ class CollectAERender(publish.AbstractCollectRender): render_q = CollectAERender.get_stub().get_render_info(comp_id) if not render_q: raise ValueError("No file extension set in Render Queue") + render_item = render_q[0] subset_name = inst.data["subset"] instance = AERenderInstance( @@ -102,8 +103,8 @@ class CollectAERender(publish.AbstractCollectRender): setMembers='', publish=True, name=subset_name, - resolutionWidth=render_q.width, - resolutionHeight=render_q.height, + resolutionWidth=render_item.width, + resolutionHeight=render_item.height, pixelAspect=1, tileRendering=False, tilesX=0, @@ -114,7 +115,7 @@ class CollectAERender(publish.AbstractCollectRender): fps=fps, app_version=app_version, publish_attributes=inst.data.get("publish_attributes", {}), - file_name=render_q.file_name + file_names=[item.file_name for item in render_q] ) comp = compositions_by_id.get(comp_id) @@ -162,28 +163,30 @@ class CollectAERender(publish.AbstractCollectRender): start = render_instance.frameStart end = render_instance.frameEnd - _, ext = os.path.splitext(os.path.basename(render_instance.file_name)) - base_dir = self._get_output_dir(render_instance) expected_files = [] - if "#" not in render_instance.file_name: # single frame (mov)W - path = os.path.join(base_dir, "{}_{}_{}.{}".format( - render_instance.asset, - render_instance.subset, - "v{:03d}".format(render_instance.version), - ext.replace('.', '') - )) - expected_files.append(path) - else: - for frame in range(start, end + 1): - path = os.path.join(base_dir, "{}_{}_{}.{}.{}".format( + for file_name in render_instance.file_names: + _, ext = os.path.splitext(os.path.basename(file_name)) + ext = ext.replace('.', '') + version_str = "v{:03d}".format(render_instance.version) + if "#" not in file_name: # single frame (mov)W + path = os.path.join(base_dir, "{}_{}_{}.{}".format( render_instance.asset, render_instance.subset, - "v{:03d}".format(render_instance.version), - str(frame).zfill(self.padding_width), - ext.replace('.', '') + version_str, + ext )) expected_files.append(path) + else: + for frame in range(start, end + 1): + path = os.path.join(base_dir, "{}_{}_{}.{}.{}".format( + render_instance.asset, + render_instance.subset, + version_str, + str(frame).zfill(self.padding_width), + ext + )) + expected_files.append(path) return expected_files def _get_output_dir(self, render_instance): diff --git a/openpype/hosts/aftereffects/plugins/publish/extract_local_render.py b/openpype/hosts/aftereffects/plugins/publish/extract_local_render.py index 309855f1c7..d535329eb4 100644 --- a/openpype/hosts/aftereffects/plugins/publish/extract_local_render.py +++ b/openpype/hosts/aftereffects/plugins/publish/extract_local_render.py @@ -24,46 +24,52 @@ class ExtractLocalRender(publish.Extractor): self.log.debug("staging_dir::{}".format(staging_dir)) # pull file name collected value from Render Queue Output module - if not instance.data["file_name"]: + if not instance.data["file_names"]: raise ValueError("No file extension set in Render Queue") comp_id = instance.data['comp_id'] stub.render(staging_dir, comp_id) - _, ext = os.path.splitext(os.path.basename(instance.data["file_name"])) - ext = ext[1:] + representations = [] + for file_name in instance.data["file_names"]: + _, ext = os.path.splitext(os.path.basename(file_name)) + ext = ext[1:] - first_file_path = None - files = [] - for file_name in os.listdir(staging_dir): - if not file_name.endswith(ext): - continue + first_file_path = None + files = [] + for found_file_name in os.listdir(staging_dir): + if not found_file_name.endswith(ext): + continue - files.append(file_name) - if first_file_path is None: - first_file_path = os.path.join(staging_dir, - file_name) + files.append(found_file_name) + if first_file_path is None: + first_file_path = os.path.join(staging_dir, + found_file_name) - if not files: - self.log.info("no files") - return + if not files: + self.log.info("no files") + return - resulting_files = files - if len(files) == 1: - resulting_files = files[0] + # single file cannot be wrapped in array + resulting_files = files + if len(files) == 1: + resulting_files = files[0] - repre_data = { - "frameStart": instance.data["frameStart"], - "frameEnd": instance.data["frameEnd"], - "name": ext, - "ext": ext, - "files": resulting_files, - "stagingDir": staging_dir - } - if instance.data["review"]: - repre_data["tags"] = ["review"] + repre_data = { + "frameStart": instance.data["frameStart"], + "frameEnd": instance.data["frameEnd"], + "name": ext, + "ext": ext, + "files": resulting_files, + "stagingDir": staging_dir + } + first_repre = not representations + if instance.data["review"] and first_repre: + repre_data["tags"] = ["review"] - instance.data["representations"] = [repre_data] + representations.append(repre_data) + + instance.data["representations"] = representations ffmpeg_path = get_ffmpeg_tool_path("ffmpeg") # Generate thumbnail. From a9249c1b75f8f8174697bd207bf908132283b825 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 15 Nov 2022 16:17:16 +0100 Subject: [PATCH 121/243] OP-4361 - remove limitation on single output module --- website/docs/artist_hosts_aftereffects.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/website/docs/artist_hosts_aftereffects.md b/website/docs/artist_hosts_aftereffects.md index ede2f7e86e..9f10a4f08d 100644 --- a/website/docs/artist_hosts_aftereffects.md +++ b/website/docs/artist_hosts_aftereffects.md @@ -68,7 +68,8 @@ Publisher allows publishing into different context, just click on any instance, #### RenderQueue AE's Render Queue is required for publishing locally or on a farm. Artist needs to configure expected result format (extension, resolution) in the Render Queue in an Output module. -Currently its expected to have only single render item and single output module per composition in the Render Queue. +Currently its expected to have only single render item per composition in the Render Queue. + AE might throw some warning windows during publishing locally, so please pay attention to them in a case publishing seems to be stuck in a `Extract Local Render`. From 9a503fce6a025d9918f1653cb531dcd56f4d2087 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 15 Nov 2022 16:49:40 +0100 Subject: [PATCH 122/243] OP-4361 - fix wrong usage of regex --- openpype/hosts/aftereffects/plugins/create/create_render.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/aftereffects/plugins/create/create_render.py b/openpype/hosts/aftereffects/plugins/create/create_render.py index 5684657bf9..11864dd68c 100644 --- a/openpype/hosts/aftereffects/plugins/create/create_render.py +++ b/openpype/hosts/aftereffects/plugins/create/create_render.py @@ -92,8 +92,8 @@ class RenderCreator(Creator): data["composition_name"] = composition_name else: subset_name = subset_name_from_ui - re.sub(subset_name, r"{composition}", '', - flags=re.IGNORECASE) + subset_name = re.sub(r"\{composition\}", '', subset_name, + flags=re.IGNORECASE) for inst in self.create_context.instances: if subset_name == inst.subset_name: From f18efd29b2aebe89f3cc8dbbbf03dc9bdfdff5b2 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 15 Nov 2022 17:36:30 +0100 Subject: [PATCH 123/243] OP-4394 - fix - lowercase extension everywhere Without it it would be stored in DB uppercased and final name would also be uppercased. --- .../webpublisher/plugins/publish/collect_published_files.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py b/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py index 40f4da9403..265e78a6c7 100644 --- a/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py +++ b/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py @@ -181,6 +181,7 @@ class CollectPublishedFiles(pyblish.api.ContextPlugin): def _get_single_repre(self, task_dir, files, tags): _, ext = os.path.splitext(files[0]) + ext = ext.lower() repre_data = { "name": ext[1:], "ext": ext[1:], @@ -200,6 +201,7 @@ class CollectPublishedFiles(pyblish.api.ContextPlugin): frame_start = list(collections[0].indexes)[0] frame_end = list(collections[0].indexes)[-1] ext = collections[0].tail + ext = ext.lower() repre_data = { "frameStart": frame_start, "frameEnd": frame_end, From fbd7531a311d1a0287c45babb12a7b029cd50a7d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 15 Nov 2022 18:42:46 +0100 Subject: [PATCH 124/243] change label of stopped publishing --- openpype/tools/publisher/widgets/validations_widget.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/publisher/widgets/validations_widget.py b/openpype/tools/publisher/widgets/validations_widget.py index 8c483e8088..935a12bc73 100644 --- a/openpype/tools/publisher/widgets/validations_widget.py +++ b/openpype/tools/publisher/widgets/validations_widget.py @@ -511,7 +511,7 @@ class ValidationsWidget(QtWidgets.QFrame): ) # After success publishing publish_started_widget = ValidationArtistMessage( - "Publishing went smoothly", self + "So far so good", self ) # After success publishing publish_stop_ok_widget = ValidationArtistMessage( From 4dd276fc4682f379bc1eaf2b088c24252920eeef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Nov 2022 23:23:26 +0000 Subject: [PATCH 125/243] Bump loader-utils from 1.4.1 to 1.4.2 in /website Bumps [loader-utils](https://github.com/webpack/loader-utils) from 1.4.1 to 1.4.2. - [Release notes](https://github.com/webpack/loader-utils/releases) - [Changelog](https://github.com/webpack/loader-utils/blob/v1.4.2/CHANGELOG.md) - [Commits](https://github.com/webpack/loader-utils/compare/v1.4.1...v1.4.2) --- updated-dependencies: - dependency-name: loader-utils dependency-type: indirect ... Signed-off-by: dependabot[bot] --- website/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website/yarn.lock b/website/yarn.lock index 177a4a3802..220a489dfa 100644 --- a/website/yarn.lock +++ b/website/yarn.lock @@ -4812,9 +4812,9 @@ loader-runner@^4.2.0: integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw== loader-utils@^1.4.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.1.tgz#278ad7006660bccc4d2c0c1578e17c5c78d5c0e0" - integrity sha512-1Qo97Y2oKaU+Ro2xnDMR26g1BwMT29jNbem1EvcujW2jqt+j5COXyscjM7bLQkM9HaxI7pkWeW7gnI072yMI9Q== + version "1.4.2" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.2.tgz#29a957f3a63973883eb684f10ffd3d151fec01a3" + integrity sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg== dependencies: big.js "^5.2.2" emojis-list "^3.0.0" From ae51001ee89812ff0c34a1175318983ad708380b Mon Sep 17 00:00:00 2001 From: OpenPype Date: Wed, 16 Nov 2022 03:40:10 +0000 Subject: [PATCH 126/243] [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 1953d0d6a5..268f33083a 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.14.7-nightly.4" +__version__ = "3.14.7-nightly.5" From 0645089ad61f0a893ce717a5cf4574ca81cd8ef2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 16 Nov 2022 10:38:08 +0100 Subject: [PATCH 127/243] size of button is fully defined by style --- openpype/style/style.css | 4 ++++ openpype/tools/publisher/window.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/style/style.css b/openpype/style/style.css index 887c044dae..a7a48cdb9d 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -1126,6 +1126,10 @@ ValidationArtistMessage QLabel { background: transparent; } +CreateNextPageOverlay { + font-size: 32pt; +} + /* Settings - NOT USED YET - we need to define font family for settings UI */ diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index 281c7ad2a1..febf55b919 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -716,7 +716,7 @@ class PublisherWindow(QtWidgets.QDialog): def _update_create_overlay_size(self): metrics = self._create_overlay_button.fontMetrics() - size = int(metrics.height() * 3) + size = int(metrics.height()) end_pos_x = self.width() start_pos_x = end_pos_x - size From 20dacc342b5b4f5ff407fd616d0dc7818c551844 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 16 Nov 2022 10:40:22 +0100 Subject: [PATCH 128/243] change style of button --- openpype/tools/publisher/widgets/widgets.py | 17 +++++++++-------- openpype/tools/publisher/window.py | 17 ++++++++++++----- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index 71f476c4ef..ce3d91ce63 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -1832,23 +1832,24 @@ class CreateNextPageOverlay(QtWidgets.QWidget): rect = QtCore.QRect(self.rect()) rect_width = rect.width() rect_height = rect.height() + radius = rect_width * 0.2 - size = rect_width * 0.9 - - x_offset = (rect_width - size) * 0.5 - y_offset = (rect_height - size) * 0.5 + x_offset = 0 + y_offset = 0 if self._anim_value != 1.0: x_offset += rect_width - (rect_width * self._anim_value) - arrow_half_height = size * 0.2 - arrow_x_start = x_offset + (size * 0.4) + arrow_height = rect_height * 0.4 + arrow_half_height = arrow_height * 0.5 + arrow_x_start = x_offset + ((rect_width - arrow_half_height) * 0.5) arrow_x_end = arrow_x_start + arrow_half_height center_y = rect.center().y() painter.setBrush(self._bg_color) - painter.drawEllipse( + painter.drawRoundedRect( x_offset, y_offset, - size, size + rect_width + radius, rect_height, + radius, radius ) src_arrow_path = QtGui.QPainterPath() diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index febf55b919..de26630312 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -257,7 +257,9 @@ class PublisherWindow(QtWidgets.QDialog): publish_btn.clicked.connect(self._on_publish_clicked) publish_frame.details_page_requested.connect(self._go_to_details_tab) - create_overlay_button.clicked.connect(self._go_to_publish_tab) + create_overlay_button.clicked.connect( + self._on_create_overlay_button_click + ) controller.event_system.add_callback( "instances.refresh.finished", self._on_instances_refresh @@ -471,6 +473,10 @@ class PublisherWindow(QtWidgets.QDialog): self._help_dialog.width(), self._help_dialog.height() ) + def _on_create_overlay_button_click(self): + self._create_overlay_button.set_under_mouse(False) + self._go_to_publish_tab() + def _on_tab_change(self, old_tab, new_tab): if old_tab == "details": self._publish_details_widget.close_details_popup() @@ -716,19 +722,20 @@ class PublisherWindow(QtWidgets.QDialog): def _update_create_overlay_size(self): metrics = self._create_overlay_button.fontMetrics() - size = int(metrics.height()) + height = int(metrics.height()) + width = int(height * 0.7) end_pos_x = self.width() - start_pos_x = end_pos_x - size + start_pos_x = end_pos_x - width center = self._content_widget.parent().mapTo( self, self._content_widget.rect().center() ) - pos_y = center.y() - (size * 0.5) + pos_y = center.y() - (height * 0.5) self._create_overlay_button.setGeometry( start_pos_x, pos_y, - size, size + width, height ) def _update_create_overlay_visibility(self, global_pos=None): From b0e433e520feab015e90ddc6de4d8e25527f3066 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 16 Nov 2022 11:24:36 +0100 Subject: [PATCH 129/243] OP-4362 - remove unnecessary call to super --- openpype/hosts/aftereffects/plugins/create/create_render.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/openpype/hosts/aftereffects/plugins/create/create_render.py b/openpype/hosts/aftereffects/plugins/create/create_render.py index 11864dd68c..8d38288257 100644 --- a/openpype/hosts/aftereffects/plugins/create/create_render.py +++ b/openpype/hosts/aftereffects/plugins/create/create_render.py @@ -134,11 +134,7 @@ class RenderCreator(Creator): def get_dynamic_data(self, variant, task_name, asset_doc, project_name, host_name, instance): - dynamic_data = super(RenderCreator, self).get_dynamic_data( - variant, task_name, asset_doc, project_name, host_name, - instance - ) - + dynamic_data = {} if instance is not None: composition_name = instance.get("composition_name") if composition_name: From 91a4a06ab6e9f9e9a8c6378aeebe014fbe6c9a21 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 16 Nov 2022 11:32:26 +0100 Subject: [PATCH 130/243] change maximum number of frame start/end and clip in/out in anatomy settings --- .../schemas/schema_anatomy_attributes.json | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_attributes.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_attributes.json index a2a566da0e..3667c9d5d8 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_attributes.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_attributes.json @@ -16,22 +16,26 @@ { "type": "number", "key": "frameStart", - "label": "Frame Start" + "label": "Frame Start", + "maximum": 999999999 }, { "type": "number", "key": "frameEnd", - "label": "Frame End" + "label": "Frame End", + "maximum": 999999999 }, { "type": "number", "key": "clipIn", - "label": "Clip In" + "label": "Clip In", + "maximum": 999999999 }, { "type": "number", "key": "clipOut", - "label": "Clip Out" + "label": "Clip Out", + "maximum": 999999999 }, { "type": "number", From 33656d00550c64f50d9e42088c389a43315cd905 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 16 Nov 2022 11:32:43 +0100 Subject: [PATCH 131/243] project manager has higher max numbers --- openpype/tools/project_manager/project_manager/view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index cca892ef72..8d1fe54e83 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -28,7 +28,7 @@ class NameDef: class NumberDef: def __init__(self, minimum=None, maximum=None, decimals=None): self.minimum = 0 if minimum is None else minimum - self.maximum = 999999 if maximum is None else maximum + self.maximum = 999999999 if maximum is None else maximum self.decimals = 0 if decimals is None else decimals From 8c6abf1c8faeea4ff5ecafa6c0e5dbfb22ce06cb Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 16 Nov 2022 11:32:59 +0100 Subject: [PATCH 132/243] remove duplicated key --- openpype/tools/settings/settings/constants.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/tools/settings/settings/constants.py b/openpype/tools/settings/settings/constants.py index d98d18c8bf..23526e4de9 100644 --- a/openpype/tools/settings/settings/constants.py +++ b/openpype/tools/settings/settings/constants.py @@ -24,7 +24,6 @@ __all__ = ( "SETTINGS_PATH_KEY", "ROOT_KEY", - "SETTINGS_PATH_KEY", "VALUE_KEY", "SAVE_TIME_KEY", "PROJECT_NAME_KEY", From e11815b663d3910032fc6f2ec492df857ce91590 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 16 Nov 2022 14:41:35 +0100 Subject: [PATCH 133/243] OP-4394 - safer handling of Settings extensions Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../plugins/publish/collect_published_files.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py b/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py index 265e78a6c7..181f8b4ab7 100644 --- a/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py +++ b/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py @@ -247,11 +247,17 @@ class CollectPublishedFiles(pyblish.api.ContextPlugin): for config in families_config: if is_sequence != config["is_sequence"]: continue + extensions = config.get("extensions") or [] + lower_extensions = set() + for ext in extensions: + if ext: + ext = ext.lower() + if ext.startswith("."): + ext = ext[1:] + lower_extensions.add(ext) - lower_extensions = [ext.lower() - for ext in config.get("extensions", [''])] - if (extension.lower() in lower_extensions or - lower_extensions[0] == ''): # all extensions setting + # all extensions setting + if not lower_extensions or extension in lower_extensions: found_family = config["result_family"] break From ed7795061f946ca71e7c3b09977c68525e3cd24c Mon Sep 17 00:00:00 2001 From: OpenPype Date: Sat, 12 Nov 2022 03:44:20 +0000 Subject: [PATCH 134/243] [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 81b2925fb5..1953d0d6a5 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.14.7-nightly.3" +__version__ = "3.14.7-nightly.4" From f9732a8385a75384d91a424ac007285a082c9a2a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 10 Nov 2022 18:52:59 +0100 Subject: [PATCH 135/243] renamed 'CollectAvalonEntities' to 'CollectContextEntities' --- .../hosts/tvpaint/plugins/publish/collect_instance_frames.py | 2 +- openpype/hosts/tvpaint/plugins/publish/validate_marks.py | 2 +- .../plugins/publish/validate_tvpaint_workfile_data.py | 2 +- ...collect_avalon_entities.py => collect_context_entities.py} | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) rename openpype/plugins/publish/{collect_avalon_entities.py => collect_context_entities.py} (97%) diff --git a/openpype/hosts/tvpaint/plugins/publish/collect_instance_frames.py b/openpype/hosts/tvpaint/plugins/publish/collect_instance_frames.py index f291c363b8..d5b79758ad 100644 --- a/openpype/hosts/tvpaint/plugins/publish/collect_instance_frames.py +++ b/openpype/hosts/tvpaint/plugins/publish/collect_instance_frames.py @@ -6,7 +6,7 @@ class CollectOutputFrameRange(pyblish.api.ContextPlugin): When instances are collected context does not contain `frameStart` and `frameEnd` keys yet. They are collected in global plugin - `CollectAvalonEntities`. + `CollectContextEntities`. """ label = "Collect output frame range" order = pyblish.api.CollectorOrder diff --git a/openpype/hosts/tvpaint/plugins/publish/validate_marks.py b/openpype/hosts/tvpaint/plugins/publish/validate_marks.py index 12d50e17ff..0030b0fd1c 100644 --- a/openpype/hosts/tvpaint/plugins/publish/validate_marks.py +++ b/openpype/hosts/tvpaint/plugins/publish/validate_marks.py @@ -39,7 +39,7 @@ class ValidateMarks(pyblish.api.ContextPlugin): def get_expected_data(context): scene_mark_in = context.data["sceneMarkIn"] - # Data collected in `CollectAvalonEntities` + # Data collected in `CollectContextEntities` frame_end = context.data["frameEnd"] frame_start = context.data["frameStart"] handle_start = context.data["handleStart"] diff --git a/openpype/hosts/webpublisher/plugins/publish/validate_tvpaint_workfile_data.py b/openpype/hosts/webpublisher/plugins/publish/validate_tvpaint_workfile_data.py index a5e4868411..d8b7bb9078 100644 --- a/openpype/hosts/webpublisher/plugins/publish/validate_tvpaint_workfile_data.py +++ b/openpype/hosts/webpublisher/plugins/publish/validate_tvpaint_workfile_data.py @@ -13,7 +13,7 @@ class ValidateWorkfileData(pyblish.api.ContextPlugin): targets = ["tvpaint_worker"] def process(self, context): - # Data collected in `CollectAvalonEntities` + # Data collected in `CollectContextEntities` frame_start = context.data["frameStart"] frame_end = context.data["frameEnd"] handle_start = context.data["handleStart"] diff --git a/openpype/plugins/publish/collect_avalon_entities.py b/openpype/plugins/publish/collect_context_entities.py similarity index 97% rename from openpype/plugins/publish/collect_avalon_entities.py rename to openpype/plugins/publish/collect_context_entities.py index 3b05b6ae98..0a6072a820 100644 --- a/openpype/plugins/publish/collect_avalon_entities.py +++ b/openpype/plugins/publish/collect_context_entities.py @@ -16,11 +16,11 @@ from openpype.client import get_project, get_asset_by_name from openpype.pipeline import legacy_io, KnownPublishError -class CollectAvalonEntities(pyblish.api.ContextPlugin): +class CollectContextEntities(pyblish.api.ContextPlugin): """Collect Anatomy into Context.""" order = pyblish.api.CollectorOrder - 0.1 - label = "Collect Avalon Entities" + label = "Collect Context Entities" def process(self, context): legacy_io.install() From 910b7d7120be3982548bf8913410cbb71669f0e9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 10 Nov 2022 18:53:34 +0100 Subject: [PATCH 136/243] get "asset" and "task" from context --- openpype/plugins/publish/collect_context_entities.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/openpype/plugins/publish/collect_context_entities.py b/openpype/plugins/publish/collect_context_entities.py index 0a6072a820..31fbeb5dbd 100644 --- a/openpype/plugins/publish/collect_context_entities.py +++ b/openpype/plugins/publish/collect_context_entities.py @@ -3,6 +3,8 @@ Requires: session -> AVALON_ASSET context -> projectName + context -> asset + context -> task Provides: context -> projectEntity - Project document from database. @@ -13,20 +15,19 @@ Provides: import pyblish.api from openpype.client import get_project, get_asset_by_name -from openpype.pipeline import legacy_io, KnownPublishError +from openpype.pipeline import KnownPublishError class CollectContextEntities(pyblish.api.ContextPlugin): - """Collect Anatomy into Context.""" + """Collect entities into Context.""" order = pyblish.api.CollectorOrder - 0.1 label = "Collect Context Entities" def process(self, context): - legacy_io.install() project_name = context.data["projectName"] - asset_name = legacy_io.Session["AVALON_ASSET"] - task_name = legacy_io.Session["AVALON_TASK"] + asset_name = context.data["asset"] + task_name = context.data["task"] project_entity = get_project(project_name) if not project_entity: From 017720d754d5912d4df5a233168c64f3caccd56f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 10 Nov 2022 19:00:27 +0100 Subject: [PATCH 137/243] get "task" from context in anatomy context data --- openpype/plugins/publish/collect_anatomy_context_data.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/plugins/publish/collect_anatomy_context_data.py b/openpype/plugins/publish/collect_anatomy_context_data.py index 8433816908..55ce8e06f4 100644 --- a/openpype/plugins/publish/collect_anatomy_context_data.py +++ b/openpype/plugins/publish/collect_anatomy_context_data.py @@ -15,7 +15,6 @@ Provides: import json import pyblish.api -from openpype.pipeline import legacy_io from openpype.pipeline.template_data import get_template_data @@ -53,7 +52,7 @@ class CollectAnatomyContextData(pyblish.api.ContextPlugin): asset_entity = context.data.get("assetEntity") task_name = None if asset_entity: - task_name = legacy_io.Session["AVALON_TASK"] + task_name = context.data["task"] anatomy_data = get_template_data( project_entity, asset_entity, task_name, host_name, system_settings From db6bfcb1ee978a3aa77ed00b1dbc2462715a187b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Jul 2022 03:59:40 +0000 Subject: [PATCH 138/243] Bump terser from 5.10.0 to 5.14.2 in /website Bumps [terser](https://github.com/terser/terser) from 5.10.0 to 5.14.2. - [Release notes](https://github.com/terser/terser/releases) - [Changelog](https://github.com/terser/terser/blob/master/CHANGELOG.md) - [Commits](https://github.com/terser/terser/commits) --- updated-dependencies: - dependency-name: terser dependency-type: indirect ... Signed-off-by: dependabot[bot] --- website/yarn.lock | 64 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/website/yarn.lock b/website/yarn.lock index 7af15e9145..177a4a3802 100644 --- a/website/yarn.lock +++ b/website/yarn.lock @@ -1543,15 +1543,37 @@ dependencies: "@hapi/hoek" "^9.0.0" +"@jridgewell/gen-mapping@^0.3.0": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" + integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + "@jridgewell/resolve-uri@^3.0.3": - version "3.0.5" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz#68eb521368db76d040a6315cdb24bf2483037b9c" - integrity sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew== + version "3.1.0" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" + integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== + +"@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== + +"@jridgewell/source-map@^0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.2.tgz#f45351aaed4527a298512ec72f81040c998580fb" + integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.0" + "@jridgewell/trace-mapping" "^0.3.9" "@jridgewell/sourcemap-codec@^1.4.10": - version "1.4.11" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz#771a1d8d744eeb71b6adb35808e1a6c7b9b8c8ec" - integrity sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg== + version "1.4.14" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" + integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== "@jridgewell/trace-mapping@^0.3.0": version "0.3.4" @@ -1561,6 +1583,14 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" +"@jridgewell/trace-mapping@^0.3.9": + version "0.3.14" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz#b231a081d8f66796e475ad588a1ef473112701ed" + integrity sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@mdx-js/mdx@1.6.22", "@mdx-js/mdx@^1.6.21": version "1.6.22" resolved "https://registry.yarnpkg.com/@mdx-js/mdx/-/mdx-1.6.22.tgz#8a723157bf90e78f17dc0f27995398e6c731f1ba" @@ -2140,10 +2170,10 @@ acorn@^6.1.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6" integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ== -acorn@^8.0.4, acorn@^8.4.1: - version "8.7.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" - integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ== +acorn@^8.0.4, acorn@^8.4.1, acorn@^8.5.0: + version "8.7.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" + integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== address@^1.0.1, address@^1.1.2: version "1.1.2" @@ -6843,11 +6873,6 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -source-map@~0.7.2: - version "0.7.3" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" - integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== - sourcemap-codec@^1.4.4: version "1.4.8" resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" @@ -7053,12 +7078,13 @@ terser-webpack-plugin@^5.1.3, terser-webpack-plugin@^5.2.4: terser "^5.7.2" terser@^5.10.0, terser@^5.7.2: - version "5.10.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.10.0.tgz#b86390809c0389105eb0a0b62397563096ddafcc" - integrity sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA== + version "5.14.2" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.14.2.tgz#9ac9f22b06994d736174f4091aa368db896f1c10" + integrity sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA== dependencies: + "@jridgewell/source-map" "^0.3.2" + acorn "^8.5.0" commander "^2.20.0" - source-map "~0.7.2" source-map-support "~0.5.20" text-table@^0.2.0: From ff760342c7719238e9ae06f9bb23c8747cabb615 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 14 Nov 2022 18:27:11 +0100 Subject: [PATCH 139/243] ignore case sensitivity of extension in files widget --- openpype/lib/attribute_definitions.py | 7 +++++++ openpype/tools/attribute_defs/files_widget.py | 6 +++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/openpype/lib/attribute_definitions.py b/openpype/lib/attribute_definitions.py index 589a4ef9ab..6baeaec045 100644 --- a/openpype/lib/attribute_definitions.py +++ b/openpype/lib/attribute_definitions.py @@ -541,6 +541,13 @@ class FileDefItem(object): return ext return None + @property + def lower_ext(self): + ext = self.ext + if ext is not None: + return ext.lower() + return ext + @property def is_dir(self): if self.is_empty: diff --git a/openpype/tools/attribute_defs/files_widget.py b/openpype/tools/attribute_defs/files_widget.py index 3f1e6a34e1..738e50ba07 100644 --- a/openpype/tools/attribute_defs/files_widget.py +++ b/openpype/tools/attribute_defs/files_widget.py @@ -349,7 +349,7 @@ class FilesModel(QtGui.QStandardItemModel): item.setData(file_item.filenames, FILENAMES_ROLE) item.setData(file_item.directory, DIRPATH_ROLE) item.setData(icon_pixmap, ITEM_ICON_ROLE) - item.setData(file_item.ext, EXT_ROLE) + item.setData(file_item.lower_ext, EXT_ROLE) item.setData(file_item.is_dir, IS_DIR_ROLE) item.setData(file_item.is_sequence, IS_SEQUENCE_ROLE) @@ -463,7 +463,7 @@ class FilesProxyModel(QtCore.QSortFilterProxyModel): for filepath in filepaths: if os.path.isfile(filepath): _, ext = os.path.splitext(filepath) - if ext in self._allowed_extensions: + if ext.lower() in self._allowed_extensions: return True elif self._allow_folders: @@ -475,7 +475,7 @@ class FilesProxyModel(QtCore.QSortFilterProxyModel): for filepath in filepaths: if os.path.isfile(filepath): _, ext = os.path.splitext(filepath) - if ext in self._allowed_extensions: + if ext.lower() in self._allowed_extensions: filtered_paths.append(filepath) elif self._allow_folders: From 213c78b9a019ac3a8956718e19564b9d5bdfa067 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Sep 2022 00:16:06 +0200 Subject: [PATCH 140/243] Avoid name conflict where `group_name != group_node` due to maya auto renaming new node --- openpype/hosts/maya/plugins/load/load_yeti_cache.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_yeti_cache.py b/openpype/hosts/maya/plugins/load/load_yeti_cache.py index 090047e22d..5ba381050a 100644 --- a/openpype/hosts/maya/plugins/load/load_yeti_cache.py +++ b/openpype/hosts/maya/plugins/load/load_yeti_cache.py @@ -73,8 +73,8 @@ class YetiCacheLoader(load.LoaderPlugin): c = colors.get(family) if c is not None: - cmds.setAttr(group_name + ".useOutlinerColor", 1) - cmds.setAttr(group_name + ".outlinerColor", + cmds.setAttr(group_node + ".useOutlinerColor", 1) + cmds.setAttr(group_node + ".outlinerColor", (float(c[0])/255), (float(c[1])/255), (float(c[2])/255) From d662b34ca7a70ddb797b4aef4d570028c23a5031 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 15 Nov 2022 13:26:22 +0100 Subject: [PATCH 141/243] added settings for validate frame range in tray publisher --- .../project_settings/traypublisher.json | 7 +++++ .../schema_project_traypublisher.json | 18 +++++++++++++ .../schemas/template_validate_plugin.json | 26 +++++++++++++++++++ 3 files changed, 51 insertions(+) create mode 100644 openpype/settings/entities/schemas/projects_schema/schemas/template_validate_plugin.json diff --git a/openpype/settings/defaults/project_settings/traypublisher.json b/openpype/settings/defaults/project_settings/traypublisher.json index 5db2a79772..e99b96b8c4 100644 --- a/openpype/settings/defaults/project_settings/traypublisher.json +++ b/openpype/settings/defaults/project_settings/traypublisher.json @@ -303,5 +303,12 @@ "extensions": [ ".mov" ] + }, + "publish": { + "ValidateFrameRange": { + "enabled": true, + "optional": true, + "active": true + } } } \ No newline at end of file diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json index 7c61aeed50..faa5033d2a 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json @@ -311,6 +311,24 @@ "object_type": "text" } ] + }, + { + "type": "dict", + "collapsible": true, + "key": "publish", + "label": "Publish plugins", + "children": [ + { + "type": "schema_template", + "name": "template_validate_plugin", + "template_data": [ + { + "key": "ValidateFrameRange", + "label": "Validate frame range" + } + ] + } + ] } ] } diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/template_validate_plugin.json b/openpype/settings/entities/schemas/projects_schema/schemas/template_validate_plugin.json new file mode 100644 index 0000000000..b57cad6719 --- /dev/null +++ b/openpype/settings/entities/schemas/projects_schema/schemas/template_validate_plugin.json @@ -0,0 +1,26 @@ +[ + { + "type": "dict", + "collapsible": true, + "key": "{key}", + "label": "{label}", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "optional", + "label": "Optional" + }, + { + "type": "boolean", + "key": "active", + "label": "Active" + } + ] + } +] From 125d0bbeed7b07640bc34dd877dac2e4c814895f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Mon, 31 Oct 2022 13:28:50 +0100 Subject: [PATCH 142/243] Feature: Auto download last published workfile as first workfile --- .../hooks/pre_copy_last_published_workfile.py | 124 ++++++++++++++++++ openpype/modules/sync_server/sync_server.py | 9 +- 2 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 openpype/hooks/pre_copy_last_published_workfile.py diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py new file mode 100644 index 0000000000..004f9d25e7 --- /dev/null +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -0,0 +1,124 @@ +import gc +import os +import shutil +from openpype.client.entities import ( + get_last_version_by_subset_id, + get_representations, + get_subsets, +) +from openpype.lib import PreLaunchHook +from openpype.modules.base import ModulesManager +from openpype.pipeline.load.utils import get_representation_path + + +class CopyLastPublishedWorkfile(PreLaunchHook): + """Copy last published workfile as first workfile. + + Prelaunch hook works only if last workfile leads to not existing file. + - That is possible only if it's first version. + """ + + # Before `AddLastWorkfileToLaunchArgs` + order = -1 + app_groups = ["blender", "photoshop", "tvpaint", "aftereffects"] + + def execute(self): + """Check if local workfile doesn't exist, else copy it. + + 1- Check if setting for this feature is enabled + 2- Check if workfile in work area doesn't exist + 3- Check if published workfile exists and is copied locally in publish + + Returns: + None: This is a void method. + """ + # TODO setting + self.log.info("Trying to fetch last published workfile...") + + last_workfile = self.data.get("last_workfile_path") + if os.path.exists(last_workfile): + self.log.debug( + "Last workfile exists. Skipping {} process.".format( + self.__class__.__name__ + ) + ) + return + + project_name = self.data["project_name"] + task_name = self.data["task_name"] + + project_doc = self.data.get("project_doc") + asset_doc = self.data.get("asset_doc") + anatomy = self.data.get("anatomy") + if project_doc and asset_doc: + # Get subset id + subset_id = next( + ( + subset["_id"] + for subset in get_subsets( + project_name, + asset_ids=[asset_doc["_id"]], + fields=["_id", "data.family"], + ) + if subset["data"]["family"] == "workfile" + ), + None, + ) + if not subset_id: + return + + # Get workfile representation + workfile_representation = next( + ( + representation + for representation in get_representations( + project_name, + version_ids=[ + get_last_version_by_subset_id( + project_name, subset_id, fields=["_id"] + )["_id"] + ], + ) + if representation["context"]["task"]["name"] == task_name + ), + None, + ) + + if workfile_representation: # TODO add setting + # Get sync server from Tray, which handles the asynchronous thread instance + sync_server = next( + ( + t["sync_server"] + for t in [ + obj + for obj in gc.get_objects() + if isinstance(obj, ModulesManager) + ] + if t["sync_server"].sync_server_thread + ), + None, + ) + + # Add site and reset timer + active_site = sync_server.get_active_site(project_name) + sync_server.add_site( + project_name, + workfile_representation["_id"], + active_site, + force=True, + ) + sync_server.reset_timer() + + # Wait for the download loop to end + sync_server.sync_server_thread.files_processed.wait() + + # Get paths + published_workfile_path = get_representation_path( + workfile_representation, root=anatomy.roots + ) + local_workfile_dir = os.path.dirname(last_workfile) + + # Copy file and substitute path + self.data["last_workfile_path"] = shutil.copy( + published_workfile_path, local_workfile_dir + ) diff --git a/openpype/modules/sync_server/sync_server.py b/openpype/modules/sync_server/sync_server.py index 8b11055e65..def9e6cfd8 100644 --- a/openpype/modules/sync_server/sync_server.py +++ b/openpype/modules/sync_server/sync_server.py @@ -236,7 +236,11 @@ class SyncServerThread(threading.Thread): """ def __init__(self, module): self.log = Logger.get_logger(self.__class__.__name__) - super(SyncServerThread, self).__init__() + + # Event to trigger files have been processed + self.files_processed = threading.Event() + + super(SyncServerThread, self).__init__(args=(self.files_processed,)) self.module = module self.loop = None self.is_running = False @@ -396,6 +400,8 @@ class SyncServerThread(threading.Thread): representation, site, error) + # Trigger files are processed + self.files_processed.set() duration = time.time() - start_time self.log.debug("One loop took {:.2f}s".format(duration)) @@ -454,6 +460,7 @@ class SyncServerThread(threading.Thread): async def run_timer(self, delay): """Wait for 'delay' seconds to start next loop""" + self.files_processed.clear() await asyncio.sleep(delay) def reset_timer(self): From af15b0d9415d1bfd2bff978ad81d370484d36bdb Mon Sep 17 00:00:00 2001 From: Felix David Date: Tue, 1 Nov 2022 10:00:26 +0100 Subject: [PATCH 143/243] Project setting --- .../hooks/pre_copy_last_published_workfile.py | 119 ++++++++++++------ .../defaults/project_settings/global.json | 3 +- .../schemas/schema_global_tools.json | 5 + 3 files changed, 88 insertions(+), 39 deletions(-) diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index 004f9d25e7..312548d2db 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -7,8 +7,10 @@ from openpype.client.entities import ( get_subsets, ) from openpype.lib import PreLaunchHook +from openpype.lib.profiles_filtering import filter_profiles from openpype.modules.base import ModulesManager from openpype.pipeline.load.utils import get_representation_path +from openpype.settings.lib import get_project_settings class CopyLastPublishedWorkfile(PreLaunchHook): @@ -32,9 +34,45 @@ class CopyLastPublishedWorkfile(PreLaunchHook): Returns: None: This is a void method. """ - # TODO setting + project_name = self.data["project_name"] + task_name = self.data["task_name"] + task_type = self.data["task_type"] + host_name = self.application.host_name + + # Check settings has enabled it + project_settings = get_project_settings(project_name) + profiles = project_settings["global"]["tools"]["Workfiles"][ + "last_workfile_on_startup" + ] + filter_data = { + "tasks": task_name, + "task_types": task_type, + "hosts": host_name, + } + last_workfile_settings = filter_profiles(profiles, filter_data) + use_last_published_workfile = last_workfile_settings.get( + "use_last_published_workfile" + ) + if use_last_published_workfile is None: + self.log.info( + ( + "Seems like old version of settings is used." + ' Can\'t access custom templates in host "{}".' + ).format(host_name) + ) + return + elif use_last_published_workfile is False: + self.log.info( + ( + 'Project "{}" has turned off to use last published workfile' + ' as first workfile for host "{}"' + ).format(project_name, host_name) + ) + return + self.log.info("Trying to fetch last published workfile...") + # Check there is no workfile available last_workfile = self.data.get("last_workfile_path") if os.path.exists(last_workfile): self.log.debug( @@ -44,9 +82,6 @@ class CopyLastPublishedWorkfile(PreLaunchHook): ) return - project_name = self.data["project_name"] - task_name = self.data["task_name"] - project_doc = self.data.get("project_doc") asset_doc = self.data.get("asset_doc") anatomy = self.data.get("anatomy") @@ -65,6 +100,9 @@ class CopyLastPublishedWorkfile(PreLaunchHook): None, ) if not subset_id: + self.log.debug('No any workfile for asset "{}".').format( + asset_doc["name"] + ) return # Get workfile representation @@ -84,41 +122,46 @@ class CopyLastPublishedWorkfile(PreLaunchHook): None, ) - if workfile_representation: # TODO add setting - # Get sync server from Tray, which handles the asynchronous thread instance - sync_server = next( - ( - t["sync_server"] - for t in [ - obj - for obj in gc.get_objects() - if isinstance(obj, ModulesManager) - ] - if t["sync_server"].sync_server_thread - ), - None, - ) + if not workfile_representation: + self.log.debug( + 'No published workfile for task "{}" and host "{}".' + ).format(task_name, host_name) + return - # Add site and reset timer - active_site = sync_server.get_active_site(project_name) - sync_server.add_site( - project_name, - workfile_representation["_id"], - active_site, - force=True, - ) - sync_server.reset_timer() + # Get sync server from Tray, which handles the asynchronous thread instance + sync_server = next( + ( + t["sync_server"] + for t in [ + obj + for obj in gc.get_objects() + if isinstance(obj, ModulesManager) + ] + if t["sync_server"].sync_server_thread + ), + None, + ) - # Wait for the download loop to end - sync_server.sync_server_thread.files_processed.wait() + # Add site and reset timer + active_site = sync_server.get_active_site(project_name) + sync_server.add_site( + project_name, + workfile_representation["_id"], + active_site, + force=True, + ) + sync_server.reset_timer() - # Get paths - published_workfile_path = get_representation_path( - workfile_representation, root=anatomy.roots - ) - local_workfile_dir = os.path.dirname(last_workfile) + # Wait for the download loop to end + sync_server.sync_server_thread.files_processed.wait() - # Copy file and substitute path - self.data["last_workfile_path"] = shutil.copy( - published_workfile_path, local_workfile_dir - ) + # Get paths + published_workfile_path = get_representation_path( + workfile_representation, root=anatomy.roots + ) + local_workfile_dir = os.path.dirname(last_workfile) + + # Copy file and substitute path + self.data["last_workfile_path"] = shutil.copy( + published_workfile_path, local_workfile_dir + ) diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 9c3f2f1e1b..7daa4afa79 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -458,7 +458,8 @@ "hosts": [], "task_types": [], "tasks": [], - "enabled": true + "enabled": true, + "use_last_published_workfile": false } ], "open_workfile_tool_on_startup": [ diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json index ba446135e2..962008d476 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json @@ -149,6 +149,11 @@ "type": "boolean", "key": "enabled", "label": "Enabled" + }, + { + "type": "boolean", + "key": "use_last_published_workfile", + "label": "Use last published workfile" } ] } From 7a7c91c418f1084dacd25e6aa453e0c70caf9fcd Mon Sep 17 00:00:00 2001 From: Felix David Date: Tue, 1 Nov 2022 10:10:12 +0100 Subject: [PATCH 144/243] docstring --- openpype/hooks/pre_copy_last_published_workfile.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index 312548d2db..b1b2fe2366 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -30,6 +30,7 @@ class CopyLastPublishedWorkfile(PreLaunchHook): 1- Check if setting for this feature is enabled 2- Check if workfile in work area doesn't exist 3- Check if published workfile exists and is copied locally in publish + 4- Substitute copied published workfile as first workfile Returns: None: This is a void method. From e24489c463af8ce3a83807df69af984357363bfb Mon Sep 17 00:00:00 2001 From: Felix David Date: Tue, 1 Nov 2022 10:14:58 +0100 Subject: [PATCH 145/243] comment length --- openpype/hooks/pre_copy_last_published_workfile.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index b1b2fe2366..d342151823 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -129,7 +129,8 @@ class CopyLastPublishedWorkfile(PreLaunchHook): ).format(task_name, host_name) return - # Get sync server from Tray, which handles the asynchronous thread instance + # Get sync server from Tray, + # which handles the asynchronous thread instance sync_server = next( ( t["sync_server"] From 17853d0b3b55658310bef044eb65bed19d533bed Mon Sep 17 00:00:00 2001 From: Felix David Date: Tue, 1 Nov 2022 10:50:30 +0100 Subject: [PATCH 146/243] lint --- openpype/hooks/pre_copy_last_published_workfile.py | 4 ++-- openpype/modules/sync_server/sync_server.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index d342151823..cf4edeac9b 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -65,8 +65,8 @@ class CopyLastPublishedWorkfile(PreLaunchHook): elif use_last_published_workfile is False: self.log.info( ( - 'Project "{}" has turned off to use last published workfile' - ' as first workfile for host "{}"' + 'Project "{}" has turned off to use last published' + ' workfile as first workfile for host "{}"' ).format(project_name, host_name) ) return diff --git a/openpype/modules/sync_server/sync_server.py b/openpype/modules/sync_server/sync_server.py index def9e6cfd8..353b39c4e1 100644 --- a/openpype/modules/sync_server/sync_server.py +++ b/openpype/modules/sync_server/sync_server.py @@ -239,7 +239,7 @@ class SyncServerThread(threading.Thread): # Event to trigger files have been processed self.files_processed = threading.Event() - + super(SyncServerThread, self).__init__(args=(self.files_processed,)) self.module = module self.loop = None From 881bcebd1dbec626d1b1e48ebf079746ad567b0c Mon Sep 17 00:00:00 2001 From: Felix David Date: Thu, 3 Nov 2022 11:41:59 +0100 Subject: [PATCH 147/243] requested cosmetic changes --- .../hooks/pre_copy_last_published_workfile.py | 172 +++++++++--------- 1 file changed, 90 insertions(+), 82 deletions(-) diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index cf4edeac9b..7a835507f7 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -35,6 +35,17 @@ class CopyLastPublishedWorkfile(PreLaunchHook): Returns: None: This is a void method. """ + # Check there is no workfile available + last_workfile = self.data.get("last_workfile_path") + if os.path.exists(last_workfile): + self.log.debug( + "Last workfile exists. Skipping {} process.".format( + self.__class__.__name__ + ) + ) + return + + # Get data project_name = self.data["project_name"] task_name = self.data["task_name"] task_type = self.data["task_type"] @@ -73,97 +84,94 @@ class CopyLastPublishedWorkfile(PreLaunchHook): self.log.info("Trying to fetch last published workfile...") - # Check there is no workfile available - last_workfile = self.data.get("last_workfile_path") - if os.path.exists(last_workfile): - self.log.debug( - "Last workfile exists. Skipping {} process.".format( - self.__class__.__name__ - ) - ) - return - project_doc = self.data.get("project_doc") asset_doc = self.data.get("asset_doc") anatomy = self.data.get("anatomy") - if project_doc and asset_doc: - # Get subset id - subset_id = next( - ( - subset["_id"] - for subset in get_subsets( - project_name, - asset_ids=[asset_doc["_id"]], - fields=["_id", "data.family"], - ) - if subset["data"]["family"] == "workfile" - ), - None, - ) - if not subset_id: - self.log.debug('No any workfile for asset "{}".').format( - asset_doc["name"] - ) - return - # Get workfile representation - workfile_representation = next( - ( - representation - for representation in get_representations( - project_name, - version_ids=[ + # Check it can proceed + if not project_doc and not asset_doc: + return + + # Get subset id + subset_id = next( + ( + subset["_id"] + for subset in get_subsets( + project_name, + asset_ids=[asset_doc["_id"]], + fields=["_id", "data.family"], + ) + if subset["data"]["family"] == "workfile" + ), + None, + ) + if not subset_id: + self.log.debug('No any workfile for asset "{}".').format( + asset_doc["name"] + ) + return + + # Get workfile representation + workfile_representation = next( + ( + representation + for representation in get_representations( + project_name, + version_ids=[ + ( get_last_version_by_subset_id( project_name, subset_id, fields=["_id"] - )["_id"] - ], - ) - if representation["context"]["task"]["name"] == task_name - ), - None, - ) + ) + or {} + ).get("_id") + ], + ) + if representation["context"]["task"]["name"] == task_name + ), + None, + ) - if not workfile_representation: - self.log.debug( - 'No published workfile for task "{}" and host "{}".' - ).format(task_name, host_name) - return + if not workfile_representation: + self.log.debug( + 'No published workfile for task "{}" and host "{}".' + ).format(task_name, host_name) + return - # Get sync server from Tray, - # which handles the asynchronous thread instance - sync_server = next( - ( - t["sync_server"] - for t in [ - obj - for obj in gc.get_objects() - if isinstance(obj, ModulesManager) - ] - if t["sync_server"].sync_server_thread - ), - None, - ) + # Get sync server from Tray, + # which handles the asynchronous thread instance + sync_server = next( + ( + t["sync_server"] + for t in [ + obj + for obj in gc.get_objects() + if isinstance(obj, ModulesManager) + ] + if t["sync_server"].sync_server_thread + ), + None, + ) - # Add site and reset timer - active_site = sync_server.get_active_site(project_name) - sync_server.add_site( - project_name, - workfile_representation["_id"], - active_site, - force=True, - ) - sync_server.reset_timer() + # Add site and reset timer + active_site = sync_server.get_active_site(project_name) + sync_server.add_site( + project_name, + workfile_representation["_id"], + active_site, + force=True, + ) + sync_server.reset_timer() - # Wait for the download loop to end - sync_server.sync_server_thread.files_processed.wait() + # Wait for the download loop to end + sync_server.sync_server_thread.files_processed.wait() - # Get paths - published_workfile_path = get_representation_path( - workfile_representation, root=anatomy.roots - ) - local_workfile_dir = os.path.dirname(last_workfile) + # Get paths + published_workfile_path = get_representation_path( + workfile_representation, root=anatomy.roots + ) + local_workfile_dir = os.path.dirname(last_workfile) - # Copy file and substitute path - self.data["last_workfile_path"] = shutil.copy( - published_workfile_path, local_workfile_dir - ) + # Copy file and substitute path + self.data["last_workfile_path"] = shutil.copy( + published_workfile_path, local_workfile_dir + ) From 9e01c5deaa1615316b82d6123df8ffa1101a15ec Mon Sep 17 00:00:00 2001 From: Felix David Date: Thu, 3 Nov 2022 18:33:54 +0100 Subject: [PATCH 148/243] Change to REST API using web server --- .../hooks/pre_copy_last_published_workfile.py | 54 ++++++++------- openpype/modules/sync_server/rest_api.py | 68 +++++++++++++++++++ openpype/modules/sync_server/sync_server.py | 12 ++-- .../modules/sync_server/sync_server_module.py | 9 +++ openpype/modules/timers_manager/rest_api.py | 2 +- 5 files changed, 112 insertions(+), 33 deletions(-) create mode 100644 openpype/modules/sync_server/rest_api.py diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index 7a835507f7..cefc7e5d40 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -1,14 +1,14 @@ -import gc import os import shutil +from time import sleep from openpype.client.entities import ( get_last_version_by_subset_id, get_representations, get_subsets, ) from openpype.lib import PreLaunchHook +from openpype.lib.local_settings import get_local_site_id from openpype.lib.profiles_filtering import filter_profiles -from openpype.modules.base import ModulesManager from openpype.pipeline.load.utils import get_representation_path from openpype.settings.lib import get_project_settings @@ -137,33 +137,37 @@ class CopyLastPublishedWorkfile(PreLaunchHook): ).format(task_name, host_name) return - # Get sync server from Tray, - # which handles the asynchronous thread instance - sync_server = next( - ( - t["sync_server"] - for t in [ - obj - for obj in gc.get_objects() - if isinstance(obj, ModulesManager) - ] - if t["sync_server"].sync_server_thread - ), - None, - ) + # POST to webserver sites to add to representations + webserver_url = os.environ.get("OPENPYPE_WEBSERVER_URL") + if not webserver_url: + self.log.warning("Couldn't find webserver url") + return - # Add site and reset timer - active_site = sync_server.get_active_site(project_name) - sync_server.add_site( - project_name, - workfile_representation["_id"], - active_site, - force=True, + entry_point_url = "{}/sync_server".format(webserver_url) + rest_api_url = "{}/add_sites_to_representations".format( + entry_point_url + ) + try: + import requests + except Exception: + self.log.warning( + "Couldn't add sites to representations ('requests' is not available)" + ) + return + + requests.post( + rest_api_url, + json={ + "project_name": project_name, + "sites": [get_local_site_id()], + "representations": [str(workfile_representation["_id"])], + }, ) - sync_server.reset_timer() # Wait for the download loop to end - sync_server.sync_server_thread.files_processed.wait() + rest_api_url = "{}/files_are_processed".format(entry_point_url) + while requests.get(rest_api_url).content: + sleep(5) # Get paths published_workfile_path = get_representation_path( diff --git a/openpype/modules/sync_server/rest_api.py b/openpype/modules/sync_server/rest_api.py new file mode 100644 index 0000000000..b7c5d26d15 --- /dev/null +++ b/openpype/modules/sync_server/rest_api.py @@ -0,0 +1,68 @@ +from aiohttp.web_response import Response +from openpype.lib import Logger + + +class SyncServerModuleRestApi: + """ + REST API endpoint used for calling from hosts when context change + happens in Workfile app. + """ + + def __init__(self, user_module, server_manager): + self._log = None + self.module = user_module + self.server_manager = server_manager + + self.prefix = "/sync_server" + + self.register() + + @property + def log(self): + if self._log is None: + self._log = Logger.get_logger(self.__class__.__name__) + return self._log + + def register(self): + self.server_manager.add_route( + "POST", + self.prefix + "/add_sites_to_representations", + self.add_sites_to_representations, + ) + self.server_manager.add_route( + "GET", + self.prefix + "/files_are_processed", + self.files_are_processed, + ) + + async def add_sites_to_representations(self, request): + # Extract data from request + data = await request.json() + try: + project_name = data["project_name"] + sites = data["sites"] + representations = data["representations"] + except KeyError: + msg = ( + "Payload must contain fields 'project_name," + " 'sites' (list of names) and 'representations' (list of IDs)" + ) + self.log.error(msg) + return Response(status=400, message=msg) + + # Add all sites to each representation + for representation_id in representations: + for site in sites: + self.module.add_site( + project_name, representation_id, site, force=True + ) + + # Force timer to run immediately + self.module.reset_timer() + + return Response(status=200) + + async def files_are_processed(self, _request): + return Response( + body=bytes(self.module.sync_server_thread.files_are_processed) + ) diff --git a/openpype/modules/sync_server/sync_server.py b/openpype/modules/sync_server/sync_server.py index 353b39c4e1..7fd2311c2d 100644 --- a/openpype/modules/sync_server/sync_server.py +++ b/openpype/modules/sync_server/sync_server.py @@ -237,15 +237,13 @@ class SyncServerThread(threading.Thread): def __init__(self, module): self.log = Logger.get_logger(self.__class__.__name__) - # Event to trigger files have been processed - self.files_processed = threading.Event() - - super(SyncServerThread, self).__init__(args=(self.files_processed,)) + super(SyncServerThread, self).__init__() self.module = module self.loop = None self.is_running = False self.executor = concurrent.futures.ThreadPoolExecutor(max_workers=3) self.timer = None + self.files_are_processed = False def run(self): self.is_running = True @@ -400,8 +398,8 @@ class SyncServerThread(threading.Thread): representation, site, error) - # Trigger files are processed - self.files_processed.set() + # Trigger files process finished + self.files_are_processed = False duration = time.time() - start_time self.log.debug("One loop took {:.2f}s".format(duration)) @@ -460,7 +458,6 @@ class SyncServerThread(threading.Thread): async def run_timer(self, delay): """Wait for 'delay' seconds to start next loop""" - self.files_processed.clear() await asyncio.sleep(delay) def reset_timer(self): @@ -469,6 +466,7 @@ class SyncServerThread(threading.Thread): if self.timer: self.timer.cancel() self.timer = None + self.files_are_processed = True def _working_sites(self, project_name): if self.module.is_project_paused(project_name): diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index e84c333a58..bff999723b 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -2089,6 +2089,15 @@ class SyncServerModule(OpenPypeModule, ITrayModule): def cli(self, click_group): click_group.add_command(cli_main) + # Webserver module implementation + def webserver_initialization(self, server_manager): + """Add routes for syncs.""" + if self.tray_initialized: + from .rest_api import SyncServerModuleRestApi + self.rest_api_obj = SyncServerModuleRestApi( + self, server_manager + ) + @click.group(SyncServerModule.name, help="SyncServer module related commands.") def cli_main(): diff --git a/openpype/modules/timers_manager/rest_api.py b/openpype/modules/timers_manager/rest_api.py index 4a2e9e6575..979db9075b 100644 --- a/openpype/modules/timers_manager/rest_api.py +++ b/openpype/modules/timers_manager/rest_api.py @@ -21,7 +21,7 @@ class TimersManagerModuleRestApi: @property def log(self): if self._log is None: - self._log = Logger.get_logger(self.__ckass__.__name__) + self._log = Logger.get_logger(self.__class__.__name__) return self._log def register(self): From 8d9542bb45088fec5c800fe7b7d9b76f5ca3c14c Mon Sep 17 00:00:00 2001 From: Felix David Date: Thu, 3 Nov 2022 18:37:13 +0100 Subject: [PATCH 149/243] lint --- openpype/hooks/pre_copy_last_published_workfile.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index cefc7e5d40..6bec4f7d2c 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -151,7 +151,8 @@ class CopyLastPublishedWorkfile(PreLaunchHook): import requests except Exception: self.log.warning( - "Couldn't add sites to representations ('requests' is not available)" + "Couldn't add sites to representations " + "('requests' is not available)" ) return From 5e02d7d2d71796b6826e320fd8cfbc3e77980d93 Mon Sep 17 00:00:00 2001 From: Felix David Date: Fri, 4 Nov 2022 10:06:59 +0100 Subject: [PATCH 150/243] legacy compatibility --- openpype/hooks/pre_copy_last_published_workfile.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index 6bec4f7d2c..f3293fa511 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -99,9 +99,11 @@ class CopyLastPublishedWorkfile(PreLaunchHook): for subset in get_subsets( project_name, asset_ids=[asset_doc["_id"]], - fields=["_id", "data.family"], + fields=["_id", "data.family", "data.families"], ) - if subset["data"]["family"] == "workfile" + if subset["data"].get("family") == "workfile" + # Legacy compatibility + or "workfile" in subset["data"].get("families", {}) ), None, ) From 1c48c0936290cbd7013df50d83392f53a68d51dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Tue, 8 Nov 2022 16:41:19 +0100 Subject: [PATCH 151/243] use 'created_dt' of representation --- openpype/client/entities.py | 28 +++++++++++++++++++ .../hooks/pre_copy_last_published_workfile.py | 19 +++++++++++-- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 43afccf2f1..43c2874f57 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -6,6 +6,7 @@ that has project name as a context (e.g. on 'ProjectEntity'?). + We will need more specific functions doing wery specific queires really fast. """ +from datetime import datetime import re import collections @@ -1367,6 +1368,33 @@ def get_representation_parents(project_name, representation): return parents_by_repre_id[repre_id] +def get_representation_last_created_time_on_site( + representation: dict, site_name: str +) -> datetime: + """Get `created_dt` value for representation on site. + + Args: + representation (dict): Representation to get creation date of + site_name (str): Site from which to get the creation date + + Returns: + datetime: Created time of representation on site + """ + created_time = next( + ( + site.get("created_dt") + for site in representation["files"][0].get("sites", []) + if site["name"] == site_name + ), + None, + ) + if created_time: + return created_time + else: + # Use epoch as 'zero' time + return datetime.utcfromtimestamp(0) + + def get_thumbnail_id_from_source(project_name, src_type, src_id): """Receive thumbnail id from source entity. diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index f3293fa511..4eb66f6f85 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -3,6 +3,8 @@ import shutil from time import sleep from openpype.client.entities import ( get_last_version_by_subset_id, + get_representation_by_id, + get_representation_last_created_time_on_site, get_representations, get_subsets, ) @@ -158,18 +160,29 @@ class CopyLastPublishedWorkfile(PreLaunchHook): ) return + local_site_id = get_local_site_id() requests.post( rest_api_url, json={ "project_name": project_name, - "sites": [get_local_site_id()], + "sites": [local_site_id], "representations": [str(workfile_representation["_id"])], }, ) # Wait for the download loop to end - rest_api_url = "{}/files_are_processed".format(entry_point_url) - while requests.get(rest_api_url).content: + last_created_time = get_representation_last_created_time_on_site( + workfile_representation, local_site_id + ) + while ( + last_created_time + >= get_representation_last_created_time_on_site( + get_representation_by_id( + project_name, workfile_representation["_id"] + ), + local_site_id, + ) + ): sleep(5) # Get paths From a614f0f805acd6d73c57dc68bc00a9d7834714cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Tue, 8 Nov 2022 18:57:02 +0100 Subject: [PATCH 152/243] add priority to add_site --- openpype/modules/sync_server/rest_api.py | 17 ++++++----------- openpype/modules/sync_server/sync_server.py | 4 ---- .../modules/sync_server/sync_server_module.py | 13 ++++++++++--- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/openpype/modules/sync_server/rest_api.py b/openpype/modules/sync_server/rest_api.py index b7c5d26d15..e92ddc8eee 100644 --- a/openpype/modules/sync_server/rest_api.py +++ b/openpype/modules/sync_server/rest_api.py @@ -1,4 +1,5 @@ from aiohttp.web_response import Response +from openpype.client.entities import get_representation_by_id from openpype.lib import Logger @@ -29,11 +30,6 @@ class SyncServerModuleRestApi: self.prefix + "/add_sites_to_representations", self.add_sites_to_representations, ) - self.server_manager.add_route( - "GET", - self.prefix + "/files_are_processed", - self.files_are_processed, - ) async def add_sites_to_representations(self, request): # Extract data from request @@ -54,15 +50,14 @@ class SyncServerModuleRestApi: for representation_id in representations: for site in sites: self.module.add_site( - project_name, representation_id, site, force=True + project_name, + representation_id, + site, + force=True, + priority=99, ) # Force timer to run immediately self.module.reset_timer() return Response(status=200) - - async def files_are_processed(self, _request): - return Response( - body=bytes(self.module.sync_server_thread.files_are_processed) - ) diff --git a/openpype/modules/sync_server/sync_server.py b/openpype/modules/sync_server/sync_server.py index 7fd2311c2d..d0a40a60ff 100644 --- a/openpype/modules/sync_server/sync_server.py +++ b/openpype/modules/sync_server/sync_server.py @@ -243,7 +243,6 @@ class SyncServerThread(threading.Thread): self.is_running = False self.executor = concurrent.futures.ThreadPoolExecutor(max_workers=3) self.timer = None - self.files_are_processed = False def run(self): self.is_running = True @@ -398,8 +397,6 @@ class SyncServerThread(threading.Thread): representation, site, error) - # Trigger files process finished - self.files_are_processed = False duration = time.time() - start_time self.log.debug("One loop took {:.2f}s".format(duration)) @@ -466,7 +463,6 @@ class SyncServerThread(threading.Thread): if self.timer: self.timer.cancel() self.timer = None - self.files_are_processed = True def _working_sites(self, project_name): if self.module.is_project_paused(project_name): diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index bff999723b..6a1fc9a1c5 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -136,7 +136,7 @@ class SyncServerModule(OpenPypeModule, ITrayModule): """ Start of Public API """ def add_site(self, project_name, representation_id, site_name=None, - force=False): + force=False, priority=None): """ Adds new site to representation to be synced. @@ -152,6 +152,7 @@ class SyncServerModule(OpenPypeModule, ITrayModule): representation_id (string): MongoDB _id value site_name (string): name of configured and active site force (bool): reset site if exists + priority (int): set priority Throws: SiteAlreadyPresentError - if adding already existing site and @@ -167,7 +168,8 @@ class SyncServerModule(OpenPypeModule, ITrayModule): self.reset_site_on_representation(project_name, representation_id, site_name=site_name, - force=force) + force=force, + priority=priority) def remove_site(self, project_name, representation_id, site_name, remove_local_files=False): @@ -1655,7 +1657,7 @@ class SyncServerModule(OpenPypeModule, ITrayModule): def reset_site_on_representation(self, project_name, representation_id, side=None, file_id=None, site_name=None, - remove=False, pause=None, force=False): + remove=False, pause=None, force=False, priority=None): """ Reset information about synchronization for particular 'file_id' and provider. @@ -1678,6 +1680,7 @@ class SyncServerModule(OpenPypeModule, ITrayModule): remove (bool): if True remove site altogether pause (bool or None): if True - pause, False - unpause force (bool): hard reset - currently only for add_site + priority (int): set priority Raises: SiteAlreadyPresentError - if adding already existing site and @@ -1705,6 +1708,10 @@ class SyncServerModule(OpenPypeModule, ITrayModule): elem = {"name": site_name} + # Add priority + if priority: + elem["priority"] = priority + if file_id: # reset site for particular file self._reset_site_for_file(project_name, representation_id, elem, file_id, site_name) From b6365d85404b88dddcb218969f0a7e30e4668e08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Tue, 8 Nov 2022 18:58:19 +0100 Subject: [PATCH 153/243] clean --- openpype/modules/sync_server/rest_api.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/modules/sync_server/rest_api.py b/openpype/modules/sync_server/rest_api.py index e92ddc8eee..0c3b914833 100644 --- a/openpype/modules/sync_server/rest_api.py +++ b/openpype/modules/sync_server/rest_api.py @@ -1,5 +1,4 @@ from aiohttp.web_response import Response -from openpype.client.entities import get_representation_by_id from openpype.lib import Logger From 138051f2f4c9d0c705eeb1cd299166d0ca249850 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Tue, 8 Nov 2022 18:59:03 +0100 Subject: [PATCH 154/243] clean --- openpype/modules/sync_server/sync_server_module.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index 6a1fc9a1c5..951cb116fc 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -1657,7 +1657,8 @@ class SyncServerModule(OpenPypeModule, ITrayModule): def reset_site_on_representation(self, project_name, representation_id, side=None, file_id=None, site_name=None, - remove=False, pause=None, force=False, priority=None): + remove=False, pause=None, force=False, + priority=None): """ Reset information about synchronization for particular 'file_id' and provider. From 3a5ebc6ea29fd4ec34b0fc80c27f5cc187ace8e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Tue, 8 Nov 2022 19:02:29 +0100 Subject: [PATCH 155/243] sort fields --- openpype/hooks/pre_copy_last_published_workfile.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index 4eb66f6f85..acbc9ec1c7 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -178,7 +178,9 @@ class CopyLastPublishedWorkfile(PreLaunchHook): last_created_time >= get_representation_last_created_time_on_site( get_representation_by_id( - project_name, workfile_representation["_id"] + project_name, + workfile_representation["_id"], + fields=["files"], ), local_site_id, ) From 29f0dee272c9b0b27c4a6e4098caab2ed11a1d7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Tue, 8 Nov 2022 19:04:03 +0100 Subject: [PATCH 156/243] clean --- openpype/modules/sync_server/sync_server_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index 951cb116fc..1292bed9af 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -1657,7 +1657,7 @@ class SyncServerModule(OpenPypeModule, ITrayModule): def reset_site_on_representation(self, project_name, representation_id, side=None, file_id=None, site_name=None, - remove=False, pause=None, force=False, + remove=False, pause=None, force=False, priority=None): """ Reset information about synchronization for particular 'file_id' From a600cf4dcad5f8caa08187ca2d449bbf9986623a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 9 Nov 2022 11:18:23 +0100 Subject: [PATCH 157/243] fix last version check --- .../hooks/pre_copy_last_published_workfile.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index acbc9ec1c7..96b5ccadb2 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -116,19 +116,19 @@ class CopyLastPublishedWorkfile(PreLaunchHook): return # Get workfile representation + last_version_doc = get_last_version_by_subset_id( + project_name, subset_id, fields=["_id"] + ) + if not last_version_doc: + self.log.debug("Subset does not have any versions") + return + workfile_representation = next( ( representation for representation in get_representations( project_name, - version_ids=[ - ( - get_last_version_by_subset_id( - project_name, subset_id, fields=["_id"] - ) - or {} - ).get("_id") - ], + version_ids=[last_version_doc["_id"]] ) if representation["context"]["task"]["name"] == task_name ), From 7596610c160cf83b5dccb00c1638756312a54cf1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 9 Nov 2022 11:19:03 +0100 Subject: [PATCH 158/243] replaced 'add_sites_to_representations' with 'reset_timer' in rest api --- openpype/modules/sync_server/rest_api.py | 31 +++--------------------- 1 file changed, 3 insertions(+), 28 deletions(-) diff --git a/openpype/modules/sync_server/rest_api.py b/openpype/modules/sync_server/rest_api.py index 0c3b914833..51769cd4fb 100644 --- a/openpype/modules/sync_server/rest_api.py +++ b/openpype/modules/sync_server/rest_api.py @@ -26,36 +26,11 @@ class SyncServerModuleRestApi: def register(self): self.server_manager.add_route( "POST", - self.prefix + "/add_sites_to_representations", - self.add_sites_to_representations, + self.prefix + "/reset_timer", + self.reset_timer, ) - async def add_sites_to_representations(self, request): - # Extract data from request - data = await request.json() - try: - project_name = data["project_name"] - sites = data["sites"] - representations = data["representations"] - except KeyError: - msg = ( - "Payload must contain fields 'project_name," - " 'sites' (list of names) and 'representations' (list of IDs)" - ) - self.log.error(msg) - return Response(status=400, message=msg) - - # Add all sites to each representation - for representation_id in representations: - for site in sites: - self.module.add_site( - project_name, - representation_id, - site, - force=True, - priority=99, - ) - + async def reset_timer(self, request): # Force timer to run immediately self.module.reset_timer() From 2052afc76a72d5845570e4900e527cabe1d1ecb1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 9 Nov 2022 11:19:15 +0100 Subject: [PATCH 159/243] added ability to rese timer from add_site --- openpype/modules/sync_server/sync_server_module.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index 1292bed9af..5e19a6fce0 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -136,7 +136,7 @@ class SyncServerModule(OpenPypeModule, ITrayModule): """ Start of Public API """ def add_site(self, project_name, representation_id, site_name=None, - force=False, priority=None): + force=False, priority=None, reset_timer=False): """ Adds new site to representation to be synced. @@ -171,6 +171,9 @@ class SyncServerModule(OpenPypeModule, ITrayModule): force=force, priority=priority) + if reset_timer: + self.reset_timer() + def remove_site(self, project_name, representation_id, site_name, remove_local_files=False): """ From f7c1fa01ae1b358b8af45ab777ebda4a6ba81bfc Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 9 Nov 2022 11:19:32 +0100 Subject: [PATCH 160/243] 'reset_timer' can reset timer via rest api endpoint --- .../modules/sync_server/sync_server_module.py | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index 5e19a6fce0..b505e25d2f 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -916,7 +916,42 @@ class SyncServerModule(OpenPypeModule, ITrayModule): In case of user's involvement (reset site), start that right away. """ - self.sync_server_thread.reset_timer() + + if not self.enabled: + return + + if self.sync_server_thread is None: + self._reset_timer_with_rest_api() + else: + self.sync_server_thread.reset_timer() + + def is_representaion_on_site( + self, project_name, representation_id, site_id + ): + # TODO implement + return False + + def _reset_timer_with_rest_api(self): + # POST to webserver sites to add to representations + webserver_url = os.environ.get("OPENPYPE_WEBSERVER_URL") + if not webserver_url: + self.log.warning("Couldn't find webserver url") + return + + rest_api_url = "{}/sync_server/reset_timer".format( + webserver_url + ) + + try: + import requests + except Exception: + self.log.warning( + "Couldn't add sites to representations " + "('requests' is not available)" + ) + return + + requests.post(rest_api_url) def get_enabled_projects(self): """Returns list of projects which have SyncServer enabled.""" From 1d028d22ba4b796b6e8fe700b70f2f2e87217edb Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 9 Nov 2022 11:20:21 +0100 Subject: [PATCH 161/243] updated prelaunch hook with new abilities of sync server --- .../hooks/pre_copy_last_published_workfile.py | 52 +++++-------------- 1 file changed, 12 insertions(+), 40 deletions(-) diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index 96b5ccadb2..6fd50a64d6 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -141,49 +141,21 @@ class CopyLastPublishedWorkfile(PreLaunchHook): ).format(task_name, host_name) return - # POST to webserver sites to add to representations - webserver_url = os.environ.get("OPENPYPE_WEBSERVER_URL") - if not webserver_url: - self.log.warning("Couldn't find webserver url") - return - - entry_point_url = "{}/sync_server".format(webserver_url) - rest_api_url = "{}/add_sites_to_representations".format( - entry_point_url - ) - try: - import requests - except Exception: - self.log.warning( - "Couldn't add sites to representations " - "('requests' is not available)" - ) - return - local_site_id = get_local_site_id() - requests.post( - rest_api_url, - json={ - "project_name": project_name, - "sites": [local_site_id], - "representations": [str(workfile_representation["_id"])], - }, + sync_server = self.modules_manager.get("sync_server") + sync_server.add_site( + project_name, + workfile_representation["_id"], + local_site_id, + force=True, + priority=99, + reset_timer=True ) - # Wait for the download loop to end - last_created_time = get_representation_last_created_time_on_site( - workfile_representation, local_site_id - ) - while ( - last_created_time - >= get_representation_last_created_time_on_site( - get_representation_by_id( - project_name, - workfile_representation["_id"], - fields=["files"], - ), - local_site_id, - ) + while not sync_server.is_representaion_on_site( + project_name, + workfile_representation["_id"], + local_site_id ): sleep(5) From 5db743080ccb22adc89f76fc86f4ca26020503fd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 9 Nov 2022 11:22:56 +0100 Subject: [PATCH 162/243] check if is sync server enabled --- openpype/hooks/pre_copy_last_published_workfile.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index 6fd50a64d6..69e3d6efe4 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -37,6 +37,12 @@ class CopyLastPublishedWorkfile(PreLaunchHook): Returns: None: This is a void method. """ + + sync_server = self.modules_manager.get("sync_server") + if not sync_server or not sync_server.enabled: + self.log.deubg("Sync server module is not enabled or available") + return + # Check there is no workfile available last_workfile = self.data.get("last_workfile_path") if os.path.exists(last_workfile): @@ -142,7 +148,6 @@ class CopyLastPublishedWorkfile(PreLaunchHook): return local_site_id = get_local_site_id() - sync_server = self.modules_manager.get("sync_server") sync_server.add_site( project_name, workfile_representation["_id"], From 75e12954ee51d09916032224f4c72be84c12bacf Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 9 Nov 2022 11:36:03 +0100 Subject: [PATCH 163/243] removed 'get_representation_last_created_time_on_site' function --- openpype/client/entities.py | 27 ------------------- .../hooks/pre_copy_last_published_workfile.py | 2 -- 2 files changed, 29 deletions(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 43c2874f57..91d4b499b0 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -1368,33 +1368,6 @@ def get_representation_parents(project_name, representation): return parents_by_repre_id[repre_id] -def get_representation_last_created_time_on_site( - representation: dict, site_name: str -) -> datetime: - """Get `created_dt` value for representation on site. - - Args: - representation (dict): Representation to get creation date of - site_name (str): Site from which to get the creation date - - Returns: - datetime: Created time of representation on site - """ - created_time = next( - ( - site.get("created_dt") - for site in representation["files"][0].get("sites", []) - if site["name"] == site_name - ), - None, - ) - if created_time: - return created_time - else: - # Use epoch as 'zero' time - return datetime.utcfromtimestamp(0) - - def get_thumbnail_id_from_source(project_name, src_type, src_id): """Receive thumbnail id from source entity. diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index 69e3d6efe4..884b0f54b6 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -3,8 +3,6 @@ import shutil from time import sleep from openpype.client.entities import ( get_last_version_by_subset_id, - get_representation_by_id, - get_representation_last_created_time_on_site, get_representations, get_subsets, ) From 99bebd82a7a3d9288799d4771a242a56dd58c40a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 9 Nov 2022 11:51:00 +0100 Subject: [PATCH 164/243] fix typo --- openpype/hooks/pre_copy_last_published_workfile.py | 2 +- openpype/modules/sync_server/sync_server_module.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index 884b0f54b6..0e561334e1 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -155,7 +155,7 @@ class CopyLastPublishedWorkfile(PreLaunchHook): reset_timer=True ) - while not sync_server.is_representaion_on_site( + while not sync_server.is_representation_on_site( project_name, workfile_representation["_id"], local_site_id diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index b505e25d2f..1f65ea9bda 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -925,7 +925,7 @@ class SyncServerModule(OpenPypeModule, ITrayModule): else: self.sync_server_thread.reset_timer() - def is_representaion_on_site( + def is_representation_on_site( self, project_name, representation_id, site_id ): # TODO implement From 44cfbf9f2922c80c21875f6f25658e6041c6b677 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 9 Nov 2022 12:27:07 +0100 Subject: [PATCH 165/243] added method to check if representation has all files on site --- .../modules/sync_server/sync_server_module.py | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index 1f65ea9bda..6250146523 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -926,10 +926,27 @@ class SyncServerModule(OpenPypeModule, ITrayModule): self.sync_server_thread.reset_timer() def is_representation_on_site( - self, project_name, representation_id, site_id + self, project_name, representation_id, site_name ): - # TODO implement - return False + """Checks if 'representation_id' has all files avail. on 'site_name'""" + representation = get_representation_by_id(project_name, + representation_id, + fields=["_id", "files"]) + if not representation: + return False + + on_site = False + for file_info in representation.get("files", []): + for site in file_info.get("sites", []): + if site["name"] != site_name: + continue + + if (site.get("progress") or site.get("error") or + not site.get("created_dt")): + return False + on_site = True + + return on_site def _reset_timer_with_rest_api(self): # POST to webserver sites to add to representations From 2e6f850b5d9a99d7063d0693414459834a6ba373 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 9 Nov 2022 12:48:52 +0100 Subject: [PATCH 166/243] small updates to docstrings --- openpype/modules/sync_server/sync_server_module.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index 6250146523..653ee50541 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -143,7 +143,7 @@ class SyncServerModule(OpenPypeModule, ITrayModule): 'project_name' must have synchronization enabled (globally or project only) - Used as a API endpoint from outside applications (Loader etc). + Used as an API endpoint from outside applications (Loader etc). Use 'force' to reset existing site. @@ -153,6 +153,8 @@ class SyncServerModule(OpenPypeModule, ITrayModule): site_name (string): name of configured and active site force (bool): reset site if exists priority (int): set priority + reset_timer (bool): if delay timer should be reset, eg. user mark + some representation to be synced manually Throws: SiteAlreadyPresentError - if adding already existing site and @@ -1601,12 +1603,12 @@ class SyncServerModule(OpenPypeModule, ITrayModule): Args: project_name (string): name of project - force to db connection as each file might come from different collection - new_file_id (string): + new_file_id (string): only present if file synced successfully file (dictionary): info about processed file (pulled from DB) representation (dictionary): parent repr of file (from DB) site (string): label ('gdrive', 'S3') error (string): exception message - progress (float): 0-1 of progress of upload/download + progress (float): 0-0.99 of progress of upload/download priority (int): 0-100 set priority Returns: From 5838f0f6097d57201402d9d5d360755f5c54b93c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Wed, 9 Nov 2022 17:09:38 +0100 Subject: [PATCH 167/243] clean --- openpype/client/entities.py | 1 - openpype/modules/sync_server/rest_api.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 91d4b499b0..43afccf2f1 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -6,7 +6,6 @@ that has project name as a context (e.g. on 'ProjectEntity'?). + We will need more specific functions doing wery specific queires really fast. """ -from datetime import datetime import re import collections diff --git a/openpype/modules/sync_server/rest_api.py b/openpype/modules/sync_server/rest_api.py index 51769cd4fb..a7d9dd80b7 100644 --- a/openpype/modules/sync_server/rest_api.py +++ b/openpype/modules/sync_server/rest_api.py @@ -30,8 +30,8 @@ class SyncServerModuleRestApi: self.reset_timer, ) - async def reset_timer(self, request): - # Force timer to run immediately + async def reset_timer(self, _request): + """Force timer to run immediately.""" self.module.reset_timer() return Response(status=200) From c096279cfcd9bdf5124a2e444da2830ebf300d56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Tue, 15 Nov 2022 09:06:09 +0100 Subject: [PATCH 168/243] logging format --- .../hooks/pre_copy_last_published_workfile.py | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index 0e561334e1..44144e5fff 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -75,16 +75,20 @@ class CopyLastPublishedWorkfile(PreLaunchHook): self.log.info( ( "Seems like old version of settings is used." - ' Can\'t access custom templates in host "{}".' - ).format(host_name) + ' Can\'t access custom templates in host "{}".'.format( + host_name + ) + ) ) return elif use_last_published_workfile is False: self.log.info( ( 'Project "{}" has turned off to use last published' - ' workfile as first workfile for host "{}"' - ).format(project_name, host_name) + ' workfile as first workfile for host "{}"'.format( + project_name, host_name + ) + ) ) return @@ -114,8 +118,8 @@ class CopyLastPublishedWorkfile(PreLaunchHook): None, ) if not subset_id: - self.log.debug('No any workfile for asset "{}".').format( - asset_doc["name"] + self.log.debug( + 'No any workfile for asset "{}".'.format(asset_doc["name"]) ) return @@ -131,8 +135,7 @@ class CopyLastPublishedWorkfile(PreLaunchHook): ( representation for representation in get_representations( - project_name, - version_ids=[last_version_doc["_id"]] + project_name, version_ids=[last_version_doc["_id"]] ) if representation["context"]["task"]["name"] == task_name ), @@ -141,8 +144,10 @@ class CopyLastPublishedWorkfile(PreLaunchHook): if not workfile_representation: self.log.debug( - 'No published workfile for task "{}" and host "{}".' - ).format(task_name, host_name) + 'No published workfile for task "{}" and host "{}".'.format( + task_name, host_name + ) + ) return local_site_id = get_local_site_id() @@ -152,13 +157,11 @@ class CopyLastPublishedWorkfile(PreLaunchHook): local_site_id, force=True, priority=99, - reset_timer=True + reset_timer=True, ) while not sync_server.is_representation_on_site( - project_name, - workfile_representation["_id"], - local_site_id + project_name, workfile_representation["_id"], local_site_id ): sleep(5) From 460adc767e6b81e62fdab5d2699f0c10c1023e9a Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 31 Oct 2022 19:36:44 +0800 Subject: [PATCH 169/243] Alembic Loader as Arnold Standin --- .../maya/plugins/load/load_abc_to_standin.py | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 openpype/hosts/maya/plugins/load/load_abc_to_standin.py diff --git a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py new file mode 100644 index 0000000000..defed4bd73 --- /dev/null +++ b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py @@ -0,0 +1,115 @@ +import os +import clique + +from openpype.pipeline import ( + load, + get_representation_path +) +from openpype.settings import get_project_settings + + +class AlembicStandinLoader(load.LoaderPlugin): + """Load Alembic as Arnold Standin""" + + families = ["model", "pointcache"] + representations = ["abc"] + + label = "Import Alembic as Standin" + order = -5 + icon = "code-fork" + color = "orange" + + def load(self, context, name, namespace, options): + + import maya.cmds as cmds + import pymel.core as pm + import mtoa.ui.arnoldmenu + from openpype.hosts.maya.api.pipeline import containerise + from openpype.hosts.maya.api.lib import unique_namespace + + version = context["version"] + version_data = version.get("data", {}) + + self.log.info("version_data: {}\n".format(version_data)) + + frameStart = version_data.get("frameStart", None) + + asset = context["asset"]["name"] + namespace = namespace or unique_namespace( + asset + "_", + prefix="_" if asset[0].isdigit() else "", + suffix="_", + ) + + #Root group + label = "{}:{}".format(namespace, name) + root = pm.group(name=label, empty=True) + + settings = get_project_settings(os.environ['AVALON_PROJECT']) + colors = settings["maya"]["load"]["colors"] + + c = colors.get('ass') + if c is not None: + cmds.setAttr(root + ".useOutlinerColor", 1) + cmds.setAttr(root + ".outlinerColor", + c[0], c[1], c[2]) + + transform_name = label + "_ABC" + + standinShape = pm.PyNode(mtoa.ui.arnoldmenu.createStandIn()) + standin = standinShape.getParent() + standin.rename(transform_name) + + pm.parent(standin, root) + + # Set the standin filepath + standinShape.dso.set(self.fname) + if frameStart is not None: + standinShape.useFrameExtension.set(1) + + nodes = [root, standin] + self[:] = nodes + + return containerise( + name=name, + namespace=namespace, + nodes=nodes, + context=context, + loader=self.__class__.__name__) + + def update(self, container, representation): + + import pymel.core as pm + + path = get_representation_path(representation) + + # Update the standin + standins = list() + members = pm.sets(container['objectName'], query=True) + for member in members: + shape = member.getShape() + if (shape and shape.type() == "aiStandIn"): + standins.append(shape) + + for standin in standins: + standin.dso.set(path) + standin.useFrameExtension.set(1) + + container = pm.PyNode(container["objectName"]) + container.representation.set(str(representation["_id"])) + + def switch(self, container, representation): + self.update(container, representation) + + def remove(self, container): + import maya.cmds as cmds + members = cmds.sets(container['objectName'], query=True) + cmds.lockNode(members, lock=False) + cmds.delete([container['objectName']] + members) + + # Clean up the namespace + try: + cmds.namespace(removeNamespace=container['namespace'], + deleteNamespaceContent=True) + except RuntimeError: + pass From ecbe06bdc8aeee163072f6173b96ba2886b0ebb1 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 31 Oct 2022 19:56:10 +0800 Subject: [PATCH 170/243] Alembic Loader as Arnold Standin --- openpype/hosts/maya/plugins/load/load_abc_to_standin.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py index defed4bd73..f39aa56650 100644 --- a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py +++ b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py @@ -1,5 +1,4 @@ import os -import clique from openpype.pipeline import ( load, @@ -41,7 +40,7 @@ class AlembicStandinLoader(load.LoaderPlugin): suffix="_", ) - #Root group + # Root group label = "{}:{}".format(namespace, name) root = pm.group(name=label, empty=True) From 2e6974b0640d0ad43bbacb2163a1cf85a7933522 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 1 Nov 2022 20:36:58 +0800 Subject: [PATCH 171/243] Alembic Loader as Arnold Standin --- .../maya/plugins/load/load_abc_to_standin.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py index f39aa56650..68aeb24069 100644 --- a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py +++ b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py @@ -13,7 +13,7 @@ class AlembicStandinLoader(load.LoaderPlugin): families = ["model", "pointcache"] representations = ["abc"] - label = "Import Alembic as Standin" + label = "Import Alembic as Arnold Standin" order = -5 icon = "code-fork" color = "orange" @@ -21,7 +21,6 @@ class AlembicStandinLoader(load.LoaderPlugin): def load(self, context, name, namespace, options): import maya.cmds as cmds - import pymel.core as pm import mtoa.ui.arnoldmenu from openpype.hosts.maya.api.pipeline import containerise from openpype.hosts.maya.api.lib import unique_namespace @@ -42,7 +41,7 @@ class AlembicStandinLoader(load.LoaderPlugin): # Root group label = "{}:{}".format(namespace, name) - root = pm.group(name=label, empty=True) + root = cmds.group(name=label, empty=True) settings = get_project_settings(os.environ['AVALON_PROJECT']) colors = settings["maya"]["load"]["colors"] @@ -55,16 +54,17 @@ class AlembicStandinLoader(load.LoaderPlugin): transform_name = label + "_ABC" - standinShape = pm.PyNode(mtoa.ui.arnoldmenu.createStandIn()) - standin = standinShape.getParent() - standin.rename(transform_name) + standinShape = cmds.ls(mtoa.ui.arnoldmenu.createStandIn())[0] + standin = cmds.listRelatives(standinShape, parent=True, typ="transform") + standin = cmds.rename(standin, transform_name) + standinShape = cmds.listRelatives(standin, children=True)[0] - pm.parent(standin, root) + cmds.parent(standin, root) # Set the standin filepath - standinShape.dso.set(self.fname) + cmds.setAttr(standinShape + ".dso", self.fname, type="string") if frameStart is not None: - standinShape.useFrameExtension.set(1) + cmds.setAttr(standinShape + ".useFrameExtension", 1) nodes = [root, standin] self[:] = nodes From ce5d4c02fa7f31c7731a04f5580e52f042933353 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 1 Nov 2022 20:38:51 +0800 Subject: [PATCH 172/243] Alembic Loader as Arnold Standin --- openpype/hosts/maya/plugins/load/load_abc_to_standin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py index 68aeb24069..5d6c52eac9 100644 --- a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py +++ b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py @@ -55,7 +55,8 @@ class AlembicStandinLoader(load.LoaderPlugin): transform_name = label + "_ABC" standinShape = cmds.ls(mtoa.ui.arnoldmenu.createStandIn())[0] - standin = cmds.listRelatives(standinShape, parent=True, typ="transform") + standin = cmds.listRelatives(standinShape, parent=True, + typ="transform") standin = cmds.rename(standin, transform_name) standinShape = cmds.listRelatives(standin, children=True)[0] From c58ef40f15c405c615cb5d4ec8a566a00de5c2eb Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 9 Nov 2022 00:32:22 +0800 Subject: [PATCH 173/243] Alembic Loader as Arnold Standin --- .../hosts/maya/plugins/load/load_abc_to_standin.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py index 5d6c52eac9..94bb974917 100644 --- a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py +++ b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py @@ -10,7 +10,7 @@ from openpype.settings import get_project_settings class AlembicStandinLoader(load.LoaderPlugin): """Load Alembic as Arnold Standin""" - families = ["model", "pointcache"] + families = ["animation", "model", "pointcache"] representations = ["abc"] label = "Import Alembic as Arnold Standin" @@ -31,6 +31,7 @@ class AlembicStandinLoader(load.LoaderPlugin): self.log.info("version_data: {}\n".format(version_data)) frameStart = version_data.get("frameStart", None) + frameEnd = version_data.get("frameEnd", None) asset = context["asset"]["name"] namespace = namespace or unique_namespace( @@ -64,7 +65,13 @@ class AlembicStandinLoader(load.LoaderPlugin): # Set the standin filepath cmds.setAttr(standinShape + ".dso", self.fname, type="string") - if frameStart is not None: + cmds.setAttr(standinShape + ".abcFPS", 25) + + if frameStart is None: + cmds.setAttr(standinShape + ".useFrameExtension", 0) + elif frameStart == 1 and frameEnd == 1: + cmds.setAttr(standinShape + ".useFrameExtension", 0) + else: cmds.setAttr(standinShape + ".useFrameExtension", 1) nodes = [root, standin] @@ -93,7 +100,8 @@ class AlembicStandinLoader(load.LoaderPlugin): for standin in standins: standin.dso.set(path) - standin.useFrameExtension.set(1) + standin.useFrameExtension.set(0) + standin.abcFPS.set(25) container = pm.PyNode(container["objectName"]) container.representation.set(str(representation["_id"])) From 031465779bc4096a2848a545b52c37a44a010128 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 9 Nov 2022 08:39:52 +0800 Subject: [PATCH 174/243] Alembic Loader as Arnold Standin --- .../hosts/maya/plugins/load/load_abc_to_standin.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py index 94bb974917..19e60d33da 100644 --- a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py +++ b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py @@ -1,6 +1,7 @@ import os from openpype.pipeline import ( + legacy_io, load, get_representation_path ) @@ -46,6 +47,7 @@ class AlembicStandinLoader(load.LoaderPlugin): settings = get_project_settings(os.environ['AVALON_PROJECT']) colors = settings["maya"]["load"]["colors"] + fps = legacy_io.Session["AVALON_FPS"] c = colors.get('ass') if c is not None: @@ -65,12 +67,14 @@ class AlembicStandinLoader(load.LoaderPlugin): # Set the standin filepath cmds.setAttr(standinShape + ".dso", self.fname, type="string") - cmds.setAttr(standinShape + ".abcFPS", 25) + cmds.setAttr(standinShape + ".abcFPS", float(fps)) if frameStart is None: cmds.setAttr(standinShape + ".useFrameExtension", 0) + elif frameStart == 1 and frameEnd == 1: cmds.setAttr(standinShape + ".useFrameExtension", 0) + else: cmds.setAttr(standinShape + ".useFrameExtension", 1) @@ -89,7 +93,7 @@ class AlembicStandinLoader(load.LoaderPlugin): import pymel.core as pm path = get_representation_path(representation) - + fps = legacy_io.Session["AVALON_FPS"] # Update the standin standins = list() members = pm.sets(container['objectName'], query=True) @@ -101,7 +105,7 @@ class AlembicStandinLoader(load.LoaderPlugin): for standin in standins: standin.dso.set(path) standin.useFrameExtension.set(0) - standin.abcFPS.set(25) + standin.abcFPS.set(float(fps)) container = pm.PyNode(container["objectName"]) container.representation.set(str(representation["_id"])) From 6e94a81393884cab2c3b2798e2c765c08617c4d1 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 10 Nov 2022 19:09:13 +0800 Subject: [PATCH 175/243] Alembic Loader as Arnold Standin --- .../maya/plugins/load/load_abc_to_standin.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py index 19e60d33da..a192d9c357 100644 --- a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py +++ b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py @@ -28,11 +28,10 @@ class AlembicStandinLoader(load.LoaderPlugin): version = context["version"] version_data = version.get("data", {}) - + family = version["data"]["families"] self.log.info("version_data: {}\n".format(version_data)) - + self.log.info("family: {}\n".format(family)) frameStart = version_data.get("frameStart", None) - frameEnd = version_data.get("frameEnd", None) asset = context["asset"]["name"] namespace = namespace or unique_namespace( @@ -48,12 +47,14 @@ class AlembicStandinLoader(load.LoaderPlugin): settings = get_project_settings(os.environ['AVALON_PROJECT']) colors = settings["maya"]["load"]["colors"] fps = legacy_io.Session["AVALON_FPS"] - - c = colors.get('ass') + c = colors.get(family[0]) if c is not None: cmds.setAttr(root + ".useOutlinerColor", 1) cmds.setAttr(root + ".outlinerColor", - c[0], c[1], c[2]) + (float(c[0])/255), + (float(c[1])/255), + (float(c[2])/255) + ) transform_name = label + "_ABC" @@ -72,7 +73,7 @@ class AlembicStandinLoader(load.LoaderPlugin): if frameStart is None: cmds.setAttr(standinShape + ".useFrameExtension", 0) - elif frameStart == 1 and frameEnd == 1: + elif "model" in family: cmds.setAttr(standinShape + ".useFrameExtension", 0) else: From 66608300969101ee75cf68a8c7a86dfd7b7710d4 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 10 Nov 2022 19:10:41 +0800 Subject: [PATCH 176/243] Alembic Loader as Arnold Standin --- openpype/hosts/maya/plugins/load/load_abc_to_standin.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py index a192d9c357..8ce1aee3ac 100644 --- a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py +++ b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py @@ -30,7 +30,6 @@ class AlembicStandinLoader(load.LoaderPlugin): version_data = version.get("data", {}) family = version["data"]["families"] self.log.info("version_data: {}\n".format(version_data)) - self.log.info("family: {}\n".format(family)) frameStart = version_data.get("frameStart", None) asset = context["asset"]["name"] @@ -51,10 +50,10 @@ class AlembicStandinLoader(load.LoaderPlugin): if c is not None: cmds.setAttr(root + ".useOutlinerColor", 1) cmds.setAttr(root + ".outlinerColor", - (float(c[0])/255), - (float(c[1])/255), - (float(c[2])/255) - ) + (float(c[0])/255), + (float(c[1])/255), + (float(c[2])/255) + ) transform_name = label + "_ABC" From 7c3f625fe324328ed7e7a93e4adff4da7c1d6e8e Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 10 Nov 2022 19:11:56 +0800 Subject: [PATCH 177/243] Alembic Loader as Arnold Standin --- openpype/hosts/maya/plugins/load/load_abc_to_standin.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py index 8ce1aee3ac..d93c85f8a4 100644 --- a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py +++ b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py @@ -50,10 +50,10 @@ class AlembicStandinLoader(load.LoaderPlugin): if c is not None: cmds.setAttr(root + ".useOutlinerColor", 1) cmds.setAttr(root + ".outlinerColor", - (float(c[0])/255), - (float(c[1])/255), - (float(c[2])/255) - ) + (float(c[0])/255), + (float(c[1])/255), + (float(c[2])/255) + ) transform_name = label + "_ABC" From bee7b17ff2b612fab87c95cdfa4143659453d049 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 10 Nov 2022 19:13:04 +0800 Subject: [PATCH 178/243] Alembic Loader as Arnold Standin --- openpype/hosts/maya/plugins/load/load_abc_to_standin.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py index d93c85f8a4..dafe999d9d 100644 --- a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py +++ b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py @@ -50,9 +50,9 @@ class AlembicStandinLoader(load.LoaderPlugin): if c is not None: cmds.setAttr(root + ".useOutlinerColor", 1) cmds.setAttr(root + ".outlinerColor", - (float(c[0])/255), - (float(c[1])/255), - (float(c[2])/255) + (float(c[0])/255), + (float(c[1])/255), + (float(c[2])/255) ) transform_name = label + "_ABC" From 3649ee7e4e163f45472c13f5f4bb74a65175e979 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 10 Nov 2022 19:15:11 +0800 Subject: [PATCH 179/243] Alembic Loader as Arnold Standin --- openpype/hosts/maya/plugins/load/load_abc_to_standin.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py index dafe999d9d..d93c85f8a4 100644 --- a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py +++ b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py @@ -50,9 +50,9 @@ class AlembicStandinLoader(load.LoaderPlugin): if c is not None: cmds.setAttr(root + ".useOutlinerColor", 1) cmds.setAttr(root + ".outlinerColor", - (float(c[0])/255), - (float(c[1])/255), - (float(c[2])/255) + (float(c[0])/255), + (float(c[1])/255), + (float(c[2])/255) ) transform_name = label + "_ABC" From cd27df0e8d35ccb94a4cfde09f399cbb6319a1c4 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 10 Nov 2022 19:16:10 +0800 Subject: [PATCH 180/243] Alembic Loader as Arnold Standin --- openpype/hosts/maya/plugins/load/load_abc_to_standin.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py index d93c85f8a4..8ce1aee3ac 100644 --- a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py +++ b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py @@ -50,10 +50,10 @@ class AlembicStandinLoader(load.LoaderPlugin): if c is not None: cmds.setAttr(root + ".useOutlinerColor", 1) cmds.setAttr(root + ".outlinerColor", - (float(c[0])/255), - (float(c[1])/255), - (float(c[2])/255) - ) + (float(c[0])/255), + (float(c[1])/255), + (float(c[2])/255) + ) transform_name = label + "_ABC" From 3186acc83e967f726ceec6c975fc74d6ea6cd8a2 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 10 Nov 2022 19:19:11 +0800 Subject: [PATCH 181/243] Alembic Loader as Arnold Standin --- openpype/hosts/maya/plugins/load/load_abc_to_standin.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py index 8ce1aee3ac..9583063c7e 100644 --- a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py +++ b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py @@ -30,6 +30,7 @@ class AlembicStandinLoader(load.LoaderPlugin): version_data = version.get("data", {}) family = version["data"]["families"] self.log.info("version_data: {}\n".format(version_data)) + self.log.info("family: {}\n".format(family)) frameStart = version_data.get("frameStart", None) asset = context["asset"]["name"] @@ -48,12 +49,12 @@ class AlembicStandinLoader(load.LoaderPlugin): fps = legacy_io.Session["AVALON_FPS"] c = colors.get(family[0]) if c is not None: + r = (float(c[0]) / 255) + g = (float(c[1]) / 255) + b = (float(c[2]) / 255) cmds.setAttr(root + ".useOutlinerColor", 1) cmds.setAttr(root + ".outlinerColor", - (float(c[0])/255), - (float(c[1])/255), - (float(c[2])/255) - ) + r, g, b) transform_name = label + "_ABC" From cf8bd8eb59590df3b2a196d68bbb47e29fcd862f Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 14 Nov 2022 17:57:32 +0800 Subject: [PATCH 182/243] Alembic Loader as Arnold Standin --- openpype/hosts/maya/plugins/load/load_abc_to_standin.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py index 9583063c7e..605a492e4d 100644 --- a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py +++ b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py @@ -98,6 +98,7 @@ class AlembicStandinLoader(load.LoaderPlugin): # Update the standin standins = list() members = pm.sets(container['objectName'], query=True) + self.log.info("container:{}".format(container)) for member in members: shape = member.getShape() if (shape and shape.type() == "aiStandIn"): @@ -105,8 +106,11 @@ class AlembicStandinLoader(load.LoaderPlugin): for standin in standins: standin.dso.set(path) - standin.useFrameExtension.set(0) standin.abcFPS.set(float(fps)) + if "modelMain" in container['objectName']: + standin.useFrameExtension.set(0) + else: + standin.useFrameExtension.set(1) container = pm.PyNode(container["objectName"]) container.representation.set(str(representation["_id"])) From 93bff0c038c262290c5d8e0b5e28847c3a210777 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Wed, 16 Nov 2022 03:40:10 +0000 Subject: [PATCH 183/243] [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 1953d0d6a5..268f33083a 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.14.7-nightly.4" +__version__ = "3.14.7-nightly.5" From 35b43f34ebbea13407154445369a4f8cdb15cf78 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Nov 2022 23:23:26 +0000 Subject: [PATCH 184/243] Bump loader-utils from 1.4.1 to 1.4.2 in /website Bumps [loader-utils](https://github.com/webpack/loader-utils) from 1.4.1 to 1.4.2. - [Release notes](https://github.com/webpack/loader-utils/releases) - [Changelog](https://github.com/webpack/loader-utils/blob/v1.4.2/CHANGELOG.md) - [Commits](https://github.com/webpack/loader-utils/compare/v1.4.1...v1.4.2) --- updated-dependencies: - dependency-name: loader-utils dependency-type: indirect ... Signed-off-by: dependabot[bot] --- website/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website/yarn.lock b/website/yarn.lock index 177a4a3802..220a489dfa 100644 --- a/website/yarn.lock +++ b/website/yarn.lock @@ -4812,9 +4812,9 @@ loader-runner@^4.2.0: integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw== loader-utils@^1.4.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.1.tgz#278ad7006660bccc4d2c0c1578e17c5c78d5c0e0" - integrity sha512-1Qo97Y2oKaU+Ro2xnDMR26g1BwMT29jNbem1EvcujW2jqt+j5COXyscjM7bLQkM9HaxI7pkWeW7gnI072yMI9Q== + version "1.4.2" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.2.tgz#29a957f3a63973883eb684f10ffd3d151fec01a3" + integrity sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg== dependencies: big.js "^5.2.2" emojis-list "^3.0.0" From ece1e8b9137d5a95d412b42b0e2b2fc5b4a9176a Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 16 Nov 2022 14:51:10 +0100 Subject: [PATCH 185/243] OP-4394 - Hound --- .../webpublisher/plugins/publish/collect_published_files.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py b/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py index 181f8b4ab7..79ed499a20 100644 --- a/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py +++ b/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py @@ -253,7 +253,7 @@ class CollectPublishedFiles(pyblish.api.ContextPlugin): if ext: ext = ext.lower() if ext.startswith("."): - ext = ext[1:] + ext = ext[1:] lower_extensions.add(ext) # all extensions setting From 4b95ad68168b138070171c862e0afaf4c08fb9f0 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 16 Nov 2022 15:57:30 +0100 Subject: [PATCH 186/243] OP-4394 - use lowercased extension in ExtractReview There might be uppercased extension sent in by accident (.PNG), which would make all checks against set of extension not work. --- openpype/plugins/publish/extract_review.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/openpype/plugins/publish/extract_review.py b/openpype/plugins/publish/extract_review.py index 1f9b30fba3..982bd9dc24 100644 --- a/openpype/plugins/publish/extract_review.py +++ b/openpype/plugins/publish/extract_review.py @@ -152,7 +152,7 @@ class ExtractReview(pyblish.api.InstancePlugin): if input_ext.startswith("."): input_ext = input_ext[1:] - if input_ext not in self.supported_exts: + if input_ext.lower() not in self.supported_exts: self.log.info( "Representation has unsupported extension \"{}\"".format( input_ext @@ -179,7 +179,7 @@ class ExtractReview(pyblish.api.InstancePlugin): single_frame_image = False if len(input_filepaths) == 1: ext = os.path.splitext(input_filepaths[0])[-1] - single_frame_image = ext in IMAGE_EXTENSIONS + single_frame_image = ext.lower() in IMAGE_EXTENSIONS filtered_defs = [] for output_def in output_defs: @@ -501,7 +501,7 @@ class ExtractReview(pyblish.api.InstancePlugin): first_sequence_frame += handle_start ext = os.path.splitext(repre["files"][0])[1].replace(".", "") - if ext in self.alpha_exts: + if ext.lower() in self.alpha_exts: input_allow_bg = True return { @@ -934,6 +934,8 @@ class ExtractReview(pyblish.api.InstancePlugin): if output_ext.startswith("."): output_ext = output_ext[1:] + output_ext = output_ext.lower() + # Store extension to representation new_repre["ext"] = output_ext From c028bb2a9446f5a7891a7a42427a62aa0f3a0886 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Wed, 16 Nov 2022 18:37:24 +0100 Subject: [PATCH 187/243] Update openpype/client/entities.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/client/entities.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index bbef8dc65e..38d6369d09 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -389,7 +389,7 @@ def get_subset_by_name(project_name, subset_name, asset_id, fields=None): returned if 'None' is passed. Returns: - Union[str, Dict]: None if subset with specified filters was not found. + Union[None, Dict[str, Any]]: None if subset with specified filters was not found. or dict subset document which can be reduced to specified 'fields'. From 24da47332bbd0951acd621fbb54274153a8a1e02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Wed, 16 Nov 2022 18:56:18 +0100 Subject: [PATCH 188/243] :bug: fix representation creation --- .../traypublisher/plugins/publish/collect_online_file.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_online_file.py b/openpype/hosts/traypublisher/plugins/publish/collect_online_file.py index 459ee463aa..82c4870fe4 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_online_file.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_online_file.py @@ -13,12 +13,11 @@ class CollectOnlineFile(pyblish.api.InstancePlugin): def process(self, instance): file = Path(instance.data["creator_attributes"]["path"]) - if not instance.data.get("representations"): - instance.data["representations"] = [ - { + instance.data["representations"].append( + { "name": file.suffix.lstrip("."), "ext": file.suffix.lstrip("."), "files": file.name, "stagingDir": file.parent.as_posix() - } - ] + } + ) From 45c6a9ab93a8c5ae0b830190eaabd559d8c369b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Wed, 16 Nov 2022 18:56:36 +0100 Subject: [PATCH 189/243] :recycle: refactor code --- .../plugins/create/create_online.py | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_online.py b/openpype/hosts/traypublisher/plugins/create/create_online.py index 5a6373730d..19f956a50e 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_online.py +++ b/openpype/hosts/traypublisher/plugins/create/create_online.py @@ -38,23 +38,31 @@ class OnlineCreator(TrayPublishCreator): return "fa.file" def create(self, subset_name, instance_data, pre_create_data): - if not pre_create_data.get("representation_file")["filenames"]: + repr_file = pre_create_data.get("representation_file") + if not repr_file: raise CreatorError("No files specified") - asset = get_asset_by_name(self.project_name, instance_data["asset"]) - origin_basename = Path(pre_create_data.get( - "representation_file")["filenames"][0]).stem + files = repr_file.get("filenames") + if not files: + # this should never happen + raise CreatorError("Missing files from representation") + origin_basename = Path(files[0]).stem + + asset = get_asset_by_name( + self.project_name, instance_data["asset"], fields=["_id"]) if get_subset_by_name( - self.project_name, origin_basename, asset["_id"]): + self.project_name, origin_basename, asset["_id"], + fields=["_id"]): raise CreatorError(f"subset with {origin_basename} already " "exists in selected asset") instance_data["originalBasename"] = origin_basename subset_name = origin_basename - path = (Path(pre_create_data.get("representation_file")["directory"]) / pre_create_data.get("representation_file")["filenames"][0]).as_posix() # noqa - instance_data["creator_attributes"] = {"path": path} + instance_data["creator_attributes"] = { + "path": (Path(repr_file["directory"]) / files[0]).as_posix() + } # Create new instance new_instance = CreatedInstance(self.family, subset_name, From 3357392e71c5a1b53747d56c3430897f68a8995b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Wed, 16 Nov 2022 18:59:21 +0100 Subject: [PATCH 190/243] :rotating_light: fix :dog: --- openpype/client/entities.py | 4 ++-- .../traypublisher/plugins/publish/collect_online_file.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 38d6369d09..c415be8816 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -389,8 +389,8 @@ def get_subset_by_name(project_name, subset_name, asset_id, fields=None): returned if 'None' is passed. Returns: - Union[None, Dict[str, Any]]: None if subset with specified filters was not found. - or dict subset document which can be reduced to + Union[None, Dict[str, Any]]: None if subset with specified filters was + not found or dict subset document which can be reduced to specified 'fields'. """ diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_online_file.py b/openpype/hosts/traypublisher/plugins/publish/collect_online_file.py index 82c4870fe4..a3f86afa13 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_online_file.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_online_file.py @@ -15,9 +15,9 @@ class CollectOnlineFile(pyblish.api.InstancePlugin): instance.data["representations"].append( { - "name": file.suffix.lstrip("."), - "ext": file.suffix.lstrip("."), - "files": file.name, - "stagingDir": file.parent.as_posix() + "name": file.suffix.lstrip("."), + "ext": file.suffix.lstrip("."), + "files": file.name, + "stagingDir": file.parent.as_posix() } ) From 64e5af230a3509ef16d8c0ee0fc826284960232b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 18 Nov 2022 12:54:50 +0100 Subject: [PATCH 191/243] OP-4394 - removed explicit lower from repre ext to not shadow upper case issue Using lower here would hide possibly broken representation, as we would expect both repre["ext"] and repre["name"] be lowercased. In case the aren't review won't get created >> someone will notice and fix issues on source representation. --- openpype/plugins/publish/extract_review.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_review.py b/openpype/plugins/publish/extract_review.py index 982bd9dc24..f299d1c6e9 100644 --- a/openpype/plugins/publish/extract_review.py +++ b/openpype/plugins/publish/extract_review.py @@ -152,7 +152,7 @@ class ExtractReview(pyblish.api.InstancePlugin): if input_ext.startswith("."): input_ext = input_ext[1:] - if input_ext.lower() not in self.supported_exts: + if input_ext not in self.supported_exts: self.log.info( "Representation has unsupported extension \"{}\"".format( input_ext From 69ddc20e3c4003db2285d2095f45c0e585cae001 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 18 Nov 2022 14:34:42 +0100 Subject: [PATCH 192/243] include secrets module to python_2 vendor --- .../vendor/python/python_2/secrets/LICENSE | 21 +++ .../python/python_2/secrets/__init__.py | 16 +++ .../vendor/python/python_2/secrets/secrets.py | 132 ++++++++++++++++++ 3 files changed, 169 insertions(+) create mode 100644 openpype/vendor/python/python_2/secrets/LICENSE create mode 100644 openpype/vendor/python/python_2/secrets/__init__.py create mode 100644 openpype/vendor/python/python_2/secrets/secrets.py diff --git a/openpype/vendor/python/python_2/secrets/LICENSE b/openpype/vendor/python/python_2/secrets/LICENSE new file mode 100644 index 0000000000..d3211e4d9f --- /dev/null +++ b/openpype/vendor/python/python_2/secrets/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2019 Scaleway + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/openpype/vendor/python/python_2/secrets/__init__.py b/openpype/vendor/python/python_2/secrets/__init__.py new file mode 100644 index 0000000000..c29ee61be1 --- /dev/null +++ b/openpype/vendor/python/python_2/secrets/__init__.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + + +__version__ = "1.0.6" + +# Emulates __all__ for Python2 +from .secrets import ( + choice, + randbelow, + randbits, + SystemRandom, + token_bytes, + token_hex, + token_urlsafe, + compare_digest +) diff --git a/openpype/vendor/python/python_2/secrets/secrets.py b/openpype/vendor/python/python_2/secrets/secrets.py new file mode 100644 index 0000000000..967d2862d9 --- /dev/null +++ b/openpype/vendor/python/python_2/secrets/secrets.py @@ -0,0 +1,132 @@ +# -*- coding: utf-8 -*- +"""Generate cryptographically strong pseudo-random numbers suitable for + +managing secrets such as account authentication, tokens, and similar. + + +See PEP 506 for more information. + +https://www.python.org/dev/peps/pep-0506/ + + +""" + + +__all__ = ['choice', 'randbelow', 'randbits', 'SystemRandom', + + 'token_bytes', 'token_hex', 'token_urlsafe', + + 'compare_digest', + + ] + +import os +import sys +from random import SystemRandom + +import base64 + +import binascii + + +# hmac.compare_digest did appear in python 2.7.7 +if sys.version_info >= (2, 7, 7): + from hmac import compare_digest +else: + # If we use an older python version, we will define an equivalent method + def compare_digest(a, b): + """Compatibility compare_digest method for python < 2.7. + This method is NOT cryptographically secure and may be subject to + timing attacks, see https://docs.python.org/2/library/hmac.html + """ + return a == b + + +_sysrand = SystemRandom() + + +randbits = _sysrand.getrandbits + +choice = _sysrand.choice + + +def randbelow(exclusive_upper_bound): + + """Return a random int in the range [0, n).""" + + if exclusive_upper_bound <= 0: + + raise ValueError("Upper bound must be positive.") + + return _sysrand._randbelow(exclusive_upper_bound) + + +DEFAULT_ENTROPY = 32 # number of bytes to return by default + + +def token_bytes(nbytes=None): + + """Return a random byte string containing *nbytes* bytes. + + + If *nbytes* is ``None`` or not supplied, a reasonable + + default is used. + + + >>> token_bytes(16) #doctest:+SKIP + + b'\\xebr\\x17D*t\\xae\\xd4\\xe3S\\xb6\\xe2\\xebP1\\x8b' + + + """ + + if nbytes is None: + + nbytes = DEFAULT_ENTROPY + + return os.urandom(nbytes) + + +def token_hex(nbytes=None): + + """Return a random text string, in hexadecimal. + + + The string has *nbytes* random bytes, each byte converted to two + + hex digits. If *nbytes* is ``None`` or not supplied, a reasonable + + default is used. + + + >>> token_hex(16) #doctest:+SKIP + + 'f9bf78b9a18ce6d46a0cd2b0b86df9da' + + + """ + + return binascii.hexlify(token_bytes(nbytes)).decode('ascii') + + +def token_urlsafe(nbytes=None): + + """Return a random URL-safe text string, in Base64 encoding. + + + The string has *nbytes* random bytes. If *nbytes* is ``None`` + + or not supplied, a reasonable default is used. + + + >>> token_urlsafe(16) #doctest:+SKIP + + 'Drmhze6EPcv0fN_81Bj-nA' + + + """ + + tok = token_bytes(nbytes) + + return base64.urlsafe_b64encode(tok).rstrip(b'=').decode('ascii') From 2db4cc43aae80fa8fb203ba775fff6fbe19a23c0 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 18 Nov 2022 17:52:57 +0100 Subject: [PATCH 193/243] Fix - typo --- openpype/hooks/pre_copy_last_published_workfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index 44144e5fff..26b43c39cb 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -38,7 +38,7 @@ class CopyLastPublishedWorkfile(PreLaunchHook): sync_server = self.modules_manager.get("sync_server") if not sync_server or not sync_server.enabled: - self.log.deubg("Sync server module is not enabled or available") + self.log.debug("Sync server module is not enabled or available") return # Check there is no workfile available From d076de0d077197bf3afc64edf9ab08837f2db549 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 18 Nov 2022 18:47:37 +0100 Subject: [PATCH 194/243] add more information about where ftrack service is storing versions or where is looking for versions --- openpype/modules/ftrack/scripts/sub_event_status.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/openpype/modules/ftrack/scripts/sub_event_status.py b/openpype/modules/ftrack/scripts/sub_event_status.py index 6c7ecb8351..eb3f63c04b 100644 --- a/openpype/modules/ftrack/scripts/sub_event_status.py +++ b/openpype/modules/ftrack/scripts/sub_event_status.py @@ -7,6 +7,8 @@ import signal import socket import datetime +import appdirs + import ftrack_api from openpype_modules.ftrack.ftrack_server.ftrack_server import FtrackServer from openpype_modules.ftrack.ftrack_server.lib import ( @@ -253,6 +255,15 @@ class StatusFactory: ) }) + items.append({ + "type": "label", + "value": ( + "Local versions dir: {}
Version repository path: {}" + ).format( + appdirs.user_data_dir("openpype", "pypeclub"), + os.environ.get("OPENPYPE_PATH") + ) + }) items.append({"type": "label", "value": "---"}) return items From 996cf3dcf95cd5042b2433780406ec5e74f1ae30 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 18 Nov 2022 20:52:55 +0100 Subject: [PATCH 195/243] Nuke: load image first frame --- openpype/hosts/nuke/plugins/load/load_image.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/plugins/load/load_image.py b/openpype/hosts/nuke/plugins/load/load_image.py index 3e81ef999b..3c5d4a7fc1 100644 --- a/openpype/hosts/nuke/plugins/load/load_image.py +++ b/openpype/hosts/nuke/plugins/load/load_image.py @@ -62,7 +62,9 @@ class LoadImage(load.LoaderPlugin): def load(self, context, name, namespace, options): self.log.info("__ options: `{}`".format(options)) - frame_number = options.get("frame_number", 1) + frame_number = options.get( + "frame_number", int(nuke.root()["first_frame"].getValue()) + ) version = context['version'] version_data = version.get("data", {}) From 8d1e720a889ebabc985505f0165ec11c4d6f7342 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 18 Nov 2022 21:19:23 +0100 Subject: [PATCH 196/243] Nuke: reset tab to first native tab --- openpype/hosts/nuke/api/pipeline.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/hosts/nuke/api/pipeline.py b/openpype/hosts/nuke/api/pipeline.py index c343c635fa..fb707ca44c 100644 --- a/openpype/hosts/nuke/api/pipeline.py +++ b/openpype/hosts/nuke/api/pipeline.py @@ -364,6 +364,9 @@ def containerise(node, set_avalon_knob_data(node, data) + # set tab to first native + node.setTab(0) + return node From c06f6891e8b021ab6e67f70a080202161059d8e8 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 18 Nov 2022 21:19:46 +0100 Subject: [PATCH 197/243] nuke: close property panel after node creation --- openpype/hosts/nuke/plugins/load/load_camera_abc.py | 3 +++ openpype/hosts/nuke/plugins/load/load_clip.py | 3 +++ openpype/hosts/nuke/plugins/load/load_effects.py | 3 +++ openpype/hosts/nuke/plugins/load/load_effects_ip.py | 3 +++ openpype/hosts/nuke/plugins/load/load_image.py | 4 ++++ openpype/hosts/nuke/plugins/load/load_model.py | 4 ++++ openpype/hosts/nuke/plugins/load/load_script_precomp.py | 3 +++ 7 files changed, 23 insertions(+) diff --git a/openpype/hosts/nuke/plugins/load/load_camera_abc.py b/openpype/hosts/nuke/plugins/load/load_camera_abc.py index f5dfc8c0ab..9fef7424c8 100644 --- a/openpype/hosts/nuke/plugins/load/load_camera_abc.py +++ b/openpype/hosts/nuke/plugins/load/load_camera_abc.py @@ -65,6 +65,9 @@ class AlembicCameraLoader(load.LoaderPlugin): object_name, file), inpanel=False ) + # hide property panel + camera_node.hideControlPanel() + camera_node.forceValidate() camera_node["frame_rate"].setValue(float(fps)) diff --git a/openpype/hosts/nuke/plugins/load/load_clip.py b/openpype/hosts/nuke/plugins/load/load_clip.py index b17356c5c7..565d777811 100644 --- a/openpype/hosts/nuke/plugins/load/load_clip.py +++ b/openpype/hosts/nuke/plugins/load/load_clip.py @@ -145,6 +145,9 @@ class LoadClip(plugin.NukeLoader): "Read", "name {}".format(read_name)) + # hide property panel + read_node.hideControlPanel() + # to avoid multiple undo steps for rest of process # we will switch off undo-ing with viewer_update_and_undo_stop(): diff --git a/openpype/hosts/nuke/plugins/load/load_effects.py b/openpype/hosts/nuke/plugins/load/load_effects.py index d164e0604c..cef4b0a5fc 100644 --- a/openpype/hosts/nuke/plugins/load/load_effects.py +++ b/openpype/hosts/nuke/plugins/load/load_effects.py @@ -89,6 +89,9 @@ class LoadEffects(load.LoaderPlugin): "Group", "name {}_1".format(object_name)) + # hide property panel + GN.hideControlPanel() + # adding content to the group node with GN: pre_node = nuke.createNode("Input") diff --git a/openpype/hosts/nuke/plugins/load/load_effects_ip.py b/openpype/hosts/nuke/plugins/load/load_effects_ip.py index 44565c139d..9bd40be816 100644 --- a/openpype/hosts/nuke/plugins/load/load_effects_ip.py +++ b/openpype/hosts/nuke/plugins/load/load_effects_ip.py @@ -90,6 +90,9 @@ class LoadEffectsInputProcess(load.LoaderPlugin): "Group", "name {}_1".format(object_name)) + # hide property panel + GN.hideControlPanel() + # adding content to the group node with GN: pre_node = nuke.createNode("Input") diff --git a/openpype/hosts/nuke/plugins/load/load_image.py b/openpype/hosts/nuke/plugins/load/load_image.py index 3e81ef999b..f7ce20eee9 100644 --- a/openpype/hosts/nuke/plugins/load/load_image.py +++ b/openpype/hosts/nuke/plugins/load/load_image.py @@ -112,6 +112,10 @@ class LoadImage(load.LoaderPlugin): r = nuke.createNode( "Read", "name {}".format(read_name)) + + # hide property panel + r.hideControlPanel() + r["file"].setValue(file) # Set colorspace defined in version data diff --git a/openpype/hosts/nuke/plugins/load/load_model.py b/openpype/hosts/nuke/plugins/load/load_model.py index 151401bad3..ad985e83c6 100644 --- a/openpype/hosts/nuke/plugins/load/load_model.py +++ b/openpype/hosts/nuke/plugins/load/load_model.py @@ -63,6 +63,10 @@ class AlembicModelLoader(load.LoaderPlugin): object_name, file), inpanel=False ) + + # hide property panel + model_node.hideControlPanel() + model_node.forceValidate() # Ensure all items are imported and selected. diff --git a/openpype/hosts/nuke/plugins/load/load_script_precomp.py b/openpype/hosts/nuke/plugins/load/load_script_precomp.py index 21e384b538..f0972f85d2 100644 --- a/openpype/hosts/nuke/plugins/load/load_script_precomp.py +++ b/openpype/hosts/nuke/plugins/load/load_script_precomp.py @@ -71,6 +71,9 @@ class LinkAsGroup(load.LoaderPlugin): "Precomp", "file {}".format(file)) + # hide property panel + P.hideControlPanel() + # Set colorspace defined in version data colorspace = context["version"]["data"].get("colorspace", None) self.log.info("colorspace: {}\n".format(colorspace)) From 554b3b256c4bfb368bb808376088e3315df54127 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Sat, 19 Nov 2022 03:37:23 +0000 Subject: [PATCH 198/243] [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 268f33083a..0116b49f4d 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.14.7-nightly.5" +__version__ = "3.14.7-nightly.6" From 33974c39d4aac0bb28ebd87e007acc166b8cd003 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Sat, 19 Nov 2022 17:13:50 +0800 Subject: [PATCH 199/243] aov Filtering --- openpype/hosts/maya/api/lib_renderproducts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/api/lib_renderproducts.py b/openpype/hosts/maya/api/lib_renderproducts.py index 58fcd2d281..6fde0df162 100644 --- a/openpype/hosts/maya/api/lib_renderproducts.py +++ b/openpype/hosts/maya/api/lib_renderproducts.py @@ -1110,7 +1110,7 @@ class RenderProductsRedshift(ARenderProducts): if light_groups_enabled: return products - beauty_name = "Beauty_other" if has_beauty_aov else "" + beauty_name = "BeautyAux" if has_beauty_aov else "" for camera in cameras: products.insert(0, RenderProduct(productName=beauty_name, From 94939e431a18cc45472276f01f96c71e5187dfc8 Mon Sep 17 00:00:00 2001 From: clement hector Date: Mon, 21 Nov 2022 16:18:26 +0100 Subject: [PATCH 200/243] rename families_to_upload to families_to_review + define it as class attribute --- openpype/hosts/tvpaint/plugins/publish/extract_sequence.py | 3 ++- openpype/settings/defaults/project_settings/tvpaint.json | 5 ++--- .../schemas/projects_schema/schema_project_tvpaint.json | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py b/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py index d8aef1ab6b..7d2e9c6f25 100644 --- a/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py +++ b/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py @@ -19,6 +19,7 @@ class ExtractSequence(pyblish.api.Extractor): label = "Extract Sequence" hosts = ["tvpaint"] families = ["review", "renderPass", "renderLayer", "renderScene"] + families_to_review = ["review"] # Modifiable with settings review_bg = [255, 255, 255, 255] @@ -129,7 +130,7 @@ class ExtractSequence(pyblish.api.Extractor): # Fill tags and new families from project settings tags = [] - if family_lowered in self.families_to_upload: + if family_lowered in self.families_to_review: tags.append("review") # Sequence of one frame diff --git a/openpype/settings/defaults/project_settings/tvpaint.json b/openpype/settings/defaults/project_settings/tvpaint.json index 2e413f50cd..9ccc318d70 100644 --- a/openpype/settings/defaults/project_settings/tvpaint.json +++ b/openpype/settings/defaults/project_settings/tvpaint.json @@ -12,11 +12,10 @@ 255, 255 ], - "families_to_upload": [ + "families_to_review": [ "review", "renderpass", - "renderlayer", - "renderscene" + "renderlayer" ] }, "ValidateProjectSettings": { diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json b/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json index 0392c9089b..61342ef738 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json @@ -59,8 +59,8 @@ }, { "type": "enum", - "key": "families_to_upload", - "label": "Families to upload", + "key": "families_to_review", + "label": "Families to review", "multiselection": true, "enum_items": [ {"review": "review"}, From cdb91c03795db7bc9b249e69dd605769562c11bc Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 21 Nov 2022 17:56:02 +0100 Subject: [PATCH 201/243] Added helper class for version resolving and sorting --- .../custom/plugins/GlobalJobPreLoad.py | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) diff --git a/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py b/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py index 9b35c9502d..6c3dd092fe 100644 --- a/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py +++ b/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py @@ -14,6 +14,137 @@ from Deadline.Scripting import ( ProcessUtils, ) +VERSION_REGEX = re.compile( + r"(?P0|[1-9]\d*)" + r"\.(?P0|[1-9]\d*)" + r"\.(?P0|[1-9]\d*)" + r"(?:-(?P[a-zA-Z\d\-.]*))?" + r"(?:\+(?P[a-zA-Z\d\-.]*))?" +) + + +class OpenPypeVersion: + """Fake semver version class for OpenPype version purposes. + + The version + """ + def __init__(self, major, minor, patch, prerelease, origin=None): + self.major = major + self.minor = minor + self.patch = patch + self.prerelease = prerelease + + is_valid = True + if not major or not minor or not patch: + is_valid = False + self.is_valid = is_valid + + if origin is None: + base = "{}.{}.{}".format(str(major), str(minor), str(patch)) + if not prerelease: + origin = base + else: + origin = "{}-{}".format(base, str(prerelease)) + + self.origin = origin + + @classmethod + def from_string(cls, version): + """Create an object of version from string. + + Args: + version (str): Version as a string. + + Returns: + Union[OpenPypeVersion, None]: Version object if input is nonempty + string otherwise None. + """ + + if not version: + return None + valid_parts = VERSION_REGEX.findall(version) + if len(valid_parts) != 1: + # Return invalid version with filled 'origin' attribute + return cls(None, None, None, None, origin=str(version)) + + # Unpack found version + major, minor, patch, pre, post = valid_parts[0] + prerelease = pre + # Post release is not important anymore and should be considered as + # part of prerelease + # - comparison is implemented to find suitable build and builds should + # never contain prerelease part so "not proper" parsing is + # acceptable for this use case. + if post: + prerelease = "{}+{}".format(pre, post) + + return cls( + int(major), int(minor), int(patch), prerelease, origin=version + ) + + def has_compatible_release(self, other): + """Version has compatible release as other version. + + Both major and minor versions must be exactly the same. In that case + a build can be considered as release compatible with any version. + + Args: + other (OpenPypeVersion): Other version. + + Returns: + bool: Version is release compatible with other version. + """ + + if self.is_valid and other.is_valid: + return self.major == other.major and self.minor == other.minor + return False + + def __bool__(self): + return self.is_valid + + def __repr__(self): + return "<{} {}>".format(self.__class__.__name__, self.origin) + + def __eq__(self, other): + if not isinstance(other, self.__class__): + return self.origin == other + return self.origin == other.origin + + def __lt__(self, other): + if not isinstance(other, self.__class__): + return None + + if not self.is_valid: + return True + + if not other.is_valid: + return False + + if self.origin == other.origin: + return None + + same_major = self.major == other.major + if not same_major: + return self.major < other.major + + same_minor = self.minor == other.minor + if not same_minor: + return self.minor < other.minor + + same_patch = self.patch == other.patch + if not same_patch: + return self.patch < other.patch + + if not self.prerelease: + return False + + if not other.prerelease: + return True + + pres = [self.prerelease, other.prerelease] + pres.sort() + return pres[0] == self.prerelease + def get_openpype_version_from_path(path, build=True): """Get OpenPype version from provided path. From b1e899d8ee2a79cd673bdf14bf4adf2134443dca Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 21 Nov 2022 17:57:10 +0100 Subject: [PATCH 202/243] Use full version for resolving and use specific build if matches requested version --- .../custom/plugins/GlobalJobPreLoad.py | 197 ++++++++++-------- 1 file changed, 110 insertions(+), 87 deletions(-) diff --git a/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py b/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py index 6c3dd092fe..375cf48b8f 100644 --- a/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py +++ b/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py @@ -152,9 +152,9 @@ def get_openpype_version_from_path(path, build=True): build (bool, optional): Get only builds, not sources Returns: - str or None: version of OpenPype if found. - + Union[OpenPypeVersion, None]: version of OpenPype if found. """ + # fix path for application bundle on macos if platform.system().lower() == "darwin": path = os.path.join(path, "Contents", "MacOS", "lib", "Python") @@ -177,8 +177,10 @@ def get_openpype_version_from_path(path, build=True): with open(version_file, "r") as vf: exec(vf.read(), version) - version_match = re.search(r"(\d+\.\d+.\d+).*", version["__version__"]) - return version_match[1] + version_str = version.get("__version__") + if version_str: + return OpenPypeVersion.from_string(version_str) + return None def get_openpype_executable(): @@ -190,6 +192,91 @@ def get_openpype_executable(): return exe_list, dir_list +def get_openpype_versions(exe_list, dir_list): + print(">>> Getting OpenPype executable ...") + openpype_versions = [] + + install_dir = DirectoryUtils.SearchDirectoryList(dir_list) + if install_dir: + print("--- Looking for OpenPype at: {}".format(install_dir)) + sub_dirs = [ + f.path for f in os.scandir(install_dir) + if f.is_dir() + ] + for subdir in sub_dirs: + version = get_openpype_version_from_path(subdir) + if not version: + continue + print(" - found: {} - {}".format(version, subdir)) + openpype_versions.append((version, subdir)) + return openpype_versions + + +def get_requested_openpype_executable( + exe, dir_list, requested_version +): + requested_version_obj = OpenPypeVersion.from_string(requested_version) + if not requested_version_obj: + print(( + ">>> Requested version does not match version regex \"{}\"" + ).format(VERSION_REGEX)) + return None + + print(( + ">>> Scanning for compatible requested version {}" + ).format(requested_version)) + openpype_versions = get_openpype_versions(dir_list) + if not openpype_versions: + return None + + # if looking for requested compatible version, + # add the implicitly specified to the list too. + if exe: + exe_dir = os.path.dirname(exe) + print("Looking for OpenPype at: {}".format(exe_dir)) + version = get_openpype_version_from_path(exe_dir) + if version: + print(" - found: {} - {}".format(version, exe_dir)) + openpype_versions.append((version, exe_dir)) + + matching_item = None + compatible_versions = [] + for version_item in openpype_versions: + version, version_dir = version_item + if requested_version_obj.has_compatible_release(version): + compatible_versions.append(version_item) + if version == requested_version_obj: + # Store version item if version match exactly + # - break if is found matching version + matching_item = version_item + break + + if not compatible_versions: + return None + + compatible_versions.sort(key=lambda item: item[0]) + if matching_item: + version, version_dir = matching_item + print(( + "*** Found exact match build version {} in {}" + ).format(version_dir, version)) + + else: + version, version_dir = compatible_versions[-1] + + print(( + "*** Latest compatible version found is {} in {}" + ).format(version_dir, version)) + + # create list of executables for different platform and let + # Deadline decide. + exe_list = [ + os.path.join(version_dir, "openpype_console.exe"), + os.path.join(version_dir, "openpype_console") + ] + return FileUtils.SearchFileList(";".join(exe_list)) + + def inject_openpype_environment(deadlinePlugin): """ Pull env vars from OpenPype and push them to rendering process. @@ -199,93 +286,29 @@ def inject_openpype_environment(deadlinePlugin): print(">>> Injecting OpenPype environments ...") try: - print(">>> Getting OpenPype executable ...") exe_list, dir_list = get_openpype_executable() - openpype_versions = [] - # if the job requires specific OpenPype version, - # lets go over all available and find compatible build. + exe = FileUtils.SearchFileList(exe_list) + requested_version = job.GetJobEnvironmentKeyValue("OPENPYPE_VERSION") if requested_version: - print(( - ">>> Scanning for compatible requested version {}" - ).format(requested_version)) - install_dir = DirectoryUtils.SearchDirectoryList(dir_list) - if install_dir: - print("--- Looking for OpenPype at: {}".format(install_dir)) - sub_dirs = [ - f.path for f in os.scandir(install_dir) - if f.is_dir() - ] - for subdir in sub_dirs: - version = get_openpype_version_from_path(subdir) - if not version: - continue - print(" - found: {} - {}".format(version, subdir)) - openpype_versions.append((version, subdir)) + exe = get_requested_openpype_executable( + exe, dir_list, requested_version + ) + if exe is None: + raise RuntimeError(( + "Cannot find compatible version available for version {}" + " requested by the job. Please add it through plugin" + " configuration in Deadline or install it to configured" + " directory." + ).format(requested_version)) - exe = FileUtils.SearchFileList(exe_list) - if openpype_versions: - # if looking for requested compatible version, - # add the implicitly specified to the list too. - print("Looking for OpenPype at: {}".format(os.path.dirname(exe))) - version = get_openpype_version_from_path( - os.path.dirname(exe)) - if version: - print(" - found: {} - {}".format( - version, os.path.dirname(exe) - )) - openpype_versions.append((version, os.path.dirname(exe))) - - if requested_version: - # sort detected versions - if openpype_versions: - # use natural sorting - openpype_versions.sort( - key=lambda ver: [ - int(t) if t.isdigit() else t.lower() - for t in re.split(r"(\d+)", ver[0]) - ]) - print(( - "*** Latest available version found is {}" - ).format(openpype_versions[-1][0])) - requested_major, requested_minor, _ = requested_version.split(".")[:3] # noqa: E501 - compatible_versions = [] - for version in openpype_versions: - v = version[0].split(".")[:3] - if v[0] == requested_major and v[1] == requested_minor: - compatible_versions.append(version) - if not compatible_versions: - raise RuntimeError( - ("Cannot find compatible version available " - "for version {} requested by the job. " - "Please add it through plugin configuration " - "in Deadline or install it to configured " - "directory.").format(requested_version)) - # sort compatible versions nad pick the last one - compatible_versions.sort( - key=lambda ver: [ - int(t) if t.isdigit() else t.lower() - for t in re.split(r"(\d+)", ver[0]) - ]) - print(( - "*** Latest compatible version found is {}" - ).format(compatible_versions[-1][0])) - # create list of executables for different platform and let - # Deadline decide. - exe_list = [ - os.path.join( - compatible_versions[-1][1], "openpype_console.exe"), - os.path.join( - compatible_versions[-1][1], "openpype_console") - ] - exe = FileUtils.SearchFileList(";".join(exe_list)) - if exe == "": - raise RuntimeError( - "OpenPype executable was not found " + - "in the semicolon separated list " + - "\"" + ";".join(exe_list) + "\". " + - "The path to the render executable can be configured " + - "from the Plugin Configuration in the Deadline Monitor.") + if not exe: + raise RuntimeError(( + "OpenPype executable was not found in the semicolon " + "separated list \"{}\"." + "The path to the render executable can be configured" + " from the Plugin Configuration in the Deadline Monitor." + ).format(";".join(exe_list))) print("--- OpenPype executable: {}".format(exe)) From dbc72502b4cbf9859493d43ce90141f84ecc9420 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 21 Nov 2022 17:57:37 +0100 Subject: [PATCH 203/243] few formatting changes --- .../custom/plugins/GlobalJobPreLoad.py | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py b/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py index 375cf48b8f..78e1371eee 100644 --- a/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py +++ b/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py @@ -326,22 +326,22 @@ def inject_openpype_environment(deadlinePlugin): export_url ] - add_args = {} - add_args['project'] = \ - job.GetJobEnvironmentKeyValue('AVALON_PROJECT') - add_args['asset'] = job.GetJobEnvironmentKeyValue('AVALON_ASSET') - add_args['task'] = job.GetJobEnvironmentKeyValue('AVALON_TASK') - add_args['app'] = job.GetJobEnvironmentKeyValue('AVALON_APP_NAME') - add_args["envgroup"] = "farm" + add_kwargs = { + "project": job.GetJobEnvironmentKeyValue("AVALON_PROJECT"), + "asset": job.GetJobEnvironmentKeyValue("AVALON_ASSET"), + "task": job.GetJobEnvironmentKeyValue("AVALON_TASK"), + "app": job.GetJobEnvironmentKeyValue("AVALON_APP_NAME"), + "envgroup": "farm" + } + if all(add_kwargs.values()): + for key, value in add_kwargs.items(): + args.extend(["--{}".format(key), value]) - if all(add_args.values()): - for key, value in add_args.items(): - args.append("--{}".format(key)) - args.append(value) else: - msg = "Required env vars: AVALON_PROJECT, AVALON_ASSET, " + \ - "AVALON_TASK, AVALON_APP_NAME" - raise RuntimeError(msg) + raise RuntimeError(( + "Missing required env vars: AVALON_PROJECT, AVALON_ASSET," + " AVALON_TASK, AVALON_APP_NAME" + )) if not os.environ.get("OPENPYPE_MONGO"): print(">>> Missing OPENPYPE_MONGO env var, process won't work") @@ -362,12 +362,12 @@ def inject_openpype_environment(deadlinePlugin): print(">>> Loading file ...") with open(export_url) as fp: contents = json.load(fp) - for key, value in contents.items(): - deadlinePlugin.SetProcessEnvironmentVariable(key, value) + + for key, value in contents.items(): + deadlinePlugin.SetProcessEnvironmentVariable(key, value) script_url = job.GetJobPluginInfoKeyValue("ScriptFilename") if script_url: - script_url = script_url.format(**contents).replace("\\", "/") print(">>> Setting script path {}".format(script_url)) job.SetJobPluginInfoKeyValue("ScriptFilename", script_url) From 61e5dc3fc9c326a90601e774722ae30b419ef390 Mon Sep 17 00:00:00 2001 From: Thomas Fricard <51854004+friquette@users.noreply.github.com> Date: Mon, 21 Nov 2022 18:21:04 +0100 Subject: [PATCH 204/243] change order of default value Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/settings/defaults/project_settings/tvpaint.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/settings/defaults/project_settings/tvpaint.json b/openpype/settings/defaults/project_settings/tvpaint.json index 9ccc318d70..e03ce32030 100644 --- a/openpype/settings/defaults/project_settings/tvpaint.json +++ b/openpype/settings/defaults/project_settings/tvpaint.json @@ -14,8 +14,8 @@ ], "families_to_review": [ "review", - "renderpass", - "renderlayer" + "renderlayer", + "renderscene" ] }, "ValidateProjectSettings": { From e24c2f853b5b976e1c441470cf6e7f435e2c0815 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 21 Nov 2022 19:15:34 +0100 Subject: [PATCH 205/243] attribute definitions can be hidden and disabled --- openpype/lib/attribute_definitions.py | 32 +++++++++++++++++++++------ 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/openpype/lib/attribute_definitions.py b/openpype/lib/attribute_definitions.py index 6baeaec045..ed151bbe4e 100644 --- a/openpype/lib/attribute_definitions.py +++ b/openpype/lib/attribute_definitions.py @@ -105,11 +105,14 @@ class AbtractAttrDef(object): How to force to set `key` attribute? Args: - key(str): Under which key will be attribute value stored. - label(str): Attribute label. - tooltip(str): Attribute tooltip. - is_label_horizontal(bool): UI specific argument. Specify if label is + key (str): Under which key will be attribute value stored. + default (Any): Default value of an attribute. + label (str): Attribute label. + tooltip (str): Attribute tooltip. + is_label_horizontal (bool): UI specific argument. Specify if label is next to value input or ahead. + hidden (bool): Will be item hidden (for UI purposes). + disabled (bool): Item will be visible but disabled (for UI purposes). """ type_attributes = [] @@ -117,16 +120,29 @@ class AbtractAttrDef(object): is_value_def = True def __init__( - self, key, default, label=None, tooltip=None, is_label_horizontal=None + self, + key, + default, + label=None, + tooltip=None, + is_label_horizontal=None, + hidden=False, + disabled=False ): if is_label_horizontal is None: is_label_horizontal = True + + if hidden is None: + hidden = False + self.key = key self.label = label self.tooltip = tooltip self.default = default self.is_label_horizontal = is_label_horizontal - self._id = uuid.uuid4() + self.hidden = hidden + self.disabled = disabled + self._id = uuid.uuid4().hex self.__init__class__ = AbtractAttrDef @@ -173,7 +189,9 @@ class AbtractAttrDef(object): "label": self.label, "tooltip": self.tooltip, "default": self.default, - "is_label_horizontal": self.is_label_horizontal + "is_label_horizontal": self.is_label_horizontal, + "hidden": self.hidden, + "disabled": self.disabled } for attr in self.type_attributes: data[attr] = getattr(self, attr) From 6abfa14e01d67eae20e7bb66c219feab99d70a37 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 21 Nov 2022 19:16:02 +0100 Subject: [PATCH 206/243] added special definition for hidden values --- openpype/lib/attribute_definitions.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/openpype/lib/attribute_definitions.py b/openpype/lib/attribute_definitions.py index ed151bbe4e..0df7b16e64 100644 --- a/openpype/lib/attribute_definitions.py +++ b/openpype/lib/attribute_definitions.py @@ -253,6 +253,26 @@ class UnknownDef(AbtractAttrDef): return value +class HiddenDef(AbtractAttrDef): + """Hidden value of Any type. + + This attribute can be used for UI purposes to pass values related + to other attributes (e.g. in multi-page UIs). + + Keep in mind the value should be possible to parse by json parser. + """ + + type = "hidden" + + def __init__(self, key, default=None, **kwargs): + kwargs["default"] = default + kwargs["hidden"] = True + super(UnknownDef, self).__init__(key, **kwargs) + + def convert_value(self, value): + return value + + class NumberDef(AbtractAttrDef): """Number definition. From fe392aa5db267ef09e0152d867eb02e45fee065e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 21 Nov 2022 19:16:41 +0100 Subject: [PATCH 207/243] implemented hidden widget --- openpype/tools/attribute_defs/widgets.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/openpype/tools/attribute_defs/widgets.py b/openpype/tools/attribute_defs/widgets.py index dc697b08a6..7f7c20009e 100644 --- a/openpype/tools/attribute_defs/widgets.py +++ b/openpype/tools/attribute_defs/widgets.py @@ -6,6 +6,7 @@ from Qt import QtWidgets, QtCore from openpype.lib.attribute_definitions import ( AbtractAttrDef, UnknownDef, + HiddenDef, NumberDef, TextDef, EnumDef, @@ -459,6 +460,29 @@ class UnknownAttrWidget(_BaseAttrDefWidget): self._input_widget.setText(str_value) +class HiddenAttrWidget(_BaseAttrDefWidget): + def _ui_init(self): + self.setVisible(False) + self._value = None + self._multivalue = False + + def setVisible(self, visible): + if visible: + visible = False + super(HiddenAttrWidget, self).setVisible(visible) + + def current_value(self): + if self._multivalue: + raise ValueError( + "{} can't output for multivalue.".format(self.__class__.__name__) + ) + return self._value + + def set_value(self, value, multivalue=False): + self._value = copy.deepcopy(value) + self._multivalue = multivalue + + class FileAttrWidget(_BaseAttrDefWidget): def _ui_init(self): input_widget = FilesWidget( From 068ec3f89809eca1fcff32d81e36158f88dc248a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 21 Nov 2022 19:17:09 +0100 Subject: [PATCH 208/243] enhanced attribute definitons widget --- openpype/tools/attribute_defs/widgets.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/openpype/tools/attribute_defs/widgets.py b/openpype/tools/attribute_defs/widgets.py index 7f7c20009e..6db6da58e1 100644 --- a/openpype/tools/attribute_defs/widgets.py +++ b/openpype/tools/attribute_defs/widgets.py @@ -23,6 +23,16 @@ from .files_widget import FilesWidget def create_widget_for_attr_def(attr_def, parent=None): + widget = _create_widget_for_attr_def(attr_def, parent) + if attr_def.hidden: + widget.setVisible(False) + + if attr_def.disabled: + widget.setEnabled(False) + return widget + + +def _create_widget_for_attr_def(attr_def, parent=None): if not isinstance(attr_def, AbtractAttrDef): raise TypeError("Unexpected type \"{}\" expected \"{}\"".format( str(type(attr_def)), AbtractAttrDef @@ -43,6 +53,9 @@ def create_widget_for_attr_def(attr_def, parent=None): if isinstance(attr_def, UnknownDef): return UnknownAttrWidget(attr_def, parent) + if isinstance(attr_def, HiddenDef): + return HiddenAttrWidget(attr_def, parent) + if isinstance(attr_def, FileDef): return FileAttrWidget(attr_def, parent) @@ -116,6 +129,10 @@ class AttributeDefinitionsWidget(QtWidgets.QWidget): self._current_keys.add(attr_def.key) widget = create_widget_for_attr_def(attr_def, self) + self._widgets.append(widget) + + if attr_def.hidden: + continue expand_cols = 2 if attr_def.is_value_def and attr_def.is_label_horizontal: @@ -134,7 +151,6 @@ class AttributeDefinitionsWidget(QtWidgets.QWidget): layout.addWidget( widget, row, col_num, 1, expand_cols ) - self._widgets.append(widget) row += 1 def set_value(self, value): From a606de5b76b63a6051731f292daa7b0420bfbbde Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 21 Nov 2022 19:17:56 +0100 Subject: [PATCH 209/243] don't add hidden widgets to publisher widgets --- openpype/tools/publisher/widgets/widgets.py | 26 +++++++++++++++------ 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index ce3d91ce63..a0d97245ba 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -9,6 +9,7 @@ import collections from Qt import QtWidgets, QtCore, QtGui import qtawesome +from openpype.lib.attribute_definitions import UnknownDef from openpype.tools.attribute_defs import create_widget_for_attr_def from openpype.tools import resources from openpype.tools.flickcharm import FlickCharm @@ -1303,6 +1304,13 @@ class CreatorAttrsWidget(QtWidgets.QWidget): else: widget.set_value(values, True) + widget.value_changed.connect(self._input_value_changed) + self._attr_def_id_to_instances[attr_def.id] = attr_instances + self._attr_def_id_to_attr_def[attr_def.id] = attr_def + + if attr_def.hidden: + continue + expand_cols = 2 if attr_def.is_value_def and attr_def.is_label_horizontal: expand_cols = 1 @@ -1321,13 +1329,8 @@ class CreatorAttrsWidget(QtWidgets.QWidget): content_layout.addWidget( widget, row, col_num, 1, expand_cols ) - row += 1 - widget.value_changed.connect(self._input_value_changed) - self._attr_def_id_to_instances[attr_def.id] = attr_instances - self._attr_def_id_to_attr_def[attr_def.id] = attr_def - self._scroll_area.setWidget(content_widget) self._content_widget = content_widget @@ -1421,8 +1424,17 @@ class PublishPluginAttrsWidget(QtWidgets.QWidget): widget = create_widget_for_attr_def( attr_def, content_widget ) - label = attr_def.label or attr_def.key - content_layout.addRow(label, widget) + hidden_widget = attr_def.hidden + # Hide unknown values of publish plugins + # - The keys in most of cases does not represent what would + # label represent + if isinstance(attr_def, UnknownDef): + widget.setVisible(False) + hidden_widget = True + + if not hidden_widget: + label = attr_def.label or attr_def.key + content_layout.addRow(label, widget) widget.value_changed.connect(self._input_value_changed) From 29cc9bdce61ea3ce1dc01d17bb05c3a2db3afffe Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 21 Nov 2022 19:39:10 +0100 Subject: [PATCH 210/243] Fix line length --- openpype/tools/attribute_defs/widgets.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/tools/attribute_defs/widgets.py b/openpype/tools/attribute_defs/widgets.py index 6db6da58e1..1ffb3d3799 100644 --- a/openpype/tools/attribute_defs/widgets.py +++ b/openpype/tools/attribute_defs/widgets.py @@ -489,9 +489,9 @@ class HiddenAttrWidget(_BaseAttrDefWidget): def current_value(self): if self._multivalue: - raise ValueError( - "{} can't output for multivalue.".format(self.__class__.__name__) - ) + raise ValueError("{} can't output for multivalue.".format( + self.__class__.__name__ + )) return self._value def set_value(self, value, multivalue=False): From 7bf1d0bc9b2efb05e9904a4784ded5ea2da5b717 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 22 Nov 2022 18:56:20 +0800 Subject: [PATCH 211/243] aov filtering --- openpype/hosts/maya/api/lib_renderproducts.py | 1 - .../modules/deadline/plugins/publish/submit_publish_job.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/api/lib_renderproducts.py b/openpype/hosts/maya/api/lib_renderproducts.py index 6fde0df162..c54e3ab3e0 100644 --- a/openpype/hosts/maya/api/lib_renderproducts.py +++ b/openpype/hosts/maya/api/lib_renderproducts.py @@ -1054,7 +1054,6 @@ class RenderProductsRedshift(ARenderProducts): # Any AOVs that still get processed, like Cryptomatte # by themselves are not multipart files. - # aov_multipart = not multipart # Redshift skips rendering of masterlayer without AOV suffix # when a Beauty AOV is rendered. It overrides the main layer. diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index c1e9dd4015..6362b4ca65 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -500,7 +500,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): if instance_data.get("multipartExr"): preview = True - self.log.info("preview:{}".format(preview)) + self.log.debug("preview:{}".format(preview)) new_instance = deepcopy(instance_data) new_instance["subset"] = subset_name new_instance["subsetGroup"] = group_name @@ -543,7 +543,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): if new_instance.get("extendFrames", False): self._copy_extend_frames(new_instance, rep) instances.append(new_instance) - self.log.info("instances:{}".format(instances)) + self.log.debug("instances:{}".format(instances)) return instances def _get_representations(self, instance, exp_files): From 996bd4897b80ae72a06a8bbc81c1b69d471485ac Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 22 Nov 2022 12:04:47 +0100 Subject: [PATCH 212/243] tabs widget can set current tab by index --- openpype/tools/publisher/widgets/tabs_widget.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/openpype/tools/publisher/widgets/tabs_widget.py b/openpype/tools/publisher/widgets/tabs_widget.py index 84638a002c..eb3eda8c19 100644 --- a/openpype/tools/publisher/widgets/tabs_widget.py +++ b/openpype/tools/publisher/widgets/tabs_widget.py @@ -68,7 +68,16 @@ class PublisherTabsWidget(QtWidgets.QFrame): self.set_current_tab(identifier) return button + def get_tab_by_index(self, index): + if index < 0 or index > self._btns_layout.count(): + return None + item = self._btns_layout.itemAt(index) + return item.widget() + def set_current_tab(self, identifier): + if isinstance(identifier, int): + identifier = self.get_tab_by_index(identifier) + if isinstance(identifier, PublisherTabBtn): identifier = identifier.identifier From 430f30c05e3dc53184277ed121efd0fdcd003b3a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 22 Nov 2022 12:05:22 +0100 Subject: [PATCH 213/243] added helper methods to know on which tab we are --- openpype/tools/publisher/window.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index f107c0e505..3879e37ad7 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -539,6 +539,18 @@ class PublisherWindow(QtWidgets.QDialog): def _go_to_report_tab(self): self._set_current_tab("report") + def _is_on_create_tab(self): + self._is_current_tab("create") + + def _is_on_publish_tab(self): + self._is_current_tab("publish") + + def _is_on_details_tab(self): + self._is_current_tab("details") + + def _is_on_report_tab(self): + self._is_current_tab("report") + def _set_publish_overlay_visibility(self, visible): if visible: widget = self._publish_overlay From ac9b9b208e055c856c32313a87d20bf1dbf403c3 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 22 Nov 2022 12:54:28 +0100 Subject: [PATCH 214/243] OP-4196 - safer getter for published_path published_path might be missing in case of thumbnail not getting published. This implementation takes from staging if published_path not present --- .../slack/plugins/publish/integrate_slack_api.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py index 643e55915b..f40a13db9f 100644 --- a/openpype/modules/slack/plugins/publish/integrate_slack_api.py +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -142,13 +142,19 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): def _get_thumbnail_path(self, instance): """Returns abs url for thumbnail if present in instance repres""" - published_path = None + thumbnail_path = None for repre in instance.data.get("representations", []): if repre.get('thumbnail') or "thumbnail" in repre.get('tags', []): - if os.path.exists(repre["published_path"]): - published_path = repre["published_path"] + self.log.info(repre) + repre_thumbnail_path = ( + repre.get("published_path") or + os.path.join(repre["stagingDir"], repre["files"]) + ) + if os.path.exists(repre_thumbnail_path): + self.log.info("exists") + thumbnail_path = repre_thumbnail_path break - return published_path + return thumbnail_path def _get_review_path(self, instance): """Returns abs url for review if present in instance repres""" From c61098b782492728f7dbbe667b2540b2805b35ba Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 22 Nov 2022 13:00:34 +0100 Subject: [PATCH 215/243] OP-4196 - fix when task_data is not dict In legacy cases task might be only string with its name, not structure with additional metadata (type etc.). This implementation handles that. --- .../modules/slack/plugins/publish/integrate_slack_api.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py index f40a13db9f..6138671180 100644 --- a/openpype/modules/slack/plugins/publish/integrate_slack_api.py +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -121,10 +121,13 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): ): fill_pairs.append(("task", task_data["name"])) - else: + elif isinstance(task_data, dict): for key, value in task_data.items(): fill_key = "task[{}]".format(key) fill_pairs.append((fill_key, value)) + else: + # fallback for legacy - if task_data is only task name + fill_pairs.append(("task", task_data)) self.log.debug("fill_pairs ::{}".format(fill_pairs)) multiple_case_variants = prepare_template_data(fill_pairs) From f993842c4ec7a4e91b5a42cbd61ddba0f9387a35 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 22 Nov 2022 13:01:27 +0100 Subject: [PATCH 216/243] OP-4196 - remove unnecessary logging --- openpype/modules/slack/plugins/publish/integrate_slack_api.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py index 6138671180..e43b07b228 100644 --- a/openpype/modules/slack/plugins/publish/integrate_slack_api.py +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -148,13 +148,11 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): thumbnail_path = None for repre in instance.data.get("representations", []): if repre.get('thumbnail') or "thumbnail" in repre.get('tags', []): - self.log.info(repre) repre_thumbnail_path = ( repre.get("published_path") or os.path.join(repre["stagingDir"], repre["files"]) ) if os.path.exists(repre_thumbnail_path): - self.log.info("exists") thumbnail_path = repre_thumbnail_path break return thumbnail_path From ab17acddc7c192dab58727e87fe87b51e242a3df Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 22 Nov 2022 13:39:24 +0100 Subject: [PATCH 217/243] OP-4196 - better handling of data It should take task from instance anatomyData, then from context and handle non dict items. --- .../slack/plugins/publish/integrate_slack_api.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py index e43b07b228..2c6f3d21bd 100644 --- a/openpype/modules/slack/plugins/publish/integrate_slack_api.py +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -112,7 +112,13 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): if review_path: fill_pairs.append(("review_filepath", review_path)) - task_data = fill_data.get("task") + task_data = ( + copy.deepcopy(instance.data.get("anatomyData", [])).get("task") + or fill_data.get("task") + ) + if not isinstance(task_data, dict): + # fallback for legacy - if task_data is only task name + task_data["name"] = task_data if task_data: if ( "{task}" in message_templ @@ -121,13 +127,10 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): ): fill_pairs.append(("task", task_data["name"])) - elif isinstance(task_data, dict): + else: for key, value in task_data.items(): fill_key = "task[{}]".format(key) fill_pairs.append((fill_key, value)) - else: - # fallback for legacy - if task_data is only task name - fill_pairs.append(("task", task_data)) self.log.debug("fill_pairs ::{}".format(fill_pairs)) multiple_case_variants = prepare_template_data(fill_pairs) From 3cd241d2dbfa56a43ae2199fb1c38bd236497cd1 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 22 Nov 2022 14:01:10 +0100 Subject: [PATCH 218/243] OP-4196 - fix wrong return type --- openpype/modules/slack/plugins/publish/integrate_slack_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py index 2c6f3d21bd..9539d03306 100644 --- a/openpype/modules/slack/plugins/publish/integrate_slack_api.py +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -113,7 +113,7 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): fill_pairs.append(("review_filepath", review_path)) task_data = ( - copy.deepcopy(instance.data.get("anatomyData", [])).get("task") + copy.deepcopy(instance.data.get("anatomyData", {})).get("task") or fill_data.get("task") ) if not isinstance(task_data, dict): From 8a121bc0ff43e86bbe42d660a29e1d1fed13e08c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 22 Nov 2022 15:11:58 +0100 Subject: [PATCH 219/243] move default settings from 'project_settings/global/tools/publish/template_name_profiles' to legacy place --- .../defaults/project_settings/global.json | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index b8995de99e..46b8b1b0c8 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -288,6 +288,17 @@ "task_types": [], "tasks": [], "template_name": "maya2unreal" + }, + { + "families": [ + "online" + ], + "hosts": [ + "traypublisher" + ], + "task_types": [], + "tasks": [], + "template_name": "online" } ] }, @@ -484,19 +495,7 @@ ] }, "publish": { - "template_name_profiles": [ - { - "families": [ - "online" - ], - "hosts": [ - "traypublisher" - ], - "task_types": [], - "task_names": [], - "template_name": "online" - } - ], + "template_name_profiles": [], "hero_template_name_profiles": [] } }, From 788ed6478006c17644460945c9a60cc8207a036c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 22 Nov 2022 15:50:46 +0100 Subject: [PATCH 220/243] fix typo --- openpype/tools/publisher/widgets/card_view_widgets.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/publisher/widgets/card_view_widgets.py b/openpype/tools/publisher/widgets/card_view_widgets.py index 9fd2bf0824..72644c09db 100644 --- a/openpype/tools/publisher/widgets/card_view_widgets.py +++ b/openpype/tools/publisher/widgets/card_view_widgets.py @@ -674,9 +674,9 @@ class InstanceCardView(AbstractInstanceView): instances_by_group[group_name] ) - self._update_ordered_group_nameS() + self._update_ordered_group_names() - def _update_ordered_group_nameS(self): + def _update_ordered_group_names(self): ordered_group_names = [CONTEXT_GROUP] for idx in range(self._content_layout.count()): if idx > 0: From 3ba5f8e0e99798c62ec295ea2a3706f3da8aac37 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 22 Nov 2022 15:51:05 +0100 Subject: [PATCH 221/243] fix tas combobox sizes --- openpype/tools/publisher/widgets/widgets.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index ce3d91ce63..332e231653 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -578,6 +578,11 @@ class TasksCombobox(QtWidgets.QComboBox): self._text = None + # Make sure combobox is extended horizontally + size_policy = self.sizePolicy() + size_policy.setHorizontalPolicy(size_policy.MinimumExpanding) + self.setSizePolicy(size_policy) + def set_invalid_empty_task(self, invalid=True): self._proxy_model.set_filter_empty(invalid) if invalid: From b2065acd7a43724ecd522a9f14531f3a45df38ce Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 22 Nov 2022 15:51:44 +0100 Subject: [PATCH 222/243] added ability to know if views have any items --- .../publisher/widgets/card_view_widgets.py | 7 +++++++ .../publisher/widgets/list_view_widgets.py | 7 +++++++ .../tools/publisher/widgets/overview_widget.py | 4 ++++ openpype/tools/publisher/widgets/widgets.py | 17 ++++++++++++++++- 4 files changed, 34 insertions(+), 1 deletion(-) diff --git a/openpype/tools/publisher/widgets/card_view_widgets.py b/openpype/tools/publisher/widgets/card_view_widgets.py index 72644c09db..09635d1a15 100644 --- a/openpype/tools/publisher/widgets/card_view_widgets.py +++ b/openpype/tools/publisher/widgets/card_view_widgets.py @@ -676,6 +676,13 @@ class InstanceCardView(AbstractInstanceView): self._update_ordered_group_names() + def has_items(self): + if self._convertor_items_group is not None: + return True + if self._widgets_by_group: + return True + return False + def _update_ordered_group_names(self): ordered_group_names = [CONTEXT_GROUP] for idx in range(self._content_layout.count()): diff --git a/openpype/tools/publisher/widgets/list_view_widgets.py b/openpype/tools/publisher/widgets/list_view_widgets.py index 32d84862f0..1cdb4cdcdb 100644 --- a/openpype/tools/publisher/widgets/list_view_widgets.py +++ b/openpype/tools/publisher/widgets/list_view_widgets.py @@ -912,6 +912,13 @@ class InstanceListView(AbstractInstanceView): if not self._instance_view.isExpanded(proxy_index): self._instance_view.expand(proxy_index) + def has_items(self): + if self._convertor_group_widget is not None: + return True + if self._group_items: + return True + return False + def get_selected_items(self): """Get selected instance ids and context selection. diff --git a/openpype/tools/publisher/widgets/overview_widget.py b/openpype/tools/publisher/widgets/overview_widget.py index 1c924d1631..b1aeda9cd4 100644 --- a/openpype/tools/publisher/widgets/overview_widget.py +++ b/openpype/tools/publisher/widgets/overview_widget.py @@ -205,6 +205,10 @@ class OverviewWidget(QtWidgets.QFrame): self._subset_views_widget.height() ) + def has_items(self): + view = self._subset_views_layout.currentWidget() + return view.has_items() + def _on_create_clicked(self): """Pass signal to parent widget which should care about changing state. diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index 332e231653..d6c6f8673c 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -305,6 +305,20 @@ class AbstractInstanceView(QtWidgets.QWidget): "{} Method 'refresh' is not implemented." ).format(self.__class__.__name__)) + def has_items(self): + """View has at least one item. + + This is more a question for controller but is called from widget + which should probably should not use controller. + + Returns: + bool: There is at least one instance or conversion item. + """ + + raise NotImplementedError(( + "{} Method 'has_items' is not implemented." + ).format(self.__class__.__name__)) + def get_selected_items(self): """Selected instances required for callbacks. @@ -1185,7 +1199,7 @@ class GlobalAttrsWidget(QtWidgets.QWidget): """Set currently selected instances. Args: - instances(list): List of selected instances. + instances(List[CreatedInstance]): List of selected instances. Empty instances tells that nothing or context is selected. """ self._set_btns_visible(False) @@ -1619,6 +1633,7 @@ class SubsetAttributesWidget(QtWidgets.QWidget): instances(List[CreatedInstance]): List of currently selected instances. context_selected(bool): Is context selected. + convertor_identifiers(List[str]): Identifiers of convert items. """ all_valid = True From d87e8fe99c68c23a5fdf1ce19fc0debe654eee97 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 22 Nov 2022 15:52:06 +0100 Subject: [PATCH 223/243] tabs widget can accept tab indexes --- openpype/tools/publisher/widgets/tabs_widget.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/openpype/tools/publisher/widgets/tabs_widget.py b/openpype/tools/publisher/widgets/tabs_widget.py index eb3eda8c19..d8ad19cfc0 100644 --- a/openpype/tools/publisher/widgets/tabs_widget.py +++ b/openpype/tools/publisher/widgets/tabs_widget.py @@ -54,6 +54,9 @@ class PublisherTabsWidget(QtWidgets.QFrame): self._buttons_by_identifier = {} def is_current_tab(self, identifier): + if isinstance(identifier, int): + identifier = self.get_tab_by_index(identifier) + if isinstance(identifier, PublisherTabBtn): identifier = identifier.identifier return self._current_identifier == identifier @@ -69,10 +72,10 @@ class PublisherTabsWidget(QtWidgets.QFrame): return button def get_tab_by_index(self, index): - if index < 0 or index > self._btns_layout.count(): - return None - item = self._btns_layout.itemAt(index) - return item.widget() + if 0 >= index < self._btns_layout.count(): + item = self._btns_layout.itemAt(index) + return item.widget() + return None def set_current_tab(self, identifier): if isinstance(identifier, int): From dd50c6723e1ec892478205f72de2e0bf57940d35 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 22 Nov 2022 15:55:39 +0100 Subject: [PATCH 224/243] small teaks and fixes --- openpype/tools/publisher/window.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index 3879e37ad7..59dd2e6ec9 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -432,7 +432,7 @@ class PublisherWindow(QtWidgets.QDialog): self._update_create_overlay_size() self._update_create_overlay_visibility() - if self._is_current_tab("create"): + if self._is_on_create_tab(): self._install_app_event_listener() # Reset if requested @@ -450,7 +450,7 @@ class PublisherWindow(QtWidgets.QDialog): self._context_label.setText(label) def _update_publish_details_widget(self, force=False): - if not force and not self._is_current_tab("details"): + if not force and not self._is_on_details_tab(): return report_data = self.controller.get_publish_report() @@ -540,16 +540,16 @@ class PublisherWindow(QtWidgets.QDialog): self._set_current_tab("report") def _is_on_create_tab(self): - self._is_current_tab("create") + return self._is_current_tab("create") def _is_on_publish_tab(self): - self._is_current_tab("publish") + return self._is_current_tab("publish") def _is_on_details_tab(self): - self._is_current_tab("details") + return self._is_current_tab("details") def _is_on_report_tab(self): - self._is_current_tab("report") + return self._is_current_tab("report") def _set_publish_overlay_visibility(self, visible): if visible: @@ -601,11 +601,8 @@ class PublisherWindow(QtWidgets.QDialog): self._set_publish_visibility(False) self._set_footer_enabled(False) self._update_publish_details_widget() - if ( - not self._is_current_tab("create") - and not self._is_current_tab("publish") ): - self._set_current_tab("publish") + self._go_to_publish_tab() def _on_publish_start(self): self._create_tab.setEnabled(False) @@ -621,8 +618,8 @@ class PublisherWindow(QtWidgets.QDialog): self._publish_details_widget.close_details_popup() - if self._is_current_tab(self._create_tab): - self._set_current_tab("publish") + if self._is_on_create_tab(): + self._go_to_publish_tab() def _on_publish_validated_change(self, event): if event["value"]: @@ -635,7 +632,7 @@ class PublisherWindow(QtWidgets.QDialog): publish_has_crashed = self._controller.publish_has_crashed validate_enabled = not publish_has_crashed publish_enabled = not publish_has_crashed - if self._is_current_tab("publish"): + if self._is_on_publish_tab(): self._go_to_report_tab() if validate_enabled: From caf94fb68f789d528e69cf6b423b29b20fe16369 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 22 Nov 2022 15:57:17 +0100 Subject: [PATCH 225/243] show publisher can accept tab to switch to --- openpype/tools/publisher/window.py | 62 +++++++++++++++++++++++++++++- openpype/tools/utils/host_tools.py | 16 ++++---- 2 files changed, 67 insertions(+), 11 deletions(-) diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index 59dd2e6ec9..0f7fd2c7e3 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -156,7 +156,7 @@ class PublisherWindow(QtWidgets.QDialog): footer_layout.addWidget(footer_bottom_widget, 0) # Content - # - wrap stacked widget under one more widget to be able propagate + # - wrap stacked widget under one more widget to be able to propagate # margins (QStackedLayout can't have margins) content_widget = QtWidgets.QWidget(under_publish_widget) @@ -267,6 +267,9 @@ class PublisherWindow(QtWidgets.QDialog): controller.event_system.add_callback( "publish.reset.finished", self._on_publish_reset ) + controller.event_system.add_callback( + "controller.reset.finished", self._on_controller_reset + ) controller.event_system.add_callback( "publish.process.started", self._on_publish_start ) @@ -337,11 +340,13 @@ class PublisherWindow(QtWidgets.QDialog): self._controller = controller self._first_show = True + self._first_reset = True # This is a little bit confusing but 'reset_on_first_show' is too long - # forin init + # for init self._reset_on_first_show = reset_on_show self._reset_on_show = True self._publish_frame_visible = None + self._tab_on_reset = None self._error_messages_to_show = collections.deque() self._errors_dialog_message_timer = errors_dialog_message_timer @@ -353,12 +358,21 @@ class PublisherWindow(QtWidgets.QDialog): self._show_timer = show_timer self._show_counter = 0 + self._window_is_visible = False @property def controller(self): return self._controller + def make_sure_is_visible(self): + if self._window_is_visible: + self.setWindowState(QtCore.Qt.ActiveWindow) + + else: + self.show() + def showEvent(self, event): + self._window_is_visible = True super(PublisherWindow, self).showEvent(event) if self._first_show: self._first_show = False @@ -372,6 +386,7 @@ class PublisherWindow(QtWidgets.QDialog): self._update_create_overlay_size() def closeEvent(self, event): + self._window_is_visible = False self._uninstall_app_event_listener() self.save_changes() self._reset_on_show = True @@ -449,6 +464,19 @@ class PublisherWindow(QtWidgets.QDialog): def set_context_label(self, label): self._context_label.setText(label) + def set_tab_on_reset(self, tab): + """Define tab that will be selected on window show. + + This is single use method, when publisher window is showed the value is + unset and not used on next show. + + Args: + tab (Union[int, Literal[create, publish, details, report]]: Index + or name of tab which will be selected on show (after reset). + """ + + self._tab_on_reset = tab + def _update_publish_details_widget(self, force=False): if not force and not self._is_on_details_tab(): return @@ -524,6 +552,11 @@ class PublisherWindow(QtWidgets.QDialog): def _set_current_tab(self, identifier): self._tabs_widget.set_current_tab(identifier) + def set_current_tab(self, tab): + self._set_current_tab(tab) + if not self._window_is_visible: + self.set_tab_on_reset(tab) + def _is_current_tab(self, identifier): return self._tabs_widget.is_current_tab(identifier) @@ -601,7 +634,32 @@ class PublisherWindow(QtWidgets.QDialog): self._set_publish_visibility(False) self._set_footer_enabled(False) self._update_publish_details_widget() + + def _on_controller_reset(self): + self._first_reset, first_reset = False, self._first_reset + if self._tab_on_reset is not None: + self._tab_on_reset, new_tab = None, self._tab_on_reset + self._set_current_tab(new_tab) + return + + # On first reset change tab based on available items + # - if there is at least one instance the tab is changed to 'publish' + # otherwise 'create' is used + # - this happens only on first show + if first_reset: + if self._overview_widget.has_items(): + self._go_to_publish_tab() + else: + self._go_to_create_tab() + + elif ( + not self._is_on_create_tab() + and not self._is_on_publish_tab() ): + # If current tab is not 'Create' or 'Publish' go to 'Publish' + # - this can happen when publishing started and was reset + # at that moment it doesn't make sense to stay at publish + # specific tabs. self._go_to_publish_tab() def _on_publish_start(self): diff --git a/openpype/tools/utils/host_tools.py b/openpype/tools/utils/host_tools.py index 046dcbdf6a..e8593a8ae2 100644 --- a/openpype/tools/utils/host_tools.py +++ b/openpype/tools/utils/host_tools.py @@ -285,14 +285,12 @@ class HostToolsHelper: return self._publisher_tool - def show_publisher_tool(self, parent=None, controller=None): + def show_publisher_tool(self, parent=None, controller=None, tab=None): with qt_app_context(): - dialog = self.get_publisher_tool(parent, controller) - - dialog.show() - dialog.raise_() - dialog.activateWindow() - dialog.showNormal() + window = self.get_publisher_tool(parent, controller) + if tab: + window.set_current_tab(tab) + window.make_sure_is_visible() def get_tool_by_name(self, tool_name, parent=None, *args, **kwargs): """Show tool by it's name. @@ -446,8 +444,8 @@ def show_publish(parent=None): _SingletonPoint.show_tool_by_name("publish", parent) -def show_publisher(parent=None): - _SingletonPoint.show_tool_by_name("publisher", parent) +def show_publisher(parent=None, **kwargs): + _SingletonPoint.show_tool_by_name("publisher", parent, **kwargs) def show_experimental_tools_dialog(parent=None): From 6af4412591b45f2001a9f01e998a36e871666ec9 Mon Sep 17 00:00:00 2001 From: clement hector Date: Tue, 22 Nov 2022 16:08:03 +0100 Subject: [PATCH 226/243] set creator window as parent of pop up window --- .../hosts/photoshop/plugins/create/create_legacy_image.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/photoshop/plugins/create/create_legacy_image.py b/openpype/hosts/photoshop/plugins/create/create_legacy_image.py index 2792a775e0..7672458165 100644 --- a/openpype/hosts/photoshop/plugins/create/create_legacy_image.py +++ b/openpype/hosts/photoshop/plugins/create/create_legacy_image.py @@ -29,7 +29,8 @@ class CreateImage(create.LegacyCreator): if len(selection) > 1: # Ask user whether to create one image or image per selected # item. - msg_box = QtWidgets.QMessageBox() + active_window = QtWidgets.QApplication.activeWindow() + msg_box = QtWidgets.QMessageBox(parent=active_window) msg_box.setIcon(QtWidgets.QMessageBox.Warning) msg_box.setText( "Multiple layers selected." @@ -102,7 +103,7 @@ class CreateImage(create.LegacyCreator): if group.long_name: for directory in group.long_name[::-1]: name = directory.replace(stub.PUBLISH_ICON, '').\ - replace(stub.LOADED_ICON, '') + replace(stub.LOADED_ICON, '') long_names.append(name) self.data.update({"subset": subset_name}) From 861cdadc9bbcd171da0d8793de6595db8446efce Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 22 Nov 2022 16:48:02 +0100 Subject: [PATCH 227/243] fix formatting --- openpype/tools/publisher/widgets/widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index d6c6f8673c..6bc09c55a3 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -316,7 +316,7 @@ class AbstractInstanceView(QtWidgets.QWidget): """ raise NotImplementedError(( - "{} Method 'has_items' is not implemented." + "{} Method 'has_items' is not implemented." ).format(self.__class__.__name__)) def get_selected_items(self): From 3b81c7f5731dfc5c018bff11e9758fc3e5e26450 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 22 Nov 2022 18:15:27 +0100 Subject: [PATCH 228/243] OP-4196 - better logging of file upload errors --- .../slack/plugins/publish/integrate_slack_api.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py index 9539d03306..0cd5ec9de8 100644 --- a/openpype/modules/slack/plugins/publish/integrate_slack_api.py +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -188,10 +188,17 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): channel=channel, title=os.path.basename(p_file) ) - attachment_str += "\n<{}|{}>".format( - response["file"]["permalink"], - os.path.basename(p_file)) - file_ids.append(response["file"]["id"]) + if response.get("error"): + error_str = self._enrich_error( + str(response.get("error")), + channel) + self.log.warning( + "Error happened: {}".format(error_str)) + else: + attachment_str += "\n<{}|{}>".format( + response["file"]["permalink"], + os.path.basename(p_file)) + file_ids.append(response["file"]["id"]) if publish_files: message += attachment_str From 855e7d1c61c16093706b276435aed02fbb108e91 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 22 Nov 2022 18:28:01 +0100 Subject: [PATCH 229/243] OP-4196 - fix filtering profiles Task types didn't work. --- .../modules/slack/plugins/publish/collect_slack_family.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/modules/slack/plugins/publish/collect_slack_family.py b/openpype/modules/slack/plugins/publish/collect_slack_family.py index 39b05937dc..27e899d59a 100644 --- a/openpype/modules/slack/plugins/publish/collect_slack_family.py +++ b/openpype/modules/slack/plugins/publish/collect_slack_family.py @@ -18,15 +18,15 @@ class CollectSlackFamilies(pyblish.api.InstancePlugin): profiles = None def process(self, instance): - task_name = legacy_io.Session.get("AVALON_TASK") + task_data = instance.data["anatomyData"].get("task", {}) family = self.main_family_from_instance(instance) key_values = { "families": family, - "tasks": task_name, + "tasks": task_data.get("name"), + "task_types": task_data.get("type"), "hosts": instance.data["anatomyData"]["app"], "subsets": instance.data["subset"] } - profile = filter_profiles(self.profiles, key_values, logger=self.log) From c3e5b7a169c670b35889a5fb0038ba4b50bf7841 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 22 Nov 2022 21:21:42 +0100 Subject: [PATCH 230/243] update history.md --- HISTORY.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/HISTORY.md b/HISTORY.md index f6cc74e114..7365696f96 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,40 @@ # Changelog +## [3.14.6](https://github.com/pypeclub/OpenPype/tree/3.14.6) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.5...3.14.6) + +### 📖 Documentation + +- Documentation: Minor updates to dev\_requirements.md [\#4025](https://github.com/pypeclub/OpenPype/pull/4025) + +**🆕 New features** + +- Nuke: add 13.2 variant [\#4041](https://github.com/pypeclub/OpenPype/pull/4041) + +**🚀 Enhancements** + +- Publish Report Viewer: Store reports locally on machine [\#4040](https://github.com/pypeclub/OpenPype/pull/4040) +- General: More specific error in burnins script [\#4026](https://github.com/pypeclub/OpenPype/pull/4026) +- General: Extract review does not crash with old settings overrides [\#4023](https://github.com/pypeclub/OpenPype/pull/4023) +- Publisher: Convertors for legacy instances [\#4020](https://github.com/pypeclub/OpenPype/pull/4020) +- workflows: adding milestone creator and assigner [\#4018](https://github.com/pypeclub/OpenPype/pull/4018) +- Publisher: Catch creator errors [\#4015](https://github.com/pypeclub/OpenPype/pull/4015) + +**🐛 Bug fixes** + +- Hiero - effect collection fixes [\#4038](https://github.com/pypeclub/OpenPype/pull/4038) +- Nuke - loader clip correct hash conversion in path [\#4037](https://github.com/pypeclub/OpenPype/pull/4037) +- Maya: Soft fail when applying capture preset [\#4034](https://github.com/pypeclub/OpenPype/pull/4034) +- Igniter: handle missing directory [\#4032](https://github.com/pypeclub/OpenPype/pull/4032) +- StandalonePublisher: Fix thumbnail publishing [\#4029](https://github.com/pypeclub/OpenPype/pull/4029) +- Experimental Tools: Fix publisher import [\#4027](https://github.com/pypeclub/OpenPype/pull/4027) +- Houdini: fix wrong path in ASS loader [\#4016](https://github.com/pypeclub/OpenPype/pull/4016) + +**🔀 Refactored code** + +- General: Import lib functions from lib [\#4017](https://github.com/pypeclub/OpenPype/pull/4017) + ## [3.14.5](https://github.com/pypeclub/OpenPype/tree/3.14.5) (2022-10-24) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.4...3.14.5) From e600cd1b3d2963a2a2e26dce79e07818bb4c5d28 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 22 Nov 2022 21:24:11 +0100 Subject: [PATCH 231/243] updating to 3.14.7 --- CHANGELOG.md | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 707b61676f..c3cccf2d1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,63 @@ # Changelog -## [3.14.6](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.14.7](https://github.com/pypeclub/OpenPype/tree/3.14.7) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.5...HEAD) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.6...3.14.7) + +**🆕 New features** + +- Hiero: loading effect family to timeline [\#4055](https://github.com/pypeclub/OpenPype/pull/4055) + +**🚀 Enhancements** + +- Ftrack: Event server status give more information about version locations [\#4112](https://github.com/pypeclub/OpenPype/pull/4112) +- General: Allow higher numbers in frames and clips [\#4101](https://github.com/pypeclub/OpenPype/pull/4101) +- Publisher: Settings for validate frame range [\#4097](https://github.com/pypeclub/OpenPype/pull/4097) +- Publisher: Ignore escape button [\#4090](https://github.com/pypeclub/OpenPype/pull/4090) +- Flame: Loading clip with native colorspace resolved from mapping [\#4079](https://github.com/pypeclub/OpenPype/pull/4079) +- General: Extract review single frame output [\#4064](https://github.com/pypeclub/OpenPype/pull/4064) +- Publisher: Prepared common function for instance data cache [\#4063](https://github.com/pypeclub/OpenPype/pull/4063) +- Publisher: Easy access to publish page from create page [\#4058](https://github.com/pypeclub/OpenPype/pull/4058) +- General/TVPaint: Attribute defs dialog [\#4052](https://github.com/pypeclub/OpenPype/pull/4052) +- Publisher: Better reset defer [\#4048](https://github.com/pypeclub/OpenPype/pull/4048) +- Publisher: Add thumbnail sources [\#4042](https://github.com/pypeclub/OpenPype/pull/4042) + +**🐛 Bug fixes** + +- General: Move default settings for template name [\#4119](https://github.com/pypeclub/OpenPype/pull/4119) +- Nuke: loaded nodes set to first tab [\#4114](https://github.com/pypeclub/OpenPype/pull/4114) +- Nuke: load image first frame [\#4113](https://github.com/pypeclub/OpenPype/pull/4113) +- Files Widget: Ignore case sensitivity of extensions [\#4096](https://github.com/pypeclub/OpenPype/pull/4096) +- Webpublisher: extension is lowercased in Setting and in uploaded files [\#4095](https://github.com/pypeclub/OpenPype/pull/4095) +- Publish Report Viewer: Fix small bugs [\#4086](https://github.com/pypeclub/OpenPype/pull/4086) +- Igniter: fix regex to match semver better [\#4085](https://github.com/pypeclub/OpenPype/pull/4085) +- Maya: aov filtering [\#4083](https://github.com/pypeclub/OpenPype/pull/4083) +- Flame/Flare: Loading to multiple batches [\#4080](https://github.com/pypeclub/OpenPype/pull/4080) +- hiero: creator from settings with set maximum [\#4077](https://github.com/pypeclub/OpenPype/pull/4077) +- Nuke: resolve hashes in file name only for frame token [\#4074](https://github.com/pypeclub/OpenPype/pull/4074) +- Publisher: Fix cache of asset docs [\#4070](https://github.com/pypeclub/OpenPype/pull/4070) +- Webpublisher: cleanup wp extract thumbnail [\#4067](https://github.com/pypeclub/OpenPype/pull/4067) +- Settings UI: Locked setting can't bypass lock [\#4066](https://github.com/pypeclub/OpenPype/pull/4066) +- Loader: Fix comparison of repre name [\#4053](https://github.com/pypeclub/OpenPype/pull/4053) +- Deadline: Extract environment subprocess failure [\#4050](https://github.com/pypeclub/OpenPype/pull/4050) + +**🔀 Refactored code** + +- General: Collect entities plugin minor changes [\#4089](https://github.com/pypeclub/OpenPype/pull/4089) +- General: Direct interfaces import [\#4065](https://github.com/pypeclub/OpenPype/pull/4065) + +**Merged pull requests:** + +- Bump loader-utils from 1.4.1 to 1.4.2 in /website [\#4100](https://github.com/pypeclub/OpenPype/pull/4100) +- Online family for Tray Publisher [\#4093](https://github.com/pypeclub/OpenPype/pull/4093) +- Bump loader-utils from 1.4.0 to 1.4.1 in /website [\#4081](https://github.com/pypeclub/OpenPype/pull/4081) +- remove underscore from subset name [\#4059](https://github.com/pypeclub/OpenPype/pull/4059) +- Alembic Loader as Arnold Standin [\#4047](https://github.com/pypeclub/OpenPype/pull/4047) + + +## [3.14.6](https://github.com/pypeclub/OpenPype/tree/3.14.6) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.5...3.14.6) ### 📖 Documentation From c63f468484b32628c6d87a35df993bf2303ecb83 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Wed, 23 Nov 2022 03:35:08 +0000 Subject: [PATCH 232/243] [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 0116b49f4d..a4af8b7a99 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.14.7-nightly.6" +__version__ = "3.14.7-nightly.7" From 0d88af8aec4c6112be2629865da7ffce4a7cce4d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 23 Nov 2022 11:40:16 +0100 Subject: [PATCH 233/243] update latest 3.14.7 --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3cccf2d1e..0c5f2cf8b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ **🚀 Enhancements** +- Photoshop: bug with pop-up window on Instance Creator [\#4121](https://github.com/pypeclub/OpenPype/pull/4121) +- Publisher: Open on specific tab [\#4120](https://github.com/pypeclub/OpenPype/pull/4120) +- Publisher: Hide unknown publish values [\#4116](https://github.com/pypeclub/OpenPype/pull/4116) - Ftrack: Event server status give more information about version locations [\#4112](https://github.com/pypeclub/OpenPype/pull/4112) - General: Allow higher numbers in frames and clips [\#4101](https://github.com/pypeclub/OpenPype/pull/4101) - Publisher: Settings for validate frame range [\#4097](https://github.com/pypeclub/OpenPype/pull/4097) @@ -25,6 +28,7 @@ **🐛 Bug fixes** - General: Move default settings for template name [\#4119](https://github.com/pypeclub/OpenPype/pull/4119) +- Slack: notification fail in new tray publisher [\#4118](https://github.com/pypeclub/OpenPype/pull/4118) - Nuke: loaded nodes set to first tab [\#4114](https://github.com/pypeclub/OpenPype/pull/4114) - Nuke: load image first frame [\#4113](https://github.com/pypeclub/OpenPype/pull/4113) - Files Widget: Ignore case sensitivity of extensions [\#4096](https://github.com/pypeclub/OpenPype/pull/4096) @@ -54,7 +58,6 @@ - remove underscore from subset name [\#4059](https://github.com/pypeclub/OpenPype/pull/4059) - Alembic Loader as Arnold Standin [\#4047](https://github.com/pypeclub/OpenPype/pull/4047) - ## [3.14.6](https://github.com/pypeclub/OpenPype/tree/3.14.6) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.5...3.14.6) From 6725c1f6d8dc025f13bffbbb1c92a242c49b618f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 23 Nov 2022 11:40:57 +0100 Subject: [PATCH 234/243] udpate history --- HISTORY.md | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/HISTORY.md b/HISTORY.md index 7365696f96..04a1073c07 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,64 @@ # Changelog + +## [3.14.7](https://github.com/pypeclub/OpenPype/tree/3.14.7) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.6...3.14.7) + +**🆕 New features** + +- Hiero: loading effect family to timeline [\#4055](https://github.com/pypeclub/OpenPype/pull/4055) + +**🚀 Enhancements** + +- Photoshop: bug with pop-up window on Instance Creator [\#4121](https://github.com/pypeclub/OpenPype/pull/4121) +- Publisher: Open on specific tab [\#4120](https://github.com/pypeclub/OpenPype/pull/4120) +- Publisher: Hide unknown publish values [\#4116](https://github.com/pypeclub/OpenPype/pull/4116) +- Ftrack: Event server status give more information about version locations [\#4112](https://github.com/pypeclub/OpenPype/pull/4112) +- General: Allow higher numbers in frames and clips [\#4101](https://github.com/pypeclub/OpenPype/pull/4101) +- Publisher: Settings for validate frame range [\#4097](https://github.com/pypeclub/OpenPype/pull/4097) +- Publisher: Ignore escape button [\#4090](https://github.com/pypeclub/OpenPype/pull/4090) +- Flame: Loading clip with native colorspace resolved from mapping [\#4079](https://github.com/pypeclub/OpenPype/pull/4079) +- General: Extract review single frame output [\#4064](https://github.com/pypeclub/OpenPype/pull/4064) +- Publisher: Prepared common function for instance data cache [\#4063](https://github.com/pypeclub/OpenPype/pull/4063) +- Publisher: Easy access to publish page from create page [\#4058](https://github.com/pypeclub/OpenPype/pull/4058) +- General/TVPaint: Attribute defs dialog [\#4052](https://github.com/pypeclub/OpenPype/pull/4052) +- Publisher: Better reset defer [\#4048](https://github.com/pypeclub/OpenPype/pull/4048) +- Publisher: Add thumbnail sources [\#4042](https://github.com/pypeclub/OpenPype/pull/4042) + +**🐛 Bug fixes** + +- General: Move default settings for template name [\#4119](https://github.com/pypeclub/OpenPype/pull/4119) +- Slack: notification fail in new tray publisher [\#4118](https://github.com/pypeclub/OpenPype/pull/4118) +- Nuke: loaded nodes set to first tab [\#4114](https://github.com/pypeclub/OpenPype/pull/4114) +- Nuke: load image first frame [\#4113](https://github.com/pypeclub/OpenPype/pull/4113) +- Files Widget: Ignore case sensitivity of extensions [\#4096](https://github.com/pypeclub/OpenPype/pull/4096) +- Webpublisher: extension is lowercased in Setting and in uploaded files [\#4095](https://github.com/pypeclub/OpenPype/pull/4095) +- Publish Report Viewer: Fix small bugs [\#4086](https://github.com/pypeclub/OpenPype/pull/4086) +- Igniter: fix regex to match semver better [\#4085](https://github.com/pypeclub/OpenPype/pull/4085) +- Maya: aov filtering [\#4083](https://github.com/pypeclub/OpenPype/pull/4083) +- Flame/Flare: Loading to multiple batches [\#4080](https://github.com/pypeclub/OpenPype/pull/4080) +- hiero: creator from settings with set maximum [\#4077](https://github.com/pypeclub/OpenPype/pull/4077) +- Nuke: resolve hashes in file name only for frame token [\#4074](https://github.com/pypeclub/OpenPype/pull/4074) +- Publisher: Fix cache of asset docs [\#4070](https://github.com/pypeclub/OpenPype/pull/4070) +- Webpublisher: cleanup wp extract thumbnail [\#4067](https://github.com/pypeclub/OpenPype/pull/4067) +- Settings UI: Locked setting can't bypass lock [\#4066](https://github.com/pypeclub/OpenPype/pull/4066) +- Loader: Fix comparison of repre name [\#4053](https://github.com/pypeclub/OpenPype/pull/4053) +- Deadline: Extract environment subprocess failure [\#4050](https://github.com/pypeclub/OpenPype/pull/4050) + +**🔀 Refactored code** + +- General: Collect entities plugin minor changes [\#4089](https://github.com/pypeclub/OpenPype/pull/4089) +- General: Direct interfaces import [\#4065](https://github.com/pypeclub/OpenPype/pull/4065) + +**Merged pull requests:** + +- Bump loader-utils from 1.4.1 to 1.4.2 in /website [\#4100](https://github.com/pypeclub/OpenPype/pull/4100) +- Online family for Tray Publisher [\#4093](https://github.com/pypeclub/OpenPype/pull/4093) +- Bump loader-utils from 1.4.0 to 1.4.1 in /website [\#4081](https://github.com/pypeclub/OpenPype/pull/4081) +- remove underscore from subset name [\#4059](https://github.com/pypeclub/OpenPype/pull/4059) +- Alembic Loader as Arnold Standin [\#4047](https://github.com/pypeclub/OpenPype/pull/4047) + ## [3.14.6](https://github.com/pypeclub/OpenPype/tree/3.14.6) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.5...3.14.6) From 2594bc2a0efa19331b7dbccb2624be41acf1032a Mon Sep 17 00:00:00 2001 From: OpenPype Date: Wed, 23 Nov 2022 10:45:17 +0000 Subject: [PATCH 235/243] [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 a4af8b7a99..a00c7de704 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.14.7-nightly.7" +__version__ = "3.14.7-nightly.8" From 8b1b09b33825dc9ff320b6c6e49597dedcf58f7f Mon Sep 17 00:00:00 2001 From: OpenPype Date: Wed, 23 Nov 2022 10:58:00 +0000 Subject: [PATCH 236/243] [Automated] Release --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index a00c7de704..ffabcf8025 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.14.7-nightly.8" +__version__ = "3.14.7" From 5779687a2b4467195a20bd9242d2fa782f7b27cd Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 23 Nov 2022 11:59:53 +0100 Subject: [PATCH 237/243] Removed unused argument --- .../deadline/repository/custom/plugins/GlobalJobPreLoad.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py b/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py index 78e1371eee..40193bac71 100644 --- a/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py +++ b/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py @@ -192,7 +192,7 @@ def get_openpype_executable(): return exe_list, dir_list -def get_openpype_versions(exe_list, dir_list): +def get_openpype_versions(dir_list): print(">>> Getting OpenPype executable ...") openpype_versions = [] From c3b7e3269544d3471c02e193acd4054b5a08eb08 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 23 Nov 2022 16:30:49 +0100 Subject: [PATCH 238/243] skip turning on/off of autosync --- .../publish/integrate_hierarchy_ftrack.py | 43 +------------------ 1 file changed, 2 insertions(+), 41 deletions(-) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py b/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py index fa7a89050c..6bae922d94 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py @@ -8,9 +8,6 @@ import pyblish.api from openpype.client import get_asset_by_id from openpype.lib import filter_profiles - -# Copy of constant `openpype_modules.ftrack.lib.avalon_sync.CUST_ATTR_AUTO_SYNC` -CUST_ATTR_AUTO_SYNC = "avalon_auto_sync" CUST_ATTR_GROUP = "openpype" @@ -97,18 +94,9 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): self.task_types = self.get_all_task_types(project) self.task_statuses = self.get_task_statuses(project) - # disable termporarily ftrack project's autosyncing - if auto_sync_state: - self.auto_sync_off(project) + # import ftrack hierarchy + self.import_to_ftrack(project_name, hierarchy_context) - try: - # import ftrack hierarchy - self.import_to_ftrack(project_name, hierarchy_context) - except Exception: - raise - finally: - if auto_sync_state: - self.auto_sync_on(project) def import_to_ftrack(self, project_name, input_data, parent=None): # Prequery hiearchical custom attributes @@ -381,33 +369,6 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): return entity - def auto_sync_off(self, project): - project["custom_attributes"][CUST_ATTR_AUTO_SYNC] = False - - self.log.info("Ftrack autosync swithed off") - - try: - self.session.commit() - except Exception: - tp, value, tb = sys.exc_info() - self.session.rollback() - self.session._configure_locations() - six.reraise(tp, value, tb) - - def auto_sync_on(self, project): - - project["custom_attributes"][CUST_ATTR_AUTO_SYNC] = True - - self.log.info("Ftrack autosync swithed on") - - try: - self.session.commit() - except Exception: - tp, value, tb = sys.exc_info() - self.session.rollback() - self.session._configure_locations() - six.reraise(tp, value, tb) - def _get_active_assets(self, context): """ Returns only asset dictionary. Usually the last part of deep dictionary which From 635c662a8c357c5170aadfb9197081a16c27c3b2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 23 Nov 2022 16:32:04 +0100 Subject: [PATCH 239/243] raise known publish error if project in ftrack was not found --- .../publish/integrate_hierarchy_ftrack.py | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py b/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py index 6bae922d94..8b0e4ab62d 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py @@ -7,6 +7,7 @@ import pyblish.api from openpype.client import get_asset_by_id from openpype.lib import filter_profiles +from openpype.pipeline import KnownPublishError CUST_ATTR_GROUP = "openpype" @@ -16,7 +17,6 @@ CUST_ATTR_GROUP = "openpype" def get_pype_attr(session, split_hierarchical=True): custom_attributes = [] hier_custom_attributes = [] - # TODO remove deprecated "avalon" group from query cust_attrs_query = ( "select id, entity_type, object_type_id, is_hierarchical, default" " from CustomAttributeConfiguration" @@ -76,19 +76,25 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): create_task_status_profiles = [] def process(self, context): - self.context = context - if "hierarchyContext" not in self.context.data: + if "hierarchyContext" not in context.data: return hierarchy_context = self._get_active_assets(context) self.log.debug("__ hierarchy_context: {}".format(hierarchy_context)) - session = self.context.data["ftrackSession"] - project_name = self.context.data["projectEntity"]["name"] - query = 'Project where full_name is "{}"'.format(project_name) - project = session.query(query).one() - auto_sync_state = project["custom_attributes"][CUST_ATTR_AUTO_SYNC] + session = context.data["ftrackSession"] + project_name = context.data["projectName"] + project = session.query( + 'select id, full_name from Project where full_name is "{}"'.format( + project_name + ) + ).first() + if not project: + raise KnownPublishError( + "Project \"{}\" was not found on ftrack.".format(project_name) + ) + self.context = context self.session = session self.ft_project = project self.task_types = self.get_all_task_types(project) From 5a0cc527325642c9871323a6aba8c263be72d194 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 23 Nov 2022 16:34:02 +0100 Subject: [PATCH 240/243] implemented helper methods to query information we need from ftrack --- .../publish/integrate_hierarchy_ftrack.py | 123 ++++++++++++++++++ 1 file changed, 123 insertions(+) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py b/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py index 8b0e4ab62d..02946f813f 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py @@ -103,6 +103,129 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): # import ftrack hierarchy self.import_to_ftrack(project_name, hierarchy_context) + def query_ftrack_entitites(self, session, ft_project): + project_id = ft_project["id"] + entities = session.query(( + "select id, name, parent_id" + " from TypedContext where project_id is \"{}\"" + ).format(project_id)).all() + + entities_by_id = {} + entities_by_parent_id = collections.defaultdict(list) + for entity in entities: + entities_by_id[entity["id"]] = entity + parent_id = entity["parent_id"] + entities_by_parent_id[parent_id].append(entity) + + ftrack_hierarchy = [] + ftrack_id_queue = collections.deque() + ftrack_id_queue.append((project_id, ftrack_hierarchy)) + while ftrack_id_queue: + item = ftrack_id_queue.popleft() + ftrack_id, parent_list = item + if ftrack_id == project_id: + entity = ft_project + name = entity["full_name"] + else: + entity = entities_by_id[ftrack_id] + name = entity["name"] + + children = [] + parent_list.append({ + "name": name, + "low_name": name.lower(), + "entity": entity, + "children": children, + }) + for child in entities_by_parent_id[ftrack_id]: + ftrack_id_queue.append((child["id"], children)) + return ftrack_hierarchy + + def find_matching_ftrack_entities( + self, hierarchy_context, ftrack_hierarchy + ): + walk_queue = collections.deque() + for entity_name, entity_data in hierarchy_context.items(): + walk_queue.append( + (entity_name, entity_data, ftrack_hierarchy) + ) + + matching_ftrack_entities = [] + while walk_queue: + item = walk_queue.popleft() + entity_name, entity_data, ft_children = item + matching_ft_child = None + for ft_child in ft_children: + if ft_child["low_name"] == entity_name.lower(): + matching_ft_child = ft_child + break + + if matching_ft_child is None: + continue + + entity = matching_ft_child["entity"] + entity_data["ft_entity"] = entity + matching_ftrack_entities.append(entity) + + hierarchy_children = entity_data.get("childs") + if not hierarchy_children: + continue + + for child_name, child_data in hierarchy_children.items(): + walk_queue.append( + (child_name, child_data, matching_ft_child["children"]) + ) + return matching_ftrack_entities + + def query_custom_attribute_values(self, session, entities, hier_attrs): + attr_ids = { + attr["id"] + for attr in hier_attrs + } + entity_ids = { + entity["id"] + for entity in entities + } + output = { + entity_id: {} + for entity_id in entity_ids + } + if not attr_ids or not entity_ids: + return {} + + joined_attr_ids = ",".join( + ['"{}"'.format(attr_id) for attr_id in attr_ids] + ) + + # Query values in chunks + chunk_size = int(5000 / len(attr_ids)) + # Make sure entity_ids is `list` for chunk selection + entity_ids = list(entity_ids) + results = [] + for idx in range(0, len(entity_ids), chunk_size): + joined_entity_ids = ",".join([ + '"{}"'.format(entity_id) + for entity_id in entity_ids[idx:idx + chunk_size] + ]) + results.extend( + session.query( + ( + "select value, entity_id, configuration_id" + " from CustomAttributeValue" + " where entity_id in ({}) and configuration_id in ({})" + ).format( + joined_entity_ids, + joined_attr_ids + ) + ).all() + ) + + for result in results: + attr_id = result["configuration_id"] + entity_id = result["entity_id"] + output[entity_id][attr_id] = result["value"] + + return output def import_to_ftrack(self, project_name, input_data, parent=None): # Prequery hiearchical custom attributes From a78ef54e56e7a0a0300fdc140ec40fb1be4111e9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 23 Nov 2022 16:35:02 +0100 Subject: [PATCH 241/243] query user at the start of import method instead of requerying it again --- .../publish/integrate_hierarchy_ftrack.py | 39 ++++++++++--------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py b/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py index 02946f813f..5d30b9bf7b 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py @@ -234,6 +234,16 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): attr["key"]: attr for attr in hier_custom_attributes } + # Query user entity (for comments) + user = self.session.query( + "User where username is \"{}\"".format(self.session.api_user) + ).first() + if not user: + self.log.warning( + "Was not able to query current User {}".format( + self.session.api_user + ) + ) # Get ftrack api module (as they are different per python version) ftrack_api = self.context.data["ftrackPythonModule"] @@ -364,25 +374,18 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): six.reraise(tp, value, tb) # Create notes. - user = self.session.query( - "User where username is \"{}\"".format(self.session.api_user) - ).first() - if user: - for comment in entity_data.get("comments", []): + entity_comments = entity_data.get("comments") + if user and entity_comments: + for comment in entity_comments: entity.create_note(comment, user) - else: - self.log.warning( - "Was not able to query current User {}".format( - self.session.api_user - ) - ) - try: - self.session.commit() - except Exception: - tp, value, tb = sys.exc_info() - self.session.rollback() - self.session._configure_locations() - six.reraise(tp, value, tb) + + try: + self.session.commit() + except Exception: + tp, value, tb = sys.exc_info() + self.session.rollback() + self.session._configure_locations() + six.reraise(tp, value, tb) # Import children. if 'childs' in entity_data: From 36afd8aa7c3a9a88001c20f6c0ae8c616a2bf51a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 23 Nov 2022 16:36:21 +0100 Subject: [PATCH 242/243] import to ftrack is not recursion based but queue based method --- .../publish/integrate_hierarchy_ftrack.py | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py b/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py index 5d30b9bf7b..12e89a1884 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py @@ -227,7 +227,7 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): return output - def import_to_ftrack(self, project_name, input_data, parent=None): + def import_to_ftrack(self, project_name, hierarchy_context): # Prequery hiearchical custom attributes hier_custom_attributes = get_pype_attr(self.session)[1] hier_attr_by_key = { @@ -247,8 +247,17 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): # Get ftrack api module (as they are different per python version) ftrack_api = self.context.data["ftrackPythonModule"] - for entity_name in input_data: - entity_data = input_data[entity_name] + # Use queue of hierarchy items to process + import_queue = collections.deque() + for entity_name, entity_data in hierarchy_context.items(): + import_queue.append( + (entity_name, entity_data, None) + ) + + while import_queue: + item = import_queue.popleft() + entity_name, entity_data, parent = item + entity_type = entity_data['entity_type'] self.log.debug(entity_data) self.log.debug(entity_type) @@ -388,9 +397,14 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): six.reraise(tp, value, tb) # Import children. - if 'childs' in entity_data: - self.import_to_ftrack( - project_name, entity_data['childs'], entity) + children = entity_data.get("childs") + if not children: + continue + + for entity_name, entity_data in children.items(): + import_queue.append( + (entity_name, entity_data, entity) + ) def create_links(self, project_name, entity_data, entity): # Clear existing links. From 5de422dea2c294bcc1ff097c272180b272e89e8a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 23 Nov 2022 16:38:04 +0100 Subject: [PATCH 243/243] change how custom attributes are filled on entities and how entities are created --- .../publish/integrate_hierarchy_ftrack.py | 156 +++++++++--------- 1 file changed, 82 insertions(+), 74 deletions(-) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py b/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py index 12e89a1884..046dfd9ad8 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py @@ -229,10 +229,10 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): def import_to_ftrack(self, project_name, hierarchy_context): # Prequery hiearchical custom attributes - hier_custom_attributes = get_pype_attr(self.session)[1] + hier_attrs = get_pype_attr(self.session)[1] hier_attr_by_key = { attr["key"]: attr - for attr in hier_custom_attributes + for attr in hier_attrs } # Query user entity (for comments) user = self.session.query( @@ -244,6 +244,19 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): self.session.api_user ) ) + + # Query ftrack hierarchy with parenting + ftrack_hierarchy = self.query_ftrack_entitites( + self.session, self.ft_project) + + # Fill ftrack entities to hierarchy context + # - there is no need to query entities again + matching_entities = self.find_matching_ftrack_entities( + hierarchy_context, ftrack_hierarchy) + # Query custom attribute values of each entity + custom_attr_values_by_id = self.query_custom_attribute_values( + self.session, matching_entities, hier_attrs) + # Get ftrack api module (as they are different per python version) ftrack_api = self.context.data["ftrackPythonModule"] @@ -260,75 +273,87 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): entity_type = entity_data['entity_type'] self.log.debug(entity_data) - self.log.debug(entity_type) - if entity_type.lower() == 'project': - entity = self.ft_project - - elif self.ft_project is None or parent is None: + entity = entity_data.get("ft_entity") + if entity is None and entity_type.lower() == "project": raise AssertionError( "Collected items are not in right order!" ) - # try to find if entity already exists - else: - query = ( - 'TypedContext where name is "{0}" and ' - 'project_id is "{1}"' - ).format(entity_name, self.ft_project["id"]) - try: - entity = self.session.query(query).one() - except Exception: - entity = None - # Create entity if not exists if entity is None: - entity = self.create_entity( - name=entity_name, - type=entity_type, - parent=parent - ) + entity = self.session.create(entity_type, { + "name": entity_name, + "parent": parent + }) + entity_data["ft_entity"] = entity + # self.log.info('entity: {}'.format(dict(entity))) # CUSTOM ATTRIBUTES - custom_attributes = entity_data.get('custom_attributes', []) - instances = [ - instance - for instance in self.context - if instance.data.get("asset") == entity["name"] - ] + custom_attributes = entity_data.get('custom_attributes', {}) + instances = [] + for instance in self.context: + instance_asset_name = instance.data.get("asset") + if ( + instance_asset_name + and instance_asset_name.lower() == entity["name"].lower() + ): + instances.append(instance) for instance in instances: instance.data["ftrackEntity"] = entity - for key in custom_attributes: + for key, cust_attr_value in custom_attributes.items(): + if cust_attr_value is None: + continue + hier_attr = hier_attr_by_key.get(key) # Use simple method if key is not hierarchical if not hier_attr: - assert (key in entity['custom_attributes']), ( - 'Missing custom attribute key: `{0}` in attrs: ' - '`{1}`'.format(key, entity['custom_attributes'].keys()) + if key not in entity["custom_attributes"]: + raise KnownPublishError(( + "Missing custom attribute in ftrack with name '{}'" + ).format(key)) + + entity['custom_attributes'][key] = cust_attr_value + continue + + attr_id = hier_attr["id"] + entity_values = custom_attr_values_by_id.get(entity["id"], {}) + # New value is defined by having id in values + # - it can be set to 'None' (ftrack allows that using API) + is_new_value = attr_id not in entity_values + attr_value = entity_values.get(attr_id) + + # Use ftrack operations method to set hiearchical + # attribute value. + # - this is because there may be non hiearchical custom + # attributes with different properties + entity_key = collections.OrderedDict(( + ("configuration_id", hier_attr["id"]), + ("entity_id", entity["id"]) + )) + op = None + if is_new_value: + op = ftrack_api.operation.CreateEntityOperation( + "CustomAttributeValue", + entity_key, + {"value": cust_attr_value} ) - entity['custom_attributes'][key] = custom_attributes[key] - - else: - # Use ftrack operations method to set hiearchical - # attribute value. - # - this is because there may be non hiearchical custom - # attributes with different properties - entity_key = collections.OrderedDict() - entity_key["configuration_id"] = hier_attr["id"] - entity_key["entity_id"] = entity["id"] - self.session.recorded_operations.push( - ftrack_api.operation.UpdateEntityOperation( - "ContextCustomAttributeValue", - entity_key, - "value", - ftrack_api.symbol.NOT_SET, - custom_attributes[key] - ) + elif attr_value != cust_attr_value: + op = ftrack_api.operation.UpdateEntityOperation( + "CustomAttributeValue", + entity_key, + "value", + attr_value, + cust_attr_value ) + if op is not None: + self.session.recorded_operations.push(op) + + if self.session.recorded_operations: try: self.session.commit() except Exception: @@ -342,7 +367,7 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): for instance in instances: task_name = instance.data.get("task") if task_name: - instances_by_task_name[task_name].append(instance) + instances_by_task_name[task_name.lower()].append(instance) tasks = entity_data.get('tasks', []) existing_tasks = [] @@ -500,21 +525,6 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): return task - def create_entity(self, name, type, parent): - entity = self.session.create(type, { - 'name': name, - 'parent': parent - }) - try: - self.session.commit() - except Exception: - tp, value, tb = sys.exc_info() - self.session.rollback() - self.session._configure_locations() - six.reraise(tp, value, tb) - - return entity - def _get_active_assets(self, context): """ Returns only asset dictionary. Usually the last part of deep dictionary which @@ -536,19 +546,17 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): hierarchy_context = context.data["hierarchyContext"] - active_assets = [] + active_assets = set() # filter only the active publishing insatnces for instance in context: if instance.data.get("publish") is False: continue - if not instance.data.get("asset"): - continue - - active_assets.append(instance.data["asset"]) + asset_name = instance.data.get("asset") + if asset_name: + active_assets.add(asset_name) # remove duplicity in list - active_assets = list(set(active_assets)) - self.log.debug("__ active_assets: {}".format(active_assets)) + self.log.debug("__ active_assets: {}".format(list(active_assets))) return get_pure_hierarchy_data(hierarchy_context)