From e6ccdc31364896217f9d59782ec834f4fa8e282d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 30 Mar 2021 15:43:17 +0200 Subject: [PATCH 001/180] Hiero: rename `master` to `hero` --- pype/hosts/hiero/api/plugin.py | 30 +++++++++---------- .../hiero/plugins/create/create_shot_clip.py | 4 +-- .../publish/collect_hierarchy_context.py | 4 +-- test_localsystem.txt | 1 + 4 files changed, 20 insertions(+), 19 deletions(-) create mode 100644 test_localsystem.txt diff --git a/pype/hosts/hiero/api/plugin.py b/pype/hosts/hiero/api/plugin.py index 06ee403a5b..cfbe7f664f 100644 --- a/pype/hosts/hiero/api/plugin.py +++ b/pype/hosts/hiero/api/plugin.py @@ -266,7 +266,7 @@ class CreatorWidget(QtWidgets.QDialog): elif v["type"] == "QSpinBox": data[k]["value"] = self.create_row( content_layout, "QSpinBox", v["label"], - setValue=v["value"], setMaximum=10000, setToolTip=tool_tip) + setValue=v["value"], setMinimum=1, setMaximum=100000, setToolTip=tool_tip) return data @@ -782,13 +782,13 @@ class PublishClip: Populating the tag data into internal variable self.tag_data """ # define vertical sync attributes - master_layer = True + hero_track = True self.review_layer = "" if self.vertical_sync: # check if track name is not in driving layer if self.track_name not in self.driving_layer: # if it is not then define vertical sync as None - master_layer = False + hero_track = False # increasing steps by index of rename iteration self.count_steps *= self.rename_index @@ -802,7 +802,7 @@ class PublishClip: self.tag_data[_k] = _v["value"] # driving layer is set as positive match - if master_layer or self.vertical_sync: + if hero_track or self.vertical_sync: # mark review layer if self.review_track and ( self.review_track not in self.review_track_default): @@ -836,32 +836,32 @@ class PublishClip: hierarchy_formating_data ) - tag_hierarchy_data.update({"masterLayer": True}) - if master_layer and self.vertical_sync: + tag_hierarchy_data.update({"heroTrack": True}) + if hero_track and self.vertical_sync: self.vertical_clip_match.update({ (self.clip_in, self.clip_out): tag_hierarchy_data }) - if not master_layer and self.vertical_sync: + if not hero_track and self.vertical_sync: # driving layer is set as negative match - for (_in, _out), master_data in self.vertical_clip_match.items(): - master_data.update({"masterLayer": False}) + for (_in, _out), hero_data in self.vertical_clip_match.items(): + hero_data.update({"heroTrack": False}) if _in == self.clip_in and _out == self.clip_out: - data_subset = master_data["subset"] - # add track index in case duplicity of names in master data + data_subset = hero_data["subset"] + # add track index in case duplicity of names in hero data if self.subset in data_subset: - master_data["subset"] = self.subset + str( + hero_data["subset"] = self.subset + str( self.track_index) # in case track name and subset name is the same then add if self.subset_name == self.track_name: - master_data["subset"] = self.subset + hero_data["subset"] = self.subset # assing data to return hierarchy data to tag - tag_hierarchy_data = master_data + tag_hierarchy_data = hero_data # add data to return data dict self.tag_data.update(tag_hierarchy_data) - if master_layer and self.review_layer: + if hero_track and self.review_layer: self.tag_data.update({"reviewTrack": self.review_layer}) def _solve_tag_hierarchy_data(self, hierarchy_formating_data): diff --git a/pype/hosts/hiero/plugins/create/create_shot_clip.py b/pype/hosts/hiero/plugins/create/create_shot_clip.py index 9f8ae1a300..268f84b127 100644 --- a/pype/hosts/hiero/plugins/create/create_shot_clip.py +++ b/pype/hosts/hiero/plugins/create/create_shot_clip.py @@ -120,9 +120,9 @@ class CreateShotClip(phiero.Creator): "vSyncTrack": { "value": gui_tracks, # noqa "type": "QComboBox", - "label": "Master track", + "label": "Hero track", "target": "ui", - "toolTip": "Select driving track name which should be mastering all others", # noqa + "toolTip": "Select driving track name which should be hero for all others", # noqa "order": 1} } }, diff --git a/pype/hosts/hiero/plugins/publish/collect_hierarchy_context.py b/pype/hosts/hiero/plugins/publish/collect_hierarchy_context.py index ba3e388c53..0696a58e39 100644 --- a/pype/hosts/hiero/plugins/publish/collect_hierarchy_context.py +++ b/pype/hosts/hiero/plugins/publish/collect_hierarchy_context.py @@ -39,8 +39,8 @@ class CollectHierarchy(pyblish.api.ContextPlugin): if not set(self.families).intersection(families): continue - # exclude if not masterLayer True - if not instance.data.get("masterLayer"): + # exclude if not heroTrack True + if not instance.data.get("heroTrack"): continue # update families to include `shot` for hierarchy integration diff --git a/test_localsystem.txt b/test_localsystem.txt new file mode 100644 index 0000000000..dde7986af8 --- /dev/null +++ b/test_localsystem.txt @@ -0,0 +1 @@ +I have run From 88a9c11773e74347bc4b93bb1848d40b5315e559 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 30 Mar 2021 15:44:54 +0200 Subject: [PATCH 002/180] Hiero: allow only hero trackItem for review also fix name of attribute to reviewTrack --- pype/hosts/hiero/plugins/publish/collect_review.py | 2 +- .../hiero/plugins/publish/precollect_instances.py | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/pype/hosts/hiero/plugins/publish/collect_review.py b/pype/hosts/hiero/plugins/publish/collect_review.py index 6f550223ce..4df490ab70 100644 --- a/pype/hosts/hiero/plugins/publish/collect_review.py +++ b/pype/hosts/hiero/plugins/publish/collect_review.py @@ -29,7 +29,7 @@ class CollectReview(api.InstancePlugin): Exception: description """ - review_track = instance.data.get("review") + review_track = instance.data.get("reviewTrack") video_tracks = instance.context.data["videoTracks"] for track in video_tracks: if review_track not in track.name(): diff --git a/pype/hosts/hiero/plugins/publish/precollect_instances.py b/pype/hosts/hiero/plugins/publish/precollect_instances.py index 2ca637cf57..51378b422e 100644 --- a/pype/hosts/hiero/plugins/publish/precollect_instances.py +++ b/pype/hosts/hiero/plugins/publish/precollect_instances.py @@ -14,6 +14,7 @@ class PreCollectInstances(api.ContextPlugin): label = "Pre-collect Instances" hosts = ["hiero"] + def process(self, context): track_items = phiero.get_track_items( selected=True, check_tagged=True, check_enabled=True) @@ -34,7 +35,7 @@ class PreCollectInstances(api.ContextPlugin): "Processing enabled track items: {}".format(len(track_items))) for _ti in track_items: - data = dict() + data = {} clip = _ti.source() # get clips subtracks and anotations @@ -60,7 +61,8 @@ class PreCollectInstances(api.ContextPlugin): asset = tag_parsed_data["asset"] subset = tag_parsed_data["subset"] - review = tag_parsed_data.get("review") + review_track = tag_parsed_data.get("reviewTrack") + hiero_track = tag_parsed_data.get("heroTrack") audio = tag_parsed_data.get("audio") # remove audio attribute from data @@ -78,8 +80,8 @@ class PreCollectInstances(api.ContextPlugin): file_info = media_source.fileinfos().pop() source_first_frame = int(file_info.startFrame()) - # apply only for feview and master track instance - if review: + # apply only for review and master track instance + if review_track and hiero_track: families += ["review", "ftrack"] data.update({ @@ -94,6 +96,7 @@ class PreCollectInstances(api.ContextPlugin): # track item attributes "track": track.name(), "trackItem": track, + "reviewTrack": review_track, # version data "versionData": { @@ -113,7 +116,7 @@ class PreCollectInstances(api.ContextPlugin): instance = context.create_instance(**data) - self.log.info("Creating instance: {}".format(instance)) + self.log.info("Creating instance.data: {}".format(instance.data)) if audio: a_data = dict() From 6e8c574509a354ad2ffd2b650b01025ea62c8363 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 30 Mar 2021 15:45:54 +0200 Subject: [PATCH 003/180] Hiero: fix `pype.api.subprocess` is obsolete api call --- .../hiero/plugins/publish/extract_review_preparation.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pype/hosts/hiero/plugins/publish/extract_review_preparation.py b/pype/hosts/hiero/plugins/publish/extract_review_preparation.py index bc4a895ba4..3fdc946f3c 100644 --- a/pype/hosts/hiero/plugins/publish/extract_review_preparation.py +++ b/pype/hosts/hiero/plugins/publish/extract_review_preparation.py @@ -132,7 +132,7 @@ class ExtractReviewPreparation(pype.api.Extractor): ).format(**locals()) self.log.debug("ffprob_cmd: {}".format(ffprob_cmd)) - audio_check_output = pype.api.subprocess(ffprob_cmd) + audio_check_output = pype.api.run_subprocess(ffprob_cmd) self.log.debug( "audio_check_output: {}".format(audio_check_output)) @@ -167,7 +167,7 @@ class ExtractReviewPreparation(pype.api.Extractor): # try to get video native resolution data try: - resolution_output = pype.api.subprocess(( + resolution_output = pype.api.run_subprocess(( "\"{ffprobe_path}\" -i \"{full_input_path}\"" " -v error " "-select_streams v:0 -show_entries " @@ -280,7 +280,7 @@ class ExtractReviewPreparation(pype.api.Extractor): # run subprocess self.log.debug("Executing: {}".format(subprcs_cmd)) - output = pype.api.subprocess(subprcs_cmd) + output = pype.api.run_subprocess(subprcs_cmd) self.log.debug("Output: {}".format(output)) repre_new = { From c5ab3e5ebbbdb4c19fc96df6f2fa9f38550cf769 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 30 Mar 2021 15:47:20 +0200 Subject: [PATCH 004/180] Global: Fix if None in `entity.data.tasks` then they should be dict and not list as it was before --- pype/plugins/publish/extract_hierarchy_avalon.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pype/plugins/publish/extract_hierarchy_avalon.py b/pype/plugins/publish/extract_hierarchy_avalon.py index 74751c6807..5643f04a76 100644 --- a/pype/plugins/publish/extract_hierarchy_avalon.py +++ b/pype/plugins/publish/extract_hierarchy_avalon.py @@ -99,13 +99,20 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): if entity: # Do not override data, only update cur_entity_data = entity.get("data") or {} + entity_tasks = cur_entity_data["tasks"] or {} + + # create tasks as dict by default + if not entity_tasks: + cur_entity_data["tasks"] = entity_tasks + new_tasks = data.pop("tasks", {}) if "tasks" not in cur_entity_data and not new_tasks: continue for task_name in new_tasks: - if task_name in cur_entity_data["tasks"].keys(): + if task_name in entity_tasks.keys(): continue - cur_entity_data["tasks"][task_name] = new_tasks[task_name] + cur_entity_data["tasks"][task_name] = new_tasks[ + task_name] cur_entity_data.update(data) data = cur_entity_data else: From 3dc41d283b2c77b5bb43dfb37636f4331a692750 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 30 Mar 2021 16:00:47 +0200 Subject: [PATCH 005/180] Hiero: hound fix --- pype/hosts/hiero/api/plugin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pype/hosts/hiero/api/plugin.py b/pype/hosts/hiero/api/plugin.py index cfbe7f664f..c2af4a011c 100644 --- a/pype/hosts/hiero/api/plugin.py +++ b/pype/hosts/hiero/api/plugin.py @@ -266,7 +266,8 @@ class CreatorWidget(QtWidgets.QDialog): elif v["type"] == "QSpinBox": data[k]["value"] = self.create_row( content_layout, "QSpinBox", v["label"], - setValue=v["value"], setMinimum=1, setMaximum=100000, setToolTip=tool_tip) + setValue=v["value"], setMinimum=1, + setMaximum=100000, setToolTip=tool_tip) return data From 7196c1bb501bdcd8ac1189db98b9a5f57ce34975 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 30 Mar 2021 19:27:20 +0200 Subject: [PATCH 006/180] removing unwanted scam file ;) --- test_localsystem.txt | 1 - 1 file changed, 1 deletion(-) delete mode 100644 test_localsystem.txt diff --git a/test_localsystem.txt b/test_localsystem.txt deleted file mode 100644 index dde7986af8..0000000000 --- a/test_localsystem.txt +++ /dev/null @@ -1 +0,0 @@ -I have run From fc7af0e4749530869c3d739075738a82b55ca849 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 30 Mar 2021 19:48:22 +0200 Subject: [PATCH 007/180] Global: ignore test_localsystem.txt --- .gitignore | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 47a37410e6..f1295e32eb 100644 --- a/.gitignore +++ b/.gitignore @@ -63,7 +63,6 @@ coverage.xml .hypothesis/ .pytest_cache/ - # Node JS packages ################## node_modules/ @@ -79,4 +78,5 @@ pype/premiere/ppro/js/debug.log # VScode files .vscode/ .env -dump.sql \ No newline at end of file +dump.sql +test_localsystem.txt From c1e4d19030224afce99e9f6ed2bf11b849d978b6 Mon Sep 17 00:00:00 2001 From: jezscha Date: Wed, 31 Mar 2021 11:43:22 +0000 Subject: [PATCH 008/180] Create draft PR for #1235 From 40fe1ba76a1487f2e09a1294516baf5247b66dee Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 6 Apr 2021 13:32:00 +0200 Subject: [PATCH 009/180] Hiero: Solving Merge conflicts --- {pype => openpype}/hosts/hiero/api/plugin.py | 16 +- .../hiero/plugins/create/create_shot_clip.py | 4 +- .../hiero/plugins/publish/collect_review.py | 2 +- .../publish/extract_review_preparation.py | 6 +- .../plugins/publish/precollect_instances.py | 13 +- .../publish/extract_hierarchy_avalon.py | 12 +- .../hiero/plugins/publish/collect_review.py | 261 -------------- .../publish/extract_review_preparation.py | 334 ------------------ .../plugins/publish/precollect_instances.py | 224 ------------ pype/modules/ftrack/python2_vendor/arrow | 1 - .../ftrack/python2_vendor/ftrack-python-api | 1 - .../publish/extract_hierarchy_avalon.py | 202 ----------- 12 files changed, 31 insertions(+), 1045 deletions(-) rename {pype => openpype}/hosts/hiero/api/plugin.py (98%) delete mode 100644 pype/hosts/hiero/plugins/publish/collect_review.py delete mode 100644 pype/hosts/hiero/plugins/publish/extract_review_preparation.py delete mode 100644 pype/hosts/hiero/plugins/publish/precollect_instances.py delete mode 160000 pype/modules/ftrack/python2_vendor/arrow delete mode 160000 pype/modules/ftrack/python2_vendor/ftrack-python-api delete mode 100644 pype/plugins/publish/extract_hierarchy_avalon.py diff --git a/pype/hosts/hiero/api/plugin.py b/openpype/hosts/hiero/api/plugin.py similarity index 98% rename from pype/hosts/hiero/api/plugin.py rename to openpype/hosts/hiero/api/plugin.py index c2af4a011c..b356c9b6ce 100644 --- a/pype/hosts/hiero/api/plugin.py +++ b/openpype/hosts/hiero/api/plugin.py @@ -4,10 +4,10 @@ import hiero from Qt import QtWidgets, QtCore from avalon.vendor import qargparse import avalon.api as avalon -import pype.api as pype +import openpype.api as openpype from . import lib -log = pype.Logger().get_logger(__name__) +log = openpype.Logger().get_logger(__name__) def load_stylesheet(): @@ -477,7 +477,7 @@ class ClipLoader: """ asset_name = self.context["representation"]["context"]["asset"] - self.data["assetData"] = pype.get_asset(asset_name)["data"] + self.data["assetData"] = openpype.get_asset(asset_name)["data"] def _make_track_item(self, source_bin_item, audio=False): """ Create track item with """ @@ -593,16 +593,16 @@ class ClipLoader: return track_item -class Creator(pype.Creator): +class Creator(openpype.Creator): """Creator class wrapper """ clip_color = "Purple" rename_index = None def __init__(self, *args, **kwargs): - import pype.hosts.hiero.api as phiero + import openpype.hosts.hiero.api as phiero super(Creator, self).__init__(*args, **kwargs) - self.presets = pype.get_current_project_settings()[ + self.presets = openpype.get_current_project_settings()[ "hiero"]["create"].get(self.__class__.__name__, {}) # adding basic current context resolve objects @@ -774,8 +774,8 @@ class PublishClip: _spl = text.split("#") _len = (len(_spl) - 1) _repl = "{{{0}:0>{1}}}".format(name, _len) - new_text = text.replace(("#" * _len), _repl) - return new_text + return text.replace(("#" * _len), _repl) + def _convert_to_tag_data(self): """ Convert internal data to tag data. diff --git a/openpype/hosts/hiero/plugins/create/create_shot_clip.py b/openpype/hosts/hiero/plugins/create/create_shot_clip.py index 07b7a62b2a..25be9f090b 100644 --- a/openpype/hosts/hiero/plugins/create/create_shot_clip.py +++ b/openpype/hosts/hiero/plugins/create/create_shot_clip.py @@ -120,9 +120,9 @@ class CreateShotClip(phiero.Creator): "vSyncTrack": { "value": gui_tracks, # noqa "type": "QComboBox", - "label": "Master track", + "label": "Hero track", "target": "ui", - "toolTip": "Select driving track name which should be mastering all others", # noqa + "toolTip": "Select driving track name which should be hero for all others", # noqa "order": 1} } }, diff --git a/openpype/hosts/hiero/plugins/publish/collect_review.py b/openpype/hosts/hiero/plugins/publish/collect_review.py index a0ab00b355..b1d97a71d7 100644 --- a/openpype/hosts/hiero/plugins/publish/collect_review.py +++ b/openpype/hosts/hiero/plugins/publish/collect_review.py @@ -29,7 +29,7 @@ class CollectReview(api.InstancePlugin): Exception: description """ - review_track = instance.data.get("review") + review_track = instance.data.get("reviewTrack") video_tracks = instance.context.data["videoTracks"] for track in video_tracks: if review_track not in track.name(): diff --git a/openpype/hosts/hiero/plugins/publish/extract_review_preparation.py b/openpype/hosts/hiero/plugins/publish/extract_review_preparation.py index 5456ddc3c4..aac476e27a 100644 --- a/openpype/hosts/hiero/plugins/publish/extract_review_preparation.py +++ b/openpype/hosts/hiero/plugins/publish/extract_review_preparation.py @@ -132,7 +132,7 @@ class ExtractReviewPreparation(openpype.api.Extractor): ).format(**locals()) self.log.debug("ffprob_cmd: {}".format(ffprob_cmd)) - audio_check_output = openpype.api.subprocess(ffprob_cmd) + audio_check_output = openpype.api.run_subprocess(ffprob_cmd) self.log.debug( "audio_check_output: {}".format(audio_check_output)) @@ -167,7 +167,7 @@ class ExtractReviewPreparation(openpype.api.Extractor): # try to get video native resolution data try: - resolution_output = openpype.api.subprocess(( + resolution_output = openpype.api.run_subprocess(( "\"{ffprobe_path}\" -i \"{full_input_path}\"" " -v error " "-select_streams v:0 -show_entries " @@ -280,7 +280,7 @@ class ExtractReviewPreparation(openpype.api.Extractor): # run subprocess self.log.debug("Executing: {}".format(subprcs_cmd)) - output = openpype.api.subprocess(subprcs_cmd) + output = openpype.api.run_subprocess(subprcs_cmd) self.log.debug("Output: {}".format(output)) repre_new = { diff --git a/openpype/hosts/hiero/plugins/publish/precollect_instances.py b/openpype/hosts/hiero/plugins/publish/precollect_instances.py index bdf007de06..2b769afee1 100644 --- a/openpype/hosts/hiero/plugins/publish/precollect_instances.py +++ b/openpype/hosts/hiero/plugins/publish/precollect_instances.py @@ -14,6 +14,7 @@ class PreCollectInstances(api.ContextPlugin): label = "Pre-collect Instances" hosts = ["hiero"] + def process(self, context): track_items = phiero.get_track_items( selected=True, check_tagged=True, check_enabled=True) @@ -34,7 +35,7 @@ class PreCollectInstances(api.ContextPlugin): "Processing enabled track items: {}".format(len(track_items))) for _ti in track_items: - data = dict() + data = {} clip = _ti.source() # get clips subtracks and anotations @@ -60,7 +61,8 @@ class PreCollectInstances(api.ContextPlugin): asset = tag_parsed_data["asset"] subset = tag_parsed_data["subset"] - review = tag_parsed_data.get("review") + review_track = tag_parsed_data.get("reviewTrack") + hiero_track = tag_parsed_data.get("heroTrack") audio = tag_parsed_data.get("audio") # remove audio attribute from data @@ -78,8 +80,8 @@ class PreCollectInstances(api.ContextPlugin): file_info = media_source.fileinfos().pop() source_first_frame = int(file_info.startFrame()) - # apply only for feview and master track instance - if review: + # apply only for review and master track instance + if review_track and hiero_track: families += ["review", "ftrack"] data.update({ @@ -94,6 +96,7 @@ class PreCollectInstances(api.ContextPlugin): # track item attributes "track": track.name(), "trackItem": track, + "reviewTrack": review_track, # version data "versionData": { @@ -113,7 +116,7 @@ class PreCollectInstances(api.ContextPlugin): instance = context.create_instance(**data) - self.log.info("Creating instance: {}".format(instance)) + self.log.info("Creating instance.data: {}".format(instance.data)) if audio: a_data = dict() diff --git a/openpype/plugins/publish/extract_hierarchy_avalon.py b/openpype/plugins/publish/extract_hierarchy_avalon.py index dd1f09bafa..e263edd931 100644 --- a/openpype/plugins/publish/extract_hierarchy_avalon.py +++ b/openpype/plugins/publish/extract_hierarchy_avalon.py @@ -2,7 +2,6 @@ import pyblish.api from avalon import io from copy import deepcopy - class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): """Create entities in Avalon based on collected data.""" @@ -100,13 +99,20 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): if entity: # Do not override data, only update cur_entity_data = entity.get("data") or {} + entity_tasks = cur_entity_data["tasks"] or {} + + # create tasks as dict by default + if not entity_tasks: + cur_entity_data["tasks"] = entity_tasks + new_tasks = data.pop("tasks", {}) if "tasks" not in cur_entity_data and not new_tasks: continue for task_name in new_tasks: - if task_name in cur_entity_data["tasks"].keys(): + if task_name in entity_tasks.keys(): continue - cur_entity_data["tasks"][task_name] = new_tasks[task_name] + cur_entity_data["tasks"][task_name] = new_tasks[ + task_name] cur_entity_data.update(data) data = cur_entity_data else: diff --git a/pype/hosts/hiero/plugins/publish/collect_review.py b/pype/hosts/hiero/plugins/publish/collect_review.py deleted file mode 100644 index 4df490ab70..0000000000 --- a/pype/hosts/hiero/plugins/publish/collect_review.py +++ /dev/null @@ -1,261 +0,0 @@ -from pyblish import api -import os -import clique -from pype.hosts.hiero.api import ( - is_overlapping, get_sequence_pattern_and_padding) - - -class CollectReview(api.InstancePlugin): - """Collect review representation. - """ - - # Run just before CollectSubsets - order = api.CollectorOrder + 0.1022 - label = "Collect Review" - hosts = ["hiero"] - families = ["review"] - - def get_review_item(self, instance): - """ - Get review clip track item from review track name - - Args: - instance (obj): publishing instance - - Returns: - hiero.core.TrackItem: corresponding track item - - Raises: - Exception: description - - """ - review_track = instance.data.get("reviewTrack") - video_tracks = instance.context.data["videoTracks"] - for track in video_tracks: - if review_track not in track.name(): - continue - for item in track.items(): - self.log.debug(item) - if is_overlapping(item, self.main_clip): - self.log.debug("Winner is: {}".format(item)) - break - - # validate the clip is fully converted with review clip - assert is_overlapping( - item, self.main_clip, strict=True), ( - "Review clip not cowering fully " - "the clip `{}`").format(self.main_clip.name()) - - return item - - def process(self, instance): - tags = ["review", "ftrackreview"] - - # get reviewable item from `review` instance.data attribute - self.main_clip = instance.data.get("item") - self.rw_clip = self.get_review_item(instance) - - # let user know there is missing review clip and convert instance - # back as not reviewable - assert self.rw_clip, "Missing reviewable clip for '{}'".format( - self.main_clip.name() - ) - - # add to representations - if not instance.data.get("representations"): - instance.data["representations"] = list() - - # get review media main info - rw_source = self.rw_clip.source().mediaSource() - rw_source_duration = int(rw_source.duration()) - self.rw_source_path = rw_source.firstpath() - rw_source_file_info = rw_source.fileinfos().pop() - - # define if review media is sequence - is_sequence = bool(not rw_source.singleFile()) - self.log.debug("is_sequence: {}".format(is_sequence)) - - # get handles - handle_start = instance.data["handleStart"] - handle_end = instance.data["handleEnd"] - - # review timeline and source frame ranges - rw_clip_in = int(self.rw_clip.timelineIn()) - rw_clip_out = int(self.rw_clip.timelineOut()) - self.rw_clip_source_in = int(self.rw_clip.sourceIn()) - self.rw_clip_source_out = int(self.rw_clip.sourceOut()) - rw_source_first = int(rw_source_file_info.startFrame()) - - # calculate delivery source_in and source_out - # main_clip_timeline_in - review_item_timeline_in + 1 - main_clip_in = self.main_clip.timelineIn() - main_clip_out = self.main_clip.timelineOut() - - source_in_diff = main_clip_in - rw_clip_in - source_out_diff = main_clip_out - rw_clip_out - - if source_in_diff: - self.rw_clip_source_in += source_in_diff - if source_out_diff: - self.rw_clip_source_out += source_out_diff - - # review clip durations - rw_clip_duration = ( - self.rw_clip_source_out - self.rw_clip_source_in) + 1 - rw_clip_duration_h = rw_clip_duration + ( - handle_start + handle_end) - - # add created data to review item data - instance.data["reviewItemData"] = { - "mediaDuration": rw_source_duration - } - - file_dir = os.path.dirname(self.rw_source_path) - file = os.path.basename(self.rw_source_path) - ext = os.path.splitext(file)[-1] - - # detect if sequence - if not is_sequence: - # is video file - files = file - else: - files = list() - spliter, padding = get_sequence_pattern_and_padding(file) - self.log.debug("_ spliter, padding: {}, {}".format( - spliter, padding)) - base_name = file.split(spliter)[0] - - # define collection and calculate frame range - collection = clique.Collection(base_name, ext, padding, set(range( - int(rw_source_first + int( - self.rw_clip_source_in - handle_start)), - int(rw_source_first + int( - self.rw_clip_source_out + handle_end) + 1)))) - self.log.debug("_ collection: {}".format(collection)) - - real_files = os.listdir(file_dir) - self.log.debug("_ real_files: {}".format(real_files)) - - # collect frames to repre files list - for item in collection: - if item not in real_files: - self.log.debug("_ item: {}".format(item)) - continue - files.append(item) - - # add prep tag - tags.extend(["prep", "delete"]) - - # change label - instance.data["label"] = "{0} - ({1})".format( - instance.data["label"], ext - ) - - self.log.debug("Instance review: {}".format(instance.data["name"])) - - # adding representation for review mov - representation = { - "files": files, - "stagingDir": file_dir, - "frameStart": rw_source_first + self.rw_clip_source_in, - "frameEnd": rw_source_first + self.rw_clip_source_out, - "frameStartFtrack": int( - self.rw_clip_source_in - handle_start), - "frameEndFtrack": int(self.rw_clip_source_out + handle_end), - "step": 1, - "fps": instance.data["fps"], - "name": "review", - "tags": tags, - "ext": ext[1:] - } - - if rw_source_duration > rw_clip_duration_h: - self.log.debug("Media duration higher: {}".format( - (rw_source_duration - rw_clip_duration_h))) - representation.update({ - "frameStart": rw_source_first + int( - self.rw_clip_source_in - handle_start), - "frameEnd": rw_source_first + int( - self.rw_clip_source_out + handle_end), - "tags": ["_cut-bigger", "prep", "delete"] - }) - elif rw_source_duration < rw_clip_duration_h: - self.log.debug("Media duration higher: {}".format( - (rw_source_duration - rw_clip_duration_h))) - representation.update({ - "frameStart": rw_source_first + int( - self.rw_clip_source_in - handle_start), - "frameEnd": rw_source_first + int( - self.rw_clip_source_out + handle_end), - "tags": ["prep", "delete"] - }) - - instance.data["representations"].append(representation) - - self.create_thumbnail(instance) - - self.log.debug( - "Added representations: {}".format( - instance.data["representations"])) - - def create_thumbnail(self, instance): - source_file = os.path.basename(self.rw_source_path) - spliter, padding = get_sequence_pattern_and_padding(source_file) - - if spliter: - head, ext = source_file.split(spliter) - else: - head, ext = os.path.splitext(source_file) - - # staging dir creation - staging_dir = os.path.dirname( - self.rw_source_path) - - # get thumbnail frame from the middle - thumb_frame = int(self.rw_clip_source_in + ( - (self.rw_clip_source_out - self.rw_clip_source_in) / 2)) - - thumb_file = "{}thumbnail{}{}".format(head, thumb_frame, ".png") - thumb_path = os.path.join(staging_dir, thumb_file) - - thumbnail = self.rw_clip.thumbnail(thumb_frame).save( - thumb_path, - format='png' - ) - self.log.debug( - "__ thumbnail: `{}`, frame: `{}`".format(thumbnail, thumb_frame)) - - self.log.debug("__ thumbnail: {}".format(thumbnail)) - thumb_representation = { - 'files': thumb_file, - 'stagingDir': staging_dir, - 'name': "thumbnail", - 'thumbnail': True, - 'ext': "png" - } - instance.data["representations"].append( - thumb_representation) - - def version_data(self, instance): - transfer_data = [ - "handleStart", "handleEnd", "sourceIn", "sourceOut", - "frameStart", "frameEnd", "sourceInH", "sourceOutH", - "clipIn", "clipOut", "clipInH", "clipOutH", "asset", - "track" - ] - - version_data = dict() - # pass data to version - version_data.update({k: instance.data[k] for k in transfer_data}) - - if 'version' in instance.data: - version_data["version"] = instance.data["version"] - - # add to data of representation - version_data.update({ - "colorspace": self.rw_clip.sourceMediaColourTransform(), - "families": instance.data["families"], - "subset": instance.data["subset"], - "fps": instance.data["fps"] - }) - instance.data["versionData"] = version_data diff --git a/pype/hosts/hiero/plugins/publish/extract_review_preparation.py b/pype/hosts/hiero/plugins/publish/extract_review_preparation.py deleted file mode 100644 index 3fdc946f3c..0000000000 --- a/pype/hosts/hiero/plugins/publish/extract_review_preparation.py +++ /dev/null @@ -1,334 +0,0 @@ -import os -import sys -import six -import errno -from pyblish import api -import pype -import clique -from avalon.vendor import filelink - - -class ExtractReviewPreparation(pype.api.Extractor): - """Cut up clips from long video file""" - - order = api.ExtractorOrder - label = "Extract Review Preparation" - hosts = ["hiero"] - families = ["review"] - - # presets - tags_addition = [] - - def process(self, instance): - inst_data = instance.data - asset = inst_data["asset"] - review_item_data = instance.data.get("reviewItemData") - - # get representation and loop them - representations = inst_data["representations"] - - # get resolution default - resolution_width = inst_data["resolutionWidth"] - resolution_height = inst_data["resolutionHeight"] - - # frame range data - media_duration = review_item_data["mediaDuration"] - - ffmpeg_path = pype.lib.get_ffmpeg_tool_path("ffmpeg") - ffprobe_path = pype.lib.get_ffmpeg_tool_path("ffprobe") - - # filter out mov and img sequences - representations_new = representations[:] - for repre in representations: - input_args = list() - output_args = list() - - tags = repre.get("tags", []) - - # check if supported tags are in representation for activation - filter_tag = False - for tag in ["_cut-bigger", "prep"]: - if tag in tags: - filter_tag = True - break - if not filter_tag: - continue - - self.log.debug("__ repre: {}".format(repre)) - - files = repre.get("files") - staging_dir = repre.get("stagingDir") - fps = repre.get("fps") - ext = repre.get("ext") - - # make paths - full_output_dir = os.path.join( - staging_dir, "cuts") - - if isinstance(files, list): - new_files = list() - - # frame range delivery included handles - frame_start = ( - inst_data["frameStart"] - inst_data["handleStart"]) - frame_end = ( - inst_data["frameEnd"] + inst_data["handleEnd"]) - self.log.debug("_ frame_start: {}".format(frame_start)) - self.log.debug("_ frame_end: {}".format(frame_end)) - - # make collection from input files list - collections, remainder = clique.assemble(files) - collection = collections.pop() - self.log.debug("_ collection: {}".format(collection)) - - # name components - head = collection.format("{head}") - padding = collection.format("{padding}") - tail = collection.format("{tail}") - self.log.debug("_ head: {}".format(head)) - self.log.debug("_ padding: {}".format(padding)) - self.log.debug("_ tail: {}".format(tail)) - - # make destination file with instance data - # frame start and end range - index = 0 - for image in collection: - dst_file_num = frame_start + index - dst_file_name = head + str(padding % dst_file_num) + tail - src = os.path.join(staging_dir, image) - dst = os.path.join(full_output_dir, dst_file_name) - self.log.info("Creating temp hardlinks: {}".format(dst)) - self.hardlink_file(src, dst) - new_files.append(dst_file_name) - index += 1 - - self.log.debug("_ new_files: {}".format(new_files)) - - else: - # ffmpeg when single file - new_files = "{}_{}".format(asset, files) - - # frame range - frame_start = repre.get("frameStart") - frame_end = repre.get("frameEnd") - - full_input_path = os.path.join( - staging_dir, files) - - os.path.isdir(full_output_dir) or os.makedirs(full_output_dir) - - full_output_path = os.path.join( - full_output_dir, new_files) - - self.log.debug( - "__ full_input_path: {}".format(full_input_path)) - self.log.debug( - "__ full_output_path: {}".format(full_output_path)) - - # check if audio stream is in input video file - ffprob_cmd = ( - "\"{ffprobe_path}\" -i \"{full_input_path}\" -show_streams" - " -select_streams a -loglevel error" - ).format(**locals()) - - self.log.debug("ffprob_cmd: {}".format(ffprob_cmd)) - audio_check_output = pype.api.run_subprocess(ffprob_cmd) - self.log.debug( - "audio_check_output: {}".format(audio_check_output)) - - # Fix one frame difference - """ TODO: this is just work-around for issue: - https://github.com/pypeclub/pype/issues/659 - """ - frame_duration_extend = 1 - if audio_check_output and ("audio" in inst_data["families"]): - frame_duration_extend = 0 - - # translate frame to sec - start_sec = float(frame_start) / fps - duration_sec = float( - (frame_end - frame_start) + frame_duration_extend) / fps - - empty_add = None - - # check if not missing frames at start - if (start_sec < 0) or (media_duration < frame_end): - # for later swithing off `-c:v copy` output arg - empty_add = True - - # init empty variables - video_empty_start = video_layer_start = "" - audio_empty_start = audio_layer_start = "" - video_empty_end = video_layer_end = "" - audio_empty_end = audio_layer_end = "" - audio_input = audio_output = "" - v_inp_idx = 0 - concat_n = 1 - - # try to get video native resolution data - try: - resolution_output = pype.api.run_subprocess(( - "\"{ffprobe_path}\" -i \"{full_input_path}\"" - " -v error " - "-select_streams v:0 -show_entries " - "stream=width,height -of csv=s=x:p=0" - ).format(**locals())) - - x, y = resolution_output.split("x") - resolution_width = int(x) - resolution_height = int(y) - except Exception as _ex: - self.log.warning( - "Video native resolution is untracable: {}".format( - _ex)) - - if audio_check_output: - # adding input for empty audio - input_args.append("-f lavfi -i anullsrc") - - # define audio empty concat variables - audio_input = "[1:a]" - audio_output = ":a=1" - v_inp_idx = 1 - - # adding input for video black frame - input_args.append(( - "-f lavfi -i \"color=c=black:" - "s={resolution_width}x{resolution_height}:r={fps}\"" - ).format(**locals())) - - if (start_sec < 0): - # recalculate input video timing - empty_start_dur = abs(start_sec) - start_sec = 0 - duration_sec = float(frame_end - ( - frame_start + (empty_start_dur * fps)) + 1) / fps - - # define starting empty video concat variables - video_empty_start = ( - "[{v_inp_idx}]trim=duration={empty_start_dur}[gv0];" # noqa - ).format(**locals()) - video_layer_start = "[gv0]" - - if audio_check_output: - # define starting empty audio concat variables - audio_empty_start = ( - "[0]atrim=duration={empty_start_dur}[ga0];" - ).format(**locals()) - audio_layer_start = "[ga0]" - - # alter concat number of clips - concat_n += 1 - - # check if not missing frames at the end - if (media_duration < frame_end): - # recalculate timing - empty_end_dur = float( - frame_end - media_duration + 1) / fps - duration_sec = float( - media_duration - frame_start) / fps - - # define ending empty video concat variables - video_empty_end = ( - "[{v_inp_idx}]trim=duration={empty_end_dur}[gv1];" - ).format(**locals()) - video_layer_end = "[gv1]" - - if audio_check_output: - # define ending empty audio concat variables - audio_empty_end = ( - "[0]atrim=duration={empty_end_dur}[ga1];" - ).format(**locals()) - audio_layer_end = "[ga0]" - - # alter concat number of clips - concat_n += 1 - - # concatting black frame togather - output_args.append(( - "-filter_complex \"" - "{audio_empty_start}" - "{video_empty_start}" - "{audio_empty_end}" - "{video_empty_end}" - "{video_layer_start}{audio_layer_start}[1:v]{audio_input}" # noqa - "{video_layer_end}{audio_layer_end}" - "concat=n={concat_n}:v=1{audio_output}\"" - ).format(**locals())) - - # append ffmpeg input video clip - input_args.append("-ss {}".format(start_sec)) - input_args.append("-t {}".format(duration_sec)) - input_args.append("-i \"{}\"".format(full_input_path)) - - # add copy audio video codec if only shortening clip - if ("_cut-bigger" in tags) and (not empty_add): - output_args.append("-c:v copy") - - # make sure it is having no frame to frame comprassion - output_args.append("-intra") - - # output filename - output_args.append("-y \"{}\"".format(full_output_path)) - - mov_args = [ - "\"{}\"".format(ffmpeg_path), - " ".join(input_args), - " ".join(output_args) - ] - subprcs_cmd = " ".join(mov_args) - - # run subprocess - self.log.debug("Executing: {}".format(subprcs_cmd)) - output = pype.api.run_subprocess(subprcs_cmd) - self.log.debug("Output: {}".format(output)) - - repre_new = { - "files": new_files, - "stagingDir": full_output_dir, - "frameStart": frame_start, - "frameEnd": frame_end, - "frameStartFtrack": frame_start, - "frameEndFtrack": frame_end, - "step": 1, - "fps": fps, - "name": "cut_up_preview", - "tags": [ - "review", "ftrackreview", "delete"] + self.tags_addition, - "ext": ext, - "anatomy_template": "publish" - } - - representations_new.append(repre_new) - - for repre in representations_new: - if ("delete" in repre.get("tags", [])) and ( - "cut_up_preview" not in repre["name"]): - representations_new.remove(repre) - - self.log.debug( - "Representations: {}".format(representations_new)) - instance.data["representations"] = representations_new - - def hardlink_file(self, src, dst): - dirname = os.path.dirname(dst) - - # make sure the destination folder exist - try: - os.makedirs(dirname) - except OSError as e: - if e.errno == errno.EEXIST: - pass - else: - self.log.critical("An unexpected error occurred.") - six.reraise(*sys.exc_info()) - - # create hardlined file - try: - filelink.create(src, dst, filelink.HARDLINK) - except OSError as e: - if e.errno == errno.EEXIST: - pass - else: - self.log.critical("An unexpected error occurred.") - six.reraise(*sys.exc_info()) diff --git a/pype/hosts/hiero/plugins/publish/precollect_instances.py b/pype/hosts/hiero/plugins/publish/precollect_instances.py deleted file mode 100644 index 51378b422e..0000000000 --- a/pype/hosts/hiero/plugins/publish/precollect_instances.py +++ /dev/null @@ -1,224 +0,0 @@ -from compiler.ast import flatten -from pyblish import api -from pype.hosts.hiero import api as phiero -import hiero -# from pype.hosts.hiero.api import lib -# reload(lib) -# reload(phiero) - - -class PreCollectInstances(api.ContextPlugin): - """Collect all Track items selection.""" - - order = api.CollectorOrder - 0.509 - label = "Pre-collect Instances" - hosts = ["hiero"] - - - def process(self, context): - track_items = phiero.get_track_items( - selected=True, check_tagged=True, check_enabled=True) - # only return enabled track items - if not track_items: - track_items = phiero.get_track_items( - check_enabled=True, check_tagged=True) - # get sequence and video tracks - sequence = context.data["activeSequence"] - tracks = sequence.videoTracks() - - # add collection to context - tracks_effect_items = self.collect_sub_track_items(tracks) - - context.data["tracksEffectItems"] = tracks_effect_items - - self.log.info( - "Processing enabled track items: {}".format(len(track_items))) - - for _ti in track_items: - data = {} - clip = _ti.source() - - # get clips subtracks and anotations - annotations = self.clip_annotations(clip) - subtracks = self.clip_subtrack(_ti) - self.log.debug("Annotations: {}".format(annotations)) - self.log.debug(">> Subtracks: {}".format(subtracks)) - - # get pype tag data - tag_parsed_data = phiero.get_track_item_pype_data(_ti) - # self.log.debug(pformat(tag_parsed_data)) - - if not tag_parsed_data: - continue - - if tag_parsed_data.get("id") != "pyblish.avalon.instance": - continue - # add tag data to instance data - data.update({ - k: v for k, v in tag_parsed_data.items() - if k not in ("id", "applieswhole", "label") - }) - - asset = tag_parsed_data["asset"] - subset = tag_parsed_data["subset"] - review_track = tag_parsed_data.get("reviewTrack") - hiero_track = tag_parsed_data.get("heroTrack") - audio = tag_parsed_data.get("audio") - - # remove audio attribute from data - data.pop("audio") - - # insert family into families - family = tag_parsed_data["family"] - families = [str(f) for f in tag_parsed_data["families"]] - families.insert(0, str(family)) - - track = _ti.parent() - media_source = _ti.source().mediaSource() - source_path = media_source.firstpath() - file_head = media_source.filenameHead() - file_info = media_source.fileinfos().pop() - source_first_frame = int(file_info.startFrame()) - - # apply only for review and master track instance - if review_track and hiero_track: - families += ["review", "ftrack"] - - data.update({ - "name": "{} {} {}".format(asset, subset, families), - "asset": asset, - "item": _ti, - "families": families, - - # tags - "tags": _ti.tags(), - - # track item attributes - "track": track.name(), - "trackItem": track, - "reviewTrack": review_track, - - # version data - "versionData": { - "colorspace": _ti.sourceMediaColourTransform() - }, - - # source attribute - "source": source_path, - "sourceMedia": media_source, - "sourcePath": source_path, - "sourceFileHead": file_head, - "sourceFirst": source_first_frame, - - # clip's effect - "clipEffectItems": subtracks - }) - - instance = context.create_instance(**data) - - self.log.info("Creating instance.data: {}".format(instance.data)) - - if audio: - a_data = dict() - - # add tag data to instance data - a_data.update({ - k: v for k, v in tag_parsed_data.items() - if k not in ("id", "applieswhole", "label") - }) - - # create main attributes - subset = "audioMain" - family = "audio" - families = ["clip", "ftrack"] - families.insert(0, str(family)) - - name = "{} {} {}".format(asset, subset, families) - - a_data.update({ - "name": name, - "subset": subset, - "asset": asset, - "family": family, - "families": families, - "item": _ti, - - # tags - "tags": _ti.tags(), - }) - - a_instance = context.create_instance(**a_data) - self.log.info("Creating audio instance: {}".format(a_instance)) - - @staticmethod - def clip_annotations(clip): - """ - Returns list of Clip's hiero.core.Annotation - """ - annotations = [] - subTrackItems = flatten(clip.subTrackItems()) - annotations += [item for item in subTrackItems if isinstance( - item, hiero.core.Annotation)] - return annotations - - @staticmethod - def clip_subtrack(clip): - """ - Returns list of Clip's hiero.core.SubTrackItem - """ - subtracks = [] - subTrackItems = flatten(clip.parent().subTrackItems()) - for item in subTrackItems: - # avoid all anotation - if isinstance(item, hiero.core.Annotation): - continue - # # avoid all not anaibled - if not item.isEnabled(): - continue - subtracks.append(item) - return subtracks - - @staticmethod - def collect_sub_track_items(tracks): - """ - Returns dictionary with track index as key and list of subtracks - """ - # collect all subtrack items - sub_track_items = dict() - for track in tracks: - items = track.items() - - # skip if no clips on track > need track with effect only - if items: - continue - - # skip all disabled tracks - if not track.isEnabled(): - continue - - track_index = track.trackIndex() - _sub_track_items = flatten(track.subTrackItems()) - - # continue only if any subtrack items are collected - if len(_sub_track_items) < 1: - continue - - enabled_sti = list() - # loop all found subtrack items and check if they are enabled - for _sti in _sub_track_items: - # checking if not enabled - if not _sti.isEnabled(): - continue - if isinstance(_sti, hiero.core.Annotation): - continue - # collect the subtrack item - enabled_sti.append(_sti) - - # continue only if any subtrack items are collected - if len(enabled_sti) < 1: - continue - - # add collection of subtrackitems to dict - sub_track_items[track_index] = enabled_sti - - return sub_track_items diff --git a/pype/modules/ftrack/python2_vendor/arrow b/pype/modules/ftrack/python2_vendor/arrow deleted file mode 160000 index b746fedf72..0000000000 --- a/pype/modules/ftrack/python2_vendor/arrow +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b746fedf7286c3755a46f07ab72f4c414cd41fc0 diff --git a/pype/modules/ftrack/python2_vendor/ftrack-python-api b/pype/modules/ftrack/python2_vendor/ftrack-python-api deleted file mode 160000 index d277f474ab..0000000000 --- a/pype/modules/ftrack/python2_vendor/ftrack-python-api +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d277f474ab016e7b53479c36af87cb861d0cc53e diff --git a/pype/plugins/publish/extract_hierarchy_avalon.py b/pype/plugins/publish/extract_hierarchy_avalon.py deleted file mode 100644 index 5643f04a76..0000000000 --- a/pype/plugins/publish/extract_hierarchy_avalon.py +++ /dev/null @@ -1,202 +0,0 @@ -import pyblish.api -from avalon import io -from copy import deepcopy - -class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): - """Create entities in Avalon based on collected data.""" - - order = pyblish.api.ExtractorOrder - 0.01 - label = "Extract Hierarchy To Avalon" - families = ["clip", "shot"] - - def process(self, context): - # processing starts here - if "hierarchyContext" not in context.data: - self.log.info("skipping IntegrateHierarchyToAvalon") - return - hierarchy_context = deepcopy(context.data["hierarchyContext"]) - - if not io.Session: - io.install() - - active_assets = [] - # 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"]) - - # remove duplicity in list - self.active_assets = list(set(active_assets)) - self.log.debug("__ self.active_assets: {}".format(self.active_assets)) - - hierarchy_context = self._get_assets(hierarchy_context) - - self.log.debug("__ hierarchy_context: {}".format(hierarchy_context)) - input_data = context.data["hierarchyContext"] = hierarchy_context - - self.project = None - self.import_to_avalon(input_data) - - def import_to_avalon(self, input_data, parent=None): - for name in input_data: - self.log.info("input_data[name]: {}".format(input_data[name])) - entity_data = input_data[name] - entity_type = entity_data["entity_type"] - - data = {} - data["entityType"] = entity_type - - # Custom attributes. - for k, val in entity_data.get("custom_attributes", {}).items(): - data[k] = val - - if entity_type.lower() != "project": - data["inputs"] = entity_data.get("inputs", []) - - # Tasks. - tasks = entity_data.get("tasks", {}) - if tasks is not None or len(tasks) > 0: - data["tasks"] = tasks - parents = [] - visualParent = None - # do not store project"s id as visualParent (silo asset) - if self.project is not None: - if self.project["_id"] != parent["_id"]: - visualParent = parent["_id"] - parents.extend( - parent.get("data", {}).get("parents", []) - ) - parents.append(parent["name"]) - data["visualParent"] = visualParent - data["parents"] = parents - - update_data = True - # Process project - if entity_type.lower() == "project": - entity = io.find_one({"type": "project"}) - # TODO: should be in validator? - assert (entity is not None), "Did not find project in DB" - - # get data from already existing project - cur_entity_data = entity.get("data") or {} - cur_entity_data.update(data) - data = cur_entity_data - - self.project = entity - # Raise error if project or parent are not set - elif self.project is None or parent is None: - raise AssertionError( - "Collected items are not in right order!" - ) - # Else process assset - else: - entity = io.find_one({"type": "asset", "name": name}) - if entity: - # Do not override data, only update - cur_entity_data = entity.get("data") or {} - entity_tasks = cur_entity_data["tasks"] or {} - - # create tasks as dict by default - if not entity_tasks: - cur_entity_data["tasks"] = entity_tasks - - new_tasks = data.pop("tasks", {}) - if "tasks" not in cur_entity_data and not new_tasks: - continue - for task_name in new_tasks: - if task_name in entity_tasks.keys(): - continue - cur_entity_data["tasks"][task_name] = new_tasks[ - task_name] - cur_entity_data.update(data) - data = cur_entity_data - else: - # Skip updating data - update_data = False - - archived_entities = io.find({ - "type": "archived_asset", - "name": name - }) - unarchive_entity = None - for archived_entity in archived_entities: - archived_parents = ( - archived_entity - .get("data", {}) - .get("parents") - ) - if data["parents"] == archived_parents: - unarchive_entity = archived_entity - break - - if unarchive_entity is None: - # Create entity if doesn"t exist - entity = self.create_avalon_asset(name, data) - else: - # Unarchive if entity was archived - entity = self.unarchive_entity(unarchive_entity, data) - - if update_data: - # Update entity data with input data - io.update_many( - {"_id": entity["_id"]}, - {"$set": {"data": data}} - ) - - if "childs" in entity_data: - self.import_to_avalon(entity_data["childs"], entity) - - def unarchive_entity(self, entity, data): - # Unarchived asset should not use same data - new_entity = { - "_id": entity["_id"], - "schema": "avalon-core:asset-3.0", - "name": entity["name"], - "parent": self.project["_id"], - "type": "asset", - "data": data - } - io.replace_one( - {"_id": entity["_id"]}, - new_entity - ) - return new_entity - - def create_avalon_asset(self, name, data): - item = { - "schema": "avalon-core:asset-3.0", - "name": name, - "parent": self.project["_id"], - "type": "asset", - "data": data - } - self.log.debug("Creating asset: {}".format(item)) - entity_id = io.insert_one(item).inserted_id - - return io.find_one({"_id": entity_id}) - - def _get_assets(self, input_dict): - """ Returns only asset dictionary. - Usually the last part of deep dictionary which - is not having any children - """ - input_dict_copy = deepcopy(input_dict) - - for key in input_dict.keys(): - self.log.debug("__ key: {}".format(key)) - # check if child key is available - if input_dict[key].get("childs"): - # loop deeper - input_dict_copy[key]["childs"] = self._get_assets( - input_dict[key]["childs"]) - else: - # filter out unwanted assets - if key not in self.active_assets: - input_dict_copy.pop(key, None) - - return input_dict_copy From 92ae62004d06efd48f87d368a33190cd2ff2f9a9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 7 Apr 2021 11:12:18 +0200 Subject: [PATCH 010/180] Hiero, Global: fixing after transition merge --- openpype/hosts/hiero/plugins/publish/collect_frame_ranges.py | 2 +- openpype/hosts/hiero/startup/Python/Startup/Startup.py | 2 +- openpype/lib/plugin_tools.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/hiero/plugins/publish/collect_frame_ranges.py b/openpype/hosts/hiero/plugins/publish/collect_frame_ranges.py index 39387578d2..21e12e89fa 100644 --- a/openpype/hosts/hiero/plugins/publish/collect_frame_ranges.py +++ b/openpype/hosts/hiero/plugins/publish/collect_frame_ranges.py @@ -5,7 +5,7 @@ class CollectFrameRanges(pyblish.api.InstancePlugin): """ Collect all framranges. """ - order = pyblish.api.CollectorOrder + order = pyblish.api.CollectorOrder - 0.1 label = "Collect Frame Ranges" hosts = ["hiero"] families = ["clip", "effect"] diff --git a/openpype/hosts/hiero/startup/Python/Startup/Startup.py b/openpype/hosts/hiero/startup/Python/Startup/Startup.py index 8de2dc2d11..21c21cd7c3 100644 --- a/openpype/hosts/hiero/startup/Python/Startup/Startup.py +++ b/openpype/hosts/hiero/startup/Python/Startup/Startup.py @@ -6,7 +6,7 @@ import openpype.hosts.hiero.api as phiero avalon.api.install(phiero) try: - __import__("pype.hosts.hiero.api") + __import__("openpype.hosts.hiero.api") __import__("pyblish") except ImportError as e: diff --git a/openpype/lib/plugin_tools.py b/openpype/lib/plugin_tools.py index eb024383d3..5c52088493 100644 --- a/openpype/lib/plugin_tools.py +++ b/openpype/lib/plugin_tools.py @@ -127,7 +127,7 @@ def filter_pyblish_plugins(plugins): plugin_kind = file.split(os.path.sep)[-2:-1][0] # TODO: change after all plugins are moved one level up - if host_from_file == "pype": + if host_from_file == "openpype": host_from_file = "global" try: From 8b157de9c3d1b13458593f1337940f053e89a4f7 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 7 Apr 2021 12:17:36 +0200 Subject: [PATCH 011/180] Hiero: fixing AttributeError when nothing selected or wrong context of selection --- openpype/hosts/hiero/api/lib.py | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/hiero/api/lib.py b/openpype/hosts/hiero/api/lib.py index b74e70cae3..286aba13e9 100644 --- a/openpype/hosts/hiero/api/lib.py +++ b/openpype/hosts/hiero/api/lib.py @@ -150,15 +150,27 @@ def get_track_items( # get selected track items or all in active sequence if selected: - selected_items = list(hiero.selection) - for item in selected_items: - if track_name and track_name in item.parent().name(): - # filter only items fitting input track name - track_items.append(item) - elif not track_name: - # or add all if no track_name was defined - track_items.append(item) - else: + try: + selected_items = list(hiero.selection) + for item in selected_items: + if track_name and track_name in item.parent().name(): + # filter only items fitting input track name + track_items.append(item) + elif not track_name: + # or add all if no track_name was defined + track_items.append(item) + except AttributeError: + pass + + # check if any collected track items are + # `core.Hiero.Python.TrackItem` instance + if track_items: + any_track_item = track_items.pop() + if not isinstance(any_track_item, hiero.core.TrackItem): + selected_items = [] + + # collect all available active sequence track items + if not track_items: sequence = get_current_sequence(name=sequence_name) # get all available tracks from sequence tracks = list(sequence.audioTracks()) + list(sequence.videoTracks()) From f3c24ac0d271816f904b6466e5fb91171177c941 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 7 Apr 2021 13:10:29 +0200 Subject: [PATCH 012/180] Hiero: Creator minimum value to 0 --- openpype/hosts/hiero/api/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/hiero/api/plugin.py b/openpype/hosts/hiero/api/plugin.py index b356c9b6ce..9b3bc25d80 100644 --- a/openpype/hosts/hiero/api/plugin.py +++ b/openpype/hosts/hiero/api/plugin.py @@ -266,7 +266,7 @@ class CreatorWidget(QtWidgets.QDialog): elif v["type"] == "QSpinBox": data[k]["value"] = self.create_row( content_layout, "QSpinBox", v["label"], - setValue=v["value"], setMinimum=1, + setValue=v["value"], setMinimum=0, setMaximum=100000, setToolTip=tool_tip) return data From 6c27152eebc918d7a1824b79a70ea66bbe1849aa Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 8 Apr 2021 10:52:15 +0200 Subject: [PATCH 013/180] Hiero: otio-workflow wip --- openpype/hosts/hiero/otio/__init__.py | 0 openpype/hosts/hiero/otio/hiero_export.py | 386 +++++++++++++ openpype/hosts/hiero/otio/hiero_import.py | 529 ++++++++++++++++++ .../Startup/otioexporter/OTIOExportTask.py | 324 +---------- .../Startup/otioexporter/OTIOExportUI.py | 10 +- .../Python/Startup/otioexporter/__init__.py | 22 - .../Python/StartupUI/otioimporter/__init__.py | 140 ++++- test_localsystem.txt | 1 + 8 files changed, 1045 insertions(+), 367 deletions(-) create mode 100644 openpype/hosts/hiero/otio/__init__.py create mode 100644 openpype/hosts/hiero/otio/hiero_export.py create mode 100644 openpype/hosts/hiero/otio/hiero_import.py create mode 100644 test_localsystem.txt diff --git a/openpype/hosts/hiero/otio/__init__.py b/openpype/hosts/hiero/otio/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openpype/hosts/hiero/otio/hiero_export.py b/openpype/hosts/hiero/otio/hiero_export.py new file mode 100644 index 0000000000..8e19b26741 --- /dev/null +++ b/openpype/hosts/hiero/otio/hiero_export.py @@ -0,0 +1,386 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +__author__ = "Daniel Flehner Heen" +__credits__ = ["Jakub Jezek", "Daniel Flehner Heen"] + +import os +import sys +import re +import hiero.core +import hiero.ui +import opentimelineio as otio + + +# build modul class +self = sys.modules[__name__] + +self.marker_color_map = { + "magenta": otio.schema.MarkerColor.MAGENTA, + "red": otio.schema.MarkerColor.RED, + "yellow": otio.schema.MarkerColor.YELLOW, + "green": otio.schema.MarkerColor.GREEN, + "cyan": otio.schema.MarkerColor.CYAN, + "blue": otio.schema.MarkerColor.BLUE, +} +self.hiero_sequence = None +self.include_tags = None + + +def get_rate(item): + if not hasattr(item, 'framerate'): + item = item.sequence() + + num, den = item.framerate().toRational() + rate = float(num) / float(den) + + if rate.is_integer(): + return rate + + return round(rate, 2) + + +def get_clip_ranges(trackitem): + # Get rate from source or sequence + if trackitem.source().mediaSource().hasVideo(): + rate_item = trackitem.source() + + else: + rate_item = trackitem.sequence() + + source_rate = get_rate(rate_item) + + # Reversed video/audio + if trackitem.playbackSpeed() < 0: + start = trackitem.sourceOut() + + else: + start = trackitem.sourceIn() + + source_start_time = otio.opentime.RationalTime( + start, + source_rate + ) + source_duration = otio.opentime.RationalTime( + trackitem.duration(), + source_rate + ) + + source_range = otio.opentime.TimeRange( + start_time=source_start_time, + duration=source_duration + ) + + hiero_clip = trackitem.source() + + available_range = None + if hiero_clip.mediaSource().isMediaPresent(): + start_time = otio.opentime.RationalTime( + hiero_clip.mediaSource().startTime(), + source_rate + ) + duration = otio.opentime.RationalTime( + hiero_clip.mediaSource().duration(), + source_rate + ) + available_range = otio.opentime.TimeRange( + start_time=start_time, + duration=duration + ) + + return source_range, available_range + + +def add_gap(trackitem, otio_track, prev_out): + gap_length = trackitem.timelineIn() - prev_out + if prev_out != 0: + gap_length -= 1 + + rate = get_rate(trackitem.sequence()) + gap = otio.opentime.TimeRange( + duration=otio.opentime.RationalTime( + gap_length, + rate + ) + ) + otio_gap = otio.schema.Gap(source_range=gap) + otio_track.append(otio_gap) + + +def get_marker_color(tag): + icon = tag.icon() + pat = r'icons:Tag(?P\w+)\.\w+' + + res = re.search(pat, icon) + if res: + color = res.groupdict().get('color') + if color.lower() in self.marker_color_map: + return self.marker_color_map[color.lower()] + + return otio.schema.MarkerColor.RED + + +def add_markers(hiero_item, otio_item): + for tag in hiero_item.tags(): + if not tag.visible(): + continue + + if tag.name() == 'Copy': + # Hiero adds this tag to a lot of clips + continue + + frame_rate = get_rate(hiero_item) + + marked_range = otio.opentime.TimeRange( + start_time=otio.opentime.RationalTime( + tag.inTime(), + frame_rate + ), + duration=otio.opentime.RationalTime( + int(tag.metadata().dict().get('tag.length', '0')), + frame_rate + ) + ) + + metadata = dict( + Hiero=tag.metadata().dict() + ) + # Store the source item for future import assignment + metadata['Hiero']['source_type'] = hiero_item.__class__.__name__ + + marker = otio.schema.Marker( + name=tag.name(), + color=get_marker_color(tag), + marked_range=marked_range, + metadata=metadata + ) + + otio_item.markers.append(marker) + + +def add_clip(trackitem, otio_track, itemindex): + hiero_clip = trackitem.source() + + # Add Gap if needed + if itemindex == 0: + prev_item = trackitem + + else: + prev_item = trackitem.parent().items()[itemindex - 1] + + clip_diff = trackitem.timelineIn() - prev_item.timelineOut() + + if itemindex == 0 and trackitem.timelineIn() > 0: + add_gap(trackitem, otio_track, 0) + + elif itemindex and clip_diff != 1: + add_gap(trackitem, otio_track, prev_item.timelineOut()) + + # Create Clip + source_range, available_range = get_clip_ranges(trackitem) + + otio_clip = otio.schema.Clip( + name=trackitem.name(), + source_range=source_range + ) + + media_reference = create_otio_reference(hiero_clip) + + otio_clip.media_reference = media_reference + + # Add Time Effects + playbackspeed = trackitem.playbackSpeed() + if playbackspeed != 1: + if playbackspeed == 0: + time_effect = otio.schema.FreezeFrame() + + else: + time_effect = otio.schema.LinearTimeWarp( + time_scalar=playbackspeed + ) + otio_clip.effects.append(time_effect) + + # Add tags as markers + if self.include_tags: + add_markers(trackitem, otio_clip) + add_markers(trackitem.source(), otio_clip) + + otio_track.append(otio_clip) + + # Add Transition if needed + if trackitem.inTransition() or trackitem.outTransition(): + add_transition(trackitem, otio_track) + +def _get_metadata(hiero_object): + metadata = hiero_object.metadata() + return {key: value for key, value in metadata.items()} + +def create_otio_reference(hiero_clip): + metadata = _get_metadata(hiero_clip) + mp_clip_property = media_pool_item.GetClipProperty() + path = mp_clip_property["File Path"] + reformat_path = utils.get_reformated_path(path, padded=True) + padding = utils.get_padding_from_path(path) + + if padding: + metadata.update({ + "isSequence": True, + "padding": padding + }) + + # get clip property regarding to type + mp_clip_property = media_pool_item.GetClipProperty() + fps = float(mp_clip_property["FPS"]) + if mp_clip_property["Type"] == "Video": + frame_start = int(mp_clip_property["Start"]) + frame_duration = int(mp_clip_property["Frames"]) + else: + audio_duration = str(mp_clip_property["Duration"]) + frame_start = 0 + frame_duration = int(utils.timecode_to_frames( + audio_duration, float(fps))) + + otio_ex_ref_item = None + + if padding: + # if it is file sequence try to create `ImageSequenceReference` + # the OTIO might not be compatible so return nothing and do it old way + try: + dirname, filename = os.path.split(path) + collection = clique.parse(filename, '{head}[{ranges}]{tail}') + padding_num = len(re.findall("(\\d+)(?=-)", filename).pop()) + otio_ex_ref_item = otio.schema.ImageSequenceReference( + target_url_base=dirname + os.sep, + name_prefix=collection.format("{head}"), + name_suffix=collection.format("{tail}"), + start_frame=frame_start, + frame_zero_padding=padding_num, + rate=fps, + available_range=create_otio_time_range( + frame_start, + frame_duration, + fps + ) + ) + except AttributeError: + pass + + if not otio_ex_ref_item: + # in case old OTIO or video file create `ExternalReference` + otio_ex_ref_item = otio.schema.ExternalReference( + target_url=reformat_path, + available_range=create_otio_time_range( + frame_start, + frame_duration, + fps + ) + ) + + # add metadata to otio item + add_otio_metadata(otio_ex_ref_item, hiero_clip, **metadata) + + return otio_ex_ref_item + + +def add_otio_metadata(otio_item, hiero_clip, **kwargs): + mp_metadata = hiero_clip.GetMetadata() + # add additional metadata from kwargs + if kwargs: + mp_metadata.update(kwargs) + + # add metadata to otio item metadata + for key, value in mp_metadata.items(): + otio_item.metadata.update({key: value}) + +def add_transition(trackitem, otio_track): + transitions = [] + + if trackitem.inTransition(): + if trackitem.inTransition().alignment().name == 'kFadeIn': + transitions.append(trackitem.inTransition()) + + if trackitem.outTransition(): + transitions.append(trackitem.outTransition()) + + for transition in transitions: + alignment = transition.alignment().name + + if alignment == 'kFadeIn': + in_offset_frames = 0 + out_offset_frames = ( + transition.timelineOut() - transition.timelineIn() + ) + 1 + + elif alignment == 'kFadeOut': + in_offset_frames = ( + trackitem.timelineOut() - transition.timelineIn() + ) + 1 + out_offset_frames = 0 + + elif alignment == 'kDissolve': + in_offset_frames = ( + transition.inTrackItem().timelineOut() - + transition.timelineIn() + ) + out_offset_frames = ( + transition.timelineOut() - + transition.outTrackItem().timelineIn() + ) + + else: + # kUnknown transition is ignored + continue + + rate = trackitem.source().framerate().toFloat() + in_time = otio.opentime.RationalTime(in_offset_frames, rate) + out_time = otio.opentime.RationalTime(out_offset_frames, rate) + + otio_transition = otio.schema.Transition( + name=alignment, # Consider placing Hiero name in metadata + transition_type=otio.schema.TransitionTypes.SMPTE_Dissolve, + in_offset=in_time, + out_offset=out_time + ) + + if alignment == 'kFadeIn': + otio_track.insert(-1, otio_transition) + + else: + otio_track.append(otio_transition) + + +def add_tracks(): + for track in self.hiero_sequence.items(): + if isinstance(track, hiero.core.AudioTrack): + kind = otio.schema.TrackKind.Audio + + else: + kind = otio.schema.TrackKind.Video + + otio_track = otio.schema.Track(name=track.name(), kind=kind) + + for itemindex, trackitem in enumerate(track): + if isinstance(trackitem.source(), hiero.core.Clip): + add_clip(trackitem, otio_track, itemindex) + + self.otio_timeline.tracks.append(otio_track) + + # Add tags as markers + if self.include_tags: + add_markers(self.hiero_sequence, self.otio_timeline.tracks) + + +def create_OTIO(sequence=None): + self.hiero_sequence = sequence or hiero.ui.activeSequence() + self.otio_timeline = otio.schema.Timeline() + + # Set global start time based on sequence + self.otio_timeline.global_start_time = otio.opentime.RationalTime( + self.hiero_sequence.timecodeStart(), + self.hiero_sequence.framerate().toFloat() + ) + self.otio_timeline.name = self.hiero_sequence.name() + + add_tracks() + + return self.otio_timeline diff --git a/openpype/hosts/hiero/otio/hiero_import.py b/openpype/hosts/hiero/otio/hiero_import.py new file mode 100644 index 0000000000..c5c72984bc --- /dev/null +++ b/openpype/hosts/hiero/otio/hiero_import.py @@ -0,0 +1,529 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +__author__ = "Daniel Flehner Heen" +__credits__ = ["Jakub Jezek", "Daniel Flehner Heen"] + + +import os +import hiero.core +import hiero.ui + +import PySide2.QtWidgets as qw + +try: + from urllib import unquote + +except ImportError: + from urllib.parse import unquote # lint:ok + +import opentimelineio as otio + + +def inform(messages): + if isinstance(messages, type('')): + messages = [messages] + + qw.QMessageBox.information( + hiero.ui.mainWindow(), + 'OTIO Import', + '\n'.join(messages), + qw.QMessageBox.StandardButton.Ok + ) + + +def get_transition_type(otio_item, otio_track): + _in, _out = otio_track.neighbors_of(otio_item) + + if isinstance(_in, otio.schema.Gap): + _in = None + + if isinstance(_out, otio.schema.Gap): + _out = None + + if _in and _out: + return 'dissolve' + + elif _in and not _out: + return 'fade_out' + + elif not _in and _out: + return 'fade_in' + + else: + return 'unknown' + + +def find_trackitem(otio_clip, hiero_track): + for item in hiero_track.items(): + if item.timelineIn() == otio_clip.range_in_parent().start_time.value: + if item.name() == otio_clip.name: + return item + + return None + + +def get_neighboring_trackitems(otio_item, otio_track, hiero_track): + _in, _out = otio_track.neighbors_of(otio_item) + trackitem_in = None + trackitem_out = None + + if _in: + trackitem_in = find_trackitem(_in, hiero_track) + + if _out: + trackitem_out = find_trackitem(_out, hiero_track) + + return trackitem_in, trackitem_out + + +def apply_transition(otio_track, otio_item, track): + warning = None + + # Figure out type of transition + transition_type = get_transition_type(otio_item, otio_track) + + # Figure out track kind for getattr below + kind = '' + if isinstance(track, hiero.core.AudioTrack): + kind = 'Audio' + + # Gather TrackItems involved in trasition + item_in, item_out = get_neighboring_trackitems( + otio_item, + otio_track, + track + ) + + # Create transition object + if transition_type == 'dissolve': + transition_func = getattr( + hiero.core.Transition, + 'create{kind}DissolveTransition'.format(kind=kind) + ) + + try: + transition = transition_func( + item_in, + item_out, + otio_item.in_offset.value, + otio_item.out_offset.value + ) + + # Catch error raised if transition is bigger than TrackItem source + except RuntimeError as e: + transition = None + warning = \ + 'Unable to apply transition "{t.name}": {e} ' \ + 'Ignoring the transition.' \ + .format(t=otio_item, e=e.message) + + elif transition_type == 'fade_in': + transition_func = getattr( + hiero.core.Transition, + 'create{kind}FadeInTransition'.format(kind=kind) + ) + + # Warn user if part of fade is outside of clip + if otio_item.in_offset.value: + warning = \ + 'Fist half of transition "{t.name}" is outside of clip and ' \ + 'not valid in Hiero. Only applied second half.' \ + .format(t=otio_item) + + transition = transition_func( + item_out, + otio_item.out_offset.value + ) + + elif transition_type == 'fade_out': + transition_func = getattr( + hiero.core.Transition, + 'create{kind}FadeOutTransition'.format(kind=kind) + ) + transition = transition_func( + item_in, + otio_item.in_offset.value + ) + + # Warn user if part of fade is outside of clip + if otio_item.out_offset.value: + warning = \ + 'Second half of transition "{t.name}" is outside of clip ' \ + 'and not valid in Hiero. Only applied first half.' \ + .format(t=otio_item) + + else: + # Unknown transition + return + + # Apply transition to track + if transition: + track.addTransition(transition) + + # Inform user about missing or adjusted transitions + return warning + + +def prep_url(url_in): + url = unquote(url_in) + + if url.startswith('file://localhost/'): + return url + + url = 'file://localhost{sep}{url}'.format( + sep=url.startswith(os.sep) and '' or os.sep, + url=url.startswith(os.sep) and url[1:] or url + ) + + return url + + +def create_offline_mediasource(otio_clip, path=None): + hiero_rate = hiero.core.TimeBase( + otio_clip.source_range.start_time.rate + ) + + legal_media_refs = ( + otio.schema.ExternalReference, + otio.schema.ImageSequenceReference + ) + if isinstance(otio_clip.media_reference, legal_media_refs): + source_range = otio_clip.available_range() + + else: + source_range = otio_clip.source_range + + if path is None: + path = otio_clip.name + + media = hiero.core.MediaSource.createOfflineVideoMediaSource( + prep_url(path), + source_range.start_time.value, + source_range.duration.value, + hiero_rate, + source_range.start_time.value + ) + + return media + + +def load_otio(otio_file, project=None, sequence=None): + otio_timeline = otio.adapters.read_from_file(otio_file) + build_sequence(otio_timeline, project=project, sequence=sequence) + + +marker_color_map = { + "PINK": "Magenta", + "RED": "Red", + "ORANGE": "Yellow", + "YELLOW": "Yellow", + "GREEN": "Green", + "CYAN": "Cyan", + "BLUE": "Blue", + "PURPLE": "Magenta", + "MAGENTA": "Magenta", + "BLACK": "Blue", + "WHITE": "Green" +} + + +def get_tag(tagname, tagsbin): + for tag in tagsbin.items(): + if tag.name() == tagname: + return tag + + if isinstance(tag, hiero.core.Bin): + tag = get_tag(tagname, tag) + + if tag is not None: + return tag + + return None + + +def add_metadata(metadata, hiero_item): + for key, value in metadata.get('Hiero', dict()).items(): + if key == 'source_type': + # Only used internally to reassign tag to correct Hiero item + continue + + if isinstance(value, dict): + add_metadata(value, hiero_item) + continue + + if value is not None: + if not key.startswith('tag.'): + key = 'tag.' + key + + hiero_item.metadata().setValue(key, str(value)) + + +def add_markers(otio_item, hiero_item, tagsbin): + if isinstance(otio_item, (otio.schema.Stack, otio.schema.Clip)): + markers = otio_item.markers + + elif isinstance(otio_item, otio.schema.Timeline): + markers = otio_item.tracks.markers + + else: + markers = [] + + for marker in markers: + meta = marker.metadata.get('Hiero', dict()) + if 'source_type' in meta: + if hiero_item.__class__.__name__ != meta.get('source_type'): + continue + + marker_color = marker.color + + _tag = get_tag(marker.name, tagsbin) + if _tag is None: + _tag = get_tag(marker_color_map[marker_color], tagsbin) + + if _tag is None: + _tag = hiero.core.Tag(marker_color_map[marker.color]) + + start = marker.marked_range.start_time.value + end = ( + marker.marked_range.start_time.value + + marker.marked_range.duration.value + ) + + if hasattr(hiero_item, 'addTagToRange'): + tag = hiero_item.addTagToRange(_tag, start, end) + + else: + tag = hiero_item.addTag(_tag) + + tag.setName(marker.name or marker_color_map[marker_color]) + # tag.setNote(meta.get('tag.note', '')) + + # Add metadata + add_metadata(marker.metadata, tag) + + +def create_track(otio_track, tracknum, track_kind): + if track_kind is None and hasattr(otio_track, 'kind'): + track_kind = otio_track.kind + + # Create a Track + if track_kind == otio.schema.TrackKind.Video: + track = hiero.core.VideoTrack( + otio_track.name or 'Video{n}'.format(n=tracknum) + ) + + else: + track = hiero.core.AudioTrack( + otio_track.name or 'Audio{n}'.format(n=tracknum) + ) + + return track + + +def create_clip(otio_clip, tagsbin, sequencebin): + # Create MediaSource + url = None + media = None + otio_media = otio_clip.media_reference + + if isinstance(otio_media, otio.schema.ExternalReference): + url = prep_url(otio_media.target_url) + media = hiero.core.MediaSource(url) + + elif isinstance(otio_media, otio.schema.ImageSequenceReference): + url = prep_url(otio_media.abstract_target_url('#')) + media = hiero.core.MediaSource(url) + + if media is None or media.isOffline(): + media = create_offline_mediasource(otio_clip, url) + + # Reuse previous clip if possible + clip = None + for item in sequencebin.clips(): + if item.activeItem().mediaSource() == media: + clip = item.activeItem() + break + + if not clip: + # Create new Clip + clip = hiero.core.Clip(media) + + # Add Clip to a Bin + sequencebin.addItem(hiero.core.BinItem(clip)) + + # Add markers + add_markers(otio_clip, clip, tagsbin) + + return clip + + +def create_trackitem(playhead, track, otio_clip, clip): + source_range = otio_clip.source_range + + trackitem = track.createTrackItem(otio_clip.name) + trackitem.setPlaybackSpeed(source_range.start_time.rate) + trackitem.setSource(clip) + + time_scalar = 1. + + # Check for speed effects and adjust playback speed accordingly + for effect in otio_clip.effects: + if isinstance(effect, otio.schema.LinearTimeWarp): + time_scalar = effect.time_scalar + # Only reverse effect can be applied here + if abs(time_scalar) == 1.: + trackitem.setPlaybackSpeed(trackitem.playbackSpeed() * time_scalar) + + elif isinstance(effect, otio.schema.FreezeFrame): + # For freeze frame, playback speed must be set after range + time_scalar = 0. + + # If reverse playback speed swap source in and out + if trackitem.playbackSpeed() < 0: + source_out = source_range.start_time.value + source_in = source_range.end_time_inclusive().value + + timeline_in = playhead + source_out + timeline_out = ( + timeline_in + + source_range.duration.value + ) - 1 + else: + # Normal playback speed + source_in = source_range.start_time.value + source_out = source_range.end_time_inclusive().value + + timeline_in = playhead + timeline_out = ( + timeline_in + + source_range.duration.value + ) - 1 + + # Set source and timeline in/out points + trackitem.setTimes( + timeline_in, + timeline_out, + source_in, + source_out + + ) + + # Apply playback speed for freeze frames + if abs(time_scalar) != 1.: + trackitem.setPlaybackSpeed(trackitem.playbackSpeed() * time_scalar) + + # Link audio to video when possible + if isinstance(track, hiero.core.AudioTrack): + for other in track.parent().trackItemsAt(playhead): + if other.source() == clip: + trackitem.link(other) + + return trackitem + + +def build_sequence(otio_timeline, project=None, sequence=None, track_kind=None): + if project is None: + if sequence: + project = sequence.project() + + else: + # Per version 12.1v2 there is no way of getting active project + project = hiero.core.projects(hiero.core.Project.kUserProjects)[-1] + + projectbin = project.clipsBin() + + if not sequence: + # Create a Sequence + sequence = hiero.core.Sequence(otio_timeline.name or 'OTIOSequence') + + # Set sequence settings from otio timeline if available + if hasattr(otio_timeline, 'global_start_time'): + if otio_timeline.global_start_time: + start_time = otio_timeline.global_start_time + sequence.setFramerate(start_time.rate) + sequence.setTimecodeStart(start_time.value) + + # Create a Bin to hold clips + projectbin.addItem(hiero.core.BinItem(sequence)) + + sequencebin = hiero.core.Bin(sequence.name()) + projectbin.addItem(sequencebin) + + else: + sequencebin = projectbin + + # Get tagsBin + tagsbin = hiero.core.project("Tag Presets").tagsBin() + + # Add timeline markers + add_markers(otio_timeline, sequence, tagsbin) + + if isinstance(otio_timeline, otio.schema.Timeline): + tracks = otio_timeline.tracks + + else: + tracks = [otio_timeline] + + for tracknum, otio_track in enumerate(tracks): + playhead = 0 + _transitions = [] + + # Add track to sequence + track = create_track(otio_track, tracknum, track_kind) + sequence.addTrack(track) + + # iterate over items in track + for itemnum, otio_clip in enumerate(otio_track): + if isinstance(otio_clip, (otio.schema.Track, otio.schema.Stack)): + inform('Nested sequences/tracks are created separately.') + + # Add gap where the nested sequence would have been + playhead += otio_clip.source_range.duration.value + + # Process nested sequence + build_sequence( + otio_clip, + project=project, + track_kind=otio_track.kind + ) + + elif isinstance(otio_clip, otio.schema.Clip): + # Create a Clip + clip = create_clip(otio_clip, tagsbin, sequencebin) + + # Create TrackItem + trackitem = create_trackitem( + playhead, + track, + otio_clip, + clip + ) + + # Add markers + add_markers(otio_clip, trackitem, tagsbin) + + # Add trackitem to track + track.addTrackItem(trackitem) + + # Update playhead + playhead = trackitem.timelineOut() + 1 + + elif isinstance(otio_clip, otio.schema.Transition): + # Store transitions for when all clips in the track are created + _transitions.append((otio_track, otio_clip)) + + elif isinstance(otio_clip, otio.schema.Gap): + # Hiero has no fillers, slugs or blanks at the moment + playhead += otio_clip.source_range.duration.value + + # Apply transitions we stored earlier now that all clips are present + warnings = list() + for otio_track, otio_item in _transitions: + # Catch warnings form transitions in case of unsupported transitions + warning = apply_transition(otio_track, otio_item, track) + if warning: + warnings.append(warning) + + if warnings: + inform(warnings) diff --git a/openpype/hosts/hiero/startup/Python/Startup/otioexporter/OTIOExportTask.py b/openpype/hosts/hiero/startup/Python/Startup/otioexporter/OTIOExportTask.py index 90504ccd18..734213a05d 100644 --- a/openpype/hosts/hiero/startup/Python/Startup/otioexporter/OTIOExportTask.py +++ b/openpype/hosts/hiero/startup/Python/Startup/otioexporter/OTIOExportTask.py @@ -1,42 +1,15 @@ -# MIT License -# -# Copyright (c) 2018 Daniel Flehner Heen (Storm Studios) -# -# 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. +#!/usr/bin/env python +# -*- coding: utf-8 -*- +__author__ = "Daniel Flehner Heen" +__credits__ = ["Jakub Jezek", "Daniel Flehner Heen"] import os -import re import hiero.core from hiero.core import util import opentimelineio as otio - - -marker_color_map = { - "magenta": otio.schema.MarkerColor.MAGENTA, - "red": otio.schema.MarkerColor.RED, - "yellow": otio.schema.MarkerColor.YELLOW, - "green": otio.schema.MarkerColor.GREEN, - "cyan": otio.schema.MarkerColor.CYAN, - "blue": otio.schema.MarkerColor.BLUE, -} +from pype.hosts.hiero.otio.hiero_export import create_OTIO class OTIOExportTask(hiero.core.TaskBase): @@ -44,295 +17,14 @@ class OTIOExportTask(hiero.core.TaskBase): def __init__(self, initDict): """Initialize""" hiero.core.TaskBase.__init__(self, initDict) + self.otio_timeline = None def name(self): return str(type(self)) - def get_rate(self, item): - if not hasattr(item, 'framerate'): - item = item.sequence() - - num, den = item.framerate().toRational() - rate = float(num) / float(den) - - if rate.is_integer(): - return rate - - return round(rate, 2) - - def get_clip_ranges(self, trackitem): - # Get rate from source or sequence - if trackitem.source().mediaSource().hasVideo(): - rate_item = trackitem.source() - - else: - rate_item = trackitem.sequence() - - source_rate = self.get_rate(rate_item) - - # Reversed video/audio - if trackitem.playbackSpeed() < 0: - start = trackitem.sourceOut() - - else: - start = trackitem.sourceIn() - - source_start_time = otio.opentime.RationalTime( - start, - source_rate - ) - source_duration = otio.opentime.RationalTime( - trackitem.duration(), - source_rate - ) - - source_range = otio.opentime.TimeRange( - start_time=source_start_time, - duration=source_duration - ) - - hiero_clip = trackitem.source() - - available_range = None - if hiero_clip.mediaSource().isMediaPresent(): - start_time = otio.opentime.RationalTime( - hiero_clip.mediaSource().startTime(), - source_rate - ) - duration = otio.opentime.RationalTime( - hiero_clip.mediaSource().duration(), - source_rate - ) - available_range = otio.opentime.TimeRange( - start_time=start_time, - duration=duration - ) - - return source_range, available_range - - def add_gap(self, trackitem, otio_track, prev_out): - gap_length = trackitem.timelineIn() - prev_out - if prev_out != 0: - gap_length -= 1 - - rate = self.get_rate(trackitem.sequence()) - gap = otio.opentime.TimeRange( - duration=otio.opentime.RationalTime( - gap_length, - rate - ) - ) - otio_gap = otio.schema.Gap(source_range=gap) - otio_track.append(otio_gap) - - def get_marker_color(self, tag): - icon = tag.icon() - pat = r'icons:Tag(?P\w+)\.\w+' - - res = re.search(pat, icon) - if res: - color = res.groupdict().get('color') - if color.lower() in marker_color_map: - return marker_color_map[color.lower()] - - return otio.schema.MarkerColor.RED - - def add_markers(self, hiero_item, otio_item): - for tag in hiero_item.tags(): - if not tag.visible(): - continue - - if tag.name() == 'Copy': - # Hiero adds this tag to a lot of clips - continue - - frame_rate = self.get_rate(hiero_item) - - marked_range = otio.opentime.TimeRange( - start_time=otio.opentime.RationalTime( - tag.inTime(), - frame_rate - ), - duration=otio.opentime.RationalTime( - int(tag.metadata().dict().get('tag.length', '0')), - frame_rate - ) - ) - - metadata = dict( - Hiero=tag.metadata().dict() - ) - # Store the source item for future import assignment - metadata['Hiero']['source_type'] = hiero_item.__class__.__name__ - - marker = otio.schema.Marker( - name=tag.name(), - color=self.get_marker_color(tag), - marked_range=marked_range, - metadata=metadata - ) - - otio_item.markers.append(marker) - - def add_clip(self, trackitem, otio_track, itemindex): - hiero_clip = trackitem.source() - - # Add Gap if needed - if itemindex == 0: - prev_item = trackitem - - else: - prev_item = trackitem.parent().items()[itemindex - 1] - - clip_diff = trackitem.timelineIn() - prev_item.timelineOut() - - if itemindex == 0 and trackitem.timelineIn() > 0: - self.add_gap(trackitem, otio_track, 0) - - elif itemindex and clip_diff != 1: - self.add_gap(trackitem, otio_track, prev_item.timelineOut()) - - # Create Clip - source_range, available_range = self.get_clip_ranges(trackitem) - - otio_clip = otio.schema.Clip( - name=trackitem.name(), - source_range=source_range - ) - - # Add media reference - media_reference = otio.schema.MissingReference() - if hiero_clip.mediaSource().isMediaPresent(): - source = hiero_clip.mediaSource() - first_file = source.fileinfos()[0] - path = first_file.filename() - - if "%" in path: - path = re.sub(r"%\d+d", "%d", path) - if "#" in path: - path = re.sub(r"#+", "%d", path) - - media_reference = otio.schema.ExternalReference( - target_url=u'{}'.format(path), - available_range=available_range - ) - - otio_clip.media_reference = media_reference - - # Add Time Effects - playbackspeed = trackitem.playbackSpeed() - if playbackspeed != 1: - if playbackspeed == 0: - time_effect = otio.schema.FreezeFrame() - - else: - time_effect = otio.schema.LinearTimeWarp( - time_scalar=playbackspeed - ) - otio_clip.effects.append(time_effect) - - # Add tags as markers - if self._preset.properties()["includeTags"]: - self.add_markers(trackitem, otio_clip) - self.add_markers(trackitem.source(), otio_clip) - - otio_track.append(otio_clip) - - # Add Transition if needed - if trackitem.inTransition() or trackitem.outTransition(): - self.add_transition(trackitem, otio_track) - - def add_transition(self, trackitem, otio_track): - transitions = [] - - if trackitem.inTransition(): - if trackitem.inTransition().alignment().name == 'kFadeIn': - transitions.append(trackitem.inTransition()) - - if trackitem.outTransition(): - transitions.append(trackitem.outTransition()) - - for transition in transitions: - alignment = transition.alignment().name - - if alignment == 'kFadeIn': - in_offset_frames = 0 - out_offset_frames = ( - transition.timelineOut() - transition.timelineIn() - ) + 1 - - elif alignment == 'kFadeOut': - in_offset_frames = ( - trackitem.timelineOut() - transition.timelineIn() - ) + 1 - out_offset_frames = 0 - - elif alignment == 'kDissolve': - in_offset_frames = ( - transition.inTrackItem().timelineOut() - - transition.timelineIn() - ) - out_offset_frames = ( - transition.timelineOut() - - transition.outTrackItem().timelineIn() - ) - - else: - # kUnknown transition is ignored - continue - - rate = trackitem.source().framerate().toFloat() - in_time = otio.opentime.RationalTime(in_offset_frames, rate) - out_time = otio.opentime.RationalTime(out_offset_frames, rate) - - otio_transition = otio.schema.Transition( - name=alignment, # Consider placing Hiero name in metadata - transition_type=otio.schema.TransitionTypes.SMPTE_Dissolve, - in_offset=in_time, - out_offset=out_time - ) - - if alignment == 'kFadeIn': - otio_track.insert(-1, otio_transition) - - else: - otio_track.append(otio_transition) - - - def add_tracks(self): - for track in self._sequence.items(): - if isinstance(track, hiero.core.AudioTrack): - kind = otio.schema.TrackKind.Audio - - else: - kind = otio.schema.TrackKind.Video - - otio_track = otio.schema.Track(name=track.name(), kind=kind) - - for itemindex, trackitem in enumerate(track): - if isinstance(trackitem.source(), hiero.core.Clip): - self.add_clip(trackitem, otio_track, itemindex) - - self.otio_timeline.tracks.append(otio_track) - - # Add tags as markers - if self._preset.properties()["includeTags"]: - self.add_markers(self._sequence, self.otio_timeline.tracks) - - def create_OTIO(self): - self.otio_timeline = otio.schema.Timeline() - - # Set global start time based on sequence - self.otio_timeline.global_start_time = otio.opentime.RationalTime( - self._sequence.timecodeStart(), - self._sequence.framerate().toFloat() - ) - self.otio_timeline.name = self._sequence.name() - - self.add_tracks() - def startTask(self): - self.create_OTIO() + self.otio_timeline = create_OTIO( + self._sequence) def taskStep(self): return False diff --git a/openpype/hosts/hiero/startup/Python/Startup/otioexporter/OTIOExportUI.py b/openpype/hosts/hiero/startup/Python/Startup/otioexporter/OTIOExportUI.py index 887ff05ec8..7f11de074d 100644 --- a/openpype/hosts/hiero/startup/Python/Startup/otioexporter/OTIOExportUI.py +++ b/openpype/hosts/hiero/startup/Python/Startup/otioexporter/OTIOExportUI.py @@ -1,3 +1,9 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +__author__ = "Daniel Flehner Heen" +__credits__ = ["Jakub Jezek", "Daniel Flehner Heen"] + import hiero.ui import OTIOExportTask @@ -14,6 +20,8 @@ except ImportError: FormLayout = QFormLayout # lint:ok +from pype.hosts.hiero.otio import hiero_export + class OTIOExportUI(hiero.ui.TaskUIBase): def __init__(self, preset): @@ -27,7 +35,7 @@ class OTIOExportUI(hiero.ui.TaskUIBase): def includeMarkersCheckboxChanged(self, state): # Slot to handle change of checkbox state - self._preset.properties()["includeTags"] = state == QtCore.Qt.Checked + hiero_export.hiero_sequence = state == QtCore.Qt.Checked def populateUI(self, widget, exportTemplate): layout = widget.layout() diff --git a/openpype/hosts/hiero/startup/Python/Startup/otioexporter/__init__.py b/openpype/hosts/hiero/startup/Python/Startup/otioexporter/__init__.py index 67e6e78d35..3c09655f01 100644 --- a/openpype/hosts/hiero/startup/Python/Startup/otioexporter/__init__.py +++ b/openpype/hosts/hiero/startup/Python/Startup/otioexporter/__init__.py @@ -1,25 +1,3 @@ -# MIT License -# -# Copyright (c) 2018 Daniel Flehner Heen (Storm Studios) -# -# 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. - from OTIOExportTask import OTIOExportTask from OTIOExportUI import OTIOExportUI diff --git a/openpype/hosts/hiero/startup/Python/StartupUI/otioimporter/__init__.py b/openpype/hosts/hiero/startup/Python/StartupUI/otioimporter/__init__.py index 1503a9e9ac..a778d558b2 100644 --- a/openpype/hosts/hiero/startup/Python/StartupUI/otioimporter/__init__.py +++ b/openpype/hosts/hiero/startup/Python/StartupUI/otioimporter/__init__.py @@ -1,42 +1,91 @@ -# MIT License -# -# Copyright (c) 2018 Daniel Flehner Heen (Storm Studios) -# -# 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. +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +__author__ = "Daniel Flehner Heen" +__credits__ = ["Jakub Jezek", "Daniel Flehner Heen"] import hiero.ui import hiero.core -from otioimporter.OTIOImport import load_otio +import PySide2.QtWidgets as qw + +from pype.hosts.hiero.otio.hiero_import import load_otio + + +class OTIOProjectSelect(qw.QDialog): + + def __init__(self, projects, *args, **kwargs): + super(OTIOProjectSelect, self).__init__(*args, **kwargs) + self.setWindowTitle('Please select active project') + self.layout = qw.QVBoxLayout() + + self.label = qw.QLabel( + 'Unable to determine which project to import sequence to.\n' + 'Please select one.' + ) + self.layout.addWidget(self.label) + + self.projects = qw.QComboBox() + self.projects.addItems(map(lambda p: p.name(), projects)) + self.layout.addWidget(self.projects) + + QBtn = qw.QDialogButtonBox.Ok | qw.QDialogButtonBox.Cancel + self.buttonBox = qw.QDialogButtonBox(QBtn) + self.buttonBox.accepted.connect(self.accept) + self.buttonBox.rejected.connect(self.reject) + + self.layout.addWidget(self.buttonBox) + self.setLayout(self.layout) + + +def get_sequence(view): + sequence = None + if isinstance(view, hiero.ui.TimelineEditor): + sequence = view.sequence() + + elif isinstance(view, hiero.ui.BinView): + for item in view.selection(): + if not hasattr(item, 'acitveItem'): + continue + + if isinstance(item.activeItem(), hiero.core.Sequence): + sequence = item.activeItem() + + return sequence def OTIO_menu_action(event): - otio_action = hiero.ui.createMenuAction( - 'Import OTIO', + # Menu actions + otio_import_action = hiero.ui.createMenuAction( + 'Import OTIO...', open_otio_file, icon=None ) - hiero.ui.registerAction(otio_action) + + otio_add_track_action = hiero.ui.createMenuAction( + 'New Track(s) from OTIO...', + open_otio_file, + icon=None + ) + otio_add_track_action.setEnabled(False) + + hiero.ui.registerAction(otio_import_action) + hiero.ui.registerAction(otio_add_track_action) + + view = hiero.ui.currentContextMenuView() + + if view: + sequence = get_sequence(view) + if sequence: + otio_add_track_action.setEnabled(True) + for action in event.menu.actions(): if action.text() == 'Import': - action.menu().addAction(otio_action) - break + action.menu().addAction(otio_import_action) + action.menu().addAction(otio_add_track_action) + + elif action.text() == 'New Track': + action.menu().addAction(otio_add_track_action) def open_otio_file(): @@ -45,8 +94,39 @@ def open_otio_file(): pattern='*.otio', requiredExtension='.otio' ) + + selection = None + sequence = None + + view = hiero.ui.currentContextMenuView() + if view: + sequence = get_sequence(view) + selection = view.selection() + + if sequence: + project = sequence.project() + + elif selection: + project = selection[0].project() + + elif len(hiero.core.projects()) > 1: + dialog = OTIOProjectSelect(hiero.core.projects()) + if dialog.exec_(): + project = hiero.core.projects()[dialog.projects.currentIndex()] + + else: + bar = hiero.ui.mainWindow().statusBar() + bar.showMessage( + 'OTIO Import aborted by user', + timeout=3000 + ) + return + + else: + project = hiero.core.projects()[-1] + for otio_file in files: - load_otio(otio_file) + load_otio(otio_file, project, sequence) # HieroPlayer is quite limited and can't create transitions etc. @@ -55,3 +135,7 @@ if not hiero.core.isHieroPlayer(): "kShowContextMenu/kBin", OTIO_menu_action ) + hiero.core.events.registerInterest( + "kShowContextMenu/kTimeline", + OTIO_menu_action + ) diff --git a/test_localsystem.txt b/test_localsystem.txt new file mode 100644 index 0000000000..dde7986af8 --- /dev/null +++ b/test_localsystem.txt @@ -0,0 +1 @@ +I have run From 2890a5fcbc31207bc86ee0d7245460b6de3c5e58 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 8 Apr 2021 16:30:13 +0200 Subject: [PATCH 014/180] Hiero: otio publishing workflow wip --- openpype/hosts/hiero/otio/hiero_export_.py | 312 +++++++++++++++++++++ openpype/hosts/hiero/otio/utils.py | 83 ++++++ 2 files changed, 395 insertions(+) create mode 100644 openpype/hosts/hiero/otio/hiero_export_.py create mode 100644 openpype/hosts/hiero/otio/utils.py diff --git a/openpype/hosts/hiero/otio/hiero_export_.py b/openpype/hosts/hiero/otio/hiero_export_.py new file mode 100644 index 0000000000..b5a96a9414 --- /dev/null +++ b/openpype/hosts/hiero/otio/hiero_export_.py @@ -0,0 +1,312 @@ +""" compatibility OpenTimelineIO 0.12.0 and older +""" + +import os +import re +import sys +import opentimelineio as otio +from . import utils +import hiero.core +import hiero.ui + +self = sys.modules[__name__] +self.track_types = { + hiero.core.VideoTrack: otio.schema.TrackKind.Video, + hiero.core.AudioTrack: otio.schema.TrackKind.Audio +} +self.project_fps = None +self.marker_color_map = { + "magenta": otio.schema.MarkerColor.MAGENTA, + "red": otio.schema.MarkerColor.RED, + "yellow": otio.schema.MarkerColor.YELLOW, + "green": otio.schema.MarkerColor.GREEN, + "cyan": otio.schema.MarkerColor.CYAN, + "blue": otio.schema.MarkerColor.BLUE, +} +self.timeline = None +self.include_tags = None + + +def create_otio_rational_time(frame, fps): + return otio.opentime.RationalTime( + float(frame), + float(fps) + ) + + +def create_otio_time_range(start_frame, frame_duration, fps): + return otio.opentime.TimeRange( + start_time=create_otio_rational_time(start_frame, fps), + duration=create_otio_rational_time(frame_duration, fps) + ) + + +def _get_metadata(item): + if hasattr(item, 'metadata'): + return {key: value for key, value in item.metadata().items()} + return {} + + +def create_otio_reference(clip): + metadata = _get_metadata(clip) + media_source = clip.mediaSource() + + # get file info for path and start frame + file_info = media_source.fileinfos().pop() + frame_start = file_info.startFrame() + path = file_info.filename() + + # get padding and other file infos + padding = media_source.filenamePadding() + file_head = media_source.filenameHead() + is_sequence = not media_source.singleFile() + frame_duration = media_source.duration() + fps = utils.get_rate(clip) + extension = os.path.splitext(path)[-1] + + if is_sequence: + metadata.update({ + "isSequence": True, + "padding": padding + }) + + otio_ex_ref_item = None + + if is_sequence: + # if it is file sequence try to create `ImageSequenceReference` + # the OTIO might not be compatible so return nothing and do it old way + try: + dirname = os.path.dirname(path) + otio_ex_ref_item = otio.schema.ImageSequenceReference( + target_url_base=dirname + os.sep, + name_prefix=file_head, + name_suffix=extension, + start_frame=frame_start, + frame_zero_padding=padding, + rate=fps, + available_range=create_otio_time_range( + frame_start, + frame_duration, + fps + ) + ) + except AttributeError: + pass + + if not otio_ex_ref_item: + reformat_path = utils.get_reformated_path(path, padded=False) + # in case old OTIO or video file create `ExternalReference` + otio_ex_ref_item = otio.schema.ExternalReference( + target_url=reformat_path, + available_range=create_otio_time_range( + frame_start, + frame_duration, + fps + ) + ) + + # add metadata to otio item + add_otio_metadata(otio_ex_ref_item, media_source, **metadata) + + return otio_ex_ref_item + + +def get_marker_color(tag): + icon = tag.icon() + pat = r'icons:Tag(?P\w+)\.\w+' + + res = re.search(pat, icon) + if res: + color = res.groupdict().get('color') + if color.lower() in self.marker_color_map: + return self.marker_color_map[color.lower()] + + return otio.schema.MarkerColor.RED + + +def create_otio_markers(otio_item, track_item): + for tag in track_item.tags(): + if not tag.visible(): + continue + + if tag.name() == 'Copy': + # Hiero adds this tag to a lot of clips + continue + + frame_rate = utils.get_rate(track_item) + + marked_range = otio.opentime.TimeRange( + start_time=otio.opentime.RationalTime( + tag.inTime(), + frame_rate + ), + duration=otio.opentime.RationalTime( + int(tag.metadata().dict().get('tag.length', '0')), + frame_rate + ) + ) + + metadata = dict( + Hiero=tag.metadata().dict() + ) + # Store the source item for future import assignment + metadata['Hiero']['source_type'] = track_item.__class__.__name__ + + marker = otio.schema.Marker( + name=tag.name(), + color=get_marker_color(tag), + marked_range=marked_range, + metadata=metadata + ) + + otio_item.markers.append(marker) + + +def create_otio_clip(track_item): + clip = track_item.source() + source_in = track_item.sourceIn() + duration = track_item.sourceDuration() + fps = utils.get_rate(track_item) + name = track_item.name() + + media_reference = create_otio_reference(clip) + source_range = create_otio_time_range( + int(source_in), + int(duration), + fps + ) + + otio_clip = otio.schema.Clip( + name=name, + source_range=source_range, + media_reference=media_reference + ) + create_otio_markers(otio_clip, track_item) + + return otio_clip + + +def create_otio_gap(gap_start, clip_start, tl_start_frame, fps): + return otio.schema.Gap( + source_range=create_otio_time_range( + gap_start, + (clip_start - tl_start_frame) - gap_start, + fps + ) + ) + + +def _create_otio_timeline(): + metadata = _get_metadata(self.timeline) + start_time = create_otio_rational_time( + self.timeline.timecodeStart(), self.project_fps) + + return otio.schema.Timeline( + name=self.timeline.name(), + global_start_time=start_time, + metadata=metadata + ) + + +def _get_metadata_media_pool_item(media_pool_item): + data = dict() + data.update({k: v for k, v in media_pool_item.GetMetadata().items()}) + property = media_pool_item.GetClipProperty() or {} + for name, value in property.items(): + if "Resolution" in name and "" != value: + width, height = value.split("x") + data.update({ + "width": int(width), + "height": int(height) + }) + if "PAR" in name and "" != value: + try: + data.update({"pixelAspect": float(value)}) + except ValueError: + if "Square" in value: + data.update({"pixelAspect": float(1)}) + else: + data.update({"pixelAspect": float(1)}) + + return data + + +def create_otio_track(track_type, track_name): + return otio.schema.Track( + name=track_name, + kind=self.track_types[track_type] + ) + + +def add_otio_gap(clip_start, otio_track, item_start_frame): + # if gap between track start and clip start + if clip_start > otio_track.available_range().duration.value: + # create gap and add it to track + otio_track.append( + create_otio_gap( + otio_track.available_range().duration.value, + item_start_frame, + self.timeline.timecodeStart(), + self.project_fps + ) + ) + + +def add_otio_metadata(otio_item, media_source, **kwargs): + metadata = _get_metadata(media_source) + + # add additional metadata from kwargs + if kwargs: + metadata.update(kwargs) + + # add metadata to otio item metadata + for key, value in metadata.items(): + otio_item.metadata.update({key: value}) + + +def create_otio_timeline(): + + # get current timeline + self.timeline = hiero.ui.activeSequence() + self.project_fps = self.timeline.framerate().toFloat() + + # convert timeline to otio + otio_timeline = _create_otio_timeline() + + # loop all defined track types + for track in self.hiero_sequence.items(): + # skip if track is disabled + if not track.isEnabled(): + continue + + # convert track to otio + otio_track = create_otio_track( + type(track), track.name()) + + for itemindex, track_item in enumerate(track): + # skip offline track items + if not track_item.isMediaPresent(): + continue + + # skip if track item is disabled + if not track_item.isEnabled(): + continue + + # calculate real clip start + clip_start = track_item.timelineIn() + + add_otio_gap( + clip_start, otio_track, clip_start) + + # create otio clip and add it to track + otio_clip = create_otio_clip(track_item) + otio_track.append(otio_clip) + + # add track to otio timeline + otio_timeline.tracks.append(otio_track) + + return otio_timeline + + +def write_to_file(otio_timeline, path): + otio.adapters.write_to_file(otio_timeline, path) diff --git a/openpype/hosts/hiero/otio/utils.py b/openpype/hosts/hiero/otio/utils.py new file mode 100644 index 0000000000..12f963fe97 --- /dev/null +++ b/openpype/hosts/hiero/otio/utils.py @@ -0,0 +1,83 @@ +import re +import opentimelineio as otio + + +def timecode_to_frames(timecode, framerate): + rt = otio.opentime.from_timecode(timecode, 24) + return int(otio.opentime.to_frames(rt)) + + +def frames_to_timecode(frames, framerate): + rt = otio.opentime.from_frames(frames, framerate) + return otio.opentime.to_timecode(rt) + + +def frames_to_secons(frames, framerate): + rt = otio.opentime.from_frames(frames, framerate) + return otio.opentime.to_seconds(rt) + + +def get_reformated_path(path, padded=True, first=False): + """ + Return fixed python expression path + + Args: + path (str): path url or simple file name + + Returns: + type: string with reformated path + + Example: + get_reformated_path("plate.[0001-1008].exr") > plate.%04d.exr + + """ + num_pattern = r"(\[\d+\-\d+\])" + padding_pattern = r"(\d+)(?=-)" + first_frame_pattern = re.compile(r"\[(\d+)\-\d+\]") + + if "[" in path: + padding = len(re.findall(padding_pattern, path).pop()) + if padded: + path = re.sub(num_pattern, f"%0{padding}d", path) + elif first: + first_frame = re.findall(first_frame_pattern, path, flags=0) + if len(first_frame) >= 1: + first_frame = first_frame[0] + path = re.sub(num_pattern, first_frame, path) + else: + path = re.sub(num_pattern, "%d", path) + return path + + +def get_padding_from_path(path): + """ + Return padding number from DaVinci Resolve sequence path style + + Args: + path (str): path url or simple file name + + Returns: + int: padding number + + Example: + get_padding_from_path("plate.[0001-1008].exr") > 4 + + """ + padding_pattern = "(\\d+)(?=-)" + if "[" in path: + return len(re.findall(padding_pattern, path).pop()) + + return None + + +def get_rate(item): + if not hasattr(item, 'framerate'): + item = item.sequence() + + num, den = item.framerate().toRational() + rate = float(num) / float(den) + + if rate.is_integer(): + return rate + + return round(rate, 4) From d5e4c1ee0c13a255f9e13d3419c5642d8a37457d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 8 Apr 2021 18:56:02 +0200 Subject: [PATCH 015/180] Hiero: otio export finishing --- openpype/hosts/hiero/otio/hiero_export.py | 524 ++++++++---------- openpype/hosts/hiero/otio/hiero_export_.py | 312 ----------- openpype/hosts/hiero/otio/hiero_export__.py | 386 +++++++++++++ .../Startup/otioexporter/OTIOExportTask.py | 8 +- 4 files changed, 629 insertions(+), 601 deletions(-) delete mode 100644 openpype/hosts/hiero/otio/hiero_export_.py create mode 100644 openpype/hosts/hiero/otio/hiero_export__.py diff --git a/openpype/hosts/hiero/otio/hiero_export.py b/openpype/hosts/hiero/otio/hiero_export.py index 8e19b26741..65c4ae13e1 100644 --- a/openpype/hosts/hiero/otio/hiero_export.py +++ b/openpype/hosts/hiero/otio/hiero_export.py @@ -1,20 +1,20 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -__author__ = "Daniel Flehner Heen" -__credits__ = ["Jakub Jezek", "Daniel Flehner Heen"] +""" compatibility OpenTimelineIO 0.12.0 and newer +""" import os -import sys import re +import sys +import opentimelineio as otio +from . import utils import hiero.core import hiero.ui -import opentimelineio as otio - -# build modul class self = sys.modules[__name__] - +self.track_types = { + hiero.core.VideoTrack: otio.schema.TrackKind.Video, + hiero.core.AudioTrack: otio.schema.TrackKind.Audio +} +self.project_fps = None self.marker_color_map = { "magenta": otio.schema.MarkerColor.MAGENTA, "red": otio.schema.MarkerColor.RED, @@ -23,88 +23,92 @@ self.marker_color_map = { "cyan": otio.schema.MarkerColor.CYAN, "blue": otio.schema.MarkerColor.BLUE, } -self.hiero_sequence = None +self.timeline = None self.include_tags = None -def get_rate(item): - if not hasattr(item, 'framerate'): - item = item.sequence() - - num, den = item.framerate().toRational() - rate = float(num) / float(den) - - if rate.is_integer(): - return rate - - return round(rate, 2) - - -def get_clip_ranges(trackitem): - # Get rate from source or sequence - if trackitem.source().mediaSource().hasVideo(): - rate_item = trackitem.source() - - else: - rate_item = trackitem.sequence() - - source_rate = get_rate(rate_item) - - # Reversed video/audio - if trackitem.playbackSpeed() < 0: - start = trackitem.sourceOut() - - else: - start = trackitem.sourceIn() - - source_start_time = otio.opentime.RationalTime( - start, - source_rate - ) - source_duration = otio.opentime.RationalTime( - trackitem.duration(), - source_rate +def create_otio_rational_time(frame, fps): + return otio.opentime.RationalTime( + float(frame), + float(fps) ) - source_range = otio.opentime.TimeRange( - start_time=source_start_time, - duration=source_duration + +def create_otio_time_range(start_frame, frame_duration, fps): + return otio.opentime.TimeRange( + start_time=create_otio_rational_time(start_frame, fps), + duration=create_otio_rational_time(frame_duration, fps) ) - hiero_clip = trackitem.source() - available_range = None - if hiero_clip.mediaSource().isMediaPresent(): - start_time = otio.opentime.RationalTime( - hiero_clip.mediaSource().startTime(), - source_rate - ) - duration = otio.opentime.RationalTime( - hiero_clip.mediaSource().duration(), - source_rate - ) - available_range = otio.opentime.TimeRange( - start_time=start_time, - duration=duration +def _get_metadata(item): + if hasattr(item, 'metadata'): + return {key: value for key, value in item.metadata().items()} + return {} + + +def create_otio_reference(clip): + metadata = _get_metadata(clip) + media_source = clip.mediaSource() + + # get file info for path and start frame + file_info = media_source.fileinfos().pop() + frame_start = file_info.startFrame() + path = file_info.filename() + + # get padding and other file infos + padding = media_source.filenamePadding() + file_head = media_source.filenameHead() + is_sequence = not media_source.singleFile() + frame_duration = media_source.duration() + fps = utils.get_rate(clip) + extension = os.path.splitext(path)[-1] + + if is_sequence: + metadata.update({ + "isSequence": True, + "padding": padding + }) + + otio_ex_ref_item = None + + if is_sequence: + # if it is file sequence try to create `ImageSequenceReference` + # the OTIO might not be compatible so return nothing and do it old way + try: + dirname = os.path.dirname(path) + otio_ex_ref_item = otio.schema.ImageSequenceReference( + target_url_base=dirname + os.sep, + name_prefix=file_head, + name_suffix=extension, + start_frame=frame_start, + frame_zero_padding=padding, + rate=fps, + available_range=create_otio_time_range( + frame_start, + frame_duration, + fps + ) + ) + except AttributeError: + pass + + if not otio_ex_ref_item: + reformat_path = utils.get_reformated_path(path, padded=False) + # in case old OTIO or video file create `ExternalReference` + otio_ex_ref_item = otio.schema.ExternalReference( + target_url=reformat_path, + available_range=create_otio_time_range( + frame_start, + frame_duration, + fps + ) ) - return source_range, available_range + # add metadata to otio item + add_otio_metadata(otio_ex_ref_item, media_source, **metadata) - -def add_gap(trackitem, otio_track, prev_out): - gap_length = trackitem.timelineIn() - prev_out - if prev_out != 0: - gap_length -= 1 - - rate = get_rate(trackitem.sequence()) - gap = otio.opentime.TimeRange( - duration=otio.opentime.RationalTime( - gap_length, - rate - ) - ) - otio_gap = otio.schema.Gap(source_range=gap) - otio_track.append(otio_gap) + return otio_ex_ref_item def get_marker_color(tag): @@ -120,8 +124,8 @@ def get_marker_color(tag): return otio.schema.MarkerColor.RED -def add_markers(hiero_item, otio_item): - for tag in hiero_item.tags(): +def create_otio_markers(otio_item, item): + for tag in item.tags(): if not tag.visible(): continue @@ -129,7 +133,7 @@ def add_markers(hiero_item, otio_item): # Hiero adds this tag to a lot of clips continue - frame_rate = get_rate(hiero_item) + frame_rate = utils.get_rate(item) marked_range = otio.opentime.TimeRange( start_time=otio.opentime.RationalTime( @@ -146,7 +150,7 @@ def add_markers(hiero_item, otio_item): Hiero=tag.metadata().dict() ) # Store the source item for future import assignment - metadata['Hiero']['source_type'] = hiero_item.__class__.__name__ + metadata['Hiero']['source_type'] = item.__class__.__name__ marker = otio.schema.Marker( name=tag.name(), @@ -158,229 +162,181 @@ def add_markers(hiero_item, otio_item): otio_item.markers.append(marker) -def add_clip(trackitem, otio_track, itemindex): - hiero_clip = trackitem.source() +def create_otio_clip(track_item): + clip = track_item.source() + source_in = track_item.sourceIn() + duration = track_item.sourceDuration() + fps = utils.get_rate(track_item) + name = track_item.name() - # Add Gap if needed - if itemindex == 0: - prev_item = trackitem - - else: - prev_item = trackitem.parent().items()[itemindex - 1] - - clip_diff = trackitem.timelineIn() - prev_item.timelineOut() - - if itemindex == 0 and trackitem.timelineIn() > 0: - add_gap(trackitem, otio_track, 0) - - elif itemindex and clip_diff != 1: - add_gap(trackitem, otio_track, prev_item.timelineOut()) - - # Create Clip - source_range, available_range = get_clip_ranges(trackitem) + media_reference = create_otio_reference(clip) + source_range = create_otio_time_range( + int(source_in), + int(duration), + fps + ) otio_clip = otio.schema.Clip( - name=trackitem.name(), - source_range=source_range + name=name, + source_range=source_range, + media_reference=media_reference ) - media_reference = create_otio_reference(hiero_clip) - - otio_clip.media_reference = media_reference - - # Add Time Effects - playbackspeed = trackitem.playbackSpeed() - if playbackspeed != 1: - if playbackspeed == 0: - time_effect = otio.schema.FreezeFrame() - - else: - time_effect = otio.schema.LinearTimeWarp( - time_scalar=playbackspeed - ) - otio_clip.effects.append(time_effect) - # Add tags as markers if self.include_tags: - add_markers(trackitem, otio_clip) - add_markers(trackitem.source(), otio_clip) + create_otio_markers(otio_clip, track_item) + create_otio_markers(otio_clip, track_item.source()) - otio_track.append(otio_clip) + return otio_clip - # Add Transition if needed - if trackitem.inTransition() or trackitem.outTransition(): - add_transition(trackitem, otio_track) -def _get_metadata(hiero_object): - metadata = hiero_object.metadata() - return {key: value for key, value in metadata.items()} - -def create_otio_reference(hiero_clip): - metadata = _get_metadata(hiero_clip) - mp_clip_property = media_pool_item.GetClipProperty() - path = mp_clip_property["File Path"] - reformat_path = utils.get_reformated_path(path, padded=True) - padding = utils.get_padding_from_path(path) - - if padding: - metadata.update({ - "isSequence": True, - "padding": padding - }) - - # get clip property regarding to type - mp_clip_property = media_pool_item.GetClipProperty() - fps = float(mp_clip_property["FPS"]) - if mp_clip_property["Type"] == "Video": - frame_start = int(mp_clip_property["Start"]) - frame_duration = int(mp_clip_property["Frames"]) - else: - audio_duration = str(mp_clip_property["Duration"]) - frame_start = 0 - frame_duration = int(utils.timecode_to_frames( - audio_duration, float(fps))) - - otio_ex_ref_item = None - - if padding: - # if it is file sequence try to create `ImageSequenceReference` - # the OTIO might not be compatible so return nothing and do it old way - try: - dirname, filename = os.path.split(path) - collection = clique.parse(filename, '{head}[{ranges}]{tail}') - padding_num = len(re.findall("(\\d+)(?=-)", filename).pop()) - otio_ex_ref_item = otio.schema.ImageSequenceReference( - target_url_base=dirname + os.sep, - name_prefix=collection.format("{head}"), - name_suffix=collection.format("{tail}"), - start_frame=frame_start, - frame_zero_padding=padding_num, - rate=fps, - available_range=create_otio_time_range( - frame_start, - frame_duration, - fps - ) - ) - except AttributeError: - pass - - if not otio_ex_ref_item: - # in case old OTIO or video file create `ExternalReference` - otio_ex_ref_item = otio.schema.ExternalReference( - target_url=reformat_path, - available_range=create_otio_time_range( - frame_start, - frame_duration, - fps - ) +def create_otio_gap(gap_start, clip_start, tl_start_frame, fps): + return otio.schema.Gap( + source_range=create_otio_time_range( + gap_start, + (clip_start - tl_start_frame) - gap_start, + fps ) - - # add metadata to otio item - add_otio_metadata(otio_ex_ref_item, hiero_clip, **metadata) - - return otio_ex_ref_item + ) -def add_otio_metadata(otio_item, hiero_clip, **kwargs): - mp_metadata = hiero_clip.GetMetadata() +def _create_otio_timeline(): + metadata = _get_metadata(self.timeline) + start_time = create_otio_rational_time( + self.timeline.timecodeStart(), self.project_fps) + + return otio.schema.Timeline( + name=self.timeline.name(), + global_start_time=start_time, + metadata=metadata + ) + + +def _get_metadata_media_pool_item(media_pool_item): + data = dict() + data.update({k: v for k, v in media_pool_item.GetMetadata().items()}) + property = media_pool_item.GetClipProperty() or {} + for name, value in property.items(): + if "Resolution" in name and "" != value: + width, height = value.split("x") + data.update({ + "width": int(width), + "height": int(height) + }) + if "PAR" in name and "" != value: + try: + data.update({"pixelAspect": float(value)}) + except ValueError: + if "Square" in value: + data.update({"pixelAspect": float(1)}) + else: + data.update({"pixelAspect": float(1)}) + + return data + + +def create_otio_track(track_type, track_name): + return otio.schema.Track( + name=track_name, + kind=self.track_types[track_type] + ) + + +def add_otio_gap(track_item, otio_track, prev_out): + gap_length = track_item.timelineIn() - prev_out + if prev_out != 0: + gap_length -= 1 + + gap = otio.opentime.TimeRange( + duration=otio.opentime.RationalTime( + gap_length, + self.project_fps + ) + ) + otio_gap = otio.schema.Gap(source_range=gap) + otio_track.append(otio_gap) + + +def add_otio_metadata(otio_item, media_source, **kwargs): + metadata = _get_metadata(media_source) + # add additional metadata from kwargs if kwargs: - mp_metadata.update(kwargs) + metadata.update(kwargs) # add metadata to otio item metadata - for key, value in mp_metadata.items(): + for key, value in metadata.items(): otio_item.metadata.update({key: value}) -def add_transition(trackitem, otio_track): - transitions = [] - if trackitem.inTransition(): - if trackitem.inTransition().alignment().name == 'kFadeIn': - transitions.append(trackitem.inTransition()) +def create_otio_timeline(): - if trackitem.outTransition(): - transitions.append(trackitem.outTransition()) + # get current timeline + self.timeline = hiero.ui.activeSequence() + self.project_fps = self.timeline.framerate().toFloat() - for transition in transitions: - alignment = transition.alignment().name - - if alignment == 'kFadeIn': - in_offset_frames = 0 - out_offset_frames = ( - transition.timelineOut() - transition.timelineIn() - ) + 1 - - elif alignment == 'kFadeOut': - in_offset_frames = ( - trackitem.timelineOut() - transition.timelineIn() - ) + 1 - out_offset_frames = 0 - - elif alignment == 'kDissolve': - in_offset_frames = ( - transition.inTrackItem().timelineOut() - - transition.timelineIn() - ) - out_offset_frames = ( - transition.timelineOut() - - transition.outTrackItem().timelineIn() - ) - - else: - # kUnknown transition is ignored - continue - - rate = trackitem.source().framerate().toFloat() - in_time = otio.opentime.RationalTime(in_offset_frames, rate) - out_time = otio.opentime.RationalTime(out_offset_frames, rate) - - otio_transition = otio.schema.Transition( - name=alignment, # Consider placing Hiero name in metadata - transition_type=otio.schema.TransitionTypes.SMPTE_Dissolve, - in_offset=in_time, - out_offset=out_time - ) - - if alignment == 'kFadeIn': - otio_track.insert(-1, otio_transition) - - else: - otio_track.append(otio_transition) - - -def add_tracks(): - for track in self.hiero_sequence.items(): - if isinstance(track, hiero.core.AudioTrack): - kind = otio.schema.TrackKind.Audio - - else: - kind = otio.schema.TrackKind.Video - - otio_track = otio.schema.Track(name=track.name(), kind=kind) - - for itemindex, trackitem in enumerate(track): - if isinstance(trackitem.source(), hiero.core.Clip): - add_clip(trackitem, otio_track, itemindex) - - self.otio_timeline.tracks.append(otio_track) + # convert timeline to otio + otio_timeline = _create_otio_timeline() # Add tags as markers if self.include_tags: - add_markers(self.hiero_sequence, self.otio_timeline.tracks) + create_otio_markers(otio_timeline, self.timeline) + + # loop all defined track types + for track in self.hiero_sequence.items(): + # skip if track is disabled + if not track.isEnabled(): + continue + + # convert track to otio + otio_track = create_otio_track( + type(track), track.name()) + + for itemindex, track_item in enumerate(track): + # skip offline track items + if not track_item.isMediaPresent(): + continue + + # skip if track item is disabled + if not track_item.isEnabled(): + continue + + # Add Gap if needed + if itemindex == 0: + # if it is first track item at track then add + # it to previouse item + prev_item = track_item + + else: + # get previouse item + prev_item = track_item.parent().items()[itemindex - 1] + + # calculate clip frame range difference from each other + clip_diff = track_item.timelineIn() - prev_item.timelineOut() + + # add gap if first track item is not starting + # at first timeline frame + if itemindex == 0 and track_item.timelineIn() > 0: + add_otio_gap(track_item, otio_track, 0) + + # or add gap if following track items are having + # frame range differences from each other + elif itemindex and clip_diff != 1: + add_otio_gap(track_item, otio_track, prev_item.timelineOut()) + + # create otio clip and add it to track + otio_clip = create_otio_clip(track_item) + otio_track.append(otio_clip) + + # Add tags as markers + if self.include_tags: + create_otio_markers(otio_track, track) + + # add track to otio timeline + otio_timeline.tracks.append(otio_track) + + return otio_timeline -def create_OTIO(sequence=None): - self.hiero_sequence = sequence or hiero.ui.activeSequence() - self.otio_timeline = otio.schema.Timeline() - - # Set global start time based on sequence - self.otio_timeline.global_start_time = otio.opentime.RationalTime( - self.hiero_sequence.timecodeStart(), - self.hiero_sequence.framerate().toFloat() - ) - self.otio_timeline.name = self.hiero_sequence.name() - - add_tracks() - - return self.otio_timeline +def write_to_file(otio_timeline, path): + otio.adapters.write_to_file(otio_timeline, path) diff --git a/openpype/hosts/hiero/otio/hiero_export_.py b/openpype/hosts/hiero/otio/hiero_export_.py deleted file mode 100644 index b5a96a9414..0000000000 --- a/openpype/hosts/hiero/otio/hiero_export_.py +++ /dev/null @@ -1,312 +0,0 @@ -""" compatibility OpenTimelineIO 0.12.0 and older -""" - -import os -import re -import sys -import opentimelineio as otio -from . import utils -import hiero.core -import hiero.ui - -self = sys.modules[__name__] -self.track_types = { - hiero.core.VideoTrack: otio.schema.TrackKind.Video, - hiero.core.AudioTrack: otio.schema.TrackKind.Audio -} -self.project_fps = None -self.marker_color_map = { - "magenta": otio.schema.MarkerColor.MAGENTA, - "red": otio.schema.MarkerColor.RED, - "yellow": otio.schema.MarkerColor.YELLOW, - "green": otio.schema.MarkerColor.GREEN, - "cyan": otio.schema.MarkerColor.CYAN, - "blue": otio.schema.MarkerColor.BLUE, -} -self.timeline = None -self.include_tags = None - - -def create_otio_rational_time(frame, fps): - return otio.opentime.RationalTime( - float(frame), - float(fps) - ) - - -def create_otio_time_range(start_frame, frame_duration, fps): - return otio.opentime.TimeRange( - start_time=create_otio_rational_time(start_frame, fps), - duration=create_otio_rational_time(frame_duration, fps) - ) - - -def _get_metadata(item): - if hasattr(item, 'metadata'): - return {key: value for key, value in item.metadata().items()} - return {} - - -def create_otio_reference(clip): - metadata = _get_metadata(clip) - media_source = clip.mediaSource() - - # get file info for path and start frame - file_info = media_source.fileinfos().pop() - frame_start = file_info.startFrame() - path = file_info.filename() - - # get padding and other file infos - padding = media_source.filenamePadding() - file_head = media_source.filenameHead() - is_sequence = not media_source.singleFile() - frame_duration = media_source.duration() - fps = utils.get_rate(clip) - extension = os.path.splitext(path)[-1] - - if is_sequence: - metadata.update({ - "isSequence": True, - "padding": padding - }) - - otio_ex_ref_item = None - - if is_sequence: - # if it is file sequence try to create `ImageSequenceReference` - # the OTIO might not be compatible so return nothing and do it old way - try: - dirname = os.path.dirname(path) - otio_ex_ref_item = otio.schema.ImageSequenceReference( - target_url_base=dirname + os.sep, - name_prefix=file_head, - name_suffix=extension, - start_frame=frame_start, - frame_zero_padding=padding, - rate=fps, - available_range=create_otio_time_range( - frame_start, - frame_duration, - fps - ) - ) - except AttributeError: - pass - - if not otio_ex_ref_item: - reformat_path = utils.get_reformated_path(path, padded=False) - # in case old OTIO or video file create `ExternalReference` - otio_ex_ref_item = otio.schema.ExternalReference( - target_url=reformat_path, - available_range=create_otio_time_range( - frame_start, - frame_duration, - fps - ) - ) - - # add metadata to otio item - add_otio_metadata(otio_ex_ref_item, media_source, **metadata) - - return otio_ex_ref_item - - -def get_marker_color(tag): - icon = tag.icon() - pat = r'icons:Tag(?P\w+)\.\w+' - - res = re.search(pat, icon) - if res: - color = res.groupdict().get('color') - if color.lower() in self.marker_color_map: - return self.marker_color_map[color.lower()] - - return otio.schema.MarkerColor.RED - - -def create_otio_markers(otio_item, track_item): - for tag in track_item.tags(): - if not tag.visible(): - continue - - if tag.name() == 'Copy': - # Hiero adds this tag to a lot of clips - continue - - frame_rate = utils.get_rate(track_item) - - marked_range = otio.opentime.TimeRange( - start_time=otio.opentime.RationalTime( - tag.inTime(), - frame_rate - ), - duration=otio.opentime.RationalTime( - int(tag.metadata().dict().get('tag.length', '0')), - frame_rate - ) - ) - - metadata = dict( - Hiero=tag.metadata().dict() - ) - # Store the source item for future import assignment - metadata['Hiero']['source_type'] = track_item.__class__.__name__ - - marker = otio.schema.Marker( - name=tag.name(), - color=get_marker_color(tag), - marked_range=marked_range, - metadata=metadata - ) - - otio_item.markers.append(marker) - - -def create_otio_clip(track_item): - clip = track_item.source() - source_in = track_item.sourceIn() - duration = track_item.sourceDuration() - fps = utils.get_rate(track_item) - name = track_item.name() - - media_reference = create_otio_reference(clip) - source_range = create_otio_time_range( - int(source_in), - int(duration), - fps - ) - - otio_clip = otio.schema.Clip( - name=name, - source_range=source_range, - media_reference=media_reference - ) - create_otio_markers(otio_clip, track_item) - - return otio_clip - - -def create_otio_gap(gap_start, clip_start, tl_start_frame, fps): - return otio.schema.Gap( - source_range=create_otio_time_range( - gap_start, - (clip_start - tl_start_frame) - gap_start, - fps - ) - ) - - -def _create_otio_timeline(): - metadata = _get_metadata(self.timeline) - start_time = create_otio_rational_time( - self.timeline.timecodeStart(), self.project_fps) - - return otio.schema.Timeline( - name=self.timeline.name(), - global_start_time=start_time, - metadata=metadata - ) - - -def _get_metadata_media_pool_item(media_pool_item): - data = dict() - data.update({k: v for k, v in media_pool_item.GetMetadata().items()}) - property = media_pool_item.GetClipProperty() or {} - for name, value in property.items(): - if "Resolution" in name and "" != value: - width, height = value.split("x") - data.update({ - "width": int(width), - "height": int(height) - }) - if "PAR" in name and "" != value: - try: - data.update({"pixelAspect": float(value)}) - except ValueError: - if "Square" in value: - data.update({"pixelAspect": float(1)}) - else: - data.update({"pixelAspect": float(1)}) - - return data - - -def create_otio_track(track_type, track_name): - return otio.schema.Track( - name=track_name, - kind=self.track_types[track_type] - ) - - -def add_otio_gap(clip_start, otio_track, item_start_frame): - # if gap between track start and clip start - if clip_start > otio_track.available_range().duration.value: - # create gap and add it to track - otio_track.append( - create_otio_gap( - otio_track.available_range().duration.value, - item_start_frame, - self.timeline.timecodeStart(), - self.project_fps - ) - ) - - -def add_otio_metadata(otio_item, media_source, **kwargs): - metadata = _get_metadata(media_source) - - # add additional metadata from kwargs - if kwargs: - metadata.update(kwargs) - - # add metadata to otio item metadata - for key, value in metadata.items(): - otio_item.metadata.update({key: value}) - - -def create_otio_timeline(): - - # get current timeline - self.timeline = hiero.ui.activeSequence() - self.project_fps = self.timeline.framerate().toFloat() - - # convert timeline to otio - otio_timeline = _create_otio_timeline() - - # loop all defined track types - for track in self.hiero_sequence.items(): - # skip if track is disabled - if not track.isEnabled(): - continue - - # convert track to otio - otio_track = create_otio_track( - type(track), track.name()) - - for itemindex, track_item in enumerate(track): - # skip offline track items - if not track_item.isMediaPresent(): - continue - - # skip if track item is disabled - if not track_item.isEnabled(): - continue - - # calculate real clip start - clip_start = track_item.timelineIn() - - add_otio_gap( - clip_start, otio_track, clip_start) - - # create otio clip and add it to track - otio_clip = create_otio_clip(track_item) - otio_track.append(otio_clip) - - # add track to otio timeline - otio_timeline.tracks.append(otio_track) - - return otio_timeline - - -def write_to_file(otio_timeline, path): - otio.adapters.write_to_file(otio_timeline, path) diff --git a/openpype/hosts/hiero/otio/hiero_export__.py b/openpype/hosts/hiero/otio/hiero_export__.py new file mode 100644 index 0000000000..8e19b26741 --- /dev/null +++ b/openpype/hosts/hiero/otio/hiero_export__.py @@ -0,0 +1,386 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +__author__ = "Daniel Flehner Heen" +__credits__ = ["Jakub Jezek", "Daniel Flehner Heen"] + +import os +import sys +import re +import hiero.core +import hiero.ui +import opentimelineio as otio + + +# build modul class +self = sys.modules[__name__] + +self.marker_color_map = { + "magenta": otio.schema.MarkerColor.MAGENTA, + "red": otio.schema.MarkerColor.RED, + "yellow": otio.schema.MarkerColor.YELLOW, + "green": otio.schema.MarkerColor.GREEN, + "cyan": otio.schema.MarkerColor.CYAN, + "blue": otio.schema.MarkerColor.BLUE, +} +self.hiero_sequence = None +self.include_tags = None + + +def get_rate(item): + if not hasattr(item, 'framerate'): + item = item.sequence() + + num, den = item.framerate().toRational() + rate = float(num) / float(den) + + if rate.is_integer(): + return rate + + return round(rate, 2) + + +def get_clip_ranges(trackitem): + # Get rate from source or sequence + if trackitem.source().mediaSource().hasVideo(): + rate_item = trackitem.source() + + else: + rate_item = trackitem.sequence() + + source_rate = get_rate(rate_item) + + # Reversed video/audio + if trackitem.playbackSpeed() < 0: + start = trackitem.sourceOut() + + else: + start = trackitem.sourceIn() + + source_start_time = otio.opentime.RationalTime( + start, + source_rate + ) + source_duration = otio.opentime.RationalTime( + trackitem.duration(), + source_rate + ) + + source_range = otio.opentime.TimeRange( + start_time=source_start_time, + duration=source_duration + ) + + hiero_clip = trackitem.source() + + available_range = None + if hiero_clip.mediaSource().isMediaPresent(): + start_time = otio.opentime.RationalTime( + hiero_clip.mediaSource().startTime(), + source_rate + ) + duration = otio.opentime.RationalTime( + hiero_clip.mediaSource().duration(), + source_rate + ) + available_range = otio.opentime.TimeRange( + start_time=start_time, + duration=duration + ) + + return source_range, available_range + + +def add_gap(trackitem, otio_track, prev_out): + gap_length = trackitem.timelineIn() - prev_out + if prev_out != 0: + gap_length -= 1 + + rate = get_rate(trackitem.sequence()) + gap = otio.opentime.TimeRange( + duration=otio.opentime.RationalTime( + gap_length, + rate + ) + ) + otio_gap = otio.schema.Gap(source_range=gap) + otio_track.append(otio_gap) + + +def get_marker_color(tag): + icon = tag.icon() + pat = r'icons:Tag(?P\w+)\.\w+' + + res = re.search(pat, icon) + if res: + color = res.groupdict().get('color') + if color.lower() in self.marker_color_map: + return self.marker_color_map[color.lower()] + + return otio.schema.MarkerColor.RED + + +def add_markers(hiero_item, otio_item): + for tag in hiero_item.tags(): + if not tag.visible(): + continue + + if tag.name() == 'Copy': + # Hiero adds this tag to a lot of clips + continue + + frame_rate = get_rate(hiero_item) + + marked_range = otio.opentime.TimeRange( + start_time=otio.opentime.RationalTime( + tag.inTime(), + frame_rate + ), + duration=otio.opentime.RationalTime( + int(tag.metadata().dict().get('tag.length', '0')), + frame_rate + ) + ) + + metadata = dict( + Hiero=tag.metadata().dict() + ) + # Store the source item for future import assignment + metadata['Hiero']['source_type'] = hiero_item.__class__.__name__ + + marker = otio.schema.Marker( + name=tag.name(), + color=get_marker_color(tag), + marked_range=marked_range, + metadata=metadata + ) + + otio_item.markers.append(marker) + + +def add_clip(trackitem, otio_track, itemindex): + hiero_clip = trackitem.source() + + # Add Gap if needed + if itemindex == 0: + prev_item = trackitem + + else: + prev_item = trackitem.parent().items()[itemindex - 1] + + clip_diff = trackitem.timelineIn() - prev_item.timelineOut() + + if itemindex == 0 and trackitem.timelineIn() > 0: + add_gap(trackitem, otio_track, 0) + + elif itemindex and clip_diff != 1: + add_gap(trackitem, otio_track, prev_item.timelineOut()) + + # Create Clip + source_range, available_range = get_clip_ranges(trackitem) + + otio_clip = otio.schema.Clip( + name=trackitem.name(), + source_range=source_range + ) + + media_reference = create_otio_reference(hiero_clip) + + otio_clip.media_reference = media_reference + + # Add Time Effects + playbackspeed = trackitem.playbackSpeed() + if playbackspeed != 1: + if playbackspeed == 0: + time_effect = otio.schema.FreezeFrame() + + else: + time_effect = otio.schema.LinearTimeWarp( + time_scalar=playbackspeed + ) + otio_clip.effects.append(time_effect) + + # Add tags as markers + if self.include_tags: + add_markers(trackitem, otio_clip) + add_markers(trackitem.source(), otio_clip) + + otio_track.append(otio_clip) + + # Add Transition if needed + if trackitem.inTransition() or trackitem.outTransition(): + add_transition(trackitem, otio_track) + +def _get_metadata(hiero_object): + metadata = hiero_object.metadata() + return {key: value for key, value in metadata.items()} + +def create_otio_reference(hiero_clip): + metadata = _get_metadata(hiero_clip) + mp_clip_property = media_pool_item.GetClipProperty() + path = mp_clip_property["File Path"] + reformat_path = utils.get_reformated_path(path, padded=True) + padding = utils.get_padding_from_path(path) + + if padding: + metadata.update({ + "isSequence": True, + "padding": padding + }) + + # get clip property regarding to type + mp_clip_property = media_pool_item.GetClipProperty() + fps = float(mp_clip_property["FPS"]) + if mp_clip_property["Type"] == "Video": + frame_start = int(mp_clip_property["Start"]) + frame_duration = int(mp_clip_property["Frames"]) + else: + audio_duration = str(mp_clip_property["Duration"]) + frame_start = 0 + frame_duration = int(utils.timecode_to_frames( + audio_duration, float(fps))) + + otio_ex_ref_item = None + + if padding: + # if it is file sequence try to create `ImageSequenceReference` + # the OTIO might not be compatible so return nothing and do it old way + try: + dirname, filename = os.path.split(path) + collection = clique.parse(filename, '{head}[{ranges}]{tail}') + padding_num = len(re.findall("(\\d+)(?=-)", filename).pop()) + otio_ex_ref_item = otio.schema.ImageSequenceReference( + target_url_base=dirname + os.sep, + name_prefix=collection.format("{head}"), + name_suffix=collection.format("{tail}"), + start_frame=frame_start, + frame_zero_padding=padding_num, + rate=fps, + available_range=create_otio_time_range( + frame_start, + frame_duration, + fps + ) + ) + except AttributeError: + pass + + if not otio_ex_ref_item: + # in case old OTIO or video file create `ExternalReference` + otio_ex_ref_item = otio.schema.ExternalReference( + target_url=reformat_path, + available_range=create_otio_time_range( + frame_start, + frame_duration, + fps + ) + ) + + # add metadata to otio item + add_otio_metadata(otio_ex_ref_item, hiero_clip, **metadata) + + return otio_ex_ref_item + + +def add_otio_metadata(otio_item, hiero_clip, **kwargs): + mp_metadata = hiero_clip.GetMetadata() + # add additional metadata from kwargs + if kwargs: + mp_metadata.update(kwargs) + + # add metadata to otio item metadata + for key, value in mp_metadata.items(): + otio_item.metadata.update({key: value}) + +def add_transition(trackitem, otio_track): + transitions = [] + + if trackitem.inTransition(): + if trackitem.inTransition().alignment().name == 'kFadeIn': + transitions.append(trackitem.inTransition()) + + if trackitem.outTransition(): + transitions.append(trackitem.outTransition()) + + for transition in transitions: + alignment = transition.alignment().name + + if alignment == 'kFadeIn': + in_offset_frames = 0 + out_offset_frames = ( + transition.timelineOut() - transition.timelineIn() + ) + 1 + + elif alignment == 'kFadeOut': + in_offset_frames = ( + trackitem.timelineOut() - transition.timelineIn() + ) + 1 + out_offset_frames = 0 + + elif alignment == 'kDissolve': + in_offset_frames = ( + transition.inTrackItem().timelineOut() - + transition.timelineIn() + ) + out_offset_frames = ( + transition.timelineOut() - + transition.outTrackItem().timelineIn() + ) + + else: + # kUnknown transition is ignored + continue + + rate = trackitem.source().framerate().toFloat() + in_time = otio.opentime.RationalTime(in_offset_frames, rate) + out_time = otio.opentime.RationalTime(out_offset_frames, rate) + + otio_transition = otio.schema.Transition( + name=alignment, # Consider placing Hiero name in metadata + transition_type=otio.schema.TransitionTypes.SMPTE_Dissolve, + in_offset=in_time, + out_offset=out_time + ) + + if alignment == 'kFadeIn': + otio_track.insert(-1, otio_transition) + + else: + otio_track.append(otio_transition) + + +def add_tracks(): + for track in self.hiero_sequence.items(): + if isinstance(track, hiero.core.AudioTrack): + kind = otio.schema.TrackKind.Audio + + else: + kind = otio.schema.TrackKind.Video + + otio_track = otio.schema.Track(name=track.name(), kind=kind) + + for itemindex, trackitem in enumerate(track): + if isinstance(trackitem.source(), hiero.core.Clip): + add_clip(trackitem, otio_track, itemindex) + + self.otio_timeline.tracks.append(otio_track) + + # Add tags as markers + if self.include_tags: + add_markers(self.hiero_sequence, self.otio_timeline.tracks) + + +def create_OTIO(sequence=None): + self.hiero_sequence = sequence or hiero.ui.activeSequence() + self.otio_timeline = otio.schema.Timeline() + + # Set global start time based on sequence + self.otio_timeline.global_start_time = otio.opentime.RationalTime( + self.hiero_sequence.timecodeStart(), + self.hiero_sequence.framerate().toFloat() + ) + self.otio_timeline.name = self.hiero_sequence.name() + + add_tracks() + + return self.otio_timeline diff --git a/openpype/hosts/hiero/startup/Python/Startup/otioexporter/OTIOExportTask.py b/openpype/hosts/hiero/startup/Python/Startup/otioexporter/OTIOExportTask.py index 734213a05d..51265a3daf 100644 --- a/openpype/hosts/hiero/startup/Python/Startup/otioexporter/OTIOExportTask.py +++ b/openpype/hosts/hiero/startup/Python/Startup/otioexporter/OTIOExportTask.py @@ -9,8 +9,7 @@ import hiero.core from hiero.core import util import opentimelineio as otio -from pype.hosts.hiero.otio.hiero_export import create_OTIO - +from openpype.hosts.hiero.otio import hiero_export class OTIOExportTask(hiero.core.TaskBase): @@ -23,8 +22,7 @@ class OTIOExportTask(hiero.core.TaskBase): return str(type(self)) def startTask(self): - self.otio_timeline = create_OTIO( - self._sequence) + self.otio_timeline = hiero_export.create_otio_timeline() def taskStep(self): return False @@ -42,7 +40,7 @@ class OTIOExportTask(hiero.core.TaskBase): util.filesystem.makeDirs(dirname) # write otio file - otio.adapters.write_to_file(self.otio_timeline, exportPath) + hiero_export.write_to_file(self.otio_timeline, exportPath) # Catch all exceptions and log error except Exception as e: From 2db6bb534b1a04a5762a6d201c9d003a62ec2c17 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 8 Apr 2021 20:57:47 +0200 Subject: [PATCH 016/180] Hiero: final changes to otio import export modules --- openpype/hosts/hiero/otio/hiero_export.py | 65 ++- openpype/hosts/hiero/otio/hiero_export__.py | 386 ------------------ openpype/hosts/hiero/otio/hiero_import.py | 25 +- openpype/hosts/hiero/otio/utils.py | 21 +- .../Startup/otioexporter/OTIOExportTask.py | 2 +- .../Startup/otioexporter/OTIOExportUI.py | 5 +- .../Python/StartupUI/otioimporter/__init__.py | 2 +- 7 files changed, 58 insertions(+), 448 deletions(-) delete mode 100644 openpype/hosts/hiero/otio/hiero_export__.py diff --git a/openpype/hosts/hiero/otio/hiero_export.py b/openpype/hosts/hiero/otio/hiero_export.py index 65c4ae13e1..b2847ff6cb 100644 --- a/openpype/hosts/hiero/otio/hiero_export.py +++ b/openpype/hosts/hiero/otio/hiero_export.py @@ -4,10 +4,12 @@ import os import re import sys +import ast import opentimelineio as otio from . import utils import hiero.core import hiero.ui +reload(utils) self = sys.modules[__name__] self.track_types = { @@ -43,7 +45,7 @@ def create_otio_time_range(start_frame, frame_duration, fps): def _get_metadata(item): if hasattr(item, 'metadata'): - return {key: value for key, value in item.metadata().items()} + return {key: value for key, value in dict(item.metadata()).items()} return {} @@ -61,7 +63,7 @@ def create_otio_reference(clip): file_head = media_source.filenameHead() is_sequence = not media_source.singleFile() frame_duration = media_source.duration() - fps = utils.get_rate(clip) + fps = utils.get_rate(clip) or self.project_fps extension = os.path.splitext(path)[-1] if is_sequence: @@ -70,6 +72,13 @@ def create_otio_reference(clip): "padding": padding }) + # add resolution metadata + metadata.update({ + "width": int(media_source.width()), + "height": int(media_source.height()), + "pixelAspect": float(media_source.pixelAspect()) + }) + otio_ex_ref_item = None if is_sequence: @@ -133,7 +142,7 @@ def create_otio_markers(otio_item, item): # Hiero adds this tag to a lot of clips continue - frame_rate = utils.get_rate(item) + frame_rate = utils.get_rate(item) or self.project_fps marked_range = otio.opentime.TimeRange( start_time=otio.opentime.RationalTime( @@ -145,12 +154,22 @@ def create_otio_markers(otio_item, item): frame_rate ) ) + # add tag metadata but remove "tag." string + metadata = {} + + for key, value in tag.metadata().dict().items(): + _key = key.replace("tag.", "") + + try: + # capture exceptions which are related to strings only + _value = ast.literal_eval(value) + except (ValueError, SyntaxError): + _value = value + + metadata.update({_key: _value}) - metadata = dict( - Hiero=tag.metadata().dict() - ) # Store the source item for future import assignment - metadata['Hiero']['source_type'] = item.__class__.__name__ + metadata['hiero_source_type'] = item.__class__.__name__ marker = otio.schema.Marker( name=tag.name(), @@ -166,7 +185,7 @@ def create_otio_clip(track_item): clip = track_item.source() source_in = track_item.sourceIn() duration = track_item.sourceDuration() - fps = utils.get_rate(track_item) + fps = utils.get_rate(track_item) or self.project_fps name = track_item.name() media_reference = create_otio_reference(clip) @@ -212,29 +231,6 @@ def _create_otio_timeline(): ) -def _get_metadata_media_pool_item(media_pool_item): - data = dict() - data.update({k: v for k, v in media_pool_item.GetMetadata().items()}) - property = media_pool_item.GetClipProperty() or {} - for name, value in property.items(): - if "Resolution" in name and "" != value: - width, height = value.split("x") - data.update({ - "width": int(width), - "height": int(height) - }) - if "PAR" in name and "" != value: - try: - data.update({"pixelAspect": float(value)}) - except ValueError: - if "Square" in value: - data.update({"pixelAspect": float(1)}) - else: - data.update({"pixelAspect": float(1)}) - - return data - - def create_otio_track(track_type, track_name): return otio.schema.Track( name=track_name, @@ -271,6 +267,7 @@ def add_otio_metadata(otio_item, media_source, **kwargs): def create_otio_timeline(): + print(">>>>>> self.include_tags: {}".format(self.include_tags)) # get current timeline self.timeline = hiero.ui.activeSequence() self.project_fps = self.timeline.framerate().toFloat() @@ -278,12 +275,8 @@ def create_otio_timeline(): # convert timeline to otio otio_timeline = _create_otio_timeline() - # Add tags as markers - if self.include_tags: - create_otio_markers(otio_timeline, self.timeline) - # loop all defined track types - for track in self.hiero_sequence.items(): + for track in self.timeline.items(): # skip if track is disabled if not track.isEnabled(): continue diff --git a/openpype/hosts/hiero/otio/hiero_export__.py b/openpype/hosts/hiero/otio/hiero_export__.py deleted file mode 100644 index 8e19b26741..0000000000 --- a/openpype/hosts/hiero/otio/hiero_export__.py +++ /dev/null @@ -1,386 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -__author__ = "Daniel Flehner Heen" -__credits__ = ["Jakub Jezek", "Daniel Flehner Heen"] - -import os -import sys -import re -import hiero.core -import hiero.ui -import opentimelineio as otio - - -# build modul class -self = sys.modules[__name__] - -self.marker_color_map = { - "magenta": otio.schema.MarkerColor.MAGENTA, - "red": otio.schema.MarkerColor.RED, - "yellow": otio.schema.MarkerColor.YELLOW, - "green": otio.schema.MarkerColor.GREEN, - "cyan": otio.schema.MarkerColor.CYAN, - "blue": otio.schema.MarkerColor.BLUE, -} -self.hiero_sequence = None -self.include_tags = None - - -def get_rate(item): - if not hasattr(item, 'framerate'): - item = item.sequence() - - num, den = item.framerate().toRational() - rate = float(num) / float(den) - - if rate.is_integer(): - return rate - - return round(rate, 2) - - -def get_clip_ranges(trackitem): - # Get rate from source or sequence - if trackitem.source().mediaSource().hasVideo(): - rate_item = trackitem.source() - - else: - rate_item = trackitem.sequence() - - source_rate = get_rate(rate_item) - - # Reversed video/audio - if trackitem.playbackSpeed() < 0: - start = trackitem.sourceOut() - - else: - start = trackitem.sourceIn() - - source_start_time = otio.opentime.RationalTime( - start, - source_rate - ) - source_duration = otio.opentime.RationalTime( - trackitem.duration(), - source_rate - ) - - source_range = otio.opentime.TimeRange( - start_time=source_start_time, - duration=source_duration - ) - - hiero_clip = trackitem.source() - - available_range = None - if hiero_clip.mediaSource().isMediaPresent(): - start_time = otio.opentime.RationalTime( - hiero_clip.mediaSource().startTime(), - source_rate - ) - duration = otio.opentime.RationalTime( - hiero_clip.mediaSource().duration(), - source_rate - ) - available_range = otio.opentime.TimeRange( - start_time=start_time, - duration=duration - ) - - return source_range, available_range - - -def add_gap(trackitem, otio_track, prev_out): - gap_length = trackitem.timelineIn() - prev_out - if prev_out != 0: - gap_length -= 1 - - rate = get_rate(trackitem.sequence()) - gap = otio.opentime.TimeRange( - duration=otio.opentime.RationalTime( - gap_length, - rate - ) - ) - otio_gap = otio.schema.Gap(source_range=gap) - otio_track.append(otio_gap) - - -def get_marker_color(tag): - icon = tag.icon() - pat = r'icons:Tag(?P\w+)\.\w+' - - res = re.search(pat, icon) - if res: - color = res.groupdict().get('color') - if color.lower() in self.marker_color_map: - return self.marker_color_map[color.lower()] - - return otio.schema.MarkerColor.RED - - -def add_markers(hiero_item, otio_item): - for tag in hiero_item.tags(): - if not tag.visible(): - continue - - if tag.name() == 'Copy': - # Hiero adds this tag to a lot of clips - continue - - frame_rate = get_rate(hiero_item) - - marked_range = otio.opentime.TimeRange( - start_time=otio.opentime.RationalTime( - tag.inTime(), - frame_rate - ), - duration=otio.opentime.RationalTime( - int(tag.metadata().dict().get('tag.length', '0')), - frame_rate - ) - ) - - metadata = dict( - Hiero=tag.metadata().dict() - ) - # Store the source item for future import assignment - metadata['Hiero']['source_type'] = hiero_item.__class__.__name__ - - marker = otio.schema.Marker( - name=tag.name(), - color=get_marker_color(tag), - marked_range=marked_range, - metadata=metadata - ) - - otio_item.markers.append(marker) - - -def add_clip(trackitem, otio_track, itemindex): - hiero_clip = trackitem.source() - - # Add Gap if needed - if itemindex == 0: - prev_item = trackitem - - else: - prev_item = trackitem.parent().items()[itemindex - 1] - - clip_diff = trackitem.timelineIn() - prev_item.timelineOut() - - if itemindex == 0 and trackitem.timelineIn() > 0: - add_gap(trackitem, otio_track, 0) - - elif itemindex and clip_diff != 1: - add_gap(trackitem, otio_track, prev_item.timelineOut()) - - # Create Clip - source_range, available_range = get_clip_ranges(trackitem) - - otio_clip = otio.schema.Clip( - name=trackitem.name(), - source_range=source_range - ) - - media_reference = create_otio_reference(hiero_clip) - - otio_clip.media_reference = media_reference - - # Add Time Effects - playbackspeed = trackitem.playbackSpeed() - if playbackspeed != 1: - if playbackspeed == 0: - time_effect = otio.schema.FreezeFrame() - - else: - time_effect = otio.schema.LinearTimeWarp( - time_scalar=playbackspeed - ) - otio_clip.effects.append(time_effect) - - # Add tags as markers - if self.include_tags: - add_markers(trackitem, otio_clip) - add_markers(trackitem.source(), otio_clip) - - otio_track.append(otio_clip) - - # Add Transition if needed - if trackitem.inTransition() or trackitem.outTransition(): - add_transition(trackitem, otio_track) - -def _get_metadata(hiero_object): - metadata = hiero_object.metadata() - return {key: value for key, value in metadata.items()} - -def create_otio_reference(hiero_clip): - metadata = _get_metadata(hiero_clip) - mp_clip_property = media_pool_item.GetClipProperty() - path = mp_clip_property["File Path"] - reformat_path = utils.get_reformated_path(path, padded=True) - padding = utils.get_padding_from_path(path) - - if padding: - metadata.update({ - "isSequence": True, - "padding": padding - }) - - # get clip property regarding to type - mp_clip_property = media_pool_item.GetClipProperty() - fps = float(mp_clip_property["FPS"]) - if mp_clip_property["Type"] == "Video": - frame_start = int(mp_clip_property["Start"]) - frame_duration = int(mp_clip_property["Frames"]) - else: - audio_duration = str(mp_clip_property["Duration"]) - frame_start = 0 - frame_duration = int(utils.timecode_to_frames( - audio_duration, float(fps))) - - otio_ex_ref_item = None - - if padding: - # if it is file sequence try to create `ImageSequenceReference` - # the OTIO might not be compatible so return nothing and do it old way - try: - dirname, filename = os.path.split(path) - collection = clique.parse(filename, '{head}[{ranges}]{tail}') - padding_num = len(re.findall("(\\d+)(?=-)", filename).pop()) - otio_ex_ref_item = otio.schema.ImageSequenceReference( - target_url_base=dirname + os.sep, - name_prefix=collection.format("{head}"), - name_suffix=collection.format("{tail}"), - start_frame=frame_start, - frame_zero_padding=padding_num, - rate=fps, - available_range=create_otio_time_range( - frame_start, - frame_duration, - fps - ) - ) - except AttributeError: - pass - - if not otio_ex_ref_item: - # in case old OTIO or video file create `ExternalReference` - otio_ex_ref_item = otio.schema.ExternalReference( - target_url=reformat_path, - available_range=create_otio_time_range( - frame_start, - frame_duration, - fps - ) - ) - - # add metadata to otio item - add_otio_metadata(otio_ex_ref_item, hiero_clip, **metadata) - - return otio_ex_ref_item - - -def add_otio_metadata(otio_item, hiero_clip, **kwargs): - mp_metadata = hiero_clip.GetMetadata() - # add additional metadata from kwargs - if kwargs: - mp_metadata.update(kwargs) - - # add metadata to otio item metadata - for key, value in mp_metadata.items(): - otio_item.metadata.update({key: value}) - -def add_transition(trackitem, otio_track): - transitions = [] - - if trackitem.inTransition(): - if trackitem.inTransition().alignment().name == 'kFadeIn': - transitions.append(trackitem.inTransition()) - - if trackitem.outTransition(): - transitions.append(trackitem.outTransition()) - - for transition in transitions: - alignment = transition.alignment().name - - if alignment == 'kFadeIn': - in_offset_frames = 0 - out_offset_frames = ( - transition.timelineOut() - transition.timelineIn() - ) + 1 - - elif alignment == 'kFadeOut': - in_offset_frames = ( - trackitem.timelineOut() - transition.timelineIn() - ) + 1 - out_offset_frames = 0 - - elif alignment == 'kDissolve': - in_offset_frames = ( - transition.inTrackItem().timelineOut() - - transition.timelineIn() - ) - out_offset_frames = ( - transition.timelineOut() - - transition.outTrackItem().timelineIn() - ) - - else: - # kUnknown transition is ignored - continue - - rate = trackitem.source().framerate().toFloat() - in_time = otio.opentime.RationalTime(in_offset_frames, rate) - out_time = otio.opentime.RationalTime(out_offset_frames, rate) - - otio_transition = otio.schema.Transition( - name=alignment, # Consider placing Hiero name in metadata - transition_type=otio.schema.TransitionTypes.SMPTE_Dissolve, - in_offset=in_time, - out_offset=out_time - ) - - if alignment == 'kFadeIn': - otio_track.insert(-1, otio_transition) - - else: - otio_track.append(otio_transition) - - -def add_tracks(): - for track in self.hiero_sequence.items(): - if isinstance(track, hiero.core.AudioTrack): - kind = otio.schema.TrackKind.Audio - - else: - kind = otio.schema.TrackKind.Video - - otio_track = otio.schema.Track(name=track.name(), kind=kind) - - for itemindex, trackitem in enumerate(track): - if isinstance(trackitem.source(), hiero.core.Clip): - add_clip(trackitem, otio_track, itemindex) - - self.otio_timeline.tracks.append(otio_track) - - # Add tags as markers - if self.include_tags: - add_markers(self.hiero_sequence, self.otio_timeline.tracks) - - -def create_OTIO(sequence=None): - self.hiero_sequence = sequence or hiero.ui.activeSequence() - self.otio_timeline = otio.schema.Timeline() - - # Set global start time based on sequence - self.otio_timeline.global_start_time = otio.opentime.RationalTime( - self.hiero_sequence.timecodeStart(), - self.hiero_sequence.framerate().toFloat() - ) - self.otio_timeline.name = self.hiero_sequence.name() - - add_tracks() - - return self.otio_timeline diff --git a/openpype/hosts/hiero/otio/hiero_import.py b/openpype/hosts/hiero/otio/hiero_import.py index c5c72984bc..db9ebdfc90 100644 --- a/openpype/hosts/hiero/otio/hiero_import.py +++ b/openpype/hosts/hiero/otio/hiero_import.py @@ -19,6 +19,7 @@ except ImportError: import opentimelineio as otio +_otio_old = False def inform(messages): if isinstance(messages, type('')): @@ -180,14 +181,23 @@ def prep_url(url_in): def create_offline_mediasource(otio_clip, path=None): + global _otio_old + hiero_rate = hiero.core.TimeBase( otio_clip.source_range.start_time.rate ) - legal_media_refs = ( - otio.schema.ExternalReference, - otio.schema.ImageSequenceReference - ) + try: + legal_media_refs = ( + otio.schema.ExternalReference, + otio.schema.ImageSequenceReference + ) + except AttributeError: + _otio_old = True + legal_media_refs = ( + otio.schema.ExternalReference + ) + if isinstance(otio_clip.media_reference, legal_media_refs): source_range = otio_clip.available_range() @@ -331,9 +341,10 @@ def create_clip(otio_clip, tagsbin, sequencebin): url = prep_url(otio_media.target_url) media = hiero.core.MediaSource(url) - elif isinstance(otio_media, otio.schema.ImageSequenceReference): - url = prep_url(otio_media.abstract_target_url('#')) - media = hiero.core.MediaSource(url) + elif not _otio_old: + if isinstance(otio_media, otio.schema.ImageSequenceReference): + url = prep_url(otio_media.abstract_target_url('#')) + media = hiero.core.MediaSource(url) if media is None or media.isOffline(): media = create_offline_mediasource(otio_clip, url) diff --git a/openpype/hosts/hiero/otio/utils.py b/openpype/hosts/hiero/otio/utils.py index 12f963fe97..f882a5d1f2 100644 --- a/openpype/hosts/hiero/otio/utils.py +++ b/openpype/hosts/hiero/otio/utils.py @@ -17,7 +17,7 @@ def frames_to_secons(frames, framerate): return otio.opentime.to_seconds(rt) -def get_reformated_path(path, padded=True, first=False): +def get_reformated_path(path, padded=True): """ Return fixed python expression path @@ -31,19 +31,12 @@ def get_reformated_path(path, padded=True, first=False): get_reformated_path("plate.[0001-1008].exr") > plate.%04d.exr """ - num_pattern = r"(\[\d+\-\d+\])" - padding_pattern = r"(\d+)(?=-)" - first_frame_pattern = re.compile(r"\[(\d+)\-\d+\]") - - if "[" in path: - padding = len(re.findall(padding_pattern, path).pop()) + if "%" in path: + padding_pattern = r"(\d+)" + padding = int(re.findall(padding_pattern, path).pop()) + num_pattern = r"(%\d+d)" if padded: - path = re.sub(num_pattern, f"%0{padding}d", path) - elif first: - first_frame = re.findall(first_frame_pattern, path, flags=0) - if len(first_frame) >= 1: - first_frame = first_frame[0] - path = re.sub(num_pattern, first_frame, path) + path = re.sub(num_pattern, "%0{}d".format(padding), path) else: path = re.sub(num_pattern, "%d", path) return path @@ -72,7 +65,7 @@ def get_padding_from_path(path): def get_rate(item): if not hasattr(item, 'framerate'): - item = item.sequence() + return None num, den = item.framerate().toRational() rate = float(num) / float(den) diff --git a/openpype/hosts/hiero/startup/Python/Startup/otioexporter/OTIOExportTask.py b/openpype/hosts/hiero/startup/Python/Startup/otioexporter/OTIOExportTask.py index 51265a3daf..7e1a8df2dc 100644 --- a/openpype/hosts/hiero/startup/Python/Startup/otioexporter/OTIOExportTask.py +++ b/openpype/hosts/hiero/startup/Python/Startup/otioexporter/OTIOExportTask.py @@ -60,7 +60,7 @@ class OTIOExportPreset(hiero.core.TaskPresetBase): """Initialise presets to default values""" hiero.core.TaskPresetBase.__init__(self, OTIOExportTask, name) - self.properties()["includeTags"] = True + self.properties()["includeTags"] = hiero_export.include_tags = True self.properties().update(properties) def supportedItems(self): diff --git a/openpype/hosts/hiero/startup/Python/Startup/otioexporter/OTIOExportUI.py b/openpype/hosts/hiero/startup/Python/Startup/otioexporter/OTIOExportUI.py index 7f11de074d..9b83eefedf 100644 --- a/openpype/hosts/hiero/startup/Python/Startup/otioexporter/OTIOExportUI.py +++ b/openpype/hosts/hiero/startup/Python/Startup/otioexporter/OTIOExportUI.py @@ -20,8 +20,7 @@ except ImportError: FormLayout = QFormLayout # lint:ok -from pype.hosts.hiero.otio import hiero_export - +from openpype.hosts.hiero.otio import hiero_export class OTIOExportUI(hiero.ui.TaskUIBase): def __init__(self, preset): @@ -35,7 +34,7 @@ class OTIOExportUI(hiero.ui.TaskUIBase): def includeMarkersCheckboxChanged(self, state): # Slot to handle change of checkbox state - hiero_export.hiero_sequence = state == QtCore.Qt.Checked + hiero_export.include_tags = state == QtCore.Qt.Checked def populateUI(self, widget, exportTemplate): layout = widget.layout() diff --git a/openpype/hosts/hiero/startup/Python/StartupUI/otioimporter/__init__.py b/openpype/hosts/hiero/startup/Python/StartupUI/otioimporter/__init__.py index a778d558b2..0f0a643909 100644 --- a/openpype/hosts/hiero/startup/Python/StartupUI/otioimporter/__init__.py +++ b/openpype/hosts/hiero/startup/Python/StartupUI/otioimporter/__init__.py @@ -9,7 +9,7 @@ import hiero.core import PySide2.QtWidgets as qw -from pype.hosts.hiero.otio.hiero_import import load_otio +from openpype.hosts.hiero.otio.hiero_import import load_otio class OTIOProjectSelect(qw.QDialog): From 61eb1cf20063f927ea73b686fe8ea7d5d48b7950 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 8 Apr 2021 21:04:29 +0200 Subject: [PATCH 017/180] hiero: hound suggestions --- openpype/hosts/hiero/otio/hiero_export.py | 1 - openpype/hosts/hiero/otio/hiero_import.py | 33 +++++++++++++---------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/openpype/hosts/hiero/otio/hiero_export.py b/openpype/hosts/hiero/otio/hiero_export.py index b2847ff6cb..f95dc7a025 100644 --- a/openpype/hosts/hiero/otio/hiero_export.py +++ b/openpype/hosts/hiero/otio/hiero_export.py @@ -9,7 +9,6 @@ import opentimelineio as otio from . import utils import hiero.core import hiero.ui -reload(utils) self = sys.modules[__name__] self.track_types = { diff --git a/openpype/hosts/hiero/otio/hiero_import.py b/openpype/hosts/hiero/otio/hiero_import.py index db9ebdfc90..b5040a32c7 100644 --- a/openpype/hosts/hiero/otio/hiero_import.py +++ b/openpype/hosts/hiero/otio/hiero_import.py @@ -21,6 +21,7 @@ import opentimelineio as otio _otio_old = False + def inform(messages): if isinstance(messages, type('')): messages = [messages] @@ -114,10 +115,9 @@ def apply_transition(otio_track, otio_item, track): # Catch error raised if transition is bigger than TrackItem source except RuntimeError as e: transition = None - warning = \ - 'Unable to apply transition "{t.name}": {e} ' \ - 'Ignoring the transition.' \ - .format(t=otio_item, e=e.message) + warning = ( + "Unable to apply transition \"{t.name}\": {e} " + "Ignoring the transition.").format(t=otio_item, e=e.message) elif transition_type == 'fade_in': transition_func = getattr( @@ -384,7 +384,8 @@ def create_trackitem(playhead, track, otio_clip, clip): time_scalar = effect.time_scalar # Only reverse effect can be applied here if abs(time_scalar) == 1.: - trackitem.setPlaybackSpeed(trackitem.playbackSpeed() * time_scalar) + trackitem.setPlaybackSpeed( + trackitem.playbackSpeed() * time_scalar) elif isinstance(effect, otio.schema.FreezeFrame): # For freeze frame, playback speed must be set after range @@ -433,7 +434,8 @@ def create_trackitem(playhead, track, otio_clip, clip): return trackitem -def build_sequence(otio_timeline, project=None, sequence=None, track_kind=None): +def build_sequence( + otio_timeline, project=None, sequence=None, track_kind=None): if project is None: if sequence: project = sequence.project() @@ -449,11 +451,13 @@ def build_sequence(otio_timeline, project=None, sequence=None, track_kind=None): sequence = hiero.core.Sequence(otio_timeline.name or 'OTIOSequence') # Set sequence settings from otio timeline if available - if hasattr(otio_timeline, 'global_start_time'): - if otio_timeline.global_start_time: - start_time = otio_timeline.global_start_time - sequence.setFramerate(start_time.rate) - sequence.setTimecodeStart(start_time.value) + if ( + hasattr(otio_timeline, 'global_start_time') + and otio_timeline.global_start_time + ): + start_time = otio_timeline.global_start_time + sequence.setFramerate(start_time.rate) + sequence.setTimecodeStart(start_time.value) # Create a Bin to hold clips projectbin.addItem(hiero.core.BinItem(sequence)) @@ -485,7 +489,7 @@ def build_sequence(otio_timeline, project=None, sequence=None, track_kind=None): sequence.addTrack(track) # iterate over items in track - for itemnum, otio_clip in enumerate(otio_track): + for _itemnum, otio_clip in enumerate(otio_track): if isinstance(otio_clip, (otio.schema.Track, otio.schema.Stack)): inform('Nested sequences/tracks are created separately.') @@ -529,9 +533,10 @@ def build_sequence(otio_timeline, project=None, sequence=None, track_kind=None): playhead += otio_clip.source_range.duration.value # Apply transitions we stored earlier now that all clips are present - warnings = list() + warnings = [] for otio_track, otio_item in _transitions: - # Catch warnings form transitions in case of unsupported transitions + # Catch warnings form transitions in case + # of unsupported transitions warning = apply_transition(otio_track, otio_item, track) if warning: warnings.append(warning) From c5e2fcfb1ba51602c7ac2b083bff6493f67e0fda Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 9 Apr 2021 12:26:57 +0200 Subject: [PATCH 018/180] Hiero: transition to openpype and add arg to api --- openpype/hosts/hiero/api/__init__.py | 2 ++ openpype/hosts/hiero/api/lib.py | 10 +++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/hiero/api/__init__.py b/openpype/hosts/hiero/api/__init__.py index fcb1d50ea8..8d0105ae5f 100644 --- a/openpype/hosts/hiero/api/__init__.py +++ b/openpype/hosts/hiero/api/__init__.py @@ -22,6 +22,7 @@ from .pipeline import ( ) from .lib import ( + pype_tag_name, get_track_items, get_current_project, get_current_sequence, @@ -73,6 +74,7 @@ __all__ = [ "work_root", # Lib functions + "pype_tag_name", "get_track_items", "get_current_project", "get_current_sequence", diff --git a/openpype/hosts/hiero/api/lib.py b/openpype/hosts/hiero/api/lib.py index 286aba13e9..d7bac7a3cc 100644 --- a/openpype/hosts/hiero/api/lib.py +++ b/openpype/hosts/hiero/api/lib.py @@ -30,9 +30,9 @@ self = sys.modules[__name__] self._has_been_setup = False self._has_menu = False self._registered_gui = None -self.pype_tag_name = "Pype Data" -self.default_sequence_name = "PypeSequence" -self.default_bin_name = "PypeBin" +self.pype_tag_name = "openpypeData" +self.default_sequence_name = "openpypeSequence" +self.default_bin_name = "openpypeBin" AVALON_CONFIG = os.getenv("AVALON_CONFIG", "pype") @@ -165,7 +165,7 @@ def get_track_items( # check if any collected track items are # `core.Hiero.Python.TrackItem` instance if track_items: - any_track_item = track_items.pop() + any_track_item = track_items[0] if not isinstance(any_track_item, hiero.core.TrackItem): selected_items = [] @@ -252,7 +252,7 @@ def set_track_item_pype_tag(track_item, data=None): # basic Tag's attribute tag_data = { "editable": "0", - "note": "Pype data holder", + "note": "OpenPype data container", "icon": "openpype_icon.png", "metadata": {k: v for k, v in data.items()} } From cd5daa030ddea393960056aa4839f96732640de9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 9 Apr 2021 12:27:31 +0200 Subject: [PATCH 019/180] Hiero: adding openpype metadata resolution and colorspace to otio --- openpype/hosts/hiero/otio/hiero_export.py | 42 ++++++++++++++++++++--- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/hiero/otio/hiero_export.py b/openpype/hosts/hiero/otio/hiero_export.py index f95dc7a025..93ddee7b38 100644 --- a/openpype/hosts/hiero/otio/hiero_export.py +++ b/openpype/hosts/hiero/otio/hiero_export.py @@ -5,6 +5,7 @@ import os import re import sys import ast +from compiler.ast import flatten import opentimelineio as otio from . import utils import hiero.core @@ -25,7 +26,20 @@ self.marker_color_map = { "blue": otio.schema.MarkerColor.BLUE, } self.timeline = None -self.include_tags = None +self.include_tags = True + + +def get_current_hiero_project(remove_untitled=False): + projects = flatten(hiero.core.projects()) + if not remove_untitled: + return next(iter(projects)) + + # if remove_untitled + for proj in projects: + if "Untitled" in proj.name(): + proj.close() + else: + return proj def create_otio_rational_time(frame, fps): @@ -73,9 +87,10 @@ def create_otio_reference(clip): # add resolution metadata metadata.update({ - "width": int(media_source.width()), - "height": int(media_source.height()), - "pixelAspect": float(media_source.pixelAspect()) + "openpype.source.colourtransform": clip.sourceMediaColourTransform(), + "openpype.source.width": int(media_source.width()), + "openpype.source.height": int(media_source.height()), + "openpype.source.pixelAspect": float(media_source.pixelAspect()) }) otio_ex_ref_item = None @@ -219,7 +234,25 @@ def create_otio_gap(gap_start, clip_start, tl_start_frame, fps): def _create_otio_timeline(): + project = get_current_hiero_project(remove_untitled=False) metadata = _get_metadata(self.timeline) + + metadata.update({ + "openpype.timeline.width": int(self.timeline.format().width()), + "openpype.timeline.height": int(self.timeline.format().height()), + "openpype.timeline.pixelAspect": int(self.timeline.format().pixelAspect()), # noqa + "openpype.project.useOCIOEnvironmentOverride": project.useOCIOEnvironmentOverride(), # noqa + "openpype.project.lutSetting16Bit": project.lutSetting16Bit(), + "openpype.project.lutSetting8Bit": project.lutSetting8Bit(), + "openpype.project.lutSettingFloat": project.lutSettingFloat(), + "openpype.project.lutSettingLog": project.lutSettingLog(), + "openpype.project.lutSettingViewer": project.lutSettingViewer(), + "openpype.project.lutSettingWorkingSpace": project.lutSettingWorkingSpace(), # noqa + "openpype.project.lutUseOCIOForExport": project.lutUseOCIOForExport(), + "openpype.project.ocioConfigName": project.ocioConfigName(), + "openpype.project.ocioConfigPath": project.ocioConfigPath() + }) + start_time = create_otio_rational_time( self.timeline.timecodeStart(), self.project_fps) @@ -266,7 +299,6 @@ def add_otio_metadata(otio_item, media_source, **kwargs): def create_otio_timeline(): - print(">>>>>> self.include_tags: {}".format(self.include_tags)) # get current timeline self.timeline = hiero.ui.activeSequence() self.project_fps = self.timeline.framerate().toFloat() From f419ed655ab0fbc76bbe8965d65c718dff2a7543 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 9 Apr 2021 12:28:08 +0200 Subject: [PATCH 020/180] Hiero: adding new precollect otio plugins --- .../hiero/plugins/publish/extract_workfile.py | 50 +++ .../plugins/publish/precollect_instances.py | 316 ++++++++---------- .../plugins/publish/precollect_workfile.py | 76 ++--- 3 files changed, 210 insertions(+), 232 deletions(-) create mode 100644 openpype/hosts/hiero/plugins/publish/extract_workfile.py diff --git a/openpype/hosts/hiero/plugins/publish/extract_workfile.py b/openpype/hosts/hiero/plugins/publish/extract_workfile.py new file mode 100644 index 0000000000..e3d60465a2 --- /dev/null +++ b/openpype/hosts/hiero/plugins/publish/extract_workfile.py @@ -0,0 +1,50 @@ +import os +import pyblish.api +import openpype.api +from openpype.hosts import resolve + + +class ExtractWorkfile(openpype.api.Extractor): + """ + Extractor export DRP workfile file representation + """ + + label = "Extract Workfile" + order = pyblish.api.ExtractorOrder + families = ["workfile"] + hosts = ["resolve"] + + def process(self, instance): + # create representation data + if "representations" not in instance.data: + instance.data["representations"] = [] + + name = instance.data["name"] + project = instance.context.data["activeProject"] + staging_dir = self.staging_dir(instance) + + resolve_workfile_ext = ".drp" + drp_file_name = name + resolve_workfile_ext + drp_file_path = os.path.normpath( + os.path.join(staging_dir, drp_file_name)) + + # write out the drp workfile + resolve.get_project_manager().ExportProject( + project.GetName(), drp_file_path) + + # create drp workfile representation + representation_drp = { + 'name': resolve_workfile_ext[1:], + 'ext': resolve_workfile_ext[1:], + 'files': drp_file_name, + "stagingDir": staging_dir, + } + + instance.data["representations"].append(representation_drp) + + # add sourcePath attribute to instance + if not instance.data.get("sourcePath"): + instance.data["sourcePath"] = drp_file_path + + self.log.info("Added Resolve file representation: {}".format( + representation_drp)) diff --git a/openpype/hosts/hiero/plugins/publish/precollect_instances.py b/openpype/hosts/hiero/plugins/publish/precollect_instances.py index 2b769afee1..242cfed254 100644 --- a/openpype/hosts/hiero/plugins/publish/precollect_instances.py +++ b/openpype/hosts/hiero/plugins/publish/precollect_instances.py @@ -1,224 +1,178 @@ -from compiler.ast import flatten -from pyblish import api +import pyblish +import openpype from openpype.hosts.hiero import api as phiero -import hiero -# from openpype.hosts.hiero.api import lib -# reload(lib) -# reload(phiero) +from openpype.hosts.hiero.api import lib +from openpype.hosts.hiero.otio import hiero_export + +# # developer reload modules +from pprint import pformat +reload(lib) +reload(phiero) -class PreCollectInstances(api.ContextPlugin): +class PrecollectInstances(pyblish.api.ContextPlugin): """Collect all Track items selection.""" - order = api.CollectorOrder - 0.509 - label = "Pre-collect Instances" + order = pyblish.api.CollectorOrder - 0.59 + label = "Precollect Instances" hosts = ["hiero"] - def process(self, context): - track_items = phiero.get_track_items( - selected=True, check_tagged=True, check_enabled=True) - # only return enabled track items - if not track_items: - track_items = phiero.get_track_items( - check_enabled=True, check_tagged=True) - # get sequence and video tracks - sequence = context.data["activeSequence"] - tracks = sequence.videoTracks() - - # add collection to context - tracks_effect_items = self.collect_sub_track_items(tracks) - - context.data["tracksEffectItems"] = tracks_effect_items - + otio_timeline = context.data["otioTimeline"] + selected_timeline_items = phiero.get_track_items( + selected=True, check_enabled=True, check_tagged=True) self.log.info( - "Processing enabled track items: {}".format(len(track_items))) + "Processing enabled track items: {}".format( + selected_timeline_items)) - for _ti in track_items: - data = {} - clip = _ti.source() + for track_item in selected_timeline_items: - # get clips subtracks and anotations - annotations = self.clip_annotations(clip) - subtracks = self.clip_subtrack(_ti) - self.log.debug("Annotations: {}".format(annotations)) - self.log.debug(">> Subtracks: {}".format(subtracks)) + data = dict() - # get pype tag data - tag_parsed_data = phiero.get_track_item_pype_data(_ti) - # self.log.debug(pformat(tag_parsed_data)) + # get openpype tag data + tag_data = phiero.get_track_item_pype_data(track_item) + self.log.debug("__ tag_data: {}".format(pformat(tag_data))) - if not tag_parsed_data: + if not tag_data: continue - if tag_parsed_data.get("id") != "pyblish.avalon.instance": + if tag_data.get("id") != "pyblish.avalon.instance": continue + + clip = track_item.source() + # add tag data to instance data data.update({ - k: v for k, v in tag_parsed_data.items() + k: v for k, v in tag_data.items() if k not in ("id", "applieswhole", "label") }) - asset = tag_parsed_data["asset"] - subset = tag_parsed_data["subset"] - review_track = tag_parsed_data.get("reviewTrack") - hiero_track = tag_parsed_data.get("heroTrack") - audio = tag_parsed_data.get("audio") - - # remove audio attribute from data - data.pop("audio") + asset = tag_data["asset"] + subset = tag_data["subset"] # insert family into families - family = tag_parsed_data["family"] - families = [str(f) for f in tag_parsed_data["families"]] + family = tag_data["family"] + families = [str(f) for f in tag_data["families"]] families.insert(0, str(family)) - track = _ti.parent() - media_source = _ti.source().mediaSource() - source_path = media_source.firstpath() - file_head = media_source.filenameHead() - file_info = media_source.fileinfos().pop() - source_first_frame = int(file_info.startFrame()) - - # apply only for review and master track instance - if review_track and hiero_track: - families += ["review", "ftrack"] - data.update({ "name": "{} {} {}".format(asset, subset, families), "asset": asset, - "item": _ti, + "item": track_item, "families": families, - - # tags - "tags": _ti.tags(), - - # track item attributes - "track": track.name(), - "trackItem": track, - "reviewTrack": review_track, - - # version data - "versionData": { - "colorspace": _ti.sourceMediaColourTransform() - }, - - # source attribute - "source": source_path, - "sourceMedia": media_source, - "sourcePath": source_path, - "sourceFileHead": file_head, - "sourceFirst": source_first_frame, - - # clip's effect - "clipEffectItems": subtracks + "publish": tag_data["publish"], + "fps": context.data["fps"] }) + # otio clip data + otio_data = self.get_otio_clip_instance_data( + otio_timeline, track_item) or {} + self.log.debug("__ otio_data: {}".format(pformat(otio_data))) + data.update(otio_data) + self.log.debug("__ data: {}".format(pformat(data))) + + # add resolution + self.get_resolution_to_data(data, context) + + # create instance instance = context.create_instance(**data) - self.log.info("Creating instance.data: {}".format(instance.data)) + # create shot instance for shot attributes create/update + self.create_shot_instance(context, **data) - if audio: - a_data = dict() + self.log.info("Creating instance: {}".format(instance)) + self.log.debug( + "_ instance.data: {}".format(pformat(instance.data))) - # add tag data to instance data - a_data.update({ - k: v for k, v in tag_parsed_data.items() - if k not in ("id", "applieswhole", "label") - }) + def get_resolution_to_data(self, data, context): + assert data.get("otioClip"), "Missing `otioClip` data" - # create main attributes - subset = "audioMain" - family = "audio" - families = ["clip", "ftrack"] - families.insert(0, str(family)) + # solve source resolution option + if data.get("sourceResolution", None): + otio_clip_metadata = data[ + "otioClip"].media_reference.metadata + data.update({ + "resolutionWidth": otio_clip_metadata[ + "openpype.source.width"], + "resolutionHeight": otio_clip_metadata[ + "openpype.source.height"], + "pixelAspect": otio_clip_metadata[ + "openpype.source.pixelAspect"] + }) + else: + otio_tl_metadata = context.data["otioTimeline"].metadata + data.update({ + "resolutionWidth": otio_tl_metadata["openpype.timeline.width"], + "resolutionHeight": otio_tl_metadata["openpype.timeline.height"], + "pixelAspect": otio_tl_metadata["openpype.timeline.pixelAspect"] + }) - name = "{} {} {}".format(asset, subset, families) + def create_shot_instance(self, context, **data): + master_layer = data.get("heroTrack") + hierarchy_data = data.get("hierarchyData") - a_data.update({ - "name": name, - "subset": subset, - "asset": asset, - "family": family, - "families": families, - "item": _ti, + if not master_layer: + return - # tags - "tags": _ti.tags(), - }) + if not hierarchy_data: + return - a_instance = context.create_instance(**a_data) - self.log.info("Creating audio instance: {}".format(a_instance)) + asset = data["asset"] + subset = "shotMain" + + # insert family into families + family = "shot" + + data.update({ + "name": "{} {} {}".format(asset, subset, family), + "subset": subset, + "asset": asset, + "family": family, + "families": [] + }) + + context.create_instance(**data) + + def get_otio_clip_instance_data(self, otio_timeline, track_item): + """ + Return otio objects for timeline, track and clip + + Args: + timeline_item_data (dict): timeline_item_data from list returned by + resolve.get_current_timeline_items() + otio_timeline (otio.schema.Timeline): otio object + + Returns: + dict: otio clip object + + """ + track_name = track_item.parent().name() + timeline_range = self.create_otio_time_range_from_timeline_item_data( + track_item) + for otio_clip in otio_timeline.each_clip(): + track_name = otio_clip.parent().name + parent_range = otio_clip.range_in_parent() + if track_name not in track_name: + continue + if otio_clip.name not in track_item.name(): + continue + if openpype.lib.is_overlapping_otio_ranges( + parent_range, timeline_range, strict=True): + + # add pypedata marker to otio_clip metadata + for marker in otio_clip.markers: + if phiero.pype_tag_name in marker.name: + otio_clip.metadata.update(marker.metadata) + return {"otioClip": otio_clip} + + return None @staticmethod - def clip_annotations(clip): - """ - Returns list of Clip's hiero.core.Annotation - """ - annotations = [] - subTrackItems = flatten(clip.subTrackItems()) - annotations += [item for item in subTrackItems if isinstance( - item, hiero.core.Annotation)] - return annotations + def create_otio_time_range_from_timeline_item_data(track_item): + timeline = phiero.get_current_sequence() + frame_start = int(track_item.timelineIn()) + frame_duration = int(track_item.sourceDuration()) + fps = timeline.framerate().toFloat() - @staticmethod - def clip_subtrack(clip): - """ - Returns list of Clip's hiero.core.SubTrackItem - """ - subtracks = [] - subTrackItems = flatten(clip.parent().subTrackItems()) - for item in subTrackItems: - # avoid all anotation - if isinstance(item, hiero.core.Annotation): - continue - # # avoid all not anaibled - if not item.isEnabled(): - continue - subtracks.append(item) - return subtracks - - @staticmethod - def collect_sub_track_items(tracks): - """ - Returns dictionary with track index as key and list of subtracks - """ - # collect all subtrack items - sub_track_items = dict() - for track in tracks: - items = track.items() - - # skip if no clips on track > need track with effect only - if items: - continue - - # skip all disabled tracks - if not track.isEnabled(): - continue - - track_index = track.trackIndex() - _sub_track_items = flatten(track.subTrackItems()) - - # continue only if any subtrack items are collected - if len(_sub_track_items) < 1: - continue - - enabled_sti = list() - # loop all found subtrack items and check if they are enabled - for _sti in _sub_track_items: - # checking if not enabled - if not _sti.isEnabled(): - continue - if isinstance(_sti, hiero.core.Annotation): - continue - # collect the subtrack item - enabled_sti.append(_sti) - - # continue only if any subtrack items are collected - if len(enabled_sti) < 1: - continue - - # add collection of subtrackitems to dict - sub_track_items[track_index] = enabled_sti - - return sub_track_items + return hiero_export.create_otio_time_range( + frame_start, frame_duration, fps) diff --git a/openpype/hosts/hiero/plugins/publish/precollect_workfile.py b/openpype/hosts/hiero/plugins/publish/precollect_workfile.py index ef7d07421b..22201cafe3 100644 --- a/openpype/hosts/hiero/plugins/publish/precollect_workfile.py +++ b/openpype/hosts/hiero/plugins/publish/precollect_workfile.py @@ -1,74 +1,48 @@ -import os import pyblish.api +import hiero.ui from openpype.hosts.hiero import api as phiero from avalon import api as avalon +from pprint import pformat +from openpype.hosts.hiero.otio import hiero_export -class PreCollectWorkfile(pyblish.api.ContextPlugin): +class PrecollectWorkfile(pyblish.api.ContextPlugin): """Inject the current working file into context""" - label = "Pre-collect Workfile" - order = pyblish.api.CollectorOrder - 0.51 + label = "Precollect Workfile" + order = pyblish.api.CollectorOrder - 0.6 def process(self, context): + asset = avalon.Session["AVALON_ASSET"] subset = "workfile" - project = phiero.get_current_project() - active_sequence = phiero.get_current_sequence() - video_tracks = active_sequence.videoTracks() - audio_tracks = active_sequence.audioTracks() - current_file = project.path() - staging_dir = os.path.dirname(current_file) - base_name = os.path.basename(current_file) + active_timeline = hiero.ui.activeSequence() + fps = active_timeline.framerate().toFloat() - # get workfile's colorspace properties - _clrs = {} - _clrs["useOCIOEnvironmentOverride"] = project.useOCIOEnvironmentOverride() # noqa - _clrs["lutSetting16Bit"] = project.lutSetting16Bit() - _clrs["lutSetting8Bit"] = project.lutSetting8Bit() - _clrs["lutSettingFloat"] = project.lutSettingFloat() - _clrs["lutSettingLog"] = project.lutSettingLog() - _clrs["lutSettingViewer"] = project.lutSettingViewer() - _clrs["lutSettingWorkingSpace"] = project.lutSettingWorkingSpace() - _clrs["lutUseOCIOForExport"] = project.lutUseOCIOForExport() - _clrs["ocioConfigName"] = project.ocioConfigName() - _clrs["ocioConfigPath"] = project.ocioConfigPath() - - # set main project attributes to context - context.data["activeProject"] = project - context.data["activeSequence"] = active_sequence - context.data["videoTracks"] = video_tracks - context.data["audioTracks"] = audio_tracks - context.data["currentFile"] = current_file - context.data["colorspace"] = _clrs - - self.log.info("currentFile: {}".format(current_file)) - - # creating workfile representation - representation = { - 'name': 'hrox', - 'ext': 'hrox', - 'files': base_name, - "stagingDir": staging_dir, - } + # adding otio timeline to context + otio_timeline = hiero_export.create_otio_timeline() instance_data = { "name": "{}_{}".format(asset, subset), "asset": asset, "subset": "{}{}".format(asset, subset.capitalize()), "item": project, - "family": "workfile", - - # version data - "versionData": { - "colorspace": _clrs - }, - - # source attribute - "sourcePath": current_file, - "representations": [representation] + "family": "workfile" } + # create instance with workfile instance = context.create_instance(**instance_data) + + # update context with main project attributes + context_data = { + "activeProject": project, + "otioTimeline": otio_timeline, + "currentFile": project.path(), + "fps": fps, + } + context.data.update(context_data) + self.log.info("Creating instance: {}".format(instance)) + self.log.debug("__ instance.data: {}".format(pformat(instance.data))) + self.log.debug("__ context_data: {}".format(pformat(context_data))) From a9f9757c08a1bad0cdd365ff053357fbcffd8eb9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 9 Apr 2021 12:29:15 +0200 Subject: [PATCH 021/180] Hiero: moving old workflow plugins to temp folder --- .../{publish => publish_old_workflow}/collect_assetbuilds.py | 0 .../{publish => publish_old_workflow}/collect_clip_resolution.py | 0 .../{publish => publish_old_workflow}/collect_frame_ranges.py | 0 .../collect_hierarchy_context.py | 0 .../{publish => publish_old_workflow}/collect_host_version.py | 0 .../plugins/{publish => publish_old_workflow}/collect_plates.py | 0 .../plugins/{publish => publish_old_workflow}/collect_review.py | 0 .../{publish => publish_old_workflow}/collect_tag_tasks.py | 0 .../plugins/{publish => publish_old_workflow}/extract_audio.py | 0 .../{publish => publish_old_workflow}/extract_clip_effects.py | 0 .../extract_review_preparation.py | 0 .../{publish => publish_old_workflow}/precollect_clip_effects.py | 0 .../plugins/{publish => publish_old_workflow}/validate_audio.py | 0 .../{publish => publish_old_workflow}/validate_hierarchy.py | 0 .../plugins/{publish => publish_old_workflow}/validate_names.py | 0 .../{publish => publish_old_workflow}/version_up_workfile.py | 0 16 files changed, 0 insertions(+), 0 deletions(-) rename openpype/hosts/hiero/plugins/{publish => publish_old_workflow}/collect_assetbuilds.py (100%) rename openpype/hosts/hiero/plugins/{publish => publish_old_workflow}/collect_clip_resolution.py (100%) rename openpype/hosts/hiero/plugins/{publish => publish_old_workflow}/collect_frame_ranges.py (100%) rename openpype/hosts/hiero/plugins/{publish => publish_old_workflow}/collect_hierarchy_context.py (100%) rename openpype/hosts/hiero/plugins/{publish => publish_old_workflow}/collect_host_version.py (100%) rename openpype/hosts/hiero/plugins/{publish => publish_old_workflow}/collect_plates.py (100%) rename openpype/hosts/hiero/plugins/{publish => publish_old_workflow}/collect_review.py (100%) rename openpype/hosts/hiero/plugins/{publish => publish_old_workflow}/collect_tag_tasks.py (100%) rename openpype/hosts/hiero/plugins/{publish => publish_old_workflow}/extract_audio.py (100%) rename openpype/hosts/hiero/plugins/{publish => publish_old_workflow}/extract_clip_effects.py (100%) rename openpype/hosts/hiero/plugins/{publish => publish_old_workflow}/extract_review_preparation.py (100%) rename openpype/hosts/hiero/plugins/{publish => publish_old_workflow}/precollect_clip_effects.py (100%) rename openpype/hosts/hiero/plugins/{publish => publish_old_workflow}/validate_audio.py (100%) rename openpype/hosts/hiero/plugins/{publish => publish_old_workflow}/validate_hierarchy.py (100%) rename openpype/hosts/hiero/plugins/{publish => publish_old_workflow}/validate_names.py (100%) rename openpype/hosts/hiero/plugins/{publish => publish_old_workflow}/version_up_workfile.py (100%) diff --git a/openpype/hosts/hiero/plugins/publish/collect_assetbuilds.py b/openpype/hosts/hiero/plugins/publish_old_workflow/collect_assetbuilds.py similarity index 100% rename from openpype/hosts/hiero/plugins/publish/collect_assetbuilds.py rename to openpype/hosts/hiero/plugins/publish_old_workflow/collect_assetbuilds.py diff --git a/openpype/hosts/hiero/plugins/publish/collect_clip_resolution.py b/openpype/hosts/hiero/plugins/publish_old_workflow/collect_clip_resolution.py similarity index 100% rename from openpype/hosts/hiero/plugins/publish/collect_clip_resolution.py rename to openpype/hosts/hiero/plugins/publish_old_workflow/collect_clip_resolution.py diff --git a/openpype/hosts/hiero/plugins/publish/collect_frame_ranges.py b/openpype/hosts/hiero/plugins/publish_old_workflow/collect_frame_ranges.py similarity index 100% rename from openpype/hosts/hiero/plugins/publish/collect_frame_ranges.py rename to openpype/hosts/hiero/plugins/publish_old_workflow/collect_frame_ranges.py diff --git a/openpype/hosts/hiero/plugins/publish/collect_hierarchy_context.py b/openpype/hosts/hiero/plugins/publish_old_workflow/collect_hierarchy_context.py similarity index 100% rename from openpype/hosts/hiero/plugins/publish/collect_hierarchy_context.py rename to openpype/hosts/hiero/plugins/publish_old_workflow/collect_hierarchy_context.py diff --git a/openpype/hosts/hiero/plugins/publish/collect_host_version.py b/openpype/hosts/hiero/plugins/publish_old_workflow/collect_host_version.py similarity index 100% rename from openpype/hosts/hiero/plugins/publish/collect_host_version.py rename to openpype/hosts/hiero/plugins/publish_old_workflow/collect_host_version.py diff --git a/openpype/hosts/hiero/plugins/publish/collect_plates.py b/openpype/hosts/hiero/plugins/publish_old_workflow/collect_plates.py similarity index 100% rename from openpype/hosts/hiero/plugins/publish/collect_plates.py rename to openpype/hosts/hiero/plugins/publish_old_workflow/collect_plates.py diff --git a/openpype/hosts/hiero/plugins/publish/collect_review.py b/openpype/hosts/hiero/plugins/publish_old_workflow/collect_review.py similarity index 100% rename from openpype/hosts/hiero/plugins/publish/collect_review.py rename to openpype/hosts/hiero/plugins/publish_old_workflow/collect_review.py diff --git a/openpype/hosts/hiero/plugins/publish/collect_tag_tasks.py b/openpype/hosts/hiero/plugins/publish_old_workflow/collect_tag_tasks.py similarity index 100% rename from openpype/hosts/hiero/plugins/publish/collect_tag_tasks.py rename to openpype/hosts/hiero/plugins/publish_old_workflow/collect_tag_tasks.py diff --git a/openpype/hosts/hiero/plugins/publish/extract_audio.py b/openpype/hosts/hiero/plugins/publish_old_workflow/extract_audio.py similarity index 100% rename from openpype/hosts/hiero/plugins/publish/extract_audio.py rename to openpype/hosts/hiero/plugins/publish_old_workflow/extract_audio.py diff --git a/openpype/hosts/hiero/plugins/publish/extract_clip_effects.py b/openpype/hosts/hiero/plugins/publish_old_workflow/extract_clip_effects.py similarity index 100% rename from openpype/hosts/hiero/plugins/publish/extract_clip_effects.py rename to openpype/hosts/hiero/plugins/publish_old_workflow/extract_clip_effects.py diff --git a/openpype/hosts/hiero/plugins/publish/extract_review_preparation.py b/openpype/hosts/hiero/plugins/publish_old_workflow/extract_review_preparation.py similarity index 100% rename from openpype/hosts/hiero/plugins/publish/extract_review_preparation.py rename to openpype/hosts/hiero/plugins/publish_old_workflow/extract_review_preparation.py diff --git a/openpype/hosts/hiero/plugins/publish/precollect_clip_effects.py b/openpype/hosts/hiero/plugins/publish_old_workflow/precollect_clip_effects.py similarity index 100% rename from openpype/hosts/hiero/plugins/publish/precollect_clip_effects.py rename to openpype/hosts/hiero/plugins/publish_old_workflow/precollect_clip_effects.py diff --git a/openpype/hosts/hiero/plugins/publish/validate_audio.py b/openpype/hosts/hiero/plugins/publish_old_workflow/validate_audio.py similarity index 100% rename from openpype/hosts/hiero/plugins/publish/validate_audio.py rename to openpype/hosts/hiero/plugins/publish_old_workflow/validate_audio.py diff --git a/openpype/hosts/hiero/plugins/publish/validate_hierarchy.py b/openpype/hosts/hiero/plugins/publish_old_workflow/validate_hierarchy.py similarity index 100% rename from openpype/hosts/hiero/plugins/publish/validate_hierarchy.py rename to openpype/hosts/hiero/plugins/publish_old_workflow/validate_hierarchy.py diff --git a/openpype/hosts/hiero/plugins/publish/validate_names.py b/openpype/hosts/hiero/plugins/publish_old_workflow/validate_names.py similarity index 100% rename from openpype/hosts/hiero/plugins/publish/validate_names.py rename to openpype/hosts/hiero/plugins/publish_old_workflow/validate_names.py diff --git a/openpype/hosts/hiero/plugins/publish/version_up_workfile.py b/openpype/hosts/hiero/plugins/publish_old_workflow/version_up_workfile.py similarity index 100% rename from openpype/hosts/hiero/plugins/publish/version_up_workfile.py rename to openpype/hosts/hiero/plugins/publish_old_workflow/version_up_workfile.py From ead1da640e5271ab54d4825462ea378a00d80016 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 9 Apr 2021 12:30:16 +0200 Subject: [PATCH 022/180] Hiero: old workfow precollectors for reference to temp folder --- .../precollect_instances.py | 224 ++++++++++++++++++ .../precollect_workfile.py | 74 ++++++ 2 files changed, 298 insertions(+) create mode 100644 openpype/hosts/hiero/plugins/publish_old_workflow/precollect_instances.py create mode 100644 openpype/hosts/hiero/plugins/publish_old_workflow/precollect_workfile.py diff --git a/openpype/hosts/hiero/plugins/publish_old_workflow/precollect_instances.py b/openpype/hosts/hiero/plugins/publish_old_workflow/precollect_instances.py new file mode 100644 index 0000000000..2b769afee1 --- /dev/null +++ b/openpype/hosts/hiero/plugins/publish_old_workflow/precollect_instances.py @@ -0,0 +1,224 @@ +from compiler.ast import flatten +from pyblish import api +from openpype.hosts.hiero import api as phiero +import hiero +# from openpype.hosts.hiero.api import lib +# reload(lib) +# reload(phiero) + + +class PreCollectInstances(api.ContextPlugin): + """Collect all Track items selection.""" + + order = api.CollectorOrder - 0.509 + label = "Pre-collect Instances" + hosts = ["hiero"] + + + def process(self, context): + track_items = phiero.get_track_items( + selected=True, check_tagged=True, check_enabled=True) + # only return enabled track items + if not track_items: + track_items = phiero.get_track_items( + check_enabled=True, check_tagged=True) + # get sequence and video tracks + sequence = context.data["activeSequence"] + tracks = sequence.videoTracks() + + # add collection to context + tracks_effect_items = self.collect_sub_track_items(tracks) + + context.data["tracksEffectItems"] = tracks_effect_items + + self.log.info( + "Processing enabled track items: {}".format(len(track_items))) + + for _ti in track_items: + data = {} + clip = _ti.source() + + # get clips subtracks and anotations + annotations = self.clip_annotations(clip) + subtracks = self.clip_subtrack(_ti) + self.log.debug("Annotations: {}".format(annotations)) + self.log.debug(">> Subtracks: {}".format(subtracks)) + + # get pype tag data + tag_parsed_data = phiero.get_track_item_pype_data(_ti) + # self.log.debug(pformat(tag_parsed_data)) + + if not tag_parsed_data: + continue + + if tag_parsed_data.get("id") != "pyblish.avalon.instance": + continue + # add tag data to instance data + data.update({ + k: v for k, v in tag_parsed_data.items() + if k not in ("id", "applieswhole", "label") + }) + + asset = tag_parsed_data["asset"] + subset = tag_parsed_data["subset"] + review_track = tag_parsed_data.get("reviewTrack") + hiero_track = tag_parsed_data.get("heroTrack") + audio = tag_parsed_data.get("audio") + + # remove audio attribute from data + data.pop("audio") + + # insert family into families + family = tag_parsed_data["family"] + families = [str(f) for f in tag_parsed_data["families"]] + families.insert(0, str(family)) + + track = _ti.parent() + media_source = _ti.source().mediaSource() + source_path = media_source.firstpath() + file_head = media_source.filenameHead() + file_info = media_source.fileinfos().pop() + source_first_frame = int(file_info.startFrame()) + + # apply only for review and master track instance + if review_track and hiero_track: + families += ["review", "ftrack"] + + data.update({ + "name": "{} {} {}".format(asset, subset, families), + "asset": asset, + "item": _ti, + "families": families, + + # tags + "tags": _ti.tags(), + + # track item attributes + "track": track.name(), + "trackItem": track, + "reviewTrack": review_track, + + # version data + "versionData": { + "colorspace": _ti.sourceMediaColourTransform() + }, + + # source attribute + "source": source_path, + "sourceMedia": media_source, + "sourcePath": source_path, + "sourceFileHead": file_head, + "sourceFirst": source_first_frame, + + # clip's effect + "clipEffectItems": subtracks + }) + + instance = context.create_instance(**data) + + self.log.info("Creating instance.data: {}".format(instance.data)) + + if audio: + a_data = dict() + + # add tag data to instance data + a_data.update({ + k: v for k, v in tag_parsed_data.items() + if k not in ("id", "applieswhole", "label") + }) + + # create main attributes + subset = "audioMain" + family = "audio" + families = ["clip", "ftrack"] + families.insert(0, str(family)) + + name = "{} {} {}".format(asset, subset, families) + + a_data.update({ + "name": name, + "subset": subset, + "asset": asset, + "family": family, + "families": families, + "item": _ti, + + # tags + "tags": _ti.tags(), + }) + + a_instance = context.create_instance(**a_data) + self.log.info("Creating audio instance: {}".format(a_instance)) + + @staticmethod + def clip_annotations(clip): + """ + Returns list of Clip's hiero.core.Annotation + """ + annotations = [] + subTrackItems = flatten(clip.subTrackItems()) + annotations += [item for item in subTrackItems if isinstance( + item, hiero.core.Annotation)] + return annotations + + @staticmethod + def clip_subtrack(clip): + """ + Returns list of Clip's hiero.core.SubTrackItem + """ + subtracks = [] + subTrackItems = flatten(clip.parent().subTrackItems()) + for item in subTrackItems: + # avoid all anotation + if isinstance(item, hiero.core.Annotation): + continue + # # avoid all not anaibled + if not item.isEnabled(): + continue + subtracks.append(item) + return subtracks + + @staticmethod + def collect_sub_track_items(tracks): + """ + Returns dictionary with track index as key and list of subtracks + """ + # collect all subtrack items + sub_track_items = dict() + for track in tracks: + items = track.items() + + # skip if no clips on track > need track with effect only + if items: + continue + + # skip all disabled tracks + if not track.isEnabled(): + continue + + track_index = track.trackIndex() + _sub_track_items = flatten(track.subTrackItems()) + + # continue only if any subtrack items are collected + if len(_sub_track_items) < 1: + continue + + enabled_sti = list() + # loop all found subtrack items and check if they are enabled + for _sti in _sub_track_items: + # checking if not enabled + if not _sti.isEnabled(): + continue + if isinstance(_sti, hiero.core.Annotation): + continue + # collect the subtrack item + enabled_sti.append(_sti) + + # continue only if any subtrack items are collected + if len(enabled_sti) < 1: + continue + + # add collection of subtrackitems to dict + sub_track_items[track_index] = enabled_sti + + return sub_track_items diff --git a/openpype/hosts/hiero/plugins/publish_old_workflow/precollect_workfile.py b/openpype/hosts/hiero/plugins/publish_old_workflow/precollect_workfile.py new file mode 100644 index 0000000000..ef7d07421b --- /dev/null +++ b/openpype/hosts/hiero/plugins/publish_old_workflow/precollect_workfile.py @@ -0,0 +1,74 @@ +import os +import pyblish.api +from openpype.hosts.hiero import api as phiero +from avalon import api as avalon + + +class PreCollectWorkfile(pyblish.api.ContextPlugin): + """Inject the current working file into context""" + + label = "Pre-collect Workfile" + order = pyblish.api.CollectorOrder - 0.51 + + def process(self, context): + asset = avalon.Session["AVALON_ASSET"] + subset = "workfile" + + project = phiero.get_current_project() + active_sequence = phiero.get_current_sequence() + video_tracks = active_sequence.videoTracks() + audio_tracks = active_sequence.audioTracks() + current_file = project.path() + staging_dir = os.path.dirname(current_file) + base_name = os.path.basename(current_file) + + # get workfile's colorspace properties + _clrs = {} + _clrs["useOCIOEnvironmentOverride"] = project.useOCIOEnvironmentOverride() # noqa + _clrs["lutSetting16Bit"] = project.lutSetting16Bit() + _clrs["lutSetting8Bit"] = project.lutSetting8Bit() + _clrs["lutSettingFloat"] = project.lutSettingFloat() + _clrs["lutSettingLog"] = project.lutSettingLog() + _clrs["lutSettingViewer"] = project.lutSettingViewer() + _clrs["lutSettingWorkingSpace"] = project.lutSettingWorkingSpace() + _clrs["lutUseOCIOForExport"] = project.lutUseOCIOForExport() + _clrs["ocioConfigName"] = project.ocioConfigName() + _clrs["ocioConfigPath"] = project.ocioConfigPath() + + # set main project attributes to context + context.data["activeProject"] = project + context.data["activeSequence"] = active_sequence + context.data["videoTracks"] = video_tracks + context.data["audioTracks"] = audio_tracks + context.data["currentFile"] = current_file + context.data["colorspace"] = _clrs + + self.log.info("currentFile: {}".format(current_file)) + + # creating workfile representation + representation = { + 'name': 'hrox', + 'ext': 'hrox', + 'files': base_name, + "stagingDir": staging_dir, + } + + instance_data = { + "name": "{}_{}".format(asset, subset), + "asset": asset, + "subset": "{}{}".format(asset, subset.capitalize()), + "item": project, + "family": "workfile", + + # version data + "versionData": { + "colorspace": _clrs + }, + + # source attribute + "sourcePath": current_file, + "representations": [representation] + } + + instance = context.create_instance(**instance_data) + self.log.info("Creating instance: {}".format(instance)) From cbf39cea7c8bf842d1c5626a57bd2d3d73f9e8fc Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 9 Apr 2021 12:30:42 +0200 Subject: [PATCH 023/180] Global: adding Hiero to hosts --- openpype/plugins/publish/collect_otio_frame_ranges.py | 2 +- openpype/plugins/publish/collect_otio_review.py | 2 +- openpype/plugins/publish/collect_otio_subset_resources.py | 2 +- openpype/plugins/publish/extract_otio_file.py | 2 +- openpype/plugins/publish/extract_otio_review.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/openpype/plugins/publish/collect_otio_frame_ranges.py b/openpype/plugins/publish/collect_otio_frame_ranges.py index 53cc249033..e1b8b95a46 100644 --- a/openpype/plugins/publish/collect_otio_frame_ranges.py +++ b/openpype/plugins/publish/collect_otio_frame_ranges.py @@ -20,7 +20,7 @@ class CollectOcioFrameRanges(pyblish.api.InstancePlugin): label = "Collect OTIO Frame Ranges" order = pyblish.api.CollectorOrder - 0.58 families = ["shot", "clip"] - hosts = ["resolve"] + hosts = ["resolve", "hiero"] def process(self, instance): # get basic variables diff --git a/openpype/plugins/publish/collect_otio_review.py b/openpype/plugins/publish/collect_otio_review.py index 0c7eeaea44..de85ef5f7d 100644 --- a/openpype/plugins/publish/collect_otio_review.py +++ b/openpype/plugins/publish/collect_otio_review.py @@ -22,7 +22,7 @@ class CollectOcioReview(pyblish.api.InstancePlugin): label = "Collect OTIO Review" order = pyblish.api.CollectorOrder - 0.57 families = ["clip"] - hosts = ["resolve"] + hosts = ["resolve", "hiero"] def process(self, instance): # get basic variables diff --git a/openpype/plugins/publish/collect_otio_subset_resources.py b/openpype/plugins/publish/collect_otio_subset_resources.py index a0c6b9339b..47cb0a21a8 100644 --- a/openpype/plugins/publish/collect_otio_subset_resources.py +++ b/openpype/plugins/publish/collect_otio_subset_resources.py @@ -19,7 +19,7 @@ class CollectOcioSubsetResources(pyblish.api.InstancePlugin): label = "Collect OTIO Subset Resources" order = pyblish.api.CollectorOrder - 0.57 families = ["clip"] - hosts = ["resolve"] + hosts = ["resolve", "hiero"] def process(self, instance): if not instance.data.get("representations"): diff --git a/openpype/plugins/publish/extract_otio_file.py b/openpype/plugins/publish/extract_otio_file.py index 146f3b88ec..3bd217d5d4 100644 --- a/openpype/plugins/publish/extract_otio_file.py +++ b/openpype/plugins/publish/extract_otio_file.py @@ -12,7 +12,7 @@ class ExtractOTIOFile(openpype.api.Extractor): label = "Extract OTIO file" order = pyblish.api.ExtractorOrder - 0.45 families = ["workfile"] - hosts = ["resolve"] + hosts = ["resolve", "hiero"] def process(self, instance): # create representation data diff --git a/openpype/plugins/publish/extract_otio_review.py b/openpype/plugins/publish/extract_otio_review.py index 91a680ddb0..85e1ea7aef 100644 --- a/openpype/plugins/publish/extract_otio_review.py +++ b/openpype/plugins/publish/extract_otio_review.py @@ -40,8 +40,8 @@ class ExtractOTIOReview(openpype.api.Extractor): order = api.ExtractorOrder - 0.45 label = "Extract OTIO review" - hosts = ["resolve"] families = ["review"] + hosts = ["resolve", "hiero"] # plugin default attributes temp_file_head = "tempFile." From 8a84b0ae3fb2a098c162e5f0c5a24e69e20c6106 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 12 Apr 2021 12:34:31 +0200 Subject: [PATCH 024/180] Global: fixing Python2 compatibility --- openpype/plugins/publish/extract_otio_review.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_otio_review.py b/openpype/plugins/publish/extract_otio_review.py index 85e1ea7aef..07fe6f2731 100644 --- a/openpype/plugins/publish/extract_otio_review.py +++ b/openpype/plugins/publish/extract_otio_review.py @@ -188,7 +188,7 @@ class ExtractOTIOReview(openpype.api.Extractor): # creating and registering representation representation = self._create_representation(start, duration) instance.data["representations"].append(representation) - self.log.info(f"Adding representation: {representation}") + self.log.info("Adding representation: {}".format(representation)) def _create_representation(self, start, duration): """ @@ -388,7 +388,7 @@ class ExtractOTIOReview(openpype.api.Extractor): (int(end_offset + duration) + 1)): seq_number = padding.format(start_frame + index) self.log.debug( - f"index: `{index}` | seq_number: `{seq_number}`") + "index: `{}` | seq_number: `{}`".format(index, seq_number)) new_frames.append(int(seq_number)) new_frames += self.used_frames self.used_frames = new_frames From 1fef682dfc5e4ed5361b5028e1a1c80c1e219b1b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 13 Apr 2021 16:58:22 +0200 Subject: [PATCH 025/180] Hiero: fixing review workflow --- openpype/hosts/hiero/api/plugin.py | 12 +++++++++--- openpype/hosts/hiero/api/tags.py | 7 +++++++ .../hiero/plugins/publish/precollect_instances.py | 4 ++-- openpype/plugins/publish/collect_hierarchy.py | 2 +- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/hiero/api/plugin.py b/openpype/hosts/hiero/api/plugin.py index 9b3bc25d80..5620fe3cb8 100644 --- a/openpype/hosts/hiero/api/plugin.py +++ b/openpype/hosts/hiero/api/plugin.py @@ -675,6 +675,9 @@ class PublishClip: if kwargs.get("avalon"): self.tag_data.update(kwargs["avalon"]) + # add publish attribute to tag data + self.tag_data.update({"publish": True}) + # adding ui inputs if any self.ui_inputs = kwargs.get("ui_inputs", {}) @@ -688,6 +691,7 @@ class PublishClip: self._create_parents() def convert(self): + # solve track item data and add them to tag data self._convert_to_tag_data() @@ -707,6 +711,11 @@ class PublishClip: else: self.tag_data["asset"] = self.ti_name + if self.tag_data["heroTrack"] and self.review_layer: + self.tag_data.update({"reviewTrack": self.review_layer}) + else: + self.tag_data.update({"reviewTrack": None}) + # create pype tag on track_item and add data lib.imprint(self.track_item, self.tag_data) @@ -862,9 +871,6 @@ class PublishClip: # add data to return data dict self.tag_data.update(tag_hierarchy_data) - if hero_track and self.review_layer: - self.tag_data.update({"reviewTrack": self.review_layer}) - def _solve_tag_hierarchy_data(self, hierarchy_formating_data): """ Solve tag data from hierarchy data and templates. """ # fill up clip name and hierarchy keys diff --git a/openpype/hosts/hiero/api/tags.py b/openpype/hosts/hiero/api/tags.py index 06fa655a2e..d2502f3c71 100644 --- a/openpype/hosts/hiero/api/tags.py +++ b/openpype/hosts/hiero/api/tags.py @@ -84,6 +84,13 @@ def update_tag(tag, data): mtd = tag.metadata() # 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.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(): mtd.setValue( diff --git a/openpype/hosts/hiero/plugins/publish/precollect_instances.py b/openpype/hosts/hiero/plugins/publish/precollect_instances.py index 242cfed254..63585b387e 100644 --- a/openpype/hosts/hiero/plugins/publish/precollect_instances.py +++ b/openpype/hosts/hiero/plugins/publish/precollect_instances.py @@ -146,13 +146,13 @@ class PrecollectInstances(pyblish.api.ContextPlugin): dict: otio clip object """ - track_name = track_item.parent().name() + ti_track_name = track_item.parent().name() timeline_range = self.create_otio_time_range_from_timeline_item_data( track_item) for otio_clip in otio_timeline.each_clip(): track_name = otio_clip.parent().name parent_range = otio_clip.range_in_parent() - if track_name not in track_name: + if ti_track_name not in track_name: continue if otio_clip.name not in track_item.name(): continue diff --git a/openpype/plugins/publish/collect_hierarchy.py b/openpype/plugins/publish/collect_hierarchy.py index 390ce443b6..1aa10fcb9b 100644 --- a/openpype/plugins/publish/collect_hierarchy.py +++ b/openpype/plugins/publish/collect_hierarchy.py @@ -15,7 +15,7 @@ class CollectHierarchy(pyblish.api.ContextPlugin): label = "Collect Hierarchy" order = pyblish.api.CollectorOrder - 0.57 families = ["shot"] - hosts = ["resolve"] + hosts = ["resolve", "hiero"] def process(self, context): temp_context = {} From e376900d6854673dd5ac80ecc21d27abf9941303 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 13 Apr 2021 17:51:29 +0200 Subject: [PATCH 026/180] Hiero: hound fixes --- openpype/hosts/hiero/otio/hiero_export.py | 2 +- openpype/hosts/hiero/otio/hiero_import.py | 2 +- .../plugins/publish/precollect_instances.py | 18 +++++++++--------- .../precollect_instances.py | 1 - 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/hiero/otio/hiero_export.py b/openpype/hosts/hiero/otio/hiero_export.py index 93ddee7b38..6e751d3aa4 100644 --- a/openpype/hosts/hiero/otio/hiero_export.py +++ b/openpype/hosts/hiero/otio/hiero_export.py @@ -175,7 +175,7 @@ def create_otio_markers(otio_item, item): _key = key.replace("tag.", "") try: - # capture exceptions which are related to strings only + # capture exceptions which are related to strings only _value = ast.literal_eval(value) except (ValueError, SyntaxError): _value = value diff --git a/openpype/hosts/hiero/otio/hiero_import.py b/openpype/hosts/hiero/otio/hiero_import.py index b5040a32c7..257c434011 100644 --- a/openpype/hosts/hiero/otio/hiero_import.py +++ b/openpype/hosts/hiero/otio/hiero_import.py @@ -117,7 +117,7 @@ def apply_transition(otio_track, otio_item, track): transition = None warning = ( "Unable to apply transition \"{t.name}\": {e} " - "Ignoring the transition.").format(t=otio_item, e=e.message) + "Ignoring the transition.").format(t=otio_item, e=str(e)) elif transition_type == 'fade_in': transition_func = getattr( diff --git a/openpype/hosts/hiero/plugins/publish/precollect_instances.py b/openpype/hosts/hiero/plugins/publish/precollect_instances.py index 63585b387e..f054971c42 100644 --- a/openpype/hosts/hiero/plugins/publish/precollect_instances.py +++ b/openpype/hosts/hiero/plugins/publish/precollect_instances.py @@ -6,8 +6,8 @@ from openpype.hosts.hiero.otio import hiero_export # # developer reload modules from pprint import pformat -reload(lib) -reload(phiero) +# reload(lib) +# reload(phiero) class PrecollectInstances(pyblish.api.ContextPlugin): @@ -20,7 +20,7 @@ class PrecollectInstances(pyblish.api.ContextPlugin): def process(self, context): otio_timeline = context.data["otioTimeline"] selected_timeline_items = phiero.get_track_items( - selected=True, check_enabled=True, check_tagged=True) + selected=True, check_enabled=True, check_tagged=True) self.log.info( "Processing enabled track items: {}".format( selected_timeline_items)) @@ -39,8 +39,6 @@ class PrecollectInstances(pyblish.api.ContextPlugin): if tag_data.get("id") != "pyblish.avalon.instance": continue - clip = track_item.source() - # add tag data to instance data data.update({ k: v for k, v in tag_data.items() @@ -95,16 +93,18 @@ class PrecollectInstances(pyblish.api.ContextPlugin): "resolutionWidth": otio_clip_metadata[ "openpype.source.width"], "resolutionHeight": otio_clip_metadata[ - "openpype.source.height"], + "openpype.source.height"], "pixelAspect": otio_clip_metadata[ - "openpype.source.pixelAspect"] + "openpype.source.pixelAspect"] }) else: otio_tl_metadata = context.data["otioTimeline"].metadata data.update({ "resolutionWidth": otio_tl_metadata["openpype.timeline.width"], - "resolutionHeight": otio_tl_metadata["openpype.timeline.height"], - "pixelAspect": otio_tl_metadata["openpype.timeline.pixelAspect"] + "resolutionHeight": otio_tl_metadata[ + "openpype.timeline.height"], + "pixelAspect": otio_tl_metadata[ + "openpype.timeline.pixelAspect"] }) def create_shot_instance(self, context, **data): diff --git a/openpype/hosts/hiero/plugins/publish_old_workflow/precollect_instances.py b/openpype/hosts/hiero/plugins/publish_old_workflow/precollect_instances.py index 2b769afee1..f9cc158e79 100644 --- a/openpype/hosts/hiero/plugins/publish_old_workflow/precollect_instances.py +++ b/openpype/hosts/hiero/plugins/publish_old_workflow/precollect_instances.py @@ -14,7 +14,6 @@ class PreCollectInstances(api.ContextPlugin): label = "Pre-collect Instances" hosts = ["hiero"] - def process(self, context): track_items = phiero.get_track_items( selected=True, check_tagged=True, check_enabled=True) From fa6c491d5286002e7eb6018a1b6f2c5f6dc721a2 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 14 Apr 2021 10:23:39 +0100 Subject: [PATCH 027/180] Fixed alembic extraction --- openpype/hosts/blender/plugins/publish/extract_abc.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/extract_abc.py b/openpype/hosts/blender/plugins/publish/extract_abc.py index 6a89c6019b..e1eef61560 100644 --- a/openpype/hosts/blender/plugins/publish/extract_abc.py +++ b/openpype/hosts/blender/plugins/publish/extract_abc.py @@ -18,7 +18,7 @@ class ExtractABC(openpype.api.Extractor): # Define extract output file path stagingdir = self.staging_dir(instance) - filename = f"{instance.name}.fbx" + filename = f"{instance.name}.abc" filepath = os.path.join(stagingdir, filename) context = bpy.context @@ -72,9 +72,7 @@ class ExtractABC(openpype.api.Extractor): # We export the abc bpy.ops.wm.alembic_export( new_context, - filepath=filepath, - start=1, - end=1 + filepath=filepath ) view_layer.active_layer_collection = old_active_layer_collection From b1b7eda2acc33e564137d1c0690f6056b69ceaaf Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 14 Apr 2021 11:13:00 +0100 Subject: [PATCH 028/180] Implemented Alembic loader --- .../hosts/blender/plugins/load/load_model.py | 77 +++++++++++++------ 1 file changed, 54 insertions(+), 23 deletions(-) diff --git a/openpype/hosts/blender/plugins/load/load_model.py b/openpype/hosts/blender/plugins/load/load_model.py index 7297e459a6..d679807534 100644 --- a/openpype/hosts/blender/plugins/load/load_model.py +++ b/openpype/hosts/blender/plugins/load/load_model.py @@ -7,6 +7,7 @@ from typing import Dict, List, Optional from avalon import api, blender import bpy +import openpype.api import openpype.hosts.blender.api.plugin as plugin @@ -108,6 +109,10 @@ class BlendModelLoader(plugin.AssetLoader): self.__class__.__name__, ) + print(lib_container) + print(container) + + container_metadata = container.get( blender.pipeline.AVALON_PROPERTY) @@ -271,36 +276,62 @@ class CacheModelLoader(plugin.AssetLoader): context: Full parenthood of representation to load options: Additional settings dictionary """ - raise NotImplementedError( - "Loading of Alembic files is not yet implemented.") - # TODO (jasper): implement Alembic import. libpath = self.fname asset = context["asset"]["name"] subset = context["subset"]["name"] - # TODO (jasper): evaluate use of namespace which is 'alien' to Blender. - lib_container = container_name = ( - plugin.asset_name(asset, subset, namespace) - ) - relative = bpy.context.preferences.filepaths.use_relative_paths - with bpy.data.libraries.load( - libpath, link=True, relative=relative - ) as (data_from, data_to): - data_to.collections = [lib_container] + lib_container = plugin.asset_name( + asset, subset + ) + unique_number = plugin.get_unique_number( + asset, subset + ) + namespace = namespace or f"{asset}_{unique_number}" + container_name = plugin.asset_name( + asset, subset, unique_number + ) + + container = bpy.data.collections.new(lib_container) + container.name = container_name + blender.pipeline.containerise_existing( + container, + name, + namespace, + context, + self.__class__.__name__, + ) + + container_metadata = container.get( + blender.pipeline.AVALON_PROPERTY) + + container_metadata["libpath"] = libpath + container_metadata["lib_container"] = lib_container + + bpy.ops.object.select_all(action='DESELECT') + + view_layer = bpy.context.view_layer + view_layer_collection = view_layer.active_layer_collection.collection + + relative = bpy.context.preferences.filepaths.use_relative_paths + bpy.ops.wm.alembic_import( + filepath=libpath, + relative_path=relative + ) + + parent = bpy.data.collections.new(container_name) + for obj in bpy.context.selected_objects: + parent.objects.link(obj) + view_layer_collection.objects.unlink(obj) + obj.name = f"{obj.name}:{container_name}" + obj.data.name = f"{obj.data.name}:{container_name}" + + bpy.ops.object.select_all(action='DESELECT') scene = bpy.context.scene - instance_empty = bpy.data.objects.new( - container_name, None - ) - scene.collection.objects.link(instance_empty) - instance_empty.instance_type = 'COLLECTION' - collection = bpy.data.collections[lib_container] - collection.name = container_name - instance_empty.instance_collection = collection + scene.collection.children.link(parent) - nodes = list(collection.objects) - nodes.append(collection) - nodes.append(instance_empty) + nodes = list(parent.objects) + nodes.append(parent) self[:] = nodes return nodes From 5e54d09ca1ce64d3f6cd5a9dd2248315330002f8 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 14 Apr 2021 11:37:36 +0100 Subject: [PATCH 029/180] Alembic loader is now more consistent with loaders for other formats --- .../hosts/blender/plugins/load/load_model.py | 69 +++++++++++-------- 1 file changed, 42 insertions(+), 27 deletions(-) diff --git a/openpype/hosts/blender/plugins/load/load_model.py b/openpype/hosts/blender/plugins/load/load_model.py index d679807534..f587a20fa1 100644 --- a/openpype/hosts/blender/plugins/load/load_model.py +++ b/openpype/hosts/blender/plugins/load/load_model.py @@ -7,7 +7,6 @@ from typing import Dict, List, Optional from avalon import api, blender import bpy -import openpype.api import openpype.hosts.blender.api.plugin as plugin @@ -109,10 +108,6 @@ class BlendModelLoader(plugin.AssetLoader): self.__class__.__name__, ) - print(lib_container) - print(container) - - container_metadata = container.get( blender.pipeline.AVALON_PROPERTY) @@ -265,6 +260,41 @@ class CacheModelLoader(plugin.AssetLoader): icon = "code-fork" color = "orange" + def _process(self, libpath, container_name, parent_collection): + bpy.ops.object.select_all(action='DESELECT') + + view_layer = bpy.context.view_layer + view_layer_collection = view_layer.active_layer_collection.collection + + relative = bpy.context.preferences.filepaths.use_relative_paths + bpy.ops.wm.alembic_import( + filepath=libpath, + relative_path=relative + ) + + parent = parent_collection + + if parent is None: + parent = bpy.context.scene.collection + + model_container = bpy.data.collections.new(container_name) + parent.children.link(model_container) + for obj in bpy.context.selected_objects: + model_container.objects.link(obj) + view_layer_collection.objects.unlink(obj) + obj.name = f"{obj.name}:{container_name}" + obj.data.name = f"{obj.data.name}:{container_name}" + + if not obj.get(blender.pipeline.AVALON_PROPERTY): + obj[blender.pipeline.AVALON_PROPERTY] = dict() + + avalon_info = obj[blender.pipeline.AVALON_PROPERTY] + avalon_info.update({"container_name": container_name}) + + bpy.ops.object.select_all(action='DESELECT') + + return model_container + def process_asset( self, context: dict, name: str, namespace: Optional[str] = None, options: Optional[Dict] = None @@ -308,30 +338,15 @@ class CacheModelLoader(plugin.AssetLoader): container_metadata["libpath"] = libpath container_metadata["lib_container"] = lib_container - bpy.ops.object.select_all(action='DESELECT') + obj_container = self._process( + libpath, container_name, None) - view_layer = bpy.context.view_layer - view_layer_collection = view_layer.active_layer_collection.collection + container_metadata["obj_container"] = obj_container - relative = bpy.context.preferences.filepaths.use_relative_paths - bpy.ops.wm.alembic_import( - filepath=libpath, - relative_path=relative - ) + # Save the list of objects in the metadata container + container_metadata["objects"] = obj_container.all_objects - parent = bpy.data.collections.new(container_name) - for obj in bpy.context.selected_objects: - parent.objects.link(obj) - view_layer_collection.objects.unlink(obj) - obj.name = f"{obj.name}:{container_name}" - obj.data.name = f"{obj.data.name}:{container_name}" - - bpy.ops.object.select_all(action='DESELECT') - - scene = bpy.context.scene - scene.collection.children.link(parent) - - nodes = list(parent.objects) - nodes.append(parent) + nodes = list(container.objects) + nodes.append(container) self[:] = nodes return nodes From e08b04a5ba137190cb16e14d95f2c7a024d6a401 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 14 Apr 2021 15:13:31 +0100 Subject: [PATCH 030/180] Implemented update and remove functions --- .../hosts/blender/plugins/load/load_model.py | 136 +++++++++++++++++- 1 file changed, 134 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/blender/plugins/load/load_model.py b/openpype/hosts/blender/plugins/load/load_model.py index f587a20fa1..db12585efb 100644 --- a/openpype/hosts/blender/plugins/load/load_model.py +++ b/openpype/hosts/blender/plugins/load/load_model.py @@ -260,6 +260,12 @@ class CacheModelLoader(plugin.AssetLoader): icon = "code-fork" color = "orange" + def _remove(self, objects, container): + for obj in list(objects): + bpy.data.meshes.remove(obj.data) + + bpy.data.collections.remove(container) + def _process(self, libpath, container_name, parent_collection): bpy.ops.object.select_all(action='DESELECT') @@ -282,8 +288,20 @@ class CacheModelLoader(plugin.AssetLoader): for obj in bpy.context.selected_objects: model_container.objects.link(obj) view_layer_collection.objects.unlink(obj) - obj.name = f"{obj.name}:{container_name}" - obj.data.name = f"{obj.data.name}:{container_name}" + + name = obj.name + data_name = obj.data.name + obj.name = f"{name}:{container_name}" + obj.data.name = f"{data_name}:{container_name}" + + # Blender handles alembic with a modifier linked to a cache file. + # Here we create the modifier for the object and link it with the + # loaded cache file. + modifier = obj.modifiers.new( + name="MeshSequenceCache", type='MESH_SEQUENCE_CACHE') + cache_file = bpy.path.basename(libpath) + modifier.cache_file = bpy.data.cache_files[cache_file] + modifier.object_path = f"/{name}/{data_name}" if not obj.get(blender.pipeline.AVALON_PROPERTY): obj[blender.pipeline.AVALON_PROPERTY] = dict() @@ -350,3 +368,117 @@ class CacheModelLoader(plugin.AssetLoader): nodes.append(container) self[:] = nodes return nodes + + def update(self, container: Dict, representation: Dict): + """Update the loaded asset. + + This will remove all objects of the current collection, load the new + ones and add them to the collection. + If the objects of the collection are used in another collection they + will not be removed, only unlinked. Normally this should not be the + case though. + + Warning: + No nested collections are supported at the moment! + """ + collection = bpy.data.collections.get( + container["objectName"] + ) + libpath = Path(api.get_representation_path(representation)) + extension = libpath.suffix.lower() + + self.log.info( + "Container: %s\nRepresentation: %s", + pformat(container, indent=2), + pformat(representation, indent=2), + ) + + assert collection, ( + f"The asset is not loaded: {container['objectName']}" + ) + assert not (collection.children), ( + "Nested collections are not supported." + ) + assert libpath, ( + "No existing library file found for {container['objectName']}" + ) + assert libpath.is_file(), ( + f"The file doesn't exist: {libpath}" + ) + assert extension in plugin.VALID_EXTENSIONS, ( + f"Unsupported file: {libpath}" + ) + + collection_metadata = collection.get( + blender.pipeline.AVALON_PROPERTY) + collection_libpath = collection_metadata["libpath"] + + obj_container = plugin.get_local_collection_with_name( + collection_metadata["obj_container"].name + ) + objects = obj_container.all_objects + + normalized_collection_libpath = ( + str(Path(bpy.path.abspath(collection_libpath)).resolve()) + ) + normalized_libpath = ( + str(Path(bpy.path.abspath(str(libpath))).resolve()) + ) + self.log.debug( + "normalized_collection_libpath:\n %s\nnormalized_libpath:\n %s", + normalized_collection_libpath, + normalized_libpath, + ) + if normalized_collection_libpath == normalized_libpath: + self.log.info("Library already loaded, not updating...") + return + + # Check if the cache file has already been loaded + if bpy.path.basename(str(libpath)) not in bpy.data.cache_files: + bpy.ops.cachefile.open(filepath=str(libpath)) + + # Set the new cache file in the objects that use the modifier + for obj in objects: + for modifier in obj.modifiers: + if modifier.type == 'MESH_SEQUENCE_CACHE': + cache_file = bpy.path.basename(str(libpath)) + modifier.cache_file = bpy.data.cache_files[cache_file] + + def remove(self, container: Dict) -> bool: + """Remove an existing container from a Blender scene. + + Arguments: + container (openpype:container-1.0): Container to remove, + from `host.ls()`. + + Returns: + bool: Whether the container was deleted. + + Warning: + No nested collections are supported at the moment! + """ + collection = bpy.data.collections.get( + container["objectName"] + ) + if not collection: + return False + assert not (collection.children), ( + "Nested collections are not supported." + ) + + collection_metadata = collection.get( + blender.pipeline.AVALON_PROPERTY) + + obj_container = plugin.get_local_collection_with_name( + collection_metadata["obj_container"].name + ) + objects = obj_container.all_objects + + self._remove(objects, obj_container) + + bpy.data.collections.remove(collection) + + # We should delete the cache file used in the modifier too, + # but Blender does not allow to do that from python. + + return True From 4a8339dd12ccec175eee24219db5bd9eab90617a Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 14 Apr 2021 15:14:07 +0100 Subject: [PATCH 031/180] Added .abc as valid extension --- openpype/hosts/blender/api/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index eb88e7af63..de30da3319 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -9,7 +9,7 @@ from avalon import api import avalon.blender from openpype.api import PypeCreatorMixin -VALID_EXTENSIONS = [".blend", ".json"] +VALID_EXTENSIONS = [".blend", ".json", ".abc"] def asset_name( From 5f58f18cd95d71835614ff51f7107dece5202121 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 14 Apr 2021 15:59:15 +0100 Subject: [PATCH 032/180] Fixed problem that published objects not in the instance --- openpype/hosts/blender/plugins/publish/extract_abc.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/extract_abc.py b/openpype/hosts/blender/plugins/publish/extract_abc.py index e1eef61560..a7653d9f5a 100644 --- a/openpype/hosts/blender/plugins/publish/extract_abc.py +++ b/openpype/hosts/blender/plugins/publish/extract_abc.py @@ -52,6 +52,8 @@ class ExtractABC(openpype.api.Extractor): old_scale = scene.unit_settings.scale_length + bpy.ops.object.select_all(action='DESELECT') + selected = list() for obj in instance: @@ -67,12 +69,11 @@ class ExtractABC(openpype.api.Extractor): # We set the scale of the scene for the export scene.unit_settings.scale_length = 0.01 - self.log.info(new_context) - # We export the abc bpy.ops.wm.alembic_export( new_context, - filepath=filepath + filepath=filepath, + selected=True ) view_layer.active_layer_collection = old_active_layer_collection From 3d25f0cd837eb3742ed1c3082cf63a257e4fb178 Mon Sep 17 00:00:00 2001 From: mkolar Date: Wed, 14 Apr 2021 16:31:23 +0000 Subject: [PATCH 033/180] Create draft PR for #1345 From e25a06ea492be7744cc05f15e91a7756e5db09d7 Mon Sep 17 00:00:00 2001 From: simonebarbieri Date: Wed, 14 Apr 2021 16:31:26 +0000 Subject: [PATCH 034/180] Create draft PR for #1345 From e1efa554233db4b641f13333c5517449c7921f69 Mon Sep 17 00:00:00 2001 From: simonebarbieri Date: Wed, 14 Apr 2021 16:31:32 +0000 Subject: [PATCH 035/180] Create draft PR for #1345 From 5b907a9d661ea376fba05de86cd29b338aa21fc5 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 15 Apr 2021 15:39:55 +0200 Subject: [PATCH 036/180] Initial version of column filtering --- openpype/modules/sync_server/tray/lib.py | 7 + openpype/modules/sync_server/tray/models.py | 140 +++++++++++++---- openpype/modules/sync_server/tray/widgets.py | 155 ++++++++++++++++++- 3 files changed, 262 insertions(+), 40 deletions(-) diff --git a/openpype/modules/sync_server/tray/lib.py b/openpype/modules/sync_server/tray/lib.py index 0282d79ea1..051567ed6c 100644 --- a/openpype/modules/sync_server/tray/lib.py +++ b/openpype/modules/sync_server/tray/lib.py @@ -1,4 +1,5 @@ from Qt import QtCore +import attr from openpype.lib import PypeLogger @@ -20,8 +21,14 @@ ProviderRole = QtCore.Qt.UserRole + 2 ProgressRole = QtCore.Qt.UserRole + 4 DateRole = QtCore.Qt.UserRole + 6 FailedRole = QtCore.Qt.UserRole + 8 +HeaderNameRole = QtCore.Qt.UserRole + 10 +@attr.s +class FilterDefinition: + type = attr.ib() + values = attr.ib(factory=list) + def pretty_size(value, suffix='B'): for unit in ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi']: if abs(value) < 1024.0: diff --git a/openpype/modules/sync_server/tray/models.py b/openpype/modules/sync_server/tray/models.py index 3cc53c6ec4..444422c56a 100644 --- a/openpype/modules/sync_server/tray/models.py +++ b/openpype/modules/sync_server/tray/models.py @@ -56,6 +56,10 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): """Returns project""" return self._project + @property + def column_filtering(self): + return self._column_filtering + def rowCount(self, _index): return len(self._data) @@ -65,7 +69,15 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): def headerData(self, section, orientation, role): if role == Qt.DisplayRole: if orientation == Qt.Horizontal: - return self.COLUMN_LABELS[section][1] + name = self.COLUMN_LABELS[section][0] + txt = "" + if name in self.column_filtering.keys(): + txt = "(F)" + return self.COLUMN_LABELS[section][1] + txt # return label + + if role == lib.HeaderNameRole: + if orientation == Qt.Horizontal: + return self.COLUMN_LABELS[section][0] # return name def get_header_index(self, value): """ @@ -190,6 +202,35 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): self.word_filter = word_filter self.refresh() + def get_column_filter_values(self, index): + """ + Returns list of available values for filtering in the column + + Args: + index(int): index of column in header + + Returns: + (dict) of value: label shown in filtering menu + 'value' is used in MongoDB query, 'label' is human readable for + menu + for some columns ('subset') might be 'value' and 'label' same + """ + column_name = self._header[index] + + filter_def = self.COLUMN_FILTERS.get(column_name) + if not filter_def: + return {} + + if filter_def['type'] == 'predefined_set': + return dict(filter_def['values']) + elif filter_def['type'] == 'available_values': + recs = self.dbcon.find({'type': column_name}, {"name": 1, + "_id": -1}) + values = {} + for item in recs: + values[item["name"]] = item["name"] + return dict(sorted(values.items(), key=lambda item: item[1])) + def set_project(self, project): """ Changes project, called after project selection is changed @@ -251,7 +292,7 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): ("files_count", "Files"), ("files_size", "Size"), ("priority", "Priority"), - ("state", "Status") + ("status", "Status") ] DEFAULT_SORT = { @@ -259,18 +300,26 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): "_id": 1 } SORT_BY_COLUMN = [ - "context.asset", # asset - "context.subset", # subset - "context.version", # version - "context.representation", # representation + "asset", # asset + "subset", # subset + "version", # version + "representation", # representation "updated_dt_local", # local created_dt "updated_dt_remote", # remote created_dt "files_count", # count of files "files_size", # file size of all files "context.asset", # priority TODO - "status" # state + "status" # status ] + COLUMN_FILTERS = { + 'status': {'type': 'predefined_set', + 'values': {k: v for k, v in lib.STATUS.items()}}, + 'subset': {'type': 'available_values'}, + 'asset': {'type': 'available_values'}, + 'representation': {'type': 'available_values'} + } + refresh_started = QtCore.Signal() refresh_finished = QtCore.Signal() @@ -297,7 +346,7 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): files_count = attr.ib(default=None) files_size = attr.ib(default=None) priority = attr.ib(default=None) - state = attr.ib(default=None) + status = attr.ib(default=None) path = attr.ib(default=None) def __init__(self, sync_server, header, project=None): @@ -308,6 +357,7 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): self._rec_loaded = 0 self._total_records = 0 # how many documents query actually found self.word_filter = None + self._column_filtering = {} self._initialized = False if not self._project or self._project == lib.DUMMY_PROJECT: @@ -359,9 +409,9 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): if role == lib.FailedRole: if header_value == 'local_site': - return item.state == lib.STATUS[2] and item.local_progress < 1 + return item.status == lib.STATUS[2] and item.local_progress < 1 if header_value == 'remote_site': - return item.state == lib.STATUS[2] and item.remote_progress < 1 + return item.status == lib.STATUS[2] and item.remote_progress < 1 if role == Qt.DisplayRole: # because of ImageDelegate @@ -397,7 +447,6 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): remote_site) for repre in result.get("paginatedResults"): - context = repre.get("context").pop() files = repre.get("files", []) if isinstance(files, dict): # aggregate returns dictionary files = [files] @@ -420,17 +469,17 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): avg_progress_local = lib.convert_progress( repre.get('avg_progress_local', '0')) - if context.get("version"): - version = "v{:0>3d}".format(context.get("version")) + if repre.get("version"): + version = "v{:0>3d}".format(repre.get("version")) else: version = "master" item = self.SyncRepresentation( repre.get("_id"), - context.get("asset"), - context.get("subset"), + repre.get("asset"), + repre.get("subset"), version, - context.get("representation"), + repre.get("representation"), local_updated, remote_updated, local_site, @@ -461,7 +510,7 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): 'sync_dt' - same for remote side 'local_site' - progress of repr on local side, 1 = finished 'remote_site' - progress on remote side, calculates from files - 'state' - + 'status' - 0 - in progress 1 - failed 2 - queued @@ -481,7 +530,7 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): if limit == 0: limit = SyncRepresentationSummaryModel.PAGE_SIZE - return [ + aggr = [ {"$match": self.get_match_part()}, {'$unwind': '$files'}, # merge potentially unwinded records back to single per repre @@ -584,16 +633,43 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): 'paused_local': {'$sum': '$paused_local'}, 'updated_dt_local': {'$max': "$updated_dt_local"} }}, - {"$project": self.projection}, - {"$sort": self.sort}, - { + {"$project": self.projection} + ] + + if self.column_filtering: + aggr.append( + {"$match": self.column_filtering} + ) + print(self.column_filtering) + + aggr.extend( + [{"$sort": self.sort}, + { '$facet': { 'paginatedResults': [{'$skip': self._rec_loaded}, {'$limit': limit}], 'totalCount': [{'$count': 'count'}] } - } - ] + }] + ) + + return aggr + + def set_column_filtering(self, checked_values): + """ + Sets dictionary used in '$match' part of MongoDB aggregate + + Args: + checked_values(dict): key:values ({'status':{1:"Foo",3:"Bar"}} + + Modifies: + self._column_filtering : {'status': {'$in': [1, 2, 3]}} + """ + filtering = {} + for key, dict_value in checked_values.items(): + filtering[key] = {'$in': list(dict_value.keys())} + + self._column_filtering = filtering def get_match_part(self): """ @@ -639,10 +715,10 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): (dict) """ return { - "context.subset": 1, - "context.asset": 1, - "context.version": 1, - "context.representation": 1, + "subset": {"$first": "$context.subset"}, + "asset": {"$first": "$context.asset"}, + "version": {"$first": "$context.version"}, + "representation": {"$first": "$context.representation"}, "data.path": 1, "files": 1, 'files_count': 1, @@ -721,7 +797,7 @@ class SyncRepresentationDetailModel(_SyncRepresentationModel): ("remote_site", "Remote site"), ("files_size", "Size"), ("priority", "Priority"), - ("state", "Status") + ("status", "Status") ] PAGE_SIZE = 30 @@ -734,7 +810,7 @@ class SyncRepresentationDetailModel(_SyncRepresentationModel): "updated_dt_remote", # remote created_dt "size", # remote progress "context.asset", # priority TODO - "status" # state + "status" # status ] refresh_started = QtCore.Signal() @@ -759,7 +835,7 @@ class SyncRepresentationDetailModel(_SyncRepresentationModel): remote_progress = attr.ib(default=None) size = attr.ib(default=None) priority = attr.ib(default=None) - state = attr.ib(default=None) + status = attr.ib(default=None) tries = attr.ib(default=None) error = attr.ib(default=None) path = attr.ib(default=None) @@ -821,9 +897,9 @@ class SyncRepresentationDetailModel(_SyncRepresentationModel): if role == lib.FailedRole: if header_value == 'local_site': - return item.state == lib.STATUS[2] and item.local_progress < 1 + return item.status == lib.STATUS[2] and item.local_progress < 1 if header_value == 'remote_site': - return item.state == lib.STATUS[2] and item.remote_progress < 1 + return item.status == lib.STATUS[2] and item.remote_progress < 1 if role == Qt.DisplayRole: # because of ImageDelegate diff --git a/openpype/modules/sync_server/tray/widgets.py b/openpype/modules/sync_server/tray/widgets.py index 5071ffa2b0..5719d13716 100644 --- a/openpype/modules/sync_server/tray/widgets.py +++ b/openpype/modules/sync_server/tray/widgets.py @@ -1,6 +1,7 @@ import os import subprocess import sys +from functools import partial from Qt import QtWidgets, QtCore, QtGui from Qt.QtCore import Qt @@ -91,7 +92,7 @@ class SyncProjectListWidget(ProjectListWidget): self.project_name = point_index.data(QtCore.Qt.DisplayRole) menu = QtWidgets.QMenu() - menu.setStyleSheet(style.load_stylesheet()) + #menu.setStyleSheet(style.load_stylesheet()) actions_mapping = {} if self.sync_server.is_project_paused(self.project_name): @@ -150,7 +151,7 @@ class SyncRepresentationWidget(QtWidgets.QWidget): ("files_count", 50), ("files_size", 60), ("priority", 50), - ("state", 110) + ("status", 110) ) def __init__(self, sync_server, project=None, parent=None): @@ -217,6 +218,14 @@ class SyncRepresentationWidget(QtWidgets.QWidget): self.selection_model = self.table_view.selectionModel() self.selection_model.selectionChanged.connect(self._selection_changed) + self.checked_values = {} + + self.horizontal_header = self.table_view.horizontalHeader() + self.horizontal_header.setContextMenuPolicy( + QtCore.Qt.CustomContextMenu) + self.horizontal_header.customContextMenuRequested.connect( + self._on_section_clicked) + def _selection_changed(self, _new_selection): index = self.selection_model.currentIndex() self._selected_id = \ @@ -246,6 +255,136 @@ class SyncRepresentationWidget(QtWidgets.QWidget): self.sync_server, _id, self.table_view.model().project) detail_window.exec() + def _on_section_clicked(self, point): + + logical_index = self.horizontal_header.logicalIndexAt(point) + + model = self.table_view.model() + column_name = model.headerData(logical_index, + Qt.Horizontal, lib.HeaderNameRole) + items_dict = model.get_column_filter_values(logical_index) + + if not items_dict: + return + + menu = QtWidgets.QMenu(self) + + # text filtering only if labels same as values, not if codes are used + if list(items_dict.keys())[0] == list(items_dict.values())[0]: + self.line_edit = QtWidgets.QLineEdit(self) + self.line_edit.setPlaceholderText("Type and enter...") + action_le = QtWidgets.QWidgetAction(menu) + action_le.setDefaultWidget(self.line_edit) + self.line_edit.returnPressed.connect( + partial(self._apply_text_filter, column_name, items_dict)) + menu.addAction(action_le) + menu.addSeparator() + + action_all = QtWidgets.QAction("All", self) + state_checked = 2 + # action_all.triggered.connect(partial(self._apply_filter, column_name, + # items_dict, state_checked)) + action_all.triggered.connect(partial(self._reset_filter, column_name)) + menu.addAction(action_all) + + action_none = QtWidgets.QAction("Unselect all", self) + state_unchecked = 0 + action_none.triggered.connect(partial(self._apply_filter, column_name, + items_dict, state_unchecked)) + menu.addAction(action_none) + menu.addSeparator() + + # nothing explicitly >> ALL implicitly >> first time + if self.checked_values.get(column_name) is None: + checked_keys = items_dict.keys() + else: + checked_keys = self.checked_values[column_name] + + for value, label in items_dict.items(): + checkbox = QtWidgets.QCheckBox(str(label), menu) + if value in checked_keys: + checkbox.setChecked(True) + + action = QtWidgets.QWidgetAction(menu) + action.setDefaultWidget(checkbox) + + checkbox.stateChanged.connect(partial(self._apply_filter, + column_name, {value: label})) + menu.addAction(action) + + self.menu = menu + self.menu_items_dict = items_dict # all available items + self.menu.exec_(QtGui.QCursor.pos()) + + def _reset_filter(self, column_name): + """ + Remove whole column from filter >> not in $match at all (faster) + """ + if self.checked_values.get(column_name) is not None: + self.checked_values.pop(column_name) + self._refresh_model_and_menu(column_name, True, True) + + def _apply_filter(self, column_name, values, state): + """ + Sets 'values' to specific 'state' (checked/unchecked), + sends to model. + """ + self._update_checked_values(column_name, values, state) + self._refresh_model_and_menu(column_name, True, False) + + def _apply_text_filter(self, column_name, items): + """ + Resets all checkboxes, prefers inserted text. + """ + self._update_checked_values(column_name, items, 0) # reset other + text_item = {self.line_edit.text(): self.line_edit.text()} + self._update_checked_values(column_name, text_item, 2) + self._refresh_model_and_menu(column_name, True, True) + + def _refresh_model_and_menu(self, column_name, model=True, menu=True): + """ + Refresh model and its content and possibly menu for big changes. + """ + if model: + self.table_view.model().set_column_filtering(self.checked_values) + self.table_view.model().refresh() + if menu: + self._menu_refresh(column_name) + + def _menu_refresh(self, column_name): + """ + Reset boxes after big change - word filtering or reset + """ + for action in self.menu.actions(): + if not isinstance(action, QtWidgets.QWidgetAction): + continue + + widget = action.defaultWidget() + if not isinstance(widget, QtWidgets.QCheckBox): + continue + + if not self.checked_values.get(column_name) or \ + widget.text() in self.checked_values[column_name].values(): + widget.setChecked(True) + else: + widget.setChecked(False) + + def _update_checked_values(self, column_name, values, state): + """ + Modify dictionary of set values in columns for filtering. + + Modifies 'self.checked_values' + """ + checked = self.checked_values.get(column_name, self.menu_items_dict) + set_items = dict(values.items()) # prevent dictionary change during iter + for value, label in set_items.items(): + if state == 2: # checked + checked[value] = label + elif state == 0 and checked.get(value): + checked.pop(value) + + self.checked_values[column_name] = checked + def _on_context_menu(self, point): """ Shows menu with loader actions on Right-click. @@ -291,17 +430,17 @@ class SyncRepresentationWidget(QtWidgets.QWidget): else: self.site_name = remote_site - if self.item.state in [lib.STATUS[0], lib.STATUS[1]]: + if self.item.status in [lib.STATUS[0], lib.STATUS[1]]: action = QtWidgets.QAction("Pause") actions_mapping[action] = self._pause menu.addAction(action) - if self.item.state == lib.STATUS[3]: + if self.item.status == lib.STATUS[3]: action = QtWidgets.QAction("Unpause") actions_mapping[action] = self._unpause menu.addAction(action) - # if self.item.state == lib.STATUS[1]: + # if self.item.status == lib.STATUS[1]: # action = QtWidgets.QAction("Open error detail") # actions_mapping[action] = self._show_detail # menu.addAction(action) @@ -467,7 +606,7 @@ class SyncRepresentationDetailWidget(QtWidgets.QWidget): ("remote_site", 185), ("size", 60), ("priority", 25), - ("state", 110) + ("status", 110) ) def __init__(self, sync_server, _id=None, project=None, parent=None): @@ -579,7 +718,7 @@ class SyncRepresentationDetailWidget(QtWidgets.QWidget): self.item = self.table_view.model()._data[point_index.row()] menu = QtWidgets.QMenu() - menu.setStyleSheet(style.load_stylesheet()) + #menu.setStyleSheet(style.load_stylesheet()) actions_mapping = {} actions_kwargs_mapping = {} @@ -604,7 +743,7 @@ class SyncRepresentationDetailWidget(QtWidgets.QWidget): actions_kwargs_mapping[action] = {'site': site} menu.addAction(action) - if self.item.state == lib.STATUS[2]: + if self.item.status == lib.STATUS[2]: action = QtWidgets.QAction("Open error detail") actions_mapping[action] = self._show_detail menu.addAction(action) From 313e0fbf1a074f8e8ced117ed67a720a2e1a534b Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Fri, 16 Apr 2021 10:12:43 +0100 Subject: [PATCH 037/180] Support for grouping and changed the update process --- .../hosts/blender/plugins/load/load_model.py | 43 +++++++++---------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/openpype/hosts/blender/plugins/load/load_model.py b/openpype/hosts/blender/plugins/load/load_model.py index db12585efb..168bdf9321 100644 --- a/openpype/hosts/blender/plugins/load/load_model.py +++ b/openpype/hosts/blender/plugins/load/load_model.py @@ -262,7 +262,10 @@ class CacheModelLoader(plugin.AssetLoader): def _remove(self, objects, container): for obj in list(objects): - bpy.data.meshes.remove(obj.data) + if obj.type == 'MESH': + bpy.data.meshes.remove(obj.data) + elif obj.type == 'EMPTY': + bpy.data.objects.remove(obj) bpy.data.collections.remove(container) @@ -290,18 +293,12 @@ class CacheModelLoader(plugin.AssetLoader): view_layer_collection.objects.unlink(obj) name = obj.name - data_name = obj.data.name obj.name = f"{name}:{container_name}" - obj.data.name = f"{data_name}:{container_name}" - # Blender handles alembic with a modifier linked to a cache file. - # Here we create the modifier for the object and link it with the - # loaded cache file. - modifier = obj.modifiers.new( - name="MeshSequenceCache", type='MESH_SEQUENCE_CACHE') - cache_file = bpy.path.basename(libpath) - modifier.cache_file = bpy.data.cache_files[cache_file] - modifier.object_path = f"/{name}/{data_name}" + # Groups are imported as Empty objects in Blender + if obj.type == 'MESH': + data_name = obj.data.name + obj.data.name = f"{data_name}:{container_name}" if not obj.get(blender.pipeline.AVALON_PROPERTY): obj[blender.pipeline.AVALON_PROPERTY] = dict() @@ -418,6 +415,8 @@ class CacheModelLoader(plugin.AssetLoader): ) objects = obj_container.all_objects + container_name = obj_container.name + normalized_collection_libpath = ( str(Path(bpy.path.abspath(collection_libpath)).resolve()) ) @@ -433,16 +432,17 @@ class CacheModelLoader(plugin.AssetLoader): self.log.info("Library already loaded, not updating...") return - # Check if the cache file has already been loaded - if bpy.path.basename(str(libpath)) not in bpy.data.cache_files: - bpy.ops.cachefile.open(filepath=str(libpath)) + parent = plugin.get_parent_collection(obj_container) - # Set the new cache file in the objects that use the modifier - for obj in objects: - for modifier in obj.modifiers: - if modifier.type == 'MESH_SEQUENCE_CACHE': - cache_file = bpy.path.basename(str(libpath)) - modifier.cache_file = bpy.data.cache_files[cache_file] + self._remove(objects, obj_container) + + obj_container = self._process( + str(libpath), container_name, parent) + + collection_metadata["obj_container"] = obj_container + collection_metadata["objects"] = obj_container.all_objects + collection_metadata["libpath"] = str(libpath) + collection_metadata["representation"] = str(representation["_id"]) def remove(self, container: Dict) -> bool: """Remove an existing container from a Blender scene. @@ -478,7 +478,4 @@ class CacheModelLoader(plugin.AssetLoader): bpy.data.collections.remove(collection) - # We should delete the cache file used in the modifier too, - # but Blender does not allow to do that from python. - return True From b3cfb1b68ccf9a7e673eca818e6bbbeb1e65850d Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Fri, 16 Apr 2021 11:48:52 +0100 Subject: [PATCH 038/180] Added loader for Alembic --- .../unreal/plugins/load/load_staticmeshfbx.py | 151 ++++++++++++++++++ 1 file changed, 151 insertions(+) diff --git a/openpype/hosts/unreal/plugins/load/load_staticmeshfbx.py b/openpype/hosts/unreal/plugins/load/load_staticmeshfbx.py index dbea1d5951..b9efb6c0fc 100644 --- a/openpype/hosts/unreal/plugins/load/load_staticmeshfbx.py +++ b/openpype/hosts/unreal/plugins/load/load_staticmeshfbx.py @@ -155,3 +155,154 @@ class StaticMeshFBXLoader(api.Loader): if len(asset_content) == 0: unreal.EditorAssetLibrary.delete_directory(parent_path) + +class StaticMeshAlembicLoader(api.Loader): + """Load Unreal StaticMesh from Alembic""" + + families = ["model", "unrealStaticMesh"] + label = "Import Alembic Static Mesh" + representations = ["abc"] + icon = "cube" + color = "orange" + + def load(self, context, name, namespace, data): + """ + Load and containerise representation into Content Browser. + + This is two step process. First, import FBX to temporary path and + then call `containerise()` on it - this moves all content to new + directory and then it will create AssetContainer there and imprint it + with metadata. This will mark this path as container. + + Args: + context (dict): application context + name (str): subset name + namespace (str): in Unreal this is basically path to container. + This is not passed here, so namespace is set + by `containerise()` because only then we know + real path. + data (dict): Those would be data to be imprinted. This is not used + now, data are imprinted by `containerise()`. + + Returns: + list(str): list of container content + """ + + # Create directory for asset and avalon container + root = "/Game/Avalon/Assets" + asset = context.get('asset').get('name') + suffix = "_CON" + if asset: + asset_name = "{}_{}".format(asset, name) + else: + asset_name = "{}".format(name) + + tools = unreal.AssetToolsHelpers().get_asset_tools() + asset_dir, container_name = tools.create_unique_asset_name( + "{}/{}/{}".format(root, asset, name), suffix="") + + container_name += suffix + + unreal.EditorAssetLibrary.make_directory(asset_dir) + + task = unreal.AssetImportTask() + + task.set_editor_property('filename', self.fname) + task.set_editor_property('destination_path', asset_dir) + task.set_editor_property('destination_name', asset_name) + task.set_editor_property('replace_existing', False) + task.set_editor_property('automated', True) + task.set_editor_property('save', True) + + # set import options here + # TODO: it seems that Unreal is ignoring any setting from python, + # at least in Unreal 4.24. Need to test in 4.26. + options = unreal.AbcImportSettings() + options.set_editor_property( + 'import_type', unreal.AlembicImportType.STATIC_MESH) + + task.options = options + unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task]) # noqa: E501 + + # Create Asset Container + lib.create_avalon_container( + container=container_name, path=asset_dir) + + data = { + "schema": "openpype:container-2.0", + "id": pipeline.AVALON_CONTAINER_ID, + "asset": asset, + "namespace": asset_dir, + "container_name": container_name, + "asset_name": asset_name, + "loader": str(self.__class__.__name__), + "representation": context["representation"]["_id"], + "parent": context["representation"]["parent"], + "family": context["representation"]["context"]["family"] + } + unreal_pipeline.imprint( + "{}/{}".format(asset_dir, container_name), data) + + asset_content = unreal.EditorAssetLibrary.list_assets( + asset_dir, recursive=True, include_folder=True + ) + + for a in asset_content: + unreal.EditorAssetLibrary.save_asset(a) + + return asset_content + + def update(self, container, representation): + name = container["asset_name"] + source_path = api.get_representation_path(representation) + destination_path = container["namespace"] + + task = unreal.AssetImportTask() + + task.set_editor_property('filename', source_path) + task.set_editor_property('destination_path', destination_path) + # strip suffix + task.set_editor_property('destination_name', name) + task.set_editor_property('replace_existing', True) + task.set_editor_property('automated', True) + task.set_editor_property('save', True) + + # set import options here + # TODO: it seems that Unreal is ignoring any setting from python, + # at least in Unreal 4.24. Need to test in 4.26. + options = unreal.AbcImportSettings() + options.set_editor_property( + 'import_type', unreal.AlembicImportType.STATIC_MESH) + + task.options = options + # do import fbx and replace existing data + unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task]) + container_path = "{}/{}".format(container["namespace"], + container["objectName"]) + # update metadata + unreal_pipeline.imprint( + container_path, + { + "representation": str(representation["_id"]), + "parent": str(representation["parent"]) + }) + + asset_content = unreal.EditorAssetLibrary.list_assets( + destination_path, recursive=True, include_folder=True + ) + + for a in asset_content: + unreal.EditorAssetLibrary.save_asset(a) + + def remove(self, container): + path = container["namespace"] + parent_path = os.path.dirname(path) + + unreal.EditorAssetLibrary.delete_directory(path) + + asset_content = unreal.EditorAssetLibrary.list_assets( + parent_path, recursive=False + ) + + if len(asset_content) == 0: + unreal.EditorAssetLibrary.delete_directory(parent_path) From 74fe75264072db7c74c00515face4490a4f731b5 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Fri, 16 Apr 2021 11:53:41 +0100 Subject: [PATCH 039/180] Hound fixes --- openpype/hosts/unreal/plugins/load/load_staticmeshfbx.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/unreal/plugins/load/load_staticmeshfbx.py b/openpype/hosts/unreal/plugins/load/load_staticmeshfbx.py index b9efb6c0fc..539edee286 100644 --- a/openpype/hosts/unreal/plugins/load/load_staticmeshfbx.py +++ b/openpype/hosts/unreal/plugins/load/load_staticmeshfbx.py @@ -156,6 +156,7 @@ class StaticMeshFBXLoader(api.Loader): if len(asset_content) == 0: unreal.EditorAssetLibrary.delete_directory(parent_path) + class StaticMeshAlembicLoader(api.Loader): """Load Unreal StaticMesh from Alembic""" @@ -215,7 +216,7 @@ class StaticMeshAlembicLoader(api.Loader): task.set_editor_property('save', True) # set import options here - # TODO: it seems that Unreal is ignoring any setting from python, + # TODO: it seems that Unreal is ignoring any setting from python, # at least in Unreal 4.24. Need to test in 4.26. options = unreal.AbcImportSettings() options.set_editor_property( @@ -268,7 +269,7 @@ class StaticMeshAlembicLoader(api.Loader): task.set_editor_property('save', True) # set import options here - # TODO: it seems that Unreal is ignoring any setting from python, + # TODO: it seems that Unreal is ignoring any setting from python, # at least in Unreal 4.24. Need to test in 4.26. options = unreal.AbcImportSettings() options.set_editor_property( From 45aec6ebbc498c6b2e7b933f31b5c373359ce0f7 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Fri, 16 Apr 2021 14:36:50 +0100 Subject: [PATCH 040/180] Updated comments --- openpype/hosts/unreal/plugins/load/load_staticmeshfbx.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/unreal/plugins/load/load_staticmeshfbx.py b/openpype/hosts/unreal/plugins/load/load_staticmeshfbx.py index 539edee286..07961a9140 100644 --- a/openpype/hosts/unreal/plugins/load/load_staticmeshfbx.py +++ b/openpype/hosts/unreal/plugins/load/load_staticmeshfbx.py @@ -216,8 +216,7 @@ class StaticMeshAlembicLoader(api.Loader): task.set_editor_property('save', True) # set import options here - # TODO: it seems that Unreal is ignoring any setting from python, - # at least in Unreal 4.24. Need to test in 4.26. + # Unreal 4.24 ignores the settings. It works with Unreal 4.26 options = unreal.AbcImportSettings() options.set_editor_property( 'import_type', unreal.AlembicImportType.STATIC_MESH) @@ -269,8 +268,7 @@ class StaticMeshAlembicLoader(api.Loader): task.set_editor_property('save', True) # set import options here - # TODO: it seems that Unreal is ignoring any setting from python, - # at least in Unreal 4.24. Need to test in 4.26. + # Unreal 4.24 ignores the settings. It works with Unreal 4.26 options = unreal.AbcImportSettings() options.set_editor_property( 'import_type', unreal.AlembicImportType.STATIC_MESH) From 0949c2c68af7d89d0f940bf2e7d3886d84ac3510 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 19 Apr 2021 12:11:15 +0200 Subject: [PATCH 041/180] AE added scene duration validation --- openpype/hosts/aftereffects/api/__init__.py | 68 ++++++++++- .../plugins/publish/collect_render.py | 8 +- .../publish/validate_scene_settings.py | 110 ++++++++++++++++++ 3 files changed, 183 insertions(+), 3 deletions(-) create mode 100644 openpype/hosts/aftereffects/plugins/publish/validate_scene_settings.py diff --git a/openpype/hosts/aftereffects/api/__init__.py b/openpype/hosts/aftereffects/api/__init__.py index 9a80801652..7ad10cde25 100644 --- a/openpype/hosts/aftereffects/api/__init__.py +++ b/openpype/hosts/aftereffects/api/__init__.py @@ -5,7 +5,7 @@ import logging from avalon import io from avalon import api as avalon from avalon.vendor import Qt -from openpype import lib +from openpype import lib, api import pyblish.api as pyblish import openpype.hosts.aftereffects @@ -81,3 +81,69 @@ def uninstall(): def on_pyblish_instance_toggled(instance, old_value, new_value): """Toggle layer visibility on instance toggles.""" instance[0].Visible = new_value + + +def get_asset_settings(): + """Get settings on current asset from database. + + Returns: + dict: Scene data. + + """ + asset_data = lib.get_asset()["data"] + fps = asset_data.get("fps") + frame_start = asset_data.get("frameStart") + frame_end = asset_data.get("frameEnd") + handle_start = asset_data.get("handleStart") + handle_end = asset_data.get("handleEnd") + resolution_width = asset_data.get("resolutionWidth") + resolution_height = asset_data.get("resolutionHeight") + duration = frame_end + handle_end - min(frame_start - handle_start, 0) + entity_type = asset_data.get("entityType") + + scene_data = { + "fps": fps, + "frameStart": frame_start, + "frameEnd": frame_end, + "handleStart": handle_start, + "handleEnd": handle_end, + "resolutionWidth": resolution_width, + "resolutionHeight": resolution_height, + "duration": duration + } + + try: + # temporary, in pype3 replace with api.get_current_project_settings + skip_resolution_check = ( + api.get_current_project_settings() + ["plugins"] + ["aftereffects"] + ["publish"] + ["ValidateSceneSettings"] + ["skip_resolution_check"] + ) + skip_timelines_check = ( + api.get_current_project_settings() + ["plugins"] + ["aftereffects"] + ["publish"] + ["ValidateSceneSettings"] + ["skip_timelines_check"] + ) + except KeyError: + skip_resolution_check = ['*'] + skip_timelines_check = ['*'] + + if os.getenv('AVALON_TASK') in skip_resolution_check or \ + '*' in skip_timelines_check: + scene_data.pop("resolutionWidth") + scene_data.pop("resolutionHeight") + + if entity_type in skip_timelines_check or '*' in skip_timelines_check: + scene_data.pop('fps', None) + scene_data.pop('frameStart', None) + scene_data.pop('frameEnd', None) + scene_data.pop('handleStart', None) + scene_data.pop('handleEnd', None) + + return scene_data diff --git a/openpype/hosts/aftereffects/plugins/publish/collect_render.py b/openpype/hosts/aftereffects/plugins/publish/collect_render.py index ba64551283..baac64ed0c 100644 --- a/openpype/hosts/aftereffects/plugins/publish/collect_render.py +++ b/openpype/hosts/aftereffects/plugins/publish/collect_render.py @@ -12,6 +12,7 @@ class AERenderInstance(RenderInstance): # extend generic, composition name is needed comp_name = attr.ib(default=None) comp_id = attr.ib(default=None) + fps = attr.ib(default=None) class CollectAERender(abstract_collect_render.AbstractCollectRender): @@ -45,6 +46,7 @@ class CollectAERender(abstract_collect_render.AbstractCollectRender): raise ValueError("Couldn't find id, unable to publish. " + "Please recreate instance.") item_id = inst["members"][0] + work_area_info = self.stub.get_work_area(int(item_id)) if not work_area_info: @@ -57,6 +59,8 @@ class CollectAERender(abstract_collect_render.AbstractCollectRender): frameEnd = round(work_area_info.workAreaStart + float(work_area_info.workAreaDuration) * float(work_area_info.frameRate)) - 1 + fps = work_area_info.frameRate + # TODO add resolution when supported by extension if inst["family"] == "render" and inst["active"]: instance = AERenderInstance( @@ -86,7 +90,8 @@ class CollectAERender(abstract_collect_render.AbstractCollectRender): frameStart=frameStart, frameEnd=frameEnd, frameStep=1, - toBeRenderedOn='deadline' + toBeRenderedOn='deadline', + fps=fps ) comp = compositions_by_id.get(int(item_id)) @@ -102,7 +107,6 @@ class CollectAERender(abstract_collect_render.AbstractCollectRender): instances.append(instance) - self.log.debug("instances::{}".format(instances)) return instances def get_expected_files(self, render_instance): diff --git a/openpype/hosts/aftereffects/plugins/publish/validate_scene_settings.py b/openpype/hosts/aftereffects/plugins/publish/validate_scene_settings.py new file mode 100644 index 0000000000..cc7db3141f --- /dev/null +++ b/openpype/hosts/aftereffects/plugins/publish/validate_scene_settings.py @@ -0,0 +1,110 @@ +# -*- coding: utf-8 -*- +"""Validate scene settings.""" +import os + +import pyblish.api + +from avalon import aftereffects + +import openpype.hosts.aftereffects.api as api + +stub = aftereffects.stub() + + +class ValidateSceneSettings(pyblish.api.InstancePlugin): + """ + Ensures that Composition Settings (right mouse on comp) are same as + in FTrack on task. + + By default checks only duration - how many frames should be rendered. + Compares: + Frame start - Frame end + 1 from FTrack + against + Duration in Composition Settings. + + If this complains: + Check error message where is discrepancy. + Check FTrack task 'pype' section of task attributes for expected + values. + Check/modify rendered Composition Settings. + + If you know what you are doing run publishing again, uncheck this + validation before Validation phase. + """ + + """ + Dev docu: + Could be configured by 'presets/plugins/aftereffects/publish' + + skip_timelines_check - fill task name for which skip validation of + frameStart + frameEnd + fps + handleStart + handleEnd + skip_resolution_check - fill entity type ('asset') to skip validation + resolutionWidth + resolutionHeight + TODO support in extension is missing for now + + By defaults validates duration (how many frames should be published) + """ + + order = pyblish.api.ValidatorOrder + label = "Validate Scene Settings" + families = ["render.farm"] + hosts = ["aftereffects"] + optional = True + + skip_timelines_check = ["*"] # * >> skip for all + skip_resolution_check = ["*"] + + def process(self, instance): + """Plugin entry point.""" + expected_settings = api.get_asset_settings() + self.log.info("expected_settings::{}".format(expected_settings)) + + # handle case where ftrack uses only two decimal places + # 23.976023976023978 vs. 23.98 + fps = instance.data.get("fps") + if fps: + if isinstance(fps, float): + fps = float( + "{:.2f}".format(fps)) + expected_settings["fps"] = fps + + duration = instance.data.get("frameEndHandle") - \ + instance.data.get("frameStartHandle") + 1 + + current_settings = { + "fps": fps, + "frameStartHandle": instance.data.get("frameStartHandle"), + "frameEndHandle": instance.data.get("frameEndHandle"), + "resolutionWidth": instance.data.get("resolutionWidth"), + "resolutionHeight": instance.data.get("resolutionHeight"), + "duration": duration + } + self.log.info("current_settings:: {}".format(current_settings)) + + invalid_settings = [] + for key, value in expected_settings.items(): + if value != current_settings[key]: + invalid_settings.append( + "{} expected: {} found: {}".format(key, value, + current_settings[key]) + ) + + if ((expected_settings.get("handleStart") + or expected_settings.get("handleEnd")) + and invalid_settings): + msg = "Handles included in calculation. Remove handles in DB " +\ + "or extend frame range in Composition Setting." + invalid_settings[-1]["reason"] = msg + + msg = "Found invalid settings:\n{}".format( + "\n".join(invalid_settings) + ) + assert not invalid_settings, msg + assert os.path.exists(instance.data.get("source")), ( + "Scene file not found (saved under wrong name)" + ) From a1f7b0828abb201359089f9af76fdf549bba99a6 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 20 Apr 2021 11:15:26 +0200 Subject: [PATCH 042/180] hound: suggestion --- openpype/hosts/hiero/plugins/publish/precollect_instances.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/openpype/hosts/hiero/plugins/publish/precollect_instances.py b/openpype/hosts/hiero/plugins/publish/precollect_instances.py index f054971c42..beab55052d 100644 --- a/openpype/hosts/hiero/plugins/publish/precollect_instances.py +++ b/openpype/hosts/hiero/plugins/publish/precollect_instances.py @@ -1,13 +1,10 @@ import pyblish import openpype from openpype.hosts.hiero import api as phiero -from openpype.hosts.hiero.api import lib from openpype.hosts.hiero.otio import hiero_export # # developer reload modules from pprint import pformat -# reload(lib) -# reload(phiero) class PrecollectInstances(pyblish.api.ContextPlugin): From f8fdb979255038048706d39e2a52a1471b54bce4 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Tue, 20 Apr 2021 16:45:29 +0200 Subject: [PATCH 043/180] use correct path to bootstrap repos --- igniter/bootstrap_repos.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/igniter/bootstrap_repos.py b/igniter/bootstrap_repos.py index f624b96125..754a2d2e25 100644 --- a/igniter/bootstrap_repos.py +++ b/igniter/bootstrap_repos.py @@ -285,7 +285,7 @@ class BootstrapRepos: """Get version of local OpenPype.""" version = {} - path = Path(os.path.dirname(__file__)).parent / "openpype" / "version.py" + path = Path(os.environ["OPENPYPE_ROOT"]) / "openpype" / "version.py" with open(path, "r") as fp: exec(fp.read(), version) return version["__version__"] From 1d047b968c90b94fd7db0242b20794cb32556f7d Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Tue, 20 Apr 2021 16:45:38 +0200 Subject: [PATCH 044/180] add macos icon --- igniter/openpype.icns | Bin 0 -> 126349 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 igniter/openpype.icns diff --git a/igniter/openpype.icns b/igniter/openpype.icns new file mode 100644 index 0000000000000000000000000000000000000000..792f819ad9b6cbe986c6d4346e3d3f3816937671 GIT binary patch literal 126349 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 z zBeB1rU|uCq%>jE-A@7!kcA<%D+m;V{AD}pkx1ityO87gQtnH6^{8T+sn|1Rum9dr= zcPh>-&q%XWjRLLlcRA)ZS(h3YdUnsSx#}_fWC~Qx5w89o^0%Be#py~uRd>=&;6g9! z$fcc$lW)HJ0Ly|c$2R{&cs-L$lm{S+q8FLw>;jY(JAA=6*HCM5ZQobG1R}{^;BE`Y z2eK8$bxj#}4#CCKa3| zkp_iy)aMfkHK-+Vv+XcZgW;D3ilqKJvW>zhHG-c=xs7)m@-%VXWkKU93!uL$w6%SVL;2sg?Tc2G7b5;TrEs-UfwJe=a(9@Yuez zNs3JJ=IZAqCi!Cfqq5NyTaw*;S~;Abqd(!V7uF`xdJ~(T9J>cN(y~fJDPdB(oa@bA z7FKW&9;oDIYVTl+F7or5cJ6#0fm$Z+{?YR#hJ;l zr^Q_l2j1#W)A0~0XtnByd<9XlVW^ZVB-rB;>&evqgw2~3_y0f?&G1uQ!y84hUIxs# z_5CirxNM%$AnY1JJcv*$vB?E68+#c^l4QuTS~Mx(JY>WyEIW?bBbaB{oOwlR2$Kf9 z#PP>iz`P)><6bl`sqnQ#av_Uy0{Gr2S;PX##ddhN(3kOxIS3po1*-_W8ay>dxeV-ShcRe=U+-fsk6@!ltahOmUo_sr3x4 zzgDJp5WW$G?n%dOOA%y_PUm@DR8M{xoE_e(T(ESYxYdc}HSfZwKe`zZROdex78P|4 ztls%s3EGk4pk`(AIXoLk0kK6`ht#XauarF|W`SZMkD&F^{NRm5B5t2l@>`H!_DtE# zE>>A<5=G?&ImDQVMZvv+^_=rLjg#17g_0gM)gKGxc9?a^FK4R}E7Z@E{{}B10>66? zSF+>mo5U`qqxnv?g)HHYF}Sre9V9D}JhH(NKDIc%0DWN>|A5%%L6WO-x%$pv3GHs! z2!OV5QkV_d;N)F?)$fAGUj4Y3&2Su2VsR{mwV16FKKYkD%GFP9UNy zI5b#xuZYU!#4oego;d`d58@?ck*w5 zQaX$&3rq_6pI>Z%`Kr||Qwkbnj-y_MaLb=aD>V7gzKyO*B6s~&&|TpP26ft;dHt_D z{aIAPiN==V)Nj;^3md7nz656Zn~#1hw8AOBD>xnYV1FX!c#RI;gy^3``&PIN*U#}| zA8cfHvpY_Vwm^g!DGj;6^Q_il&Xm*sC;Ov;6Ey&poorU`AD`_0G*GvVNlV-bu7Y)9 zaYc1s8_`MLvx*JHdqF;AkUK|e9DU{Z)A!J!a(vQdtC#2>XQ`Q9*`Mt3Wz{=CgT|-=ST)QM5qs>xRL#!ES zJ%hiq%8_oN;5z;G1HgamAyqkwlZ86pCXEBxe%!{>RCme2V28!jbG%%C5 zPtzsWW_yyrTMFnpOO0aI-vgr$l5CvK>kT#r-FOk-6s#ineIdFDiTKgT&7s2u9)NMR@>Cnq_dcT^HsoUoRAkv=Du*YX=e zNxykY9vYHkY?G@l^Mj-oST#*I6p!ZJN{uqagIJwzg;DK%BRWGDlC0VHjm*?Wkg)GCRVsIrAE$kFe8lae-blyof$J>N&u_W zcT9WYaPPe(CjukbVxkgoyo7qVpZ2&G@SfvL4XEhd@E2+wi(FI)b4FPlP(fsHdN6DP zyW5z1@v9=n2KL{OkdHc0pUHn_#8H0I%4%jk_ygG-lwIN$Gbn%z2-xdV<^KdK!rux$VZ{Q=rLe$2$=Z2pMa7n~% zGo(E^fc-0$rjMa~@zfqm=B=Qw6bBj(F&fk>bzy^MWT87_F~6aPfNKQa2ur&Q zu6a7C(ql@3%VyKp1H2`#ga*qmNazR!mE{Dt?vnaS@J#-h5ef4Xdw}qM&@T*4_-chD z@%j2Ry#FjFbHlET?KcS20Y9ud>7K;iPe3C5{XYg7xAD*W;?UC731sxPHENxI^z*D1 z!hfbD}!g>Y`Ut zuf#}4^zx2CXfsc*%zSUbJ${E)zMF5j!Dro*?X(hh(zyr*;wa$x_|JvYZ5!q7Ae1@R z5lRSxDmm)Nglev!Vx&;{miR}JV$Vw`IS4gOerWu6#9+;)u;d{Gc6WSzv(m`+ypHOK zd34?0q7A4`uIidBnBcRo))*o?Es^)wib727Y=5m!)4hC;tbo-g&6 zLlV9YgmP7{jb?4gh1rKPzFZO>lPswN=$LpB^8z1z2d(h3&M}2^l=Nc9qU&)tI!L($ zRrf8lU`+JV2Ja<%o!q&ay!-MUiV|G=3k5|eNa|SXDKuWd4!w3T`gJT3bAYpsxXg*= z?KO_Ev_;lt)26s%CYc_o(D0?{3FhDq-Gz=f{mA}KuKA*?y$==hrIFjZj)~lhue>Bs zHf8^H;hWJObBz!J4m3ow4q#_9k_e)d-l*Q97SIp})h1{{8a5GwN(vCeY;dg&>ngT` zH)PfEe5F*RxL3rLK@;;1w)UHQsp6hr#`J00r}xdEy9zt3iiqMFEvg~wH*zxp7Y&vx zme>bFVvz-?n`+P@OfNQuF6A=46&I4pOy&!+6s0kqQA#pa-*0$Jz9PnU0ExSt`J#Ai zT9THby#je^U2!7BdW!)*4aw=KVG%3J8p+FvDaW*pX)|90yl_IV)8M4$!z=zQyAHY0 zcz&?I`%^cFZ%=8&Us?~eaUA3qJ*%nf5stdyn&72D1Wk2FeJygh#uKl?{X(UUb(FtZ zP83942TCPXYbH|hrjBM8u{@&nS`xyrhksg-)J9IE`FJcx>Jo{{D<_I-0iKLYl0~8F zH+)2~vp2!&qT6xK^(lWrYTmileXso?i>9NPFpvEIJxkmhZ6h@g3X$2kqSD`z;sX+~ zEcGJx)}s!~31OC*_dwkm$x_3YqHNVVt|TvHD!dN!Sw&ZF=k|k`=#Tt)OYXeVAi8*x zM8IgPKy;2FBF{hEoG!+X+L@mdNyMQpb&!)MUUdGVIoV@5Tie&I1(v;$Y4n$0-iXce z!-ZYPsS-g8etZc5TGSOhi%=1fh7k)Y32FC?K1tblXdUo}h5nwe;9qBSQ?a-$p3YBG zP$+k&0@;-699jUf3rJOwCLQ0=p)!2J8vUGk%IYn>dbBT9s&X_xf#vJZIaGkn7l5xK z$O;(sqS&s{o8Hvm9tU%N<7D(SC)eeOZ&*IA6$sjV-cfx#4_Dfx9dHy?>)})xX`a=a ztb&av@^tB;-~JK<2KNm$1b(+j-{h1U4HuN`N+RgJzySLmb#otgvbNi`C^NU(6TL78ZZLZc0T#ZG6+vo zgtH;;nQy*5umzUcsUBFB$*S)=*EbEar@ZjD-)~+@GaP#2jkYQ$E=N4TSE{X!c~ak* z`(VjQ-aAR>Kn$Y7`N00l(;M#DlsvEZ z3;N{lMPIU+^T7^y`U|Es1o!#rxdN&#>2N323S`{FK;sdeL?e9#3_j&oM0NE087-&! zb)|I>jB(Vx5$f9?*rgI~9+uD(^!)~$!3>$7+0y;qLzs}Hqb&~q3FTt@y-FSSXP3^t zu2HF1uh31NGa=t`5V_E@_ihi%4C*g9At_yxNuxbXH>jv`{epzb!!5uWX&ZbQ9p4pa zsP*0f?%F6ERD3Xhc)?a?u9A+bZrO^%C>Bi?$QOC_1m{0OKs8$XIf|;oR<_u=;CluS zlZfqteqggXFauyOytCq3a%zBo>+tEf`RncYbqD~_9kr-@!GmqjQ zdxRLN=||l}H1tv!vlav3nL%00J+Ozk5?Svzq=2ym2*3mu|tFuO%d{R>~7fmhh?{!!`(+N2=qkw7)wVD723oV(;+Q3qx2 z;t8Y_SB-R>{yJaCX$#zgB((FE7**p8{{fVpVa3r^tIa-uz#wGbtq<6_w7$Be2o%kD zLH`v;Dn{CH&{}X{?N&VlaDcQ?=ua za{nx?()tRdg%DS`A{}=Y=Iz2c`Yf-S_!*-r(@0blg{_O;99(n?{~(Ky+Zw$BVES8H zxnsvPo+b@R=e_9n1#FE}rSvKvFNS*T8$9kK8MbqqRha{Nz_rPCF@*>jJQNTDvGux4 zh(qUx$9LA`n}2B_3$TKv%YJU1Ql4#Ihgoe&CDj_nn4Nt@)>W%cW=E%`?a(ud(xgR} zVKVHfzWNQ=zP0J#wq{I*YKF0lFiHZp)DO`yDZ~a>*}62FQLwBrf%f5t756%ffw%E+ zi8c8s&$=%qkP(t<%JWer)0%d&gRd|L#obdsc`rVAgrEtO3p`_2H#WOe18e@svNRS)sVS+Aj1H`o5&4&S(s5IqmbEBr9H{AUZ3}P%N+aS zdGMih@SKSfArpA+#|%0NpPRyb)yQG~LNjn3`B9~=p*czL|77Fz*><_S9(icDB?BGq zHXTEC`>s{*Ap5|I090# zhqX*YEo7!-rszuFS|>9*+uT(OSFU|mMr(is7!3EqSTcPALQS$lW~L>O8KqH=k@xQY zV{z!9R>a$bLs>ezCW=icBqFKuwUrN4Hc%h>S3>4f*-B}jF;7my@j{Y-uMNAUM&u04 zP#`}eIeCA1#q$}hq&W@sUr8HeGr}4lhJ+Xet%_kgga2B;66%kbW)ARl*VaYz$k`Z= z4r(+7YpPKjrMZ>|Q!q*FJ{86tZsy^M8HFcJgV!@)s*!W0RS5Xnw#bgGkmDwX!l@Ug*E)y2ZOH4ZT7!i2S|{ODj60ziMw=_#`r^KRFispkeyo{g^+(*83TMZ6}-cbZ?J2!R#7p*hH|! z-~Sf8%#@QMt@hOcDO_v1;41E5CK1ZIPvW`ZU4)Qux1K%U!LPs$z%A_})d*+mOpLKv z42rt{v!G2UQRDe(MZ{_N(}-0&@#d$K^1KoN*3BEFs_3n2Ed?JfeV zzpJ>8^BD4-wxA-JhnAb5BLK6S7$cKc25P;Gbnz&L?($}y&rM!UQGo0hs zE@LNBxO*{8H97X-^{Qlufo=4PC-_2BGLMO0X^h3GfdydMadAaaD&l+~9+5N3TV73V&TAyEJhEwi>~Pb}5EYDX z5(l1kG$3EYxQ7L3_*cW+yL zf68!Oy9|(wNpNC_D@VUNi<{W^Y(49*ymEJ3s#r~O-s~!4c9Ht)t}oV|(LIS-==k~U ziaOLz)^6To_dKC$(zrRx3L#^#CYcC_Q6XUfad6P_l#&)0fRST6RKrt@cNDR>xVu}>i(pK#nfwkOzOw~WI??kI&tgCFw0D)fUMQ*X} zojZdV&GIY&hUMZ20@c$l;OF7k6EICbZ8(119itmUYBvMfLj&Ya(58pjHi$OG_Hr~u zJr6Z217wd%4Eq|DvL%CnDz3>GRz}%B{1?4i`Mbc+d)POikgVa*$%AqTqdf$>>Zs0WiH;ez`rnCUYz zq*o*mbLHmze;G83VqzU57u|!umov*%D62M*1s~<*+WA}UgM?Zbhj#V}H@1QM|9*61 z&#AkYU{)$$pv=c3Gny{GMEe5l_egQQgi2bQ2jT}F?|z>UV(^8SYZU!p>`CJ3_Z-$h zxF&qF?riLW`mnLkWLWKDoI^^wG~Vr1$q^g&Y0xYnh`$s+kE z@Z-Y8D&#V%JPhslq{mv7KDOoUd`LDBS2NTtOtj3Y_syH!EW6Ze12-9x{u zIkc->RJhJt(W%RcSuSU45? z-c^0_uSaOl-sp6s0Uqp5&(q(H5T%g@xEnK~-`}_`9SXB9w!4F!GVz6O4HXLb9;HTi zn==YhP#a!9dU*5%GwR5U^)5aB6ZeciY!Emn2LAzuYc%cp+U8U;*sE!5T?Q!MeW4XW zB`snKdaW%aN&?1MT!M_o@+Jn_pa1{?MG&WG=-1v$(EG}J6;gPZC(I_Ce_7$jm&p#? zH=)7qx)%8**m4GlCKMqmQUOmtri9y*qxx1ykgX!BIj{umL@euo?MyEf%6JFx>{Ur= zkA~QDVa#eyWcxcPl&1}8yLethG48k}sS4qHn6kv3s=;OSk!nD68?By(~s(-INP+^11v{cJd2ibS2ce^X;Gd|RktPuv? z1gF$-p|iho(K_L}wxdT$pfAmJ)0nu(DGUUf)Q+S=YSg@>7F2-1^XM`&i3RRbeFZ*Y zhDm|TJAFH%Yx3}Bg($(gb}WU6(+CJR@AoA%Fb%+%^`8v-9JHau8xhN@F`MSJ!MW74 z`V7@mhRrQ-e6g5dFb&xmNRoXVnkIIX*7lh@G0=S@3-8oJ@B}W{kOvm2wSZ7nU*`xt z1}B*~sOL#(Up_23ZlbqBgRDK4$MRA+5p9lj=7B8{C%a5Dg;fUJU2?rG)n&G14*?J6 zDK`7SfBrCJTB&(0W}~CCKaiurAsX-WoGlPb(%~_oC|H>}W^WY_eyMH;-YIU_hNEa- zeRljaJFGOx&#*|nuEo^!#wR;cZ70FXX`x3DAsCqMS`bF{pt~M=F7xZ7_am`)N zx9(H6htDophRBV^IaUJ(ms&0sbE6L{I^%YHU?upDnNDkgP86}Az8q-&I1S#U^1$jZ z&tX%(#~cz{X8N%u&;nwxspc8>5goL*L^*Gb_TR5IHjYP!LzdGwYTNTe_rFUk^{BWM zXlwjxtA@Cj6)#=aJ){KWSC~&zqv?Vmr(ceA=zA8%y-gfD>H`bGB1whx!Dsvx4X%Cx z*<(iRu{m#Yx{{`tl9_^rhEP|oDFI}4Q=IYQ3xJ*uhQFf=0c%qQ9YZulESb15J{sgB zbu7Aqlf6s%`H)v2O~y~%Y*QzM*;>0aL8}y&rLgzfZ(M!Azw`QfgSs(?HzZ7A!-}YF zdkpExTmJ}|KD1Tg!0)3e#fiIoP#rH)20LBZag0aa=yBaGt-ELejTtdeekH&TE@{D`^*WN@r-0F*H?qYc1RWDtnn-JAuM^xdE@jzUB z^*M$^B85a5E~mieSaPk12xR{kP3C;MI9a%V))UtOxjD!m_xlWdj&ag_v@>ocQBMkF z^Y+NxpQR{sx&b!#rTSt`)d=G>7MtJ!dZ4y;ea03C5=VUaDFV0`n@n>WiyKTpO~_K^ zj37n@p#?r;+N|I@(624_KC+B(1Ksmtni8I=jjOVt5^T!B74&Z+{;~1wc`e=W%$2`T zcOs+>$~gu#U=6tqnud_-^|TsWZz5D*G2MNAbBx>GlB5hMm(keotCitVBPl;Jk4eAy zT9GFD@~x~nrD1wRqm-2)K1T$5+iUZLm@FwU~c@G>{UJDwz9(N_|QtSiP7w=i(z>E0w2C#Y>AuYf1eacL7f@~_sm7vPKqhP2FN zAeucxLOD*-`w!Fj*tiP-y>{H#!$E8@uCNQdk8%ogt5FDJtjZ6=5ZI??bY3XPBCSo& zfOyk{+Xknm;@Oj;R=TjIV#SSI{#nnT+<3Ie6Tbld{kM2jC}3WR{FnQRSNMV*ub}%( z_C*S3w9hzoAD|+*OwX8vijIwb%J?55cwL_#SiNj}h$v?ZXRtr1Xpn(2Yn9sMy!rQS zRcdxwr60XF%vDpcr7sDJ6bwBHV=JKWfb8X=CB>k-)|er%s!PwD_j#5`I#`Z*qY6 zq|`IXa+tx04XOK`W+i@cmH!WPk~7<+rx#PYik&zBaol4KYh;Lj%1f5&{zpePfft3F z^CSM;?cL(|v-9+1th))t6+9qx@PzqJwO?>V7Xe`WSgMyYqn)b|XX|A1HBbnT*tcEH^?M7~g|c3V>TemtpCT31vi3<1_!Ldo|(Gqj$qLg2CGr zaqCR*ZRsMEOOYl2E#Lu=vOgaBqd9|TBBLXGf|J}O{vTQ+O_9ShC66SFGBam*5NP7082?TO`w_NB*{Gnan!G6R_L8Nl@jS2jr?IggS-{`v~A`wgV+8Tkb59$<#3^(e~S}M zu8w2p#v19O!#TNQ%iKIS8uLcBexev3r}cfA(~-;Uu8@(P_5P@%QJ~mG3g*jbw$$u4 ziXE_8zo_|35jx1!dG_8KH25&%KJ3AypEIQ?jC88Osik|tbD|u}X#in$9uaH)=0px;R?E6=(5b z>uI%TqL3pqv_r`25nz6v@$yS`j`qqVahcmPl?8+-7|t;5joNEZW8oq}seOa;513&eJ67*n&8NOj&*RECnm4kkXi>25NFM3+{Tii(SR4vqDzKRMJM30i z@Rz5}a=PfHXqNsvE=IY2 zB+Obz??XN4$@?REm8(jasVK=j)F8tOaK}-&a_@nA;2rMNdp}-wQfeTbDk^5dpkBcZ zqd$0Y2zE-Uwlr92%jY6v0-XT>iNc`gb1dU(@ta)<+btXuf+`aLEa;nZ;MR@W+xEi~ zCZ_W>j}H)l!_43OsAy{WU{4H802wd3Ga{Yrv(7q)R*6C#9a~Zm_s~^MP$2N!g^t)H zp?Std#}&jPMOlY(Vh2l3HY?ge>!&UggoDbchZ6rwBJc*j=EP^3xW_lIx@&Ttj#UO` z;pF9LMFEBrGMRl!oA>MgAn1_B$&nDO3O((A06`}?owI#zY7lw}PVAzk6)cJ0r7OM- z7&s*duynr3DR857vT+&EEH=-DIDViy3Gag4Bl6PKDq zCizA^iP08UlnHWwdb*O8;5_7$6E$=vxW^(Wy5%sly0RxqPeX84{j;ln#%znB36wt@ zHSxT+4&};0Yw-_Af)~HU#xV>{0u7fcKONL#-;1QR-w69ZTvYH0F4dZSeXICF$re8Z@Wp?VmNXZ}=; zV1pNQoyft18_0m-#6GFMZ`VW+Fr3D|2|YF`XVme27HuDPVUR_oi@1{d13h%6BQGp- zxl@miN}goO#I5O4s(Y<7^r|fuYg-%P)yeIS zz;NfT*=9ST73_P0Q=jh&S{{Wp-)FV)tw6^pKj%M9TBLVD8 zcPr<}gPf^%Ba0ao@Ac9f?n-tU-5ZJt^%C9S)kVZ6G{=MN<=X#wEKl(|KnM)c{JRQ4 zMBb0b0g~K*hORnBIPZ?{w+56?6?6^GSf4t=b!0D-vK+>ns*&kzM|cgu-e#u9uzpO~ z;#Sf`-e_epWe|VYJr?yAehF{fq3gJMCZC-}*>eNzWEFHfDA%T&XzH@CU6;gy#F#RJ zenXi5c{ldWiCV_mWH9Qqw}Zt^&w%Ee&9YyA!rFgtLlRDR${M%7Fl@&cg|4}Z1fV8} z`4@TA8S|cJo{axDvwyRkqg8Q2keATr8~N$pq*w{^tw#v9^DXwP2T?#|N9MhX2E0Z< zpAw<6bwEm_*UO-NH!hcvXb`JhVg!_5B*ww-&LUw6ZL8L>d?#T*$>;R z+x2L_>eFuew%_*YKkCp=)vs68vNzSDoL{R&e^$ExTXj#EJ@fCM)uA`lrZ?50Jo)x? z`TDEhR=(d>h~HK1`*z>GPTDxd-n!){n5iKWs7@y@&5=N4l4DD(W`O&arcMK+5E^!%PGjGz z6~E&TCsao_o_=wXRr}_rG&Fiq9@nbGJ)*!sVZ_9eg zY$Rb5|4R~pAgV-&jGQRmt8qeSh;nOM$FM|i_UTKhcX^Uu>o8WZ=zRCH;Ks6yF@&e< z-Xz4D2~QqS-3Lr+6nu@edx#b_t7mRm;i&}u2fwRj)b8n5z6$C7^r@+$j9{8`FP+zQ zalJ8#+c?Tg&`ApUR?Eau%S{)@y*lgx0}(0;8%IExHG1V30RW>SBOm@9*`y~*`2^P4 zA$p#-itcnc=k|oRE`CdT*vdkV5`su91%&FEedu}YRUqHNq-r*c^$zRMS|9|*MRL}2jJgH1F= zdZjIzc)wOe<;8)uc0dmdTNR=_%+%L#)vpOAC!gm1a2VEC1<$Q@(u4TDbEacNImU#Nu;fydQo)k#~iY6 z=l@azT0(&H_aXpK^nL`Z`*P%3mPhSAr4N>QpdyBjCDZo`rv~4v=hfsy%K08;)7gDe zT?gNa=!vYl#3R3eX=Y@!_5fVX72q|j?kGXak${Dm3w7g13dVzs%3;Mn3n>QNTk;RG zBU2OXFoeMcz)|?H>XvZ^ov9rUQF0b5*iBq|nbOb2W`y$Lu&*5bLm_cj@1;=wM$El- zHuTjTBM5Uj>g&EV_Gb9(K|F+_{+uX4U}+W*jVT~-`NN}XyH57r#6>8O8Rc9F{qdbX z+rOf?p!TT4&rVvA!G?z&5IBW1B+{~c7}Bsw9Bfy0#7j^nINtUQzf8RIu48>QIyTjLx(lT0EpW&B1azb9pW+0g_Zy4sz&Nz>7FMrqmnst&+FDGSU=ai( zVH)+}uF>QP3hKFJ?h7UDoE2sXafIbCl~npsn`w z@rz`{5mO=hKlWe|L^8EfKsRv(JHtWwK^o!pXM)UEqoAy6|z zr?Rnh>Cn~wFSNL#UZYJ6QOX$}v*C!1ngihT3;_Ik(rGs3oUkrWt~^O?=;!Go9Pnx# zrhHg7U%Ipfl3P-ow9gwQWd3B%W%mZ+BM>RZ^2vS$w9tsv)8yR*fJD;Z4Ps)A1V21@ zU1mbn%!_nZGaUaU)7|G&Z=A6L{^NDNGj#2SRcSlYm{yUggtoD}+S-$^IUxr^Gy(zn=%COhzPQRuuMDq8E@Q+eMGx&|;AW#t7 z7kkFW)f-{`OArcT-;9C1x(8-mc6UGXz? zSQWKUpC$?K6gECb$6!5ZNQ;yP?LGo%tyrJFBTUG*Y&X&hTm|$ilHdn&@0k`Rk_jXp z8P_UgEku=biD?e$qkFHKiqi`oSPWJ2TQAG#5~i>>kb>g9dSO_8oO1{wk00m zKw%tR;&lN9$mVn{sO*F3(ElVl!=p%;2!tD%p#`s=(?J!qK@+c!eOf(^j!DZ`=r)|Y z=+iTF0^_W^$2BFK{r6~xqA+X)K$_M9qWFd-FEYqr6z!y>m(WA9Ngg1e55!^R>{*`u-ahaTos;QQK;xP^RCEaJ0ur-Gx3(RhaZC1 zsucD^yb-+ibqqS3u_mT{lV0;As(=06DYK4M;v}rNbaXROJ1-!1!5!%i%ycxb3BxM)@%bQ;ozB)hkOD@L2-n57YW=BE4F}Gd&5+qxn_8NW2yEpI zq*s{h0UUF}l%I1=kNc7T71wf}$KU$)?gSPiq0i%1p@z}I9nC9Rz&4>&I+)Q`$2W3( zby7Zzje|NzO*mYFyObo)ARhu;zx4(Q?hi(b5wEO|bnI#ZTDV3@myyzz=FzynmCKq6 z*3{cta-MEt4HCaYFgdRe|5pUO%r?X6Xp&9(1}d%>!YA9mt-ObfY{lGol71W_oaH?Y zCBC)s5kS4NU_cLqsaj&$9G(lI@(;Ov@&FtD1bAGbNzP(HlqJ@d(W7Gm`S z+G_IZf&yilz+dB!{2VbJ0k1G>I2d;#`0;e%AD7)I`baL_#KB~`4&#-8#??25^H#ogv{HN`uh>@}2Pp~i6G(+d`-F}Gfm zk|8z)tJP*F?3#uB4YLN^&nR#RYv+?iw-{bQMs-(lG&(tJXnC=iYiynakHDkJD0pg^MrNTrEt(wtU}TkglEgC?y-U!I z*Z|8u=6Bqjgb*`zwa6vl-xmhZS`}8&qvQ5y(ui&UQw5}@eN)81Z3{{#KV&?zK;6UL zV`G;Z43r$Y%g}&3cDt^c^zu!9wL^HcOt<#iBNq6`KpOB|_r5WpYUn*mQ{$nm+eWmR zLnhv=M}C@C<(q08!uk#d@uV)klGvdTS^DTcKOVX&XnS;d{mRp-_l$yo89vjy%5g$< zwG0Tn?2;Td4@Euug-aOV@j3HH~oW38TEiVGOMTl^P~UaaOuRO9FivU#AWlaTS?b zEH3j!;!OWCS2IAX=_7S0BO~9BRpG4c#VgQ*Cd?$ypg>FVj$a5@!2}B2o6cZTZYX*BpYb5<{c8j6W{s2S z?pSQ)9<6r;>@5a| z(Csz=8MiM1<^!07n<9|+;cJ3|bLK$XxlKbWLssJ!B|8fHgMnwueW*e2{N2=lEo%C1 z*5jdTMElUZkl_PsE&)(g2@OL1oCpjHD1JJ9`pgNF$hFPtgp)+Ad1)Ij8=fqeJKgnU zlJd^eds~kFDvl~{;@Rp_peVSY#zpjNaxqy!gMkE1aPitsWhcXEv_sANrB?xK|8V-z z{ujv)|9|)1rG0sWZ?uZ#ru8TKeW{tUo;rUr(aw*0P8}!qsLg3iV8&jtuyd%;o6Kfk zzKgk}Ej8|THRR5sHoKlKQH-Z{`p|9%_%Tm7`5xN3BEE%x+~ zJ&UY~#;glB#ax8ND2PqbH?BTU)w&z8_Ij&%rtNfM^-0sHV0C?k)4GG+l$&fX!Vw|F zP1zY>LD=&e1)Bbg3zaNq>-mF-mp#rwSjfEhpf!0rH+Z}9liZhC#EZjkjp2)@W_e6* z#0w|T?$_QU$+tKzlMxKQs*VB~-t@~0fFf@Fc~fKcuu5N23O|WYeY%Z2$taf3SXhl# zpFQ=imM``~{5xFpa$6Vy!ElkdP`fE5`&5JO7N@iDopktU?IHN{x-`MVXD5O+S4gjc zpNln}aMj2A_8*F|HHo^wJYYVch0AKDQLr1-hdSTa3MpL;wQDX#O+0N>b1xm*!q$d6 zpNyg&%Vef40rt-&v?AR~-BjusxlySrT*r*9;P7%nL<{Ev3I0ig+mn>zNZJK0oT z5_Wk59D3_7K8AxJkNTvq=q%*I1IAgbd}%CQ{O#JwXRP11Tp zLAv`RL}{0K6+mPY27NRNVxXioPL4f44w_`!G}eI(a-iA!I!N!zIn2tJuX^}L7V-7n z+-NY@fiUB-hi*R_`bn>O8LHB7UGGO+Pm6qYerk{#!|aM^Dimir;t+<E4Dgi5_&34Dcd-ya=j2hTwqRNVrN{p|KJ{)Z9to6E z!*ymRW-I{8+jB_d)6yA!5q=GfefYTkfBM8~!2m?;pZ9Le*N}A*(rJ!=X9q{v#hqdS z@5o-;W_37F90M)-CNbrm3rnbx)R_Q}7A0M#0$$Mj_w=R->?5HJ{~LMrq87OD@W!G}#7X&>O!s zO%yXjzH8ulfZEcG*H_IUv%nMw*D)Ho79oeRYVFg!YcBqL5pRUr@G56RNx^xPz!M4I z)8mb+e`m43HXTNwMDQ0=O(3chMT%?}NpX95WE;P@zzt6{x!E41W<4@q-Vl>NkJe$L!olU%AaP(5~j98P$RtNn6j zfrrb-ILSGvgzxtpsJXYwqx$YJgf%L0xitHuq)kZL58n1E`Z&Jg{!K2TD+Y6$`gIIl z%f?yd&ad;N4?dlUNFi6EhLRlstQLbCcf`36xvy1Sm zR6w`Ld9Ez{=H)yt9E*CNwf}kzCgG>h0rtVEBK<&fEl7QGOopkd*7BTMEY9X$OE?CxzuLv~D-K ztjSTJPEt@m_)sn}wM>6uVO#g92Y73>QKco!27pKU^{C$sJ43Zc1AX}kfzm$6eP_7< z;Q?>?6}Uc32Zs>9^z(H+s5y^ExpI z@m<7m=3;c+3aNl!P9VP|tWO5|rku$yGz9Ni{IMg@xfN27gKm4M&!-mdug#8WcGw0% z1IC@d@3{0+q#-_8kwHm7ZJJ&jU&%TG{I#*)PYaV4^uc0D2lf8Wx*B=JrbQ&&JUZkJ zOtvZiDSr(_Wtm)0E_iPv0cF(>@;h(|vZ*!b$gi8%)3b$6kN}*^wcf;`toWmpdq&Of zPi6`86Zq7;p&u3cXruU)dUPw0SbnCjObjycr@uWDr$AGV1ww;Xx0CDILZh6}FpqLo z9mAo%?)K-6GuRm@h@Avb;bv?zRZZ~soAy8deUB&cNJgX06l;Mr&Bhy&NUwsDvm}>9 z{W{YO8w9MURm?0iM97bd8Hk5}j?vybX?<2Y=i5SG&J!coCa8~aejgJ*QktMZUNIEv z=RxP-;P$O9pX>CrM>c+FRCM&Uk>I6=t_8OUFJorZ@}(^RIyN-t-nNLyc|iLPF;l-1 zdT>g>)b$8gsjS)~vtletMNASwELHnU&a)pxTXm9n=ymqkLc(GMzbMZn)9C4)xLqqA z2Cch zeNe)|KvSxg^y5cKy19d1CJ(^1xShH}U5ur@I!c|F!Iv*jTmUJp#9xS@DW%#Whq!<; z!_+VRji0ssP5Vtr?~`>_-VVI|s1mYI3B*wqnKw3Csj>EDs`eSB^(Z+2>K7tShVub_ zoOi)wk7{uo^e=uN{3q7mDXXVm!MP{*px2iZ8?(&Gvsu3*!SOZ&gwu(zJNh!m7aZ5j zf`>9&V)j*WsS5Bb#v(}y(*~rtuTpvF$9s3Jlh{mbe3wnx+2*{#vN+eftZ%G-Uu4`O zE}g44N0HyIO%2-h8?8-zkLL z;U{3a6%5rR1?ObesDz%)@>dqF%CyeQT4LuMVNpC-o~wgh=sU4$n3p@zu@C9yPhBg( zW+HoZf$}--1_)!0dddaw!z5qnD*LEz8fr!E;zrO*oNcn)eOy8v^v3o~>%}i9EF$LR zD@Qtj)La)p?(S}Y=}U^xWqPoabq;RN@d%dHo z8k>AZ{n{Tdie5VfCu9gUC|D`HcSPt<_U?FS&wCnhq0MLrldXoa?5%3M$Vmzu0E|F$ zzk|m9J!iai5+feQ=Bkn^>i$*%ACg1^CA`KDENE^205@tZ~Sa#$=PFKrhLH z&0G&x3Hx7N!6T;GW0rOWX90xHK?KCV8Qvm3>d-WN|F zjBK{CA|?dOo3tE-1J}NNJf<7nu?%o|xu+*zbrBL^89x~Tq%+TVYj>H1&A(9=ou{QH z3SCU!F(DqIG;-#f=lt;U@7m%29M3MDbuyd!v~|c6_1>Yg;mD6fEo=H;Co5hpKzf+0wRYFRl{BA~&d}D_@B7PQ1Mla%B&_A>v$;|8W5b{k zIAMy5uIE7TN&Ok za%&u)5L6i~>Xy8jM@S}?vpf(N+ckS3vMzftBirqG@d^;+JWOuge?PPh{6sTH9T%Qg z&QM04_o}PAkvdsd79fVZsofs2E>hCoY=BxjXeyeKks*s5#4N@n5fH^MXe9tZR;Y4si>LOzVo#& zapteDavVOG5EebkrC9Lx%}mU*Q8lEe{CI^sPBv@QIAW{Ogj3IfD;4HUGn%1$jVF5i z#MM0jr?Yo7|9K;{2SC5P#+Z-3u1IYkY4DbcC03n_;LvAVH=gP|E?xBSr1`n~9Fm;y zw)IfiM%vG?qJGWPuA;E>*@QgSmxv43Vd6kD*NZX5DJ8yBCF)E8WO zUo6!ea%h+0s`>NDq7htm6SAi4c*tmR2R?K~6beLon^p&yEDWTkan;eB*XUK>OD1Xp zth~4QhJr*lY3tfOEOmv8s;X?%7i&=q!PZ!%C9Mcen#9K)R9Y2T{#$Aj{>O2^!dZag zZX_2Af?%arY7mvSU$J-IP#f(zX{a&r%^Ne~ou{RDHgA4nXbNB`A1|MlFC@3qZ~P5h_Q=1ol()kA#!fqbpysq#xV_%0*23yAVFwr=H6`z$4-zJc3St<>)w7N{jfiA z^UEQQN!;E5+QiINjH|@1YUr>q3ypPQWS-yIBG)Q87v3M7|8tSTP*{myd(sV?3QEur zZ}hr-n;0u#;~w)-2~O&IxjX+}Mf~)f*1!vId874DSSUZmtB2&&95#)T<2qa&WS%IL z(;iazHXO8t+WB9^+Bvpm1;iv-9+7ydWCP~5g^T&?M1ZDZBlvGy&UDv9e?1(m{riXB zgthI^!qr#@Y&_JM!!@A< z%5$FW$Y!AzL-E>ubPts^tAi9H=|!1d!4gb+sKzCpME?&XSLbE_f5Mx*4^b=SmdwX> zhqCHCuZV-QJ_B+u+6S9CX6L;*X0&}Tc$4^GT2pM<>bb$;C-2Mup6P*%#D6k+}H5O98YMn8K3F6kf_fF>Y5Bv+MA z1$A8Zx;@n-`hTV|_kJhImwXqO3Mb^VDp~n_MTOsyWI(c{(A8+sL9gOGR)tUg{e&Tl zg#obf3rl+xEkuq;%q(HAU$=hX$^qQPcx3-P@R)$nl)7~s zzt}~kF&}jnopADdt;sg&Xj#*XzdqclfiE6=MO9z^r6d{wsB&xbF5R`uk-mTHg=#cQ zf#>hVntSu$?77&JL#UXZC~2sK44{1&wzt(3Yv!eSV!w*PR(?bY6?En- zZGT0t9;&emIDg>vFVEgIokb|sy*ne~KpB5q;JXWs5>1wCZ)M-r6tcg* zL9+gb@&}Al8eok!(iU2cUWau`>3b{5%z$eBqeBf?go!EQJiUKc%!CJN_xyK%=f&@` z{J)TjP3lvmjti@3sq5L}+a#JqPfk^Tj(1EMq&TfPlqCFb*V04@Qc~vCxr>Qvc#6Zi z#BqB7}tOZw}o?bYCvma#z z4)n8bhbm5JF@PpfH=@CVUU6Nf>eGdSKfI4XCE)uw_R@KP|8ym$MtF#R?}*l?CV%VU zR}LLFjMJZZh=-m0habT8-y$?BoJ*V>V ziWbE+6@844JycnS)gr6WWjo}dN8`7Se6KHQ!z0sZc6ZPR=>sJ0z*Ihg{UZLV=MCZ) znMle~!r>_xJW%^89!8T=#O1EY!#87X?R;Q5|5EKEL!67qGcryqgmCC#`0tFKXn+Sf z@uomK6>6)Mt2*It3;xGb;gEm-fBzSK3flz>u5ZamEycieI^ld6r8QYZYZ4f^&q!?j zzAE*=SdO0f??*L}Is7WDQgV+x=2>*VNX}v;8bJpVB(7Wfo`n<9ZUKwbK)>F{0_#_)Z?qK8M>HJ?qNIfom>?IjrQmsnea3^v-RFYVq}N=(R1QKP>ez^H|jV*10fes`grF<5UTj!?lAtQPZti9 zh@!DK=y5OJjM9bR%WECanczW>k?l)HGNqzc%+bC^d=`3)+P*I@rDLeJZ3uhO%)MiA~2ZVrP$xbML<&Nq(WPi7^V?1CO zY3!AP$Y$^ag-z(2y?3QLQq{t(lqcZ}19po=L$(Z~_(@(VO* zM9cfDe2_5i`&#u!wBH=Z$C=G#X^5#6jH=2C^#N5yJv~nKFeMvVk9#FsTWH5Rp*B7B zA!oi87|$y0}s>|9|hM@1BA1 zMHct7`DO<{`N+loDL9wrz3gX{`d!9-m6R;|`rb$IhdU29k0*^|=HHD$%#h~b0S>Za zxLY_7`P4j~fA={ODGRK45PA693e)*Q-@$gaQGT6SfP#32^Q3vo9|6?!4#S05LYj}+ z9smi51I2-72prJTEikou_J=xfnH`X~iW%`hlc6(h;OlJpIsTJdd zBQ%H8qqZSGFHZeELx*bms*lv81>N2-)1z&Ttm9NrQ!HINV43FltW^qIY4&Xv%RHrO4T)#!En7{d`u6ryBlyQB)2OKLee4s6bv4UfgPeugri85{7a) z*8hK7UWQk6vQ55%yYeYqS;fDAsk@>OLY9dIkVNrJE5abMC_M_hQ|Z%(#GavkyY*^v z7=K*v&btpsl7y9TV>;D~bfpGg;Zc$<78sxTj z8K;ILCx+}x%4$5=><3`2n_)lyW#3=i+V8uMI|S*Q&t;#*r= zUAvb%Z#Vb)^8{jKG|qYfeedRC+eu6=2~!pRfAQ8Ge7+bu0qJB64zr6v?XbUKgKPAM zK|CB!eJ3;D8Frcq6G%P8BE}Mq*!4VVF`9{TmI~$i_8=)AxMNg7^qBpJSFqU9rmk+m zkdxAlL2M1lK>vUH<5nCQTL*_<_r$Ynm(Ss3|6a-6WZ%B+b2ia9UY*IToHGx=kJeQ& zM^mo6&Q2@E(B}QArPY|`k@Ub*hBhH(;c?1NyoJ7T%54g#euiT7#OaL(E0kXJSP^FU zR=2O{a)cNI9lm?mvcAKNmItxbdJRaf_7X60_Dpjh36{gbTe z@-`mDYdvvCavT)0#Ev^_NB1)*3cG)F&n3`!gP+d|Yk8=%7)rp;o*#4KTEFMD3?RI1 zeQ^X`u9#Jb*b3^egBjCEg+U$;<4jF#45Z8hgt&PTE=7ukXX_>2V>D8_cjj#5AuK9e)?`rRwAjk6$TxNa2*72^@> z2$$P{hTGL+XpZ>CE5L1HUty*q!xI)YR6*qtN(c_zdH~HL{xKC9Ipn|sg$r6fv?&b? z(DY1+ms#fXVJnknI*w3YQ^WDADut4_^{>7Ygw;g!7g zzF~pzk&3z`-547f*^e)EQHP)}Se7axtubEs9k#Tc15g?xNTELm+ z;lMFTKI%E~J0bK&=nx#$%XJK2U+1@So3g`JivYLQLkz35U;D2ig-iV9HpC}JUQjXm zoC<*>lv*?uHxjKdyl7}X2^wwo)0G`phY+)>ut)rYq@X%vA$PkVK^V^Au*2aqQhgqO z{Or$pu&lOQM#R7HcuLBZp8~UBMidqm&wmlX2mKfte>j)KfH$@G=I`@lEsQ?O*}d1W zY6Hh7Z7)!4Az>Id0Mnp=w#Dnq0}|*Jf|T~CWu8Q#v9e@z2p-mWRoEb!w}?j`!&T~W zmDxHu@~?t|UoBqFA#9;RnQ}|bA^vVsY6hBDZe>uDX_;XaOfemGKd@Q*XGrq2%!;2& zVs5~|%>zR*j1~*PYy);nWh76DB$B1o_<)XIrYabGPL?{wK=Gw9*^5Xjw<@;}hRczg z(|+36_H}Q93`*`$IHt&cN2dBIB{W-q{F2S)+Yr86c5%S~rk7o3dRREP72hVdvI>R5 z)~g-Uj9wE|wiV)>FdY!Ih2EpOU+f>qi}!1;AIwWO8QU7Yq|OIcI6koCf_8O)Pl7E$ zYuBALgcjx$5i;>UH!Z&#-`_}I+}_JgSbUK7urIgw`$R2#zbQljy}YO9?dgd{BAPW z=(xk=AOO3u$1W}e+sNANH#@xhbde4rV*~$x|9@>)Dyp&nfB$$%T~!b2p}!lnlimUX zV@0?gC_(X*DkW@Cb<;fwGRMcH{s}9gaRmC-xvbzb2(WZP^w5vo$$i!08;HKE8g)l| z@C1rrT^BBoMAf05-V~$9W)|j&)sEXp4J5=7sDo598%{WgjlU&!!mWr60m(9|<$Ch; z?PIQ9@-`&%7=}3!9;s*evj)q#+$O)J%xdR+>F6FOG)R^jpLMBcU@^=+Wvt?ESF zTMm?PxHN6A+N-b+JSQHjPv|mZo>#_onapq3-QPCS-jV-8mO?j_vw1K4-k;1RZL+)mKZRdH=L#Q4S~*!J zlLqa*&HOaE_j#sz$G^T2!J6iy&ppRG9L!JDpC2g+i2^D)r$^g>a!3SZ#%78^et3pj znqwM0qN9bK3&S0d5kG6mUlk#o7bXrEVxwpb!qiytmYD<&8T2=spHP~V=zF0OD~Sc zTogAF#s3^-3)ch%wjh3G?llGP(v9hPfG6(lGI8RKNnfa-G*GLiNv8%pbC?&@F@AM_ z|9{_NFU!Oy|9pZu%Kv|}I*c;`p!K^2j791No)I+5cCAI#v`nJ>+JI;|Xk+&gW64HZ zgssAn0y!HoTC8^aj0J1+tEX4Poc7Y2)wrZzJmz!_YpeDr+pd+ zqJ$0}0+ci zR5-BQw#(tdq#_r2n-98mDvl2LE7n$nK7-2BN)ho z5Vf-elO5CA0)NplBwGcDRL2|qrPVhO5)}j|9}5~ z{S;0wM9x?LfBe`4LRRdZk7&>Oc76yq6WV6fu);DMH9 zVnfVPPgJnK03HR!{v+^{uH7qshmZkfA}JRYokCr;qdGR$GG8FIYx~A$G}R&)Yp{_1 z3-;$-moiFy^x(^ubIT-wm0TEd7kP$`udZ1t@2Cqr)k2Y@6ypQ5G%=TnKR7d-f*xN(Gy8`G-Ojkcn!SzoULFW5IOY94v>ui=*!y`B;+aRYO1 zP@Wy#ps~I3VYp$}sddO^f{Fif=9xe9eY(+oy$Sc*q;U8I{u_IJy+hw`S3kpa|8UsL z?d#Y69YKE$i~btd{5oC!9X4NX#q)i-+59?e|3g{l(jNPE8vA;UzTTp*w`P0o(|hge zZ~h%WUv9|XZma(XPM^c1yY1NCKf_l)he}`Y)H(A7zTJ;i>Py84cytddpZ(Qo{B&yVV(Vsr0Y*Tb z$^&J#cbGeM<9JqjK0a|`EOfIN9~~lDnx&AoMYKmLdwZK($ZZz;FVEXj<7>gvQp}@{ zZLFeTJi`Sl!SLp;Gvj?dr4X0&Jb>wPJQ3y7q^XStYhcUv#iR%@naYdn$oA{cZmp|~Y8mW^)$2bKAZ8TFW3Mwdo-!ad*L3F-BA?JLlveH3@05`TJ z?3E+dNmh6UN^o=!l$GC~iln*MQ$qyEceWO?hmT?mz#y?gtc-PHb&}t0<~(458u1{v zq6UFbAeyr|Xg&IO*H9u0$ai6R9=P1IN-or@Qj@8Wfe#-+lXVKkJGNh?!9kYpoFlj6 z$efOo7}Gdt61QSPit~@w6yg%+5lXu9BqtQuBrf4pb=32Bf8j=hYU;S)uYw(LRjx2q zjDaR_oe2172r$h+Iu@gR6&0z2Wg3kEo91B+C`k*YcoaHtmOM(GTk@y} zMefA>-XZ?GT2|r}0MYpI18>&F-nfX#bQdfe#|(^2PzgU(5SV<&t+=eKh0)>kCWqgD zI3|%pkpKrs&z$`{L`{mKzth%cI+nxB+7f(nu?==XOn{ni>Wmv?2SFK-$Q#Bb`19IfdXj1()<)A z#mnHaO+t@nDjU(p?kcC3q7eXf5P9r;UPK0jN{Yv(a%$VZy((C2yLkvRZH2-qgoavY z;$f6tCdBTw80&N?S@^BL@xw!ZEbqKD)-s9!o9;gqjxABiO z$gfLC80{zg$aNOBDh>;g(zQ}k!`rJtE;05G-Zzqsd55X}zOH$fC9uUp&gRA(8hK#4 zr7|+ssWl4wU>O)BAIsTGVt9pVkH8$tQ!YgTFtcIcdq)7gk@?fFpAied!}-Ytr7bG?vi9pC zl+6EfO%XoO^;@q5;~oi*6z<{=&(&G_woO$rQl#$N?RrCxjw;Zqs=89lQNh#bZv7Cy zngrl-xBAF!-WhYx579|my+c|LNq#{k9!)4YO|NedPt0x=iW)suwF}+SwZLOY4oQf4 zSp~!`w*A;@VGv{8M5xJ}{3mha4Nha(3B+N|8TQJ{k5R%@a@DmPPRHG(hI5~EZ4Tqd z+I7VWw;RsQ1CIL}v}onMYVu;Z=c7_{a|sq_yZoCULb)BH z7i<~7_)tUJodxi-1ZoL87diQiU7!gT zZc&~nYuZz~m0Z}FMKdLy`^LUh%}y2Q%49?a&yjhU=*z=@0bJ-DXGX2ISzm&jp+^t? zkbZh>(ioPTLex0W23-XQ{DDl7M7HLyMn3rYiG%nBs7h%}*)92MKT?Fv=-;Q@`9so$ zku5?_TwHi2sIV-~(Vl`4ptVvFGU9=7$SgJJEVmDg^+c+JQ1q~`foY8r0j zm*&2|_euBDKj34VE##jItib;L?-++&l#*F)B|@sZUXa3+xirL-V5l zYFD*C&AI*2B|k;ZGT9H8Mz*x2KViMgvWK%NYGiDaH<$vbU_|?Kc>4H8Gdbi9)l&ts z;YKQSH5Cx(N~xlhoyiV&QA_<(^`c!omh-|*rZ0`eKu**J976BZm0)H~=MB4ExcI&V zOk(e}<`YoGedPCzK-T+EtHlt&%jM*C)%%g-TnmmmYHIuylv+=Z9N@M~uvt2{NB&_| zs=^@M;OS60x1_`xhqXsN^|TCvXxx0Dqy5}!b3{j??TW{#B-pa|FR8W8mrU^r5xPmm zXQ&KOu{Q?!nQUPw>jhaT5?{u;bNem&4l0$Io7z>lPvYo-r%@E`E-qDyZrDH9459EO zTt@i^FO20ntv7irZdRv3&3K1@yf;IT+z7v%5!lCI)K+`T#2W1aMcyFKO&tLasPs}ns5W0l`i5m&HmlB?DO z$up=yTt#?PMz9K$GMvfq_H7b&=29stKs1~pAtJTn8{cYeR+gd&;P(d{_@}=ye>q0C zrr7gyYJHD!Cwt6jGBF0v;a->h&z-EVjTWpV0l|b<#pZN%olyRtnwK9kYz07Dm-nH_P(Y_T)Z7ZymgLx6NR97Uzx zD+PPD49*hN$mCUhOgin(xhdewM)=Z3-#`AB<`xZw+REGEU?FO21*-vaXHG+7xzS{0 zSQZNes1DHV0)yAjwEQ2XgMuvTw#dl>+(dXt=ICvv#)vN#m=hpKrq3EAprRUL8w|ZT zIrYTvp>3nsQq~H%I?8d?SW=!#E+1Ft(B@-lqg8GkKgZQ4vUXEq29 ze6NIyiNmbX*5y)5f^owEVHo0>>5o~Xg9nJzXCkhOos9KDvwEPWA&TRuywTc>Qlv;P z;YK+y&5nv*N28rG>rtJ(PGo|6M0P@yzKhI?j@?IH9Lj`je5+xu`JO5Z_njFYAyF6d z-ILq!3xvdJbknLE%VK~gco}^DNX9eEO?wHSZMavt!A5yDjacVnOoekXf_Kr`PeLv4yWAl*iJCfr)wJr$jVLEIE(qW(_yI6CIk22|No%HNx6}#mY>7?k^%kWXP{;;W2(9a`xla z2Npjl=uBCQ)Vv{hEsAJW_qwidaY&I@S~gQOy6l{}r7zvGOzaVd*0#DbBSKRZU07Uv zd)??CK#5;@J-gLovO1S_qN0Bq(hyO*+_;d=1IWiMm~x584h_~hz3RNgp~N5*xE39j zdfEt7zF6$ObPX8e&oaw=E!DzKAxABY(XjcvOIbr^fiiM>$j$XvY&}joB?kAmR4|he zw2mZfwfY5UBT~bXG(&# zNc2>oxegF@l(pC1!{5^8=KBY!5k~7oHh(=t#Yh*^T?lVGuMbwt+Vl>_LHH^xWr=VK(iua|ENl>>DyR=pVuX0xZgLIyLb`_cUtXCJ6hm2S|lI zJN}XkF}PrrDwH##7s4Lkfv9x9rl0{aitteT5RAV&Fv}O`%!R16>Ozofn&;v*9yLs~Uy{!?tpQ4tD_a-lZ{4?cC=@g4Vg6;Fo#fSn}J&Toh9`d*z)H3H7)r zI&d;^o0xp}kkr_zXuPdPmU#w|@GXg{Bm4fB-LC5A{9l%F-;(G9!2_=<4+Y_+P`!dS z09TD-Gab-$c{wODN@uHLLvI(%8ojuYW6i1G?DL@t6FU6||9}5~%tKk)2c9Kt_Y2x- zR~KPDbht7sRtqV#VYsS~SAed*lsK>{EM$v!$rdD)0X3zx+Usrc^w+LBv+xYk&rAdv zZM;dC&a@580@jWqK$|$~OFFU@B>nV)GDSQXd}ERbQz%@W>>z=R93&FaO6?C^W3Z%4 zAO4pLv>5aM7TsL)VUAjjeGTNwE?#iu?L$)*QRAchgPyQ&fQBX0SW#P#cvY4PgQ&NH zAJ6x$Q9-QXqx_VZyQ0uxH!DKctDdM_5`p*Yt$yp;+5eI`i=jF|^bNmbZ zS!I;IixKel@yxd1*RL%%wZ-4cX$}0 z;g~!QEHE4NJokfmgenj!kB%GjhzH&;1z4Xi9%~Nms`0s5k}U=US^{5u%z-Wo85P2e zpqX2~m=H6+^IJv3X0uMRw%(fH(fS%0{_Lm-DVC8IYCFVx{?~P0ARVFMKUF9xQ_<%- zYK21B_v%5uVwkbU(W!5Y>E|q4_E3WE2mRe#QaZY(@M=R;izF_P9~&@+wGP_;#!JF^ zMW7G>4?|N?b^R)$HYITUu3U=0Qt+~B7kO;a*4H*s5F;QjE(63+Irid~TptwyNdg5b zmQLi0NW90g?-ONB(4*tIK>oA}_X^&#fxFFrec0P$iVc zXI7ao{E<0c<8$!!L9rAqNS%3$`o%1$;EZj)+L05Nqk6~W6|;WsLLb>~n{qxT^U`Rr z{#*snBy25$T*PevBfzo3AlxKINhy4ANEX0?H2GT;1ApUp*XR4DWa6gkA0E~Y#5Pxe z&ft+P9Q9>C6+vzi`vMz6VLS_CA&XRtojJx-%p^Fk^3rYpMLoAq4l^#K>ak2!*3NoP z7IOq_9h%a;K8urR((F_*q{VhO)_m&xGEEScP~z`5MQ)Yr@3~?RzA%p4ou%M0gWD5E zsb?m_Wd6qW?TyM5A#9mT*{$5$tp5|Q`>r1` zlsgyJ6Ch;af1E)t?DO1|_LZ(Sb;#*~8DVv<3(bL8n}3*5ILKRX=7Izuu_J3XL*Xah z0;Lau)l`;{1_(_lAK7@kwqk1d(Y}z|yA&^3fR!OCU9wN9T|u^vBr&#QQe_bj5%aXu}FGq5X7WN_1^u(pITD1PJSJnDFz}Behjto{T^XEsv4UxliP};d% z!r93*)*LQ*E8c-Ff0hufB2i&Rfmq|~&WP0iY~KKHWC+e=1jMu?uf}ssNpDY@?MiT3 z*!pO(^fPqUTUdO1+2Y7DN#(zkO-hI%Ep@olO=?lS3WaZzZ}?6!LDbCdGIqnV%@EO1 za`t@JJ>dp6`UIsFK@COt^Oq*(2zbaVn9>9JpsMHVv%}j(X7x=rq~4c5G_!QIgk1sX zS6ANkyk`JhZPbojz906Jy#xPuJDkqdbn9^gyhdF1q{`rGAz~x`V(Xsw1alESfjMIf z8nkB(3KdEXbMfB{x|`Z9|9-n*YQ~)Q(6i%QT5|G6E zDpp+k?`m@ja=@aT26utjlp-iF5J!*99JGgq1{DTp>f0y$ZNc zm89=M(th4Ef?fEXITILeu}r%(z8t6MjE|CQDbx$O`>w1(kHsESMNOgNVT9AyUm_Ny z%mn{BY~jWn5P6^7ms99#K|3jckem5-a+hcr13g=lW#146TaLy%OCGdVZPhfLiPNI2 zKzAw8PS_dgV}k?~Pd4F8&|&3AaoCuH)cOfI&}BArbBK}$zA;+_NRpRyoql1~o!(7X zAC2%)OS-E;Zv1TCr8X$1f$XF)5w+i-PpDh>tYQ}ug?}qbWAutc@5A0{L<**zrzp49 zEiW@lYnl5L`pg`0>`NgL9LKXU{r(eaAIAP@tIlJR8mlNoGDV9^{ds1R**&H zw2gDy+nj2}9PGjAo>dJm9>c;d@N22`)d-h+_P?`3CvuMg1yK3p5U0~oHQ&3F1@){| z)iIqrtXb2fOuOoWis^i!)MHhShY7k3Fd@e1d9o>dsYgi z*zM?{jBg(ROIikDSt7)<&;ZfIr&`mO)DR3NF0byGsxc#TT$m8>)~cajgcFXwaMw<* z?rN_VuZ0L{O2+&+&gxXjO{?N?fiy+}O`-pPkG41SF5b0u*=O2aF((CDK}UD{a{mVl z1d%Z#B|gO2H7u80lEwH2z}peMj^<$Ne7rDS<5*YY;k;lh2vY(&Om5g~h1&T2dkQve zG@>M=8b{8X=*=zlZMlEZzAZ(K>u zt4+3+>$i(FYyy!i*!ik$-dOKIj7I}Ph~=2fCQ5*K@5J#0&w#;0Gqs17lgpSdGMMr?tSFMoXByY%{XUD^=2XYoP%I(3 zamouTcvFxI?EA7zYY_~OrXz|&1iLQ<57d^lf>`~(!a8;hg&oJ3WLo)!#I`Sn2;2!e zZCJg&v5kCx|9`f6q!IntvWnIEcx%6os26s0P+@PQtOA065Z?llM6-{#mH&NID6e}* zK3K2ZBpt}}-RCT&4ieV*Cm29{oU>G*gpPKatDP~)N@NASp9M~>*+unX0k!g5eJJ~W z?s-$CmBRj8>x8JCsEkNY(=thj7C`W~3Zn~0RI5#>M|J9RKQ>EVn7?ug)u^7uAqO41*m5H7T`tsJ& zShY5Ty9!R@P-3G)y;6TPym$9Z?HYl4zu>;Ut-TiQiQ*(k8^H?f!#behtdg^4klfa5 zTIap}2`w$Th@9A;^M||@R#p2IP3rW3g!|&Dm$?9U=;pSkb~c9AAW7(5vJTy`Qi(3} ztTUg8YaM5js0-s}9BQzd;(jAhl24@YfYuEShQMmylIEv3VFZTe9#{xx7VHJSxM04c z*9Eds!v66QP6YWE690H`VgVW=MBkF@3`<$zQ<;K zjr^G(T+~S4rN5KGZIf>eyqPBWHsusW)MyWTvy)Gg7Z$AlCp0cCZ3HSduT)@VnWzzPCkhk5QH7DHIK`7fl)n$?{fu95XEdgFH?b|=+17naq9s9#cAJ5 z^7-sk?PPsDr{_}Ju(U;suXD^DfM*%SfJ3&PD>4JI%mO`kEu74inF!zmWpgdbr@fv2 zIf6p8u!W}ca;(K5qRoUPFyEZ5TkJgvptW-8E%N|fdF8O_x!`GpkLe#1_;8qyhken2 zx@tI^0`XaOi~S#hCgX7!CV<($G#NNA%&M@)`HK}88Gny8lgV~}Myy^3au}pl97TpiuLiw;{AaIab4V}+~1mN=& zKhSO6#~4xC^f!AvA7u!hY^9t}6BgIzfd40`()0+9gP^Z^*hYndvBPCrt5@bWsS`5=oRlM)o^_?u}Mhk2D; z-{x45gI20-M}LjshWjA9AQ9pWfY@uMvN~C3Oip&fT%PZrOcCsCHaAD>yr6r&L)qQctnrjpxKmj3 zwI&zqxbkCOrbU5`V8kHOv!%p2!tJms2l>Yyy~;ZTqXN27eN5=#--mrZdU7 z__(Di4TiMB?FPj(3lZIA9Q$H3nPdDaL!8e41*3RhZhh$((4q5HvTN-~L%kOA=n^?c z%I?6^u4BN3lu6tu`FGd}tk|u{UiPHt)R-^&i=9Mren)p_^q8rIOskGbew-2fOr-<9 zd*OGSl>#msgA?P}BXfW$4YrgW#i05YXps+hK@{Z#$9{0kaEqtGAym&ZBH58Y%W=`t zy#o>rUCG)H9SFby;XPT#bev?Jb==cTML5A{BNrt8O*fZt?}8WIi|2If81>CLA>)dJ z`DXnXirfBRp;AsRcutoqMv#N zfQKR>fT;3OSGS1tfD@2lp%2#i|5VZL{le&4r+2QyrAJ)--<-iu-B|DHMOI(Bw)ZF> zzyE(sUOVmdj;4x>qOLhL_1JjH<6LYcV_k|a+ag|cf=~ek3Z(R+QbLXDQ^~Uld(X6~ zjUL6zsWFBO7utG*qd;Qv7Zx<6XMzZaX70%VKz|oJTDkN8eX^Eme#{`4fvh=#Wg7LE zVWOK>APQI;M_2S5wDMRTVDh=r2~J@;YI;YZ7ExOAum zLU4iz2Nx&_Ow!`1VaGdLJuf366H2><9KUboZ1a{dc=jlTm}b{#T0&0;aoh-&fsmft*%^SJsWj)JXlhz6RMwf;E5DjLh@%-nsRmrQbglQ8-sJo zYLdQmWSpCRxP1cSRifMnr5|<*sWEaIsXcf7iQdHmm5!8ImOvjuA|bNa;a$O80)}+x zW2H@6p#lWNfq@z>K=@O5m&jfg4)OPs2!!Unw;5-2mJ(ymgGa>zo3!&iuH7y$fVgF@N4+eQt^P<(O=^@$LbjVijd$WQze~g! zOU@Oa9r{IV(4M6~okF)^(IL!m=cyC_U9ag&-QugJ?SZIC^;J>ogaJ0i7-F#rm> zMqY?)#Y|3?X$_MtSMU6WJ5_d_if;y4gRq^`>Th zCv4j4cWygdDn#U=w4o5}op|R!RwPLcg9af|yyg&BB2vBW5bGhmk7wB%k)1&5s1%b` zCz!Ir$rIK`fY5@W{PnY}eu(b|1@y4lAm*N?h=f6=I^A-WR@o-EXpJ@>apvIQzQKco! z27pKU^{C$sJ43Zc1AX}kfzm$6eU$kYk;5Q=(~&I)Ew?$ESP-&+6?ol(bvS=LbcXI< z%~7M#oxNXz5rvKw8O*hEbf^0R z_InEnET)1lvE1U&|9{R_nWTfRa9#+?Vm(AUrZhn=BfB7$r!j}LsIQYS?;{G2_3wv( zc7A$s#IkKtCvD@70)2`JI__d|i{dxJnKJ-%9c{f?hLaX#Pzp=2#a#=qw!|QEO-}PJ zUI*2vYM5>`X)e4;v%kbv4Txa$zou2hB@L&-Ki)7l4AF>CNKY%b)=kw)NODGtW8Y!6 zcY;x@HNfkpScPP>=g7b|5s_YjQ-A2=O?EqauA(s%y2in5KWnv+ratc*dM6hmV+i8AUkl z3Z-y*Cq@yTU3IqD#qY_KHUQ1`F4FIDq;W=8;{d+*LKgZu`g*_jZ9F&iH|7xTulEOt z#`FWmuv7lS&FvVOrwbDJQ&5&r)*fF%gfj?LjskUtK<4L>E>Bwc>f^lhl&+Ij6yCxM{)(5db=u;n~q|qUn-Yv)8Pv{|sdgY)S@+ zUl1CMLNVSvG8)R9rjIA5L{nuwk2%BT6b`uOjm>zWlvfO#ZV)|JqApDIEs_#;d_SXq z1cf_Hkl$!u56`s?MyRP@jd9``{>-jTJ77kWC7yUc>u%awZTV4z@n-iG|BTv88b5?6 zNjGtkJMBPE9OA@z&T|uq5E8p} z1MsRtpffkjwkCy)vhy|5ob)X!iL80JhWqSl=nk{Ck1`~jM(L$~yj+380(-dnaISbNm)SdR4tOgIgZTyGhA|UqB;t>4ev}Hu zA^9$hEtyU{ZFu(DNohj!II`-3Un{s~4mxO2{|+DXI$tlVrKJ|13UYh8IYd(_?Ed*< zyiDWsVHiM<4gGENg-n-hUIpHs{VR%z`EG}x*X{MM2SbyQfgqx;m5_V(t0ltwCs1a}G zb*DPI``6=f5;v$&UZIOFhYX6eurJBdrbVT5<+<2ruB4*#yd-~;ve3|!uAJZxYu)`S z%I9<@6aHyU5v7VhK3;QO8q1{U-3!~ce-zWp)Uub+;w&f z(e#QU%L4PtWj5Q1&Un72x=%sYjnIBu(ckw4CTPXu&bmCDKE#J z1i&Uoj#R*W-eXOUOfVa~>b*1633{qwML~8v<(}s-dEh?f#yAEJqq~rbF_d)&kGW;P z)o&^D(yv0r>|fO(t(o35iJ#s8B3(n}^Ctf7{ERVQ^+Ggc7E?&(ze z#o^5+@%CX0V`0;%=&OWtc@1q#{RS8E#wSMCJ;hwA3KO+zTZLZWRy80)FFzeCwRio( zV82&p?41Br1*AZ}&mB}V8O>N3R1CM)grrL)%rft;%BG#HO5*U0@fA}L3_D(^$-Bpb zS4qQ=&{68OfKpv~PBTYjSiG-`Oi9yaL18rx+Eh}_gFItmgaxBB*dTLTGISU#r2#|9InfR=VyQJw_!EHcYQvFM!>VeleVtAQckHnLb5=h#4y z8i+RrMxCAgLs%ImZB6*ITSy(~WcrVcl7QMqci;~-c*)dB`^ez)|5A!L?Dgi ztVxsOhkOYZQnjgdZxzUPygh^mWME>+O`BLk#d^zbwA#)?ER=#)CJb{G*Jcg5zGXL+ zVD^g_@(Z`^*_L(1B@O^FXKPonSGy5sIaIT|w#uxgVFoRrdWmTfn93&Bk4NtJM=EfI zS?ytiW{f=FNCOEjHBOV%7k|CpQk-$i0SA;jkWae-MPRc#Sbt5XE(VsNv=&$yBZ&*A z&I@G%EcdC`D^#L(T>-_1^JQLmN^jNY-xBrnyh0b#HRB{$jszFw{da`= z>1lGiU%KFPxHy6&pr|s`2i^Mh!=)G3#>;9w17+89J>#!_iC0-oOjF65V97RCJ@b_i z){)&Xt5lK?5G+~T#UiCUJ&>BIUZgRyWg)$8+0!Tq6XhA78El-dPicTp;2T81q%|G@ z$2AHRsF`55?1({jYUFqk?iqH#l4t4fDI>j;OZ^cgdUOoX4Wha3}I-+>0#nrf!bs+HBxO(MV%3Xu$rDOUFH6eP<8Jq$(jy?J=(6))%onQR0 z2VP&8BD?LNZY6nXcgoWb7gToipC@c{B%fD3$VLYMSB~A5P44yea-94>kCKnUWWFxY z_n_pg8qkuAKP?}K zx=vu6{g!P(EvjFT@JltKUyFFwiz63JtwD#oXH$Rt=uf_nEzzxRm3I7eV-trddim z%$MKy+jZTgt&=PYR&G49mRq3za*#)PE^WGmpliN)%JbF0(9HS92L7b)Fvd!&ImK=B zSLWC&YQ1YBpKl7Lyx1y=BS$YZO)L!1oR6iKdX5w~4^Pf3q%rt^UL&4jL$%Cd^cWwp zcJqzX!kWF6p{zr9_tJVad3Z$|BiMgb{pmEb zQp?-r*-CTkfwtbLOaPv?+N80y5}I3cgkYG`XJ$*KF?Fz(AaL^xB<5eaTE66s!HA)G z8v9?+bWPW^u5F-(G`PvB@A~7wIdH;}E$j4NR}fLLU}Y_WXQbjj^#T%h!9esE&G4g+ zL+V$Lk>P{!5l@n4(LcV&@Lv|}wz?Ow-HX)wW7nWPfk>Qm%lSF6KdTO`*8M2xeWs)%udNqjQs8Q; zewScKFky!5r?dYvqh8#s0BFL&EM7-%rO1ASR9qDXvq`Zby8HF!Zbg%QSS~!M5`$O^ z5g<25(axRKU+4u)4~Jku=}#ttLWlD z7;l?!CD06C$FRn0`2}7wdli+Y!VRP$RXsgdLLn~qW$no4EjD`SJ?Zg2+cSDk7BP~& zacAYP_R4n6k@=M<9`YS{22Z&vxi8?JW~(gVjIPmUu(N238rum+C2%v=^44y(dHsJs`(D8;WgT{q6HUO%>O&jJpQjMMgk&V;sz|Fq z4@Ap~y5{(&gwB{wbum-@@Rf@9I7LguQ~;>SXPOLvmx_C(ULZ5q%(rz+FO#jXw^B;j zKJaD2={_{Du1Fe(1M7b1J)5KjQ0R1v-TB=UXE2-*j)JwEFf2s{9X>=xwKgHvu)jfRK$4r{B$mI zbw^XYF@nLnxlMHlA?sUdNA+NwnOZtyjqmyYAwjG^n+2+}%(gx|2;|Hn3rEp;W&%ie z{K}WC+3dMpN2|OwklduwgX5(R)Cb9EP-S?VHUBEsRG}Pme^>5PW=`S+d_YD#$GVxr zEE#Di!hUVf-+XkU2gN03S_c$nX}v}yf% zm77X5Y+AQGs!GjR>1W+SHa)%xBS-9$%Q0ZjICJk|w!A!sj)8>G@&n~oRFgt`ov(=n z7$rlCaz?%m)xAuCtxOen#*e-&*4k&ZjeRlK4p}mIy>vIvY02xvb`JJM{&YMiS zDrp{LOer4`uLArq;r=1gvX~-B6CP=vBOiuH?eh!}FQPhl^GQw86-R$?(PY^z83A-$ z-BIK1?B)jXM+m-RSh_!8FCofOBDR7-2Lu47Rb2v#>=Jf05+SnQUv4#Md^644j@hqb zsK`rUM*+uNL#5}Bq9_!@BI#reVO*YFGbPo9NkFAL#&A%!H|^&>pF6$an}hek1lIIq zR&PwxyRf!SeX=z7RTx;U;RIDRphC$JGNzBKee3E>f~F`;H8}?iBZ{;aJUJ>^&LbR$ zk^FSi7`|{^E+9+J#$kK&9$E^fRC{Y#1m8wsnkn}F*H00T3d(M>0ihj$Kca}ac3#8J z`UdIb^Lg8hhd(*ct?K%(SA5Jm#1F*rvqKydunBplQgU3Gk{$WMU8A20;68rggbKR{4yZ9O;;`T3_nb)o5K$6bWa@vhGY!{X8 z@lH+?{O~`)pM8TkvTTBpI$HX4t`@i#Zf?X==7nK-!B->^ z>AFtUQMv%Jc)eNS!_ac(>JIcfR~{_^E`7s5CS4vQ!zMw|g^B>~HNonZ6f8~WVdin6 zi*ZWScuBthkUES8ca)kcs)gr2y>s~p`b@0WB+}_L3>awHQ&&_X#X(nnE^zK;-ZMU) z8z=(F;~PC_9n^orK2EFh0pq*CUEbOv7Mx1Cf^ee>3G;f=*5qFu$wuUSyUGz^6dZRQ zJE)vnJ9l?2x@75ckQ)hvsJ7{3JgWM5sm6A2ep>{5B_VL4Hz#T&x`@Ay!fxlVc)e>{8&3v zq_w}FjC_xzDo~7AoHS1VE}959DeMB`v6D+~)8i;$xz@QlEzlTs9CS72G&K;4`F5iu zAeZ|#ft#oa94{nq35P5)pJbX?y@753;UUE`pFS3{ifnK9Ehhx-| z_3X`s8XD}$*K19{xpU#qXXE!rl7`1E_;E-GF0fuz5bLf8C-BK!^I4-C>j9i_=h;&l z!QDD$nvMK6spZZ>x+TdEKe7nZa58(t1qf0I_HDjVBq>YP@^cnVg3%FV{x*+0u`B(!&#bzQl9X2tcxZVZS6;rCuQ zMp^faj+yYW#$af)Om0W7ovwe%AQ^>UppE*XL%5mXw3Sq9v21SFQq@SJwgnER~$Na+43%qZGD-Z?cULKx|a^) zKR_*}lAtE`WuuhgNyhVMR+=Vq3fQAT6CP(%Pom2)scyt0D&L$qG$W!rKz8CVzSN+o zmuG|z?Lf_E5%SlymeTGW_n6AAVX*v+=YEGf{+4jW{03JEcFNO~D|nB_kyJ9FrbQe+ zj`5k_4SUj_<$$$E^PXCfQ2EqJ?ffLn*;Gw(V2<)GR-fTv5IJ(3OSA4-owA5{2ZK$O zkqf(cdB1kqvH}UPIU@oLvP}`y7_!eudnGG{`J^)$02Sw5TJ^EKAFvCxWdX;vHH0JL zRz-=WF^%uHznm$hI4R=u8K-}van;8*Q_AB{E#`|LFdRgqV(;kEw5*e0O%$&j7%)<) z;QbvQslX19Cohdmbee~+hD&{p=Y%Yy(GxeeAQ0OY0h=DQi(%EGZVBiv%34;ji}ZYY z-*sfHMJvB-%}*5+Oe`DCn-Eh%!Ex|h(HaX&Keft(pSCN3(8gy}qHR8C^)G%&i*KGO z6e5qX_f$*m=c47W=C@oWIqQ#A2PphbGkJGJlieg)_axpxo~Hx}=E zA*hH=NXECw%1#^Ysm|GRFu`dV?S>+4UaPB^%YT%0Ww<=8Ysz=P`4j14tMd(hVIrcYns4Ay+@Eo6accmrJgNNf=n25&qJ5%O1pamUl||o&&$E9?Cg% zPW3~H13Uu?@elJdLU;^{N=9mDSW2)tx(>sFFJH4y7t`2Zgq6!wQb_U8T#Gh6^Vo}h;nqtg|CpH9k49?byDBg;Mm%LbrFQta>b1*DDXo9vhJq( zITX7IT8?dq_=h|F!?TA?e(c4ixKo4g_CzN0vZh%OF!BHh08F}CxeJ!lHA&^wW z8`?5>bz&uz?YGGN6nERYQk%IDc0{#|Yl&Vm50A*yb zr2bIK9G(!pheqJmA0AabR0Alus!sI^BygvlBnpu<_0->O-95>4G?U>JLio@pu@%H} z!yZJP(W}6J)K>LIu0m{r>^RZAxKQJPJh=t<#2sAULe>Ba5m3V~&GBeYg&3wEk`fPV z_#F(JOCdosa-hCOtUBTamqx6`<0-2U6r-y8x7~RNsH#51sxMV%kD>ah%Ry%~BNm{#8;QJdkzlU1dMw zi0mtzt0`b~v-h(luYuOxTzA%XvP_=X2WN|`7!H788Y@RhPw(#P{xnCR(S4enX?9k$ zau&!VLhw6Qf`(w9%exZPUKJHMQk4&cxoXMm!nH6oZSnP8<#3!c2X#AIz4dj;s)&M+ zTK84?AHZIoiSK?@PI}qh^$Qm25nVgA5}1b7gPZnw44MG|9m4Gd*Ao8H15wlP=TLWOMPD=jc4hO zX$%bf?s=Rc3zP_8E7qsS1e#OOtYwd-fhGP%4$&xhggGY{|MnL#*bD^x5+3p{@$Q4I zYut%Dc>`K?YFcygVEl)oIuzecXx*T^DoJQk64E2&bsPdNVPk1+looc9qm6#s%k=PXey)1)-PS>B&27!MLHdcvlq&J zPKTn7S=feML&(0DY;I zkn6s3VRy7CdE6T>b73+BElZT8)m51MNGVhkyNf50e92Gd_yJN|MjsD@Sx7a zX(p{x5bFYtJ<)&$oy;MrRA?5>AN+ncGyo(>SUP0+4q<+Mi3*2RiC7dasl57rUYZv* zOPO38!dH$NFy&x?uM|BThu*Vl#g41jTu3{3`^Y{2?PtaOuw~ET%8;r!JMmn zU2*y2$dKsJ_&EgUSMY!~W{1py zF_;O&NRrp=N25Q9G67K)D&zPVSIG6vTl~EH-%LKJG!9EC_9rRuR^-bud&ctwX5ue% z5ezx%SBx+SXS69q&X#Mr&vPKX8@qb}1Xp*o_!Y03G)aDWH_O<3SL>BsxoV_K<+f57 zSrdOJ#?3qPmnT%(rc>N3Io50G=aQ+E)GOmtDFX9i!ey(vIXnI0Q)$X7qxU7s3HD5% z5Oz4+md?v;=48w%wWY9ga`x#-q6~P3E0he%+2~0*E`Ua$04=&l`@IMoaC+AmJQ*P) zU*ME=YlVJ`s|2UwMpBM&(j3w&1XS6Qm!mN-m_-FzzI8~62viW(geO2pD8O70BIrw2 zM5V9AG~-m$$U!PFijH@jF|$NaU&;GhhnVCYr%vBRzeN0EIDL;P|KlHuX*^x}vh3C< zxQ2c8yH7P*@KTfZj^ep{2DWM6xDf~8r4t7eQtx_Kr5K4Y z?BC$I!R#TI(BX5&Zf2RjJhK$6rOf_-9V~cfMe$%g)?hjki5D(AS&=RZ`*YRC&AVTZ zARDOjkqz0*ZLnSQjsW*R&Bj{l{xF6=`GI~kdC;1hm_zx(r8l=|RQv0K!I&%bPsM0G zcllkWoIN3j8}_Bt0+uop0U`j7hh(lG!ve^=tP<@J)2wVa3*WQGxm@ibEV8}o4yGd{ zC}}Q^{BOu&0qossDMt!#1E4jkrg(cVf!3M1-#ZnwXs+C!!<28d$+RW&BJL8hpEKNq z>K|qnN#^3Q4{&B|+Co(x1|AK1c)NJ$N^D9o4M@H@c>c{{2R2NH=X!BXfK{>`KfJt6$r$er8j*M(n55fipv|*pApwT^t12A*W84NKz*Vo*`q! zdw;ms&cD1Nf$z|O%dH2D*IPRxmrjosTDG2dOAz&X9Oxdi&+9Wkc@l88^&q>ls|(qD!TT?EG5A}%oI*dHa~&&O!X1SK4A&*Atb#F>#% zH)Nx0(w)iLMDQSuBFeY=lU6csXjdm2682Up)2SfPpIpM<3khq) zcnML(ghsG*jIi)_UG|mj1(LKjQ@b&9I~VKJOdl2ZYHlkRJ>DlleUT7Fg?RR~ zt5K6ORNzKsY17zvAO5L*Y_2OS(#PVSSe{x;5AF6S^04;A_JEg1?2N2h|DYOB5;_J6 zW;sbJSkIXZy%fHG8Ur3aeakE&beW~n299=L4{LE(CIl_%*_)`(gm}i!ZdFNDlAvI- zzIHSD(bs+uJ^Ms}zazoUiA8)Zug6y@KlvwF%st#5Bgo2Gkf=|%2$x_?C)$XYST41= z@{evee%%;^8WSO8z8~uKpKlaM;ymUmEg7Cid2Jc%Zl~KA?Y+HoTEaoGity^~q2w|9 z7O?Zh0-y+e=n5IDB_*mfRjVVeiN^Wc#C$z8PPXzz`o~{pIXrS*B=3d{Qd&achRLsx zb&+d7D-Bxm?X3M2{<-R%Y|d01N2#Bmxo|HtPeL-Zi`SL7+t{xaNiWY2&{IefDx-e z8W(_-c{}wqTc|idI|v@zSyI=?`ava^Tt^mT+h0MKKgr@Cnn*=uDFPFmlazgwMlCL8 z@Rlm6p}8hf9)`0{vEa;3)v^-gEsKJIEyN?t9!~wjwn`BC#vQ)fS3SbMSNAK3b_(Rd zCqPb_Xk+Ek;*`9Q@G$Q3rDjHs;$HNd=RD-_=%;CZ?pNq zn!?PQg&bTMqj*ODnqJnrQ>$xN)ksA(UBRT-p+!|hingY5VI_J-?)Xr7j50x1f zL0fnNfu|L&k6ag(M6Tl2p7)bmn+m&qF(%pN zTT}t-AXIlvpr~p1abZSN@9cH_7$BvJnmwX@odg=`8{lV2G1enpB|&sDg&j6PyNZ~&ld^gAb=rVXxd0@wbX@F3cr z&BGlA#L=Tj$H9G>v~-5FS*9K*H|6#UY%ry;SI9Xtzu zLCJM8n)`|JIxFzEIEM!lwOXIZ^0AF(ZD=o|iKxAJ&b~Wo6hFGiq*ZJe~CON6oJeghC8Exwq5eP zta`Aj1SgS2-7v|!NoRaMWltWr4^Y%b6IZaGjvykagi{FhFtR%aW51g|=zaqBVT&&) zgDnubBC?|O%yopXdVbM6_8~YggM56{#+R$EP=9V#K0QA{X4v2!(Vps%rne%n-QUVz z68GhaE7gUGfz1$qL$2aSg>d^8C29-4fbLOxM~xD-kSjulj{41Ttf@9mbl}){u}DZ_ z+H&V8#HeLllM&z&wX9W|TYw@G&eW>p(tnCt z+eg?5!r&MFjp`rfVL2)ta&$}3@O}flL|QH#Q-RL-5>(h8Oi7<$cAk@{H-w<$UZUHW z>lp8uMJ<%%6_t{iGc&zVicXDX_|>5V366bAdb0hOvGVi{_cmPh$als4PtTME)vH_6 z!Wm4#4Y8NfqbtU_1~2X^YqI+iM4Q1i)_*a~>7<>A2kA|<`d=#~k;UQ8C#5Th2-Omr zc>?+Lbf;8{pr2Y^P2DeOO0IsH7J7flKEaG(1x}nng0{DFM|dEG{?Sy6pr_Ab*m&B6 z3Ot<%_m=Cff$#VJ#bME;ZW>@r>$QC4^GAMH*lL%s=Hj4ZRN3AkQk;7>2F#8he$QaD zy`>~;=Dj?=3bH7GD>f#?z`q9AE*Hc>e0*j192niKdrWQT1(!2d@IPczAo!;DOFlDm z+={QQzS-2$M#k9Z)ekqOgW?0&(JP`oq-u^YKE|U zOOojU0pCnOx?T|{abyvlLlT`N{2=WoI8(jIoUr2+;UUz+h2=7LT%529maok8AL*W@R+ygc&4H9Wro*urDb`lXSVrE$Txd?y~-^v4InQ+`v_6dG}E|gaa`FR zNtK>vD)o~}(XPm#H{1nSut+6LGPOG36ezW*1XN@Uj@FMYN!l6$Ket`-yNaprJ^+M6 z6`>T(JTM1{`vd@OgWW@AkfRBaE-XZ8!e=i6(we@;!-%bWizjBu(b;BztWqFT0bd`+ zSxoeEWGYt#I;&2000`Ch3jiMu9l0x$7a1H@gu4t^4PvKO9QRimNSYDfHV-8v-p%{D zZA{W{Oqes68%Ik^G>dO4QrSRRcKE2=U|UCZli z!qOD1`5uZrj@DPcXc$*kPCnZl*(_+HFVXpiBkRa(sfrWxXo+9Tg`!@VPU4SxC&@RInQ{`;7WHFk zA>r(VZNS-WL%WrQ94}m3;uQ3w2MQ*AY8~YFk>s(w@Oq%duQqV>RR85+vxKlxze+^k z{6c2^nju6}i9qoubcOJ|8ktArdQNj5tE@ZbI~pBYSZv3oPlZ~RbApuW!jYp^Q-)la zEg*ISmo3N}uwNDMnTTQ_Q%umI4@lX%NWTW4BUo|%WIxB!LuwGl#*9LJ_Y@+I8hM!2 z>4R23R^=2r<2yCFWH%yB#%R50)b>W_@l&AKA$@rN3ix}-6@lY>gN?m+|MEIHQ32J;Nc?P(cZ$;Ny@W> zbssTOX!*n>rw{#SEzBv_5}>xg-cJvW^8~FgX2&XMMG9-WFFp=JX(EdZ(F{4XY{wX%ViOR> z&idDO--~4*gw;4g+wJ{zeki$t-%j-f*{a0&S2I!Rq;JBAB>rcCy*Gw?N46#z$)8NN zbQ{9x(@}wtfX@SgPs#{_x`t#BT{CCG%tOqNu8QS_y$a*v8Rn2V-nXXAS2_kBmR7$^ zHyumNFASeZ*jGy_Jz9u(I9x8KrNR-Wg*OgxTI?kq85V`24Ksa+zJhsdOd7o0a{s1v z?(VT(qi95@Q`Q&<8eR&5R9_sNwk?O(nhDgv{A2R!umU3ikHDSbcH*@sB8M zcb?h*`~}kxCBgHts<{`PCTV6{H1j{EIa_xhx_>7ftW7O`*@1C1e4aZc?4@Qzy#?1P z(v8*F`ixTLl5bup>Pk=dG(2BzmLf_=1Sac2AgCxMZN}f7#obT8QOL@ZtM;)q+Ecr?hd#UkI^@dc2N8dntrmIlcl@Vber=gs$tRk_u(dyhTCrz%Q4+s!F?{@X zWU5s`F>BIaO4(7_dv!>Ppe=J27x039j*YugsumQ?dl3Qd1+rE5eGYJ`=-MzbY_dbCY-?nmo>hiY{fLDVDTwYL zZA1pN?X#HyTl*k&1@SRPiTY34Yv3~Fib-qGvwaP@hl_^~87&Eq zf|NA*n7cn>LAH8nII0n#ZPQXKp?kg~jf$|DSw#m6Lid;M@D3y!D+C`_^bq@RW353i zl=&x{_>XBLX|zgDmI)ytes;y5zBwAFe~(~X{F`kLQXH9?E01IESoD-rA z;a%B$a&P#dUBGhz8=b_Zy+=S|w|EVA?jDI#!`BTR)&(I0cbs%tgLu#B@oxMdsOqv+ zF;^0#j3%wpY=om&TtCw=3;2-mdAxEF1P_;Nl!c4xW4&4F#}dAoSCK6qn)Y#3JT`l; z0Z^-K>L_T(mCUGqrF!ybbu{gc08a-_WDUz#jhi9=a)t}=^7$n0N212G z3Qt2Aw1~u6Z|u&tJk`s$uU|LDnVz%tM(fE}j`02=q@xgus0y|P=MaV0An-T&((H@i zPpZqRt(|8+e+Dlt8V7OBfeVP)%b^`eo-wl+lwH8d9F80yQ6Xa){SRs7ssbu-V?l;y zMX6XJ-_@1dL5nZ4snkZ~iuTs=@yi195EBjCHI)Pe;U~!tSi)p;z{=}V#srm^Fa}A9 zaI!bivE6iZ<*{3q$4%p}+M>M8`xFbvyk(CY_~kxt%`z z9z-t2YwS`vq&q~;`2(89kB+EmaI-_S%_tmpgk}TBT}mY^twn(}pg2*F{P!iZCesj> zf(y!KzEbD!VFFj!W~XFwk*{f5#{p@o=zXGw+9&6rj<;_8KINm{!qz%T~qFAZx!WVBL0up&)!qw#UMHx%-{VeKqf$Oxjx!3 z#ehc|Rn+tshq5%(%OoX~n0Q5N>beNTPCq#LjELhe zLTw?;ImlXwqB`>?Cq7!g%5b)C*)R6GxTzP_>-Ny&ATa8zfVBaW@~wVhPli70&6FBR znFUQPTv`iwG+lY=z#a3Mwd}k5Sq^bFc zOR3%}X#XmuM$O}yXr)_J#Ics_<-hw^90#7iKk0;a{RU-DxojR~(z7bpi3>^2!h?G4?pAp}Z6}^x zLVm$T{e~p{Lc@Q-E`Pyx{9t+BffuM6~2wYYsPm)z@lp~`q&H`|$>^Wl}RHZ0h zxB>ZcMtEhuF8BO261L*+nC!G<{4l!87*=%RMwHBGQKJXP>J4~K_)^mhqU_o?xK$Oim zr7*kOCD-^pzBQ?f)Y>(R*#xcFkqU1;#9db_5B;YkY;Rrt>64?LDw^rim@x;ddHi0^ zkX$%)p9KQ4ICut*B*Bv2A-gSzdC1ccq_S9+9tD_F>QGY5T5T3Ss|!eYBrK z6FsCKq^0GURfmxfXvzOLKbDr+APA1xSK=KH-WpbuA$reTV!$Q5m{rdLfSWDTJ>e{z++zXK>o3jGiw5NpV*%?1zTNmN{ zZB{-C=5h{LcJn?AzA5@k*XJy&*7~)12yeYpMaB3cM?zQ?9WLh!e+RLj2jL!ISc0h` z&DajAUVbnN&|@C1)i*otI?4?l!67W9UCj$bgtbbnI@vG1!;gH5fFh{uokMaIBU7-| z!w%s;UgK63@o@RZ86nrGEN?`L!uE`0MR%GecCr9(aKN+ML<+tFjAq!`hQGxre zCV10a&wGixab6h0&95$J<4ZfNWK?Z89XEW0eu7mSe7q70=#++#xgElbP*7`hLHpA} ziu35Rr5NrS`VaLYHh3ODc#Yt*50S|TKQcfFtjF9^42{zjPG{T+<5purWBbZ7Nctjt z>+}~xpd;+^Cm`7*3=g;G*Qc{I1?_A21DPtQYlHWWN-tfM$&X#C^^&WPs_Rg(R92*F z*Pd*B#4{2of0dQ5w;ZnQ5zw;JkqSMX^MVA{s=1bi)6>T{^5(^`i6>@tu(l?aszg?4ds1Quo@eH0!+ijAjFMp4F=WJYWZvnCWoIO3vKJP z+t39krf-0fcGF@MFSAKWer0<>KHu-RXBs5mTPV+sGIQArY{9q;w3>mG+W_pz$HTKjy$7_+RLv!-zYPY+S)-aj=;O|}eDx$B0*MJZ) z!PF5;7&n|}5)meQjMy6~zPv+frfKe9rv-NLr-Xoy7UydmzLtlMPp)q#?yJ=J{4{_G zuY_b_%ftfodS_!93CD_n7=!RBq?@kCSP5k$16a}Qyj!>60EvcEq_5lKe~RljFs55b zMu%|Fbky(h#B}8hv_`Zt)GTKU0vx zZ_DVAA>tl@=26VYKgF;Tv z!O?>aCLZKjfJ_v8h}nXi&A3Eklh>>>_pBM{-xRq(HA|EWSTPYr{zFkNZ@P=zioYr2 z0+t;eXM4e{P11}GOk;>KfKg_kPiEa5gQ*)z*Shc&xocGTV|PFluHCN6nr@CtQ}PYk zXVEP9(#BET%wNNHUn8v!&kt7E>=A;k&}JnzK0j3nz{TpO`kCJVRVP&GWnkELOLrk} z{KxUEc2N@!Pk|C1k3$GI z6UKgJ6x`R{l`yVE!B|Q${tN1-FlNsjdZ~Yy+oRxJAaME(uaQn&0c?_92s1LW@EF_& zgGHO8j4P{fYfVVOUs>o*9#^1g!ip0O-9vE8L$O`p9-xuf&^g{PFF^>28Be7n$=VP4 zfG+yxm))4a8xm{hpmG<>vtRWCv~~6)x({-M?YRC*ygtD4z+`dH-R~ zQ)zdzN7=4$^UuNQS$pZf#_-lfGW$@b_c-e6H02il95HN9wch`*f@{>_^B~kwjIN`l@dnX(9@5P+8iMD|O5@aSX=2yy@uycmE*gWz3fg2oJ^Q8idIpqYz3|6tR^Av~^ZWyLwkB0L)@C?KjRufQ84? znk}{7UiJ4a89oNq%1w4EIVyWic;nPGu9p)i-VKJ-8esoQny&Fzt|nrSy%cz22L1vi zq*&3OKP-hfch~P4s+c$AAb^Cf`uQc0oBB0b_`twHu3Z(j=Mb?6PkL9W@(UHhQisn} zMTOnKsChfAatcI3;wnlI0;R&%>VjTn`q2(0T~5GS$Z;Bsc1UEztnVpE`9g(9iLm1+ z{&cL3Ya#5mxot99LA5UjMo_9?7jQnRU1_$Al;g~|LBtR@Os%YwflIadu-1Hp3`260 zVDqp{zi;MZL_x;MNsBMw^yTFzeRofFwz;d{R))g`?n7W}kqxoz`QAYA5I&hdjkblZ zm|fFtMb%&dojqu4Q9*q*kDTC9~IGiR< z4PO=|cIY#8IM$^cpX(J40ePh&S^$pCS73AcD9@s*hzo|*cAgKS2msDUd_%pNl-qqF zVV6#<5e!yOz;2^Xc_M&KX)o6#9AGRlvzb}wwO(wRl|^GzBTH>jH%H^jb6~SLsW~45 z&B2(ux1w!)AV0;KDYS*=$PrL2F5?i)uW>l3r1~lYX?9ZuWr=4`xgJ1NWtTb`oM!S@I<5I7U|nUV>PE| zm%>kq9{{BiutYpj6Kmjb&m$zAw!S!g;aQNMDC^g@2~%&WEJ*rvwQU<9-_FBd*qHTs zwspyXItb%y;U65K`J=n$KjOyigjX zGA-hye&NiGjbZr9as@Upt1S7^K1r zpRGT4t*Gc^;r-UhD9L2n=q>;2CjfD`63|Xe&&GBF#_~iSNF1VAdlWw6pEFlh5ptFK zU&P&GcV$tuCg3C|tRxlNKCx4=ZQHhO8x_0aimi%m+qNoBPTX~SjJ{v`Q|tC$*n6+B z=UQvN&m2)$D2CbJKQjQ1iEgL#?Q%N?e$j)dUWjKw1=;R(>?*fL@Ma~Y?*SWSHq z#wq$wam-2^PIZfR^|ZLJA@`(i*+qx55bNG?9uAlYiQIEA1tPQF_UL}qap9wJwWKDa zh3KfoI$iF70K12O`|H;5mUWy`ZS-+*NqDa6q9eDKVxME8JI=WIEjI37@|Qo>SBg2x zPzQ_)B5yxF<;@cZ%Wm;eAC76h?}JZGcw^3SCG#DLAq0sv0J15QH_t@ImZ+hbEVLcF z(965e+Mv;6U`oJ;Y5f!NUAd5wx034PC7Z1US;@C|GWm|$HA}820)`q(INCLC>-BAl z=8KE?5Z^uCKzDxxtrTZHg}cRx{qDzbuP5wpuHi*SSpx}u{^peaQVKjv(TM*UPSzg{ zU_v41MA{msdhbQq1y@A22)*{B7Pw)V;UI}|PZz9!(5n4Kc&#b6yZbOI>^HY_E>(~!?*SFxLb{OV^V*TtqFO?jOpr|$M1=55)6M* zwjOA2wy9({TK79sFLf`&6nqQTL&UT7v>RZ+ADKw}Ls8D2BUJDSqcS){_Ih@XtvWDM z81fi9C(Y2V1-wmLsLCbD2nYzTOv;ov&}sQCd54O@R`CHqH^>@dRlzhiih5HyULJWR zox8*!q^FM(o>1P=-DqwgxcMm*I^pA68_BYI4sVQAKCk`oh%r2zmpo1d*V`U%; ziL!IN5vO<)&k7)H!u#^5Cc!=J9w?WypCMHJO9!|5LHbx4TB*7;khZ`;&7IPtY$dcD zck4*-RP2XNzuYS1wbJA@yWt#6TZk*Ny(*IsKs`6(05IS?F6 zWS~U3Jp6rm!yZ4ufeDXWRy^O1Pw386GiL9pSggqhrJ4@%DQ<~a07S0XVgJp8I-JJy z?bSRMBw$6%;0?WVHh5BWse@bI&#I@fcR*i=4LVI+6z3e}i}}}|JC5%!b|uB&J)koP z(F8?yH!=O~FqV)}*>#v;!|)c_fMZ5@&I~<9A@Y{{I?B%ibv0S#Ti{Ggx-$l2ek+a( zPwiuk`1fO|{epc#Ci6nKsngx0yf1I7x-5e`us8m3hV;66q_(6CN{?5h>VwubHKx6Y zp?KNpp#_(LAjT!SCHMj(0bZ=!;M~NQuKqeY8IY339EET^whydcQf=p66&*@TgT*ss z{m&MKh|L|+G2a}KG9{yEjW^5XQDh%*UEzUjC`l5LKU?wsU{%3HXh8|L8N25?KsdNx zq;>A$8n2e!?a%VPhHn<+dT)r6tJAL)2)qm@>9f6wb;MH&6#5B>G;fkgnQP$oslnQp^-q+GwpN~2ao^I$T_@@S+gddV|4t?8&KgP`oa@I zVbo;*WF*Im#?prw0RbnKd)3m=;6$2ao0tflwB-V-tAste!Y~?N#^bbQ=rcF2o`g$- z)LSRtoKtW_25y(X&3pJzbYsUzS)01I!7Tj9ij~1iN=_QxsvGL8;s3LUCf`1ch=k>R z{P!q*CFU2HPK~Gwn2~%Z3|_lwDwMm!2oTOuEdnNEaW)f#IjXW}8Kh|l8z7^O#Fvtf zcSPi&6m*5?VOSs%=$)yx&H^<)S=X}hKv$?0x&66)=Z?Lb%mN9f+Fc{?tGP1v%D$c<=?00agaj-El||>EiPypvDuU=};qyz= zZvttD>}gly|V7Y$nt73=d;o^@x*W zBeP`YX*#y>GrUz6(`<^g2Q{9q=28~Z%QfYUslAq9D>Y0ibAeOQosy7GeHUS@~i!z z1XLx)%!`Ux0}?e~&1#2{pTZ~PnPm~F#7&N`T0@To z#H2cyHXfX73fOCha=^_GUcif6?^2LdaPJfAaROyM<>x$eoJ(VWpTCH+Sg5+@6Haz) zZ>(Khywlz>89I`LhI;E~5}TX@gqg+P9=WkRYlz+Ez+0!p&QFijl}0B$PW)#WsiwIYPC2-)J`4yzp20;Z z!aqc|Dz}$_+eZtEQs}(&Y<>iu3!1bf@D4hEPJf`idRGX1UGm?JF?AR0F?`E6G!=vR z3G)I+Bno46|2)>79&noyK>|Rrd<^umh93%DjEXoBFA6^=$9g1VGw016LgeZ#nK)A*I{cGG=?wB~uV?y*CoJwX2a z6wG!3yaY14SaXcSPIiN@i=r;vkGXFsW}OTR!dj$npZ!Hf1HJ1v-LnIn&n29k6KX#J?kma^*3B6| zr@Tnq;rwhjkSx$mH;$#k3@RID&6@}nwX^*f+FI*Ea;)mo7)Ln_Qp9*$_K@)|e8j#e zXH(Jyr(HOaZuqNuVmP+K>a@i2;5Yif-l&y*#UiU=Myl~r)vy52kNm)f*0(fvH8BmB zdz^WHDscw(^cBuwhl}B3MAS%fS-wrB>u=cQX6zci?uwJx9-)(z`KZ$ca_mk6*uLaL zAaQMS>GLm6Zrx@}`Vm(CWn#|Dn))b@P!s^)sDO8}Hr%-K) zZ)HUIOq2Nqq9S2>CA_U&v~o-{I7mWz-ouzKUQ!ztrFoM<&h*U2rxH2Cw2tXC_) zEyC>{EN3U~-V9AZ25#U@1#T~^Hf*%ZqZ5Hcy%Q+y2eL^6GVvw6t+1i$-@}}{4`yPP z-S4$PHgdy~`3d848AlNP>1eHi7qAiugcML!A3^^KvfjbJ`N<62XR%IHRl1DBTtK4k zdVHKz&Ow)OPO-4t1ODVHLWD}62HlcR4Uf;+$dG#o-ib(6GvwF{DBaK1i-^u^+)_$# zS2Jbt6js$NX8&tF@ky_639r3$D+GH8*U-_Ji9O^01}X`YCuE2X1}412;bQpe<9spM z7bJe>>%B||e?%n-4;~&NPCTX!W_{P2;nWYhhmS03%CPvIbBr|0c*y>*MA^6)=+E2H zGAnSqe9cYQiPgA%4Bm^{9_IELMI{Z0IG`bj?sc=K4706qqY&$SDt5NeN$vJub5CVp znaVDe03(L}anIK|KPd@pv+MKwDZm5g8hFCSTT+Ufyp23Tz~{5{hwJGx78_3jR@Kts znCQd^y&|+CVW36$ZMU*k$Y7LQm~TW7xNrzdmAMg-(Hw>ITRDFVx{?;ibK;ukA$*!o zN!(|g=jeRR5L-ZjY@RpU0XQNnGxI29b0OfO+fBFVwQQ(EHoaO;u9t*G!${P@9?g30 zWRMx`gzh5_${kPXWCH7n92-e<8*akRS~0Bnch8&W19h+yV5KQQi{DO1q?Fc?8t5D5 z3fvG-GL1hveS;7p}-8|2DMP}z`E1+cP+D%Ug-o%mX^%hX;%Hp6S{93|%F@Was zGGX3NY1#bB>rmlgN|vU9ZXQ(B_cAoFL?9`K9>L%;v38W2MA^F%m%yP##7{7b zszK64$ohOXcxj$J7d7Dl2XvMHTS(j9U76*PVp-Gt;wHHmH%8}KA^lgdV=BS+1{I>UOvwEWLe98M)Skzke5XG%q0k!m6+*!e zzLPRGdOva($odF+g|YG+kfo!+z?5AL*9f4T>L1xIuBOe}=De;CAcWzRHCT{!Y58h( zE?p7gc1#V1&04@HN|=Q}2^+F<&?NQ2ct&ZW&l<&u_q)2;W@#QPI=?4Fe{ZWREj-bz zTF0IQ*Ngm(Ihn+xlxH2Kk|MglpNRNWTfGDaJRdq->CcffGt&wRq;t}>8dtm5=mug- zV$VaqfAsIaOt&nt6E%ZbrAWH%G;e|I$ivR5Jikl!0U#M6jp>G)sSr?czE=zNLXTJg zr>a@Qn_`aDBdw+Ab+VXl3sWSjo;0WXU;9IOhe&~tPcPFi31o5+-!}{3zGe_fxgUPMgzDT(0 zU%XmWHWNybWGQe%p=znwf(=b|`W_lKdi}Z~0@hhoGvANql;D?-_-ZR~V)|HmPBZn= zaQ1#ZH+m!Q9L<02*fqVW2(c9$_69bk3{7Bzu(4#C>EKMJVc*MSLd{flN&*tJP+z%I z#Guw|Jg`QAN9=#g3aL?y)t`slMWgNO`FupRa?c8c9_i}FoNs>e)D(F!B?xrcC0+80jg*3?D9UdRw>$om9Rb{j6a%~`fgCE;zR|xeG7_=-3PS3DxWhQU63hWLjipoYiMB?9Dtw zwY_HZK=Xs&5tE_C58Dgs7a*;q{%Fl3psR)=SeiOTyB=!S0gc^mWXc4(nf&yPVKhxE zqR~5Tqf^?t11JOS$gh9)ay&JaKc1(43&Q;2h1GBbn~zXj{y>jH@*;mO;TUSUOJ-u1 zG<(*|TsQ0h^;=!&fv>HJ^*K42?KR9_Z9ZCJ^I=k{?!;QU!38S5VJ+%n3h76L9&6X< zIckQDms?Uzz=8r1LJ%ROQew|(anNrE8^POad$u$z!y5b^JCDDgVqSM_^`9p+GYxGO zfSk0q>3`e&IQLDKc?;FESC5YQ0a_^@IT|UP=87_M?((^{zWetV5G8}$(JSsO_1g}b z?S{X-tTb1d!57fd*P#+#(HG7yw(8WHm_zRKgSpV8y)3Q2E}|MMSY7=d;~If|mHXA> z*)C%Zv9lC6@@*^0Yw2KST;MY3`Q6}}5Hu8KM|s+^d8309U@e2>|2xuPZ|rYDONMQ|qyYt7Rm@`*I%cJF(dis+Hr@XA(A?e3z(K;&Ms_5uHR?}W4o!Zd zuJTJNtRSEEy-s7Wxtja=HjLJ?I2tFDnJQMXDj0PwmBdJQGl!N?c8kU&+hADVMV|#J zbnDFV{EET-R>PDeY3;{5$3dY??s zKf#L$R0aos)|)h4h)vLpGQn_C0e1}Lnq0vqVy~b*P|QxxJo9iL!rHJnH{v{a$K#@# z|GQ_bGSUHb^DD8A7F4OH3bXY79jczN^dD?Q${E~2!kb%;nBIvI(`Rsz!dgaW04Kft zeK|gG$A9Bc%E+(3n@^%$_rjBcoL16=$X+`b0RwC}@H1KK2I1b0g}aOEJDqaw^1bYJR1*%$$yS*m2x~;BfdK z*`-o;_w-8cU6)o6)UM~KRawlNVPE2t=Qj>V)11}moQUpeJ@|D~SNsWxrmAqhGq}kp zja!Z0xir&A1El|Is4Zi>R9mVN2CK=@0bqBR$+sw3tr>i8p_Y6kg{P_mH85`kdW?{w zWWV{z<0X45!Kj^i1H3>qXepdB!{R>?U)(`|z#*2V9Vh*BZ5OWG3b?MORC?*C4e$id z5{qI{_Es-^7#oDxb7z>s)luo;rYdlq&W-UIcH&gRhvhCZncyosSz{^9ryOJie-eg; zO(NvOe$W(;;#$D%GAjJ<@|}FJ*Zc~l(WrT)@@=3fo%5dV9*E_qp&basakco&S1NTo zcPXZYOe{5dqC|+iY}$5Bc7-nzGHDyUK7I-lubu^ec)ju|&GU(;r-Zo1O}*5FnOb_s zu;q|5h;`fS=Bk+CZa;(ixD}y+XMqO&g~roAHJ6gs^qDd|dRY%+Owx2u#+-~xhQ+iK zNVPUb8dpu4>z+LqcZkA@48)4I`#5LaS|mFh6F1<3y+dg!6+&_WL6LnhJ(tqycU*s= zS;U9Tb^2&-sEb+D7J_nbeQ`p|IT6mZiOo8YzaK)VN?ph7_v1ia;a7CddYF3S{$Mb& zd=^86gKW0QjhypyP%0AbJjCt{#KeDPo%{p#Nsb}FN+~VPj77CQ<&0EuDXzvaIT@71 zLf9_a>w~!!bDaPO#wnq7gUb)H5ufv>UW~Ea02g{<6(o~od&>2&wJq1iX{show&_W1 zXox=Zx+&762>ETw+tQfM>2k+E`lQY>6QDPxTFsCEGjO{llYIFJCDkd|^@Dq|wdzKe z4Al_)SH0Ia8O_mKB^#vr?1iyn_RicyYNH5~PkhAQ`Y-RChEKSeyK_!AOaC7oZeEs^ z>)s+le$5vEmN&IqY(_9$+N|8?u($@F<<|-w!nCq7shnC6y*jhNpwYt<9z`>pSi{h6 zO5UAt?~r^iD);GY&pgqU*J`tY#(8F858|o(S-h%Nzn^|geCppVS*-Srq9!}^G_9Vw z@5pD>ina5!x?ch3A5gWEYvF**9aS@gQi>Oer~V1*?(T;dLDkZkThNFFHRp2@jFRRH zGe>odq~R)*32I}AarrzpCK=lf1h#ssR%_nTk4RVp=_B(PUl+Slq#zQ;+F$Vsc1b}V zcNthi7E*yKs;3TPk5WY`&Q_kt4 zlLYx1;ld9!CZf$zW{D3dSt?1kKS)MG3b_};#k*_zw!8HIv}P{?R8p=DDa}|Wx&`D%)6psPpVKNqk}xLc7F<`8L${JNN-VFz$GN;#3WpNY;m7l0~`=h<}u3# zKDsyYh|JABgE1s@UM=wv`frPKf*6a4; zyiw8KC1BjtfA)x~R~R~XKOQof;D7AzA+^oMvPB6xg?HOzHq~K4587rNO*=~C@2A0c zzJ*<-&+K-w&3L)t`c- zP2#&k?k+d-q92~k4}Go->Jn2nAwjYwVB_ya}*j2cE92|9~{4fdP@qJ zeA1m!jQ(pxrkGDrxC5GgFn$*1L0UtU0baO#z6K5ZG;5>#noW-Zy&5H^$5OVE1l;8= z>r*&kb+(ItP%nLPJzzs9r&)7BdvT^Y@NQT*Yhj8EyZaCK8uAZ5y`e?XEyu8^IZxnc z8^N<8YJqwq{rZ}sr{ZO{u)xzzqtZ~H+#5&b5X7*4IaMQl zcG+1FIn7Y#F08bCSx^-RH3q2tB~`=b)+&bgb@g%hPx?~wX(LELgTS;`r4xRg1 z&WQ1@ptlv)(Toy3C|2c0$$PiKIH6>Y`zvC&2u#4RYm_i`G;a!u zcF|(mwVELruGmh|&S!VZeHg626$W=iW6>YWxs}eN*0hl+u$4kK6lK{Au?S^%F%*9f81<>CrvdKYnYYia0e>KAbx(t<(NG zKOSi&(x!7^^bkUBd@S_a=XQont>vs22;;frBG_p}vzJbY( ztw67@+b|^U2=`q+3MEYsk{rp3Yh_x}XMjfa-p*>2YQ>troWmDmv{YKEUO3XqIaT87 z(mokhSqYcn$=*TRZWw7!b61-PT=>othDY3h|6q{dy3q{$>NSDl)WTDzc@BCctq@Jp z!aSeN7JrfShno>(OQobPrgXdEwv+5G5GRF{QN_!0?(UIK-mBx89%wU{8KiY{G z-o>PLZp2;11qqh2*XWl>Ejhu!gLE>i@m`tFkq5xRm>8$;%;!=uHW}_bG?@|4>yL$P zCqaM8ScYQ*enGlTW$-OKcMRya-Kxj;BIb{C6JofXtv7HRzLxtLRxIP<@r%mHKzaJ zSt-7$u->L1f^shLWi5;JSiME@Ia-3zo2aKe*2Bt^@{s1i(hhXrw1=VH+uyu$O#V4h z$0n@}Zn^`POL3mgaRioXt_Kq<-fd;WnUz!tP0Q8+4fa=p!p4Ttzf3TN010o!D=$`sFeb+dGoKb@?*VFriXQM6gBDr z0PW!<*Kkx+!+EH@BiWU3TwkL_?o07nscd9<`vjE*x!w*<*@l zo1}AzjgH(BQ#}+#l7e07{*D@P%XqdabKnOaTOMYk+<>0dDm!@d^Ef~Aew9yy* z214V9)fvQz$v9P^O+#l!uNV08yOLAXft>Pc!|yonX1Wf{BN~QK^x1|wcQvF^sc)Q) z5Mh&SX`J%AE%kU!KL!gWGi)^r9%#iU1`X_Q^)Lp735)p*r>Y zMF)Pk!N!Jzmz81qHvu8#sz|;fL@okLD!?u>O%@7v|L(N@G7=!AC^f^{CSy zl{M4bZR|2oa#8BXM=b2UGocGl>+BILQc$<~Yaq4RAk%0ZA|8Nq3p+}&Jsk@+##w*= zKqaDrxJDwYcOjj>0Qv_^>)ono;1M&G!sSJjd~+x+tNTpo__xU298n&LnK{DvEmFXw z2i*>L?!bQ{=~!$ATKnEug9X(h$s!PgiWMpx-6uKsMk8YhDSyRFZ#4H%;fs{GlVXU2 zEIP-U3iB5#y-(GEe*5gpes|9-@}c$lF^2?Voa-gHcw}Li8sx8on#CRTh*cZx&eU=- z32AKHK3$v=7K`}wcn$|#8!7}ZR*Nd0nhqKk{{w84u>_67PxD^fj_il6y7>#={GP;> zG7p^hHK>u$flsY6KGCussi)p#*0|{k39)Y8b}wh=JdN&jq$3JSACQo~&H1LZ`Q;>q zY$8D)idrI)W^s@Kc4MO{pj0u4sW{||Drs$E8eqRx|cg13YQ-UBOWtK{dA?~qYgW7rRSsL1bzPX0MgBJ7~jG)=PHF7b4%n}a1B$#^s zq&wIIV~ii@QIB|o6tO=idEDSXP_Wc9(({F&@%ju#vu1j z4BfB|BZ;2;4Y z$7$z-J+Q4!?N)K_k~VXUGtl2ZB_rX%bZ<~d!&{~hD39d*32*UJ6MWYBC#!#+-f%NUedjI2r z>zUu1kWEjz_pO4ZK2C%gnO)0Ck?^szXczfJ&oXyWdHxI$Uc!25OS|UojSYDKu?Ev{ zY;+VH_ZO1Cft8((;#d-V-^h~BcwU5Eq|q4#caE|7qqqy1py~Srha#zOve@y(xGoDA zzw!AiG_B)Mf0UCKt<&WLixI^S<$3hgrb{+W=n{Esa@s8=u8L|@Uja87Bvt8P*N;CN zZl~=S=jriQr1-Q{q;w~>`kDBCNd#U&hoUKCHO_4M|5|LB%&JmO8PcEs*@gE&RBX)( zpm@2)h!#~4bZJ0fQkg$2$4@!3!L6%w?^Kdgt$MrVpB2>!wbC%-r{eI9Z`s+g!~xsj zrTtzPa5`VMFx}ESkPoamy=6!uFShD&N-Wq;_!wPn&yl36y>I051oDYs0PnzK6r-!x zS1%KnVv0$OF=?NsU_V?YiKNlbKA|MXFHHsz{J|U8gaB!5WMCw5cn1|TQZxF^ve<6g zbcRo*+~6&IPg}3GJH^LUAPrMm_1o=EJ7&lGY<3|bgeu(-qqba#0wizhY%8FS`hwB-zM z-7*__v9!D_ntJUIdC3R<$lTPKt1X3T6^a!Yp60OE%a%_Y!DgDzcGHH|pUh)XOG})# z{uupa4@LTQr-;pB($$iVK5UHWLr|`<8BjyyYERS_DMVW#0B+*Ml(VY)0_7#yXwg8Q zE?1VT=Dq(KBpBu_K!71&!%4}NGQ@NX;jZ#Pq$7wz@)umE*=huXP*-b4%t0rDt-_`-_GGsR7oz-Bf z+7QTwjGqW1%7; z%}Vef+7-isH%;F_$;O1h%YH5QivB&*0+TND^E$k*TVm(%$2S?~^QjVjvqJ5r#xr5J zthR&fnR@Nasj}!ZbZ+UcvK6QJ;AU*Wa2*O(WR-MT&Jqu4+37N)y); z;Sb~ip=$f<+R9aylzNj7!7tv4=!yA~kLaVDxAH0xgKEH9+y2(xNzbt!c8TsuG1`$P z8TuPS!Z8_647J>@$KXQbs|l&vRuHIbRuhXlnbr%YNW9t&Ui0*@!~kZAOosK-TF_He z$_Hk-Xh1}CD!1J&u`o)HqaMp|U4UC-%!@VtT=7rKrQ0b>W^`(-z-ADoR8#749f=G% z<$jy;A>v#4l$5FX z0npHVP+}aW@t4T&sp?9!RK7KuL`&~}KW?Hib(fzNmD~DF+J03f7#bAZBID{(P)Gy$ zr^IZ{KsgjHNtry@3<5n5SetKl)wZYVntdQ%d;`sN5_=KxqDEij5>GKX;b;t&j{8l(>&6v z$_%E;sm$v?6E=P8dIlfInV=QsM^MakJbzp?Zij2>2w1mwbFdd{%0e6#^qx&y|XwF(kcElvb@Ft$I+32oHQm zvlQ0oHz1z=lElBH%b?lq8Vc6#j6va+a{%$4bZ?#c1z*mB2bqta;qM)&^(PQRh_>nH z7I2U|a1z^i;$s)$&mPZ~-eUOiksb51W+4C+bSjC%adog6-H2Hs(}MNiYo#< zbBBu4Nc+YwVO(wRme6@8q2qzF9Z7Q7W0PTQwlyX5L@+PIXp5C{#~+IKs!t?edw6!3 zgc~bFV%bBnJ0@dRY)q7SnoEA=V3H(wVebwvHY|?z1K_{>D z*9qAxW%$6ZR4b;Hgr%2C_zLK(P9*>`JCW#=KCFKr&3KZ21swil!hsXih?rEEADur1 z!TM*6oW=>}=VR><&lvga?%(|6_HPO{&yg+F2FxbV`V#9nD%I@9_>@267A4n|oC4|k z`2y5)G2R7AI4BiD-PSH^`!#~W3*CNvnKf99Y&HvMI$y6D}Q_q(q@naZzn-O7e^!MEO7c}qmUr>ZqY?@`i; z4(ofmR2FoPMMX{B2h_=a-AlkjnskOoi_!nmLa|`f7^ZzG|Z4*V73bpjiuby+~d)s zt5Db6kb{3s(||SIjVPRR@~%;PmHd0WL(eIkGI5wKwy}<(2K-)F z#Q?5Ab=L113eFc)6BPN3E=K4ZNZ`o3Qvk#xV{ryd)gzJXM4IlzEGEMGg>`fTdhNt{Iab~yNv4@>Y%g>feLk)oOx zgp}Z~THQoQ+p>6wyH?sGu0_IS+hsX;PBmsv3n=(jmwjwLSKlLTW z;91wa1+oU>Y-RveuffqwTd8MKM;L9jHjI`Ys#*D%==x;Sn-AyQ!#PB@%w6A~x3K&D zHX8eKhc0%&cXfw6KS2f-AxqTm3Zw=c-qKOwBS-#xq-i8=#yILp_Q}f$jw#q4_iQ`o#<&8L`b79WF~*)q#)lMP8e54 z^OHl3UHiRAI`JioG(+@?U?O`9N$=*nIxJL}j2Fck4*Gx&XB8qNy%%K;iYk=1#vM}j z`^YVk)9RexG1g$)KQxRPLj{>;mON(W`%IthN=oBUE6}JW9+Wt|vYHWUo%<>5W)mw$ zl~S?E(T>{xW%Iu@KEF)9&jMAketZt9_oWP>cLbKV(_1@a2c+c83(e3n>YkapAFVv)@6YvZ$$>U|N26 zPNHYrsj`4**8LsHb+KEV4XE4Msf+UQZ*HGXzUS@8DSW8}2B>vyVC%c6Ig4hpkt&}F zW}C}$C#l@9Jm&`g*DO3SfU-@!p8jHB07!zsJBoN_=Ip7&KkQ-{>&wxar*Mn`11?GW zp%6aIdLUJSw@kOKPdvRSX8Pl|5D-`RmZw~@3Rk#phvg|Zt);ZoR#C3KV}J7IlkN0S zCRX2HxaF-)^MG3Sw+d7pL-vsf_?dJ;4gEmd4;rIUls4B5s47U9Z)Rjx!#bC|>&Ii) zHh)Cg533DApK)u=hM;rEj>{grEM46^U) zs8TIlD9k5|A^PgT8!=Ow5G)z8 z0jfe)Y&L{wn&abVYPQio~d{nnZcnJW%cQ|;KO zuB5-nW|ozR@3MPbs;Bs|%sBtDTT6s!*w|8z+%Jufgv-*U1r}_8+T@;@&F-?u?L2Cp zj)N)$&|wm-TyLPrmsRS0Lom0HdXG*zv=7TPS36-IctST*5=3TM+aiX|+vEY(&s5pF zynn|b)b;x4N>)gK^3R$k`yNNYAK4oOj=eiT*fwmKGg7ETOZYWO+KQWE*UmogqmeLcEm+F$rS_Ie<(>p(rzAd-WISXFL_O|k1QH_SG z>Ig^;eGyA{1<3OBE=MrHX=?qJ*qy)rFQerez7$gzQ714LNy9&pRQ^4wU0JX$4aD-; z-0$1eyb*_~Pn8-lb!E?@%2Lrq>^TvGd33(_=!(%rwDGl7b-`~z;#X1DDmf`?StKP} zHbkRJg4KdxH~;>PXys!|r3lG~E^;PI8$%2=2dsKuXwLFG7@={iUsUIV=@*W)?VVe@ zYNZ;e{0^x)jemN$8fVdLh=^*4S9$GTn{Dp>a5!du+t|C>2OduRKUCtl-9+_=f}KzC zV?=O*;!G(aw){7;5NjV~FJWItss9nB0bklDhYIa2;N0i`@hJtAq5nL9D2-D>4(4sq z=8|%W$`A($=SqBjigA=2ct6uzx(G;E|lW(y*GSvMor8;Z*6 zrWbo99R`q`p#PJGwBL+n@XG%p&{pkiv$9O+TD?H8e>fu7J*mEb?4jEr5Kxx?dBhH> zra7yo$&)7@A<)WUu9kUNRguFTy3J#7>iNB@`(XMfRfIqlLUd%aYBrTl|Fou3|}EUD{$##Sr=T(9A6) z0E+&vzx8C{gvH%TF6o|{GQjhFaF2t3y&o$MlKlFnho5B~j^oDr|G ziph_T7?P$^7-zCAT{)2lsA@0fPOg8m69knUp-)3`d1Tj7454Y?C~YY@cCKFU91odP z;*({?BjsHJl;)hPVV^+@HCL(KzXh=zVz{AU9G|A4%p=@wSEyO-wv0X*gC*28LVdSy z{&Ec;DR9$3x=`ZtslPNk#)%V#I~Y*Epm!Q&NVwJ?`t?zbFgmHmqpzI|;*_QO zAO8r-#3Ag)xKKB)|Lck6$&=@Mee!nDRCJ{9Ux}X`tmY1jYFN8cKMQ;z|740oz!n_! zUDtA)dHIQo!9_HtoY%hyQywXrQDm39B?jg*f)^_iK)u^_+iGvUwu4pbF2fN=|8Iun zNv`i9olK~jCr(ELsTI?HB3B;RU*s$HE#cU+pqf1L^-1{rZ7<;_9jX)cS6w#=$^jo@ z7N(%&_bxI1^W>6007C3LVKhQNiLtC^nf4vvNCg4BvitD<_mE*5;+%yX#~b2y!z|B> zfH$32BgyUV=&D*(Pddj<+l`_H!Rdf<{VAQR9h-k_>fJ|Hz?4Rt)MLZT|3j^NO6C(c(XRvCFUw-;$*Sji-Qjt|K^Zh5&sfi z?A{{mo?`gKHS%kp@yjB8B+{ZqfcW*A^(}-1TKt$QK{IXU$%H}#Dq>rlf{(|AhU7Ok zrbeyQswh~jOSN4hWPf+1{xXn5s?;bxFvll7=dW65rizU{tP%S)FFzoP z&v6)&6o*{Ep>S%l z-#YN?I0N(QjziGOf>U`1|AZa~Ujp{;XDdEtSE<+Q7E3&`KG+wbVB=UB{?TyUqAD3i zBG^y0DJxxq^93~A=4NVp)MCJD)};mL^zG8Sux-Wd6yvb?UrBE8`zL3X9U+Tn$7?RW zb|r0dSC3mk3Sr>{m?Dp3MVmIt)2zq^+vPvp^%*JEpe=jc>ULg$x~u$55Ki0mx_v+36Znnrx zCa3y`aH5}?*{B3`s||uJefbS!=8%2kMK}17y>*wuIyEL+VL_MFbRE>JO3P>^e(i?Y z;T_y})}Of4zbVwU{(17+>`OD|Q9LZfijR8S#K1)m`v+ZD<%R{X%4B1^uwL0(GB{&! zUp@;}SoAg$DS*xL*JT1I{pLDd!=3^D&ZkwrZTAcPzQ5IIZv|UKbHe`k z;q)!9NWcUCJ(AgS5!-<8B6nMd6Z{L|wZDAx-ifgWq)(FT*fd%((ollq;mA%F=)~~e zm}v7XEHC+fY9b0Ov4kCXeN>d1`UiIzI}^zB!caYql~=Dog0On8+LjIbcwME-TYXR| zmYc1<$0ck8tdmaI95X`fz-f`*0k%|bf5N7Db{IuA@X#pI2oRt(%Rk3n{32^0oj`O} zrlP0#>q5A*cRKbet`Rp2!x%O78Zr7$5duRpE}E>|#5#$;IhrK3+U9}+Fy2sW!-~LZsmfa5> z#6EGP+qIFs>UNO=_u225%z{q#-YQdv{r)m8f*YP@DY3C4FZ@Ygq*WNrodR||4$%j_ z%K}Zp# zh^uY~fg?UV4;tDb#00i8CdjFxZtz-r66U(!5^wiQ$U3l!{M{s-LRt~wdtRihGQEaY zD(d=g0YoOd*_#(NP2=@AgjphfOTw=v5Ro_=HG7cm*$eiY?nL!c~%UeOICsWfU3tJWIM?NyW|hVmS&X;h$=Qkb}#F+ z*K(ctE@B~6e#swsc;O%*DW?ksa4EDwS0tKu9C;RwVmP3lW6-y&ePPX&MdxWlR}Rej z<_POsf&wyty4|I$;ou zEJ84mfa=nM<#6na!WLDO${tBpZlDvpzGWrts>B@1rq^fApd4rgb?|Gg?d0w-eB}ri zCuTW`)5=YgKwbX}ufNqn{YP2(EWN0x_klqS@S1jckID&hW~>E}hfDVCF9`8DE!_5t zH%77o+THgevcG>lc<4Kn_0aw+4$@rC;Jav5AfC-#XD<1T@?h*fD8ZWQg!xA-2|CI| z9O!}FKM!Ic?qFx}O+{E(2E~&v5=!^ej(e%~gjP>k z;!ar55*ELn<`Y6ZPq?YpR%_-XFYNTc zi%;-kgB!MS`qQtGxmP$EpXZ6&V`-d;(meyJI>^lXN8GCL?%?1TcYa1A%LI!~NPETB zH*1Jl>$)ri2B^A~t7b~D{r6MWDBC9%%4=vemSuNJ&3H;!1u|d_0l{oT$RiZ%BWV%y z9@O9|393K@y+yMqs0AD|?XX{8_QU~47)0{n@9_J^jIDitam%#jiQQKWzLfqAi>NB;x%D!H;fA=rcWX$CM%D^hYY`HvycaJX7q z0qn>*!tB6uGrA$7s@z?-mM)iZ4)?I-Z~rSWD~Tu1|9we_W!|k>kxSLo?BN997ZM2! z+Svm;*45LIj7>30?E>;z6x8L`REw<|6G;!v1{QlN60|R^@eBF~jKrhEAZs>uewc<{ zpRo={I`BOdk1?;9e{dgc3xdvOR`2eXkIvDi;FJxVX&s1tNjE-w4cg~A9PC${Eoq+- zKp=5?8*r*6tVm%pWYihMPbmRpvqe@BXs1fOZAHDm&25;qUe)eX7^3Bh3#A@yZw+WNT5@w5Qk zxa{w>NQ6Ylpf4K-9S_Z#LR5#8aT7Oprs}%$Vqg}-_NKiU)G^SxnI<;OQ(9WjrEUuN z1qFThLyVJZCstpw2v!uE(nb0r2?b+8g{TxHorO<5z2ZvdBhXM?PYfs6IrdG!VRcz1f_` z@B^=Ga|dk#JeU`UO6~g@ z0e)Y!!8$_7>^A)BH$?r+{uL0jb9@4bfdbfT`1=pqoy=^D3f2U+8a4jFOt%y`K6817 zf1zX^fGUe3FTFK!Iup;|h=m7I!iiRHqM3SgwRPUK95|=d7Z5iE88A@}ViTO3=SaL- zlLSW{Si9#y#BFEa5&cF7GW|ch-AW8D5t=>N>4REQ*H)TUDxzr;)Xv?E{C}^sevcW@ zdUj44I*1OMk}B{yk&_#B?ZIBV zTAk1LY}XMavKkM`KNht7AnkRCuVyv3VqtEWMuI4qlHvbk3WF4iv-8%|^bCqZ@XZK* zlQab}_d>>XCnnrEkg{`Ty2G>gic@$zgzNArh2m&l%Hi<|)`0jed&CUUZ9;k(>f`N<-AGCD`?8}ZnkulAOCbqRc4e_pw z-hj0GJK-?ykDN0~AM-^E@3|q{9eLAv@?F((|0K6+o|}}8_u2^Ik7v`E1)}0M&~6QgZkUWY3(DTRb;nreKKys zbuEg^O#0vcGIMh#7i)`8!G%qXKjh_O^p7K@q?c4yAmmwV6YCLtWG)t@lR*lEI}5%v zFepw8;Rr9TPfm!Uz2FD7{F;^vPe=AWNzBz@COEiL5Y@pg8@<{|yN3b__DcBG8}~`+ zc&I9L_wDOVmR^yL3TM!Vsl5QNhquS=>s=9lSY zZPg`5Wvqsd&H(dRMob9A6Ut`Pg(Rq`M95~cURiIVL<#hgkwrq9#FS<#Z&(=MWnRo6w8cfOV zlz$i!J)5NT;A?vFlB$)%){V-skA&k7eZK4Sp^P2+7U+raId!nsNiY$I-JW$9A5*RM z#6`M3Y~;p6L`py*-NHVZ*&FKVr+oA(g=*#}CYrS+&UIj#TGM3H(y>15cI=;3P=TKH zEl2&83`(ztFFx26#Zz3yx}z(3xn2@Y2RqmQAuc}f=?|8#_qK2IMYL8vMM+zbWC z6G2<|Y7HVO$N|F8n`D@sSWfRqFCw;r8CAQoaO90_m<%}Q-zAC^4n}Y%voV@#f*^90 z93^`?(J>;{|wvf!Cxo{x~qrMhSldxaSE}&tLmHLR>he3VRPJnlJxI zJ`HXsUCgLEOKlqd^9&jnOqK*V+^kW-B)FPZle!x^W8<8&_AANrnZW&v@rbLd>Kv*S zT8%FUzHO>J@YI!V2t_Gy=_6Z`a+@$>ySOZw@h#pCq?cEIu@m|s_HO(S)51hn+vMY| z;hBeaqYvPjQ7~=qJLc6ESJS)fVgEfOfuZG(CdP)YfOMm+*;pBRu12v+BadN#yv929 zbmU{9a!0%I$Y5YUsR;6_RI1WWHS7dr%^@;K?$6oB?zW;cSLB#`~n`R!PLHAV#)ZRODn!fbCxx*AKloT+WxiP1jk%JGVl6MBkNePvKcD$oi zzmUd6|1eMh000000002S0Oym@ipGrO)Z^S8#p{dv!P<{@Zel^Eolx{}SwR~O1cNgYz zfI+gp@w=Bb!mGVQ~C7rod>VT)xdQtYR}Lzwz~;Lz8Yow^&+@_ z-tcU2Q%b^X^ejP(uR~nyo_4J-s_J2)pCi-+lt2_?hMU^)k}L+gh)yH2O$2@FMjihu z&Ja&SOYW?DD+p}}8~Yq~^My11G;Qn=nK|*%HeYcrzuO!ws`OC&epq6!{3=J^d1fA( zK*hxPt4DcE-w8$cfIs)}ppxaFPSHa3ne7Ky##_g-IbZ8a*dO-|hS7_PJLc=V3I*2) zs8G`J~!C4k!0T$VE;=Ix9oX8@eobKNP5r~BGo=;QkQ*>p*h<< zW*Zb@g-%oAoox~7McpM1O4b%caw>}3E}xhj(nDh=7wQ6OGV0E?7}y#SK`ImZg^-&% z&U(z)U2)L49ZXusG{CevX(l^6R}3ASS(ZRgB54`V^cBN85KMKenctb8iS?yVkYwhc z>M#+=9%%2_HH#KBVP)bUOiR_(cCAb57Z33ISNC@e0l#-xYZukR#-eb9=t22hQrFS2 z(oVOa@ORMaWO!syY`H{nq(22|AreXs_bSv)+H5wqTxLVQrmAM|`7kD@mpTI%miVF( z1V%&Uc()z&noH9%HSVasy|`HUZyG7jeY~Bcz`Gzn)x)@ z`oXT0Sp`RxD;gZMRxzJPR-21HvelT#ddQ{Gv4<;?Fwv+(eh(Ai_kHx_A7Hkfur#p-jvIF;WCGE8O8x>B*pEZ&AIOO3w7f!QhN-=+lda zO93r1XnVA++mOB9EM59ICOHxX_Ch4lcCA1b&Mn+qYPSXp#HrrHwkodIc&R<%$UH??`5QV_+zZ6BWPX*9X z^@+es@oyVVxOM@6Ft8RRG(;7c6I9#VVl920yc(B)85yB01mLJpE#TMr>@NPX@|PGp zqb46I#_G==OkM)tgS%u&HEoToF*B>xXzaH|S<~C=90J zW-YO|Ns_~AJ(Jff<-OtRlmZsAU6uN0F_6xuwPz-Zl+=J%Nh2WgktBS~;X~@VRZn}R zj3#kL#c_FYX=@?{9zZ;?PJjLALw(J%DvGlYx# z(rz@zkh5NjP;9?#U~jEEg3G39t*K40>4$jyt)AA!OIWVkt5L~n&?Gn_oRaL7pLMXE zAYNx+xYfQRz;Atb_3Vk7k9PX_h|+VLHv0hW2xQ9S*&bOz53T3;#PmpCT$>GkndGk1 zrZ>nvKb#QFIH1+DhnX2};32XY@61>J&VJ5`F&xz^8;eItI5K9^Z&BdFp~OL5RvQE2 zd?`VA4QVGQvV=B@t%f{H9u_-#FQp(R_cSDJ&1K1k&?(c;F%Je&MR4BUw=RE)4EAhr zH7?MsCO{W=D0~=*&7w3z6t;U000B1>pp99B{WQtzd3umBl^n>Dp0VP0*fHR*O0d&q3uBre4000025D|btxmEqEIAi@+pD8kR(s!96g*tyq z7DvEfx2@+jgWcKLU}35-NDIBT<<~()1|Z4NsV~iytAYbRqb8i#*E_7BMFTh_!Xpbl z*hD-Y2+R{$WF~5Y7?H946!hG*1i8dL=%|eaTb%V^c*|gT*%smgRx~AZQj=!`O+4aD zbEJa9opex>US51s=5XG&Xi0K{_5A22-atoSzxZ6@e2hE|4G=~r*u_Hb{5<*8qkUls z>NaZy^6N4b_W?G=bOD=%r5cDX-D*>54Q8_2o}xi!co~K*tIcP6z7WqXi-J}E3o3qP z069{&8Z8(XV5>Vj`qrC}i&|ZE>%*_lF8mQ45x9v$=ouEuFI@DG`Rz6HaDTdB{c;ytdw`%en?7XBbuW_A}w%o)(e;@eU^k4(3Ay7imj zbemP*0(i?VcR?G5D)!Sd*OC0}@ z>80|>2dA~M8d3bDikis>>U4eN6CE(7j7g6Gej2Y0==!8T&5BtzBKNOC3(M!S`ON&GfJ1zx@G=e-qzvNON!v&5Ia9T|ihIVk`kN zTr=OQEWj_1!8Pv`Fn~EO(ZPv{Wqde)vORg}9;rR^etv4I)4W=ay}YfUZxvg#n}To3 zFI1kJK&e~2ll{sS=HusV$ZG1#?G0IBbbtf|ZDn2y!1%Th6Ht*aj-iMS94E(3k(&E( z#u1@Mc#&}<#84skEp%G7<2_d_N-9DbO|iLIYlMpkw;X}eLM8>%v;t8>XHNCzRJ~kC zxf5URqzRzemCj7`ehU;bOvEMi(pWJmHiCevNfOHdz;(vbc-~%ogj5F=CG^^=BglVTa0}2-aSmIL|PwyLl;jNab1DCw%6`~vFNKIKn=F@s+ zFsGxu7kjjCq&d>5lfF8R=}ag`2nH8eLHVQ+da#MBT2Uqu_I}#e$Dgc8=NScI7{H& zrZmx%McSUPh>QPS>~cH!X-DS$yVDyHLVVm@Xhd4uLmRv+(`(e(SF4dU*=-mn!)R=vF-~L7K;xNK!{PCTgS&k_-cfoMY_NY?OeMkdgF6iH0)b*7cZH)+rAV(0yM6r^g)hcst zY6UaI$Dzm?aswNG@2II^RmPRd8df6R_6Shu15wq%T9D=_^g>N{vCu7PMJ1~!L9E#3 z-{}_tb_>$grmp`rQa-rQaZpef*m`nv-*TR@te%G0xu5dYUF{S1q(~QVzXb@I9V(7% zF9L25NfOsN8({6}1z(=G+ZtqADLP$Uw~(`;mN=8$P`M8`U*F|| zm*s;%KO5h-#60H86b%Z^c+VRu(q(HYnvgHFu%UpqJ!j>>XUx4cZ&c>7y+bz($*r+t z%#r9GEN=Ml7{3~RoX77Uu?4!YNu+D67yLfo7Tgmx@0J808_}YlBpv4itt7h=u_Ut` zzslf2WM}W!L^7mt=gg|(nwidofQAno$r;5od0X7-Eud3>1D2^ZEk%PLA^hF{FDc)X zD!?*^r*aN@3gjHGZ4Rd$U|2?Khhi!{*a;R_dUX`>4&2rJGmS#HtK5jotnZ6Gv@f*w z-tXtVKaZDkY_G_PCl=|DqLMvu#_5~#{P|$C390?N*Y_U}Qp1QC8L29imWhBKkW$9oeq)o>}R7} z`wbK41pcHZCgxh!N%6k-O`2`O4e7+hUw-KWkxi1@qhFqmceYUw97WpSqSt%`AbNgs zPx3W!LV(ogsOo(7M6X3oTy=9UA;VuY-_z%{4<>`b`NQm^=Z#&E5IiQhPV%eUHIZV1 z_<#Cb z&PS#3;eMpl2vDs4%gsJ;x)<{|2(&iqy(~=$Z>3Iy{B~~u@OhK@AcNr{8Q4hgFt9EK z?0&+=ayU3a%P11jV<@rpd=qNWzxY%LI%vo4XcVIpw`EhOHu1F zk+M=mM7GBG!`Ji3*?k7yDUcn{{ z^c=P*ugG2g#HY`>6SV&fUp;Jl6`SLzX583)2E)a4X9F?PDG_|q7OM_wof^|KHtnmK z&?#unh?N+fyy%b39M_pZL05w`{URhvbzNf{-u1@3)|*XTp4?sVT-c2>^Z8KdQ2$+w z01V_%aIm--P;J55`5-MKaKER7UIbnf1O_KyhT%+1Mt1W%1=mgIm61^qLMKX1vtSoO3+b zb7t^up?kv-br*OZq*YTM*DkOVv;=GZk=kop6zVYuB?l&Wthw2xc0 zeX7&w@G*z-R+x_p-!qg6f$%BO>IqO$MyFrT>;4Su!mInHT{SXu+i3nwa+NM;y59d$ z51gtp*RtOlo|R%60kbyq_oIpQ+mt_Rl0h3rji17W$JBEV6wR;+6O=tzVZ3xo)z?6= zjSej8BW*ld$^aF8@DCmX`E)8$96{91*UaF*iqH{YcP|BxH zNz2eFfzw4k)POIHY?}31O0ZE7V*8@Jq49s-dSwW}c^zSkQ2%Wu^ghHE-SWoAw!oTS zVRLVR4x=p0vvTIShM>IPTTqM_l`NH}d8GYUFEls;Qh9ClT>$ ztm~vZ44h6kkJBwrp`x=agi>ZxX_EM}DHUNS{fvYMrD)R|;<(}tTa~(ueQSGWc4xRM z<(GN<87U^05>*;ULm9>e1Xj27cMige&kN%CTya5r9gMB*2NQ!4(Hbe3$s5<9aXu7M^F6)fZ6dyJ+_GJ*lni< zt}@dg%O(zODp*{pE>CtkrL6tGb-A zgzpFL+j?r!@dS4nPT{`7F!Kl326on*oqSwhBb5@di@jn)D2sPCMxk&e0n3R2qP(<~ z>s;qUM-%N*h6c8Komi9?iJgy)DI)z?AZ5au$V)8U#IjPJ&13`;VQ3e>S_nNP;oS6w zq}eWUC;Vt+_;DTk+7(L$Y)wTd0xa9!o{e?-2v+0WaISOiWJbg`{ zVXr{$u><=C8I_Y;JJeeWJdsw4&3eW2f{B_+*jm<+VDE+#&uG62dpa-cD1`>?l1(2X zO}OD_(k12`@Ep~k4~fCF$*An2kxdCClW9E|0=r^bKCiF*YX4Bw)-uSHb7via6l3BK zZY-A_!0uZE3!OGM8=0w2?0>kBye4XIRbb5S&V;O9!&(1uT{{v>l7P83a}E5+fYTIM z737^4ot_q^@b6zSVh@a-EOJ}+dj Date: Tue, 20 Apr 2021 16:46:10 +0200 Subject: [PATCH 045/180] fix mongo cloud dns connection --- igniter/tools.py | 2 +- setup.py | 23 ++++++++++++++++------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/igniter/tools.py b/igniter/tools.py index ff2db6bc7e..368e9a2b3d 100644 --- a/igniter/tools.py +++ b/igniter/tools.py @@ -130,7 +130,7 @@ def validate_mongo_connection(cnx: str) -> (bool, str): mongo_args["port"] = int(port) try: - client = MongoClient(**mongo_args) + client = MongoClient(cnx) client.server_info() client.close() except ServerSelectionTimeoutError as e: diff --git a/setup.py b/setup.py index fd589e5251..309f83c05c 100644 --- a/setup.py +++ b/setup.py @@ -45,7 +45,8 @@ install_requires = [ "googleapiclient", "httplib2", # Harmony implementation - "filecmp" + "filecmp", + "dns" ] includes = [] @@ -69,7 +70,11 @@ if sys.platform == "win32": "pythoncom" ]) -build_options = dict( + +icon_path = openpype_root / "igniter" / "openpype.ico" +mac_icon_path = openpype_root / "igniter" / "openpype.icns" + +build_exe_options = dict( packages=install_requires, includes=includes, excludes=excludes, @@ -78,13 +83,16 @@ build_options = dict( optimize=0 ) -icon_path = openpype_root / "igniter" / "openpype.ico" +bdist_mac_options = dict( + bundle_name="OpenPype", + iconfile=mac_icon_path +) executables = [ - Executable("start.py", base=None, - target_name="openpype_console", icon=icon_path.as_posix()), Executable("start.py", base=base, - target_name="openpype_gui", icon=icon_path.as_posix()) + target_name="openpype_gui", icon=icon_path.as_posix()), + Executable("start.py", base=None, + target_name="openpype_console", icon=icon_path.as_posix()) ] setup( @@ -93,7 +101,8 @@ setup( description="Ultimate pipeline", cmdclass={"build_sphinx": BuildDoc}, options={ - "build_exe": build_options, + "build_exe": build_exe_options, + "bdist_mac": bdist_mac_options, "build_sphinx": { "project": "OpenPype", "version": __version__, From 29a04d913f428355c8eacbf89d33b82a0117045a Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Tue, 20 Apr 2021 16:46:22 +0200 Subject: [PATCH 046/180] and OPENPYPE_ROOT to sys.path --- start.py | 1 + 1 file changed, 1 insertion(+) diff --git a/start.py b/start.py index a2a03f112c..0d4addf59f 100644 --- a/start.py +++ b/start.py @@ -115,6 +115,7 @@ else: os.path.join(OPENPYPE_ROOT, "dependencies") ) sys.path.append(frozen_libs) + sys.path.insert(0, OPENPYPE_ROOT) # add stuff from `/dependencies` to PYTHONPATH. pythonpath = os.getenv("PYTHONPATH", "") paths = pythonpath.split(os.pathsep) From 2b488c5935d2b51d2a8ab404fd11a2286dda8041 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Tue, 20 Apr 2021 16:48:11 +0200 Subject: [PATCH 047/180] copy dependencies into openpype.app on mac --- tools/build_dependencies.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/build_dependencies.py b/tools/build_dependencies.py index e49e930a70..6c87a4d689 100644 --- a/tools/build_dependencies.py +++ b/tools/build_dependencies.py @@ -22,6 +22,7 @@ import os import sys import site from distutils.util import get_platform +import platform from pathlib import Path import shutil import blessed @@ -76,7 +77,10 @@ _print(f"Working with: {site_pkg}", 2) build_dir = "exe.{}-{}".format(get_platform(), sys.version[0:3]) # create full path -build_dir = Path(os.path.dirname(__file__)).parent / "build" / build_dir +if platform.system().lower() == "darwin": + build_dir = Path(os.path.dirname(__file__)).parent / "build" / "OpenPype.app" / "Contents" / "MacOS" +else: + build_dir = Path(os.path.dirname(__file__)).parent / "build" / build_dir _print(f"Using build at {build_dir}", 2) if not build_dir.exists(): From 919863307b2a2edea6019a94dee503c36336f9d7 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Tue, 20 Apr 2021 16:56:33 +0200 Subject: [PATCH 048/180] use bdist_mac command to build on mac --- tools/build.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tools/build.sh b/tools/build.sh index b95e2969c4..c6938494ae 100755 --- a/tools/build.sh +++ b/tools/build.sh @@ -158,7 +158,12 @@ main () { fi echo -e "${BIGreen}>>>${RST} Building ..." - poetry run python3 "$openpype_root/setup.py" build > "$openpype_root/build/build.log" || { echo -e "${BIRed}!!!${RST} Build failed, see the build log."; return; } + if [[ "$OSTYPE" == "linux-gnu"* ]]; then + poetry run python3 "$openpype_root/setup.py" build > "$openpype_root/build/build.log" || { echo -e "${BIRed}!!!${RST} Build failed, see the build log."; return; } + elif [[ "$OSTYPE" == "darwin"* ]]; then + poetry run python3 "$openpype_root/setup.py" bdist_mac > "$openpype_root/build/build.log" || { echo -e "${BIRed}!!!${RST} Build failed, see the build log."; return; } + fi + poetry run python3 "$openpype_root/tools/build_dependencies.py" echo -e "${BICyan}>>>${RST} All done. You will find OpenPype and build log in \c" From afca03cdd12e0ccbfb934fbd5f9beebbde05e310 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 20 Apr 2021 16:59:14 +0200 Subject: [PATCH 049/180] Hiero: updating documentation --- website/docs/artist_hosts_hiero.md | 193 ++++++++++++ website/docs/artist_hosts_nukestudio.md | 284 ------------------ website/docs/assets/hiero_createUIFrames.png | Bin 0 -> 40766 bytes website/docs/assets/hiero_createUIRename.png | Bin 0 -> 30586 bytes website/docs/assets/hiero_defaultTags.png | Bin 0 -> 7266 bytes website/docs/assets/hiero_instanceCreator.png | Bin 0 -> 10665 bytes .../docs/assets/hiero_menuColorspaceClip.png | Bin 0 -> 6593 bytes .../assets/hiero_menuColorspaceProject.png | Bin 0 -> 6864 bytes website/docs/assets/hiero_menuCreate.png | Bin 0 -> 6545 bytes website/docs/assets/hiero_menuDefaultTags.png | Bin 0 -> 9200 bytes website/docs/assets/hiero_menuPublish.png | Bin 0 -> 6011 bytes website/docs/assets/hiero_tagHandles.png | Bin 0 -> 20629 bytes website/docs/assets/hiero_timelinePrep.png | Bin 0 -> 17150 bytes .../assets/nukestudio_basic_clipNaming.png | Bin 21136 -> 0 bytes .../docs/assets/nukestudio_defaultTags.png | Bin 5628 -> 0 bytes website/docs/assets/nukestudio_lutSucess.png | Bin 14149 -> 0 bytes .../assets/nukestudio_publishing_basic.png | Bin 159949 -> 0 bytes website/docs/assets/nukestudio_reviewing.png | Bin 44909 -> 0 bytes website/docs/assets/nukestudio_setContext.png | Bin 13623 -> 0 bytes .../docs/assets/nukestudio_softEffects.png | Bin 29510 -> 0 bytes .../assets/nukestudio_tagsToClips_basic.png | Bin 180993 -> 0 bytes .../nukestudio_workfiles_openCorrect.png | Bin 78681 -> 0 bytes .../nukestudio_workfiles_openingLimit.png | Bin 74591 -> 0 bytes website/sidebars.js | 2 +- 24 files changed, 194 insertions(+), 285 deletions(-) create mode 100644 website/docs/artist_hosts_hiero.md delete mode 100644 website/docs/artist_hosts_nukestudio.md create mode 100644 website/docs/assets/hiero_createUIFrames.png create mode 100644 website/docs/assets/hiero_createUIRename.png create mode 100644 website/docs/assets/hiero_defaultTags.png create mode 100644 website/docs/assets/hiero_instanceCreator.png create mode 100644 website/docs/assets/hiero_menuColorspaceClip.png create mode 100644 website/docs/assets/hiero_menuColorspaceProject.png create mode 100644 website/docs/assets/hiero_menuCreate.png create mode 100644 website/docs/assets/hiero_menuDefaultTags.png create mode 100644 website/docs/assets/hiero_menuPublish.png create mode 100644 website/docs/assets/hiero_tagHandles.png create mode 100644 website/docs/assets/hiero_timelinePrep.png delete mode 100644 website/docs/assets/nukestudio_basic_clipNaming.png delete mode 100644 website/docs/assets/nukestudio_defaultTags.png delete mode 100644 website/docs/assets/nukestudio_lutSucess.png delete mode 100644 website/docs/assets/nukestudio_publishing_basic.png delete mode 100644 website/docs/assets/nukestudio_reviewing.png delete mode 100644 website/docs/assets/nukestudio_setContext.png delete mode 100644 website/docs/assets/nukestudio_softEffects.png delete mode 100644 website/docs/assets/nukestudio_tagsToClips_basic.png delete mode 100644 website/docs/assets/nukestudio_workfiles_openCorrect.png delete mode 100644 website/docs/assets/nukestudio_workfiles_openingLimit.png diff --git a/website/docs/artist_hosts_hiero.md b/website/docs/artist_hosts_hiero.md new file mode 100644 index 0000000000..4ada1fba2d --- /dev/null +++ b/website/docs/artist_hosts_hiero.md @@ -0,0 +1,193 @@ +--- +id: artist_hosts_hiero +title: Hiero +sidebar_label: Hiero / Nuke Studio +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +:::note +All the information also applies to **_Nuke Studio_**(NKS), but for simplicity we only refer to Hiero/NKS. The workflows are identical for both. We are supporting versions **`11.0`** and above. +::: + + + +## OpenPype global tools + +- [Work Files](artist_tools.md#workfiles) +- [Create](artist_tools.md#creator) +- [Load](artist_tools.md#loader) +- [Manage (Inventory)](artist_tools.md#inventory) +- [Publish](artist_tools.md#publisher) + + +## Hiero specific tools + + + +
+
+ +### Create Default Tags + +This tool will recreate all necessary OpenPype tags needed for successful publishing. It is automatically ran at start of the Hiero/NKS. Use this tool to manually re-create all the tags if you accidentaly delete them, or you want to reset them to default values. + +#### Result + +- Will create tags in Tags bin in case there were none +- Will set all tags to default values if they have been altered + +
+
+ +![Default Tags](assets/hiero_defaultTags.png) + +
+
+ +
+
+ +### Apply Colorspace Project + +This tool will set any defined colorspace definition from OpenPype `Settings / Project / Anatomy / Color Management and Output Formats / Hiero / Workfile` to Hiero `menu / Project / Edit Settings / Color Management tab` + +#### Result + +- Define corect color management settings on project + +
+
+ +![Default Tags](assets/hiero_menuColorspaceProject.png) + +
+
+ + +
+
+ +### Apply Colorspace Clips + +This tool will set any defined colorspace definition from OpenPype `Settings / Project / Anatomy / Color Management and Output Formats / Hiero / Colorspace on Inputs by regex detection` to any matching clip's source path. + +#### Result + +- Set correct `Set Media Color Transform` on each clip of active timeline if it matches defined expressions + +
+
+ +![Default Tags](assets/hiero_menuColorspaceClip.png) + +
+
+ +## Publishing Shots + + + +
+ +With OpenPype, you can use Hiero/NKS as a starting point for creating a project's **shots** as *assets* from timeline clips with its *hierarchycal parents* like **episodes**, **sequences**, **folders**, and its child **tasks**. Most importantly it will create **versions** of plate *subsets*, with or without **reference video**. Publishig is naturally creating clip's **thumbnails** and assigns it to shot *asset*. Hiero is also publishing **audio** *subset* and various **soft-effects** either as retiming component as part of published plates or **color-tranformations**, that will be evailable later on for compositor artists to use either as *viewport input-process* or *loaded nodes* in graph editor. +



+ +### Preparing timeline for conversion to instances +Because we don't support on-fly data conversion so in case of working with raw camera sources or some other formats which need to be converted for 2D/3D work. We suggest to convert those before and reconform the timeline. Before any clips in timeline could be converted to publishable instances we recomend following. +1. Merge all tracks which supposed to be one and they are multipy only because of editor's style +2. Rename tracks to follow basic structure > if only one layer then `main` in case of multiple layer (elements) for one shot then `main`, and other elements for example: `bg`, `greenscreen`, `fg01`, `fg02`, `display01`, etc. please avoid using [-/_.,%&*] or spaces. These names will be later used in *subset* name creation as `{family}{trackName}` so for example **plateMain** or **plateFg01** +3. Define correct `Set Media Color Transform` at all clips as those will be also published to metadata and used for later loading with correct color transformation. +4. Reviewable video material which you wish to be used as preview videos on any supported Projec manager platform (Ftrack) has to be added ideally to track named **review**. This can be offline edit used as reference video for 2D/3D artists. This video material can be edited to fit length of **main** timeline track or it cand be one long video clip under all clips in **main** track, because OpenPype will trim this to appropriate length with use of FFMPEG. Please be avare we only support MP4(h264) or JPG sequence at the moment. + +
+ +![Create menu](assets/hiero_timelinePrep.png) + +
+ + +### Converting timeline clips to instances + +Every clip on timeline which is inteded to be published has to be converted to publishable instance. + +
+ +In OpenPype it is done by tagging a clip with our own metadata. Select all clips you wish to convert and `menu > OpenPype > Create`. +



+ +
+ +
+ +![Create menu](assets/hiero_menuCreate.png) + +
+ +
+ +Then chose `Create Publishable Clip` in **Instance Creator** dialogue. +

+ +Then you can alter Subset name, but this will be changed dynamically and replaces with timeline's track name. +

+ +Keep **Use selection** on. +

+ +Hit **Create** +

+
+ +
+ +![Instance Creator](assets/hiero_instanceCreator.png) + +
+
+ +Dialogue `Pype publish attributes creator` will open. Here you can define instance properties. If you wish to rename clips dynamically during creation then Keep **Rename clips** ticked. +

+ +In case you wish to use *multiple elements of shots* workflow then keep **Enamble vertical sync** ticked on and define correct hero track which is holding main plates, this is usually the **main** track. +
+ +
+ +![Create menu](assets/hiero_createUIRename.png) + +
+
+ +Subset name is created dynamically if `` is selected on **Subset name**. +

+ +I case you wish to publish reviewable video as explained above then find the appropriate track from drop down menu **Use review track**. Usually named `review` +

+ +Hover above each input field for help. +

+ +Handles can be defined here to. In case you wish to have individual clip set differently we recomend to set here the default value and later change those in the created OpenPype tag's metadata under `handleStart` and `handleEnd` properties (look bellow for details) +
+ +
+ +![Create menu](assets/hiero_createUIFrames.png) + +
+
+ +After you hit **Ok** tags are added to selected clips (except clips in **review** tracks). +

+ +If you wish to change any individual propertie of the shot then you are able to do it here. In this example we can change `handleStart` and `handleEnd` to some other values. +
+ +
+ +![Create menu](assets/hiero_tagHandles.png) + +
+
diff --git a/website/docs/artist_hosts_nukestudio.md b/website/docs/artist_hosts_nukestudio.md deleted file mode 100644 index 23301f53bf..0000000000 --- a/website/docs/artist_hosts_nukestudio.md +++ /dev/null @@ -1,284 +0,0 @@ ---- -id: artist_hosts_nukestudio -title: Hiero -sidebar_label: Hiero / Nuke Studio ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -:::note -All the information also applies to **_Nuke Studio_**, but for simplicity we only refer to Hiero. The workflows are identical for both. We are supporting versions **`11.0`** and above. -::: - - -## Hiero specific tools - - - -
-
- -### Create Default Tags - -This tool will recreate all necessary OpenPype tags needed for successful publishing. It is automatically ran at start of the Hiero. Use this tool to manually re-create all the tags if you accidentaly delete them, or you want to reset them to default values. - -
-
- -![Default Tags](assets/nukestudio_defaultTags.png) - -
-
- -#### Result - -- Will create tags in Tags bin in case there were none -- Will set all tags to default values if they have been altered - -## Publishing Shots - - - -
-
- -With OpenPype, you can use Hiero as a starting point for creating project hierarchy in avalon and ftrack database (episodes, sequences, shots, folders etc.), publishing plates, reference quicktimes, audio and various soft effects that will be evailable later on for compositors and 3D artist to use. - -There are two ways to `Publish` data and create shots in database from Hiero. Use either context menu on right clicking selected clips or go to top `menu > OpenPype > Publish`. - -
-
- -![Clips naming](assets/nukestudio_basic_clipNaming.png) - -
-
- -Keep in mind that the publishing currently works on selected shots - -Shot names for all the related plates that you want to publish (subsets) has to be the same to be correctly paired together (as it is shown in image). -Note the layer **review** which contains `plateMainReview`. -This media is just h264, 1920x1080 video for that will be used as preview of the actual `plateMain` subset and will be uploaded to Ftrack. We explain how to work with review tag in [**Reviewing**](#reviewing). - - -:::important -To to successfuly publish a shot from Hiero: -1. At least one clip of your shot must be tagged with `Hierarchy`, `subset` and `handleStart/End`. -2. Your source media must be pre-cut to correct length (including handles) -::: - -### Tagging - - -OpenPype's custom tags are used for defining shot parameters and to define which clips and how they are going to be published. - -If you want to add any properties to your clips you'll need to adjust values on the given tag and then drag it onto the clip. - - -
- -![Tags basic](assets/nukestudio_tagsToClips_basic.png) - -
- -1. double click on preferable tag and drag&drop it to selected clip(s) -2. Basic set of tags on clip (usually subset: plateMain) -3. Additionally select clip and edit its parameters -4. Edit parameters here but do not touch `family` - -
-
- -:::important -Only clips with `subset` will be directly processed for publishing. -::: - -### Custom Tags Details - -#### Asset related -| Icon | Description | Editable | Options | -| ------------------- | ---------------------------------------------------------------------------------- | ------------------------------------- | ---------------------------------------------------------------------------------------- | -| ![Hierarchy][hi] | Define parent hierarchy of the shot. Usually combined with one of subset tags. | root, folder, sequence, episode, shot | example: {sequence} = name of Hiero sequence or overwrite by any text without `-` or `_` | -| ![Frame Start][fst] | Set start frame of the shot. Using `"source"` will keep original frame numbers. | number | int `number` or `"source"` | - - -#### Subsets - -| Icon | Description | Editable | Options | -| ------------------ | ------------------------------------------------------------------------------ | -------- | --------------------------------- | -| ![Review][rew] | Choose which track holds review quicktime for the given shot. | track | `"review"` or other track name | -| ![Plate Main][pmn] | Main plate subset identifier | subset | `"main"` or other | -| ![Plate FG][pfg] | Foreground plate subset identifier (comped over the main plate) | subset | `"Fg##"` or other | -| ![Plate BG][pbg] | Background plate subset identifier (comped under the main plate) | subset | `"Bg##"` or other | -| ![Plate Ref][ref] | Reference plate subset identifier | subset | `"Ref"` or other | - -#### Subset's attributes - -| Icon | Description | Editable | Options | -| ------------------ | --------------------------------------------------------------------------------- | ------------------- | ----------------------------- | -| ![Resolution][rsl] | Use source resolution instead of sequence settings. | none | | -| ![Retiming][rtm] | Publish retime metadata to shot if retime or time-warp found on clip | marginIn, marginOut | int `number` frame cushioning | -| ![Lens][lns] | Specify lens focal length metadata (work in progress) | focalLengthMm | int `number` | - -#### Handles - -| Icon | Description | Editable | Options | -| --------------------- | ---------------------------------------------------------------------------- | -------- | -------------------------- | -| ![Handles Start][ahs] | Handles at the start of the clip/shot | value | change to any int `number` | -| ![Handles End][ahe] | Handles at the end of a clip/shot | value | change to any int `number` | - -[hi]: assets/nks_icons/hierarchy.png - -[ahs]: assets/nks_icons/3_add_handles_start.png - -[ahe]: assets/nks_icons/1_add_handles_end.png - -[rsl]: assets/nks_icons/resolution.png - -[rtm]: assets/nks_icons/retiming.png - -[rew]: assets/nks_icons/review.png - -[pmn]: assets/nks_icons/z_layer_main.png - -[pfg]: assets/nks_icons/z_layer_fg.png - -[pbg]: assets/nks_icons/z_layer_bg.png - -[lns]: assets/nks_icons/lense1.png - -[fst]: assets/nks_icons/frame_start.png - -[ref]: assets/nks_icons/reference.png - -### Handles - -OpenPype requires handle information in shot metadata even if they are set to 0. -For this you need to add handles tags to the main clip (Should be the one with Hierarchy tag). -This way we are defining a shot property. In case you wish to have different -handles on other subsets (e.g. when plateBG is longer than plateFG) you can add handle tags with different value to this longer plate. - -If you wish to have different handles length (say 100) than one of the default tags, simply drag `start: add 10 frames` to your clip -and then go to clips tags, find the tag, then replace 10 for 100 in name and also change value to 100. -This is also explained following tutorial [`Extending premade handles tags`](#extending-premade-handles-tags) - -:::caution -Even if you don't need any handles you have to add `start: add 0 frames` and `end: add 0 frames` tags to the clip with Hierarchy tag. -::: - -### Retiming - -OpenPype is also able to publish retiming parameters into the database. -Any clip with **editorial**/**retime** or **TimeWarp** soft effect has to be tagged with `Retiming` tag, if you want this information preserved during publishing. - -Any animation on **TimeWarp** is also preserved and reapplied in _Nuke_. - -You can only combine **retime** and with a single **Timewarp**. - -### Reviewing - -There are two ways to publish reviewable **h264 mov** into OpenPype (and Ftrack). - - - - - - - -The first one uses the Review Tag pointing to the track that holds the reviewable quicktimes for plates. - -This tag metadata has `track` key inside that points to `review` track by default. If you drop this tag onto any publishable clip on the timeline you're telling OpenPype "you will find quicktime version of this plate on `review` track (clips must have the same name)" - -In the image on the right we dropped it to **plateMain** clip. Then we renamed the layer tha hold reviewable quicktime called `plateMainReview`. You can see that the clip names are the same. - - - -
- -![Reviewing](assets/nukestudio_reviewing.png) - -
- -1. `-review` suffix is added to publishing item label if any reviewable file is found -2. `plateMain` clip is holding the Review tag -3. layer name is `review` as it is used as default in _Review_ Tag in _track_ -4. name of clip is the same across all subsets - -
-
- - -
- - -Second way would be to add the **h264 mov 1920x1080** into the same folder -as image sequence. The name of the file has to be the same as image sequence. -Publisher will pick this file up and add it to the files list during collecting. -This will also add `"- review"` to instance label in **Publish**. - -Example: - -- img seq: `image_sequence_name.0001.exr` -- mov: `image_sequence_name.mov` - - -
- - --------------- - - -### LUT Workflow - - -
-
- -It is possible to publish Hiero soft effects for compositors to use later on. You can add the effect to a particular clip or to whole layer as shows on the picture. All clips -below the `Video 6` layer (green arrow) will be published with the **LUT** subset which combines all the colour corrections from he soft effects. Any clips above the `Video 6` layer will have no **LUT** published with them. - - -
-
- -![Reviewing](assets/nukestudio_softEffects.png) - -
-
- -Any external Lut files used in the soft effects will be copied over to `resources` of the published subset folder `lutPlateMain` (in our example). - -:::note - -
-
- -You cannot currently publish soft effects on their own because at the moment we only support soft effects as a part of other subset publishing. Image is demonstrating successful publishing. - -
-
- -![Reviewing](assets/nukestudio_lutSucess.png) - -
-
- -::: - -## Tutorials - - -### Basic publishing with soft effects - - - - -### Extending premade handles tags - - diff --git a/website/docs/assets/hiero_createUIFrames.png b/website/docs/assets/hiero_createUIFrames.png new file mode 100644 index 0000000000000000000000000000000000000000..798b3efb797bbeef8b6f6e9215d6b533a7e97b25 GIT binary patch literal 40766 zcmd?RcT|&GyDu6=QA9)qML~#IKt)7BKstg_7FCK!SLr1nARr}xBA`+N(hNxNh;%|H zx-6j=DItUq6heTIAT0D`&`SNDY`ONvu-}8IQ0I$A=Aih1Cx_&56V_$6XA-1N z^jhXM4@iItue{zd6CriR^by&mJM4tZIinNRJNEaVe#U-2VOjNd-ay#7n2SAz{6-5J zY`e5$YOljn7A}R|@ffY%LcFxD`dT;DOR`?{CMabs)SRwW#&_7HBi!i{OoN%kwP|C6 z+YS!?x6RC;Qr~!R$$g0i%5ZDbK5hetqkf7@A9=hd-|fI4Lqq46P~g5t3sorZR-m(& zN)&QvSX9J0AQ&>q2G$DQ1HAO) zYBmMeTwj}^;E#AZptu;a-mC{gAlX1+J$xs=KPGE98A!WVSN{CCGb7?(JkF6ye`<1+ z^+|buXK%@|V}G^GS*<{IUg6{j*2m%UQMC)~?7v$N{@0cUCVmseoKtB>GFzLrikM3} z%btue6ai1C6cMvMvbVZCTg*JR zK6-3?sxe-kd1JhsuA(H4-ypbb5JQ*pmch)m#j|zhcYb~X>kb-Sx!lBA5_*yORRJHb zvZVv-AQ(_yM&W&sF^b=_7lOYFPkr4Z6wfxu-HBGN(+OD|GhSX~;6qg~alfX)$C^f7 zdiL&O0>P{|u0w@B zwub;?&f?En)@-lnkLUxRLl+n6t<$$ixegXZvuawO-ksw&V*XHJCTxL22~S&mJ&fX1 z*21<1>V*USima%S&JP;uV4u3RW3`vt(OaKQg7_<*4g?9cJ|EeO6Tpw}f zy$eqL0KIy&=aMBaRV$)$%6Ah}?cRxQ6|9WTABMQ(7vdTtZDejhJD_8Hbe zuESWv(+}^8Z*77$}W;WM2t$18kdU+gxesr?e`xvDnt2$q20;5@&sZ}!@*%{svnlPhT3SI@}n#+d3}Udp$5w{7J&C>5DML& zo8C5iybp3GDT;G)8fcPMf3QJG^P_7;NSHH6Cb55MQ->kvLN$LAF$Qz>Qxw(i>85(v zR@3g^yLWG{=u_li^$t@$;KTu^s#L0&`8`!++S3|$Hgtvfnc!)2JQ3g2V9X$yEhdCg zM;f+dRisWSfFJpr39l(oP;~x3GLrJX<{x*?jl%|#%Nt|HoVmiR-LnF$9x4$17mt1W z9w(oVjDp<$e1^3-bL_AW z!t=Zjm=D*xp)_Y8bFdB@RsRa)20Po|X5JJq-c(_P%Jk0+N0BZTD)P2-A}Vwed=8Hq zFuYj6wu-vXR?2u7y67jIfB!BoR|%dTN;mbHO0r4Z9GQzE*p|n2Y>G0Hf*;K@PZ_9e zg(emWZ6Z6zM?-~~J@7@C8c9FEZ=JgxCKz-F&>7l3{566EOY3(TJ<}u=KV&dJoHVAH0aNB1eat1M7UF<-23vSAl;>2bUd_}g ze6xyQD0twoeBb-2H9oSsPjf@#JGs3K!RV~S1!@41Wmp_Ye9$acKD9bs60bGOfZ?8+ zY)mmPGK1{&7+X_p5>H~{)aN2Y_QIn7rym4iNqC!*Xh$QfF zfo1$Zc4?@y3j>`3-9Gqv6wQQ9Q_mGMu%TP2gsd{5cqUCkw}Wp6<*<=A#sAtX!ELSkF zw(+4$J$wXP2Em{m=@G2mMx!AUheq4)tBi0(XscZSa1KDaZ8#S&`eg$*Jz08w@K=-F z>Hk)1G=6hpy^Rk9a`ypdzhCVF2oxR;@Q3SKyFqs!u+(`kz}5C2_{kBDfWqEi2W;QT z{GFi7*S1*&2*kbzu(aXv`#|p|PyU?q4$!d^|NrSM26Eh}4;QbIcZfDD;N9$HS^j$O zkoxFzu}^*Gvc3rwvjN>f9?e*PSGPQZWsVti9%Z*j<~WO-I0+B$KhQCm-Xu&^^To=G z%H~2mFY30pw`;khpT@+-bgb$YGvd1hlAH>oGo)&u)|W=EAq9+P6%hd*V1I;vD0iKy zz3ojVqxH)hqIo^-${I)As10jdTDeN(ubm0^%ttf`?H+Er=r09& zM*fHA!=R5ZUZec_ApW$Ty(RQQGbqJR1-oQ*fA$ct>49LQ8c!-p;^uWDQYIxO*7Mi~b49xD)}|G+z%}xde;`7>h8e z4_!G$TciXOjzwgg>Lc6Ja)o`phF$vRvdBoKOsyrPsnrN>!v%q^&VY*h&emXhgK!vP zJ+q!#M^mU(ifcg|n2dxd+;w9~f{@SuOhP*>_1AXafc1oVPrY8qc;IM>PPM=E7J1S4 zt*4W{8($tuyV>!39Ku%a?BHOBX5ot10I2#@Q6jt~j!at!Q*csw^0wpK=8JCF5mLo@ z7(1ddKR6$8j#S8$yZ1rC0D_hE#ex@u46J%HC46U%x6DMc z_n-mfbF7N-#CRKtcxQDTCy?yx8?xYYN?sI|L7p*ti8ey{1nQS-62ywWp)=(4S70cT zx1;-w&o7a6743N8^=q}U{Up%j%iREjoNnN>b@|wo<*~5K~{cZg4}3G;^W?Z{rN6;Qy2)3@BfX z;Vl)I|G-WoOA$h{UOsr~K~lN&U3PgrQPhd~p3zL(Psgbfub7~q3(96{ed%`<$eTpO zFadwz;}Gd#NQ=+QQH5KRuQgx>RwbHW18&8=>QC#=y##))cHhbas}n(mQ;KknI&pbg z&@TM%>2sEFb%ewe&R5%ih{o%8Q74p}AS#m|3C<55&KH3t(hxYmvV0}uHj$!n0TY=T zqzA5_%Gl_5!?Ss3a8ikVsTKySzkX-=*iT1IRV z85D4l>q@<3=WFod>jwEXb2J=9S}M9UvySziGh|$JeUK$ibMZOxe)Dz|IZXTp0EwgX z@%*-Ke%c4-R9_yTobG{*7`VuV*QZ3lTO`CjLEnr z*Cn~fACisRazgyEMF(KbfJ$Tief1?t-;m8VQf^%}q3iYnw#`!yvas(bXJ?00JZaUT zZ#=T3996_+wf$DC;__v$jGPU6uBH`*R4*dJT~0IC2K^L_i`E?Fv-y5QTSJ3K`y0nd zNrhM9Rx9(kN2cA<*)9sb?+D5VxF$mA`sDg&d(PK0V$EvsS1@g@W)3~)n+3T#XHM&2 zvwYm(5KpVdm*d5Nckr$%A`WfU;gEYXz_Lo>p4CDVu4(_d8K#dQXtiCQi_h=-KJi=En;&D`Z`0HXn z<=RUb3mcWDwn4wbMShQsQpHuFZO}J`OB^&%7SfU4!WA$Oit+)dcEDY0 zEY)%$nWNt0Blyu+J<;7$`RFS9L@V{c?C8gbe01?gEtHIQ$C0s$skM6-_Z_gKdh(3@ zW3q7%*JWW3gY+#%9I|AWs1q`6eyD`?W_%>l+Sxk05<<< za#D@8rXb@0Iqvkp^5*&Kj5!72BdwZ%M$fuv-RxaB7^e2RBIDH7j(a~Xx-0CE(c_oj z%!d%R3cJJNUjTIA|1M9^fXA0g01=RWft;#quo3P9Ki>Hn%j8Fn(1f0kz3oj}C>8Gv zw6ji$3vlr1GV;8dKahJ#r_Kv5L)c^zVV%<+&A2C4^(4m{0xFdR_nYtX=?y9GC`s@m zKO#`t^ct}$Vsk|slq6{!GjshFk~nCflCk#1w2@~VE!epE zQrsZKpk6^j7Mr}M-lawegm}!h=fJD=Fa1Gf5?84ay1KgU^mRP`2u`E^Z2o;`2tJvz2Z z-|s7zcG9;*=piiS;Ov?;wpe{5O62UL+lad`(J5)CU(3Q83_m=4Fu~aLSui=k4 zx1I-);&D--a3{xp6u!)JV{MA&jw3mVzC2h;gC`gG7PYipteMhQC#e7Oj#~$CnKS$c~;nvbjx<7}*9d zeJTei>ngAU(%^73AHh)nQToWc*^rn_j-1Dm&14VRXZjtN>TT@NCO%-;LLK_Kh=!=W zfkk0878VOrfS{hxb?aeH_Go>W$F=;K`&1rKOsU#@l*-oE?T(>Zu@|jsYW&`U6Zg~w zkK-#|n~&QU$_m6;9x~c-*RFJ533#(V%3_ul!>eQwe^n@YlMA0Jj#E)HCc822&lg`cZX@jXXPxel0Kz%E3==S znt(u^9I;Sk0V5|Tr)D79)DKJ?WT@}FumZ#`a%wDrVWe3O1K}y~P4*r`koXf|o*Dom zWWNT6Nq_a~^1Yh>;B)K!^||6A3rTHaPfSYpj}S zWdKMKd6nN)@8}cX$c`sgvcywHwkcEZ<;_{09|Pv0JYXQa_QM_9;>81R(GiHmUp{h41fFi4{*)(17=l7qU*Lf@n8fAZ_y?gTp zGGb`+%k|^OGiHx{j*OL)<#UsisZzM(0qA-kKIX|>&q~MCnaV=gX_41s2X%l$2)&ZH zZ}oY|z7kG1E*>MxC!>E__^=xUfT1M6p5>9QUDbrrOR2=%>PCkWEFMB<@pA}{;La?U zxLR-O7&&@=5y9-N^&(GyR-4~Q?+rOM;a<#<>k+^&)7RBAyVSGN)8u6-OK7QSeXn~V z3=+p$x>Y?by`A`i52rCF9m6N9mZlGfrCltqXb+aey6&79d!h?NrS2U%iY}-Mx#gHh z)(ha>=ajgdFqT^;JK|6!b0M+D3(L2*j>FI~zAPf0x15|PDVMP78FF@?rz^Ot-rd)7`43VV)@0y-%&{cTvXFnb(%jNUNYwyD@^4VP;K^bbUMiV9UXz8Ny zvcZE3Ykd@zD@r!mOnv(36>|%;PjaZS`BMB?PPa9>WW3z19>9H$pBoLRsByIz{HvAx zhy^9o%EmS`x=Xj+t=zM;%ZUGQMaOyBA0qE7A8CsuL=J^J+?=~UcGHkweL5xcXreq!)azT?9qYKg6jt~Q#qdwO;Tqx%4Zf_X$;h##6nYsuB)QJJVjJWYCaj?csycEcXZ*qQi;fG zd4k5>!Xu!~6986Z%@)PGDt{@L=*&quc{DVqmFD%Me3}x_H6fVQ*?mnOi|yJyepk)` zDR&a#mb=CdxB~urJgEE1+%RnfjSCfugUm-7kI1XDpWB|43Uoi)dCUmF321JGnydZS zw2X)@WH?k^ttJcF1-~g%^XBc>aZMKJ-ut<#M~zHiX41QK^z+u79C(u)&3X(f ze%sc#1JvZibQ{{HOB(>l{c$S)01%1O7BGI*!vV8lK+PseUTAb2My0`KjC(hP~rj)Cc z_Tki>4uTn}>D1S_#tRa=Ng!30JyE{0@Ds;(z59*h7F}bJD7~Zw;(dsTi2t6>b^VU= zV%rrlE^}MOQ$ZG;H7}kHrIt^e=bMz_6>6^3rpblY2>X|}@m^#LQ)KDVlX#2NZA5Qp ze;4=FE0K2`Hb-PJwS@yImN(U1OTHs|)LiGLc3mD~S%8m~Q9}`RksKwM??juO@%=C? zd0~FpqFT94@g0zf@MV*#6!%>~t%qny!}65H`|3PG$SZvWcy@E zM~cuzDT=p2>d`F7+wY_WnTY4z^XEHFGGo7!qc!hU6!z&&V^7l=>31Sn=Yv#<^RI+F zQ*6sJlHBxFangj^RCy5{(%9mbK+Cea>q=y<_hX5d0!Ey>t{Y1nRHFyNmmB&DXl|p& zCzV^S?^m_G&pNUn6cXjnHoHbwPuxi@uzS3W5FX~Pd)&pwkDfN+H(Fa)J$SoN5mOkV z9XHKxf{U`zsI(O6(X*L)EN>~D$nPrgAV5_lAHfj|yyU3vKkEkmLr5$v=Mzh18$?VO z(^N(Yx}^fwrpz&$%kGDTPK#`n+dCRbjC||bDM>Tb*0Rp1=*aDTSuWAxlM~44_T`R( z0Qt1&f{1+Nkk(xD1ZULHBlfE`+_JCO>%Gnhugz?nnN8s;C3<@6T?i}xY2xm2mu&d= zpuYO}wyA6BjffAYq2oWxYM+%V8L;!wn+M?9_fNu~7Rw3HvBKW$Olk0j>}`Yk;Nhe> zV!;Qpgw!)$juOIRAb0SV&E$;DJuG)4>7EbIA}Cgk-ZNm4UXzLck(=u z!bpM9`fS`rqKEQ8X*P*zLvl4^7iDv)j#B9SF`2%PK&;Scri+GM8#IMz)!mblW9Nbg z)a1%XyO3^DZtNIhKEv-aEz;+=upHBU;+iT|3gDvy3-6u-lFZnx0u%1!2&GulWRQxe}jC!CMit~B`tJ~UP<<%oAsQNzo8)7dTK zH+UrM-bdCBL@MWN94*ISTEu^N-okn_EKPirTqpEQ^&-?<^W~yTYSKG&9m*H>&yJk` zVo-DdowxJc<86`cddz!Y3%o|?O1>$Tw}O=d*&LASF_w8b`3tG`|8E;9K%PEL)(o1s z8}!fsufEoLbybMO`8P{Nv6}*v`~|zok>AXB8})v1;ca%h19VRGDs|(}3 z0QKK~vW=R*nD`Nn+yE80_9w%?xH+I2NjHAe?SEK{Uj%&HOvpd_k!b=t2U`R{W|y#U z@VVtlHYym}-0 z_8}^vD}X=U^|q&c4@l|VwvBmvHGd@aKxg~x4V0m?`+cn*w-2jx_w_!D_|+#h1|Bl9 zZ3>hs39|8+P{bQ~K>Uk8mt0S&Q$OscI?jTm&8=(`0x}@d4!0UC0VA( z^s>2vSId191BsppI25-K2>X~zz^MdN7y-hOpZVA2bJXk#|{n~{Bbbh&n(Ei9zN<4W>noHWRS|8c_ zzmG3PD&P;tCrKRPDhX67YqUBk@$vIm#L#}(r$;ljm9i#AhgvY_?Q2LoR2P2&k$>At zMzs&Icale`FFR^zGGq0Ymadb&>E);LJ0yAJ6Wxr~(uVV`!XMa?YkkbG2RFW-55D#^ zP1#z0yv6FIriYeq+f2tf9-~2-*CcawnPwr->OmGG2Dn72Q2<0~AAwEvlsQH*kJcIWE>+@9UVe^+txUrei4)%v?)}&Sn$ga{|8bX5E3xi0m6y#8=3+R`o9G%;rhHG&Xcth ze1X^5K$p(}oR!5;^SOg3*_KD`V7s$s16Z*GVAD)qz-a$K#=sw6qa4uX*6&3Fg;;zR z(C6e+zvCAFcBsE27XO|?zwKn@HY@-7rSHE$a`6FRf3*Qi1}ZiF7t-zf7fxsV&6ioP z0jzeLiO=)?3wR3x+XTcgKp>N%f1&5|fRNjWF8_}J2=H3ePv8Y)96tZIQvkrc{BHwN ztp6`Wei(37wq1mOC&J+1V6K|Msv=927ytbZaXx)YzfKT0%elDET86QD&#VPpv+R!> zed;XJ^y>c;EAszxQw2)n1HZi(_ZUwR6W!^68|y}tRv&?u1!mFxCbkpC&0d$RiulG? zss%IC-yLM_&Rk{?B>`X$?0|mDNabfXC#W>ki;>QGW@o}vsuHfiGnYpEyJ~GyqwA;; zxLu{Nh!?H%`CXizStTsp!?@2YBJ_h_cN-3Ad2Tf$QQ#IPHiF~$@#D|+rkUZ3ACs{g zq0AP$9!ntb+qIT zb*}{2l;2!4)7R_LwaD}-%jkVf^Y`hC-oP=N_t;2zgi4W?H+j%KQ1J^)PYOdROxW}{ zGC)XxgtsMYbe2vA(7p&CfBxtAn+_DiPoo-etcGo0%Dr`CM;8YZOdg(~+MzixLEpR; z)h%@#{~XHES(?n)r6V<}5k9*#S5^Dd@6x2z?zsQ?yc7*m)_I((KxXfBq=`a5){w^= zND4#yb{&;!w@sC@J1r>_)VMa<9b;3|gFqdWm!(kO`}p5Tsl&_KB+nIddj$kNvIc3W{yKZMso(%<_-%=8EHsjiidMIRhg4KJP=~ z0=`8gnuI~~T#jME!+cbH+5tw?%lF-ie&K1%Dr97{spzx0$@vtl%-H^qjwcPq*4PJH zd9?Q~!O3~_w1X#{k)OO8YlXPx`HvZ4j+gP6&5JY$UU__$gXnm)4;SU*-XYJE)V&Ca z%ju`)EVHtHsgL88+ezBWv`rwdZBu zFCwfmhs}*;D{JMDAP8b`hGN^`CwPq z6VSJvp+&w942_WVM%s6#&4mDQ4PSk$5uHgi)_mcQ4Oex7J zS2eFj+VSqt$uIi@_2k&fI|c0%edqihU%r0^En*`#u^(P{(;Thrcf?3(z;6jv+(RD= z3(*KodyrY%IAC|K1zJ4rB4u7b5XS8|qj7#NIUjXKIfT#i<(lM$G~h!j|OV@*`3cVq~?aeDnAc`LSCC37soIje-nw&^8@uXdP@ zgcpaP+r(yn2+mg=mGN4XHg_Xi&u2ms`;<0Ftvi;H*WK-su)?-~2h03VZ1nnj%&q4) zb+5q(-rcc%;d$As!qTdtFueG~_{Blxi(}N7)$&<)T#8>_pbs@|$ZV?eJ}&N0TUm5| z+729MqEa@)K2CyvmUIi=SkmqyQ48~k%Ne8sOt{6(MK9=9)*zhx1k&dE!T&bD{a6M* zUm}AUDKP6c^GeA_=hm}_W00$!d;j?0_ZX3u(-@%Z0_O6{TV|6-(g@sQ5R$&@p;-GTD zy|G6AQlE;&K*&aI09$^nD`et^we(11iK9)%Kbg{&Q6n%qVTJbft zxyDV&OShY?g;K8AR}}QDzDbv9U2!T1#rqLu2R1w#9=DD83KwP$dN`-=gusz{U3f*S z5%019l7gU_4*qlqxWz~13U18NCe$L+(*+yk@TWDnqdS{T{TYi;hH#dscLXP$2tCss zb^sJ!@cY>E3;VwLR8u^M)(66YAcWX|hnwZ!ViC~f|2~&ux%~e}{BjFs{O@_;Z3iu= z{h|#}tatlAW8D8rgI(}#(CYe0&K?$b27F}0^DJ3dnZExl6#OeV{3{dp&$QmZ32syg z#=v4b=!*dc_bgO~4uISXfe7|Lf)$_qvfmryD~D+l>&hRL54;`R%<%)=l>$h81+w*LWRiH9X^BN5UWK9wlx{(@+83IBuvk`zCoV)vSf5v1;u=vt@ zE;lqZEXZm5+y`SJLPlaFw9NhzY92I0+&8=gY`D2ilg(;d-%)`&xv8~i z$#evM9LbUB#wn>psdXT+i?>@0F*-XqFm`D8gL;vQn7H=Qj}$5-GS1W9s<3C%*ZiT= zEAC5mROx+weR3#eWc8_)yq>mN6^V@AAl|DmjPkI(7Y>2@`Uh?Cat!;WR ze;=E%s3G?a5BP~#tPHB9?VM)!715gFi7P}#&&%bOHZ0!SPo%D0nw3wUP5!3_kF2Bf zg*cxUq=1h6c+}#hr%#Bu;%|MJnues2i<>SIcHegxi{3f<^<4Z}h#P3oeCUT)x{Nwq z2fHxjsifiA9CN8J8pXUe(HbnQh9++XTTz4W0RZarmWqOR-|+j_&j;x{iE$#jfJ8aP)amMHf0% z*5G)ro`zdd-d$qwLp-8qd3^8cek9HBkJ6nKlMe6%4d$ijVnR|yC(?y zbDD6gN~-Ea9s|XD&U;O%Ud}ARC4L_pEHQ0p>)lE_#tlxMzct!n69k9USNX|gS`Qf> zab|YA7Kap>BhPGUyniF}WpmiP5l}>lgpCI4#bAjq=;Dd8R*|}?0FFi3qfz$$G?Ej2 z(b*SWl>#27I8q0{dd2|H!GCB=Pj+7TPKtDPseL zivzLZzakE-8jZi)I)zf`J$(glRd0^!rU}1o?aPd%EURDCE z&VQW^WT^4afv3cZE3FsC z8>N2OX75bb%W}&ho}BzU3_g0mT@||YiSKdeA(LAs|NP?V{QC<1{SUr<~47r=V`vPVB36%aFn z;^JSZ{f7*w<$VwH`BZwa;BE!(xGrUsDUFtCvPGC#|>cz;#Q;O<*LWo7@a@{gol zeB>)3Q32Grc{ftrje&X-zx0z8Z8(u4`c&7NyY<_to_C0$D(H7upP~oL9MrTP*tr-YM03V668WX~n>4c}?!o!sr#X3y}iCzR|n?barm#C}!cl z@~P9OPa9cUrGDPL3D+ZO5X3|syzh@E!D3FN=Xj{#GIzzJ%)PG^WU0f`BGt!F0SGn?_X-7#tO>$Mq|Q`Pe(R1S;?P>C|D}7 z$L=O!MvHGZH>pnT( zDmreMeaPBaKPYZ1zVyBKjl!$XL}1c)oxqEUPB`*wGV(~o&_+UW%h~+Tph|{UlmU~e7ZVHpQ z{Mov`VqR>yvpq&}Y6ObGUm7~f4%|jyX)9Qm6&`H*w7fIBdE%)FSFlaIw0nR}WsFLS zKU%!*sh(!B%6nzk$zeAwCm_iD?QZK!xOUG1-l0>VTHZGC)7rU<+u2MGuKk-DnH)UU z5ku(d+qKAM&z6gOMCgpeMhQ2!&(bgXs_uf^tv$;Zt>g&1lrui*dc^NTBy@Yv`{s8v z{b(8)luVIrcKHY-I44SYTvg2LO#~9Ep5q*?gA+|86Zw^7XqL7~dA)5%N4Atn`{&J9 z5<;6v4iK$jS#lE3iFLHRBBOy3qIpS^mOgGPh0O|9_sn_FbH z!}pX`|1f`)lfC=@FPKR?wMa&|Dax!&U}I{!ZkPkoFF z9E=BF{y-w*=;sBlt&EK}H*a{gEPLGVoSLZfM0B`?c21DN0?|8XU-1~3>SRH{7Gtl0 zv{t|v;`A{iQwUNw?%MmR!6_$4Nhtg^{CQ_y{5*QCp0h6E@xLtdeT7P4(+_O@oPel@ z0G#=67Z1Ct5>^`v z`x^Uw4n6p#GcJ9ZO4UK@lx^E#`JUZ0+jZAAC~pt-%W?RrUcU-dwiV!45|dR^{8uI5 z|G-yDwj=fcKLo1$ST5A>k$w06j@1KUXyAYW;|p%f2MU`%_&fX_75_Wl{(CONKnC-_ z*3}nbt`Zt-*RS*hFKd`D@tX^OTibJyK<~B0{(ykF2&eGh@pe}K(ACaVLVL(hbGr{1 zIbXtd^^x7Ob7?1wi9YcSUw+~=v3Ux`1{#%Qtv7HthJP=RPCg@p%>7D9 zyZCgTCcnE=@^MYK%%YO8nTIC5%}bxEe--Ayy8A+xsvWf?D?%sus2_C@%(TR6T%VKBG-N3L-+xtM=kJ?{kV3IHx#y;Lf6&tfC<%7H;) zUw#hAZ&dTX+Bkr>__lt>>go+25kIK*%vhZR8_BArr(H|$DB8^4=0iS41Umy%Q1Xrg zDdUNJZgF>zyO}6Qaj!ez&b4C9e7|`g?w|7huzJ3d(SLUF(b&4&#QGaar4;vynwG+z z1H=zhE>J&E?Q3Ynj2JqzfNMJwwQ8Xwdk4W)b*r*k>ea1Aj)zh1ldGheTG|Bs+&c^g zJ0jzo0mn)Es>gK|_;QtAaw_~%>jm6Ax<+nWe{JPiCi5X1i?6knBjJ4Ih{9waXZ(9I z8}Y-|0nj<%Heu3Gs;oW+)AsOojn*|WL&~s{S7Ro|Gceq^2wEt7=|;Hq(7HWBs;$k> zw0BmfFm{HK#<~>bIT0eiR?;5;1aT>Pw{6lJi2y4Fs@%?%fIVMJssWeiB5KaW*Kw3s z_&Ww(oUmOUSpS59_tI)fFPK(XJwQUY`?h!G|SGKDO25bdfZ~h zZdSl-VZpvOmcyw>@`KH#?3${^>z57y_w;0K^xAxCb+uw&f#&Pr6K4qHVYLr>D)Q)% z-Upct-+~t?vj(P4KX8u4t9~3X#3H*v-9GqN*Z=PjGF7cDx}DYpeK~=>r*|tRVO2 zDZaf|(qXF=a_ABSOCgPZuFoN(0iMmJHd<@RjM1(j^QXq#ph_TD%PQsmddN0>J~%&= z_M&?(#^Y`wAE;99=WkeP(ftwHq*m>N|75+gxV=!&DGqtT|G$~^FQJ9_rkaj)*Py}lcy8Ub`C$I2aE7$U`Q zwyn4J)xM!^eg!k`ZM17Dp90foH3@cL+N-Tu7^66NYYLp2Y8zs%y!70lT~uX58=jSw znkZ_(U!DC{N6 zuR#kZyBS?M_tTv6ZX5d(tkB(Az*yT>sLxMN=f}jwb^iF#urOBFwd=NZ(ba~r@2)P- z>fB%Xs*6{eUf?bv3WeNZY^;Y=w8sg8`ej$4F6Z}wLS{itzu3d;PW5?kaNqi_J$pf; zu6QU)!9s0Me=HCH0ZcBzM94^;jSaNQsqzqn`^6yi4b)4S-`K<6zM~zM(E)rtz~p-a zbBsK&s55$ToA7oD=jVDW_*0T-_!XgS?@cRCr9IFViF-9HKOc}9@J{}~gl&F{me`pr z#%~f}`==x8dcwB!$zS>>+m5rDe%WD@<qjb12b>1IW5oavv>yFy zQ7!;1Ndiz;x1ZzRRB^e=|9*}B3>XG@s3YIN1N{H%SHk%stGg8|!{7fEN>uNRG@-GN zr`l{h8HbD~OSBw5_W_}%b+cld6w#l?r}gtaP~m0=O7T}v-ovTHC%%?I9pA6gQCU}2 zweOK?t`&dgVfq9cP10YAa_{M^f*kyNIzPu8&kBDJ6iI33JcL{?M6rz1Ez6{}wVQHR zs}w3@i!_Dns;NOY*uvX5NCKW{eIz^*C3;|Yr>sr|HN11~ojxETFd_Wq$RRTCQ~Bfh zcDJw>^5+u`vtH$h24<4MMtswOtC~%IlKTp$j4{z@ z>0!y~rf;6#b`h1ayr5g&3^|;Obx#MXJbNjmO|_cdB|>0f*;gU6YWiB8qhN-fU;UC( z>Iw~4+8iHveL@D(CeAmb-<9rKrnNho?_61fiVUIYz ztH;h`ne^An;U?JU?hN_KX984AW>`@3fRLRK;L|H&;wS`g zYYT!}cvE$uV%F;{wn9QE$$ILRU=3vTk&DcPhzrR?vU$7;FT_$gS#-%Y-n8$2^X3?* z%|?Svr8Q1(tjZpgvIJfd6{Kg7NB(%z8a7rSIP3sg_v^Cyj4ql(0ov&`8x#e_G<}8nZ0ONEfh3Q0UUD1uWmN zc5f))4WqDUAQ{@5_u{5Tb_e)n4UTLc**Js+uPwCMgS~JM>7dJ~q`k?2l5gw(KbOt* zcFIUPn@yQ-WEE6v7@t>p*U1i2sW|%jc~6KH(o#ed87DsgO&?e*gAHwYkLlZ6bNTP4Hgg^wt) zknqDXqsKj$KMI?=I1kpL(?t3bpJpudc16X}Kt)D&Esc~8@Q82+XQpt* zrpw6gOWswb((B+4 zyJ`!@@=shFbft<*@?#SVlr~Gnr?-_ok}8p-`GV8!dKdk5@Td5Xt#XZW z^Lz$eZ+gGzecs#ZgovHlfqEV{$>Y!|2f54eRZ%4k*}XHnkNpk6-fskCJ#hr9kO9pshC+8CD~mUp@)9!y85432c3Cr&-WO^}UAjPNbP zxeA@lMthYuSm)I7iNjJQ5t|tf>ZNTM_F-yQJ_jVwW3f{}6t{eB(mFjWiC$-)!82A( zdMM*{Lu4J@-IVA#8>`vj?y&l!uHq)SLZTyzEYn1}%^NpJuG7o7PA}VNM|zP_1t~*r zV)ZH=O)&r2bR;wiHFNE@jWmjP{koG&;?#qWv}f}ZfqHT92mHG+9rRCeE&>XHtLFf% zWra0c50nIt!>(Vcl`8R?Tpbm7vf!J;iIyawQ>82Cbl-4=_99%#?uwQ!<>(moLdy{P z!VTWLiQoC^LLH?og*?H#QTZ@<*KYZ7T$X^5FAlTdHH6Lu;k@-+jp_%dis>yOPMgWI z$Be|e-ke^kzCY$3S)S-Q?n2RYVpGpCgi^(T2uJfRP;3P)M!j@oV|C53@lf4*0$kNl zkvjLo)1T%m=Ys9ynTH|Et?^q;8A6)m`V*+PeVvSs>1*U}7hZign&{L6`4wG6A6rcP z3maAd7_iyBhN0tG8*wILt-|N7I4t(U$S-UbT>6J9-gtM$bW@G1yTtGWXu7HnxP{*A zPuR0aF)IZ396WAC6hz%wopEpsR`*p_iT<@{d2Vo zG579E?#oWpSD1&x9UJdC@c~umo44$#SBAi$*3E4jtZ&zzh!VI#Zi_tk+Ty9ns>Lr} z(jamqxf+O=e4ws4^$^(M=3OzC%TD3gdtxkJ@Hbg8!YbsKu6LZK#5hE2PD9_5E04sT zkOMe?!8Qjt@57OiF(SScSzTBXI_1_DO{Vr-HRV(+(k|O?QOOTMSarH-iXZTOB1P;=B)U6{EJs$ ze(84@Lv;DY12H}D?rT3V$9r2a9SbrWq;bjC$BH3k^p32x{q>1tpEA1F|HIyUMm3%G z?}I2KsE9aYK|w%91skA5h|+~oQ0YYkgy0|`B?Rd`Dhd`PC^hun6e2Bj0=7u+1QG%& zosb|Ul!TDI131q-|J~pJoISfQ_Qk&7c!=a%@B34)>-u ze>XFZc90}W@4rWmApRjyg5qn>^tx22>nCKmEp>P$jeC;Qd-&b(wd|FSpeM8)Rr0n2 z`nlpgeMSRn`D`--r-xx>2)sbtfSdQBvXhy13>p*+ozhZRs+vDTYX8DRHO zQ~nj`9?wlDY=-qF!MR;O9fHSgP|6Z0)%A)ZBs=J`>$itMYEUmTIbiOwB_};wsx}VQm_Sb-y`{K{c^jNS~ z%Qo)TC2VOJ=sd~6DfsOQ}=T+xA z!bGbJcePx+bzKLl0c8>5cN<0)xO~=O+(~@&26}`I-u8>2#q&PzxIXK zu_}M6&c7DB03CYLRQ)})R9_^PX2fKE7YOW6Z2lN3z)LyU+q$0OJATA>m^oy>UdJ#h z=kN7rGlx@xMm{zK^9Fmqb3w5~kt?BNp9RX3moqfO;}_DA!mPZBt1#Xm<>7Epz?;-3 zU&>K=j_o&yTX~5)g#3Yn<@ z-Lw|=+*`@gTBFu9Gjh5pR!^7_qj8g+j zoPWm2ou5p2UpIHii_v;9Q{}-aeHsO;!V|N1=HKrLBYwR-TG)DiaxU=gx>RMIA35=( zK+;MC`a54Q^%K9t)g??|nYamZs5a14+Opet2w70*f;IlR>A;EkEJJnP;IhY0eFo}@ zjV3CZFHd1CAE^(o;N0H`8t){@4GC9py7W(i)PKk{3EeMe=1Ou?eQXNoDzqqjdp9yX zav#}_l)Z90-g9JK1Xid>iKn#6UmwO`Nt&$3S!Ayb*i+~`; zcqke=9H2<{&X~jA`5Fb)m;kAf0^PWj>OdND@LedhI8@2ZPShn<{lr>~&v27;#}SD? zf2BPH3aE|N8fXRj+4P` zAnxUV&RA$)?@5un+rpFGA@935`z?gCcb0nj^ZF1$3((e?LY_}zYYk!-ks}>TbuLR7 zd~H?Lncd1BIsD8P1clO58_*K;8>b6( z5KZ7Z8lCWPx@ENQ5Fvq-O~QayBYMoF5sMCruZq2=Yy_n(BtP%;u;>o5F|q2jxd4X( zT7K$`5Nf&w6L%#j5ae-Rl&oGGDP#<~wJar``)Xd=+H^ffPa+D_csApBL10)vkR|!r zbDBTRPqh6n#2aROpe624)i*K2@}SwC=98eon_V+fC6RKYDOqtN=R+9rj=Gnp2M$&p zYp1zmOPKZ8QJxq2Ce#rlT{{AfufBoEa{oX8#s^R@?c`6v}~#6 zSG%Xils;1g8ixzffGhTdyak)%0lNva_F|2QVVS>_3HZ)8UxW&5xf7b-{xXGGnG%`8 z$V-u(fcC_gEUic-U9Ve#2(|{@c-o^8x)%uenv@im4jmo+yP)J1RU?00^r@(#uv73@ z!??Dy9kaID*3IKic4YC*&?NY>3*9c>JG^dTpp_&x zAlWW~vY1ZrIz9$w=G4$`AE7}Y9@C2l9}nD9G@wsJ}nTqCca8aiE{`V zMBcx#J|q+NwdwV^c775ZB2Ks2KaWtwS*YAqpU#*k)LlH?tn1ox9+J@(maEeCn;w7m zb>He@56hTMk0$oV?!3N~)j3rg6)yp};ExTDv1GDrgBT={lZfUDk*1za5x(Xics^$!%~-F?v_tb(R;3 zJhF%y=xGj_&kDZYL1!K=5;sYi%!@?C>4TC|6gMxGK^Tp(TrP$Mfn-#*3jvbRBk0%m z^F#<`4c;SCdFLkfV?C(5abgSvMPs@&V5BK`?cM=>#gHB`^fOhO-uC>}3f0{)?1k7F z{6Z0X48H^o9+d~3k~dbCKD~pVGR2soSiVl&6?B9hGUOc0Tz+iHhV4}~fLn)qepwuE zO}OIZaJb4EL>&xvPL0?Oq_op8t#hUJ*UlHnn)CG3L; z2|0ThZNy7lj#ysle0MCf=5J}(B@rZ_nl2JJ9AQ@p?a`1M{1HJ~y+4T&TP^94=>vvx zUnNNQF452HZv`txH3lQ{RoR1j?8^&uZ3X|l3FT59QBL$zQHrU8F}vxlh=6V-fn?udh{U_6*k#WW zB#SgnX8A2d&3<^wH|~_;amXZPzn@X>>cT|pu)@Z+D}b$kkZI;s`MGTYy6b6=zHp(4 z-vmMbiShmqH3^A-O1SpMjz;V3S1E|>yg}HY$iT2Fty6%LJYt4e@K*mLHF>3#rWrByLtP!J3EVl8K z8jW3Hqsqkkc)5(s>$#IPvweuYydy5ITpt@=X?7crhS*_OGQ?Q91Bkqce3;Ss1LAUj@am#! zUpHC_R=G+U1qGUE#J80a>&Knn_O*S1#7g9@dXqItDLQqCi@SX6ZE9w9lD=2(ba$?mzm$ZiVxF!wxhka8a5y^=&g}XSS|t^CI)JZBahS!w+8)) zaI9;4YsvnTYHygpx_B{_s8Gh5dzYVdB+yR6$XnKLA<08MKcAZ7+=m{NDZ9slK)^%t zRy}-b>k-ivGP4PpZ7S#3-ykFN-|t%jYN+=z%nSF}F=sGQr|`PmU4vkUh4`M9bB;H) z$|%k(>89RywL!r2io zbiM^pjdR#q^&m@1&lyJ+T{dC_xA|G`2eoKUod76%%hf(`JYUy>)6yb3Gm^V>w7PMa zB~v!{Hfr@yqG-OUF?dO+g=H$qesVTVp=rH5_r~na-MvLs%Io`O63@5aMb^?ZQ@s7A z9;pbTj)t?{x8agO1}_b;=}S&I@dQE$rzXL`cz{yjW{L?ix^nT;i&dC-;V$(tle1OG zKz8?$n+MN$A1ug`r>vAA)9BQhj=!iXl`jFMy?Vu|X1>m=DeKXdVIO)UdaeQAieE(` zJ1hj8L9gk*lC+-XsMg&MwtJZC-kkgZE$RI`0FRWpPw4I!TC6&p>d3g${l6rak|*{$ zX!TMK4Q~9c$Hz@<$X2NZWMiBt9~F{qRU-b1wH(Cew9Rg8WH^q#OL1TDcpd%SfsYMO zl5=PYEJW+N_aLAb$|@+ttNi|gqe9z}B5uMaH9vp83mqRXi=$W$GF16b^J7p-^Uu=6 z`-;7vI^}vkB@UmFKSh5-i)10l>)tgBNwXKlUJvLV;E2~fjM#1L5UTRXB=@`?I>L|k zJ#;JuIlKH~rhgk3>2aOC)K&3@KpE~kTV}vA2V_aOUnd(^|&GFhjzko3I znx!7B?STHvf(CK*OAju~)^C;Y%wc#hYZ9fGed!?e6*Pd0#4~Gl>d(#6uBSGEU2Tcu zwZ!|da6NQg2Nvcu9XDmn!0}I+uJzAm^aP$JW4css6D6(BF3yYu4gCxi9FBZpHPAA9 zeN36}D(H`a&fXcZWgGn&pI`LX4}i#dz?OUZrA*yUV;4y9ng2Q=X4ZEyE%Yz^%Inv^ zYruqW;JmtogoSTTneM496|n|&E`!tj{IrA%`X@!c(Ju@7NqfCqKKBbe1p0|*I91YJ z+$P62LkAS-*8`Mf)+X$7qh`HT>j27SR$UEAzw%a2r?Tt}N2#x8Z2nHFvm#}_!kLq@ zNBfTPxs^XKt^0W+b&m#6ApTV+)CG8hnti1r8pE_*M}&_=HNWLzjZ*Tx1hAR_KeyF^JC%dbaJ;#5TJ|V^l2ua5AL1s%Pw1}%bL0P&W{vtJBmhSd0FC^&o8seAQ*@|)a#tVuhi-rh4hR#CgS^}aj zsg#Yqqtzr`L!Ie1Lo!;cyDB&tohY8u920h1ZtZTB*Ml}M5aX-&Q3>#yV$3m`&EnJX z;o?RNc)Y)E%-%nwXW&GDh_Iejz=kluYUi$j{k?K`T{%6%5kWin14|)_plu&^?B?TJ zHe;FEy8ZI_F>R99p?gw9yRmyUdfmEzNWhMWye>LEFmiu5XGbLvLHQi4%Pym?K0t?y zCe-^-qoyKNT&8fc_#oWomJoYE@I9zfIQ4jP{?{@$n@Okan0DI1dQJ*rENB%F!ZzY$L*^5<~@}q?e~5a zC$&AbFu>uYQ{shQ=LsUk9Tzqsn;PwnPxr#fFQ%nMUy@`!TUUo9+kslF!?E@0x7m7( zOWoeir=h7KO}l;-pSF)VQzGIENXVCJU9(jY`m|9cN@$o$LK!_Ni3oY-#(S)Hnr7vCpO553dUg z(STlsJxSZ{D5=ag++J>XmVIAlk=He^SBWn)W>(OrO21IYcU(EXe0ckWtcS1Kfp5B5 zpn|eoem!pfUIOFf;S32hHgD`Ud9&8E+TLsPOQcFJ->y)kP_XK}KmySj`!zFu^uxyxZXZD^ilL#FQ|W zf2*0rF$^q0!&c4VN7l9~H`+sT7LQXnst|YQe*7-Hx6EoSy~RgTpLp4i^y4PcBNGSU$Ywo;P8t!Fs11 zE)jTs0&x-&=~o0#MSPvEvHJV>9XR?)Uuk2RG~ntULhk+v%?!F~<#1Uw-)t82ZvtUT zXJpntSULnyjbt5~&D9-4vFVKAs@fAT3BqoaZiuYxLRgL0S%u}7v14sy!dOL#p0n6i zn8JZGFwSR65dH!zhR%z(PePDORV}C9+qv}2eTW+f0t#!z9!z^q7buv7$RLok1wk|E!++2|Kxido(EV{g{MYm|H zWnzD8kRRC=&r*WCvw4iK8nFL3Q@?vOyy@ z>qK|ery^>EL?2om0|oSr#>}P@UF0{=_^-|l`NyV{-vYrj4W#Y{Qfb@sw>}1aJxK|P zJ;H`m8_tjV88skFcWkIc|1VJTUIqKxTO(h^3;alAZU^zz&i{N3kk3v5!W`GO8vC?o zTQ7a)msH8XeQ+4+r>)p$BE;Cg)ghXYIUQFB*VfTN4|~rJN%=3$Tv&3%iTQqtL!+-# zN#*T6i=CPfbt-plS4jke>|JwwWn6%Z)%NQsp~kG*CMUyPd4~@uE`I(Az)>={<8rjM z0x3iO!H9LPj`qTG&2OMYscIy}Yq*C-MUfl>xNi1t^gS37X+vOov8tQ(-2 z0WtI1`OKy1c;h{i{KF8_+V=2Nv)T7Ft4HvW;@%m zRRse~N~fI4QC2gmpU8-z*}8&hidWa_(H$E3K?1#_qxWuJQG8;I{|6s*_U{amX#=&J zhgnY5m@Zd!?6W@?P*yf{tMk$H)XVuK{UgNw@ZcsyvxQ#u_Xj1Y281g9yvl^1SIbp) z_73(fVZ#Qe%rT`UCZ&T4tFBvOK5%}+U%OW!<+uopo^WcW++Y%Fu*Y;WdU;({w=cK5 z;KV1(O+JdRq%gT|cTkF#-q(Ju%60fe1S6?D9r?Me=9vG5OCFCR%)7jf8n3A!mM-jR zR3i~ytn>TLmFHWFJ?Wtq@QU}^844SRA|&YjSIrRbK=E{4?2?zxj%g2@!|IN?x>rch z%tHa)oq`2zg%Zg@l=8@8ALi4i`exPCH~ZFNk{WtdCadf$4(cuqi84f4DYOZxh6I7GRbV(iXc=q9rB@KLMZ;*xEXFdQyO=1>dXm8E>#>HeR z;T}H8mt>i;+*cP?h$*4Yie|0y<|Kh$nf%qkmRXHZ8yWw$-Ky-!g>1U~n3!LydmgG; z1^PlshNLVg?-4|#pUh|(mR=0%ElirnN0AUzQ~YvV9}BxqqZA_1vao8KMtIK5y2k9n z(C}$Pznar)0WTaxSYlm^NcEB{o-6A4V)bwEv9YA~O+uzHr_5gtO(-1;y68gk?vr|V znR<9YElvs+xr1~oJf^Oa$v1<0t5SBf8{oDS=`+4xi{KiLaGP=ft(~ikXo=@jqf$Nzs>8&0`3Hu!O);Cdt99nQ8L6&MV*a z{V;9aeb{D=6%w@UfbLdZxYU#ExSZ%wUV>U!nq97J8NP$WIM#F)*Vydp#^L?b_gJ&5 zgVY^`3^U%Bxfx1Gei>np_yGM@>$7eOXj{2$(z|Wib;7dQyz?BjK$PxR(em089hHx| zhg0BAUP>8r$l{}Z>j6?%mxTyP zu;-$fR@mCk#{tQ~!?p+NYvEXcbR95@*%p@f$ z@&2mt=o*V3=dx7fKSR6h&<_8Iz$sR>)9g-=j`|O{_t6kjrO|q%7udNa%dl=tS37^s zD=hv;3itAZ#)a-e87YOCHG0Z8L znzO5Pz!+)iG(q5F(WJT=%ZRd5uJLM#Gv&v-?Mhpp5pevAjjX^a>u+~Nu&h=hkU88V zn@4HS4N3Q;Y4j z&NQj^x*=v%9VFW(C@wIQyipy>zUU%4QME$h6yT z72=*sO{PzJw6#EK9tgbG2#3yZx9r54Hwm|*x{M6Y_tekOpdXn#hdNkbdQ=jlC)?#f zI!1D=b^Rio6d}|JE$Kt=3rwp-qz^Pm2630#UPcJP%bR_>U12k{1TCSCH^pELt*K1g zRL`oZ?k3WszPK@*8)9e-bztQRde3iiY157UhrkoncwD{7mNIl?nsG*qp~a& z*VkXxNeue!QdUj4Ftp-XI}z!NX{v$kH?#@5fA+wS#pT3^I6*MGq1Uib-1P)&vaZnq zqM`A(#&mPZl;+PlJpHU?YbJKqXrk(-fp3YP4(W2HP@{1CSxA4JV7i41>LWTV95l&R6$Pe9Y3{GPfr5s-=G9P3XHA7u_qZK=_HE~hSr7I!viriUKPGZ> z+Y{qie?v^tt3CicucEa$x+{Jhyni4r?@mQbo_C;Ga{~05a`E!JJMK*sgZ|bS-X&WX zV>dgQK>HGA7)iVEdhh(J#nbx@%V_V;6+kK3&^uX#4m-e7UWTYot2v-SL2%gT8PDZC z$629qNuP~KtIF_*@GO77mX=K2lioiiTb46J>fWimZyS1$QVbPZ^<)KVufRd*oD*=4 zVdF7qs=HI$7o5uDG4&dw$4%M`DkG&&9_;DGFYfrm?>iUQLr0MBfF2N-8U?btj*vPv z07dTu^*GD^{k2*W>4azN0*C!tX97Fj#kk%vXK}a;ReH{Pl+ygy z_~c|=9UYw`3i1>z@`v8qbL(o~A$p;A<1&)H+^jgfs;ePpu{`Lm_tJm17MeAp_}*ki zEa##0eXXPyR$?|88RJhE^cQ86xZVS_)4*$rl{((JGdK*;z^7vV)AGb^i; z8K@?}HEO{k8G7s~a1xJH!cPa@btbL4{hfPd+R`IlQLU+Sx5}-MEc!Rbm5b-_RVMgG zHd(;qE<%=+cE!Zrm`*Qt3OLHO$D8LOisuV}hcp8Oh*6ojPS8(YWXH-kUe!}#EA_l# z%Q!acOG&%R*>q5IK;;3rSo$>MU z(aMQrh@HLtSbvet7m&A&0VbbBkEnvjSLe54A47?)86#CZhQeypZx{)X9f&MT`TIVa z=SED%1>#sFoX8#3XCY&^%NdvCQm1V>)CoSJ+uQ29P-BXns2(s$;p%EfsLH*>OAr1$ zYz}uVOEckq>+;sdC6}zEknN;a_PvKc%jFpVj&A+iTz;QxT@oT*G{JLZ7I+`J0Pq`_ z1|Y^r6MMs<5r+sH!*BPdPPe@$raEdkf&*MtceFZBJs|YZ3P6Vpg_Kf)7*iiggK|Z< zHfZ*M8pxpn7;XrB&$Xoa{3!FR5H%_u@EO9*z*=eLxhlj@U%ofqI|%^apbd zp;zxfk?y_pAJ^+^Za1&v*p>6!9h%v#I6y+-;xXI8_%V)Uwl$9V9k@X_bk9#|T@;$J zMd|w6+gkYWNe##Ej08{`{?EY$)TbZcL0lXqHlB(L*RyT*2tjzLFU8@n$CW_}KuqXD zd1|+l?76f&R(6jmXQnj`fFDrOBI(sVd!Gc-jojG3;KhvdTmCztV49Y%i`q@@L>w4d zCKBG4RZ>j#{m^Itbp<0mbRZnff0e2q3N-~3=f2Wh#Ug>&6M5?BWYysMDm+TDeY^;8 zlzy$IogHftZHZ?jGbL;-#0+)5I@F3c-Vrc|z0r{PdXjKsx-8=Qr56}M<)i>l!+?d} z=>Uk*XXu&L@6EeRv26fZ)%tG1#moHi*pu5-O(&R#Eo)xHF4`7H*rJdg^Ao$+z zN8>yTJey`Qp8gB{8EFg8C6iBufh$nlq|EFq*0D1jIX_ke70MzY{RjoucKP3Grk1@1 z&|(q{*7q|LQgr$InFn^g4n}CN1juu#vGMG5uM*6TN{AUel1c80lc``R@0GaXyO064~u|wCPZnj3$(MorHd4Nn?C1Y$4CX!%4%iYWE1| zNGo$|lH-)SAk)1s3$@K!Ym*)H^{zzK%%|JKLx3Y&zr>j+0!&(>$ZWubqU}Um(l0_7 z>xiMJHv5s-$~FzW4{$ZL%^Vb)EUk8V=one@eXrLHs9|LGcI;P1xg0j1<*Q8v!we6A z-Vdj@ZJ!1RU!#ERB}WnUpSj?>Hiv)3VlFp>89;lMznCzk&aSh6TUqSZ2FxM3Sqxc^YlfD$w?rr`YIzgS|s zx2RWK#?LmoHbC41Oorpsskpcf0!s7-T=oAy)CQyO|IxD=5EZ9?I#@}rkTTW%tqFT} zIgV#;1t@TF2zS(g0E7~ zy8VfHm!F?_+=kCr*|g?O!ESiEA4_Vt{ECWt$_w~IE^Mu_b}U1`phyWpC|biTl&$))81 z_x}3wrkQ`*;8?P^z25?nuRxDbwlv&x6LXz^ksSevJn9TFi?xtc2_HX4zYJPwuY&L2 zPgrjI>x!yE&bT*M{k4tu!1$NEOGmHXJ%iqH5%Sz1PN7@=;Yo`2SBpp^zx{?n*hSgL zd&Q+gEALJ(oxB`{6>RSTg4PZpji7-?x(M(^=* zhnZeN=d#x^i5BEF}5M#mb)_ujC zJLtL!ri)Pw!K#^|%Yx!0=~6fWz>8ifdOp=Bzrg5r3+l!$H$ACVd9!+mo2xzqNENs- zmg7MMnulwHnhI?9|L2S@hbFAmBMm0{p81D-@C#l62snEK=TYI|Sfxf1$3Xxjg{QM~ zi?jQ1;Buq?(1K`p2)&+L;4p`m@I^yFx#!3CP%$2CPq>~8PFgdWt-nRP2DhKPAqv4^ zA8+CSfJe#vCt&{<^+9n6nauHvNB*z7EpBH@frQve@;;(HSkafpqF@_i{|XLQ+VcilUI?f;<3)<9R9Dr;kVE# zz%d1|HYOgO(8FBbKHP6pi8=SAHwZs3g6L&UolP{>T?hVGYYmQhy@LBhTQC* z<_s3AI-@s=#jP%0^5p8?_7==+OZD42^Heap=hD7{7F4rGF>S?7dGD^X3&l=n^6b_7 z;%j6Ty6%`7gMms-n8vA7VP&Oc2hth%cwM!_Tk`7U{swT-8T{x1@%gpZ`>c&QWD(gY z#aM4g+-~{IY3tsC3{BJ@hL2wS%FSd{XfVzo#J{C>{iR@I9y^w0;_y!9h7`I1(ohD( zB(7a-`BIWU6=oV}7EW4vbKc8$Am~o51YeLE1O@Ia`6c$bh8r=)#b3eU<7&k)zc+E% zE)a33pA~nfGLWyaU{)lZnZsoJhK$e3~Exn(eOYOMiJ@tnb+v81cJVUwxU^EeXvLR63AK#J|naQ5IGc=ICGj zxmr=>RdnQHD}I^3k~2*qz4!uoV(_FHA+Yxi3hVUJie;xIcg1l7woz5~nVvfBo3JYfq%r@A_8z}a1#L)K|9uE;i#e%a{I@R=p9V0!DDBl`_Efw8H z0hcoO_OGOP&+21og@VM=-hB0DIhz5V5XC)9xjMgl5v5eV!?d}&V zNeAx!?~_HVmKs^GLdwMzd2Yug!^d{}T9v#s9OtW-GJIc+KYlk*$E3#1lMUirtePS^ z^Mc+h)%Uz)M1&ZSzfdy@j8dh>2(3=tvE8T@vF(gYd+P@9tyR7gWwxrX@9xeMKB$1? zlGproamOS#jps)-q_@Dr9uJlo&1tqcR6N&68&EMTkKz%P(MP-SH!vRfm8Jj$LYoW4IYlgWa9Hli zt4y&6P`TyUpx>-4E{Y|R_wCP|jp4a*p**4dTTDJ59Z_94oDv|3k@M$4iz$g8JF zC@+*^A|Yn|YSrF6(n`FsNQ=ilO3Ypluwc!9?d*hz_#w3eJHBRQ$nTM}pYGGuywVnM zAn{!{<#=TM0a&|_0{d)6ms%zy*S=MAeM;^fdcKCM_Z)-?lFEPp4C}hf{PXa%e(n3| zHmplK3*&E!7JJ6b;Kw_weB`kowH(xQ>Y37jRVOA1 zl()Jt3{qQb7UF5`zXL9fk{|q@ow{P6fMe)dA3h(sVU^ekl; zcDB*Uij&>zx&zfSHZ~)S6oLEt;Z5rJl%3dvU9^A-dexNXBl(4Pr(5SU_Z|bh+YYy< z+w+0G-fm+sCU%1d7Yc;x|yq1|KuHBP;S^JdiXbvddTB;f3DSkE{3Xp-iK*BR&ZUt2=@38iMZRs(T!#k%j8E@!ewZ@;rjxtI6dpFTh0T` z!n)DeS};iTQW~wz#P_t(5b}R<^`LT*Z^KYt9z4E0 zf3Tt&2TtV|sNlOJ(Ava$s)Kkpi5rif`g?eTxr}fEmsE z) z9)bumJA`z&D!mTGjQyN+qyy)w&as>LE7!&n+-%3*&X>HMcA8&xg6XjaiBhRKJM%10 z$;Aoo$pf>STaT!GMC)izTUl8(_f*z>D-|@m?Otwro%skL{;DPTbQQ1g4Fp1`o81PZ zS{uizN`_8TXmO-YEr-+0Q@j91N`leLnGabL3165?> zv%f;*9_c8d7QPT;%X0g_jR7q)jQGRvK9H@(65h}u`P$!w9d5u398@=!3(mTu1GZrUpEk(M& zR%r7QchmDI5#}D2DV_g=U*3EUr1OUk?8Km{w`^7TUCg1o;Uh-6l_8aOoL;c6#3c!8 zHSkB|o-#PqGD40VOYWP%MlX4mYlmNBwn+(`rxTAFndi7$d^}(XJ>#){Co7r274~c6 z_^^G|9Su0EAnYV0I7G4A;lu538MH5Qm!o%i=(@TS(BJYrNzv6fCer|9pY@a_@^_>M z43_K^Gr`j=jmm>tmQvpja$Tz_^a|JB$2zmBf`KsP^z=g|8LmFi&iYqkQLXQIzMaoz zolycL6YN$007~0ZyF;W12y96GcvXp8fi3grE2AnFy4TJU4C`JH>OSCCFRjGIDH>HN<0Ju6L8Ri{%$#i{-6H}R#om8_VPN0$ zE|3S*VylCihQ>sNg~d=!k>3tGX4sOWW*i4pP763AW@&nr(DP+qu?1cyhtFA8de7%T zT|ND^ca=-ww0D5HqMS`CWqK-=;R#IHf9(7}wuw8YouhpUsRz&T##W3D(Cc5T#a6BV zV?Ix3PIF}kn>^#`>G1m;yUP75+1WcBN!-D+uztZ=yqBGl2^ir2b^UwBSvH{ND)CU{ zlB;#b2U6oTS*wemh6l%=X;*R-;A?Tp8fky;Sc$CKOwYuy_Hp!mF{*(ef5F75@l2DkLGPN+ z41{J%fU+C5pcYK|V-}MbgG(AD=l4iZ3k^A)>0&&&R(ZzPid%z2G;!AAXSKUh_ZeOQ zIgDlQz4PAOiEYQsY1%ov@;Iu}q5V#7XMlR88l@1@U_`;5^#*`~jOpUcM_trvTcTr0 zD-Sbvz*PMs;r)}Ij&CMUf};7iR^mW}BknOL6b%vCTtI1DgyJBeY>GzEOP*G z9!5=fBRfL-&i>~NF1MTc@~%I#;F$eSE=HomS|+X%p597))pb7SrBLPVSVTFL5L%e% zW~bxH1gi68WV>Ki3Deb6%&>fx%%ZMa^Z+!-hG_)D5CWyD2lbNIWUqq7dVFKCDjoQy zP64?_O<{LKPR7LVikROvy zlaq%d^I_u%_FJQ9<+^j_$w8)P{<$GXcIfGYeypl7K#sYqsYO+{4PFK07QVnJ8Ybli zEle-$6-smLy6c{@&SIAvUS-ZHQM}l4@9CVbg!K6_my$>8S#*(x2!)5vTApml(+7pV zt$`U18+9Ffjr`gJl8cP8!JFXr4tpssHmWKp2nD21h-b>iAh7SNW<8Wv80In*;kHscGpA$7Y#~i0Fx1k z7efyAoL-^eKM55!9#D_Jqs;5TQV{%k z+OYh~C?I?l&t*4!pBE@yr?Cz4_mgk68-X?NW5qoNY+wrbwAqaaa;g3YIvZyDC4c_T zvw!Fx-0~|W;=NWcx?jm|DkgHm;D0#}Jq_zq4-ci0yo3!atHpvJM-X>-pXcQ<_?4Cb z`*zPxsydDIhv9{HU`MTNsL1p`y7If<5cgM(yGZ*v0K$oogJ4f=D#`HjG{DyCX8BZR zw~cZ<7Js~-jw7%_drCxRRrNS?@i_6=S#Uy$gCAY+)w3y)0)GG~^CU-@Xdr^4eCYKQ zESd5V*0SoUS!Hd`S2)7__8QZns%zg1H#axeW`9vWGiXvnOm-0mX|thhZq6-@NTBb& zUs5+yeOs@TDL8xe4Gd8Dx9$gL02q^d72A4cj5a==rSWclytdF@-tkb#9K(6|(Ls8k zQ~0qBm|{H2n{z!N*aJO8`OLP~R?mO`t^%+-mYm1GA;KKNp9iGw|Lb=lg=(8`1Z?dh?k0xua%$7{QLICm*V*I$sKSH3-BCGraiW@ zf8Wx&+yk`Icy@49Ft@2|eNN?Y1JA30%RRtJVL5_f;0=OaDHrFf9C-NQW8fBdb9$cK z0G5A+!vwT`2%g`DaOTFruG#`>;qZ6WRY~@VhoapCt4X+{@+nj+E*Ml6fhv-uypv!NTDDpMqBMr3dO(KHqn6Da%8l_Ey1Z1iNQ@ z-_%$=DoDea2J(kkCD?A>M*TxjQ6}+Q0%jJlO2){~abJw=6p|WRImB$cJp{}n!7Sg{_minil84U~J9QFMD zq1(s$jXAF`)Yu9`dgv=siJ7gQA9uhd5(d07Fof2wAOB2i%BSW2{6E?!jxV4tUIN)D7Cs)^gCL?_0%272b1;2b}?n06%r{5 z2cpsP>DHBQ5`NvpiI%Rvf`?1yu!1h1(-v)WUadwSM5ibm3w2&C;G3UO$zgG*a#e`# zbvVRFw1ah$bw#czq(|Rkl6#!)u36Z#9W$3ZWAUo$T;Eb-35_x{wjviuap!Gmamrcd zaR%DvUxb{r3;_5c{;Sz_+LZ`h!8+wcLC!&a6$*+=C z(Qef&cfnB#v93>#B$>JNp~wcmGcP!k>a_NF@WqG8GGAeBx~JE*x*xyw1U9N(>`Ymt z-_Y9e;S3=C$^2zJGb(&NF7dU!`dA)k*Z|X@hJMi3_5@Md!6r}sH1A#sI@uG*9hwtP zd!ws@NI>+kFX$;z({}5&s8%D{+A#a$vJ1aYc&-!>#(WOw+b7RWXUu}gHb8NahRdhX z1Zs~J4Ol@Sr|F+F;M=Y50D`?t^P|RyR=PiapZ8W=-a+vrIXQyJ z_oU}aBwI*zHN;9Xd5ycPrp@D485lxnpkIKrvhtcvS2g+FkX+5AimcNt*OO?A0%WRy z^V25v{B=E{G{F;ReiC&c{)X(eW;9*lAvtj5o*mW}5a3GR{nU0Wr2zt2@DSro&E%dp z7;@D~%)oG2z?g~Tr|A~Bp{S!+G{9^6)-F|!$^}u4<|t6BoY-v_N1amQ!`3fM)`ffW z(A_zVt}?yGE9D}KocD$)Mln4*x&_7jMvuqZTJoMG0R6)eCg>_4b;5xxAG)~Opgs(9 zy%nf+m1Wk}SLwmLiMI@xV(8~sJw}IN5RfE5%H{+bkAoQpS%9`7wfH)Quwv;uavUm%6cyIbwrXv_wvnv1F%^gDRCUN9-EfN3BF61N#0t zf8G?<>e-%m{&rs+EGns?P4>x+aK2tZBYz)5@W~;}Kx8xMHH6a)jtv8DfoG5S!G0BB zyXtNLzVHgyq7@pLGu{m#15j)~d|osBD97YIsk7RZr`Er>wglnq{BG=e-S41@^in`= z7Ra1{!C%IR5{>-usU?>nHZ9PfP)h5jcW4Ndak$Qpq)g1Tl!MlMetzDW>PHR%=Bd7~ zlVCkrW(F`_!Rc>+PS0;5C!ETJI5s@Fjzg>iRWGdA7B8a zZRB&ePZqdr&j%IKheO1fhrrYvKJb5RYAdh~xlm20!XTpd`RKd~)Xlg7Y{dZ2heN5d d5-KA8{a?7gtEJ;@WCa5dc)I$ztaD0e0sxk8DI5R* literal 0 HcmV?d00001 diff --git a/website/docs/assets/hiero_createUIRename.png b/website/docs/assets/hiero_createUIRename.png new file mode 100644 index 0000000000000000000000000000000000000000..3c02254559ff27f3b357054282b643df8d62dea4 GIT binary patch literal 30586 zcmdSA2~<+=`!7oMRV!1QwM=ccvb3_Chh{5NQ!_KgAyZ35G!t+DDyPb+vNUsQG6$S; z00)%Pk{l@~6ci#;6hv?U5(MwY?{D~@_5Yu9&pGSfyUyKfvD|Jp@B2RQexB#^44?hz zvaOZU>aD9mAdu39^Jg4Dpk-VTXobp3dEm-|1K1b%vnJ+H7Yug0y&tHBgZBBwf zA5#<;yySrYR|TJU2?c?an`NJ6L}m);2UmN8Jr{ zSYL>M`K2gkR4MH^>P;GAg(MD~`L#Rk$l&0dbRTm3H{6(Y(QsON2#mgx!9qku%V;K@x!VI~O zAk``@@Q~CBF--L215uBV-0MfX?zkvHH{{)n=!v#LF+1Eei5~Y zmJ+*k`1B{J(FhU@p+K6aJSEOl;;Swq9L?uXbFbvlK1s-cr!x zi6SVstN=Y%i9R9ef-d^v4gZ$Kx@McEW>FcE6EOX5@D?C3oSy=N$&HZ_FQQ)yeOSEt-<087cKZC=|Ve83(0sEL2ond^N76b zE=llMNDWcyo^j&)3~%o#_?F)5j5?%yz2Wi(vanq1EWg9^LNzlcrLze* z9O_;MOBa4TRr`6?r~BC|F8z-c`ZSV5h$Q$o3inhm=H@zja2q@%zqHWlqG-BAtI*j) z+R&c@7N_*-p#{at0ank z9!bubqZp55ztL#}kK^gl@7g&YGggqui~(@mVg=2_?XiFqkRptnKuNjwmT$2&(v5wo z?v0{RMq@n8ud<9V)&AKV*O}g%PFUE}&^70Ai+UbuXxZ#Ne%6fSfNtKin6-u)XpqVx zyQbx&BHDYUDA#VH%*I^ae~lFweTJlU^9=N-2x3PYKEcd1U)qv;%?Uj+@mR!8=_(-1 z^y4t-g+V92^CIGOcDN9pxRF-aKDM{G;Rk&UrTMT1p=ri%jox<>g{<*3H8K^O| zr)*6b%%jn~WZg_d`m8PgMQ*r@q(9x=o*-rF762MZYBa^LoGswzB+a>j1q8lF<7r8o zpE}FYa?Uo)7Al74WVDZ=ErBQb_WrSq<9Vk#p4SpZa8)B%gXLxRaoqvVb`ClJ5hJ4# z{hIe83ZqT*tz-?@Os9ZpCoJnJ$xMxPdzp(HFmpIvqx{RuWse z+JO1B-#DmS(`mOTvnpMk%`q!I~cq?5^WmB z-B=7^p;5h82m*riQH}iV{aem=ZePgLW4qu8NrS}9;XHZP>q~$x3Y#zK!P1xtXMTi8 zI2FjPDA20*Q4Z&$RU;mf_Lev%3=wPgyF}&mXw?j@*Vt9xctf%)dqbICEoqZ%_ZHIM zKG8h>NuL2pqc6lSrivnS-5uejcvs1w7V3gzJPGQ={ez_6SiHY^#GEcT#PY+^-bfQV z;RPgmK*6GTp4dwe8!jH8xn6HmaM1U@=pjlLJPI;;tegAcG3NwF7ZWFbm3I@pSW(=t z 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 Ryj;ks<7fXYIqG`z-v9xy18D#N literal 0 HcmV?d00001 diff --git a/website/docs/assets/hiero_defaultTags.png b/website/docs/assets/hiero_defaultTags.png new file mode 100644 index 0000000000000000000000000000000000000000..225ec7d48493f1406dd0818c8c779e54d07b30ed GIT binary patch literal 7266 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
;+ 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 W3UtM%t}w4x0+i*|UzW<6`2R07Ov*6; literal 0 HcmV?d00001 diff --git a/website/docs/assets/hiero_menuColorspaceClip.png b/website/docs/assets/hiero_menuColorspaceClip.png new file mode 100644 index 0000000000000000000000000000000000000000..4014da26750c560f7e0ee833b4a05afa1cd9ce2f GIT binary patch literal 6593 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 literal 0 HcmV?d00001 diff --git a/website/docs/assets/hiero_menuColorspaceProject.png b/website/docs/assets/hiero_menuColorspaceProject.png new file mode 100644 index 0000000000000000000000000000000000000000..6b8e6e1b892cb15b6daf470fcd20285feee3fc82 GIT binary patch literal 6864 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 literal 0 HcmV?d00001 diff --git a/website/docs/assets/hiero_menuDefaultTags.png b/website/docs/assets/hiero_menuDefaultTags.png new file mode 100644 index 0000000000000000000000000000000000000000..ba542e10194d9567b0b814914793caead73505e5 GIT binary patch literal 9200 zcmb7qcR1Vq+jh{FE{dw!D>hN8h)s=%P3_ehrL`)E+C^JCNQ_#gc2RCy%)57uM(j~4 zw%9`L%@ghO{@&knyw7_a&+|u;PxAeK)^(ldd0p4{lSn-s_1iZXZvX&*+ZqrR0|0;o zOl&VvTqC|aYlK9HDAF_`sB~Ep7rjHkGPL=GB%`aFP17FR;3)RyZ1TtMf}Zn{wuuo+JzGA@EhAX z;~6<-mMZwrd6x|su5dbAJ`l`RRaHd_0Hk$&uXV*;0RY75Ae4Z9Cw4T49SmmRGWHkA z1E;L|eLBDuprn0&-W1xCH^TSVSMm{g4}u_o0^;_)ADN4}&B+dqCnw(Fgpu3rf2>G~ z3E}kPLpxNAo6})?D|KdF_jAvRmnX+Kz_bv`y94j>PC2zI*!FR=itJUFd#HJot>^Pe zolo*2QM@P#|H&YkdKDvM>wUPFP6D1Xv++4=I@ZSP#~Mp`8``)$=Rf7Rj zJw)YO_(0X52-O+v|M+eGA}DDFgd8Hx@r3p%1aQ= zpmX+UG3z+tcWbS0cAK%=!q-%=OjXd&Buy;!Mmm4N&W**~?HMk8&CV~wG38`M&3mw^ zhfQ(F!~(z7Z#pl@JJ!q!U@det-Jsp_cj`QeD_YC>V3VM~ut4k4OhMyO?N(?fdbumP zWag~z!du64c^@(oQEmqs9;yYwySw))z#2ApbhX?gM^W43V!}L6fPYzJG{IDxb|XOZ zu8TD`1#&I72{e13d99-O%VD^$9c4Ll+w(h6MBDbfWBc%7%-FM}1XGYO@-0NDT9)fM zG3&)4Dwdsls4rUE6})L)(DG4~HIWFNI9aI#gsI zoxG#$v~^DAHU~0OfDFOOPSBj(bC%bMVDxsoZOsSA2-?>+{oGA zHI?BY7)~eW*AX&Z!dXuw=w_|uj`Tiny}%HDR&~Aw9&{k@Vi8&l8XK|s9->uY_^tP* z$iTjJ2)68F27OiM!ZBj2T%LQOX2^Q;FR5Tx?Tm5ZcTntTk9S#?0^jq(oQCkeR;HTP zhpTJHlJazsuXPLUH0q+?5N#QloB`_7Krw^lF>MKA=%1qnldKwU+8IHR8w;sTmrMJ) zG1Rr@B$ES&!05A>>1hRMMqC@Wpc}t@J(|5Wgq{P{#n7OZmMl#KkT%JgL`UxAIZ&hX zuxc$~Y;M`L&{|?NHEqOBmSf7L8I;>HUwNCQUEi$GE^_rH5ld||+=a;&bN`^y=i#-H z@2Z?_y}{+$jdDC-x<^OjYu6feWkkwt8d=@L2c72@?nISP>C+Fo`kMxxTzj+w|=gOhBhn3*ux9vM~lZTUspV3wxL4|hD2+-d|LULJVna%sceU+qrzgo|~O%Hcl8V(Qk-&jOh1O5EbQ+KOTq^u=MNS_RT)KnA9z>W38Wv(9pC+dIq=@> z{V)*)mh{OM;Q;5DNf*0k4QNFe32|F!`x&8m+63VP16XyL&vqRL=`?%+(c{L3TqBRS zR2)`@y@ah`7%J9gw)kA9l^IBFQG;cLgDyy>6&mFH_OHEE{;Iz^71szGJF)AX|8nsP5pGwIHz^>LZz+fa#4}S+ z6YpUM-{G{<=-rDR%5J%Y;Bpi1@&QXhVPSi+uti&@&(ul=mjn?7qNhlUJA%&6whmv7 zjFbO_>{N&TCzqSmf?-Q?Y8o;M=#AUVv57<5FEvX+g)6XXE-;O$X0?>Fpe=_XInr60 zXw)Y>VD3jeRLGBs-Q!)#mW4wRrB)ca>$i|MFE=ZI%HYmjFhVy8wHM~fAhQw0wj?hH zmqBiJswt=#Aq;;i%oi3wI)7xGSuZDdZwjRPQM%PZH9vdm(xaL9+UR#lHKsH`9vTgS z#5^HSCQWazv>zzt*}7&+7`*yJ$oQP?AnBe4LWH(d_RA2aGE&EWj~c)0@iA&+`-=M5AZ($^K`=+W?Ssl|)xc0`<)t%n zg+rw+V!C8H@p?XX^}xhx=Ue-C1dGg-`>X;<8TTR*!&?Z^ob_~uLav%RUC)iw49pc> ztWcCd$5`&0BaYjiKS;d8di{?BnC0^A*M5AND{I`KIhgQ_^F)j(!XB&DAfw;4xvTSY zBIlsiaaHanv0ggB!fnpkLx-UPJToC7k^jrZ($U5!$^7WX2chV}?2AiVe8F=ve5iY! z0J)_EtP7?7tQ7@RS0OkkFy>EfNuHO1gE(?4yl08JDwU1$yi3|~6n5)MQ!{hJv5 zKOnop;^N^e2v8!yT)o1we$jYH4G1MA;!1k#;^bKEU+taTSiqI5M4);7b#d~~|NVXM zo5#QIISTXWckV?*s;jBD7vaerSr+o^wPh8gd4d*&$R+`QCOABI9r57FJmt5WYbbG( z)KjRkn=7|;;66MrmFv*oI(xX$0CGBmuQoCmA`MS?eEf^7;Z0GXeI*o;7@?H=t-+hM zFJHT9X*X>)yDVPJ^%uRy$ES`y5>Yoed{Wg3MP>jd+BMA?Z*>J-cHOnE2QkU1Cb z?(Y_FJ}3LVhG2i!QW^yRS2R`}3d+*vO7P*oaNtjo(w5yet7_@xMX-3{H)Ge9s_kes zK_Ikp4AB90$KN{M(g32HI=1 zJ6qZ<8WZHjWi@u)FCTcDA-Ur;XzlQ#cxU`Y6(aPw-<#zu$&I&geYGjEZNLd!F4x%o zCeF}ybYaCFjX&oaC#1VOR&MC8Nqw)L+TiI=!dohw0#Hdpd!T|K}=&Ms@z*3&kerd+vl`+Y!T)dT5I;i!C(%hk81XNS) zAn!;(ce{D)VV&QeGE%Hs_j%?X(33MjZaA? zF5=|ZiMUoGKW1z7cJ>ZfFn*Nal;aUZlM-yvwScSC8IW!`*TrAa*+cPWmOdg_EUtw8pz2E^n`%R#LwZ`qRXJ*!N+Nsj)S1zfq z1G{PK@P*r4xMQ_4_oKEMCW)A*P+v;5Ao@{7W&EOWd2QQx7iq~nG0HTPBX341LETQa zUo!Xn+@4)=@-@1lum0xq{i;1LCSbq#%i*)dnybo0L5TRO0cr@evMO*e)HCy4@soCi z3UZ-UK5j{iW$u3)H#enrR%>Oyd$|N-m~6wgsy%!hG+&4Q=m)hP4o6$p3afiv4<(v3 z;wASIZIm!uT$3#X_LJg)g-BQQ+f~GU4zwPT;W_e1I|r}El_dIdEOv0=UD6}CKU02H z0$YLzc&~xAksu5WBW7MVBm5vB8hw~xW5Y{~U~inp)x~5>zedKYTF!f7d)FbL-;qp* zD1+qxE2I85;q?EfpE)qhPhATAU%X+v6sx?x#}ojbODhY2_cH3&b7KdWnp2Oghyk7X zk5_huBrmFPoO4T!U*d0>y>(O#{(R?iP@1kvUs~1n-oxo8!kc~~GIGQ)Nl~S?Ux8oBeeVfXzzo^kW47b?`q}qPZ9S6H>lthywdc>DU%AgbKR@3az-9#1 z`GiMJfz+HBe64x0rKm1VjM@^D!kceh)iJQ~3SusIJM(}T|7h*(>}-b$wCDD5?)2%B z?(kk}CWzbL!`4LC03dD=A$S1aH6)WQI~WiZf`5M>Lixs)_#EOIk}UiFzg^bC2uEsm zsPo)6>5Dk4r2NeQCfmw{iRv-nPDdww`@0`kD~giscnXLi4;VjjXTH@czi1=pH7o%2 zA$jG@u`KFGJ~xE&N+fb(x9M@5wV9I|kb0`b4h#lk(TJki?_{r9no39x^ zk8!^hKMP0uRnlHls(g`o(f7HgZe{cO3mtW4qOf z)9r=qXr&&wY|gUIe&ip@C` zafL5*VcLE^BSCyTfS~0j1$HiUp^rOc>h)pUicezqGNrnPc2B~KiX}Xq#{%B1Vud>D zSD7L#te^$CR`-@ztFcONj%sgGi!j!elt=gJYPX5mxyxG0aqVMHBR|Wub+;H2C)v_O zWXGWtVE{hNfI0*z$+}B;DXTC{czN1ECMwFk5wUa&DEV}o+p*$8OAc7b3Qx5L50RS< z?ktSajqqX1QVs&708)|oUtsiXt}sU98VJ0{u6drVRlM~2)pG-rx@V)0f#h9enA@U{ zdBG>ti!^?#ZZs7~IW^sW@yvWwK*=_7C=B{4faS+EAbA6T2$lEe@T5Z51dkPlx-#Qb zveV~eGhW>2a;pH%H?yD2F)J22x9N@3`lc=Qwg()3P>raxo-cr^h(HVPJqaYIj0)3K zZ}Ti{1?e>JH<%s`r1yetiPJNTXi&a6zD!bx;Igj=)@|3{XJMe`in?Cmo^PKzmoZj1 zkTflXP>`VB4Eu0m4e7c}uDqtycV^*R8a(K42Nk|^I~qv+>%h;{qCVm}Y?J(I67nDf zeezc1vBTvsk#PeX>b%`Y8Kwl4GG%2EV^jcRMVc2reMfLK?U+{Xk$p!)vmv z%QuJRp8&DBEYo|%)~S#v9-w<%{*KC@uFFt+D|T_OPuV3j#0}=f;9~&TcuU+|_VEe< z6%B-;u)`iS{K~K+qOYYs?a2F~qh3LJ{d`LvVg{i3oZ!e}%>fI)^d$UaSP=5;dCDxD z?yWW>hCuHV(;XSMK50bvm0=_PLDpSp+2kl*TwuGt51~~gGM#?Y*CFk_rjGJS&y-sd z{iZsxV1WS9XSO(PuDRqR`feYSOdo~My3+Ml!q+vRU7`ueYN~>FscDQ`!48owk6fWX zAN0r(u7bgK6hwX~FUj@1v%z`ron=g-hcUKVXbx0ginV~!(B`rJMn!<=nhd;H)TT1C=PN?Mh&VS8@kBBW$KUu2r-_#zgcBxaUKVDCM$<6D~ z!TMICFU-Pfk!q;>WHzm6O2EHm;Bkx2*tI16!nuk&Khh2i4>BoNW+`uw_5Hd^teM)oB8t#hHe@>PdraN z&FZ4aEFWr;?;LUDxO!6Vhf$@@XwWv&M{zUm-S;#ZHev@(`w79SI}@yU|A`<~;?e-B z*OdU#)A99gCN+2`))j|G30_zI&2H-H%339dTg6G|?7c|kDc<;Sz7(1-u3=OtI779+ zj`=lbpQSC% z_;99$A(^viYw=D9r_a=ZNtXKJ_om?^O0ZU5Rs7sv6Q6+e_2>>I6ednz3*A+RAXGg$!zgbc(QUl zDk`3I$1|5-xu&^Gmnd{hJ3;e%5~62Z1C?wXL%%*IFJ`ZNPt;MOK{XIWgHkioGm~IO z?hC>U$@K~sE-nXBfw2TDW$6k$FkQZp_ZW72L2@yce3~ocxF`FVg=U*o*M=++?*y8; zdE8Uzhe=@eI}(ovIiP$$TK=qfqz&62G6#kQNU*Y`$jj3R5(8mu_@W^iKUa7-NXFy8 zV{yakyPBMm)XBF;tCD;-f@?}5kq-~9vgC#kGYufQpXvxw6aQSHQa1Vh2`_mx79z2F z{cq#l*{q^!YvWf8i4;RYhN2e8ogdK)6*<^k?_tBYsAE57UtDTzbxibIx&)rJqwR{1+J6dMCV)Vw7DY8y|9sI<$0wU97U2;5P zDqicY;q1J2(=9}jX|swVWe2^gU8_I3%LvD~R;^*GUuJC|?aQbv3P*Ra#I{%5M&n9{ z0s>k*q<-WEbunjwF4I{k=4V~V^YM=qvNr^6I&iz{9!N3N_vD1P<67_T9;93Jaoppk zh%)GQ?V8p})!_ujwg&s0MLfyLFtsnWZrEhT#W(i6tqC@5WHcI;+&9h7ZRYX(RrkFFWeKO@kNFOg`T9_qUubA$U7F{Ko3>^8L}d4P-E1f!C$E&}-Q$$J zpm``iv~6sv(CAc`N+Dkr*m5>lfT!4$pgkgbnQg6t7q=F*+W<^po-C_9yBcV{jAXtT z6kHbZ8rdQ^ccm;Oslwh#*&7uLX)-dPv1^+CI5TI6Wnk0fda?m>zDz(EJW*_{8G$s^ z4-w(~EOR@aIl8O+dzm#KW@U6T0@0G~u8An6_-b^MaFI4qMykgf13@g32K$UG6vyCQ zZW;{FMDr;Ie>!txdjOZcjKz_7>GRMaX6_$2JAwHn6Z`fhS=tj^>OlJ>QzoBJs=c8iL63 ziM;TwYh;nJg)53HcLLbiI6h&Nrd`)ZuB^Z+9X=xT;V!q6EH}=bdL>ej%vVXr9pB?$ zb3ZUWbexkSiVU)gn_j^jbF!RYyJ9={*|E~0wcBQcpXYX>SdsR41$QocG%o@%~g@1 zf!YDT7DDxd+MY7rq2j=t#ixZ^ds8XaK`)`q^N+Nu5UbZ1Vr`>JM~xlvD7BDb`BCpGpw! zHij)imsJ8W!Sy(f5Y$(&q~WgL=AQ2Et}@=5Cq{E=BL zc3BV<*NHujs#Q`pQAoE*&|?_M_bZcrlAPk0ax&;U|4M(jJevQyD{WuysN1?MBYAB2 zs$sfBV&7WW>L)w3&R(N^jY#>Jmu>g#u}Rk3NkpbP zF1zkq(qcb+JE(}IMM1o}#a%Yr{1E-(acHAPr6J5f8Hx3z>&bNrDheLd$j!aZRO}*2 zdQ8S5-m6o&V zPw*J?2A7F_lw^5~ErUm%(X^BaW<4exbtyjTM`PmX6xe(jt|NN)43V5B+zSEQZ;qA7 zHEiAl^wY&sN?T1XUjbx)?g#5EHMAHBhdrbnCTIOJMxA+^<#nia{MGCi%R4yljis%c zX!q*?fRjKxGa!3F5f(dlRaCOlkQtu*qBk;-iMOqV&cuw>VfDC`N%w2GH%1Z zb4->V7I)D#Z=1M{x}|q>Sg9&6XWNgbK`db+JEs=T&5J#3$-(?R2z`YPMeMgPt3llQ z^XR!!ipI7i5gt=pg?3CVCXh0nF>>(`o(B2D{k)|YC0aHZTQxQr-W_2~yLV2dl*mwc zCcSW9MG4=RzVc!<|6Y$}I9T=U9>?1Db-lk>V^erS&ZmvYpG3}+YR!p(e)-bBfS;~p zM5l0|r4zu}{3|Neydt_d?5TbHixCMK4OsrmC7kO+(KecgLuI1WS9$qS&!U`T%$|s6 zA7pVGhhmdfSH*!Q^vp|Yi_Cjj3aRtuz=Pz&U_*o(f|U{ELj0EwKtokWrA+y0=zjnK Cz2i0j literal 0 HcmV?d00001 diff --git a/website/docs/assets/hiero_menuPublish.png b/website/docs/assets/hiero_menuPublish.png new file mode 100644 index 0000000000000000000000000000000000000000..4259dc44e4b99aaf868e9e78471087d67a721c6e GIT binary patch literal 6011 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,~G6m2oi1P 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 P00000NkvXXu0mjflZ0NV literal 0 HcmV?d00001 diff --git a/website/docs/assets/hiero_timelinePrep.png b/website/docs/assets/hiero_timelinePrep.png new file mode 100644 index 0000000000000000000000000000000000000000..253bc114ee3e53e0e044bfdb8f1509c30f6d97c3 GIT binary patch literal 17150 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 T8$RGIk}%@J(n4hdIzImwWBtqH diff --git a/website/docs/assets/nukestudio_defaultTags.png b/website/docs/assets/nukestudio_defaultTags.png deleted file mode 100644 index 3ba15ccc17c8d21e1c67f8f0f8aad026a05828bd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5628 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 KMNUMnLSTa3GgW8+ diff --git a/website/docs/assets/nukestudio_reviewing.png b/website/docs/assets/nukestudio_reviewing.png deleted file mode 100644 index 0d3b4170dfcd3e401c2103863ae9a3f7daba41c5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 44909 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

@lTF+Ve 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 diff --git a/website/docs/assets/nukestudio_tagsToClips_basic.png b/website/docs/assets/nukestudio_tagsToClips_basic.png deleted file mode 100644 index fadb85342b625a0ab07a26f7b882b5574757e759..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 180993 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�g9WV{6cY6mNEfzmU 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 k%iYb>>r`I)NuZtWAz*UI9bpeTk~ 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 Whil+^rN66wKQ=Nj*GK3+O#VOkT3v(y diff --git a/website/docs/assets/nukestudio_workfiles_openingLimit.png b/website/docs/assets/nukestudio_workfiles_openingLimit.png deleted file mode 100644 index d0e893f4e5264b2b22a2237faeb9ac7c91bd2c57..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 74591 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 V$;IV8_jl|+%1SCp6udO>`Cpl0y+!~4 diff --git a/website/sidebars.js b/website/sidebars.js index 82f063e252..842d7a0a49 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -16,7 +16,7 @@ module.exports = { collapsed: false, label: "Integrations", items: [ - "artist_hosts_nukestudio", + "artist_hosts_hiero", "artist_hosts_nuke", "artist_hosts_maya", "artist_hosts_blender", From 2c2a2d11e567b01c99a189856b8829e550645680 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 20 Apr 2021 16:59:36 +0200 Subject: [PATCH 050/180] Hiero: udpating menu items --- openpype/hosts/hiero/api/menu.py | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/openpype/hosts/hiero/api/menu.py b/openpype/hosts/hiero/api/menu.py index 9ccf5e39d1..ab49251093 100644 --- a/openpype/hosts/hiero/api/menu.py +++ b/openpype/hosts/hiero/api/menu.py @@ -68,50 +68,45 @@ def menu_install(): menu.addSeparator() - workfiles_action = menu.addAction("Work Files...") + workfiles_action = menu.addAction("Work Files ...") workfiles_action.setIcon(QtGui.QIcon("icons:Position.png")) workfiles_action.triggered.connect(launch_workfiles_app) - default_tags_action = menu.addAction("Create Default Tags...") + default_tags_action = menu.addAction("Create Default Tags") default_tags_action.setIcon(QtGui.QIcon("icons:Position.png")) default_tags_action.triggered.connect(tags.add_tags_to_workfile) menu.addSeparator() - publish_action = menu.addAction("Publish...") + publish_action = menu.addAction("Publish ...") publish_action.setIcon(QtGui.QIcon("icons:Output.png")) publish_action.triggered.connect( lambda *args: publish(hiero.ui.mainWindow()) ) - creator_action = menu.addAction("Create...") + creator_action = menu.addAction("Create ...") creator_action.setIcon(QtGui.QIcon("icons:CopyRectangle.png")) creator_action.triggered.connect(creator.show) - loader_action = menu.addAction("Load...") + loader_action = menu.addAction("Load ...") loader_action.setIcon(QtGui.QIcon("icons:CopyRectangle.png")) loader_action.triggered.connect(cbloader.show) - sceneinventory_action = menu.addAction("Manage...") + sceneinventory_action = menu.addAction("Manage ...") sceneinventory_action.setIcon(QtGui.QIcon("icons:CopyRectangle.png")) sceneinventory_action.triggered.connect(sceneinventory.show) menu.addSeparator() - reload_action = menu.addAction("Reload pipeline...") - reload_action.setIcon(QtGui.QIcon("icons:ColorAdd.png")) - reload_action.triggered.connect(reload_config) + if os.getenv("OPENPYPE_DEVELOP"): + reload_action = menu.addAction("Reload pipeline") + reload_action.setIcon(QtGui.QIcon("icons:ColorAdd.png")) + reload_action.triggered.connect(reload_config) menu.addSeparator() - apply_colorspace_p_action = menu.addAction("Apply Colorspace Project...") + apply_colorspace_p_action = menu.addAction("Apply Colorspace Project") apply_colorspace_p_action.setIcon(QtGui.QIcon("icons:ColorAdd.png")) apply_colorspace_p_action.triggered.connect(apply_colorspace_project) - apply_colorspace_c_action = menu.addAction("Apply Colorspace Clips...") + apply_colorspace_c_action = menu.addAction("Apply Colorspace Clips") apply_colorspace_c_action.setIcon(QtGui.QIcon("icons:ColorAdd.png")) apply_colorspace_c_action.triggered.connect(apply_colorspace_clips) - - self.context_label_action = context_label_action - self.workfile_actions = workfiles_action - self.default_tags_action = default_tags_action - self.publish_action = publish_action - self.reload_action = reload_action From 250d37b318429f1143cce16c25d58a95f95f0145 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Tue, 20 Apr 2021 17:56:15 +0200 Subject: [PATCH 051/180] generate dmg if possible --- tools/build.sh | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tools/build.sh b/tools/build.sh index c6938494ae..f64d7e79b4 100755 --- a/tools/build.sh +++ b/tools/build.sh @@ -163,9 +163,21 @@ main () { elif [[ "$OSTYPE" == "darwin"* ]]; then poetry run python3 "$openpype_root/setup.py" bdist_mac > "$openpype_root/build/build.log" || { echo -e "${BIRed}!!!${RST} Build failed, see the build log."; return; } fi - poetry run python3 "$openpype_root/tools/build_dependencies.py" + +if command -v create-dmg > /dev/null 2>&1; then + create-dmg \ + --volname "OpenPype Installer" \ + --window-pos 200 120 \ + --window-size 600 300 \ + --app-drop-link 100 50 \ + "$openpype_root/build/OpenPype-Installer.dmg" \ + "$openpype_root/build/OpenPype.app" + elseß + echo create-dmg command is not availableg + fi + echo -e "${BICyan}>>>${RST} All done. You will find OpenPype and build log in \c" echo -e "${BIWhite}$openpype_root/build${RST} directory." } From 92c0255e9d80a939009d1dd4c3153c58a096318c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 20 Apr 2021 18:02:48 +0200 Subject: [PATCH 052/180] Hiero: fixing colorspace menu items functions wasn't updated retaining imageio presets --- openpype/hosts/hiero/api/lib.py | 35 ++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/openpype/hosts/hiero/api/lib.py b/openpype/hosts/hiero/api/lib.py index d7bac7a3cc..c2aa057e6e 100644 --- a/openpype/hosts/hiero/api/lib.py +++ b/openpype/hosts/hiero/api/lib.py @@ -9,7 +9,7 @@ import hiero import avalon.api as avalon import avalon.io from avalon.vendor.Qt import QtWidgets -from openpype.api import (Logger, Anatomy, config) +from openpype.api import (Logger, Anatomy, get_anatomy_settings) from . import tags import shutil from compiler.ast import flatten @@ -756,10 +756,13 @@ def _set_hrox_project_knobs(doc, **knobs): # set attributes to Project Tag proj_elem = doc.documentElement().firstChildElement("Project") for k, v in knobs.items(): - proj_elem.setAttribute(k, v) + if isinstance(v, dict): + continue + proj_elem.setAttribute(str(k), v) def apply_colorspace_project(): + project_name = os.getenv("AVALON_PROJECT") # get path the the active projects project = get_current_project(remove_untitled=True) current_file = project.path() @@ -768,9 +771,9 @@ def apply_colorspace_project(): project.close() # get presets for hiero - presets = config.get_init_presets() - colorspace = presets["colorspace"] - hiero_project_clrs = colorspace.get("hiero", {}).get("project", {}) + imageio = (get_anatomy_settings(project_name) + ["imageio"].get("hiero", None)) + presets = imageio.get("workfile") # save the workfile as subversion "comment:_colorspaceChange" split_current_file = os.path.splitext(current_file) @@ -801,13 +804,13 @@ def apply_colorspace_project(): os.remove(copy_current_file_tmp) # use the code from bellow for changing xml hrox Attributes - hiero_project_clrs.update({"name": os.path.basename(copy_current_file)}) + presets.update({"name": os.path.basename(copy_current_file)}) # read HROX in as QDomSocument doc = _read_doc_from_path(copy_current_file) # apply project colorspace properties - _set_hrox_project_knobs(doc, **hiero_project_clrs) + _set_hrox_project_knobs(doc, **presets) # write QDomSocument back as HROX _write_doc_to_path(doc, copy_current_file) @@ -817,14 +820,17 @@ def apply_colorspace_project(): def apply_colorspace_clips(): + project_name = os.getenv("AVALON_PROJECT") project = get_current_project(remove_untitled=True) clips = project.clips() # get presets for hiero - presets = config.get_init_presets() - colorspace = presets["colorspace"] - hiero_clips_clrs = colorspace.get("hiero", {}).get("clips", {}) + imageio = (get_anatomy_settings(project_name) + ["imageio"].get("hiero", None)) + from pprint import pprint + presets = imageio.get("regexInputs", {}).get("inputs", {}) + pprint(presets) for clip in clips: clip_media_source_path = clip.mediaSource().firstpath() clip_name = clip.name() @@ -834,10 +840,11 @@ def apply_colorspace_clips(): continue # check if any colorspace presets for read is mathing - preset_clrsp = next((hiero_clips_clrs[k] - for k in hiero_clips_clrs - if bool(re.search(k, clip_media_source_path))), - None) + preset_clrsp = None + for k in presets: + if not bool(re.search(k["regex"], clip_media_source_path)): + continue + preset_clrsp = k["colorspace"] if preset_clrsp: log.debug("Changing clip.path: {}".format(clip_media_source_path)) From a38b8eaa2849f767544954c02643524ce2f37de2 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 20 Apr 2021 18:07:49 +0200 Subject: [PATCH 053/180] Hound: suggestions --- openpype/hosts/hiero/api/lib.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/hiero/api/lib.py b/openpype/hosts/hiero/api/lib.py index c2aa057e6e..553741e56d 100644 --- a/openpype/hosts/hiero/api/lib.py +++ b/openpype/hosts/hiero/api/lib.py @@ -771,8 +771,8 @@ def apply_colorspace_project(): project.close() # get presets for hiero - imageio = (get_anatomy_settings(project_name) - ["imageio"].get("hiero", None)) + imageio = get_anatomy_settings( + project_name)["imageio"].get("hiero", None) presets = imageio.get("workfile") # save the workfile as subversion "comment:_colorspaceChange" @@ -825,8 +825,8 @@ def apply_colorspace_clips(): clips = project.clips() # get presets for hiero - imageio = (get_anatomy_settings(project_name) - ["imageio"].get("hiero", None)) + imageio = get_anatomy_settings( + project_name)["imageio"].get("hiero", None) from pprint import pprint presets = imageio.get("regexInputs", {}).get("inputs", {}) From 0ec066af7ed1a34f40d162a6e40ad9f6aa0f9d88 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 20 Apr 2021 18:12:47 +0200 Subject: [PATCH 054/180] defined constants in ftrack lib --- openpype/modules/ftrack/lib/__init__.py | 13 +++++++++++++ openpype/modules/ftrack/lib/constants.py | 12 ++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 openpype/modules/ftrack/lib/constants.py diff --git a/openpype/modules/ftrack/lib/__init__.py b/openpype/modules/ftrack/lib/__init__.py index 82b6875590..87dadf6480 100644 --- a/openpype/modules/ftrack/lib/__init__.py +++ b/openpype/modules/ftrack/lib/__init__.py @@ -1,3 +1,10 @@ +from .constants import ( + CUST_ATTR_ID_KEY, + CUST_ATTR_AUTO_SYNC, + CUST_ATTR_GROUP, + CUST_ATTR_TOOLS, + CUST_ATTR_APPLICATIONS +) from . settings import ( get_ftrack_url_from_settings, get_ftrack_event_mongo_info @@ -10,6 +17,12 @@ from .ftrack_action_handler import BaseAction, ServerAction, statics_icon __all__ = ( + "CUST_ATTR_ID_KEY", + "CUST_ATTR_AUTO_SYNC", + "CUST_ATTR_GROUP", + "CUST_ATTR_TOOLS", + "CUST_ATTR_APPLICATIONS", + "get_ftrack_url_from_settings", "get_ftrack_event_mongo_info", diff --git a/openpype/modules/ftrack/lib/constants.py b/openpype/modules/ftrack/lib/constants.py new file mode 100644 index 0000000000..73d5112e6d --- /dev/null +++ b/openpype/modules/ftrack/lib/constants.py @@ -0,0 +1,12 @@ +# Group name of custom attributes +CUST_ATTR_GROUP = "openpype" + +# name of Custom attribute that stores mongo_id from avalon db +CUST_ATTR_ID_KEY = "avalon_mongo_id" +# Auto sync of project +CUST_ATTR_AUTO_SYNC = "avalon_auto_sync" + +# Applications custom attribute name +CUST_ATTR_APPLICATIONS = "applications" +# Environment tools custom attribute +CUST_ATTR_TOOLS = "tools_env" From 5fbc62678b168ff1134ccb6e75803ce162e9b428 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 20 Apr 2021 18:13:13 +0200 Subject: [PATCH 055/180] fixed and replaced import of constants --- .../action_create_cust_attrs.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_user/action_create_cust_attrs.py b/openpype/modules/ftrack/event_handlers_user/action_create_cust_attrs.py index 63025d35b3..8d585dee20 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_create_cust_attrs.py +++ b/openpype/modules/ftrack/event_handlers_user/action_create_cust_attrs.py @@ -2,9 +2,16 @@ import collections import json import arrow import ftrack_api -from openpype.modules.ftrack.lib import BaseAction, statics_icon +from openpype.modules.ftrack.lib import ( + BaseAction, + statics_icon, + CUST_ATTR_ID_KEY, + CUST_ATTR_GROUP, + CUST_ATTR_TOOLS, + CUST_ATTR_APPLICATIONS +) from openpype.modules.ftrack.lib.avalon_sync import ( - CUST_ATTR_ID_KEY, CUST_ATTR_GROUP, default_custom_attributes_definition + default_custom_attributes_definition ) from openpype.api import get_system_settings from openpype.lib import ApplicationManager @@ -387,7 +394,7 @@ class CustomAttributes(BaseAction): applications_custom_attr_data = { "label": "Applications", - "key": "applications", + "key": CUST_ATTR_APPLICATIONS, "type": "enumerator", "entity_type": "show", "group": CUST_ATTR_GROUP, @@ -411,7 +418,7 @@ class CustomAttributes(BaseAction): tools_custom_attr_data = { "label": "Tools", - "key": "tools_env", + "key": CUST_ATTR_TOOLS, "type": "enumerator", "is_hierarchical": True, "group": CUST_ATTR_GROUP, From 3f63fc7a9b4eb6105f33dd509dd8a4ee155c3f40 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 20 Apr 2021 18:13:27 +0200 Subject: [PATCH 056/180] avalon_sync is using constants defined in constants.py --- openpype/modules/ftrack/lib/avalon_sync.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/openpype/modules/ftrack/lib/avalon_sync.py b/openpype/modules/ftrack/lib/avalon_sync.py index 79e1366a0d..5f44181e5f 100644 --- a/openpype/modules/ftrack/lib/avalon_sync.py +++ b/openpype/modules/ftrack/lib/avalon_sync.py @@ -26,6 +26,12 @@ from pymongo import UpdateOne import ftrack_api from openpype.lib import ApplicationManager +from .constants import ( + CUST_ATTR_ID_KEY, + CUST_ATTR_AUTO_SYNC, + CUST_ATTR_GROUP +) + log = Logger.get_logger(__name__) @@ -36,14 +42,6 @@ EntitySchemas = { "config": "openpype:config-2.0" } -# Group name of custom attributes -CUST_ATTR_GROUP = "openpype" - -# name of Custom attribute that stores mongo_id from avalon db -CUST_ATTR_ID_KEY = "avalon_mongo_id" -CUST_ATTR_AUTO_SYNC = "avalon_auto_sync" - - def default_custom_attributes_definition(): json_file_path = os.path.join( os.path.dirname(os.path.abspath(__file__)), From 10b362937b1ce0887204882e5b9334e74dc7987b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 20 Apr 2021 18:27:42 +0200 Subject: [PATCH 057/180] application manager can be initialized to use different settings --- openpype/lib/applications.py | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/openpype/lib/applications.py b/openpype/lib/applications.py index 51c646d494..dc83037378 100644 --- a/openpype/lib/applications.py +++ b/openpype/lib/applications.py @@ -261,14 +261,32 @@ class Application: class ApplicationManager: - def __init__(self): - self.log = PypeLogger().get_logger(self.__class__.__name__) + """Load applications and tools and store them by their full name. + + Args: + system_settings (dict): Preloaded system settings. When passed manager + will always use these values. Gives ability to create manager + using different settings. + """ + def __init__(self, system_settings=None): + self.log = PypeLogger.get_logger(self.__class__.__name__) self.app_groups = {} self.applications = {} self.tool_groups = {} self.tools = {} + self._system_settings = system_settings + + self.refresh() + + def set_system_settings(self, system_settings): + """Ability to change init system settings. + + This will trigger refresh of manager. + """ + self._system_settings = system_settings + self.refresh() def refresh(self): @@ -278,9 +296,12 @@ class ApplicationManager: self.tool_groups.clear() self.tools.clear() - settings = get_system_settings( - clear_metadata=False, exclude_locals=False - ) + if self._system_settings is not None: + settings = copy.deepcopy(self._system_settings) + else: + settings = get_system_settings( + clear_metadata=False, exclude_locals=False + ) app_defs = settings["applications"] for group_name, variant_defs in app_defs.items(): From d746c4009f487ec34ae75c34a7ae9355b77af903 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 20 Apr 2021 18:49:29 +0200 Subject: [PATCH 058/180] added custom_attributes.py to ftrack lib --- openpype/modules/ftrack/lib/__init__.py | 10 +++++ .../modules/ftrack/lib/custom_attributes.py | 38 +++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 openpype/modules/ftrack/lib/custom_attributes.py diff --git a/openpype/modules/ftrack/lib/__init__.py b/openpype/modules/ftrack/lib/__init__.py index 87dadf6480..bc0c989c02 100644 --- a/openpype/modules/ftrack/lib/__init__.py +++ b/openpype/modules/ftrack/lib/__init__.py @@ -9,6 +9,12 @@ from . settings import ( get_ftrack_url_from_settings, get_ftrack_event_mongo_info ) +from .custm_attributes import ( + default_custom_attributes_definition, + app_definitions_from_app_manager, + tool_definitions_from_app_manager +) + from . import avalon_sync from . import credentials from .ftrack_base_handler import BaseHandler @@ -26,6 +32,10 @@ __all__ = ( "get_ftrack_url_from_settings", "get_ftrack_event_mongo_info", + "default_custom_attributes_definition", + "app_definitions_from_app_manager", + "tool_definitions_from_app_manager", + "avalon_sync", "credentials", diff --git a/openpype/modules/ftrack/lib/custom_attributes.py b/openpype/modules/ftrack/lib/custom_attributes.py new file mode 100644 index 0000000000..18efd7bcae --- /dev/null +++ b/openpype/modules/ftrack/lib/custom_attributes.py @@ -0,0 +1,38 @@ +import os +import json + + +def default_custom_attributes_definition(): + json_file_path = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "custom_attributes.json" + ) + with open(json_file_path, "r") as json_stream: + data = json.load(json_stream) + return data + + +def app_definitions_from_app_manager(app_manager): + app_definitions = [] + for app_name, app in app_manager.applications.items(): + if app.enabled and app.is_host: + app_definitions.append({ + app_name: app.full_label + }) + + if not app_definitions: + app_definitions.append({"empty": "< Empty >"}) + return app_definitions + + +def tool_definitions_from_app_manager(app_manager): + tools_data = [] + for tool_name, tool in app_manager.tools.items(): + tools_data.append({ + tool_name: tool.label + }) + + # Make sure there is at least one item + if not tools_data: + tools_data.append({"empty": "< Empty >"}) + return tools_data From d9ef2b59abdbeb7147cf21db47592147cb7f0d0f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 20 Apr 2021 18:49:51 +0200 Subject: [PATCH 059/180] create update custom attributes is using functions from lib --- .../action_create_cust_attrs.py | 35 +++++-------------- openpype/modules/ftrack/lib/avalon_sync.py | 9 ----- 2 files changed, 9 insertions(+), 35 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_user/action_create_cust_attrs.py b/openpype/modules/ftrack/event_handlers_user/action_create_cust_attrs.py index 8d585dee20..63605eda5e 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_create_cust_attrs.py +++ b/openpype/modules/ftrack/event_handlers_user/action_create_cust_attrs.py @@ -5,14 +5,17 @@ import ftrack_api from openpype.modules.ftrack.lib import ( BaseAction, statics_icon, + CUST_ATTR_ID_KEY, CUST_ATTR_GROUP, CUST_ATTR_TOOLS, - CUST_ATTR_APPLICATIONS -) -from openpype.modules.ftrack.lib.avalon_sync import ( - default_custom_attributes_definition + CUST_ATTR_APPLICATIONS, + + default_custom_attributes_definition, + app_definitions_from_app_manager, + tool_definitions_from_app_manager ) + from openpype.api import get_system_settings from openpype.lib import ApplicationManager @@ -377,20 +380,8 @@ class CustomAttributes(BaseAction): exc_info=True ) - def app_defs_from_app_manager(self): - app_definitions = [] - for app_name, app in self.app_manager.applications.items(): - if app.enabled and app.is_host: - app_definitions.append({ - app_name: app.full_label - }) - - if not app_definitions: - app_definitions.append({"empty": "< Empty >"}) - return app_definitions - def applications_attribute(self, event): - apps_data = self.app_defs_from_app_manager() + apps_data = app_definitions_from_app_manager(self.app_manager) applications_custom_attr_data = { "label": "Applications", @@ -406,15 +397,7 @@ class CustomAttributes(BaseAction): self.process_attr_data(applications_custom_attr_data, event) def tools_attribute(self, event): - tools_data = [] - for tool_name, tool in self.app_manager.tools.items(): - tools_data.append({ - tool_name: tool.label - }) - - # Make sure there is at least one item - if not tools_data: - tools_data.append({"empty": "< Empty >"}) + tools_data = tool_definitions_from_app_manager(self.app_manager) tools_custom_attr_data = { "label": "Tools", diff --git a/openpype/modules/ftrack/lib/avalon_sync.py b/openpype/modules/ftrack/lib/avalon_sync.py index 5f44181e5f..6e83be8b64 100644 --- a/openpype/modules/ftrack/lib/avalon_sync.py +++ b/openpype/modules/ftrack/lib/avalon_sync.py @@ -42,15 +42,6 @@ EntitySchemas = { "config": "openpype:config-2.0" } -def default_custom_attributes_definition(): - json_file_path = os.path.join( - os.path.dirname(os.path.abspath(__file__)), - "custom_attributes.json" - ) - with open(json_file_path, "r") as json_stream: - data = json.load(json_stream) - return data - def check_regex(name, entity_type, in_schema=None, schema_patterns=None): schema_name = "asset-3.0" From 02238eef7cbbdcce38d6fe0ccc64f44991907f40 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 20 Apr 2021 19:07:53 +0200 Subject: [PATCH 060/180] added `get_openpype_attr` to custom_attributes --- openpype/modules/ftrack/lib/__init__.py | 6 ++-- .../modules/ftrack/lib/custom_attributes.py | 35 +++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/openpype/modules/ftrack/lib/__init__.py b/openpype/modules/ftrack/lib/__init__.py index bc0c989c02..ce6d5284b6 100644 --- a/openpype/modules/ftrack/lib/__init__.py +++ b/openpype/modules/ftrack/lib/__init__.py @@ -9,10 +9,11 @@ from . settings import ( get_ftrack_url_from_settings, get_ftrack_event_mongo_info ) -from .custm_attributes import ( +from .custom_attributes import ( default_custom_attributes_definition, app_definitions_from_app_manager, - tool_definitions_from_app_manager + tool_definitions_from_app_manager, + get_openpype_attr ) from . import avalon_sync @@ -35,6 +36,7 @@ __all__ = ( "default_custom_attributes_definition", "app_definitions_from_app_manager", "tool_definitions_from_app_manager", + "get_openpype_attr", "avalon_sync", diff --git a/openpype/modules/ftrack/lib/custom_attributes.py b/openpype/modules/ftrack/lib/custom_attributes.py index 18efd7bcae..33eea32baa 100644 --- a/openpype/modules/ftrack/lib/custom_attributes.py +++ b/openpype/modules/ftrack/lib/custom_attributes.py @@ -1,6 +1,8 @@ import os import json +from .constants import CUST_ATTR_GROUP + def default_custom_attributes_definition(): json_file_path = os.path.join( @@ -36,3 +38,36 @@ def tool_definitions_from_app_manager(app_manager): if not tools_data: tools_data.append({"empty": "< Empty >"}) return tools_data + + +def get_openpype_attr(session, split_hierarchical=True, query_keys=None): + custom_attributes = [] + hier_custom_attributes = [] + if not query_keys: + query_keys = [ + "id", + "entity_type", + "object_type_id", + "is_hierarchical", + "default" + ] + # TODO remove deprecated "pype" group from query + cust_attrs_query = ( + "select {}" + " from CustomAttributeConfiguration" + # Kept `pype` for Backwards Compatiblity + " where group.name in (\"pype\", \"{}\")" + ).format(", ".join(query_keys), CUST_ATTR_GROUP) + all_avalon_attr = session.query(cust_attrs_query).all() + for cust_attr in all_avalon_attr: + if split_hierarchical and cust_attr["is_hierarchical"]: + hier_custom_attributes.append(cust_attr) + continue + + custom_attributes.append(cust_attr) + + if split_hierarchical: + # return tuple + return custom_attributes, hier_custom_attributes + + return custom_attributes From eb1b9556673b63d51ef324fb5a13e2caceb07b43 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 20 Apr 2021 19:09:30 +0200 Subject: [PATCH 061/180] replaced usage of get_pype_attr with get_openpype_attr --- .../action_prepare_project.py | 8 ++-- .../event_sync_to_avalon.py | 9 +++-- .../action_clean_hierarchical_attributes.py | 9 +++-- .../action_prepare_project.py | 8 ++-- openpype/modules/ftrack/ftrack_module.py | 4 +- openpype/modules/ftrack/lib/avalon_sync.py | 38 ++----------------- 6 files changed, 24 insertions(+), 52 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_server/action_prepare_project.py b/openpype/modules/ftrack/event_handlers_server/action_prepare_project.py index 8248bf532e..12d687bbf2 100644 --- a/openpype/modules/ftrack/event_handlers_server/action_prepare_project.py +++ b/openpype/modules/ftrack/event_handlers_server/action_prepare_project.py @@ -2,9 +2,9 @@ import json from openpype.api import ProjectSettings -from openpype.modules.ftrack.lib import ServerAction -from openpype.modules.ftrack.lib.avalon_sync import ( - get_pype_attr, +from openpype.modules.ftrack.lib import ( + ServerAction, + get_openpype_attr, CUST_ATTR_AUTO_SYNC ) @@ -159,7 +159,7 @@ class PrepareProjectServer(ServerAction): for key, entity in project_anatom_settings["attributes"].items(): attribute_values_by_key[key] = entity.value - cust_attrs, hier_cust_attrs = get_pype_attr(self.session, True) + cust_attrs, hier_cust_attrs = get_openpype_attr(self.session, True) for attr in hier_cust_attrs: key = attr["key"] diff --git a/openpype/modules/ftrack/event_handlers_server/event_sync_to_avalon.py b/openpype/modules/ftrack/event_handlers_server/event_sync_to_avalon.py index 347b227dd3..3bb01798e4 100644 --- a/openpype/modules/ftrack/event_handlers_server/event_sync_to_avalon.py +++ b/openpype/modules/ftrack/event_handlers_server/event_sync_to_avalon.py @@ -18,12 +18,15 @@ from avalon import schema from avalon.api import AvalonMongoDB from openpype.modules.ftrack.lib import ( + get_openpype_attr, + CUST_ATTR_ID_KEY, + CUST_ATTR_AUTO_SYNC, + avalon_sync, + BaseEvent ) from openpype.modules.ftrack.lib.avalon_sync import ( - CUST_ATTR_ID_KEY, - CUST_ATTR_AUTO_SYNC, EntitySchemas ) @@ -125,7 +128,7 @@ class SyncToAvalonEvent(BaseEvent): @property def avalon_cust_attrs(self): if self._avalon_cust_attrs is None: - self._avalon_cust_attrs = avalon_sync.get_pype_attr( + self._avalon_cust_attrs = get_openpype_attr( self.process_session, query_keys=self.cust_attr_query_keys ) return self._avalon_cust_attrs diff --git a/openpype/modules/ftrack/event_handlers_user/action_clean_hierarchical_attributes.py b/openpype/modules/ftrack/event_handlers_user/action_clean_hierarchical_attributes.py index c326c56a7c..45cc9adf55 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_clean_hierarchical_attributes.py +++ b/openpype/modules/ftrack/event_handlers_user/action_clean_hierarchical_attributes.py @@ -1,7 +1,10 @@ import collections import ftrack_api -from openpype.modules.ftrack.lib import BaseAction, statics_icon -from openpype.modules.ftrack.lib.avalon_sync import get_pype_attr +from openpype.modules.ftrack.lib import ( + BaseAction, + statics_icon, + get_openpype_attr +) class CleanHierarchicalAttrsAction(BaseAction): @@ -52,7 +55,7 @@ class CleanHierarchicalAttrsAction(BaseAction): ) entity_ids_joined = ", ".join(all_entities_ids) - attrs, hier_attrs = get_pype_attr(session) + attrs, hier_attrs = get_openpype_attr(session) for attr in hier_attrs: configuration_key = attr["key"] diff --git a/openpype/modules/ftrack/event_handlers_user/action_prepare_project.py b/openpype/modules/ftrack/event_handlers_user/action_prepare_project.py index bd25f995fe..5298c06371 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_prepare_project.py +++ b/openpype/modules/ftrack/event_handlers_user/action_prepare_project.py @@ -4,10 +4,8 @@ from openpype.api import ProjectSettings from openpype.modules.ftrack.lib import ( BaseAction, - statics_icon -) -from openpype.modules.ftrack.lib.avalon_sync import ( - get_pype_attr, + statics_icon, + get_openpype_attr, CUST_ATTR_AUTO_SYNC ) @@ -162,7 +160,7 @@ class PrepareProjectLocal(BaseAction): for key, entity in project_anatom_settings["attributes"].items(): attribute_values_by_key[key] = entity.value - cust_attrs, hier_cust_attrs = get_pype_attr(self.session, True) + cust_attrs, hier_cust_attrs = get_openpype_attr(self.session, True) for attr in hier_cust_attrs: key = attr["key"] diff --git a/openpype/modules/ftrack/ftrack_module.py b/openpype/modules/ftrack/ftrack_module.py index d242268048..b057503007 100644 --- a/openpype/modules/ftrack/ftrack_module.py +++ b/openpype/modules/ftrack/ftrack_module.py @@ -150,7 +150,7 @@ class FtrackModule( return import ftrack_api - from openpype.modules.ftrack.lib import avalon_sync + from openpype.modules.ftrack.lib import get_openpype_attr session = self.create_ftrack_session() project_entity = session.query( @@ -166,7 +166,7 @@ class FtrackModule( project_id = project_entity["id"] - cust_attr, hier_attr = avalon_sync.get_pype_attr(session) + cust_attr, hier_attr = get_openpype_attr(session) cust_attr_by_key = {attr["key"]: attr for attr in cust_attr} hier_attrs_by_key = {attr["key"]: attr for attr in hier_attr} for key, value in attributes_changes.items(): diff --git a/openpype/modules/ftrack/lib/avalon_sync.py b/openpype/modules/ftrack/lib/avalon_sync.py index 6e83be8b64..cfe1f011e7 100644 --- a/openpype/modules/ftrack/lib/avalon_sync.py +++ b/openpype/modules/ftrack/lib/avalon_sync.py @@ -31,6 +31,7 @@ from .constants import ( CUST_ATTR_AUTO_SYNC, CUST_ATTR_GROUP ) +from .custom_attributes import get_openpype_attr log = Logger.get_logger(__name__) @@ -80,39 +81,6 @@ def join_query_keys(keys): return ",".join(["\"{}\"".format(key) for key in keys]) -def get_pype_attr(session, split_hierarchical=True, query_keys=None): - custom_attributes = [] - hier_custom_attributes = [] - if not query_keys: - query_keys = [ - "id", - "entity_type", - "object_type_id", - "is_hierarchical", - "default" - ] - # TODO remove deprecated "pype" group from query - cust_attrs_query = ( - "select {}" - " from CustomAttributeConfiguration" - # Kept `pype` for Backwards Compatiblity - " where group.name in (\"pype\", \"{}\")" - ).format(", ".join(query_keys), CUST_ATTR_GROUP) - all_avalon_attr = session.query(cust_attrs_query).all() - for cust_attr in all_avalon_attr: - if split_hierarchical and cust_attr["is_hierarchical"]: - hier_custom_attributes.append(cust_attr) - continue - - custom_attributes.append(cust_attr) - - if split_hierarchical: - # return tuple - return custom_attributes, hier_custom_attributes - - return custom_attributes - - def get_python_type_for_custom_attribute(cust_attr, cust_attr_type_name=None): """Python type that should value of custom attribute have. @@ -910,7 +878,7 @@ class SyncEntitiesFactory: def set_cutom_attributes(self): self.log.debug("* Preparing custom attributes") # Get custom attributes and values - custom_attrs, hier_attrs = get_pype_attr( + custom_attrs, hier_attrs = get_openpype_attr( self.session, query_keys=self.cust_attr_query_keys ) ent_types = self.session.query("select id, name from ObjectType").all() @@ -2497,7 +2465,7 @@ class SyncEntitiesFactory: if new_entity_id not in p_chilren: self.entities_dict[parent_id]["children"].append(new_entity_id) - cust_attr, _ = get_pype_attr(self.session) + cust_attr, _ = get_openpype_attr(self.session) for _attr in cust_attr: key = _attr["key"] if key not in av_entity["data"]: From 4996903f23dbc7174270649d88f775c1a1d7a767 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 20 Apr 2021 19:19:41 +0200 Subject: [PATCH 062/180] settings has defined few api exceptions --- openpype/settings/__init__.py | 7 +++++++ openpype/settings/exceptions.py | 11 +++++++++++ openpype/settings/lib.py | 4 ++++ 3 files changed, 22 insertions(+) create mode 100644 openpype/settings/exceptions.py diff --git a/openpype/settings/__init__.py b/openpype/settings/__init__.py index b4187829fc..3755fabeb2 100644 --- a/openpype/settings/__init__.py +++ b/openpype/settings/__init__.py @@ -1,3 +1,7 @@ +from .exceptions import ( + SaveWarning, + SaveSettingsValidation +) from .lib import ( get_system_settings, get_project_settings, @@ -12,6 +16,9 @@ from .entities import ( __all__ = ( + "SaveWarning", + "SaveSettingsValidation", + "get_system_settings", "get_project_settings", "get_current_project_settings", diff --git a/openpype/settings/exceptions.py b/openpype/settings/exceptions.py new file mode 100644 index 0000000000..86f04c76b9 --- /dev/null +++ b/openpype/settings/exceptions.py @@ -0,0 +1,11 @@ +class SaveSettingsValidation(Exception): + pass + + +class SaveWarning(SaveSettingsValidation): + def __init__(self, warnings): + if isinstance(warnings, str): + warnings = [warnings] + self.warnings = warnings + msg = ", ".join(warnings) + super(SaveWarning, self).__init__(msg) diff --git a/openpype/settings/lib.py b/openpype/settings/lib.py index 3bf2141808..31c7c902cb 100644 --- a/openpype/settings/lib.py +++ b/openpype/settings/lib.py @@ -4,6 +4,10 @@ import functools import logging import platform import copy +from .exceptions import ( + SaveSettingsValidation, + SaveWarning +) from .constants import ( M_OVERRIDEN_KEY, M_ENVIRONMENT_KEY, From 9ac5427aaf4e79939d586e3b320e07fed8ea8660 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 20 Apr 2021 19:19:55 +0200 Subject: [PATCH 063/180] ftrack updates custom attributes on application save --- openpype/modules/ftrack/ftrack_module.py | 84 +++++++++++++++++++++++- 1 file changed, 81 insertions(+), 3 deletions(-) diff --git a/openpype/modules/ftrack/ftrack_module.py b/openpype/modules/ftrack/ftrack_module.py index b057503007..28281786b3 100644 --- a/openpype/modules/ftrack/ftrack_module.py +++ b/openpype/modules/ftrack/ftrack_module.py @@ -1,4 +1,5 @@ import os +import json import collections from abc import ABCMeta, abstractmethod import six @@ -12,6 +13,7 @@ from openpype.modules import ( ILaunchHookPaths, ISettingsChangeListener ) +from openpype.settings import SaveWarning FTRACK_MODULE_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -128,10 +130,86 @@ class FtrackModule( if self.tray_module: self.tray_module.changed_user() - def on_system_settings_save(self, *_args, **_kwargs): + def on_system_settings_save(self, old_value, new_value, changes): """Implementation of ISettingsChangeListener interface.""" - # Ignore - return + try: + session = self.create_ftrack_session() + except Exception: + self.log.warning("Couldn't create ftrack session.", exc_info=True) + raise SaveWarning(( + "Couldn't create Ftrack session." + " You may need to update applications" + " and tools in Ftrack custom attributes using defined action." + )) + + from .lib import ( + get_openpype_attr, + CUST_ATTR_APPLICATIONS, + CUST_ATTR_TOOLS, + app_definitions_from_app_manager, + tool_definitions_from_app_manager + ) + from openpype.api import ApplicationManager + query_keys = [ + "id", + "key", + "config" + ] + custom_attributes = get_openpype_attr( + session, + split_hierarchical=False, + query_keys=query_keys + ) + app_attribute = None + tool_attribute = None + for custom_attribute in custom_attributes: + key = custom_attribute["key"] + if key == CUST_ATTR_APPLICATIONS: + app_attribute = custom_attribute + elif key == CUST_ATTR_TOOLS: + tool_attribute = custom_attribute + + app_manager = ApplicationManager(new_value) + missing_attributes = [] + if not app_attribute: + missing_attributes.append(CUST_ATTR_APPLICATIONS) + else: + config = json.loads(app_attribute["config"]) + new_data = app_definitions_from_app_manager(app_manager) + prepared_data = [] + for item in new_data: + for key, label in item.items(): + prepared_data.append({ + "menu": label, + "value": key + }) + + config["data"] = json.dumps(prepared_data) + app_attribute["config"] = json.dumps(config) + + if not tool_attribute: + missing_attributes.append(CUST_ATTR_TOOLS) + else: + config = json.loads(tool_attribute["config"]) + new_data = tool_definitions_from_app_manager(app_manager) + prepared_data = [] + for item in new_data: + for key, label in item.items(): + prepared_data.append({ + "menu": label, + "value": key + }) + config["data"] = json.dumps(prepared_data) + tool_attribute["config"] = json.dumps(config) + + session.commit() + + if missing_attributes: + raise SaveWarning(( + "Couldn't find custom attribute/s ({}) to update." + " You may need to update applications" + " and tools in Ftrack custom attributes using defined action." + ).format(", ".join(missing_attributes))) def on_project_settings_save(self, *_args, **_kwargs): """Implementation of ISettingsChangeListener interface.""" From cbc0fca2e3114b279470361334036224b269d42f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 20 Apr 2021 19:32:56 +0200 Subject: [PATCH 064/180] removed user module with all settings --- openpype/modules/__init__.py | 7 - openpype/modules/ftrack/ftrack_module.py | 7 - openpype/modules/user/__init__.py | 10 -- openpype/modules/user/rest_api.py | 35 ---- openpype/modules/user/user_module.py | 169 ------------------ openpype/modules/user/widget_user.py | 88 --------- .../defaults/system_settings/modules.json | 3 - .../schemas/system_schema/schema_modules.json | 14 -- 8 files changed, 333 deletions(-) delete mode 100644 openpype/modules/user/__init__.py delete mode 100644 openpype/modules/user/rest_api.py delete mode 100644 openpype/modules/user/user_module.py delete mode 100644 openpype/modules/user/widget_user.py diff --git a/openpype/modules/__init__.py b/openpype/modules/__init__.py index d7c6d99fe6..bae48c540b 100644 --- a/openpype/modules/__init__.py +++ b/openpype/modules/__init__.py @@ -18,10 +18,6 @@ from .webserver import ( WebServerModule, IWebServerRoutes ) -from .user import ( - UserModule, - IUserModule -) from .idle_manager import ( IdleManager, IIdleManager @@ -60,9 +56,6 @@ __all__ = ( "WebServerModule", "IWebServerRoutes", - "UserModule", - "IUserModule", - "IdleManager", "IIdleManager", diff --git a/openpype/modules/ftrack/ftrack_module.py b/openpype/modules/ftrack/ftrack_module.py index d242268048..e639e1a634 100644 --- a/openpype/modules/ftrack/ftrack_module.py +++ b/openpype/modules/ftrack/ftrack_module.py @@ -8,7 +8,6 @@ from openpype.modules import ( ITrayModule, IPluginPaths, ITimersManager, - IUserModule, ILaunchHookPaths, ISettingsChangeListener ) @@ -32,7 +31,6 @@ class FtrackModule( ITrayModule, IPluginPaths, ITimersManager, - IUserModule, ILaunchHookPaths, ISettingsChangeListener ): @@ -123,11 +121,6 @@ class FtrackModule( if self.tray_module: self.tray_module.stop_timer_manager() - def on_pype_user_change(self, username): - """Implementation of IUserModule interface.""" - if self.tray_module: - self.tray_module.changed_user() - def on_system_settings_save(self, *_args, **_kwargs): """Implementation of ISettingsChangeListener interface.""" # Ignore diff --git a/openpype/modules/user/__init__.py b/openpype/modules/user/__init__.py deleted file mode 100644 index a97ac0eef6..0000000000 --- a/openpype/modules/user/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -from .user_module import ( - UserModule, - IUserModule -) - - -__all__ = ( - "UserModule", - "IUserModule" -) diff --git a/openpype/modules/user/rest_api.py b/openpype/modules/user/rest_api.py deleted file mode 100644 index 566425a19b..0000000000 --- a/openpype/modules/user/rest_api.py +++ /dev/null @@ -1,35 +0,0 @@ -import json -from aiohttp.web_response import Response - - -class UserModuleRestApi: - def __init__(self, user_module, server_manager): - self.module = user_module - self.server_manager = server_manager - - self.prefix = "/user" - - self.register() - - def register(self): - self.server_manager.add_route( - "GET", - self.prefix + "/username", - self.get_username - ) - self.server_manager.add_route( - "GET", - self.prefix + "/show_widget", - self.show_user_widget - ) - - async def get_username(self, request): - return Response( - status=200, - body=json.dumps(self.module.cred, indent=4), - content_type="application/json" - ) - - async def show_user_widget(self, request): - self.module.action_show_widget.trigger() - return Response(status=200) diff --git a/openpype/modules/user/user_module.py b/openpype/modules/user/user_module.py deleted file mode 100644 index 7d257f1781..0000000000 --- a/openpype/modules/user/user_module.py +++ /dev/null @@ -1,169 +0,0 @@ -import os -import json -import getpass - -from abc import ABCMeta, abstractmethod - -import six -import appdirs - -from .. import ( - PypeModule, - ITrayModule, - IWebServerRoutes -) - - -@six.add_metaclass(ABCMeta) -class IUserModule: - """Interface for other modules to use user change callbacks.""" - - @abstractmethod - def on_pype_user_change(self, username): - """What should happen on Pype user change.""" - pass - - -class UserModule(PypeModule, ITrayModule, IWebServerRoutes): - cred_folder_path = os.path.normpath( - appdirs.user_data_dir('pype-app', 'pype') - ) - cred_filename = 'user_info.json' - env_name = "OPENPYPE_USERNAME" - - name = "user" - - def initialize(self, modules_settings): - user_settings = modules_settings[self.name] - self.enabled = user_settings["enabled"] - - self.callbacks_on_user_change = [] - self.cred = {} - self.cred_path = os.path.normpath(os.path.join( - self.cred_folder_path, self.cred_filename - )) - - # Tray attributes - self.widget_login = None - self.action_show_widget = None - - self.rest_api_obj = None - - def tray_init(self): - from .widget_user import UserWidget - self.widget_login = UserWidget(self) - - self.load_credentials() - - def register_callback_on_user_change(self, callback): - self.callbacks_on_user_change.append(callback) - - def tray_start(self): - """Store credentials to env and preset them to widget""" - username = "" - if self.cred: - username = self.cred.get("username") or "" - - os.environ[self.env_name] = username - self.widget_login.set_user(username) - - def tray_exit(self): - """Nothing special for User.""" - return - - def get_user(self): - return self.cred.get("username") or getpass.getuser() - - def webserver_initialization(self, server_manager): - """Implementation of IWebServerRoutes interface.""" - from .rest_api import UserModuleRestApi - - self.rest_api_obj = UserModuleRestApi(self, server_manager) - - def connect_with_modules(self, enabled_modules): - for module in enabled_modules: - if isinstance(module, IUserModule): - self.callbacks_on_user_change.append( - module.on_pype_user_change - ) - - # Definition of Tray menu - def tray_menu(self, parent_menu): - from Qt import QtWidgets - """Add menu or action to Tray(or parent)'s menu""" - action = QtWidgets.QAction("Username", parent_menu) - action.triggered.connect(self.show_widget) - parent_menu.addAction(action) - parent_menu.addSeparator() - - self.action_show_widget = action - - def load_credentials(self): - """Get credentials from JSON file """ - credentials = {} - try: - file = open(self.cred_path, "r") - credentials = json.load(file) - file.close() - - self.cred = credentials - username = credentials.get("username") - if username: - self.log.debug("Loaded Username \"{}\"".format(username)) - else: - self.log.debug("Pype Username is not set") - - return credentials - - except FileNotFoundError: - return self.save_credentials(getpass.getuser()) - - except json.decoder.JSONDecodeError: - self.log.warning(( - "File where users credentials should be stored" - " has invalid json format. Loading system username." - )) - return self.save_credentials(getpass.getuser()) - - def change_credentials(self, username): - self.save_credentials(username) - for callback in self.callbacks_on_user_change: - try: - callback(username) - except Exception: - self.log.warning( - "Failed to execute callback \"{}\".".format( - str(callback) - ), - exc_info=True - ) - - def save_credentials(self, username): - """Save credentials to JSON file, env and widget""" - if username is None: - username = "" - - username = str(username).strip() - - self.cred = {"username": username} - os.environ[self.env_name] = username - if self.widget_login: - self.widget_login.set_user(username) - try: - file = open(self.cred_path, "w") - file.write(json.dumps(self.cred)) - file.close() - self.log.debug("Username \"{}\" stored".format(username)) - except Exception: - self.log.error( - "Could not store username to file \"{}\"".format( - self.cred_path - ), - exc_info=True - ) - - return self.cred - - def show_widget(self): - """Show dialog to enter credentials""" - self.widget_login.show() diff --git a/openpype/modules/user/widget_user.py b/openpype/modules/user/widget_user.py deleted file mode 100644 index f8ecadf56b..0000000000 --- a/openpype/modules/user/widget_user.py +++ /dev/null @@ -1,88 +0,0 @@ -from Qt import QtCore, QtGui, QtWidgets -from avalon import style -from openpype import resources - - -class UserWidget(QtWidgets.QWidget): - - MIN_WIDTH = 300 - - def __init__(self, module): - - super(UserWidget, self).__init__() - - self.module = module - - # Style - icon = QtGui.QIcon(resources.pype_icon_filepath()) - self.setWindowIcon(icon) - self.setWindowTitle("Username Settings") - self.setMinimumWidth(self.MIN_WIDTH) - self.setStyleSheet(style.load_stylesheet()) - - self.setWindowFlags( - QtCore.Qt.WindowCloseButtonHint | - QtCore.Qt.WindowMinimizeButtonHint - ) - - self.setLayout(self._main()) - - def show(self, *args, **kwargs): - super().show(*args, **kwargs) - # Move widget to center of active screen on show - screen = QtWidgets.QApplication.desktop().screen() - screen_center = lambda self: ( - screen.rect().center() - self.rect().center() - ) - self.move(screen_center(self)) - - def _main(self): - main_layout = QtWidgets.QVBoxLayout() - - form_layout = QtWidgets.QFormLayout() - form_layout.setContentsMargins(10, 15, 10, 5) - - label_username = QtWidgets.QLabel("Username:") - label_username.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor)) - label_username.setTextFormat(QtCore.Qt.RichText) - - input_username = QtWidgets.QLineEdit() - input_username.setPlaceholderText( - QtCore.QCoreApplication.translate("main", "e.g. John Smith") - ) - - form_layout.addRow(label_username, input_username) - - btn_save = QtWidgets.QPushButton("Save") - btn_save.clicked.connect(self.click_save) - - btn_cancel = QtWidgets.QPushButton("Cancel") - btn_cancel.clicked.connect(self.close) - - btn_group = QtWidgets.QHBoxLayout() - btn_group.addStretch(1) - btn_group.addWidget(btn_save) - btn_group.addWidget(btn_cancel) - - main_layout.addLayout(form_layout) - main_layout.addLayout(btn_group) - - self.input_username = input_username - - return main_layout - - def set_user(self, username): - self.input_username.setText(username) - - def click_save(self): - # all what should happen - validations and saving into appsdir - username = self.input_username.text() - self.module.change_credentials(username) - self._close_widget() - - def closeEvent(self, event): - event.ignore() - self._close_widget() - - def _close_widget(self): - self.hide() diff --git a/openpype/settings/defaults/system_settings/modules.json b/openpype/settings/defaults/system_settings/modules.json index b3065058a1..6e4b493116 100644 --- a/openpype/settings/defaults/system_settings/modules.json +++ b/openpype/settings/defaults/system_settings/modules.json @@ -161,9 +161,6 @@ "log_viewer": { "enabled": true }, - "user": { - "enabled": true - }, "standalonepublish_tool": { "enabled": true } diff --git a/openpype/settings/entities/schemas/system_schema/schema_modules.json b/openpype/settings/entities/schemas/system_schema/schema_modules.json index a30cafd0c2..878958b12d 100644 --- a/openpype/settings/entities/schemas/system_schema/schema_modules.json +++ b/openpype/settings/entities/schemas/system_schema/schema_modules.json @@ -154,20 +154,6 @@ } ] }, - { - "type": "dict", - "key": "user", - "label": "User setting", - "collapsible": true, - "checkbox_key": "enabled", - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - } - ] - }, { "type": "dict", "key": "standalonepublish_tool", From 72ac25e60e949ad3ab8185983f2d011dc9703c5d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 20 Apr 2021 20:18:31 +0200 Subject: [PATCH 065/180] SyncServer GUI - rework of header and menus Reiplemented QHeaderView Refactored accessing models Still wip --- openpype/modules/sync_server/tray/lib.py | 96 ++++ openpype/modules/sync_server/tray/models.py | 102 ++-- openpype/modules/sync_server/tray/widgets.py | 505 ++++++++++++------- 3 files changed, 488 insertions(+), 215 deletions(-) diff --git a/openpype/modules/sync_server/tray/lib.py b/openpype/modules/sync_server/tray/lib.py index 051567ed6c..41b0eb43f9 100644 --- a/openpype/modules/sync_server/tray/lib.py +++ b/openpype/modules/sync_server/tray/lib.py @@ -1,5 +1,7 @@ from Qt import QtCore import attr +import abc +import six from openpype.lib import PypeLogger @@ -24,6 +26,100 @@ FailedRole = QtCore.Qt.UserRole + 8 HeaderNameRole = QtCore.Qt.UserRole + 10 +@six.add_metaclass(abc.ABCMeta) +class AbstractColumnFilter: + + def __init__(self, column_name, dbcon=None): + self.column_name = column_name + self.dbcon = dbcon + self._search_variants = [] + + def search_variants(self): + """ + Returns all flavors of search available for this column, + """ + return self._search_variants + + @abc.abstractmethod + def values(self): + """ + Returns dict of available values for filter {'label':'value'} + """ + pass + + @abc.abstractmethod + def prepare_match_part(self, values): + """ + Prepares format valid for $match part from 'values + + Args: + values (dict): {'label': 'value'} + Returns: + (dict): {'COLUMN_NAME': {'$in': ['val1', 'val2']}} + """ + pass + + +class PredefinedSetFilter(AbstractColumnFilter): + + def __init__(self, column_name, values): + super().__init__(column_name) + self._search_variants = ['text', 'checkbox'] + self._values = values + + def values(self): + return {k: v for k, v in self._values.items()} + + def prepare_match_part(self, values): + return {'$in': list(values.keys())} + + +class RegexTextFilter(AbstractColumnFilter): + + def __init__(self, column_name): + super().__init__(column_name) + self._search_variants = ['text'] + + def values(self): + return {} + + def prepare_match_part(self, values): + """ values = {'text1 text2': 'text1 text2'} """ + if not values: + return {} + + regex_strs = set() + text = list(values.keys())[0] # only single key always expected + for word in text.split(): + regex_strs.add('.*{}.*'.format(word)) + + return {"$regex": "|".join(regex_strs), + "$options": 'i'} + + +class MultiSelectFilter(AbstractColumnFilter): + + def __init__(self, column_name, values=None, dbcon=None): + super().__init__(column_name) + self._values = values + self.dbcon = dbcon + self._search_variants = ['checkbox'] + + def values(self): + if self._values: + return {k: v for k, v in self._values.items()} + + recs = self.dbcon.find({'type': self.column_name}, {"name": 1, + "_id": -1}) + values = {} + for item in recs: + values[item["name"]] = item["name"] + return dict(sorted(values.items(), key=lambda it: it[1])) + + def prepare_match_part(self, values): + return {'$in': list(values.keys())} + + @attr.s class FilterDefinition: type = attr.ib() diff --git a/openpype/modules/sync_server/tray/models.py b/openpype/modules/sync_server/tray/models.py index 444422c56a..4b70fbae15 100644 --- a/openpype/modules/sync_server/tray/models.py +++ b/openpype/modules/sync_server/tray/models.py @@ -6,6 +6,7 @@ from Qt import QtCore from Qt.QtCore import Qt from avalon.tools.delegates import pretty_timestamp +from avalon.vendor import qtawesome from openpype.lib import PypeLogger @@ -67,18 +68,24 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): return len(self._header) def headerData(self, section, orientation, role): + name = self.COLUMN_LABELS[section][0] if role == Qt.DisplayRole: if orientation == Qt.Horizontal: - name = self.COLUMN_LABELS[section][0] - txt = "" - if name in self.column_filtering.keys(): - txt = "(F)" - return self.COLUMN_LABELS[section][1] + txt # return label + return self.COLUMN_LABELS[section][1] + + if role == Qt.DecorationRole: + if name in self.column_filtering.keys(): + return qtawesome.icon("fa.filter", color="white") + if self.COLUMN_FILTERS.get(name): + return qtawesome.icon("fa.filter", color="gray") if role == lib.HeaderNameRole: if orientation == Qt.Horizontal: return self.COLUMN_LABELS[section][0] # return name + def get_column(self, index): + return self.COLUMN_LABELS[index] + def get_header_index(self, value): """ Returns index of 'value' in headers @@ -199,9 +206,28 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): Args: word_filter (str): string inputted by user """ - self.word_filter = word_filter + self._word_filter = word_filter self.refresh() + def get_column_filter(self, index): + """ + Returns filter object for column 'index + + Args: + index(int): index of column in header + + Returns: + (AbstractColumnFilter) + """ + column_name = self._header[index] + + filter_rec = self.COLUMN_FILTERS.get(column_name) + if filter_rec: + filter_rec.dbcon = self.dbcon # up-to-date db connection + + return filter_rec + + def get_column_filter_values(self, index): """ Returns list of available values for filtering in the column @@ -215,21 +241,11 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): menu for some columns ('subset') might be 'value' and 'label' same """ - column_name = self._header[index] - - filter_def = self.COLUMN_FILTERS.get(column_name) - if not filter_def: + filter_rec = self.get_column_filter(index) + if not filter_rec: return {} - if filter_def['type'] == 'predefined_set': - return dict(filter_def['values']) - elif filter_def['type'] == 'available_values': - recs = self.dbcon.find({'type': column_name}, {"name": 1, - "_id": -1}) - values = {} - for item in recs: - values[item["name"]] = item["name"] - return dict(sorted(values.items(), key=lambda item: item[1])) + return filter_rec.values() def set_project(self, project): """ @@ -313,11 +329,10 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): ] COLUMN_FILTERS = { - 'status': {'type': 'predefined_set', - 'values': {k: v for k, v in lib.STATUS.items()}}, - 'subset': {'type': 'available_values'}, - 'asset': {'type': 'available_values'}, - 'representation': {'type': 'available_values'} + 'status': lib.PredefinedSetFilter('status', lib.STATUS), + 'subset': lib.RegexTextFilter('subset'), + 'asset': lib.RegexTextFilter('asset'), + 'representation': lib.MultiSelectFilter('representation') } refresh_started = QtCore.Signal() @@ -356,9 +371,11 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): self._project = project self._rec_loaded = 0 self._total_records = 0 # how many documents query actually found - self.word_filter = None + self._word_filter = None self._column_filtering = {} + self._word_filter = None + self._initialized = False if not self._project or self._project == lib.DUMMY_PROJECT: return @@ -383,6 +400,19 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): self.timer.timeout.connect(self.tick) self.timer.start(self.REFRESH_SEC) + def get_filters(self): + """ + Returns all available filter editors per column_name keys. + """ + filters = {} + for column_name, _ in self.COLUMN_LABELS: + filter_rec = self.COLUMN_FILTERS.get(column_name) + if filter_rec: + filter_rec.dbcon = self.dbcon + filters[column_name] = filter_rec + + return filters + def data(self, index, role): item = self._data[index.row()] @@ -666,8 +696,12 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): self._column_filtering : {'status': {'$in': [1, 2, 3]}} """ filtering = {} - for key, dict_value in checked_values.items(): - filtering[key] = {'$in': list(dict_value.keys())} + for column_name, dict_value in checked_values.items(): + column_f = self.COLUMN_FILTERS.get(column_name) + if not column_f: + continue + column_f.dbcon = self.dbcon + filtering[column_name] = column_f.prepare_match_part(dict_value) self._column_filtering = filtering @@ -690,18 +724,18 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): 'files.sites.name': {'$all': [self.local_site, self.remote_site]} } - if not self.word_filter: + if not self._word_filter: return base_match else: - regex_str = '.*{}.*'.format(self.word_filter) + regex_str = '.*{}.*'.format(self._word_filter) base_match['$or'] = [ {'context.subset': {'$regex': regex_str, '$options': 'i'}}, {'context.asset': {'$regex': regex_str, '$options': 'i'}}, {'context.representation': {'$regex': regex_str, '$options': 'i'}}] - if ObjectId.is_valid(self.word_filter): - base_match['$or'] = [{'_id': ObjectId(self.word_filter)}] + if ObjectId.is_valid(self._word_filter): + base_match['$or'] = [{'_id': ObjectId(self._word_filter)}] return base_match @@ -848,7 +882,7 @@ class SyncRepresentationDetailModel(_SyncRepresentationModel): self._project = project self._rec_loaded = 0 self._total_records = 0 # how many documents query actually found - self.word_filter = None + self._word_filter = None self._id = _id self._initialized = False @@ -1114,13 +1148,13 @@ class SyncRepresentationDetailModel(_SyncRepresentationModel): Returns: (dict) """ - if not self.word_filter: + if not self._word_filter: return { "type": "representation", "_id": self._id } else: - regex_str = '.*{}.*'.format(self.word_filter) + regex_str = '.*{}.*'.format(self._word_filter) return { "type": "representation", "_id": self._id, diff --git a/openpype/modules/sync_server/tray/widgets.py b/openpype/modules/sync_server/tray/widgets.py index 5719d13716..f9f904cd03 100644 --- a/openpype/modules/sync_server/tray/widgets.py +++ b/openpype/modules/sync_server/tray/widgets.py @@ -15,6 +15,7 @@ from openpype.api import get_local_site_id from openpype.lib import PypeLogger from avalon.tools.delegates import pretty_timestamp +from avalon.vendor import qtawesome from openpype.modules.sync_server.tray.models import ( SyncRepresentationSummaryModel, @@ -142,15 +143,15 @@ class SyncRepresentationWidget(QtWidgets.QWidget): message_generated = QtCore.Signal(str) default_widths = ( - ("asset", 220), - ("subset", 190), - ("version", 55), - ("representation", 95), + ("asset", 200), + ("subset", 170), + ("version", 60), + ("representation", 135), ("local_site", 170), ("remote_site", 170), ("files_count", 50), ("files_size", 60), - ("priority", 50), + ("priority", 70), ("status", 110) ) @@ -183,8 +184,6 @@ class SyncRepresentationWidget(QtWidgets.QWidget): QtWidgets.QAbstractItemView.SelectRows) self.table_view.horizontalHeader().setSortIndicator( -1, Qt.AscendingOrder) - self.table_view.setSortingEnabled(True) - self.table_view.horizontalHeader().setSortIndicatorShown(True) self.table_view.setAlternatingRowColors(True) self.table_view.verticalHeader().hide() @@ -196,10 +195,6 @@ class SyncRepresentationWidget(QtWidgets.QWidget): delegate = ImageDelegate(self) self.table_view.setItemDelegateForColumn(column, delegate) - for column_name, width in self.default_widths: - idx = model.get_header_index(column_name) - self.table_view.setColumnWidth(idx, width) - layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.addLayout(top_bar_layout) @@ -213,23 +208,26 @@ class SyncRepresentationWidget(QtWidgets.QWidget): model.refresh_started.connect(self._save_scrollbar) model.refresh_finished.connect(self._set_scrollbar) - self.table_view.model().modelReset.connect(self._set_selection) + model.modelReset.connect(self._set_selection) + + self.model = model self.selection_model = self.table_view.selectionModel() self.selection_model.selectionChanged.connect(self._selection_changed) - self.checked_values = {} + self.horizontal_header = HorizontalHeader(self) + self.table_view.setHorizontalHeader(self.horizontal_header) + # self.table_view.setSortingEnabled(True) + # self.table_view.horizontalHeader().setSortIndicatorShown(True) - self.horizontal_header = self.table_view.horizontalHeader() - self.horizontal_header.setContextMenuPolicy( - QtCore.Qt.CustomContextMenu) - self.horizontal_header.customContextMenuRequested.connect( - self._on_section_clicked) + for column_name, width in self.default_widths: + idx = model.get_header_index(column_name) + self.table_view.setColumnWidth(idx, width) def _selection_changed(self, _new_selection): index = self.selection_model.currentIndex() self._selected_id = \ - self.table_view.model().data(index, Qt.UserRole) + self.model.data(index, Qt.UserRole) def _set_selection(self): """ @@ -238,7 +236,7 @@ class SyncRepresentationWidget(QtWidgets.QWidget): Keep selection during model refresh. """ if self._selected_id: - index = self.table_view.model().get_index(self._selected_id) + index = self.model.get_index(self._selected_id) if index and index.isValid(): mode = QtCore.QItemSelectionModel.Select | \ QtCore.QItemSelectionModel.Rows @@ -250,141 +248,11 @@ class SyncRepresentationWidget(QtWidgets.QWidget): """ Opens representation dialog with all files after doubleclick """ - _id = self.table_view.model().data(index, Qt.UserRole) + _id = self.model.data(index, Qt.UserRole) detail_window = SyncServerDetailWindow( - self.sync_server, _id, self.table_view.model().project) + self.sync_server, _id, self.model.project) detail_window.exec() - def _on_section_clicked(self, point): - - logical_index = self.horizontal_header.logicalIndexAt(point) - - model = self.table_view.model() - column_name = model.headerData(logical_index, - Qt.Horizontal, lib.HeaderNameRole) - items_dict = model.get_column_filter_values(logical_index) - - if not items_dict: - return - - menu = QtWidgets.QMenu(self) - - # text filtering only if labels same as values, not if codes are used - if list(items_dict.keys())[0] == list(items_dict.values())[0]: - self.line_edit = QtWidgets.QLineEdit(self) - self.line_edit.setPlaceholderText("Type and enter...") - action_le = QtWidgets.QWidgetAction(menu) - action_le.setDefaultWidget(self.line_edit) - self.line_edit.returnPressed.connect( - partial(self._apply_text_filter, column_name, items_dict)) - menu.addAction(action_le) - menu.addSeparator() - - action_all = QtWidgets.QAction("All", self) - state_checked = 2 - # action_all.triggered.connect(partial(self._apply_filter, column_name, - # items_dict, state_checked)) - action_all.triggered.connect(partial(self._reset_filter, column_name)) - menu.addAction(action_all) - - action_none = QtWidgets.QAction("Unselect all", self) - state_unchecked = 0 - action_none.triggered.connect(partial(self._apply_filter, column_name, - items_dict, state_unchecked)) - menu.addAction(action_none) - menu.addSeparator() - - # nothing explicitly >> ALL implicitly >> first time - if self.checked_values.get(column_name) is None: - checked_keys = items_dict.keys() - else: - checked_keys = self.checked_values[column_name] - - for value, label in items_dict.items(): - checkbox = QtWidgets.QCheckBox(str(label), menu) - if value in checked_keys: - checkbox.setChecked(True) - - action = QtWidgets.QWidgetAction(menu) - action.setDefaultWidget(checkbox) - - checkbox.stateChanged.connect(partial(self._apply_filter, - column_name, {value: label})) - menu.addAction(action) - - self.menu = menu - self.menu_items_dict = items_dict # all available items - self.menu.exec_(QtGui.QCursor.pos()) - - def _reset_filter(self, column_name): - """ - Remove whole column from filter >> not in $match at all (faster) - """ - if self.checked_values.get(column_name) is not None: - self.checked_values.pop(column_name) - self._refresh_model_and_menu(column_name, True, True) - - def _apply_filter(self, column_name, values, state): - """ - Sets 'values' to specific 'state' (checked/unchecked), - sends to model. - """ - self._update_checked_values(column_name, values, state) - self._refresh_model_and_menu(column_name, True, False) - - def _apply_text_filter(self, column_name, items): - """ - Resets all checkboxes, prefers inserted text. - """ - self._update_checked_values(column_name, items, 0) # reset other - text_item = {self.line_edit.text(): self.line_edit.text()} - self._update_checked_values(column_name, text_item, 2) - self._refresh_model_and_menu(column_name, True, True) - - def _refresh_model_and_menu(self, column_name, model=True, menu=True): - """ - Refresh model and its content and possibly menu for big changes. - """ - if model: - self.table_view.model().set_column_filtering(self.checked_values) - self.table_view.model().refresh() - if menu: - self._menu_refresh(column_name) - - def _menu_refresh(self, column_name): - """ - Reset boxes after big change - word filtering or reset - """ - for action in self.menu.actions(): - if not isinstance(action, QtWidgets.QWidgetAction): - continue - - widget = action.defaultWidget() - if not isinstance(widget, QtWidgets.QCheckBox): - continue - - if not self.checked_values.get(column_name) or \ - widget.text() in self.checked_values[column_name].values(): - widget.setChecked(True) - else: - widget.setChecked(False) - - def _update_checked_values(self, column_name, values, state): - """ - Modify dictionary of set values in columns for filtering. - - Modifies 'self.checked_values' - """ - checked = self.checked_values.get(column_name, self.menu_items_dict) - set_items = dict(values.items()) # prevent dictionary change during iter - for value, label in set_items.items(): - if state == 2: # checked - checked[value] = label - elif state == 0 and checked.get(value): - checked.pop(value) - - self.checked_values[column_name] = checked - def _on_context_menu(self, point): """ Shows menu with loader actions on Right-click. @@ -393,7 +261,7 @@ class SyncRepresentationWidget(QtWidgets.QWidget): if not point_index.isValid(): return - self.item = self.table_view.model()._data[point_index.row()] + self.item = self.model._data[point_index.row()] self.representation_id = self.item._id log.debug("menu representation _id:: {}". format(self.representation_id)) @@ -410,7 +278,7 @@ class SyncRepresentationWidget(QtWidgets.QWidget): for site, progress in {local_site: local_progress, remote_site: remote_progress}.items(): - project = self.table_view.model().project + project = self.model.project provider = self.sync_server.get_provider_for_site(project, site) if provider == 'local_drive': @@ -476,10 +344,10 @@ class SyncRepresentationWidget(QtWidgets.QWidget): if to_run: to_run(**to_run_kwargs) - self.table_view.model().refresh() + self.model.refresh() def _pause(self): - self.sync_server.pause_representation(self.table_view.model().project, + self.sync_server.pause_representation(self.model.project, self.representation_id, self.site_name) self.site_name = None @@ -487,7 +355,7 @@ class SyncRepresentationWidget(QtWidgets.QWidget): def _unpause(self): self.sync_server.unpause_representation( - self.table_view.model().project, + self.model.project, self.representation_id, self.site_name) self.site_name = None @@ -497,7 +365,7 @@ class SyncRepresentationWidget(QtWidgets.QWidget): # temporary here for testing, will be removed TODO def _add_site(self): log.info(self.representation_id) - project_name = self.table_view.model().project + project_name = self.model.project local_site_name = get_local_site_id() try: self.sync_server.add_site( @@ -525,15 +393,15 @@ class SyncRepresentationWidget(QtWidgets.QWidget): try: local_site = get_local_site_id() self.sync_server.remove_site( - self.table_view.model().project, + self.model.project, self.representation_id, local_site, True) self.message_generated.emit("Site {} removed".format(local_site)) except ValueError as exp: self.message_generated.emit("Error {}".format(str(exp))) - self.table_view.model().refresh( - load_records=self.table_view.model()._rec_loaded) + self.model.refresh( + load_records=self.model._rec_loaded) def _reset_local_site(self): """ @@ -541,11 +409,11 @@ class SyncRepresentationWidget(QtWidgets.QWidget): redo of upload/download """ self.sync_server.reset_provider_for_file( - self.table_view.model().project, + self.model.project, self.representation_id, 'local') - self.table_view.model().refresh( - load_records=self.table_view.model()._rec_loaded) + self.model.refresh( + load_records=self.model._rec_loaded) def _reset_remote_site(self): """ @@ -553,18 +421,18 @@ class SyncRepresentationWidget(QtWidgets.QWidget): redo of upload/download """ self.sync_server.reset_provider_for_file( - self.table_view.model().project, + self.model.project, self.representation_id, 'remote') - self.table_view.model().refresh( - load_records=self.table_view.model()._rec_loaded) + self.model.refresh( + load_records=self.model._rec_loaded) def _open_in_explorer(self, site): if not self.item: return fpath = self.item.path - project = self.table_view.model().project + project = self.model.project fpath = self.sync_server.get_local_file_path(project, site, fpath) @@ -647,11 +515,11 @@ class SyncRepresentationDetailWidget(QtWidgets.QWidget): self.table_view.setAlternatingRowColors(True) self.table_view.verticalHeader().hide() - column = self.table_view.model().get_header_index("local_site") + column = model.get_header_index("local_site") delegate = ImageDelegate(self) self.table_view.setItemDelegateForColumn(column, delegate) - column = self.table_view.model().get_header_index("remote_site") + column = model.get_header_index("remote_site") delegate = ImageDelegate(self) self.table_view.setItemDelegateForColumn(column, delegate) @@ -671,14 +539,15 @@ class SyncRepresentationDetailWidget(QtWidgets.QWidget): model.refresh_started.connect(self._save_scrollbar) model.refresh_finished.connect(self._set_scrollbar) - self.table_view.model().modelReset.connect(self._set_selection) + model.modelReset.connect(self._set_selection) + self.model = model self.selection_model = self.table_view.selectionModel() self.selection_model.selectionChanged.connect(self._selection_changed) def _selection_changed(self): index = self.selection_model.currentIndex() - self._selected_id = self.table_view.model().data(index, Qt.UserRole) + self._selected_id = self.model.data(index, Qt.UserRole) def _set_selection(self): """ @@ -687,7 +556,7 @@ class SyncRepresentationDetailWidget(QtWidgets.QWidget): Keep selection during model refresh. """ if self._selected_id: - index = self.table_view.model().get_index(self._selected_id) + index = self.model.get_index(self._selected_id) if index and index.isValid(): mode = QtCore.QItemSelectionModel.Select | \ QtCore.QItemSelectionModel.Rows @@ -715,7 +584,7 @@ class SyncRepresentationDetailWidget(QtWidgets.QWidget): if not point_index.isValid(): return - self.item = self.table_view.model()._data[point_index.row()] + self.item = self.model._data[point_index.row()] menu = QtWidgets.QMenu() #menu.setStyleSheet(style.load_stylesheet()) @@ -729,7 +598,7 @@ class SyncRepresentationDetailWidget(QtWidgets.QWidget): for site, progress in {local_site: local_progress, remote_site: remote_progress}.items(): - project = self.table_view.model().project + project = self.model.project provider = self.sync_server.get_provider_for_site(project, site) if provider == 'local_drive': @@ -776,12 +645,12 @@ class SyncRepresentationDetailWidget(QtWidgets.QWidget): redo of upload/download """ self.sync_server.reset_provider_for_file( - self.table_view.model().project, + self.model.project, self.representation_id, 'local', self.item._id) - self.table_view.model().refresh( - load_records=self.table_view.model()._rec_loaded) + self.model.refresh( + load_records=self.model._rec_loaded) def _reset_remote_site(self): """ @@ -789,12 +658,12 @@ class SyncRepresentationDetailWidget(QtWidgets.QWidget): redo of upload/download """ self.sync_server.reset_provider_for_file( - self.table_view.model().project, + self.model.project, self.representation_id, 'remote', self.item._id) - self.table_view.model().refresh( - load_records=self.table_view.model()._rec_loaded) + self.model.refresh( + load_records=self.model._rec_loaded) def _open_in_explorer(self, site): if not self.item: @@ -957,3 +826,277 @@ class SyncRepresentationErrorWindow(QtWidgets.QDialog): self.setLayout(body_layout) self.setWindowTitle("Sync Representation Error Detail") + + +class HorizontalHeader(QtWidgets.QHeaderView): + + def __init__(self, parent=None): + super(HorizontalHeader, self).__init__(QtCore.Qt.Horizontal, parent) + self._parent = parent + self.checked_values = {} + + self.setSectionsMovable(True) + self.setSectionsClickable(True) + self.setHighlightSections(True) + + self.menu_items_dict = {} + self.menu = None + self.header_cells = [] + self.filter_buttons = {} + + self.init_layout() + + self.filter_icon = qtawesome.icon("fa.filter", color="gray") + self.filter_set_icon = qtawesome.icon("fa.filter", color="white") + + self._resetting = False + + self.sectionResized.connect(self.handleSectionResized) + self.sectionMoved.connect(self.handleSectionMoved) + #self.sectionPressed.connect(self.model.sort) + + + @property + def model(self): + """Keep model synchronized with parent widget""" + return self._parent.model + + def init_layout(self): + for i in range(self.count()): + cell_content = QtWidgets.QWidget(self) + column_name, column_label = self.model.get_column(i) + + layout = QtWidgets.QHBoxLayout() + layout.setContentsMargins(5, 5, 5, 0) + layout.setAlignment(Qt.AlignVCenter) + layout.addWidget(QtWidgets.QLabel(column_label)) + + filter_rec = self.model.get_filters().get(column_name) + if filter_rec: + icon = self.filter_icon + button = QtWidgets.QPushButton(icon, "") + layout.addWidget(button) + + # button.setMenu(menu) + button.setFixedSize(24, 24) + # button.setAlignment(Qt.AlignRight) + button.setStyleSheet("QPushButton::menu-indicator{width:0px;}" + "QPushButton{border: none}") + button.clicked.connect(partial(self._get_menu, + column_name, i)) + button.setFlat(True) + self.filter_buttons[column_name] = button + + cell_content.setLayout(layout) + + self.header_cells.append(cell_content) + + def showEvent(self, event): + if not self.header_cells: + self.init_layout() + + for i in range(len(self.header_cells)): + cell_content = self.header_cells[i] + cell_content.setGeometry(self.sectionViewportPosition(i), 0, + self.sectionSize(i)-1, self.height()) + + cell_content.show() + + if len(self.model.get_filters()) > self.count(): + for i in range(self.count(), len(self.header_cells)): + self.header_cells[i].deleteLater() + + super(HorizontalHeader, self).showEvent(event) + + def _set_filter_icon(self, column_name): + button = self.filter_buttons.get(column_name) + if button: + if self.checked_values.get(column_name): + button.setIcon(self.filter_set_icon) + else: + button.setIcon(self.filter_icon) + + def _reset_filter(self, column_name): + """ + Remove whole column from filter >> not in $match at all (faster) + """ + self._resetting = True # mark changes to consume them + if self.checked_values.get(column_name) is not None: + self.checked_values.pop(column_name) + self._set_filter_icon(column_name) + self._filter_and_refresh_model_and_menu(column_name, True, True) + self._resetting = False + + def _apply_filter(self, column_name, values, state): + """ + Sets 'values' to specific 'state' (checked/unchecked), + sends to model. + """ + if self._resetting: # event triggered by _resetting, skip it + return + + self._update_checked_values(column_name, values, state) + self._set_filter_icon(column_name) + self._filter_and_refresh_model_and_menu(column_name, True, False) + + def _apply_text_filter(self, column_name, items): + """ + Resets all checkboxes, prefers inserted text. + """ + self._update_checked_values(column_name, items, 0) # reset other + if self.checked_values.get(column_name) is not None or \ + self.line_edit.text() == '': + self.checked_values.pop(column_name) # reset during typing + + text_item = {self.line_edit.text(): self.line_edit.text()} + if self.line_edit.text(): + self._update_checked_values(column_name, text_item, 2) + self._set_filter_icon(column_name) + self._filter_and_refresh_model_and_menu(column_name, True, True) + + def _filter_and_refresh_model_and_menu(self, column_name, + model=True, menu=True): + """ + Refresh model and its content and possibly menu for big changes. + """ + if model: + self.model.set_column_filtering(self.checked_values) + self.model.refresh() + if menu: + self._menu_refresh(column_name) + + def _get_menu(self, column_name, index): + """Prepares content of menu for 'column_name'""" + menu = QtWidgets.QMenu(self) + filter_rec = self.model.get_filters()[column_name] + self.menu_items_dict[column_name] = filter_rec.values() + self.line_edit = None + + # text filtering only if labels same as values, not if codes are used + if 'text' in filter_rec.search_variants(): + self.line_edit = QtWidgets.QLineEdit(self) + self.line_edit.setSizePolicy( + QtWidgets.QSizePolicy.Maximum, + QtWidgets.QSizePolicy.Maximum) + txt = "Type..." + if self.checked_values.get(column_name): + txt = list(self.checked_values.get(column_name).keys())[0] + self.line_edit.setPlaceholderText(txt) + + action_le = QtWidgets.QWidgetAction(menu) + action_le.setDefaultWidget(self.line_edit) + self.line_edit.textChanged.connect( + partial(self._apply_text_filter, column_name, + filter_rec.values())) + menu.addAction(action_le) + menu.addSeparator() + + if 'checkbox' in filter_rec.search_variants(): + action_all = QtWidgets.QAction("All", self) + action_all.triggered.connect(partial(self._reset_filter, + column_name)) + menu.addAction(action_all) + + action_none = QtWidgets.QAction("Unselect all", self) + state_unchecked = 0 + action_none.triggered.connect(partial(self._apply_filter, + column_name, + filter_rec.values(), + state_unchecked)) + menu.addAction(action_none) + menu.addSeparator() + + # nothing explicitly >> ALL implicitly >> first time + if self.checked_values.get(column_name) is None: + checked_keys = self.menu_items_dict[column_name].keys() + else: + checked_keys = self.checked_values[column_name] + + for value, label in self.menu_items_dict[column_name].items(): + checkbox = QtWidgets.QCheckBox(str(label), menu) + + # temp + checkbox.setStyleSheet("QCheckBox{spacing: 5px;" + "padding:5px 5px 5px 5px;}") + if value in checked_keys: + checkbox.setChecked(True) + + action = QtWidgets.QWidgetAction(menu) + action.setDefaultWidget(checkbox) + + checkbox.stateChanged.connect(partial(self._apply_filter, + column_name, {value: label})) + menu.addAction(action) + + self.menu = menu + + self._show_menu(index, menu) + + def _show_menu(self, index, menu): + """Shows 'menu' under header column of 'index'""" + global_pos_point = self.mapToGlobal( + QtCore.QPoint(self.sectionViewportPosition(index), 0)) + menu.setMinimumWidth(self.sectionSize(index)) + menu.setMinimumHeight(self.height()) + menu.exec_(QtCore.QPoint(global_pos_point.x(), + global_pos_point.y() + self.height())) + + def _menu_refresh(self, column_name): + """ + Reset boxes after big change - word filtering or reset + """ + for action in self.menu.actions(): + if not isinstance(action, QtWidgets.QWidgetAction): + continue + + widget = action.defaultWidget() + if not isinstance(widget, QtWidgets.QCheckBox): + continue + + if not self.checked_values.get(column_name) or \ + widget.text() in self.checked_values[column_name].values(): + widget.setChecked(True) + else: + widget.setChecked(False) + + def _update_checked_values(self, column_name, values, state): + """ + Modify dictionary of set values in columns for filtering. + + Modifies 'self.checked_values' + """ + checked = self.checked_values.get(column_name, + dict(self.menu_items_dict[column_name])) + set_items = dict(values.items()) # prevent dict change during loop + for value, label in set_items.items(): + if state == 2 and label: # checked + checked[value] = label + elif state == 0 and checked.get(value): + checked.pop(value) + + self.checked_values[column_name] = checked + + def handleSectionResized(self, i): + if not self.header_cells: + self.init_layout() + for i in range(self.count()): + j = self.visualIndex(i) + logical = self.logicalIndex(j) + self.header_cells[i].setGeometry( + self.sectionViewportPosition(logical), 0, + self.sectionSize(logical) - 1, self.height()) + + def handleSectionMoved(self, i, oldVisualIndex, newVisualIndex): + if not self.header_cells: + self.init_layout() + for i in range(min(oldVisualIndex, newVisualIndex), self.count()): + logical = self.logicalIndex(i) + self.header_cells[i].setGeometry( + self.ectionViewportPosition(logical), 0, + self.sectionSize(logical) - 2, self.height()) + + def fixComboPositions(self): + for i in range(self.count()): + self.header_cells[i].setGeometry( + self.sectionViewportPosition(i), 0, + self.sectionSize(i) - 2, self.height()) From ace609fb4164d1e9dc812a90d90e61aa72f23d42 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Tue, 20 Apr 2021 20:47:44 +0200 Subject: [PATCH 066/180] fix hound comments --- setup.py | 2 +- tools/build_dependencies.py | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 309f83c05c..c096befa34 100644 --- a/setup.py +++ b/setup.py @@ -90,7 +90,7 @@ bdist_mac_options = dict( executables = [ Executable("start.py", base=base, - target_name="openpype_gui", icon=icon_path.as_posix()), + target_name="openpype_gui", icon=icon_path.as_posix()), Executable("start.py", base=None, target_name="openpype_console", icon=icon_path.as_posix()) ] diff --git a/tools/build_dependencies.py b/tools/build_dependencies.py index 6c87a4d689..570c61cfdb 100644 --- a/tools/build_dependencies.py +++ b/tools/build_dependencies.py @@ -78,9 +78,15 @@ build_dir = "exe.{}-{}".format(get_platform(), sys.version[0:3]) # create full path if platform.system().lower() == "darwin": - build_dir = Path(os.path.dirname(__file__)).parent / "build" / "OpenPype.app" / "Contents" / "MacOS" + build_dir = os.path.join(Path(os.path.dirname(__file__)).parent, + "build", + "OpenPype.app", + "Contents", + "MacOS") else: - build_dir = Path(os.path.dirname(__file__)).parent / "build" / build_dir + build_dir = os.path.join(Path(os.path.dirname(__file__)).parent, + "build", + build_dir) _print(f"Using build at {build_dir}", 2) if not build_dir.exists(): From 92e5d10fdc05c79a5cdc501fef79d8e0b007f23d Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Tue, 20 Apr 2021 21:11:24 +0200 Subject: [PATCH 067/180] fix conversion of path to string --- tools/build_dependencies.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/tools/build_dependencies.py b/tools/build_dependencies.py index 570c61cfdb..fb52e2b5fd 100644 --- a/tools/build_dependencies.py +++ b/tools/build_dependencies.py @@ -78,15 +78,13 @@ build_dir = "exe.{}-{}".format(get_platform(), sys.version[0:3]) # create full path if platform.system().lower() == "darwin": - build_dir = os.path.join(Path(os.path.dirname(__file__)).parent, - "build", - "OpenPype.app", - "Contents", - "MacOS") + build_dir = Path(os.path.dirname(__file__)).parent.joinpath( + "build", + "OpenPype.app", + "Contents", + "MacOS") else: - build_dir = os.path.join(Path(os.path.dirname(__file__)).parent, - "build", - build_dir) + build_dir = Path(os.path.dirname(__file__)).parent / "build" / build_dir _print(f"Using build at {build_dir}", 2) if not build_dir.exists(): From bb427ac15d2d852528dd52e78a44dc84029156f1 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Tue, 20 Apr 2021 21:49:26 +0200 Subject: [PATCH 068/180] remove openpype from self dependency in poetry --- pyproject.toml | 2 +- tools/create_env.ps1 | 2 +- tools/create_env.sh | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 12b9c4446d..88c977cd99 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "OpenPype" version = "3.0.0-beta2" -description = "Multi-platform open-source pipeline built around the Avalon platform, expanding it with extra features and integrations." +description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" homepage = "https://openpype.io" diff --git a/tools/create_env.ps1 b/tools/create_env.ps1 index 44e1799be8..e72e98e04b 100644 --- a/tools/create_env.ps1 +++ b/tools/create_env.ps1 @@ -133,7 +133,7 @@ if (-not (Test-Path -PathType Leaf -Path "$($openpype_root)\poetry.lock")) { Write-Host ">>> " -NoNewline -ForegroundColor green Write-Host "Installing virtual environment from lock." } -& poetry install $poetry_verbosity +& poetry install --no-root $poetry_verbosity if ($LASTEXITCODE -ne 0) { Write-Host "!!! " -ForegroundColor yellow -NoNewline Write-Host "Poetry command failed." diff --git a/tools/create_env.sh b/tools/create_env.sh index 7bdb8503fd..04414ddea5 100755 --- a/tools/create_env.sh +++ b/tools/create_env.sh @@ -160,7 +160,7 @@ main () { echo -e "${BIGreen}>>>${RST} Installing dependencies ..." fi - poetry install $poetry_verbosity || { echo -e "${BIRed}!!!${RST} Poetry environment installation failed"; return; } + poetry install --no-root $poetry_verbosity || { echo -e "${BIRed}!!!${RST} Poetry environment installation failed"; return; } echo -e "${BIGreen}>>>${RST} Cleaning cache files ..." clean_pyc From a8319ad336d6013d011cfbfbdf2932e4f267918e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 21 Apr 2021 10:25:57 +0200 Subject: [PATCH 069/180] local settings can store open pype username --- .../settings/local_settings/general_widget.py | 22 ++++++++++++++----- .../tools/settings/local_settings/window.py | 13 +++++------ 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/openpype/tools/settings/local_settings/general_widget.py b/openpype/tools/settings/local_settings/general_widget.py index e820d8ab8b..f2147e626a 100644 --- a/openpype/tools/settings/local_settings/general_widget.py +++ b/openpype/tools/settings/local_settings/general_widget.py @@ -5,16 +5,28 @@ class LocalGeneralWidgets(QtWidgets.QWidget): def __init__(self, parent): super(LocalGeneralWidgets, self).__init__(parent) + username_input = QtWidgets.QLineEdit(self) + + layout = QtWidgets.QFormLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + + layout.addRow("OpenPype Username", username_input) + + self.username_input = username_input def update_local_settings(self, value): - return - - # RETURNING EARLY TO HIDE WIDGET WITHOUT CONTENT + username = "" + if value: + username = value.get("username", username) + self.username_input.setText(username) def settings_value(self): # Add changed # If these have changed then output = {} - # TEMPORARILY EMPTY AS THERE IS NOTHING TO PUT HERE - + username = self.username_input.text() + if username: + output["username"] = username + # Do not return output yet since we don't have mechanism to save or + # load these data through api calls return output diff --git a/openpype/tools/settings/local_settings/window.py b/openpype/tools/settings/local_settings/window.py index a12a2289b5..b6ca56d348 100644 --- a/openpype/tools/settings/local_settings/window.py +++ b/openpype/tools/settings/local_settings/window.py @@ -80,7 +80,6 @@ class LocalSettingsWidget(QtWidgets.QWidget): general_widget = LocalGeneralWidgets(general_content) general_layout.addWidget(general_widget) - general_expand_widget.hide() self.main_layout.addWidget(general_expand_widget) @@ -127,9 +126,9 @@ class LocalSettingsWidget(QtWidgets.QWidget): self.system_settings.reset() self.project_settings.reset() - # self.general_widget.update_local_settings( - # value.get(LOCAL_GENERAL_KEY) - # ) + self.general_widget.update_local_settings( + value.get(LOCAL_GENERAL_KEY) + ) self.app_widget.update_local_settings( value.get(LOCAL_APPS_KEY) ) @@ -139,9 +138,9 @@ class LocalSettingsWidget(QtWidgets.QWidget): def settings_value(self): output = {} - # general_value = self.general_widget.settings_value() - # if general_value: - # output[LOCAL_GENERAL_KEY] = general_value + general_value = self.general_widget.settings_value() + if general_value: + output[LOCAL_GENERAL_KEY] = general_value app_value = self.app_widget.settings_value() if app_value: From 7aa222327bffcfca727ed201c4ece88cbec6d103 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 21 Apr 2021 10:26:18 +0200 Subject: [PATCH 070/180] get_local_settings is part of `openpype.settings` --- openpype/settings/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/settings/__init__.py b/openpype/settings/__init__.py index b4187829fc..c8dd64a41c 100644 --- a/openpype/settings/__init__.py +++ b/openpype/settings/__init__.py @@ -3,7 +3,8 @@ from .lib import ( get_project_settings, get_current_project_settings, get_anatomy_settings, - get_environments + get_environments, + get_local_settings ) from .entities import ( SystemSettings, @@ -17,6 +18,7 @@ __all__ = ( "get_current_project_settings", "get_anatomy_settings", "get_environments", + "get_local_settings", "SystemSettings", "ProjectSettings" From fee9e9bd563caf5d9e5e99ebde1e04b234efb53e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 21 Apr 2021 10:26:48 +0200 Subject: [PATCH 071/180] implemented function to get openpype username --- openpype/lib/local_settings.py | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/openpype/lib/local_settings.py b/openpype/lib/local_settings.py index 56bdd047c9..67845c77cf 100644 --- a/openpype/lib/local_settings.py +++ b/openpype/lib/local_settings.py @@ -1,9 +1,11 @@ # -*- coding: utf-8 -*- """Package to deal with saving and retrieving user specific settings.""" import os +import json +import getpass +import platform from datetime import datetime from abc import ABCMeta, abstractmethod -import json # TODO Use pype igniter logic instead of using duplicated code # disable lru cache in Python 2 @@ -24,11 +26,11 @@ try: except ImportError: import ConfigParser as configparser -import platform - import six import appdirs +from openpype.settings import get_local_settings + from .import validate_mongo_connection _PLACEHOLDER = object() @@ -538,3 +540,25 @@ def change_openpype_mongo_url(new_mongo_url): if existing_value is not None: registry.delete_item(key) registry.set_item(key, new_mongo_url) + + +def get_openpype_username(): + """OpenPype username used for templates and publishing. + + May be different than machine's username. + + Always returns "OPENPYPE_USERNAME" environment if is set then tries local + settings and last option is to use `getpass.getuser()` which returns + machine username. + """ + username = os.environ.get("OPENPYPE_USERNAME") + if not username: + local_settings = get_local_settings() + username = ( + local_settings + .get("general", {}) + .get("username") + ) + if not username: + username = getpass.getuser() + return username From 7d9e665e2d7c08f649c5c72eeef325f993e9c8f3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 21 Apr 2021 10:28:05 +0200 Subject: [PATCH 072/180] get_openpype_username replaced usage of OPENPYPE_USERNAME environment in code --- openpype/lib/__init__.py | 34 ++++++++++--------- openpype/lib/applications.py | 3 +- .../publish/collect_current_pype_user.py | 6 ++-- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/openpype/lib/__init__.py b/openpype/lib/__init__.py index f46c81bf7a..895d11601f 100644 --- a/openpype/lib/__init__.py +++ b/openpype/lib/__init__.py @@ -79,6 +79,16 @@ from .avalon_context import ( change_timer_to_current_context ) +from .local_settings import ( + IniSettingRegistry, + JSONSettingRegistry, + OpenPypeSecureRegistry, + OpenPypeSettingsRegistry, + get_local_site_id, + change_openpype_mongo_url, + get_openpype_username +) + from .applications import ( ApplicationLaunchFailed, ApplictionExecutableNotFound, @@ -112,15 +122,6 @@ from .plugin_tools import ( should_decompress ) -from .local_settings import ( - IniSettingRegistry, - JSONSettingRegistry, - OpenPypeSecureRegistry, - OpenPypeSettingsRegistry, - get_local_site_id, - change_openpype_mongo_url -) - from .path_tools import ( version_up, get_version_from_path, @@ -179,6 +180,14 @@ __all__ = [ "change_timer_to_current_context", + "IniSettingRegistry", + "JSONSettingRegistry", + "OpenPypeSecureRegistry", + "OpenPypeSettingsRegistry", + "get_local_site_id", + "change_openpype_mongo_url", + "get_openpype_username", + "ApplicationLaunchFailed", "ApplictionExecutableNotFound", "ApplicationNotFound", @@ -224,13 +233,6 @@ __all__ = [ "validate_mongo_connection", "OpenPypeMongoConnection", - "IniSettingRegistry", - "JSONSettingRegistry", - "OpenPypeSecureRegistry", - "OpenPypeSettingsRegistry", - "get_local_site_id", - "change_openpype_mongo_url", - "timeit", "is_overlapping_otio_ranges", diff --git a/openpype/lib/applications.py b/openpype/lib/applications.py index 51c646d494..a0b5569b02 100644 --- a/openpype/lib/applications.py +++ b/openpype/lib/applications.py @@ -25,6 +25,7 @@ from . import ( PypeLogger, Anatomy ) +from .local_settings import get_openpype_username from .avalon_context import ( get_workdir_data, get_workdir_with_workdir_data @@ -1224,7 +1225,7 @@ def _prepare_last_workfile(data, workdir): file_template = anatomy.templates["work"]["file"] workdir_data.update({ "version": 1, - "user": os.environ.get("OPENPYPE_USERNAME") or getpass.getuser(), + "user": get_openpype_username(), "ext": extensions[0] }) diff --git a/openpype/plugins/publish/collect_current_pype_user.py b/openpype/plugins/publish/collect_current_pype_user.py index de4e950d56..003c779836 100644 --- a/openpype/plugins/publish/collect_current_pype_user.py +++ b/openpype/plugins/publish/collect_current_pype_user.py @@ -1,6 +1,7 @@ import os import getpass import pyblish.api +from openpype.lib import get_openpype_username class CollectCurrentUserPype(pyblish.api.ContextPlugin): @@ -11,9 +12,6 @@ class CollectCurrentUserPype(pyblish.api.ContextPlugin): label = "Collect Pype User" def process(self, context): - user = os.getenv("OPENPYPE_USERNAME", "").strip() - if not user: - user = context.data.get("user", getpass.getuser()) - + user = get_openpype_username() context.data["user"] = user self.log.debug("Colected user \"{}\"".format(user)) From e5737db9032c5db26d08559186d090bbf31910b9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 21 Apr 2021 10:28:23 +0200 Subject: [PATCH 073/180] render jobs do not care about OPENPYPE_USERNAME --- .../deadline/plugins/publish/submit_aftereffects_deadline.py | 1 - .../modules/deadline/plugins/publish/submit_harmony_deadline.py | 1 - .../modules/deadline/plugins/publish/submit_maya_deadline.py | 1 - 3 files changed, 3 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py b/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py index 38a6b9b246..69159fda1a 100644 --- a/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py @@ -64,7 +64,6 @@ class AfterEffectsSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline "AVALON_ASSET", "AVALON_TASK", "AVALON_APP_NAME", - "OPENPYPE_USERNAME", "OPENPYPE_DEV", "OPENPYPE_LOG_NO_COLORS" ] diff --git a/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py b/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py index ba1ffdcf30..37041a84b1 100644 --- a/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py @@ -273,7 +273,6 @@ class HarmonySubmitDeadline( "AVALON_ASSET", "AVALON_TASK", "AVALON_APP_NAME", - "OPENPYPE_USERNAME", "OPENPYPE_DEV", "OPENPYPE_LOG_NO_COLORS" ] diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 3aea837bb1..0e92fb38bb 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -441,7 +441,6 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): "AVALON_ASSET", "AVALON_TASK", "AVALON_APP_NAME", - "OPENPYPE_USERNAME", "OPENPYPE_DEV", "OPENPYPE_LOG_NO_COLORS" ] From f593f3330bc3e4f5483169c982bf29ef2bd4c32f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 21 Apr 2021 10:38:08 +0200 Subject: [PATCH 074/180] added machine user as placeholder --- openpype/tools/settings/local_settings/general_widget.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/tools/settings/local_settings/general_widget.py b/openpype/tools/settings/local_settings/general_widget.py index f2147e626a..78bc53fdd2 100644 --- a/openpype/tools/settings/local_settings/general_widget.py +++ b/openpype/tools/settings/local_settings/general_widget.py @@ -1,3 +1,5 @@ +import getpass + from Qt import QtWidgets @@ -6,6 +8,7 @@ class LocalGeneralWidgets(QtWidgets.QWidget): super(LocalGeneralWidgets, self).__init__(parent) username_input = QtWidgets.QLineEdit(self) + username_input.setPlaceholderText(getpass.getuser()) layout = QtWidgets.QFormLayout(self) layout.setContentsMargins(0, 0, 0, 0) From fe921927930ed2ebc9fb64561cfa9550fe92a38e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 21 Apr 2021 10:39:31 +0200 Subject: [PATCH 075/180] add gamma as video filter in ffmpeg arguments --- openpype/settings/defaults/project_settings/global.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index ca1b258e72..f8d4cf78fa 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -26,11 +26,11 @@ "ftrackreview" ], "ffmpeg_args": { - "video_filters": [], - "audio_filters": [], - "input": [ - "-gamma 2.2" + "video_filters": [ + "eq=gamma=2.2" ], + "audio_filters": [], + "input": [], "output": [ "-pix_fmt yuv420p", "-crf 18", From cc3f483aec706303e68faaa4d7b28a22d8f04b2b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 21 Apr 2021 11:14:39 +0200 Subject: [PATCH 076/180] removed SaveSettingsValidation from settings init --- openpype/settings/__init__.py | 4 +--- openpype/settings/exceptions.py | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/openpype/settings/__init__.py b/openpype/settings/__init__.py index 3755fabeb2..78a287f07e 100644 --- a/openpype/settings/__init__.py +++ b/openpype/settings/__init__.py @@ -1,6 +1,5 @@ from .exceptions import ( - SaveWarning, - SaveSettingsValidation + SaveWarning ) from .lib import ( get_system_settings, @@ -17,7 +16,6 @@ from .entities import ( __all__ = ( "SaveWarning", - "SaveSettingsValidation", "get_system_settings", "get_project_settings", diff --git a/openpype/settings/exceptions.py b/openpype/settings/exceptions.py index 86f04c76b9..758a778794 100644 --- a/openpype/settings/exceptions.py +++ b/openpype/settings/exceptions.py @@ -7,5 +7,5 @@ class SaveWarning(SaveSettingsValidation): if isinstance(warnings, str): warnings = [warnings] self.warnings = warnings - msg = ", ".join(warnings) + msg = " | ".join(warnings) super(SaveWarning, self).__init__(msg) From 00c55d0990b25303932ef1453acb2f3435ae37e4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 21 Apr 2021 11:14:57 +0200 Subject: [PATCH 077/180] save warning are raised afterwards --- openpype/settings/lib.py | 42 +++++++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/openpype/settings/lib.py b/openpype/settings/lib.py index 31c7c902cb..dd3f79b5b3 100644 --- a/openpype/settings/lib.py +++ b/openpype/settings/lib.py @@ -5,7 +5,6 @@ import logging import platform import copy from .exceptions import ( - SaveSettingsValidation, SaveWarning ) from .constants import ( @@ -118,11 +117,18 @@ def save_studio_settings(data): changes = calculate_changes(old_data, new_data) modules_manager = ModulesManager(_system_settings=new_data) + + warnings = [] for module in modules_manager.get_enabled_modules(): if isinstance(module, ISettingsChangeListener): - module.on_system_settings_save(old_data, new_data, changes) + try: + module.on_system_settings_save(old_data, new_data, changes) + except SaveWarning as exc: + warnings.extend(exc.warnings) - return _SETTINGS_HANDLER.save_studio_settings(data) + _SETTINGS_HANDLER.save_studio_settings(data) + if warnings: + raise SaveWarning(warnings) @require_handler @@ -159,13 +165,20 @@ def save_project_settings(project_name, overrides): changes = calculate_changes(old_data, new_data) modules_manager = ModulesManager() + warnings = [] for module in modules_manager.get_enabled_modules(): if isinstance(module, ISettingsChangeListener): - module.on_project_settings_save( - old_data, new_data, project_name, changes - ) + try: + module.on_project_settings_save( + old_data, new_data, project_name, changes + ) + except SaveWarning as exc: + warnings.extend(exc.warnings) - return _SETTINGS_HANDLER.save_project_settings(project_name, overrides) + _SETTINGS_HANDLER.save_project_settings(project_name, overrides) + + if warnings: + raise SaveWarning(warnings) @require_handler @@ -202,13 +215,20 @@ def save_project_anatomy(project_name, anatomy_data): changes = calculate_changes(old_data, new_data) modules_manager = ModulesManager() + warnings = [] for module in modules_manager.get_enabled_modules(): if isinstance(module, ISettingsChangeListener): - module.on_project_anatomy_save( - old_data, new_data, changes, project_name - ) + try: + module.on_project_anatomy_save( + old_data, new_data, changes, project_name + ) + except SaveWarning as exc: + warnings.extend(exc.warnings) - return _SETTINGS_HANDLER.save_project_anatomy(project_name, anatomy_data) + _SETTINGS_HANDLER.save_project_anatomy(project_name, anatomy_data) + + if warnings: + raise SaveWarning(warnings) @require_handler From 32336eeb63392c397661d2837d62ff48c8819882 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 21 Apr 2021 11:27:01 +0200 Subject: [PATCH 078/180] added use_python_2 to unreal's defaults --- openpype/settings/defaults/system_settings/applications.json | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/settings/defaults/system_settings/applications.json b/openpype/settings/defaults/system_settings/applications.json index 2355f39aa1..56d63ecf09 100644 --- a/openpype/settings/defaults/system_settings/applications.json +++ b/openpype/settings/defaults/system_settings/applications.json @@ -1165,6 +1165,7 @@ }, "variants": { "4-26": { + "use_python_2": false, "executables": { "windows": [], "darwin": [], From b661e5b1139761badf1eb1d2b3185d29e9a342c0 Mon Sep 17 00:00:00 2001 From: jezscha Date: Wed, 21 Apr 2021 09:37:07 +0000 Subject: [PATCH 079/180] Create draft PR for #1375 From d8f84a750d1b65676b228c5db2bbb44d16ae4705 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 21 Apr 2021 11:41:08 +0200 Subject: [PATCH 080/180] ftrack module raise SaveWarning exception if crashes during changing values --- openpype/modules/ftrack/ftrack_module.py | 62 +++++++++++++++++++++--- 1 file changed, 55 insertions(+), 7 deletions(-) diff --git a/openpype/modules/ftrack/ftrack_module.py b/openpype/modules/ftrack/ftrack_module.py index 28281786b3..46403bbc26 100644 --- a/openpype/modules/ftrack/ftrack_module.py +++ b/openpype/modules/ftrack/ftrack_module.py @@ -230,30 +230,45 @@ class FtrackModule( import ftrack_api from openpype.modules.ftrack.lib import get_openpype_attr - session = self.create_ftrack_session() + try: + session = self.create_ftrack_session() + except Exception: + self.log.warning("Couldn't create ftrack session.", exc_info=True) + raise SaveWarning(( + "Couldn't create Ftrack session." + " You may need to update applications" + " and tools in Ftrack custom attributes using defined action." + )) + project_entity = session.query( "Project where full_name is \"{}\"".format(project_name) ).first() if not project_entity: - self.log.warning(( - "Ftrack project with names \"{}\" was not found." + msg = ( + "Ftrack project with names \"{}\" was not found in Ftrack." " Skipping settings attributes change callback." - )) - return + ).format(project_name) + self.log.warning(msg) + raise SaveWarning(msg) project_id = project_entity["id"] cust_attr, hier_attr = get_openpype_attr(session) cust_attr_by_key = {attr["key"]: attr for attr in cust_attr} hier_attrs_by_key = {attr["key"]: attr for attr in hier_attr} + + failed = {} + missing = {} for key, value in attributes_changes.items(): configuration = hier_attrs_by_key.get(key) if not configuration: configuration = cust_attr_by_key.get(key) if not configuration: + missing[key] = value continue + # TODO add add permissions check # TODO add value validations # - value type and list items entity_key = collections.OrderedDict() @@ -267,10 +282,43 @@ class FtrackModule( "value", ftrack_api.symbol.NOT_SET, value - ) ) - session.commit() + try: + session.commit() + self.log.debug( + "Changed project custom attribute \"{}\" to \"{}\"".format( + key, value + ) + ) + except Exception: + self.log.warning( + "Failed to set \"{}\" to \"{}\"".format(key, value), + exc_info=True + ) + session.rollback() + failed[key] = value + + if not failed and not missing: + return + + error_msg = ( + "Values were not updated on Ftrack which may cause issues." + ) + if missing: + error_msg += " Missing Custom attributes on Ftrack: {}.".format( + ", ".join([ + '"{}"'.format(key) + for key in missing.keys() + ]) + ) + if failed: + joined_failed = ", ".join([ + '"{}": "{}"'.format(key, value) + for key, value in failed.items() + ]) + error_msg += " Failed to set: {}".format(joined_failed) + raise SaveWarning(error_msg) def create_ftrack_session(self, **session_kwargs): import ftrack_api From c5ac2f1f3c361ee44ca5063abc35259a910b3cbc Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 21 Apr 2021 11:45:27 +0200 Subject: [PATCH 081/180] AE fix max instead of min 0 is minimum value, not preferred --- openpype/hosts/aftereffects/api/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/aftereffects/api/__init__.py b/openpype/hosts/aftereffects/api/__init__.py index 7ad10cde25..99636e8dda 100644 --- a/openpype/hosts/aftereffects/api/__init__.py +++ b/openpype/hosts/aftereffects/api/__init__.py @@ -98,7 +98,7 @@ def get_asset_settings(): handle_end = asset_data.get("handleEnd") resolution_width = asset_data.get("resolutionWidth") resolution_height = asset_data.get("resolutionHeight") - duration = frame_end + handle_end - min(frame_start - handle_start, 0) + duration = frame_end + handle_end - max(frame_start - handle_start, 0) entity_type = asset_data.get("entityType") scene_data = { From ad64ef3ff95e089c9526aacdeb2f4d86437627e5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 21 Apr 2021 11:53:28 +0200 Subject: [PATCH 082/180] added some docstrings --- openpype/settings/lib.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/openpype/settings/lib.py b/openpype/settings/lib.py index dd3f79b5b3..c4ed9453f1 100644 --- a/openpype/settings/lib.py +++ b/openpype/settings/lib.py @@ -104,8 +104,14 @@ def save_studio_settings(data): For saving of data cares registered Settings handler. + Warning messages are not logged as module raising them should log it within + it's logger. + Args: data(dict): Overrides data with metadata defying studio overrides. + + Raises: + SaveWarning: If any module raises the exception. """ # Notify Pype modules from openpype.modules import ModulesManager, ISettingsChangeListener @@ -140,10 +146,16 @@ def save_project_settings(project_name, overrides): For saving of data cares registered Settings handler. + Warning messages are not logged as module raising them should log it within + it's logger. + Args: project_name (str): Project name for which overrides are passed. Default project's value is None. overrides(dict): Overrides data with metadata defying studio overrides. + + Raises: + SaveWarning: If any module raises the exception. """ # Notify Pype modules from openpype.modules import ModulesManager, ISettingsChangeListener @@ -190,10 +202,16 @@ def save_project_anatomy(project_name, anatomy_data): For saving of data cares registered Settings handler. + Warning messages are not logged as module raising them should log it within + it's logger. + Args: project_name (str): Project name for which overrides are passed. Default project's value is None. overrides(dict): Overrides data with metadata defying studio overrides. + + Raises: + SaveWarning: If any module raises the exception. """ # Notify Pype modules from openpype.modules import ModulesManager, ISettingsChangeListener From 55dd8f7005785fef10ba7585fa30e2a30af15aed Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 21 Apr 2021 11:53:54 +0200 Subject: [PATCH 083/180] settings gui catch SaveWarning --- .../tools/settings/settings/widgets/categories.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/openpype/tools/settings/settings/widgets/categories.py b/openpype/tools/settings/settings/widgets/categories.py index 9d286485a3..d782aaf54f 100644 --- a/openpype/tools/settings/settings/widgets/categories.py +++ b/openpype/tools/settings/settings/widgets/categories.py @@ -27,7 +27,7 @@ from openpype.settings.entities import ( SchemaError ) -from openpype.settings.lib import get_system_settings +from openpype.settings import SaveWarning from .widgets import ProjectListWidget from . import lib @@ -272,6 +272,15 @@ class SettingsCategoryWidget(QtWidgets.QWidget): # not required. self.reset() + except SaveWarning as exc: + msg = "Settings were saved but few issues happened.\n\n" + msg += "\n".join(exc.warnings) + + dialog = QtWidgets.QMessageBox(self) + dialog.setText(msg) + dialog.setIcon(QtWidgets.QMessageBox.Warning) + dialog.exec_() + except Exception as exc: formatted_traceback = traceback.format_exception(*sys.exc_info()) dialog = QtWidgets.QMessageBox(self) From d9424cdf39ec0657919390a57858c117e3125c26 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 21 Apr 2021 12:08:21 +0200 Subject: [PATCH 084/180] replcaed spaces with new line char --- openpype/modules/ftrack/ftrack_module.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/modules/ftrack/ftrack_module.py b/openpype/modules/ftrack/ftrack_module.py index 46403bbc26..c7ce7d6d07 100644 --- a/openpype/modules/ftrack/ftrack_module.py +++ b/openpype/modules/ftrack/ftrack_module.py @@ -306,7 +306,7 @@ class FtrackModule( "Values were not updated on Ftrack which may cause issues." ) if missing: - error_msg += " Missing Custom attributes on Ftrack: {}.".format( + error_msg += "\nMissing Custom attributes on Ftrack: {}.".format( ", ".join([ '"{}"'.format(key) for key in missing.keys() @@ -317,7 +317,7 @@ class FtrackModule( '"{}": "{}"'.format(key, value) for key, value in failed.items() ]) - error_msg += " Failed to set: {}".format(joined_failed) + error_msg += "\nFailed to set: {}".format(joined_failed) raise SaveWarning(error_msg) def create_ftrack_session(self, **session_kwargs): From b01d541c59ebb9358aaa40855f4507fbd49ff03a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 21 Apr 2021 12:08:36 +0200 Subject: [PATCH 085/180] fixed save warning catch --- .../tools/settings/settings/widgets/categories.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/openpype/tools/settings/settings/widgets/categories.py b/openpype/tools/settings/settings/widgets/categories.py index d782aaf54f..be31a063fe 100644 --- a/openpype/tools/settings/settings/widgets/categories.py +++ b/openpype/tools/settings/settings/widgets/categories.py @@ -273,14 +273,21 @@ class SettingsCategoryWidget(QtWidgets.QWidget): self.reset() except SaveWarning as exc: - msg = "Settings were saved but few issues happened.\n\n" - msg += "\n".join(exc.warnings) + warnings = [ + "Settings were saved but few issues happened." + ] + for item in exc.warnings: + warnings.append(item.replace("\n", "
")) + + msg = "

".join(warnings) dialog = QtWidgets.QMessageBox(self) dialog.setText(msg) dialog.setIcon(QtWidgets.QMessageBox.Warning) dialog.exec_() + self.reset() + except Exception as exc: formatted_traceback = traceback.format_exception(*sys.exc_info()) dialog = QtWidgets.QMessageBox(self) From 4358aace7ef8a8a0f6e5909aa48078405849f1b2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 21 Apr 2021 12:09:11 +0200 Subject: [PATCH 086/180] ProjectSettings will try to store project settings if anatomy crashes --- openpype/settings/entities/root_entities.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/openpype/settings/entities/root_entities.py b/openpype/settings/entities/root_entities.py index eed3d47f46..05c2b61700 100644 --- a/openpype/settings/entities/root_entities.py +++ b/openpype/settings/entities/root_entities.py @@ -23,6 +23,7 @@ from openpype.settings.constants import ( PROJECT_ANATOMY_KEY, KEY_REGEX ) +from openpype.settings.exceptions import SaveWarning from openpype.settings.lib import ( DEFAULTS_DIR, @@ -724,8 +725,19 @@ class ProjectSettings(RootEntity): project_settings = settings_value.get(PROJECT_SETTINGS_KEY) or {} project_anatomy = settings_value.get(PROJECT_ANATOMY_KEY) or {} - save_project_settings(self.project_name, project_settings) - save_project_anatomy(self.project_name, project_anatomy) + warnings = [] + try: + save_project_settings(self.project_name, project_settings) + except SaveWarning as exc: + warnings.extend(exc.warnings) + + try: + save_project_anatomy(self.project_name, project_anatomy) + except SaveWarning as exc: + warnings.extend(exc.warnings) + + if warnings: + raise SaveWarning(warnings) def _validate_defaults_to_save(self, value): """Valiations of default values before save.""" From 4ba8eb7610e3b95d352e8430222f65bf9b2f6d71 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 21 Apr 2021 12:09:33 +0200 Subject: [PATCH 087/180] defined max length of exc info marks --- openpype/lib/log.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/lib/log.py b/openpype/lib/log.py index 9745279e28..39b6c67080 100644 --- a/openpype/lib/log.py +++ b/openpype/lib/log.py @@ -123,6 +123,8 @@ class PypeFormatter(logging.Formatter): if record.exc_info is not None: line_len = len(str(record.exc_info[1])) + if line_len > 30: + line_len = 30 out = "{}\n{}\n{}\n{}\n{}".format( out, line_len * "=", From 1418d9734ac5029ccc68ebb328a69b918bae295b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 21 Apr 2021 12:15:19 +0200 Subject: [PATCH 088/180] added more messages --- openpype/modules/ftrack/ftrack_module.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/openpype/modules/ftrack/ftrack_module.py b/openpype/modules/ftrack/ftrack_module.py index c7ce7d6d07..54e92af42c 100644 --- a/openpype/modules/ftrack/ftrack_module.py +++ b/openpype/modules/ftrack/ftrack_module.py @@ -246,8 +246,8 @@ class FtrackModule( if not project_entity: msg = ( - "Ftrack project with names \"{}\" was not found in Ftrack." - " Skipping settings attributes change callback." + "Ftrack project with name \"{}\" was not found in Ftrack." + " Can't push attribute changes." ).format(project_name) self.log.warning(msg) raise SaveWarning(msg) @@ -265,6 +265,9 @@ class FtrackModule( if not configuration: configuration = cust_attr_by_key.get(key) if not configuration: + self.log.warning( + "Custom attribute \"{}\" was not found.".format(key) + ) missing[key] = value continue @@ -304,6 +307,8 @@ class FtrackModule( error_msg = ( "Values were not updated on Ftrack which may cause issues." + " Try to update OpenPype custom attributes and resave" + " project settings." ) if missing: error_msg += "\nMissing Custom attributes on Ftrack: {}.".format( From bfb0874f0399a2e67f8cb389ddea1404329cd53a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 21 Apr 2021 13:35:04 +0200 Subject: [PATCH 089/180] renamed SaveWarning to SaveWarningExc --- openpype/modules/ftrack/ftrack_module.py | 12 +++++------ openpype/settings/__init__.py | 4 ++-- openpype/settings/entities/root_entities.py | 8 ++++---- openpype/settings/exceptions.py | 4 ++-- openpype/settings/lib.py | 20 +++++++++---------- .../settings/settings/widgets/categories.py | 4 ++-- 6 files changed, 26 insertions(+), 26 deletions(-) diff --git a/openpype/modules/ftrack/ftrack_module.py b/openpype/modules/ftrack/ftrack_module.py index 54e92af42c..60eed5c941 100644 --- a/openpype/modules/ftrack/ftrack_module.py +++ b/openpype/modules/ftrack/ftrack_module.py @@ -13,7 +13,7 @@ from openpype.modules import ( ILaunchHookPaths, ISettingsChangeListener ) -from openpype.settings import SaveWarning +from openpype.settings import SaveWarningExc FTRACK_MODULE_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -136,7 +136,7 @@ class FtrackModule( session = self.create_ftrack_session() except Exception: self.log.warning("Couldn't create ftrack session.", exc_info=True) - raise SaveWarning(( + raise SaveWarningExc(( "Couldn't create Ftrack session." " You may need to update applications" " and tools in Ftrack custom attributes using defined action." @@ -205,7 +205,7 @@ class FtrackModule( session.commit() if missing_attributes: - raise SaveWarning(( + raise SaveWarningExc(( "Couldn't find custom attribute/s ({}) to update." " You may need to update applications" " and tools in Ftrack custom attributes using defined action." @@ -234,7 +234,7 @@ class FtrackModule( session = self.create_ftrack_session() except Exception: self.log.warning("Couldn't create ftrack session.", exc_info=True) - raise SaveWarning(( + raise SaveWarningExc(( "Couldn't create Ftrack session." " You may need to update applications" " and tools in Ftrack custom attributes using defined action." @@ -250,7 +250,7 @@ class FtrackModule( " Can't push attribute changes." ).format(project_name) self.log.warning(msg) - raise SaveWarning(msg) + raise SaveWarningExc(msg) project_id = project_entity["id"] @@ -323,7 +323,7 @@ class FtrackModule( for key, value in failed.items() ]) error_msg += "\nFailed to set: {}".format(joined_failed) - raise SaveWarning(error_msg) + raise SaveWarningExc(error_msg) def create_ftrack_session(self, **session_kwargs): import ftrack_api diff --git a/openpype/settings/__init__.py b/openpype/settings/__init__.py index 78a287f07e..e65c89e603 100644 --- a/openpype/settings/__init__.py +++ b/openpype/settings/__init__.py @@ -1,5 +1,5 @@ from .exceptions import ( - SaveWarning + SaveWarningExc ) from .lib import ( get_system_settings, @@ -15,7 +15,7 @@ from .entities import ( __all__ = ( - "SaveWarning", + "SaveWarningExc", "get_system_settings", "get_project_settings", diff --git a/openpype/settings/entities/root_entities.py b/openpype/settings/entities/root_entities.py index 05c2b61700..b89473d9fb 100644 --- a/openpype/settings/entities/root_entities.py +++ b/openpype/settings/entities/root_entities.py @@ -23,7 +23,7 @@ from openpype.settings.constants import ( PROJECT_ANATOMY_KEY, KEY_REGEX ) -from openpype.settings.exceptions import SaveWarning +from openpype.settings.exceptions import SaveWarningExc from openpype.settings.lib import ( DEFAULTS_DIR, @@ -728,16 +728,16 @@ class ProjectSettings(RootEntity): warnings = [] try: save_project_settings(self.project_name, project_settings) - except SaveWarning as exc: + except SaveWarningExc as exc: warnings.extend(exc.warnings) try: save_project_anatomy(self.project_name, project_anatomy) - except SaveWarning as exc: + except SaveWarningExc as exc: warnings.extend(exc.warnings) if warnings: - raise SaveWarning(warnings) + raise SaveWarningExc(warnings) def _validate_defaults_to_save(self, value): """Valiations of default values before save.""" diff --git a/openpype/settings/exceptions.py b/openpype/settings/exceptions.py index 758a778794..a06138eeaf 100644 --- a/openpype/settings/exceptions.py +++ b/openpype/settings/exceptions.py @@ -2,10 +2,10 @@ class SaveSettingsValidation(Exception): pass -class SaveWarning(SaveSettingsValidation): +class SaveWarningExc(SaveSettingsValidation): def __init__(self, warnings): if isinstance(warnings, str): warnings = [warnings] self.warnings = warnings msg = " | ".join(warnings) - super(SaveWarning, self).__init__(msg) + super(SaveWarningExc, self).__init__(msg) diff --git a/openpype/settings/lib.py b/openpype/settings/lib.py index c4ed9453f1..9c05c8e86c 100644 --- a/openpype/settings/lib.py +++ b/openpype/settings/lib.py @@ -5,7 +5,7 @@ import logging import platform import copy from .exceptions import ( - SaveWarning + SaveWarningExc ) from .constants import ( M_OVERRIDEN_KEY, @@ -111,7 +111,7 @@ def save_studio_settings(data): data(dict): Overrides data with metadata defying studio overrides. Raises: - SaveWarning: If any module raises the exception. + SaveWarningExc: If any module raises the exception. """ # Notify Pype modules from openpype.modules import ModulesManager, ISettingsChangeListener @@ -129,12 +129,12 @@ def save_studio_settings(data): if isinstance(module, ISettingsChangeListener): try: module.on_system_settings_save(old_data, new_data, changes) - except SaveWarning as exc: + except SaveWarningExc as exc: warnings.extend(exc.warnings) _SETTINGS_HANDLER.save_studio_settings(data) if warnings: - raise SaveWarning(warnings) + raise SaveWarningExc(warnings) @require_handler @@ -155,7 +155,7 @@ def save_project_settings(project_name, overrides): overrides(dict): Overrides data with metadata defying studio overrides. Raises: - SaveWarning: If any module raises the exception. + SaveWarningExc: If any module raises the exception. """ # Notify Pype modules from openpype.modules import ModulesManager, ISettingsChangeListener @@ -184,13 +184,13 @@ def save_project_settings(project_name, overrides): module.on_project_settings_save( old_data, new_data, project_name, changes ) - except SaveWarning as exc: + except SaveWarningExc as exc: warnings.extend(exc.warnings) _SETTINGS_HANDLER.save_project_settings(project_name, overrides) if warnings: - raise SaveWarning(warnings) + raise SaveWarningExc(warnings) @require_handler @@ -211,7 +211,7 @@ def save_project_anatomy(project_name, anatomy_data): overrides(dict): Overrides data with metadata defying studio overrides. Raises: - SaveWarning: If any module raises the exception. + SaveWarningExc: If any module raises the exception. """ # Notify Pype modules from openpype.modules import ModulesManager, ISettingsChangeListener @@ -240,13 +240,13 @@ def save_project_anatomy(project_name, anatomy_data): module.on_project_anatomy_save( old_data, new_data, changes, project_name ) - except SaveWarning as exc: + except SaveWarningExc as exc: warnings.extend(exc.warnings) _SETTINGS_HANDLER.save_project_anatomy(project_name, anatomy_data) if warnings: - raise SaveWarning(warnings) + raise SaveWarningExc(warnings) @require_handler diff --git a/openpype/tools/settings/settings/widgets/categories.py b/openpype/tools/settings/settings/widgets/categories.py index be31a063fe..e4832c989a 100644 --- a/openpype/tools/settings/settings/widgets/categories.py +++ b/openpype/tools/settings/settings/widgets/categories.py @@ -27,7 +27,7 @@ from openpype.settings.entities import ( SchemaError ) -from openpype.settings import SaveWarning +from openpype.settings import SaveWarningExc from .widgets import ProjectListWidget from . import lib @@ -272,7 +272,7 @@ class SettingsCategoryWidget(QtWidgets.QWidget): # not required. self.reset() - except SaveWarning as exc: + except SaveWarningExc as exc: warnings = [ "Settings were saved but few issues happened." ] From e918acea48755bab9b4744cfe6ecb8dd8e67f08c Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 21 Apr 2021 15:13:33 +0200 Subject: [PATCH 090/180] SyncServer GUI - working version for Summary list --- openpype/modules/sync_server/tray/models.py | 14 +- openpype/modules/sync_server/tray/widgets.py | 172 ++++++++++--------- 2 files changed, 95 insertions(+), 91 deletions(-) diff --git a/openpype/modules/sync_server/tray/models.py b/openpype/modules/sync_server/tray/models.py index 4b70fbae15..266a9289ca 100644 --- a/openpype/modules/sync_server/tray/models.py +++ b/openpype/modules/sync_server/tray/models.py @@ -64,20 +64,20 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): def rowCount(self, _index): return len(self._data) - def columnCount(self, _index): + def columnCount(self, _index=None): return len(self._header) - def headerData(self, section, orientation, role): + def headerData(self, section, orientation, role=Qt.DisplayRole): name = self.COLUMN_LABELS[section][0] if role == Qt.DisplayRole: if orientation == Qt.Horizontal: return self.COLUMN_LABELS[section][1] - if role == Qt.DecorationRole: - if name in self.column_filtering.keys(): - return qtawesome.icon("fa.filter", color="white") - if self.COLUMN_FILTERS.get(name): - return qtawesome.icon("fa.filter", color="gray") + # if role == Qt.DecorationRole: + # if name in self.column_filtering.keys(): + # return qtawesome.icon("fa.filter", color="white") + # if self.COLUMN_FILTERS.get(name): + # return qtawesome.icon("fa.filter", color="gray") if role == lib.HeaderNameRole: if orientation == Qt.Horizontal: diff --git a/openpype/modules/sync_server/tray/widgets.py b/openpype/modules/sync_server/tray/widgets.py index f9f904cd03..25abc73a70 100644 --- a/openpype/modules/sync_server/tray/widgets.py +++ b/openpype/modules/sync_server/tray/widgets.py @@ -42,6 +42,8 @@ class SyncProjectListWidget(ProjectListWidget): self.local_site = None self.icons = {} + self.layout().setContentsMargins(0, 0, 0, 0) + def validate_context_change(self): return True @@ -143,12 +145,12 @@ class SyncRepresentationWidget(QtWidgets.QWidget): message_generated = QtCore.Signal(str) default_widths = ( - ("asset", 200), + ("asset", 190), ("subset", 170), ("version", 60), - ("representation", 135), - ("local_site", 170), - ("remote_site", 170), + ("representation", 145), + ("local_site", 160), + ("remote_site", 160), ("files_count", 50), ("files_size", 60), ("priority", 70), @@ -215,10 +217,10 @@ class SyncRepresentationWidget(QtWidgets.QWidget): self.selection_model = self.table_view.selectionModel() self.selection_model.selectionChanged.connect(self._selection_changed) - self.horizontal_header = HorizontalHeader(self) - self.table_view.setHorizontalHeader(self.horizontal_header) - # self.table_view.setSortingEnabled(True) - # self.table_view.horizontalHeader().setSortIndicatorShown(True) + horizontal_header = HorizontalHeader(self) + + self.table_view.setHorizontalHeader(horizontal_header) + self.table_view.setSortingEnabled(True) for column_name, width in self.default_widths: idx = model.get_header_index(column_name) @@ -828,6 +830,21 @@ class SyncRepresentationErrorWindow(QtWidgets.QDialog): self.setWindowTitle("Sync Representation Error Detail") +class TransparentWidget(QtWidgets.QWidget): + clicked = QtCore.Signal(str) + + def __init__(self, column_name, *args, **kwargs): + super(TransparentWidget, self).__init__(*args, **kwargs) + self.column_name = column_name + # self.setStyleSheet("background: red;") + + def mouseReleaseEvent(self, event): + if event.button() == QtCore.Qt.LeftButton: + self.clicked.emit(self.column_name) + + super(TransparentWidget, self).mouseReleaseEvent(event) + + class HorizontalHeader(QtWidgets.QHeaderView): def __init__(self, parent=None): @@ -835,26 +852,23 @@ class HorizontalHeader(QtWidgets.QHeaderView): self._parent = parent self.checked_values = {} - self.setSectionsMovable(True) + self.setModel(self._parent.model) + self.setSectionsClickable(True) - self.setHighlightSections(True) self.menu_items_dict = {} self.menu = None self.header_cells = [] self.filter_buttons = {} - self.init_layout() - self.filter_icon = qtawesome.icon("fa.filter", color="gray") self.filter_set_icon = qtawesome.icon("fa.filter", color="white") + self.init_layout() + self._resetting = False - self.sectionResized.connect(self.handleSectionResized) - self.sectionMoved.connect(self.handleSectionMoved) - #self.sectionPressed.connect(self.model.sort) - + self.sectionClicked.connect(self.on_section_clicked) @property def model(self): @@ -862,38 +876,30 @@ class HorizontalHeader(QtWidgets.QHeaderView): return self._parent.model def init_layout(self): - for i in range(self.count()): - cell_content = QtWidgets.QWidget(self) - column_name, column_label = self.model.get_column(i) - - layout = QtWidgets.QHBoxLayout() - layout.setContentsMargins(5, 5, 5, 0) - layout.setAlignment(Qt.AlignVCenter) - layout.addWidget(QtWidgets.QLabel(column_label)) - + for column_idx in range(self.model.columnCount()): + column_name, column_label = self.model.get_column(column_idx) filter_rec = self.model.get_filters().get(column_name) - if filter_rec: - icon = self.filter_icon - button = QtWidgets.QPushButton(icon, "") - layout.addWidget(button) + if not filter_rec: + continue - # button.setMenu(menu) - button.setFixedSize(24, 24) - # button.setAlignment(Qt.AlignRight) - button.setStyleSheet("QPushButton::menu-indicator{width:0px;}" - "QPushButton{border: none}") - button.clicked.connect(partial(self._get_menu, - column_name, i)) - button.setFlat(True) - self.filter_buttons[column_name] = button + icon = self.filter_icon + button = QtWidgets.QPushButton(icon, "", self) - cell_content.setLayout(layout) + # button.setMenu(menu) + button.setFixedSize(24, 24) + # button.setAlignment(Qt.AlignRight) + button.setStyleSheet("QPushButton::menu-indicator{width:0px;}" + "QPushButton{border: none;background: transparent;}") + button.clicked.connect(partial(self._get_menu, + column_name, column_idx)) + button.setFlat(True) + self.filter_buttons[column_name] = button - self.header_cells.append(cell_content) + def on_section_clicked(self, column_name): + print("on_section_clicked {}".format(column_name)) def showEvent(self, event): - if not self.header_cells: - self.init_layout() + super(HorizontalHeader, self).showEvent(event) for i in range(len(self.header_cells)): cell_content = self.header_cells[i] @@ -902,12 +908,6 @@ class HorizontalHeader(QtWidgets.QHeaderView): cell_content.show() - if len(self.model.get_filters()) > self.count(): - for i in range(self.count(), len(self.header_cells)): - self.header_cells[i].deleteLater() - - super(HorizontalHeader, self).showEvent(event) - def _set_filter_icon(self, column_name): button = self.filter_buttons.get(column_name) if button: @@ -939,18 +939,18 @@ class HorizontalHeader(QtWidgets.QHeaderView): self._set_filter_icon(column_name) self._filter_and_refresh_model_and_menu(column_name, True, False) - def _apply_text_filter(self, column_name, items): + def _apply_text_filter(self, column_name, items, line_edit): """ Resets all checkboxes, prefers inserted text. """ + le_text = line_edit.text() self._update_checked_values(column_name, items, 0) # reset other if self.checked_values.get(column_name) is not None or \ - self.line_edit.text() == '': + le_text == '': self.checked_values.pop(column_name) # reset during typing - text_item = {self.line_edit.text(): self.line_edit.text()} - if self.line_edit.text(): - self._update_checked_values(column_name, text_item, 2) + if le_text: + self._update_checked_values(column_name, {le_text: le_text}, 2) self._set_filter_icon(column_name) self._filter_and_refresh_model_and_menu(column_name, True, True) @@ -970,24 +970,23 @@ class HorizontalHeader(QtWidgets.QHeaderView): menu = QtWidgets.QMenu(self) filter_rec = self.model.get_filters()[column_name] self.menu_items_dict[column_name] = filter_rec.values() - self.line_edit = None # text filtering only if labels same as values, not if codes are used if 'text' in filter_rec.search_variants(): - self.line_edit = QtWidgets.QLineEdit(self) - self.line_edit.setSizePolicy( - QtWidgets.QSizePolicy.Maximum, - QtWidgets.QSizePolicy.Maximum) + line_edit = QtWidgets.QLineEdit(menu) + line_edit.setClearButtonEnabled(True) + + line_edit.setFixedHeight(line_edit.height()) txt = "Type..." if self.checked_values.get(column_name): txt = list(self.checked_values.get(column_name).keys())[0] - self.line_edit.setPlaceholderText(txt) + line_edit.setPlaceholderText(txt) action_le = QtWidgets.QWidgetAction(menu) - action_le.setDefaultWidget(self.line_edit) - self.line_edit.textChanged.connect( + action_le.setDefaultWidget(line_edit) + line_edit.textChanged.connect( partial(self._apply_text_filter, column_name, - filter_rec.values())) + filter_rec.values(), line_edit)) menu.addAction(action_le) menu.addSeparator() @@ -1076,27 +1075,32 @@ class HorizontalHeader(QtWidgets.QHeaderView): self.checked_values[column_name] = checked - def handleSectionResized(self, i): - if not self.header_cells: - self.init_layout() - for i in range(self.count()): - j = self.visualIndex(i) - logical = self.logicalIndex(j) - self.header_cells[i].setGeometry( - self.sectionViewportPosition(logical), 0, - self.sectionSize(logical) - 1, self.height()) + def paintEvent(self, event): + self._fix_size() + super(HorizontalHeader, self).paintEvent(event) - def handleSectionMoved(self, i, oldVisualIndex, newVisualIndex): - if not self.header_cells: - self.init_layout() - for i in range(min(oldVisualIndex, newVisualIndex), self.count()): - logical = self.logicalIndex(i) - self.header_cells[i].setGeometry( - self.ectionViewportPosition(logical), 0, - self.sectionSize(logical) - 2, self.height()) + def _fix_size(self): + for column_idx in range(self.count()): + vis_index = self.visualIndex(column_idx) + index = self.logicalIndex(vis_index) + section_width = self.sectionSize(index) + + column_name = self.model.headerData(column_idx, + QtCore.Qt.Horizontal, + lib.HeaderNameRole) + button = self.filter_buttons.get(column_name) + if not button: + continue + + pos_x = self.sectionViewportPosition( + index) + section_width - self.height() + + pos_y = 0 + if button.height() < self.height(): + pos_y = int((self.height() - button.height()) / 2) + button.setGeometry( + pos_x, + pos_y, + self.height(), + self.height()) - def fixComboPositions(self): - for i in range(self.count()): - self.header_cells[i].setGeometry( - self.sectionViewportPosition(i), 0, - self.sectionSize(i) - 2, self.height()) From 981443b5c3a789dba845703ea2a512707753db4e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 21 Apr 2021 15:15:59 +0200 Subject: [PATCH 091/180] removed unused imports --- openpype/modules/ftrack/lib/avalon_sync.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/openpype/modules/ftrack/lib/avalon_sync.py b/openpype/modules/ftrack/lib/avalon_sync.py index cfe1f011e7..f58e858a5a 100644 --- a/openpype/modules/ftrack/lib/avalon_sync.py +++ b/openpype/modules/ftrack/lib/avalon_sync.py @@ -14,24 +14,21 @@ else: from avalon.api import AvalonMongoDB import avalon + from openpype.api import ( Logger, Anatomy, get_anatomy_settings ) +from openpype.lib import ApplicationManager + +from .constants import CUST_ATTR_ID_KEY +from .custom_attributes import get_openpype_attr from bson.objectid import ObjectId from bson.errors import InvalidId from pymongo import UpdateOne import ftrack_api -from openpype.lib import ApplicationManager - -from .constants import ( - CUST_ATTR_ID_KEY, - CUST_ATTR_AUTO_SYNC, - CUST_ATTR_GROUP -) -from .custom_attributes import get_openpype_attr log = Logger.get_logger(__name__) From d9cbb6eea6cfc18e8ad7b4a2f2c7357f60a9e94b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 21 Apr 2021 16:04:58 +0200 Subject: [PATCH 092/180] SyncServer GUI - working version for Detail list --- openpype/modules/sync_server/tray/models.py | 101 +++++++++++-------- openpype/modules/sync_server/tray/widgets.py | 63 ++++++------ 2 files changed, 90 insertions(+), 74 deletions(-) diff --git a/openpype/modules/sync_server/tray/models.py b/openpype/modules/sync_server/tray/models.py index 266a9289ca..d2dc3594c6 100644 --- a/openpype/modules/sync_server/tray/models.py +++ b/openpype/modules/sync_server/tray/models.py @@ -68,17 +68,14 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): return len(self._header) def headerData(self, section, orientation, role=Qt.DisplayRole): + if section >= len(self.COLUMN_LABELS): + return + name = self.COLUMN_LABELS[section][0] if role == Qt.DisplayRole: if orientation == Qt.Horizontal: return self.COLUMN_LABELS[section][1] - # if role == Qt.DecorationRole: - # if name in self.column_filtering.keys(): - # return qtawesome.icon("fa.filter", color="white") - # if self.COLUMN_FILTERS.get(name): - # return qtawesome.icon("fa.filter", color="gray") - if role == lib.HeaderNameRole: if orientation == Qt.Horizontal: return self.COLUMN_LABELS[section][0] # return name @@ -199,7 +196,7 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): representations = self.dbcon.aggregate(self.query) self.refresh(representations) - def set_filter(self, word_filter): + def set_word_filter(self, word_filter): """ Adds text value filtering @@ -209,6 +206,19 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): self._word_filter = word_filter self.refresh() + def get_filters(self): + """ + Returns all available filter editors per column_name keys. + """ + filters = {} + for column_name, _ in self.COLUMN_LABELS: + filter_rec = self.COLUMN_FILTERS.get(column_name) + if filter_rec: + filter_rec.dbcon = self.dbcon + filters[column_name] = filter_rec + + return filters + def get_column_filter(self, index): """ Returns filter object for column 'index @@ -227,6 +237,25 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): return filter_rec + def set_column_filtering(self, checked_values): + """ + Sets dictionary used in '$match' part of MongoDB aggregate + + Args: + checked_values(dict): key:values ({'status':{1:"Foo",3:"Bar"}} + + Modifies: + self._column_filtering : {'status': {'$in': [1, 2, 3]}} + """ + filtering = {} + for column_name, dict_value in checked_values.items(): + column_f = self.COLUMN_FILTERS.get(column_name) + if not column_f: + continue + column_f.dbcon = self.dbcon + filtering[column_name] = column_f.prepare_match_part(dict_value) + + self._column_filtering = filtering def get_column_filter_values(self, index): """ @@ -400,19 +429,6 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): self.timer.timeout.connect(self.tick) self.timer.start(self.REFRESH_SEC) - def get_filters(self): - """ - Returns all available filter editors per column_name keys. - """ - filters = {} - for column_name, _ in self.COLUMN_LABELS: - filter_rec = self.COLUMN_FILTERS.get(column_name) - if filter_rec: - filter_rec.dbcon = self.dbcon - filters[column_name] = filter_rec - - return filters - def data(self, index, role): item = self._data[index.row()] @@ -685,26 +701,6 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): return aggr - def set_column_filtering(self, checked_values): - """ - Sets dictionary used in '$match' part of MongoDB aggregate - - Args: - checked_values(dict): key:values ({'status':{1:"Foo",3:"Bar"}} - - Modifies: - self._column_filtering : {'status': {'$in': [1, 2, 3]}} - """ - filtering = {} - for column_name, dict_value in checked_values.items(): - column_f = self.COLUMN_FILTERS.get(column_name) - if not column_f: - continue - column_f.dbcon = self.dbcon - filtering[column_name] = column_f.prepare_match_part(dict_value) - - self._column_filtering = filtering - def get_match_part(self): """ Extend match part with word_filter if present. @@ -843,10 +839,15 @@ class SyncRepresentationDetailModel(_SyncRepresentationModel): "updated_dt_local", # local created_dt "updated_dt_remote", # remote created_dt "size", # remote progress - "context.asset", # priority TODO + "size", # priority TODO "status" # status ] + COLUMN_FILTERS = { + 'status': lib.PredefinedSetFilter('status', lib.STATUS), + 'file': lib.RegexTextFilter('file'), + } + refresh_started = QtCore.Signal() refresh_finished = QtCore.Signal() @@ -885,6 +886,7 @@ class SyncRepresentationDetailModel(_SyncRepresentationModel): self._word_filter = None self._id = _id self._initialized = False + self._column_filtering = {} self.sync_server = sync_server # TODO think about admin mode @@ -1033,7 +1035,7 @@ class SyncRepresentationDetailModel(_SyncRepresentationModel): if limit == 0: limit = SyncRepresentationSummaryModel.PAGE_SIZE - return [ + aggr = [ {"$match": self.get_match_part()}, {"$unwind": "$files"}, {'$addFields': { @@ -1129,7 +1131,16 @@ class SyncRepresentationDetailModel(_SyncRepresentationModel): ]} ]}} }}, - {"$project": self.projection}, + {"$project": self.projection} + ] + + if self.column_filtering: + aggr.append( + {"$match": self.column_filtering} + ) + print(self.column_filtering) + + aggr.extend([ {"$sort": self.sort}, { '$facet': { @@ -1138,7 +1149,9 @@ class SyncRepresentationDetailModel(_SyncRepresentationModel): 'totalCount': [{'$count': 'count'}] } } - ] + ]) + + return aggr def get_match_part(self): """ diff --git a/openpype/modules/sync_server/tray/widgets.py b/openpype/modules/sync_server/tray/widgets.py index 25abc73a70..e3d5d0fd12 100644 --- a/openpype/modules/sync_server/tray/widgets.py +++ b/openpype/modules/sync_server/tray/widgets.py @@ -203,7 +203,7 @@ class SyncRepresentationWidget(QtWidgets.QWidget): layout.addWidget(self.table_view) self.table_view.doubleClicked.connect(self._double_clicked) - self.filter.textChanged.connect(lambda: model.set_filter( + self.filter.textChanged.connect(lambda: model.set_word_filter( self.filter.text())) self.table_view.customContextMenuRequested.connect( self._on_context_menu) @@ -475,7 +475,7 @@ class SyncRepresentationDetailWidget(QtWidgets.QWidget): ("local_site", 185), ("remote_site", 185), ("size", 60), - ("priority", 25), + ("priority", 60), ("status", 110) ) @@ -499,53 +499,58 @@ class SyncRepresentationDetailWidget(QtWidgets.QWidget): top_bar_layout = QtWidgets.QHBoxLayout() top_bar_layout.addWidget(self.filter) - self.table_view = QtWidgets.QTableView() + table_view = QtWidgets.QTableView() headers = [item[0] for item in self.default_widths] model = SyncRepresentationDetailModel(sync_server, headers, _id, project) - self.table_view.setModel(model) - self.table_view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) - self.table_view.setSelectionMode( + table_view.setModel(model) + table_view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) + table_view.setSelectionMode( QtWidgets.QAbstractItemView.SingleSelection) - self.table_view.setSelectionBehavior( + table_view.setSelectionBehavior( QtWidgets.QTableView.SelectRows) - self.table_view.horizontalHeader().setSortIndicator(-1, - Qt.AscendingOrder) - self.table_view.setSortingEnabled(True) - self.table_view.horizontalHeader().setSortIndicatorShown(True) - self.table_view.setAlternatingRowColors(True) - self.table_view.verticalHeader().hide() + table_view.horizontalHeader().setSortIndicator(-1, Qt.AscendingOrder) + table_view.horizontalHeader().setSortIndicatorShown(True) + table_view.setAlternatingRowColors(True) + table_view.verticalHeader().hide() column = model.get_header_index("local_site") delegate = ImageDelegate(self) - self.table_view.setItemDelegateForColumn(column, delegate) + table_view.setItemDelegateForColumn(column, delegate) column = model.get_header_index("remote_site") delegate = ImageDelegate(self) - self.table_view.setItemDelegateForColumn(column, delegate) - - for column_name, width in self.default_widths: - idx = model.get_header_index(column_name) - self.table_view.setColumnWidth(idx, width) + table_view.setItemDelegateForColumn(column, delegate) layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.addLayout(top_bar_layout) - layout.addWidget(self.table_view) + layout.addWidget(table_view) - self.filter.textChanged.connect(lambda: model.set_filter( + self.model = model + + self.selection_model = table_view.selectionModel() + self.selection_model.selectionChanged.connect(self._selection_changed) + + horizontal_header = HorizontalHeader(self) + + table_view.setHorizontalHeader(horizontal_header) + table_view.setSortingEnabled(True) + + for column_name, width in self.default_widths: + idx = model.get_header_index(column_name) + table_view.setColumnWidth(idx, width) + + self.table_view = table_view + + self.filter.textChanged.connect(lambda: model.set_word_filter( self.filter.text())) - self.table_view.customContextMenuRequested.connect( - self._on_context_menu) + table_view.customContextMenuRequested.connect(self._on_context_menu) model.refresh_started.connect(self._save_scrollbar) model.refresh_finished.connect(self._set_scrollbar) model.modelReset.connect(self._set_selection) - self.model = model - - self.selection_model = self.table_view.selectionModel() - self.selection_model.selectionChanged.connect(self._selection_changed) def _selection_changed(self): index = self.selection_model.currentIndex() @@ -885,9 +890,7 @@ class HorizontalHeader(QtWidgets.QHeaderView): icon = self.filter_icon button = QtWidgets.QPushButton(icon, "", self) - # button.setMenu(menu) button.setFixedSize(24, 24) - # button.setAlignment(Qt.AlignRight) button.setStyleSheet("QPushButton::menu-indicator{width:0px;}" "QPushButton{border: none;background: transparent;}") button.clicked.connect(partial(self._get_menu, @@ -1080,7 +1083,7 @@ class HorizontalHeader(QtWidgets.QHeaderView): super(HorizontalHeader, self).paintEvent(event) def _fix_size(self): - for column_idx in range(self.count()): + for column_idx in range(self.model.columnCount()): vis_index = self.visualIndex(column_idx) index = self.logicalIndex(vis_index) section_width = self.sectionSize(index) From 7c53a6587d27f2849c7d40d9bd86c4f80d4c8a47 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 21 Apr 2021 16:11:28 +0200 Subject: [PATCH 093/180] SyncServer GUI - renamed methods --- openpype/modules/sync_server/tray/models.py | 27 +++++++++------------ 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/openpype/modules/sync_server/tray/models.py b/openpype/modules/sync_server/tray/models.py index d2dc3594c6..8a4bc53b25 100644 --- a/openpype/modules/sync_server/tray/models.py +++ b/openpype/modules/sync_server/tray/models.py @@ -119,7 +119,7 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): self._rec_loaded = 0 if not representations: - self.query = self.get_default_query(load_records) + self.query = self.get_query(load_records) representations = self.dbcon.aggregate(self.query) self.add_page_records(self.local_site, self.remote_site, @@ -154,7 +154,7 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): log.debug("fetchMore") items_to_fetch = min(self._total_records - self._rec_loaded, self.PAGE_SIZE) - self.query = self.get_default_query(self._rec_loaded) + self.query = self.get_query(self._rec_loaded) representations = self.dbcon.aggregate(self.query) self.beginInsertRows(index, self._rec_loaded, @@ -187,7 +187,7 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): order = -1 self.sort = {self.SORT_BY_COLUMN[index]: order, '_id': 1} - self.query = self.get_default_query() + self.query = self.get_query() # import json # log.debug(json.dumps(self.query, indent=4).\ # replace('False', 'false').\ @@ -415,12 +415,10 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): self.local_site = self.sync_server.get_active_site(self.project) self.remote_site = self.sync_server.get_remote_site(self.project) - self.projection = self.get_default_projection() - self.sort = self.DEFAULT_SORT - self.query = self.get_default_query() - self.default_query = list(self.get_default_query()) + self.query = self.get_query() + self.default_query = list(self.get_query()) representations = self.dbcon.aggregate(self.query) self.refresh(representations) @@ -544,7 +542,7 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): self._data.append(item) self._rec_loaded += 1 - def get_default_query(self, limit=0): + def get_query(self, limit=0): """ Returns basic aggregate query for main table. @@ -735,7 +733,8 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): return base_match - def get_default_projection(self): + @property + def projection(self): """ Projection part for aggregate query. @@ -896,10 +895,7 @@ class SyncRepresentationDetailModel(_SyncRepresentationModel): self.sort = self.DEFAULT_SORT - # in case we would like to hide/show some columns - self.projection = self.get_default_projection() - - self.query = self.get_default_query() + self.query = self.get_query() representations = self.dbcon.aggregate(self.query) self.refresh(representations) @@ -1021,7 +1017,7 @@ class SyncRepresentationDetailModel(_SyncRepresentationModel): self._data.append(item) self._rec_loaded += 1 - def get_default_query(self, limit=0): + def get_query(self, limit=0): """ Gets query that gets used when no extra sorting, filtering or projecting is needed. @@ -1174,7 +1170,8 @@ class SyncRepresentationDetailModel(_SyncRepresentationModel): '$or': [{'files.path': {'$regex': regex_str, '$options': 'i'}}] } - def get_default_projection(self): + @property + def projection(self): """ Projection part for aggregate query. From ef208e715de6e19e7c611388ba64895cb7835546 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 21 Apr 2021 16:15:59 +0200 Subject: [PATCH 094/180] changed how config actions are registered --- openpype/launcher_actions.py | 30 ------------------------------ openpype/tools/launcher/actions.py | 23 ++++++++--------------- 2 files changed, 8 insertions(+), 45 deletions(-) delete mode 100644 openpype/launcher_actions.py diff --git a/openpype/launcher_actions.py b/openpype/launcher_actions.py deleted file mode 100644 index cf68dfb5c1..0000000000 --- a/openpype/launcher_actions.py +++ /dev/null @@ -1,30 +0,0 @@ -import os -import sys - -from avalon import api, pipeline - -PACKAGE_DIR = os.path.dirname(__file__) -PLUGINS_DIR = os.path.join(PACKAGE_DIR, "plugins", "launcher") -ACTIONS_DIR = os.path.join(PLUGINS_DIR, "actions") - - -def register_launcher_actions(): - """Register specific actions which should be accessible in the launcher""" - - actions = [] - ext = ".py" - sys.path.append(ACTIONS_DIR) - - for f in os.listdir(ACTIONS_DIR): - file, extention = os.path.splitext(f) - if ext in extention: - module = __import__(file) - klass = getattr(module, file) - actions.append(klass) - - if actions is []: - return - - for action in actions: - print("Using launcher action from config @ '{}'".format(action.name)) - pipeline.register_plugin(api.Action, action) diff --git a/openpype/tools/launcher/actions.py b/openpype/tools/launcher/actions.py index 6261fe91ca..f2308c0150 100644 --- a/openpype/tools/launcher/actions.py +++ b/openpype/tools/launcher/actions.py @@ -2,6 +2,7 @@ import os import importlib from avalon import api, lib, style +from openpype import PLUGINS_DIR from openpype.api import Logger, resources from openpype.lib import ( ApplictionExecutableNotFound, @@ -70,21 +71,6 @@ def register_default_actions(): api.register_plugin(api.Action, LoaderLibrary) -def register_config_actions(): - """Register actions from the configuration for Launcher""" - - module_name = os.environ["AVALON_CONFIG"] - config = importlib.import_module(module_name) - if not hasattr(config, "register_launcher_actions"): - print( - "Current configuration `%s` has no 'register_launcher_actions'" - % config.__name__ - ) - return - - config.register_launcher_actions() - - def register_actions_from_paths(paths): if not paths: return @@ -105,6 +91,13 @@ def register_actions_from_paths(paths): api.register_plugin_path(api.Action, path) + +def register_config_actions(): + """Register actions from the configuration for Launcher""" + + actions_dir = os.path.join(PLUGINS_DIR, "actions") + register_actions_from_paths([actions_dir]) + def register_environment_actions(): """Register actions from AVALON_ACTIONS for Launcher.""" From 9efe5d4f6e675de00b5688c2858e6f7c704efb8c Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 21 Apr 2021 16:16:30 +0200 Subject: [PATCH 095/180] SyncServer GUI - added icon, clean up --- openpype/modules/sync_server/tray/models.py | 1 - openpype/modules/sync_server/tray/widgets.py | 7 ++----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/openpype/modules/sync_server/tray/models.py b/openpype/modules/sync_server/tray/models.py index 8a4bc53b25..3ee372d27d 100644 --- a/openpype/modules/sync_server/tray/models.py +++ b/openpype/modules/sync_server/tray/models.py @@ -684,7 +684,6 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): aggr.append( {"$match": self.column_filtering} ) - print(self.column_filtering) aggr.extend( [{"$sort": self.sort}, diff --git a/openpype/modules/sync_server/tray/widgets.py b/openpype/modules/sync_server/tray/widgets.py index e3d5d0fd12..9771d656ff 100644 --- a/openpype/modules/sync_server/tray/widgets.py +++ b/openpype/modules/sync_server/tray/widgets.py @@ -873,8 +873,6 @@ class HorizontalHeader(QtWidgets.QHeaderView): self._resetting = False - self.sectionClicked.connect(self.on_section_clicked) - @property def model(self): """Keep model synchronized with parent widget""" @@ -898,9 +896,6 @@ class HorizontalHeader(QtWidgets.QHeaderView): button.setFlat(True) self.filter_buttons[column_name] = button - def on_section_clicked(self, column_name): - print("on_section_clicked {}".format(column_name)) - def showEvent(self, event): super(HorizontalHeader, self).showEvent(event) @@ -978,6 +973,8 @@ class HorizontalHeader(QtWidgets.QHeaderView): if 'text' in filter_rec.search_variants(): line_edit = QtWidgets.QLineEdit(menu) line_edit.setClearButtonEnabled(True) + line_edit.addAction(self.filter_icon, + QtWidgets.QLineEdit.LeadingPosition) line_edit.setFixedHeight(line_edit.height()) txt = "Type..." From db0f6dcefe6d9091b280f5fd422487f541a706ae Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 21 Apr 2021 16:16:33 +0200 Subject: [PATCH 096/180] removed "default actions" and it's registration function --- openpype/modules/launcher_action.py | 1 - openpype/tools/launcher/actions.py | 63 +---------------------------- 2 files changed, 1 insertion(+), 63 deletions(-) diff --git a/openpype/modules/launcher_action.py b/openpype/modules/launcher_action.py index da0468d495..5ed8585b6a 100644 --- a/openpype/modules/launcher_action.py +++ b/openpype/modules/launcher_action.py @@ -22,7 +22,6 @@ class LauncherAction(PypeModule, ITrayAction): # Register actions if self.tray_initialized: from openpype.tools.launcher import actions - # actions.register_default_actions() actions.register_config_actions() actions_paths = self.manager.collect_plugin_paths()["actions"] actions.register_actions_from_paths(actions_paths) diff --git a/openpype/tools/launcher/actions.py b/openpype/tools/launcher/actions.py index f2308c0150..eb89ad3d88 100644 --- a/openpype/tools/launcher/actions.py +++ b/openpype/tools/launcher/actions.py @@ -1,7 +1,6 @@ import os -import importlib -from avalon import api, lib, style +from avalon import api, style from openpype import PLUGINS_DIR from openpype.api import Logger, resources from openpype.lib import ( @@ -11,66 +10,6 @@ from openpype.lib import ( from Qt import QtWidgets, QtGui -class ProjectManagerAction(api.Action): - name = "projectmanager" - label = "Project Manager" - icon = "gear" - order = 999 # at the end - - def is_compatible(self, session): - return "AVALON_PROJECT" in session - - def process(self, session, **kwargs): - return lib.launch( - executable="python", - args=[ - "-u", "-m", "avalon.tools.projectmanager", - session['AVALON_PROJECT'] - ] - ) - - -class LoaderAction(api.Action): - name = "loader" - label = "Loader" - icon = "cloud-download" - order = 998 - - def is_compatible(self, session): - return "AVALON_PROJECT" in session - - def process(self, session, **kwargs): - return lib.launch( - executable="python", - args=[ - "-u", "-m", "avalon.tools.loader", session['AVALON_PROJECT'] - ] - ) - - -class LoaderLibrary(api.Action): - name = "loader_os" - label = "Library Loader" - icon = "book" - order = 997 # at the end - - def is_compatible(self, session): - return True - - def process(self, session, **kwargs): - return lib.launch( - executable="python", - args=["-u", "-m", "avalon.tools.libraryloader"] - ) - - -def register_default_actions(): - """Register default actions for Launcher""" - api.register_plugin(api.Action, ProjectManagerAction) - api.register_plugin(api.Action, LoaderAction) - api.register_plugin(api.Action, LoaderLibrary) - - def register_actions_from_paths(paths): if not paths: return From ac0e2b34a96e53fac9851c3bf3da4b2e0df9fe33 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 21 Apr 2021 16:24:57 +0200 Subject: [PATCH 097/180] removed empty spaces --- openpype/tools/launcher/actions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/launcher/actions.py b/openpype/tools/launcher/actions.py index eb89ad3d88..72c7aece72 100644 --- a/openpype/tools/launcher/actions.py +++ b/openpype/tools/launcher/actions.py @@ -30,7 +30,7 @@ def register_actions_from_paths(paths): api.register_plugin_path(api.Action, path) - + def register_config_actions(): """Register actions from the configuration for Launcher""" From a5441f153351480d1125317a4697cf7b836b270c Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 21 Apr 2021 15:51:21 +0100 Subject: [PATCH 098/180] Added support for point caches --- .../plugins/create/create_pointcache.py | 35 +++ .../hosts/blender/plugins/load/load_abc.py | 247 ++++++++++++++++++ .../hosts/blender/plugins/load/load_model.py | 237 ----------------- .../blender/plugins/publish/extract_abc.py | 2 +- 4 files changed, 283 insertions(+), 238 deletions(-) create mode 100644 openpype/hosts/blender/plugins/create/create_pointcache.py create mode 100644 openpype/hosts/blender/plugins/load/load_abc.py diff --git a/openpype/hosts/blender/plugins/create/create_pointcache.py b/openpype/hosts/blender/plugins/create/create_pointcache.py new file mode 100644 index 0000000000..03a468f82e --- /dev/null +++ b/openpype/hosts/blender/plugins/create/create_pointcache.py @@ -0,0 +1,35 @@ +"""Create a pointcache asset.""" + +import bpy + +from avalon import api +from avalon.blender import lib +import openpype.hosts.blender.api.plugin + + +class CreatePointcache(openpype.hosts.blender.api.plugin.Creator): + """Polygonal static geometry""" + + name = "pointcacheMain" + label = "Point Cache" + family = "pointcache" + icon = "gears" + + def process(self): + + asset = self.data["asset"] + subset = self.data["subset"] + name = openpype.hosts.blender.api.plugin.asset_name(asset, subset) + collection = bpy.data.collections.new(name=name) + bpy.context.scene.collection.children.link(collection) + self.data['task'] = api.Session.get('AVALON_TASK') + lib.imprint(collection, self.data) + + if (self.options or {}).get("useSelection"): + objects = lib.get_selection() + for obj in objects: + collection.objects.link(obj) + if obj.type == 'EMPTY': + objects.extend(obj.children) + + return collection diff --git a/openpype/hosts/blender/plugins/load/load_abc.py b/openpype/hosts/blender/plugins/load/load_abc.py new file mode 100644 index 0000000000..9e20ccabbc --- /dev/null +++ b/openpype/hosts/blender/plugins/load/load_abc.py @@ -0,0 +1,247 @@ +"""Load an asset in Blender from an Alembic file.""" + +import logging +from pathlib import Path +from pprint import pformat +from typing import Dict, List, Optional + +from avalon import api, blender +import bpy +import openpype.hosts.blender.api.plugin as plugin + + +class CacheModelLoader(plugin.AssetLoader): + """Load cache models. + + Stores the imported asset in a collection named after the asset. + + Note: + At least for now it only supports Alembic files. + """ + + families = ["model", "pointcache"] + representations = ["abc"] + + label = "Link Alembic" + icon = "code-fork" + color = "orange" + + def _remove(self, objects, container): + for obj in list(objects): + if obj.type == 'MESH': + bpy.data.meshes.remove(obj.data) + elif obj.type == 'EMPTY': + bpy.data.objects.remove(obj) + + bpy.data.collections.remove(container) + + def _process(self, libpath, container_name, parent_collection): + bpy.ops.object.select_all(action='DESELECT') + + view_layer = bpy.context.view_layer + view_layer_collection = view_layer.active_layer_collection.collection + + relative = bpy.context.preferences.filepaths.use_relative_paths + bpy.ops.wm.alembic_import( + filepath=libpath, + relative_path=relative + ) + + parent = parent_collection + + if parent is None: + parent = bpy.context.scene.collection + + model_container = bpy.data.collections.new(container_name) + parent.children.link(model_container) + for obj in bpy.context.selected_objects: + model_container.objects.link(obj) + view_layer_collection.objects.unlink(obj) + + name = obj.name + obj.name = f"{name}:{container_name}" + + # Groups are imported as Empty objects in Blender + if obj.type == 'MESH': + data_name = obj.data.name + obj.data.name = f"{data_name}:{container_name}" + + if not obj.get(blender.pipeline.AVALON_PROPERTY): + obj[blender.pipeline.AVALON_PROPERTY] = dict() + + avalon_info = obj[blender.pipeline.AVALON_PROPERTY] + avalon_info.update({"container_name": container_name}) + + bpy.ops.object.select_all(action='DESELECT') + + return model_container + + def process_asset( + self, context: dict, name: str, namespace: Optional[str] = None, + options: Optional[Dict] = None + ) -> Optional[List]: + """ + Arguments: + name: Use pre-defined name + namespace: Use pre-defined namespace + context: Full parenthood of representation to load + options: Additional settings dictionary + """ + + libpath = self.fname + asset = context["asset"]["name"] + subset = context["subset"]["name"] + + lib_container = plugin.asset_name( + asset, subset + ) + unique_number = plugin.get_unique_number( + asset, subset + ) + namespace = namespace or f"{asset}_{unique_number}" + container_name = plugin.asset_name( + asset, subset, unique_number + ) + + container = bpy.data.collections.new(lib_container) + container.name = container_name + blender.pipeline.containerise_existing( + container, + name, + namespace, + context, + self.__class__.__name__, + ) + + container_metadata = container.get( + blender.pipeline.AVALON_PROPERTY) + + container_metadata["libpath"] = libpath + container_metadata["lib_container"] = lib_container + + obj_container = self._process( + libpath, container_name, None) + + container_metadata["obj_container"] = obj_container + + # Save the list of objects in the metadata container + container_metadata["objects"] = obj_container.all_objects + + nodes = list(container.objects) + nodes.append(container) + self[:] = nodes + return nodes + + def update(self, container: Dict, representation: Dict): + """Update the loaded asset. + + This will remove all objects of the current collection, load the new + ones and add them to the collection. + If the objects of the collection are used in another collection they + will not be removed, only unlinked. Normally this should not be the + case though. + + Warning: + No nested collections are supported at the moment! + """ + collection = bpy.data.collections.get( + container["objectName"] + ) + libpath = Path(api.get_representation_path(representation)) + extension = libpath.suffix.lower() + + self.log.info( + "Container: %s\nRepresentation: %s", + pformat(container, indent=2), + pformat(representation, indent=2), + ) + + assert collection, ( + f"The asset is not loaded: {container['objectName']}" + ) + assert not (collection.children), ( + "Nested collections are not supported." + ) + assert libpath, ( + "No existing library file found for {container['objectName']}" + ) + assert libpath.is_file(), ( + f"The file doesn't exist: {libpath}" + ) + assert extension in plugin.VALID_EXTENSIONS, ( + f"Unsupported file: {libpath}" + ) + + collection_metadata = collection.get( + blender.pipeline.AVALON_PROPERTY) + collection_libpath = collection_metadata["libpath"] + + obj_container = plugin.get_local_collection_with_name( + collection_metadata["obj_container"].name + ) + objects = obj_container.all_objects + + container_name = obj_container.name + + normalized_collection_libpath = ( + str(Path(bpy.path.abspath(collection_libpath)).resolve()) + ) + normalized_libpath = ( + str(Path(bpy.path.abspath(str(libpath))).resolve()) + ) + self.log.debug( + "normalized_collection_libpath:\n %s\nnormalized_libpath:\n %s", + normalized_collection_libpath, + normalized_libpath, + ) + if normalized_collection_libpath == normalized_libpath: + self.log.info("Library already loaded, not updating...") + return + + parent = plugin.get_parent_collection(obj_container) + + self._remove(objects, obj_container) + + obj_container = self._process( + str(libpath), container_name, parent) + + collection_metadata["obj_container"] = obj_container + collection_metadata["objects"] = obj_container.all_objects + collection_metadata["libpath"] = str(libpath) + collection_metadata["representation"] = str(representation["_id"]) + + def remove(self, container: Dict) -> bool: + """Remove an existing container from a Blender scene. + + Arguments: + container (openpype:container-1.0): Container to remove, + from `host.ls()`. + + Returns: + bool: Whether the container was deleted. + + Warning: + No nested collections are supported at the moment! + """ + collection = bpy.data.collections.get( + container["objectName"] + ) + if not collection: + return False + assert not (collection.children), ( + "Nested collections are not supported." + ) + + collection_metadata = collection.get( + blender.pipeline.AVALON_PROPERTY) + + obj_container = plugin.get_local_collection_with_name( + collection_metadata["obj_container"].name + ) + objects = obj_container.all_objects + + self._remove(objects, obj_container) + + bpy.data.collections.remove(collection) + + return True \ No newline at end of file diff --git a/openpype/hosts/blender/plugins/load/load_model.py b/openpype/hosts/blender/plugins/load/load_model.py index 168bdf9321..d645bedfcc 100644 --- a/openpype/hosts/blender/plugins/load/load_model.py +++ b/openpype/hosts/blender/plugins/load/load_model.py @@ -242,240 +242,3 @@ class BlendModelLoader(plugin.AssetLoader): bpy.data.collections.remove(collection) return True - - -class CacheModelLoader(plugin.AssetLoader): - """Load cache models. - - Stores the imported asset in a collection named after the asset. - - Note: - At least for now it only supports Alembic files. - """ - - families = ["model"] - representations = ["abc"] - - label = "Link Model" - icon = "code-fork" - color = "orange" - - def _remove(self, objects, container): - for obj in list(objects): - if obj.type == 'MESH': - bpy.data.meshes.remove(obj.data) - elif obj.type == 'EMPTY': - bpy.data.objects.remove(obj) - - bpy.data.collections.remove(container) - - def _process(self, libpath, container_name, parent_collection): - bpy.ops.object.select_all(action='DESELECT') - - view_layer = bpy.context.view_layer - view_layer_collection = view_layer.active_layer_collection.collection - - relative = bpy.context.preferences.filepaths.use_relative_paths - bpy.ops.wm.alembic_import( - filepath=libpath, - relative_path=relative - ) - - parent = parent_collection - - if parent is None: - parent = bpy.context.scene.collection - - model_container = bpy.data.collections.new(container_name) - parent.children.link(model_container) - for obj in bpy.context.selected_objects: - model_container.objects.link(obj) - view_layer_collection.objects.unlink(obj) - - name = obj.name - obj.name = f"{name}:{container_name}" - - # Groups are imported as Empty objects in Blender - if obj.type == 'MESH': - data_name = obj.data.name - obj.data.name = f"{data_name}:{container_name}" - - if not obj.get(blender.pipeline.AVALON_PROPERTY): - obj[blender.pipeline.AVALON_PROPERTY] = dict() - - avalon_info = obj[blender.pipeline.AVALON_PROPERTY] - avalon_info.update({"container_name": container_name}) - - bpy.ops.object.select_all(action='DESELECT') - - return model_container - - def process_asset( - self, context: dict, name: str, namespace: Optional[str] = None, - options: Optional[Dict] = None - ) -> Optional[List]: - """ - Arguments: - name: Use pre-defined name - namespace: Use pre-defined namespace - context: Full parenthood of representation to load - options: Additional settings dictionary - """ - - libpath = self.fname - asset = context["asset"]["name"] - subset = context["subset"]["name"] - - lib_container = plugin.asset_name( - asset, subset - ) - unique_number = plugin.get_unique_number( - asset, subset - ) - namespace = namespace or f"{asset}_{unique_number}" - container_name = plugin.asset_name( - asset, subset, unique_number - ) - - container = bpy.data.collections.new(lib_container) - container.name = container_name - blender.pipeline.containerise_existing( - container, - name, - namespace, - context, - self.__class__.__name__, - ) - - container_metadata = container.get( - blender.pipeline.AVALON_PROPERTY) - - container_metadata["libpath"] = libpath - container_metadata["lib_container"] = lib_container - - obj_container = self._process( - libpath, container_name, None) - - container_metadata["obj_container"] = obj_container - - # Save the list of objects in the metadata container - container_metadata["objects"] = obj_container.all_objects - - nodes = list(container.objects) - nodes.append(container) - self[:] = nodes - return nodes - - def update(self, container: Dict, representation: Dict): - """Update the loaded asset. - - This will remove all objects of the current collection, load the new - ones and add them to the collection. - If the objects of the collection are used in another collection they - will not be removed, only unlinked. Normally this should not be the - case though. - - Warning: - No nested collections are supported at the moment! - """ - collection = bpy.data.collections.get( - container["objectName"] - ) - libpath = Path(api.get_representation_path(representation)) - extension = libpath.suffix.lower() - - self.log.info( - "Container: %s\nRepresentation: %s", - pformat(container, indent=2), - pformat(representation, indent=2), - ) - - assert collection, ( - f"The asset is not loaded: {container['objectName']}" - ) - assert not (collection.children), ( - "Nested collections are not supported." - ) - assert libpath, ( - "No existing library file found for {container['objectName']}" - ) - assert libpath.is_file(), ( - f"The file doesn't exist: {libpath}" - ) - assert extension in plugin.VALID_EXTENSIONS, ( - f"Unsupported file: {libpath}" - ) - - collection_metadata = collection.get( - blender.pipeline.AVALON_PROPERTY) - collection_libpath = collection_metadata["libpath"] - - obj_container = plugin.get_local_collection_with_name( - collection_metadata["obj_container"].name - ) - objects = obj_container.all_objects - - container_name = obj_container.name - - normalized_collection_libpath = ( - str(Path(bpy.path.abspath(collection_libpath)).resolve()) - ) - normalized_libpath = ( - str(Path(bpy.path.abspath(str(libpath))).resolve()) - ) - self.log.debug( - "normalized_collection_libpath:\n %s\nnormalized_libpath:\n %s", - normalized_collection_libpath, - normalized_libpath, - ) - if normalized_collection_libpath == normalized_libpath: - self.log.info("Library already loaded, not updating...") - return - - parent = plugin.get_parent_collection(obj_container) - - self._remove(objects, obj_container) - - obj_container = self._process( - str(libpath), container_name, parent) - - collection_metadata["obj_container"] = obj_container - collection_metadata["objects"] = obj_container.all_objects - collection_metadata["libpath"] = str(libpath) - collection_metadata["representation"] = str(representation["_id"]) - - def remove(self, container: Dict) -> bool: - """Remove an existing container from a Blender scene. - - Arguments: - container (openpype:container-1.0): Container to remove, - from `host.ls()`. - - Returns: - bool: Whether the container was deleted. - - Warning: - No nested collections are supported at the moment! - """ - collection = bpy.data.collections.get( - container["objectName"] - ) - if not collection: - return False - assert not (collection.children), ( - "Nested collections are not supported." - ) - - collection_metadata = collection.get( - blender.pipeline.AVALON_PROPERTY) - - obj_container = plugin.get_local_collection_with_name( - collection_metadata["obj_container"].name - ) - objects = obj_container.all_objects - - self._remove(objects, obj_container) - - bpy.data.collections.remove(collection) - - return True diff --git a/openpype/hosts/blender/plugins/publish/extract_abc.py b/openpype/hosts/blender/plugins/publish/extract_abc.py index a7653d9f5a..a6315908fc 100644 --- a/openpype/hosts/blender/plugins/publish/extract_abc.py +++ b/openpype/hosts/blender/plugins/publish/extract_abc.py @@ -11,7 +11,7 @@ class ExtractABC(openpype.api.Extractor): label = "Extract ABC" hosts = ["blender"] - families = ["model"] + families = ["model", "pointcache"] optional = True def process(self, instance): From 5d150a73f65bcbfbef2018a90b8e6d9418a5f71c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Wed, 21 Apr 2021 17:09:49 +0200 Subject: [PATCH 099/180] fix first frame for sequence --- openpype/hosts/maya/plugins/publish/extract_redshift_proxy.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/hosts/maya/plugins/publish/extract_redshift_proxy.py b/openpype/hosts/maya/plugins/publish/extract_redshift_proxy.py index d0c6c4eb14..7c9e201986 100644 --- a/openpype/hosts/maya/plugins/publish/extract_redshift_proxy.py +++ b/openpype/hosts/maya/plugins/publish/extract_redshift_proxy.py @@ -74,6 +74,8 @@ class ExtractRedshiftProxy(openpype.api.Extractor): 'files': repr_files, "stagingDir": staging_dir, } + if anim_on: + representation["frameStart"] = instance.data["proxyFrameStart"] instance.data["representations"].append(representation) self.log.info("Extracted instance '%s' to: %s" From ee598d1521677d7d87635d6cc952452c3c64ebe1 Mon Sep 17 00:00:00 2001 From: jezscha Date: Wed, 21 Apr 2021 15:16:29 +0000 Subject: [PATCH 100/180] Create draft PR for #1391 From a8ac382c8a219a4941c6baebb3fc59f4cbb84d52 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 21 Apr 2021 17:17:54 +0200 Subject: [PATCH 101/180] Nuke: fixing menu and publish callback --- openpype/hosts/nuke/api/__init__.py | 2 +- openpype/hosts/nuke/api/menu.py | 28 ++++++++++++++-------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/openpype/hosts/nuke/api/__init__.py b/openpype/hosts/nuke/api/__init__.py index c80507e7ea..bd7a95f916 100644 --- a/openpype/hosts/nuke/api/__init__.py +++ b/openpype/hosts/nuke/api/__init__.py @@ -106,7 +106,7 @@ def on_pyblish_instance_toggled(instance, old_value, new_value): log.info("instance toggle: {}, old_value: {}, new_value:{} ".format( instance, old_value, new_value)) - from avalon.api.nuke import ( + from avalon.nuke import ( viewer_update_and_undo_stop, add_publish_knob ) diff --git a/openpype/hosts/nuke/api/menu.py b/openpype/hosts/nuke/api/menu.py index 2317066528..8434712138 100644 --- a/openpype/hosts/nuke/api/menu.py +++ b/openpype/hosts/nuke/api/menu.py @@ -26,9 +26,9 @@ def install(): menu.addCommand( name, workfiles.show, - index=(rm_item[0]) + index=2 ) - + menu.addSeparator(index=3) # replace reset resolution from avalon core to pype's name = "Reset Resolution" new_name = "Set Resolution" @@ -63,27 +63,27 @@ def install(): # add colorspace menu item name = "Set Colorspace" menu.addCommand( - name, lambda: WorkfileSettings().set_colorspace(), - index=(rm_item[0] + 2) + name, lambda: WorkfileSettings().set_colorspace() ) log.debug("Adding menu item: {}".format(name)) + # add item that applies all setting above + name = "Apply All Settings" + menu.addCommand( + name, + lambda: WorkfileSettings().set_context_settings() + ) + log.debug("Adding menu item: {}".format(name)) + + menu.addSeparator() + # add workfile builder menu item name = "Build Workfile" menu.addCommand( - name, lambda: BuildWorkfile().process(), - index=(rm_item[0] + 7) + name, lambda: BuildWorkfile().process() ) log.debug("Adding menu item: {}".format(name)) - # add item that applies all setting above - name = "Apply All Settings" - menu.addCommand( - name, - lambda: WorkfileSettings().set_context_settings(), - index=(rm_item[0] + 3) - ) - log.debug("Adding menu item: {}".format(name)) # adding shortcuts add_shortcuts_from_presets() From b40579a861752fb5736884e84038f834367dd145 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 21 Apr 2021 17:19:18 +0200 Subject: [PATCH 102/180] Hound: suggestion --- openpype/hosts/nuke/api/menu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/api/menu.py b/openpype/hosts/nuke/api/menu.py index 8434712138..021ea04159 100644 --- a/openpype/hosts/nuke/api/menu.py +++ b/openpype/hosts/nuke/api/menu.py @@ -67,7 +67,7 @@ def install(): ) log.debug("Adding menu item: {}".format(name)) - # add item that applies all setting above + # add item that applies all setting above name = "Apply All Settings" menu.addCommand( name, From 69845c3e02a1688c32ad2e1f1038f7dc3afffcd4 Mon Sep 17 00:00:00 2001 From: jezscha Date: Wed, 21 Apr 2021 15:22:15 +0000 Subject: [PATCH 103/180] Create draft PR for #1393 From 265dd2fcc16b8a9f885d8a1b10f8613a0500d81d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 21 Apr 2021 18:35:58 +0200 Subject: [PATCH 104/180] maya: clear old code for settings deadline submiter --- .../defaults/project_settings/maya.json | 4 --- .../schemas/schema_maya_publish.json | 28 ------------------- 2 files changed, 32 deletions(-) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index dfece74f80..8600e49518 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -382,10 +382,6 @@ "optional": true, "active": true, "bake_attributes": [] - }, - "MayaSubmitDeadline": { - "enabled": true, - "tile_assembler_plugin": "DraftTileAssembler" } }, "load": { diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json index 9d2e39edde..95b02a7936 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json @@ -283,34 +283,6 @@ "is_list": true } ] - }, - { - "type": "dict", - "collapsible": true, - "key": "MayaSubmitDeadline", - "label": "Submit maya job to deadline", - "checkbox_key": "enabled", - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, - { - "type": "enum", - "key": "tile_assembler_plugin", - "label": "Tile Assembler Plugin", - "multiselection": false, - "enum_items": [ - { - "DraftTileAssembler": "Draft Tile Assembler" - }, - { - "oiio": "Open Image IO" - } - ] - } - ] } ] } From 6f1c985ceccfb51225509267093f5a5a3c46e354 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 21 Apr 2021 18:36:16 +0200 Subject: [PATCH 105/180] nuke: clear old code for settings deadline submiter --- .../defaults/project_settings/nuke.json | 6 ---- .../schemas/schema_nuke_publish.json | 29 ------------------- 2 files changed, 35 deletions(-) diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index 852e041805..bb5232cea7 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -86,12 +86,6 @@ }, "ExtractSlateFrame": { "viewer_lut_raw": false - }, - "NukeSubmitDeadline": { - "deadline_priority": 50, - "deadline_pool": "", - "deadline_pool_secondary": "", - "deadline_chunk_size": 1 } }, "load": { diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json index 0e3770ac78..087e6c13a9 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json @@ -146,35 +146,6 @@ "label": "Viewer LUT raw" } ] - }, - { - "type": "dict", - "collapsible": true, - "key": "NukeSubmitDeadline", - "label": "NukeSubmitDeadline", - "is_group": true, - "children": [ - { - "type": "number", - "key": "deadline_priority", - "label": "deadline_priority" - }, - { - "type": "text", - "key": "deadline_pool", - "label": "deadline_pool" - }, - { - "type": "text", - "key": "deadline_pool_secondary", - "label": "deadline_pool_secondary" - }, - { - "type": "number", - "key": "deadline_chunk_size", - "label": "deadline_chunk_size" - } - ] } ] } From 4ddb9c4c3fcd3f6cf5293aa752dcd44a17b3632a Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 21 Apr 2021 18:37:02 +0200 Subject: [PATCH 106/180] nuke: adding deadline support for gpu rendering fix old name of argument --- .../deadline/plugins/publish/submit_nuke_deadline.py | 7 ++++++- openpype/settings/defaults/project_settings/deadline.json | 3 ++- .../schemas/projects_schema/schema_project_deadline.json | 5 +++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py index 2e30e624ef..7faa3393e5 100644 --- a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py @@ -31,6 +31,7 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): group = "" department = "" limit_groups = {} + use_gpu = False def process(self, instance): instance.data["toBeRenderedOn"] = "deadline" @@ -206,6 +207,10 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): # Resolve relative references "ProjectPath": script_path, "AWSAssetFile0": render_path, + + # using GPU by default + "UseGpu": self.use_gpu, + # Only the specific write node is rendered. "WriteNode": exe_node_name }, @@ -375,7 +380,7 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): list: captured groups list """ captured_groups = [] - for lg_name, list_node_class in self.deadline_limit_groups.items(): + for lg_name, list_node_class in self.limit_groups.items(): for node_class in list_node_class: for node in nuke.allNodes(recurseGroups=True): # ignore all nodes not member of defined class diff --git a/openpype/settings/defaults/project_settings/deadline.json b/openpype/settings/defaults/project_settings/deadline.json index 9ff551491c..905ba68d60 100644 --- a/openpype/settings/defaults/project_settings/deadline.json +++ b/openpype/settings/defaults/project_settings/deadline.json @@ -21,7 +21,8 @@ "secondary_pool": "", "group": "", "department": "", - "limit_groups": {} + "limit_groups": {}, + "use_gpu": true }, "HarmonySubmitDeadline": { "enabled": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json b/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json index f46221ba63..1346fb3dad 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json @@ -128,6 +128,11 @@ "key": "department", "label": "Department" }, + { + "type": "boolean", + "key": "use_gpu", + "label": "Use GPU" + }, { "type": "dict-modifiable", "key": "limit_groups", From 88feba27243e7681e85bd14dc71990ae801a7655 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 21 Apr 2021 17:48:17 +0100 Subject: [PATCH 107/180] Hound fixes --- openpype/hosts/blender/plugins/load/load_abc.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/blender/plugins/load/load_abc.py b/openpype/hosts/blender/plugins/load/load_abc.py index 9e20ccabbc..4248cffd69 100644 --- a/openpype/hosts/blender/plugins/load/load_abc.py +++ b/openpype/hosts/blender/plugins/load/load_abc.py @@ -1,6 +1,5 @@ """Load an asset in Blender from an Alembic file.""" -import logging from pathlib import Path from pprint import pformat from typing import Dict, List, Optional @@ -244,4 +243,4 @@ class CacheModelLoader(plugin.AssetLoader): bpy.data.collections.remove(collection) - return True \ No newline at end of file + return True From 6742fc89009642df555e20aefdaad736a55da637 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 21 Apr 2021 21:42:10 +0200 Subject: [PATCH 108/180] Hound --- openpype/modules/sync_server/tray/models.py | 14 ++++++++------ openpype/modules/sync_server/tray/widgets.py | 12 +++++------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/openpype/modules/sync_server/tray/models.py b/openpype/modules/sync_server/tray/models.py index 3ee372d27d..8e97f5207d 100644 --- a/openpype/modules/sync_server/tray/models.py +++ b/openpype/modules/sync_server/tray/models.py @@ -6,7 +6,6 @@ from Qt import QtCore from Qt.QtCore import Qt from avalon.tools.delegates import pretty_timestamp -from avalon.vendor import qtawesome from openpype.lib import PypeLogger @@ -71,7 +70,6 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): if section >= len(self.COLUMN_LABELS): return - name = self.COLUMN_LABELS[section][0] if role == Qt.DisplayRole: if orientation == Qt.Horizontal: return self.COLUMN_LABELS[section][1] @@ -453,9 +451,11 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): if role == lib.FailedRole: if header_value == 'local_site': - return item.status == lib.STATUS[2] and item.local_progress < 1 + return item.status == lib.STATUS[2] and \ + item.local_progress < 1 if header_value == 'remote_site': - return item.status == lib.STATUS[2] and item.remote_progress < 1 + return item.status == lib.STATUS[2] and \ + item.remote_progress < 1 if role == Qt.DisplayRole: # because of ImageDelegate @@ -928,9 +928,11 @@ class SyncRepresentationDetailModel(_SyncRepresentationModel): if role == lib.FailedRole: if header_value == 'local_site': - return item.status == lib.STATUS[2] and item.local_progress < 1 + return item.status == lib.STATUS[2] and \ + item.local_progress < 1 if header_value == 'remote_site': - return item.status == lib.STATUS[2] and item.remote_progress < 1 + return item.status == lib.STATUS[2] and \ + item.remote_progress <1 if role == Qt.DisplayRole: # because of ImageDelegate diff --git a/openpype/modules/sync_server/tray/widgets.py b/openpype/modules/sync_server/tray/widgets.py index 9771d656ff..e8b276580a 100644 --- a/openpype/modules/sync_server/tray/widgets.py +++ b/openpype/modules/sync_server/tray/widgets.py @@ -95,7 +95,6 @@ class SyncProjectListWidget(ProjectListWidget): self.project_name = point_index.data(QtCore.Qt.DisplayRole) menu = QtWidgets.QMenu() - #menu.setStyleSheet(style.load_stylesheet()) actions_mapping = {} if self.sync_server.is_project_paused(self.project_name): @@ -269,7 +268,6 @@ class SyncRepresentationWidget(QtWidgets.QWidget): format(self.representation_id)) menu = QtWidgets.QMenu() - menu.setStyleSheet(style.load_stylesheet()) actions_mapping = {} actions_kwargs_mapping = {} @@ -594,7 +592,6 @@ class SyncRepresentationDetailWidget(QtWidgets.QWidget): self.item = self.model._data[point_index.row()] menu = QtWidgets.QMenu() - #menu.setStyleSheet(style.load_stylesheet()) actions_mapping = {} actions_kwargs_mapping = {} @@ -889,7 +886,8 @@ class HorizontalHeader(QtWidgets.QHeaderView): button = QtWidgets.QPushButton(icon, "", self) button.setFixedSize(24, 24) - button.setStyleSheet("QPushButton::menu-indicator{width:0px;}" + button.setStyleSheet( + "QPushButton::menu-indicator{width:0px;}" "QPushButton{border: none;background: transparent;}") button.clicked.connect(partial(self._get_menu, column_name, column_idx)) @@ -902,7 +900,7 @@ class HorizontalHeader(QtWidgets.QHeaderView): for i in range(len(self.header_cells)): cell_content = self.header_cells[i] cell_content.setGeometry(self.sectionViewportPosition(i), 0, - self.sectionSize(i)-1, self.height()) + self.sectionSize(i) - 1, self.height()) cell_content.show() @@ -1064,8 +1062,8 @@ class HorizontalHeader(QtWidgets.QHeaderView): Modifies 'self.checked_values' """ - checked = self.checked_values.get(column_name, - dict(self.menu_items_dict[column_name])) + copy_menu_items = dict(self.menu_items_dict[column_name]) + checked = self.checked_values.get(column_name, copy_menu_items) set_items = dict(values.items()) # prevent dict change during loop for value, label in set_items.items(): if state == 2 and label: # checked From 498151db82eba4656f669c3c9dc562c5e1483765 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 21 Apr 2021 22:15:50 +0200 Subject: [PATCH 109/180] Hound --- openpype/modules/sync_server/tray/models.py | 8 ++++---- openpype/modules/sync_server/tray/widgets.py | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/openpype/modules/sync_server/tray/models.py b/openpype/modules/sync_server/tray/models.py index 8e97f5207d..dc2094825e 100644 --- a/openpype/modules/sync_server/tray/models.py +++ b/openpype/modules/sync_server/tray/models.py @@ -452,10 +452,10 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): if role == lib.FailedRole: if header_value == 'local_site': return item.status == lib.STATUS[2] and \ - item.local_progress < 1 + item.local_progress < 1 if header_value == 'remote_site': return item.status == lib.STATUS[2] and \ - item.remote_progress < 1 + item.remote_progress < 1 if role == Qt.DisplayRole: # because of ImageDelegate @@ -929,10 +929,10 @@ class SyncRepresentationDetailModel(_SyncRepresentationModel): if role == lib.FailedRole: if header_value == 'local_site': return item.status == lib.STATUS[2] and \ - item.local_progress < 1 + item.local_progress < 1 if header_value == 'remote_site': return item.status == lib.STATUS[2] and \ - item.remote_progress <1 + item.remote_progress <1 if role == Qt.DisplayRole: # because of ImageDelegate diff --git a/openpype/modules/sync_server/tray/widgets.py b/openpype/modules/sync_server/tray/widgets.py index e8b276580a..2cdc99c671 100644 --- a/openpype/modules/sync_server/tray/widgets.py +++ b/openpype/modules/sync_server/tray/widgets.py @@ -1101,4 +1101,3 @@ class HorizontalHeader(QtWidgets.QHeaderView): pos_y, self.height(), self.height()) - From 8b00525f9d3da0efe8512513e75af01ed2ca44f3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 22 Apr 2021 10:20:56 +0200 Subject: [PATCH 110/180] also pass new values with metadata --- openpype/modules/ftrack/ftrack_module.py | 8 +++++--- openpype/modules/settings_action.py | 8 +++++--- openpype/settings/lib.py | 19 ++++++++++++++++--- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/openpype/modules/ftrack/ftrack_module.py b/openpype/modules/ftrack/ftrack_module.py index 60eed5c941..848f4cea82 100644 --- a/openpype/modules/ftrack/ftrack_module.py +++ b/openpype/modules/ftrack/ftrack_module.py @@ -130,7 +130,9 @@ class FtrackModule( if self.tray_module: self.tray_module.changed_user() - def on_system_settings_save(self, old_value, new_value, changes): + def on_system_settings_save( + self, old_value, new_value, changes, new_value_metadata + ): """Implementation of ISettingsChangeListener interface.""" try: session = self.create_ftrack_session() @@ -169,7 +171,7 @@ class FtrackModule( elif key == CUST_ATTR_TOOLS: tool_attribute = custom_attribute - app_manager = ApplicationManager(new_value) + app_manager = ApplicationManager(new_value_metadata) missing_attributes = [] if not app_attribute: missing_attributes.append(CUST_ATTR_APPLICATIONS) @@ -217,7 +219,7 @@ class FtrackModule( return def on_project_anatomy_save( - self, old_value, new_value, changes, project_name + self, old_value, new_value, changes, project_name, new_value_metadata ): """Implementation of ISettingsChangeListener interface.""" if not project_name: diff --git a/openpype/modules/settings_action.py b/openpype/modules/settings_action.py index 371e190c12..3f7cb8c3ba 100644 --- a/openpype/modules/settings_action.py +++ b/openpype/modules/settings_action.py @@ -16,18 +16,20 @@ class ISettingsChangeListener: } """ @abstractmethod - def on_system_settings_save(self, old_value, new_value, changes): + def on_system_settings_save( + self, old_value, new_value, changes, new_value_metadata + ): pass @abstractmethod def on_project_settings_save( - self, old_value, new_value, changes, project_name + self, old_value, new_value, changes, project_name, new_value_metadata ): pass @abstractmethod def on_project_anatomy_save( - self, old_value, new_value, changes, project_name + self, old_value, new_value, changes, project_name, new_value_metadata ): pass diff --git a/openpype/settings/lib.py b/openpype/settings/lib.py index 9c05c8e86c..f61166fa69 100644 --- a/openpype/settings/lib.py +++ b/openpype/settings/lib.py @@ -119,6 +119,7 @@ def save_studio_settings(data): old_data = get_system_settings() default_values = get_default_settings()[SYSTEM_SETTINGS_KEY] new_data = apply_overrides(default_values, copy.deepcopy(data)) + new_data_with_metadata = copy.deepcopy(new_data) clear_metadata_from_settings(new_data) changes = calculate_changes(old_data, new_data) @@ -128,7 +129,9 @@ def save_studio_settings(data): for module in modules_manager.get_enabled_modules(): if isinstance(module, ISettingsChangeListener): try: - module.on_system_settings_save(old_data, new_data, changes) + module.on_system_settings_save( + old_data, new_data, changes, new_data_with_metadata + ) except SaveWarningExc as exc: warnings.extend(exc.warnings) @@ -173,6 +176,7 @@ def save_project_settings(project_name, overrides): old_data = get_default_project_settings(exclude_locals=True) new_data = apply_overrides(default_values, copy.deepcopy(overrides)) + new_data_with_metadata = copy.deepcopy(new_data) clear_metadata_from_settings(new_data) changes = calculate_changes(old_data, new_data) @@ -182,7 +186,11 @@ def save_project_settings(project_name, overrides): if isinstance(module, ISettingsChangeListener): try: module.on_project_settings_save( - old_data, new_data, project_name, changes + old_data, + new_data, + project_name, + changes, + new_data_with_metadata ) except SaveWarningExc as exc: warnings.extend(exc.warnings) @@ -229,6 +237,7 @@ def save_project_anatomy(project_name, anatomy_data): old_data = get_default_anatomy_settings(exclude_locals=True) new_data = apply_overrides(default_values, copy.deepcopy(anatomy_data)) + new_data_with_metadata = copy.deepcopy(new_data) clear_metadata_from_settings(new_data) changes = calculate_changes(old_data, new_data) @@ -238,7 +247,11 @@ def save_project_anatomy(project_name, anatomy_data): if isinstance(module, ISettingsChangeListener): try: module.on_project_anatomy_save( - old_data, new_data, changes, project_name + old_data, + new_data, + changes, + project_name, + new_data_with_metadata ) except SaveWarningExc as exc: warnings.extend(exc.warnings) From 02447101bf4e83b2229279ebb602fdfe1a4a7474 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 22 Apr 2021 10:43:19 +0200 Subject: [PATCH 111/180] SyncServer GUI - small fixes in Status filtering Clear text in column filtering --- openpype/modules/sync_server/tray/lib.py | 5 +++- openpype/modules/sync_server/tray/models.py | 2 +- openpype/modules/sync_server/tray/widgets.py | 30 ++++++++++++-------- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/openpype/modules/sync_server/tray/lib.py b/openpype/modules/sync_server/tray/lib.py index 41b0eb43f9..3597213b31 100644 --- a/openpype/modules/sync_server/tray/lib.py +++ b/openpype/modules/sync_server/tray/lib.py @@ -64,8 +64,11 @@ class PredefinedSetFilter(AbstractColumnFilter): def __init__(self, column_name, values): super().__init__(column_name) - self._search_variants = ['text', 'checkbox'] + self._search_variants = ['checkbox'] self._values = values + if self._values and \ + list(self._values.keys())[0] == list(self._values.values())[0]: + self._search_variants.append('text') def values(self): return {k: v for k, v in self._values.items()} diff --git a/openpype/modules/sync_server/tray/models.py b/openpype/modules/sync_server/tray/models.py index dc2094825e..981299c6cf 100644 --- a/openpype/modules/sync_server/tray/models.py +++ b/openpype/modules/sync_server/tray/models.py @@ -932,7 +932,7 @@ class SyncRepresentationDetailModel(_SyncRepresentationModel): item.local_progress < 1 if header_value == 'remote_site': return item.status == lib.STATUS[2] and \ - item.remote_progress <1 + item.remote_progress < 1 if role == Qt.DisplayRole: # because of ImageDelegate diff --git a/openpype/modules/sync_server/tray/widgets.py b/openpype/modules/sync_server/tray/widgets.py index 2cdc99c671..6d8348becb 100644 --- a/openpype/modules/sync_server/tray/widgets.py +++ b/openpype/modules/sync_server/tray/widgets.py @@ -165,13 +165,16 @@ class SyncRepresentationWidget(QtWidgets.QWidget): self.representation_id = None self.site_name = None # to pause/unpause representation - self.filter = QtWidgets.QLineEdit() - self.filter.setPlaceholderText("Filter representations..") + self.txt_filter = QtWidgets.QLineEdit() + self.txt_filter.setPlaceholderText("Quick filter representations..") + self.txt_filter.setClearButtonEnabled(True) + self.txt_filter.addAction(qtawesome.icon("fa.filter", color="gray"), + QtWidgets.QLineEdit.LeadingPosition) self._scrollbar_pos = None top_bar_layout = QtWidgets.QHBoxLayout() - top_bar_layout.addWidget(self.filter) + top_bar_layout.addWidget(self.txt_filter) self.table_view = QtWidgets.QTableView() headers = [item[0] for item in self.default_widths] @@ -202,8 +205,8 @@ class SyncRepresentationWidget(QtWidgets.QWidget): layout.addWidget(self.table_view) self.table_view.doubleClicked.connect(self._double_clicked) - self.filter.textChanged.connect(lambda: model.set_word_filter( - self.filter.text())) + self.txt_filter.textChanged.connect(lambda: model.set_word_filter( + self.txt_filter.text())) self.table_view.customContextMenuRequested.connect( self._on_context_menu) @@ -489,13 +492,16 @@ class SyncRepresentationDetailWidget(QtWidgets.QWidget): self._selected_id = None - self.filter = QtWidgets.QLineEdit() - self.filter.setPlaceholderText("Filter representation..") + self.txt_filter = QtWidgets.QLineEdit() + self.txt_filter.setPlaceholderText("Quick filter representation..") + self.txt_filter.setClearButtonEnabled(True) + self.txt_filter.addAction(qtawesome.icon("fa.filter", color="gray"), + QtWidgets.QLineEdit.LeadingPosition) self._scrollbar_pos = None top_bar_layout = QtWidgets.QHBoxLayout() - top_bar_layout.addWidget(self.filter) + top_bar_layout.addWidget(self.txt_filter) table_view = QtWidgets.QTableView() headers = [item[0] for item in self.default_widths] @@ -542,8 +548,8 @@ class SyncRepresentationDetailWidget(QtWidgets.QWidget): self.table_view = table_view - self.filter.textChanged.connect(lambda: model.set_word_filter( - self.filter.text())) + self.txt_filter.textChanged.connect(lambda: model.set_word_filter( + self.txt_filter.text())) table_view.customContextMenuRequested.connect(self._on_context_menu) model.refresh_started.connect(self._save_scrollbar) @@ -975,10 +981,10 @@ class HorizontalHeader(QtWidgets.QHeaderView): QtWidgets.QLineEdit.LeadingPosition) line_edit.setFixedHeight(line_edit.height()) - txt = "Type..." + txt = "" if self.checked_values.get(column_name): txt = list(self.checked_values.get(column_name).keys())[0] - line_edit.setPlaceholderText(txt) + line_edit.setText(txt) action_le = QtWidgets.QWidgetAction(menu) action_le.setDefaultWidget(line_edit) From 5f36346079f82bd57bc042d8825716de30293806 Mon Sep 17 00:00:00 2001 From: kalisp Date: Thu, 22 Apr 2021 08:51:22 +0000 Subject: [PATCH 112/180] Create draft PR for #1336 From 94a0e79e44e0fafc4f555ba7b5210259edd2bef5 Mon Sep 17 00:00:00 2001 From: antirotor Date: Thu, 22 Apr 2021 13:45:17 +0000 Subject: [PATCH 113/180] Create draft PR for #1397 From 673d589653ca211c4185363e0dcad3858627e25b Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 22 Apr 2021 16:08:29 +0200 Subject: [PATCH 114/180] specifically list history of VrayPluginNodeMtl --- openpype/hosts/maya/plugins/publish/collect_look.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openpype/hosts/maya/plugins/publish/collect_look.py b/openpype/hosts/maya/plugins/publish/collect_look.py index 238213c000..bf24b463ac 100644 --- a/openpype/hosts/maya/plugins/publish/collect_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_look.py @@ -348,6 +348,13 @@ class CollectLook(pyblish.api.InstancePlugin): history = [] for material in materials: history.extend(cmds.listHistory(material)) + + # handle VrayPluginNodeMtl node - see #1397 + vray_plugin_nodes = cmds.ls( + history, type="VRayPluginNodeMtl", long=True) + for vray_node in vray_plugin_nodes: + history.extend(cmds.listHistory(vray_node)) + files = cmds.ls(history, type="file", long=True) files.extend(cmds.ls(history, type="aiImage", long=True)) From 01966abfef38cb3c65dba99139ef585e489879fb Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 22 Apr 2021 15:10:29 +0100 Subject: [PATCH 115/180] Loading of alembics as Static or Skeletal Mesh, or Geometry Cache --- .../load/load_alembic_geometrycache.py | 162 ++++++++++++++++++ .../plugins/load/load_alembic_skeletalmesh.py | 156 +++++++++++++++++ .../plugins/load/load_alembic_staticmesh.py | 156 +++++++++++++++++ .../unreal/plugins/load/load_staticmeshfbx.py | 151 ---------------- 4 files changed, 474 insertions(+), 151 deletions(-) create mode 100644 openpype/hosts/unreal/plugins/load/load_alembic_geometrycache.py create mode 100644 openpype/hosts/unreal/plugins/load/load_alembic_skeletalmesh.py create mode 100644 openpype/hosts/unreal/plugins/load/load_alembic_staticmesh.py diff --git a/openpype/hosts/unreal/plugins/load/load_alembic_geometrycache.py b/openpype/hosts/unreal/plugins/load/load_alembic_geometrycache.py new file mode 100644 index 0000000000..a9279bf6e0 --- /dev/null +++ b/openpype/hosts/unreal/plugins/load/load_alembic_geometrycache.py @@ -0,0 +1,162 @@ +import os + +from avalon import api, pipeline +from avalon.unreal import lib +from avalon.unreal import pipeline as unreal_pipeline +import unreal + + +class PointCacheAlembicLoader(api.Loader): + """Load Point Cache from Alembic""" + + families = ["model", "pointcache"] + label = "Import Alembic Point Cache" + representations = ["abc"] + icon = "cube" + color = "orange" + + def load(self, context, name, namespace, data): + """ + Load and containerise representation into Content Browser. + + This is two step process. First, import FBX to temporary path and + then call `containerise()` on it - this moves all content to new + directory and then it will create AssetContainer there and imprint it + with metadata. This will mark this path as container. + + Args: + context (dict): application context + name (str): subset name + namespace (str): in Unreal this is basically path to container. + This is not passed here, so namespace is set + by `containerise()` because only then we know + real path. + data (dict): Those would be data to be imprinted. This is not used + now, data are imprinted by `containerise()`. + + Returns: + list(str): list of container content + """ + + # Create directory for asset and avalon container + root = "/Game/Avalon/Assets" + asset = context.get('asset').get('name') + suffix = "_CON" + if asset: + asset_name = "{}_{}".format(asset, name) + else: + asset_name = "{}".format(name) + + tools = unreal.AssetToolsHelpers().get_asset_tools() + asset_dir, container_name = tools.create_unique_asset_name( + "{}/{}/{}".format(root, asset, name), suffix="") + + container_name += suffix + + unreal.EditorAssetLibrary.make_directory(asset_dir) + + task = unreal.AssetImportTask() + + task.set_editor_property('filename', self.fname) + task.set_editor_property('destination_path', asset_dir) + task.set_editor_property('destination_name', asset_name) + task.set_editor_property('replace_existing', False) + task.set_editor_property('automated', True) + task.set_editor_property('save', True) + + # set import options here + # Unreal 4.24 ignores the settings. It works with Unreal 4.26 + options = unreal.AbcImportSettings() + options.set_editor_property( + 'import_type', unreal.AlembicImportType.GEOMETRY_CACHE) + + options.geometry_cache_settings.set_editor_property( + 'flatten_tracks', False) + + task.options = options + unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task]) # noqa: E501 + + # Create Asset Container + lib.create_avalon_container( + container=container_name, path=asset_dir) + + data = { + "schema": "openpype:container-2.0", + "id": pipeline.AVALON_CONTAINER_ID, + "asset": asset, + "namespace": asset_dir, + "container_name": container_name, + "asset_name": asset_name, + "loader": str(self.__class__.__name__), + "representation": context["representation"]["_id"], + "parent": context["representation"]["parent"], + "family": context["representation"]["context"]["family"] + } + unreal_pipeline.imprint( + "{}/{}".format(asset_dir, container_name), data) + + asset_content = unreal.EditorAssetLibrary.list_assets( + asset_dir, recursive=True, include_folder=True + ) + + for a in asset_content: + unreal.EditorAssetLibrary.save_asset(a) + + return asset_content + + def update(self, container, representation): + name = container["asset_name"] + source_path = api.get_representation_path(representation) + destination_path = container["namespace"] + + task = unreal.AssetImportTask() + + task.set_editor_property('filename', source_path) + task.set_editor_property('destination_path', destination_path) + # strip suffix + task.set_editor_property('destination_name', name) + task.set_editor_property('replace_existing', True) + task.set_editor_property('automated', True) + task.set_editor_property('save', True) + + # set import options here + # Unreal 4.24 ignores the settings. It works with Unreal 4.26 + options = unreal.AbcImportSettings() + options.set_editor_property( + 'import_type', unreal.AlembicImportType.GEOMETRY_CACHE) + + options.geometry_cache_settings.set_editor_property( + 'flatten_tracks', False) + + task.options = options + # do import fbx and replace existing data + unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task]) + container_path = "{}/{}".format(container["namespace"], + container["objectName"]) + # update metadata + unreal_pipeline.imprint( + container_path, + { + "representation": str(representation["_id"]), + "parent": str(representation["parent"]) + }) + + asset_content = unreal.EditorAssetLibrary.list_assets( + destination_path, recursive=True, include_folder=True + ) + + for a in asset_content: + unreal.EditorAssetLibrary.save_asset(a) + + def remove(self, container): + path = container["namespace"] + parent_path = os.path.dirname(path) + + unreal.EditorAssetLibrary.delete_directory(path) + + asset_content = unreal.EditorAssetLibrary.list_assets( + parent_path, recursive=False + ) + + if len(asset_content) == 0: + unreal.EditorAssetLibrary.delete_directory(parent_path) diff --git a/openpype/hosts/unreal/plugins/load/load_alembic_skeletalmesh.py b/openpype/hosts/unreal/plugins/load/load_alembic_skeletalmesh.py new file mode 100644 index 0000000000..b652af0b89 --- /dev/null +++ b/openpype/hosts/unreal/plugins/load/load_alembic_skeletalmesh.py @@ -0,0 +1,156 @@ +import os + +from avalon import api, pipeline +from avalon.unreal import lib +from avalon.unreal import pipeline as unreal_pipeline +import unreal + + +class SkeletalMeshAlembicLoader(api.Loader): + """Load Unreal SkeletalMesh from Alembic""" + + families = ["pointcache"] + label = "Import Alembic Skeletal Mesh" + representations = ["abc"] + icon = "cube" + color = "orange" + + def load(self, context, name, namespace, data): + """ + Load and containerise representation into Content Browser. + + This is two step process. First, import FBX to temporary path and + then call `containerise()` on it - this moves all content to new + directory and then it will create AssetContainer there and imprint it + with metadata. This will mark this path as container. + + Args: + context (dict): application context + name (str): subset name + namespace (str): in Unreal this is basically path to container. + This is not passed here, so namespace is set + by `containerise()` because only then we know + real path. + data (dict): Those would be data to be imprinted. This is not used + now, data are imprinted by `containerise()`. + + Returns: + list(str): list of container content + """ + + # Create directory for asset and avalon container + root = "/Game/Avalon/Assets" + asset = context.get('asset').get('name') + suffix = "_CON" + if asset: + asset_name = "{}_{}".format(asset, name) + else: + asset_name = "{}".format(name) + + tools = unreal.AssetToolsHelpers().get_asset_tools() + asset_dir, container_name = tools.create_unique_asset_name( + "{}/{}/{}".format(root, asset, name), suffix="") + + container_name += suffix + + unreal.EditorAssetLibrary.make_directory(asset_dir) + + task = unreal.AssetImportTask() + + task.set_editor_property('filename', self.fname) + task.set_editor_property('destination_path', asset_dir) + task.set_editor_property('destination_name', asset_name) + task.set_editor_property('replace_existing', False) + task.set_editor_property('automated', True) + task.set_editor_property('save', True) + + # set import options here + # Unreal 4.24 ignores the settings. It works with Unreal 4.26 + options = unreal.AbcImportSettings() + options.set_editor_property( + 'import_type', unreal.AlembicImportType.SKELETAL) + + task.options = options + unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task]) # noqa: E501 + + # Create Asset Container + lib.create_avalon_container( + container=container_name, path=asset_dir) + + data = { + "schema": "openpype:container-2.0", + "id": pipeline.AVALON_CONTAINER_ID, + "asset": asset, + "namespace": asset_dir, + "container_name": container_name, + "asset_name": asset_name, + "loader": str(self.__class__.__name__), + "representation": context["representation"]["_id"], + "parent": context["representation"]["parent"], + "family": context["representation"]["context"]["family"] + } + unreal_pipeline.imprint( + "{}/{}".format(asset_dir, container_name), data) + + asset_content = unreal.EditorAssetLibrary.list_assets( + asset_dir, recursive=True, include_folder=True + ) + + for a in asset_content: + unreal.EditorAssetLibrary.save_asset(a) + + return asset_content + + def update(self, container, representation): + name = container["asset_name"] + source_path = api.get_representation_path(representation) + destination_path = container["namespace"] + + task = unreal.AssetImportTask() + + task.set_editor_property('filename', source_path) + task.set_editor_property('destination_path', destination_path) + # strip suffix + task.set_editor_property('destination_name', name) + task.set_editor_property('replace_existing', True) + task.set_editor_property('automated', True) + task.set_editor_property('save', True) + + # set import options here + # Unreal 4.24 ignores the settings. It works with Unreal 4.26 + options = unreal.AbcImportSettings() + options.set_editor_property( + 'import_type', unreal.AlembicImportType.SKELETAL) + + task.options = options + # do import fbx and replace existing data + unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task]) + container_path = "{}/{}".format(container["namespace"], + container["objectName"]) + # update metadata + unreal_pipeline.imprint( + container_path, + { + "representation": str(representation["_id"]), + "parent": str(representation["parent"]) + }) + + asset_content = unreal.EditorAssetLibrary.list_assets( + destination_path, recursive=True, include_folder=True + ) + + for a in asset_content: + unreal.EditorAssetLibrary.save_asset(a) + + def remove(self, container): + path = container["namespace"] + parent_path = os.path.dirname(path) + + unreal.EditorAssetLibrary.delete_directory(path) + + asset_content = unreal.EditorAssetLibrary.list_assets( + parent_path, recursive=False + ) + + if len(asset_content) == 0: + unreal.EditorAssetLibrary.delete_directory(parent_path) diff --git a/openpype/hosts/unreal/plugins/load/load_alembic_staticmesh.py b/openpype/hosts/unreal/plugins/load/load_alembic_staticmesh.py new file mode 100644 index 0000000000..12b9320f72 --- /dev/null +++ b/openpype/hosts/unreal/plugins/load/load_alembic_staticmesh.py @@ -0,0 +1,156 @@ +import os + +from avalon import api, pipeline +from avalon.unreal import lib +from avalon.unreal import pipeline as unreal_pipeline +import unreal + + +class StaticMeshAlembicLoader(api.Loader): + """Load Unreal StaticMesh from Alembic""" + + families = ["model"] + label = "Import Alembic Static Mesh" + representations = ["abc"] + icon = "cube" + color = "orange" + + def load(self, context, name, namespace, data): + """ + Load and containerise representation into Content Browser. + + This is two step process. First, import FBX to temporary path and + then call `containerise()` on it - this moves all content to new + directory and then it will create AssetContainer there and imprint it + with metadata. This will mark this path as container. + + Args: + context (dict): application context + name (str): subset name + namespace (str): in Unreal this is basically path to container. + This is not passed here, so namespace is set + by `containerise()` because only then we know + real path. + data (dict): Those would be data to be imprinted. This is not used + now, data are imprinted by `containerise()`. + + Returns: + list(str): list of container content + """ + + # Create directory for asset and avalon container + root = "/Game/Avalon/Assets" + asset = context.get('asset').get('name') + suffix = "_CON" + if asset: + asset_name = "{}_{}".format(asset, name) + else: + asset_name = "{}".format(name) + + tools = unreal.AssetToolsHelpers().get_asset_tools() + asset_dir, container_name = tools.create_unique_asset_name( + "{}/{}/{}".format(root, asset, name), suffix="") + + container_name += suffix + + unreal.EditorAssetLibrary.make_directory(asset_dir) + + task = unreal.AssetImportTask() + + task.set_editor_property('filename', self.fname) + task.set_editor_property('destination_path', asset_dir) + task.set_editor_property('destination_name', asset_name) + task.set_editor_property('replace_existing', False) + task.set_editor_property('automated', True) + task.set_editor_property('save', True) + + # set import options here + # Unreal 4.24 ignores the settings. It works with Unreal 4.26 + options = unreal.AbcImportSettings() + options.set_editor_property( + 'import_type', unreal.AlembicImportType.STATIC_MESH) + + task.options = options + unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task]) # noqa: E501 + + # Create Asset Container + lib.create_avalon_container( + container=container_name, path=asset_dir) + + data = { + "schema": "openpype:container-2.0", + "id": pipeline.AVALON_CONTAINER_ID, + "asset": asset, + "namespace": asset_dir, + "container_name": container_name, + "asset_name": asset_name, + "loader": str(self.__class__.__name__), + "representation": context["representation"]["_id"], + "parent": context["representation"]["parent"], + "family": context["representation"]["context"]["family"] + } + unreal_pipeline.imprint( + "{}/{}".format(asset_dir, container_name), data) + + asset_content = unreal.EditorAssetLibrary.list_assets( + asset_dir, recursive=True, include_folder=True + ) + + for a in asset_content: + unreal.EditorAssetLibrary.save_asset(a) + + return asset_content + + def update(self, container, representation): + name = container["asset_name"] + source_path = api.get_representation_path(representation) + destination_path = container["namespace"] + + task = unreal.AssetImportTask() + + task.set_editor_property('filename', source_path) + task.set_editor_property('destination_path', destination_path) + # strip suffix + task.set_editor_property('destination_name', name) + task.set_editor_property('replace_existing', True) + task.set_editor_property('automated', True) + task.set_editor_property('save', True) + + # set import options here + # Unreal 4.24 ignores the settings. It works with Unreal 4.26 + options = unreal.AbcImportSettings() + options.set_editor_property( + 'import_type', unreal.AlembicImportType.STATIC_MESH) + + task.options = options + # do import fbx and replace existing data + unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task]) + container_path = "{}/{}".format(container["namespace"], + container["objectName"]) + # update metadata + unreal_pipeline.imprint( + container_path, + { + "representation": str(representation["_id"]), + "parent": str(representation["parent"]) + }) + + asset_content = unreal.EditorAssetLibrary.list_assets( + destination_path, recursive=True, include_folder=True + ) + + for a in asset_content: + unreal.EditorAssetLibrary.save_asset(a) + + def remove(self, container): + path = container["namespace"] + parent_path = os.path.dirname(path) + + unreal.EditorAssetLibrary.delete_directory(path) + + asset_content = unreal.EditorAssetLibrary.list_assets( + parent_path, recursive=False + ) + + if len(asset_content) == 0: + unreal.EditorAssetLibrary.delete_directory(parent_path) diff --git a/openpype/hosts/unreal/plugins/load/load_staticmeshfbx.py b/openpype/hosts/unreal/plugins/load/load_staticmeshfbx.py index 07961a9140..dcb566fa4c 100644 --- a/openpype/hosts/unreal/plugins/load/load_staticmeshfbx.py +++ b/openpype/hosts/unreal/plugins/load/load_staticmeshfbx.py @@ -1,7 +1,6 @@ import os from avalon import api, pipeline -from avalon import unreal as avalon_unreal from avalon.unreal import lib from avalon.unreal import pipeline as unreal_pipeline import unreal @@ -155,153 +154,3 @@ class StaticMeshFBXLoader(api.Loader): if len(asset_content) == 0: unreal.EditorAssetLibrary.delete_directory(parent_path) - - -class StaticMeshAlembicLoader(api.Loader): - """Load Unreal StaticMesh from Alembic""" - - families = ["model", "unrealStaticMesh"] - label = "Import Alembic Static Mesh" - representations = ["abc"] - icon = "cube" - color = "orange" - - def load(self, context, name, namespace, data): - """ - Load and containerise representation into Content Browser. - - This is two step process. First, import FBX to temporary path and - then call `containerise()` on it - this moves all content to new - directory and then it will create AssetContainer there and imprint it - with metadata. This will mark this path as container. - - Args: - context (dict): application context - name (str): subset name - namespace (str): in Unreal this is basically path to container. - This is not passed here, so namespace is set - by `containerise()` because only then we know - real path. - data (dict): Those would be data to be imprinted. This is not used - now, data are imprinted by `containerise()`. - - Returns: - list(str): list of container content - """ - - # Create directory for asset and avalon container - root = "/Game/Avalon/Assets" - asset = context.get('asset').get('name') - suffix = "_CON" - if asset: - asset_name = "{}_{}".format(asset, name) - else: - asset_name = "{}".format(name) - - tools = unreal.AssetToolsHelpers().get_asset_tools() - asset_dir, container_name = tools.create_unique_asset_name( - "{}/{}/{}".format(root, asset, name), suffix="") - - container_name += suffix - - unreal.EditorAssetLibrary.make_directory(asset_dir) - - task = unreal.AssetImportTask() - - task.set_editor_property('filename', self.fname) - task.set_editor_property('destination_path', asset_dir) - task.set_editor_property('destination_name', asset_name) - task.set_editor_property('replace_existing', False) - task.set_editor_property('automated', True) - task.set_editor_property('save', True) - - # set import options here - # Unreal 4.24 ignores the settings. It works with Unreal 4.26 - options = unreal.AbcImportSettings() - options.set_editor_property( - 'import_type', unreal.AlembicImportType.STATIC_MESH) - - task.options = options - unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task]) # noqa: E501 - - # Create Asset Container - lib.create_avalon_container( - container=container_name, path=asset_dir) - - data = { - "schema": "openpype:container-2.0", - "id": pipeline.AVALON_CONTAINER_ID, - "asset": asset, - "namespace": asset_dir, - "container_name": container_name, - "asset_name": asset_name, - "loader": str(self.__class__.__name__), - "representation": context["representation"]["_id"], - "parent": context["representation"]["parent"], - "family": context["representation"]["context"]["family"] - } - unreal_pipeline.imprint( - "{}/{}".format(asset_dir, container_name), data) - - asset_content = unreal.EditorAssetLibrary.list_assets( - asset_dir, recursive=True, include_folder=True - ) - - for a in asset_content: - unreal.EditorAssetLibrary.save_asset(a) - - return asset_content - - def update(self, container, representation): - name = container["asset_name"] - source_path = api.get_representation_path(representation) - destination_path = container["namespace"] - - task = unreal.AssetImportTask() - - task.set_editor_property('filename', source_path) - task.set_editor_property('destination_path', destination_path) - # strip suffix - task.set_editor_property('destination_name', name) - task.set_editor_property('replace_existing', True) - task.set_editor_property('automated', True) - task.set_editor_property('save', True) - - # set import options here - # Unreal 4.24 ignores the settings. It works with Unreal 4.26 - options = unreal.AbcImportSettings() - options.set_editor_property( - 'import_type', unreal.AlembicImportType.STATIC_MESH) - - task.options = options - # do import fbx and replace existing data - unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task]) - container_path = "{}/{}".format(container["namespace"], - container["objectName"]) - # update metadata - unreal_pipeline.imprint( - container_path, - { - "representation": str(representation["_id"]), - "parent": str(representation["parent"]) - }) - - asset_content = unreal.EditorAssetLibrary.list_assets( - destination_path, recursive=True, include_folder=True - ) - - for a in asset_content: - unreal.EditorAssetLibrary.save_asset(a) - - def remove(self, container): - path = container["namespace"] - parent_path = os.path.dirname(path) - - unreal.EditorAssetLibrary.delete_directory(path) - - asset_content = unreal.EditorAssetLibrary.list_assets( - parent_path, recursive=False - ) - - if len(asset_content) == 0: - unreal.EditorAssetLibrary.delete_directory(parent_path) From 448259c2d83e3b22d1042ccad2583e8cc2172f7b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 22 Apr 2021 16:30:13 +0200 Subject: [PATCH 116/180] added letterbox to extract review settings schema --- .../defaults/project_settings/global.json | 9 +++- .../schemas/schema_global_publish.json | 48 +++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index ca1b258e72..9623ba2c5b 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -45,7 +45,14 @@ ] }, "width": 0, - "height": 0 + "height": 0, + "letter_box": { + "enabled": false, + "ratio": 0.0, + "state": "letterbox", + "thickness": "fill", + "color": "black" + } } } } diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json index 3c079a130d..68a88b34af 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json @@ -203,6 +203,54 @@ "default": 0, "minimum": 0, "maximum": 100000 + }, + { + "key": "letter_box", + "label": "Letter box", + "type": "dict", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled", + "default": false + }, + { + "key": "ratio", + "label": "Letter box ratio", + "type": "number", + "decimal": 4, + "default": 0, + "minimum": 0, + "maximum": 10000 + }, + { + "key": "state", + "label": "Type", + "type": "enum", + "enum_items": [ + { + "letterbox": "Letterbox" + }, + { + "pillar": "Pillar" + } + ] + }, + { + "key": "thickness", + "label": "Thickness", + "type": "text", + "default": "fill" + }, + { + "key": "color", + "label": "Color", + "type": "text", + "default": "#000000" + } + ] } ] } From c1de9281449d87c15847f98d22213a630abe2f8f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 22 Apr 2021 16:51:27 +0200 Subject: [PATCH 117/180] converted logic from PR https://github.com/pypeclub/pype/pull/1371/files --- openpype/plugins/publish/extract_review.py | 96 ++++++++++++++++------ 1 file changed, 72 insertions(+), 24 deletions(-) diff --git a/openpype/plugins/publish/extract_review.py b/openpype/plugins/publish/extract_review.py index a71b1db66b..f6042a5de9 100644 --- a/openpype/plugins/publish/extract_review.py +++ b/openpype/plugins/publish/extract_review.py @@ -704,6 +704,59 @@ class ExtractReview(pyblish.api.InstancePlugin): return audio_in_args, audio_filters, audio_out_args + def get_letterbox_filters( + self, + letter_box_def, + input_res_ratio, + output_res_ratio, + pixel_aspect, + scale_factor_by_width, + scale_factor_by_height + ): + output = [] + + ratio = letter_box_def["ratio"] + state = letter_box_def["state"] + thickness = letter_box_def["thickness"] + color = letter_box_def["color"] + + if input_res_ratio == output_res_ratio: + ratio /= pixel_aspect + elif input_res_ratio < output_res_ratio: + ratio /= scale_factor_by_width + else: + ratio /= scale_factor_by_height + + if state == "letterbox": + top_box = ( + "drawbox=0:0:iw:round((ih-(iw*(1/{})))/2):t={}:c={}" + ).format(ratio, thickness, color) + + bottom_box = ( + "drawbox=0:ih-round((ih-(iw*(1/{0})))/2)" + ":iw:round((ih-(iw*(1/{0})))/2):t={1}:c={2}" + ).format(ratio, thickness, color) + + output.extend([top_box, bottom_box]) + + elif state == "pillar": + right_box = ( + "drawbox=0:0:round((iw-(ih*{}))/2):ih:t={}:c={}" + ).format(ratio, thickness, color) + + left_box = ( + "drawbox=(round(ih*{0})+round((iw-(ih*{0}))/2))" + ":0:round((iw-(ih*{0}))/2):ih:t={1}:c={2}" + ).format(ratio, thickness, color) + + output.extend([right_box, left_box]) + else: + raise ValueError( + "Letterbox state \"{}\" is not recognized".format(state) + ) + + return output + def rescaling_filters(self, temp_data, output_def, new_repre): """Prepare vieo filters based on tags in new representation. @@ -715,7 +768,8 @@ class ExtractReview(pyblish.api.InstancePlugin): """ filters = [] - letter_box = output_def.get("letter_box") + letter_box_def = output_def["letter_box"] + letter_box_enabled = letter_box_def["enabled"] # Get instance data pixel_aspect = temp_data["pixel_aspect"] @@ -795,7 +849,7 @@ class ExtractReview(pyblish.api.InstancePlugin): if ( output_width == input_width and output_height == input_height - and not letter_box + and not letter_box_enabled and pixel_aspect == 1 ): self.log.debug( @@ -834,30 +888,24 @@ class ExtractReview(pyblish.api.InstancePlugin): ) # letter_box - if letter_box: - if input_res_ratio == output_res_ratio: - letter_box /= pixel_aspect - elif input_res_ratio < output_res_ratio: - letter_box /= scale_factor_by_width - else: - letter_box /= scale_factor_by_height - - scale_filter = "scale={}x{}:flags=lanczos".format( - output_width, output_height + if letter_box_enabled: + filters.extend([ + "scale={}x{}:flags=lanczos".format( + output_width, output_height + ), + "setsar=1" + ]) + filters.extend( + self.get_letterbox_filters( + letter_box_def, + input_res_ratio, + output_res_ratio, + pixel_aspect, + scale_factor_by_width, + scale_factor_by_height + ) ) - top_box = ( - "drawbox=0:0:iw:round((ih-(iw*(1/{})))/2):t=fill:c=black" - ).format(letter_box) - - bottom_box = ( - "drawbox=0:ih-round((ih-(iw*(1/{0})))/2)" - ":iw:round((ih-(iw*(1/{0})))/2):t=fill:c=black" - ).format(letter_box) - - # Add letter box filters - filters.extend([scale_filter, "setsar=1", top_box, bottom_box]) - # scaling none square pixels and 1920 width if ( input_height != output_height From 50b6913f57160b3bcd91e7e7ff20b0953bd4261b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 22 Apr 2021 17:57:04 +0200 Subject: [PATCH 118/180] Nuke: fixing imageio functions, have to rename module self to opnl --- openpype/hosts/nuke/api/lib.py | 344 +++++++++++++++++---------------- 1 file changed, 174 insertions(+), 170 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 34337f726f..7ef5401292 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -1,6 +1,8 @@ import os import re import sys +import six +import platform from collections import OrderedDict @@ -19,7 +21,6 @@ from openpype.api import ( get_hierarchy, get_asset, get_current_project_settings, - config, ApplicationManager ) @@ -29,36 +30,34 @@ from .utils import set_context_favorites log = Logger().get_logger(__name__) -self = sys.modules[__name__] -self._project = None -self.workfiles_launched = False -self._node_tab_name = "{}".format(os.getenv("AVALON_LABEL") or "Avalon") +opnl = sys.modules[__name__] +opnl._project = None +opnl.project_name = os.getenv("AVALON_PROJECT") +opnl.workfiles_launched = False +opnl._node_tab_name = "{}".format(os.getenv("AVALON_LABEL") or "Avalon") -def get_node_imageio_setting(**kwarg): +def get_created_node_imageio_setting(**kwarg): ''' Get preset data for dataflow (fileType, compression, bitDepth) ''' - log.info(kwarg) - host = str(kwarg.get("host", "nuke")) + log.debug(kwarg) nodeclass = kwarg.get("nodeclass", None) creator = kwarg.get("creator", None) - project_name = os.getenv("AVALON_PROJECT") - assert any([host, nodeclass]), nuke.message( + assert any([creator, nodeclass]), nuke.message( "`{}`: Missing mandatory kwargs `host`, `cls`".format(__file__)) - imageio_nodes = (get_anatomy_settings(project_name) - ["imageio"] - .get(host, None) - ["nodes"] - ["requiredNodes"] - ) + imageio = get_anatomy_settings(opnl.project_name)["imageio"] + imageio_nodes = imageio["nuke"]["nodes"]["requiredNodes"] + imageio_node = None for node in imageio_nodes: log.info(node) - if node["nukeNodeClass"] == nodeclass: - if creator in node["plugins"]: - imageio_node = node + if (node["nukeNodeClass"] != nodeclass) and ( + creator not in node["plugins"]): + continue + + imageio_node = node log.info("ImageIO node: {}".format(imageio_node)) return imageio_node @@ -67,12 +66,9 @@ def get_node_imageio_setting(**kwarg): def get_imageio_input_colorspace(filename): ''' Get input file colorspace based on regex in settings. ''' - imageio_regex_inputs = (get_anatomy_settings(os.getenv("AVALON_PROJECT")) - ["imageio"] - ["nuke"] - ["regexInputs"] - ["inputs"] - ) + imageio_regex_inputs = ( + get_anatomy_settings(opnl.project_name) + ["imageio"]["nuke"]["regexInputs"]["inputs"]) preset_clrsp = None for regexInput in imageio_regex_inputs: @@ -104,40 +100,39 @@ def check_inventory_versions(): """ # get all Loader nodes by avalon attribute metadata for each in nuke.allNodes(): - if each.Class() == 'Read': - container = avalon.nuke.parse_container(each) + container = avalon.nuke.parse_container(each) - if container: - node = nuke.toNode(container["objectName"]) - avalon_knob_data = avalon.nuke.read( - node) + if container: + node = nuke.toNode(container["objectName"]) + avalon_knob_data = avalon.nuke.read( + node) - # get representation from io - representation = io.find_one({ - "type": "representation", - "_id": io.ObjectId(avalon_knob_data["representation"]) - }) + # get representation from io + representation = io.find_one({ + "type": "representation", + "_id": io.ObjectId(avalon_knob_data["representation"]) + }) - # Get start frame from version data - version = io.find_one({ - "type": "version", - "_id": representation["parent"] - }) + # Get start frame from version data + version = io.find_one({ + "type": "version", + "_id": representation["parent"] + }) - # get all versions in list - versions = io.find({ - "type": "version", - "parent": version["parent"] - }).distinct('name') + # get all versions in list + versions = io.find({ + "type": "version", + "parent": version["parent"] + }).distinct('name') - max_version = max(versions) + max_version = max(versions) - # check the available version and do match - # change color of node if not max verion - if version.get("name") not in [max_version]: - node["tile_color"].setValue(int("0xd84f20ff", 16)) - else: - node["tile_color"].setValue(int("0x4ecd25ff", 16)) + # check the available version and do match + # change color of node if not max verion + if version.get("name") not in [max_version]: + node["tile_color"].setValue(int("0xd84f20ff", 16)) + else: + node["tile_color"].setValue(int("0x4ecd25ff", 16)) def writes_version_sync(): @@ -153,34 +148,33 @@ def writes_version_sync(): except Exception: return - for each in nuke.allNodes(): - if each.Class() == 'Write': - # check if the node is avalon tracked - if self._node_tab_name not in each.knobs(): + for each in nuke.allNodes(filter="Write"): + # check if the node is avalon tracked + if opnl._node_tab_name not in each.knobs(): + continue + + avalon_knob_data = avalon.nuke.read( + each) + + try: + if avalon_knob_data['families'] not in ["render"]: + log.debug(avalon_knob_data['families']) continue - avalon_knob_data = avalon.nuke.read( - each) + node_file = each['file'].value() - try: - if avalon_knob_data['families'] not in ["render"]: - log.debug(avalon_knob_data['families']) - continue + node_version = "v" + get_version_from_path(node_file) + log.debug("node_version: {}".format(node_version)) - node_file = each['file'].value() - - node_version = "v" + get_version_from_path(node_file) - log.debug("node_version: {}".format(node_version)) - - node_new_file = node_file.replace(node_version, new_version) - each['file'].setValue(node_new_file) - if not os.path.isdir(os.path.dirname(node_new_file)): - log.warning("Path does not exist! I am creating it.") - os.makedirs(os.path.dirname(node_new_file)) - except Exception as e: - log.warning( - "Write node: `{}` has no version in path: {}".format( - each.name(), e)) + node_new_file = node_file.replace(node_version, new_version) + each['file'].setValue(node_new_file) + if not os.path.isdir(os.path.dirname(node_new_file)): + log.warning("Path does not exist! I am creating it.") + os.makedirs(os.path.dirname(node_new_file)) + except Exception as e: + log.warning( + "Write node: `{}` has no version in path: {}".format( + each.name(), e)) def version_up_script(): @@ -201,24 +195,22 @@ def check_subsetname_exists(nodes, subset_name): Returns: bool: True of False """ - result = next((True for n in nodes - if subset_name in avalon.nuke.read(n).get("subset", "")), False) - return result + return next((True for n in nodes + if subset_name in avalon.nuke.read(n).get("subset", "")), + False) def get_render_path(node): ''' Generate Render path from presets regarding avalon knob data ''' - data = dict() - data['avalon'] = avalon.nuke.read( - node) - + data = {'avalon': avalon.nuke.read(node)} data_preset = { - "class": data['avalon']['family'], - "preset": data['avalon']['families'] + "nodeclass": data['avalon']['family'], + "families": [data['avalon']['families']], + "creator": data['avalon']['creator'] } - nuke_imageio_writes = get_node_imageio_setting(**data_preset) + nuke_imageio_writes = get_created_node_imageio_setting(**data_preset) application = lib.get_application(os.environ["AVALON_APP_NAME"]) data.update({ @@ -324,7 +316,7 @@ def create_write_node(name, data, input=None, prenodes=None, review=True): node (obj): group node with avalon data as Knobs ''' - imageio_writes = get_node_imageio_setting(**data) + imageio_writes = get_created_node_imageio_setting(**data) app_manager = ApplicationManager() app_name = os.environ.get("AVALON_APP_NAME") if app_name: @@ -367,8 +359,7 @@ def create_write_node(name, data, input=None, prenodes=None, review=True): # adding dataflow template log.debug("imageio_writes: `{}`".format(imageio_writes)) for knob in imageio_writes["knobs"]: - if knob["name"] not in ["_id", "_previous"]: - _data.update({knob["name"]: knob["value"]}) + _data.update({knob["name"]: knob["value"]}) _data = anlib.fix_data_for_node_create(_data) @@ -506,7 +497,7 @@ def create_write_node(name, data, input=None, prenodes=None, review=True): add_deadline_tab(GN) # open the our Tab as default - GN[self._node_tab_name].setFlag(0) + GN[opnl._node_tab_name].setFlag(0) # set tile color tile_color = _data.get("tile_color", "0xff0000ff") @@ -629,7 +620,7 @@ class WorkfileSettings(object): root_node=None, nodes=None, **kwargs): - self._project = kwargs.get( + opnl._project = kwargs.get( "project") or io.find_one({"type": "project"}) self._asset = kwargs.get("asset_name") or api.Session["AVALON_ASSET"] self._asset_entity = get_asset(self._asset) @@ -672,7 +663,7 @@ class WorkfileSettings(object): ] erased_viewers = [] - for v in [n for n in nuke.allNodes(filter="Viewer")]: + for v in nuke.allNodes(filter="Viewer"): v['viewerProcess'].setValue(str(viewer_dict["viewerProcess"])) if str(viewer_dict["viewerProcess"]) \ not in v['viewerProcess'].value(): @@ -716,7 +707,7 @@ class WorkfileSettings(object): log.error(msg) nuke.message(msg) - log.debug(">> root_dict: {}".format(root_dict)) + log.warning(">> root_dict: {}".format(root_dict)) # first set OCIO if self._root_node["colorManagement"].value() \ @@ -738,41 +729,41 @@ class WorkfileSettings(object): # third set ocio custom path if root_dict.get("customOCIOConfigPath"): - self._root_node["customOCIOConfigPath"].setValue( - str(root_dict["customOCIOConfigPath"]).format( - **os.environ - ).replace("\\", "/") - ) - log.debug("nuke.root()['{}'] changed to: {}".format( - "customOCIOConfigPath", root_dict["customOCIOConfigPath"])) - root_dict.pop("customOCIOConfigPath") + unresolved_path = root_dict["customOCIOConfigPath"] + ocio_paths = unresolved_path[platform.system().lower()] + + resolved_path = None + for ocio_p in ocio_paths: + resolved_path = str(ocio_p).format(**os.environ) + if not os.path.exists(resolved_path): + continue + + if resolved_path: + self._root_node["customOCIOConfigPath"].setValue( + str(resolved_path).replace("\\", "/") + ) + log.debug("nuke.root()['{}'] changed to: {}".format( + "customOCIOConfigPath", resolved_path)) + root_dict.pop("customOCIOConfigPath") # then set the rest for knob, value in root_dict.items(): + # skip unfilled ocio config path + # it will be dict in value + if isinstance(value, dict): + continue if self._root_node[knob].value() not in value: self._root_node[knob].setValue(str(value)) log.debug("nuke.root()['{}'] changed to: {}".format( knob, value)) - def set_writes_colorspace(self, write_dict): + def set_writes_colorspace(self): ''' Adds correct colorspace to write node dict - Arguments: - write_dict (dict): nuke write node as dictionary - ''' - # scene will have fixed colorspace following presets for the project - if not isinstance(write_dict, dict): - msg = "set_root_colorspace(): argument should be dictionary" - log.error(msg) - return - from avalon.nuke import read - for node in nuke.allNodes(): - - if node.Class() in ["Viewer", "Dot"]: - continue + for node in nuke.allNodes(filter="Group"): # get data from avalon knob avalon_knob_data = read(node) @@ -788,49 +779,63 @@ class WorkfileSettings(object): if avalon_knob_data.get("families"): families.append(avalon_knob_data.get("families")) - # except disabled nodes but exclude backdrops in test - for fmly, knob in write_dict.items(): - write = None - if (fmly in families): - # Add all nodes in group instances. - if node.Class() == "Group": - node.begin() - for x in nuke.allNodes(): - if x.Class() == "Write": - write = x - node.end() - elif node.Class() == "Write": - write = node - else: - log.warning("Wrong write node Class") + data_preset = { + "nodeclass": avalon_knob_data["family"], + "families": families, + "creator": avalon_knob_data['creator'] + } - write["colorspace"].setValue(str(knob["colorspace"])) - log.info( - "Setting `{0}` to `{1}`".format( - write.name(), - knob["colorspace"])) + nuke_imageio_writes = get_created_node_imageio_setting( + **data_preset) - def set_reads_colorspace(self, reads): + log.debug("nuke_imageio_writes: `{}`".format(nuke_imageio_writes)) + + if not nuke_imageio_writes: + return + + write_node = None + + # get into the group node + node.begin() + for x in nuke.allNodes(): + if x.Class() == "Write": + write_node = x + node.end() + + if not write_node: + return + + # write all knobs to node + for knob in nuke_imageio_writes["knobs"]: + value = knob["value"] + if isinstance(value, six.text_type): + value = str(value) + if str(value).startswith("0x"): + value = int(value, 16) + + write_node[knob["name"]].setValue(value) + + + def set_reads_colorspace(self, read_clrs_inputs): """ Setting colorspace to Read nodes Looping trought all read nodes and tries to set colorspace based on regex rules in presets """ - changes = dict() + changes = {} for n in nuke.allNodes(): file = nuke.filename(n) - if not n.Class() == "Read": + if n.Class() != "Read": continue - # load nuke presets for Read's colorspace - read_clrs_presets = config.get_init_presets()["colorspace"].get( - "nuke", {}).get("read", {}) - # check if any colorspace presets for read is mathing - preset_clrsp = next((read_clrs_presets[k] - for k in read_clrs_presets - if bool(re.search(k, file))), - None) + preset_clrsp = None + + for input in read_clrs_inputs: + if not bool(re.search(input["regex"], file)): + continue + preset_clrsp = input["colorspace"] + log.debug(preset_clrsp) if preset_clrsp is not None: current = n["colorspace"].value() @@ -864,13 +869,15 @@ class WorkfileSettings(object): def set_colorspace(self): ''' Setting colorpace following presets ''' - nuke_colorspace = config.get_init_presets( - )["colorspace"].get("nuke", None) + # get imageio + imageio = get_anatomy_settings(opnl.project_name)["imageio"] + nuke_colorspace = imageio["nuke"] try: - self.set_root_colorspace(nuke_colorspace["root"]) + self.set_root_colorspace(nuke_colorspace["workfile"]) except AttributeError: - msg = "set_colorspace(): missing `root` settings in template" + msg = "set_colorspace(): missing `workfile` settings in template" + nuke.message(msg) try: self.set_viewers_colorspace(nuke_colorspace["viewer"]) @@ -880,15 +887,14 @@ class WorkfileSettings(object): log.error(msg) try: - self.set_writes_colorspace(nuke_colorspace["write"]) - except AttributeError: - msg = "set_colorspace(): missing `write` settings in template" - nuke.message(msg) - log.error(msg) + self.set_writes_colorspace() + except AttributeError as _error: + nuke.message(_error) + log.error(_error) - reads = nuke_colorspace.get("read") - if reads: - self.set_reads_colorspace(reads) + read_clrs_inputs = nuke_colorspace["regexInputs"].get("inputs", []) + if read_clrs_inputs: + self.set_reads_colorspace(read_clrs_inputs) try: for key in nuke_colorspace: @@ -1070,15 +1076,14 @@ class WorkfileSettings(object): def set_favorites(self): work_dir = os.getenv("AVALON_WORKDIR") asset = os.getenv("AVALON_ASSET") - project = os.getenv("AVALON_PROJECT") favorite_items = OrderedDict() # project # get project's root and split to parts projects_root = os.path.normpath(work_dir.split( - project)[0]) + opnl.project_name)[0]) # add project name - project_dir = os.path.join(projects_root, project) + "/" + project_dir = os.path.join(projects_root, opnl.project_name) + "/" # add to favorites favorite_items.update({"Project dir": project_dir.replace("\\", "/")}) @@ -1128,13 +1133,13 @@ def get_write_node_template_attr(node): data['avalon'] = avalon.nuke.read( node) data_preset = { - "class": data['avalon']['family'], - "families": data['avalon']['families'], - "preset": data['avalon']['families'] # omit < 2.0.0v + "nodeclass": data['avalon']['family'], + "families": [data['avalon']['families']], + "creator": data['avalon']['creator'] } # get template data - nuke_imageio_writes = get_node_imageio_setting(**data_preset) + nuke_imageio_writes = get_created_node_imageio_setting(**data_preset) # collecting correct data correct_data = OrderedDict({ @@ -1230,8 +1235,7 @@ class ExporterReview: """ anlib.reset_selection() ipn_orig = None - for v in [n for n in nuke.allNodes() - if "Viewer" == n.Class()]: + for v in nuke.allNodes(filter="Viewer"): ip = v['input_process'].getValue() ipn = v['input_process_node'].getValue() if "VIEWER_INPUT" not in ipn and ip: @@ -1644,8 +1648,8 @@ def launch_workfiles_app(): if not open_at_start: return - if not self.workfiles_launched: - self.workfiles_launched = True + if not opnl.workfiles_launched: + opnl.workfiles_launched = True workfiles.show(os.environ["AVALON_WORKDIR"]) From 975f9f089883459ef9b182f9dda79221d3b14ccf Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 22 Apr 2021 17:57:34 +0200 Subject: [PATCH 119/180] Nuke: adding creator name into OpenPype data on write node --- .../hosts/nuke/plugins/create/create_write_prerender.py | 8 ++++++-- openpype/hosts/nuke/plugins/create/create_write_render.py | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/nuke/plugins/create/create_write_prerender.py b/openpype/hosts/nuke/plugins/create/create_write_prerender.py index 38d1a0c2ed..6e1a2ddd96 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_prerender.py +++ b/openpype/hosts/nuke/plugins/create/create_write_prerender.py @@ -77,10 +77,14 @@ class CreateWritePrerender(plugin.PypeCreator): write_data = { "nodeclass": self.n_class, "families": [self.family], - "avalon": self.data, - "creator": self.__class__.__name__ + "avalon": self.data } + # add creator data + creator_data = {"creator": self.__class__.__name__} + self.data.update(creator_data) + write_data.update(creator_data) + if self.presets.get('fpath_template'): self.log.info("Adding template path from preset") write_data.update( diff --git a/openpype/hosts/nuke/plugins/create/create_write_render.py b/openpype/hosts/nuke/plugins/create/create_write_render.py index 72f851f19c..04983e9c75 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_render.py +++ b/openpype/hosts/nuke/plugins/create/create_write_render.py @@ -80,10 +80,14 @@ class CreateWriteRender(plugin.PypeCreator): write_data = { "nodeclass": self.n_class, "families": [self.family], - "avalon": self.data, - "creator": self.__class__.__name__ + "avalon": self.data } + # add creator data + creator_data = {"creator": self.__class__.__name__} + self.data.update(creator_data) + write_data.update(creator_data) + if self.presets.get('fpath_template'): self.log.info("Adding template path from preset") write_data.update( From b9ddf0199024581ad2153ad53cb9da927881ac23 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 22 Apr 2021 17:58:18 +0200 Subject: [PATCH 120/180] Nuke: adding defaults to settings imageio --- openpype/settings/defaults/project_anatomy/imageio.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_anatomy/imageio.json b/openpype/settings/defaults/project_anatomy/imageio.json index 4e98463ee4..ff16c22663 100644 --- a/openpype/settings/defaults/project_anatomy/imageio.json +++ b/openpype/settings/defaults/project_anatomy/imageio.json @@ -25,6 +25,9 @@ } }, "nuke": { + "viewer": { + "viewerProcess": "sRGB" + }, "workfile": { "colorManagement": "Nuke", "OCIO_config": "nuke-default", @@ -102,7 +105,7 @@ }, { "name": "tile_color", - "value": "0xff0000ff" + "value": "0xadab1dff" }, { "name": "channels", From cf067a2e18676cafd28f8693b4eadb9f4e9bea3f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 22 Apr 2021 17:58:36 +0200 Subject: [PATCH 121/180] Nuke: adding imageio Viewer attribute --- .../schemas/schema_anatomy_imageio.json | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_imageio.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_imageio.json index b48f90bd91..edd5c18f51 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_imageio.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_imageio.json @@ -118,6 +118,19 @@ "type": "dict", "label": "Nuke", "children": [ + { + "key": "viewer", + "type": "dict", + "label": "Viewer", + "collapsible": false, + "children": [ + { + "type": "text", + "key": "viewerProcess", + "label": "Viewer Process" + } + ] + }, { "key": "workfile", "type": "dict", From 4036eefe7b9b5a3e8dc472c536d8073367840097 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 22 Apr 2021 19:26:10 +0200 Subject: [PATCH 122/180] SyncServer GUI - multiselect for Summary --- openpype/modules/sync_server/tray/lib.py | 1 + openpype/modules/sync_server/tray/models.py | 19 +- openpype/modules/sync_server/tray/widgets.py | 321 +++++++++++-------- 3 files changed, 201 insertions(+), 140 deletions(-) diff --git a/openpype/modules/sync_server/tray/lib.py b/openpype/modules/sync_server/tray/lib.py index 3597213b31..5e8a3fdd31 100644 --- a/openpype/modules/sync_server/tray/lib.py +++ b/openpype/modules/sync_server/tray/lib.py @@ -24,6 +24,7 @@ ProgressRole = QtCore.Qt.UserRole + 4 DateRole = QtCore.Qt.UserRole + 6 FailedRole = QtCore.Qt.UserRole + 8 HeaderNameRole = QtCore.Qt.UserRole + 10 +FullItemRole = QtCore.Qt.UserRole + 12 @six.add_metaclass(abc.ABCMeta) diff --git a/openpype/modules/sync_server/tray/models.py b/openpype/modules/sync_server/tray/models.py index 981299c6cf..80c6345263 100644 --- a/openpype/modules/sync_server/tray/models.py +++ b/openpype/modules/sync_server/tray/models.py @@ -120,7 +120,7 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): self.query = self.get_query(load_records) representations = self.dbcon.aggregate(self.query) - self.add_page_records(self.local_site, self.remote_site, + self.add_page_records(self.active_site, self.remote_site, representations) self.endResetModel() self.refresh_finished.emit() @@ -158,7 +158,7 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): self._rec_loaded, self._rec_loaded + items_to_fetch - 1) - self.add_page_records(self.local_site, self.remote_site, + self.add_page_records(self.active_site, self.remote_site, representations) self.endInsertRows() @@ -283,7 +283,7 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): """ self._project = project self.sync_server.set_sync_project_settings() - self.local_site = self.sync_server.get_active_site(self.project) + self.active_site = self.sync_server.get_active_site(self.project) self.remote_site = self.sync_server.get_remote_site(self.project) self.refresh() @@ -410,7 +410,7 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): self.sync_server = sync_server # TODO think about admin mode # this is for regular user, always only single local and single remote - self.local_site = self.sync_server.get_active_site(self.project) + self.active_site = self.sync_server.get_active_site(self.project) self.remote_site = self.sync_server.get_remote_site(self.project) self.sort = self.DEFAULT_SORT @@ -428,6 +428,9 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): def data(self, index, role): item = self._data[index.row()] + if role == lib.FullItemRole: + return item + header_value = self._header[index.column()] if role == lib.ProviderRole: if header_value == 'local_site': @@ -585,7 +588,7 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): }}, 'order_local': { '$filter': {'input': '$files.sites', 'as': 'p', - 'cond': {'$eq': ['$$p.name', self.local_site]} + 'cond': {'$eq': ['$$p.name', self.active_site]} }} }}, {'$addFields': { @@ -714,7 +717,7 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): """ base_match = { "type": "representation", - 'files.sites.name': {'$all': [self.local_site, + 'files.sites.name': {'$all': [self.active_site, self.remote_site]} } if not self._word_filter: @@ -889,7 +892,7 @@ class SyncRepresentationDetailModel(_SyncRepresentationModel): self.sync_server = sync_server # TODO think about admin mode # this is for regular user, always only single local and single remote - self.local_site = self.sync_server.get_active_site(self.project) + self.active_site = self.sync_server.get_active_site(self.project) self.remote_site = self.sync_server.get_remote_site(self.project) self.sort = self.DEFAULT_SORT @@ -1042,7 +1045,7 @@ class SyncRepresentationDetailModel(_SyncRepresentationModel): }}, 'order_local': { '$filter': {'input': '$files.sites', 'as': 'p', - 'cond': {'$eq': ['$$p.name', self.local_site]} + 'cond': {'$eq': ['$$p.name', self.active_site]} }} }}, {'$addFields': { diff --git a/openpype/modules/sync_server/tray/widgets.py b/openpype/modules/sync_server/tray/widgets.py index 6d8348becb..a0c054a67e 100644 --- a/openpype/modules/sync_server/tray/widgets.py +++ b/openpype/modules/sync_server/tray/widgets.py @@ -161,9 +161,7 @@ class SyncRepresentationWidget(QtWidgets.QWidget): self.sync_server = sync_server - self._selected_id = None # keep last selected _id - self.representation_id = None - self.site_name = None # to pause/unpause representation + self._selected_ids = [] # keep last selected _id self.txt_filter = QtWidgets.QLineEdit() self.txt_filter.setPlaceholderText("Quick filter representations..") @@ -183,7 +181,7 @@ class SyncRepresentationWidget(QtWidgets.QWidget): self.table_view.setModel(model) self.table_view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.table_view.setSelectionMode( - QtWidgets.QAbstractItemView.SingleSelection) + QtWidgets.QAbstractItemView.ExtendedSelection) self.table_view.setSelectionBehavior( QtWidgets.QAbstractItemView.SelectRows) self.table_view.horizontalHeader().setSortIndicator( @@ -228,10 +226,12 @@ class SyncRepresentationWidget(QtWidgets.QWidget): idx = model.get_header_index(column_name) self.table_view.setColumnWidth(idx, width) - def _selection_changed(self, _new_selection): - index = self.selection_model.currentIndex() - self._selected_id = \ - self.model.data(index, Qt.UserRole) + def _selection_changed(self, new_selected, all_selected): + idxs = self.selection_model.selectedRows() + self._selected_ids = [] + + for index in idxs: + self._selected_ids.append(self.model.data(index, Qt.UserRole)) def _set_selection(self): """ @@ -239,14 +239,16 @@ class SyncRepresentationWidget(QtWidgets.QWidget): Keep selection during model refresh. """ - if self._selected_id: - index = self.model.get_index(self._selected_id) + existing_ids = [] + for selected_id in self._selected_ids: + index = self.model.get_index(selected_id) if index and index.isValid(): mode = QtCore.QItemSelectionModel.Select | \ QtCore.QItemSelectionModel.Rows - self.selection_model.setCurrentIndex(index, mode) - else: - self._selected_id = None + self.selection_model.select(index, mode) + existing_ids.append(selected_id) + + self._selected_ids = existing_ids def _double_clicked(self, index): """ @@ -256,59 +258,62 @@ class SyncRepresentationWidget(QtWidgets.QWidget): detail_window = SyncServerDetailWindow( self.sync_server, _id, self.model.project) detail_window.exec() - + def _on_context_menu(self, point): """ Shows menu with loader actions on Right-click. + + Supports multiple selects - adds all available actions, each + action handles if it appropriate for item itself, if not it skips. """ + is_multi = len(self._selected_ids) > 1 point_index = self.table_view.indexAt(point) - if not point_index.isValid(): + if not point_index.isValid() and not is_multi: return - self.item = self.model._data[point_index.row()] - self.representation_id = self.item._id - log.debug("menu representation _id:: {}". - format(self.representation_id)) + if is_multi: + index = self.model.get_index(self._selected_ids[0]) + self.item = self.model.data(index, lib.FullItemRole) + else: + self.item = self.model.data(point_index, lib.FullItemRole) menu = QtWidgets.QMenu() actions_mapping = {} - actions_kwargs_mapping = {} + action_kwarg_map = {} + + active_site = self.model.active_site + remote_site = self.model.remote_site - local_site = self.item.local_site local_progress = self.item.local_progress - remote_site = self.item.remote_site remote_progress = self.item.remote_progress - for site, progress in {local_site: local_progress, + project = self.model.project + for site, progress in {active_site: local_progress, remote_site: remote_progress}.items(): - project = self.model.project - provider = self.sync_server.get_provider_for_site(project, - site) + provider = self.sync_server.get_provider_for_site(project, site) if provider == 'local_drive': if 'studio' in site: txt = " studio version" else: txt = " local version" action = QtWidgets.QAction("Open in explorer" + txt) - if progress == 1.0: + if progress == 1.0 or is_multi: actions_mapping[action] = self._open_in_explorer - actions_kwargs_mapping[action] = {'site': site} + action_kwarg_map[action] = \ + self._get_action_kwargs(site) menu.addAction(action) - # progress smaller then 1.0 --> in progress or queued - if local_progress < 1.0: - self.site_name = local_site - else: - self.site_name = remote_site - - if self.item.status in [lib.STATUS[0], lib.STATUS[1]]: - action = QtWidgets.QAction("Pause") + if self.item.status in [lib.STATUS[0], lib.STATUS[1]] or is_multi: + action = QtWidgets.QAction("Pause in queue") actions_mapping[action] = self._pause + # pause handles which site_name it will pause itself + action_kwarg_map[action] = {"repre_ids": self._selected_ids} menu.addAction(action) - if self.item.status == lib.STATUS[3]: - action = QtWidgets.QAction("Unpause") + if self.item.status == lib.STATUS[3] or is_multi: + action = QtWidgets.QAction("Unpause in queue") actions_mapping[action] = self._unpause + action_kwarg_map[action] = {"repre_ids": self._selected_ids} menu.addAction(action) # if self.item.status == lib.STATUS[1]: @@ -316,24 +321,29 @@ class SyncRepresentationWidget(QtWidgets.QWidget): # actions_mapping[action] = self._show_detail # menu.addAction(action) - if remote_progress == 1.0: + if remote_progress == 1.0 or is_multi: action = QtWidgets.QAction("Re-sync Active site") - actions_mapping[action] = self._reset_local_site + action_kwarg_map[action] = self._get_action_kwargs(active_site) + actions_mapping[action] = self._reset_site menu.addAction(action) - if local_progress == 1.0: + if local_progress == 1.0 or is_multi: action = QtWidgets.QAction("Re-sync Remote site") - actions_mapping[action] = self._reset_remote_site + action_kwarg_map[action] = self._get_action_kwargs(remote_site) + actions_mapping[action] = self._reset_site menu.addAction(action) - if local_site != self.sync_server.DEFAULT_SITE: + if active_site == get_local_site_id(): action = QtWidgets.QAction("Completely remove from local") + action_kwarg_map[action] = self._get_action_kwargs(active_site) actions_mapping[action] = self._remove_site menu.addAction(action) - else: - action = QtWidgets.QAction("Mark for sync to local") - actions_mapping[action] = self._add_site - menu.addAction(action) + + # # temp for testing only !!! + # action = QtWidgets.QAction("Download") + # action_kwarg_map[action] = self._get_action_kwargs(active_site) + # actions_mapping[action] = self._add_site + # menu.addAction(action) if not actions_mapping: action = QtWidgets.QAction("< No action >") @@ -343,46 +353,65 @@ class SyncRepresentationWidget(QtWidgets.QWidget): result = menu.exec_(QtGui.QCursor.pos()) if result: to_run = actions_mapping[result] - to_run_kwargs = actions_kwargs_mapping.get(result, {}) + to_run_kwargs = action_kwarg_map.get(result, {}) if to_run: to_run(**to_run_kwargs) self.model.refresh() - def _pause(self): - self.sync_server.pause_representation(self.model.project, - self.representation_id, - self.site_name) - self.site_name = None - self.message_generated.emit("Paused {}".format(self.representation_id)) + def _pause(self, repre_ids=None): + log.debug("Pause {}".format(repre_ids)) + for representation_id in repre_ids: + item = self._get_item_by_repre_id(representation_id) + if item.status not in [lib.STATUS[0], lib.STATUS[1]]: + continue + for site_name in [self.model.active_site, self.model.remote_site]: + check_progress = self._get_progress(item, site_name) + if check_progress < 1: + self.sync_server.pause_representation(self.model.project, + representation_id, + site_name) - def _unpause(self): - self.sync_server.unpause_representation( - self.model.project, - self.representation_id, - self.site_name) - self.site_name = None - self.message_generated.emit("Unpaused {}".format( - self.representation_id)) + self.message_generated.emit("Paused {}".format(representation_id)) + + def _unpause(self, repre_ids=None): + log.debug("UnPause {}".format(repre_ids)) + for representation_id in repre_ids: + item = self._get_item_by_repre_id(representation_id) + if item.status not in lib.STATUS[3]: + continue + for site_name in [self.model.active_site, self.model.remote_site]: + check_progress = self._get_progress(item, site_name) + if check_progress < 1: + self.sync_server.unpause_representation( + self.model.project, + representation_id, + site_name) + + self.message_generated.emit("Unpause {}".format(representation_id)) # temporary here for testing, will be removed TODO - def _add_site(self): - log.info(self.representation_id) - project_name = self.model.project - local_site_name = get_local_site_id() - try: - self.sync_server.add_site( - project_name, - self.representation_id, - local_site_name - ) - self.message_generated.emit( - "Site {} added for {}".format(local_site_name, - self.representation_id)) - except ValueError as exp: - self.message_generated.emit("Error {}".format(str(exp))) + def _add_site(self, repre_ids=None, site_name=None): + log.debug("Add site {}:{}".format(repre_ids, site_name)) + for representation_id in repre_ids: + item = self._get_item_by_repre_id(representation_id) + if item.local_site == site_name or item.remote_site == site_name: + # site already exists skip + continue - def _remove_site(self): + try: + self.sync_server.add_site( + self.model.project, + representation_id, + site_name + ) + self.message_generated.emit( + "Site {} added for {}".format(site_name, + representation_id)) + except ValueError as exp: + self.message_generated.emit("Error {}".format(str(exp))) + + def _remove_site(self, repre_ids=None, site_name=None): """ Removes site record AND files. @@ -392,65 +421,93 @@ class SyncRepresentationWidget(QtWidgets.QWidget): This could only happen when artist work on local machine, not connected to studio mounted drives. """ - log.info("Removing {}".format(self.representation_id)) - try: - local_site = get_local_site_id() - self.sync_server.remove_site( + log.debug("Remove site {}:{}".format(repre_ids, site_name)) + for representation_id in repre_ids: + log.info("Removing {}".format(representation_id)) + try: + self.sync_server.remove_site( + self.model.project, + representation_id, + site_name, + True) + self.message_generated.emit( + "Site {} removed".format(site_name)) + except ValueError as exp: + self.message_generated.emit("Error {}".format(str(exp))) + + self.model.refresh( + load_records=self.model._rec_loaded) + + def _reset_site(self, repre_ids=None, site_name=None): + """ + Removes errors or success metadata for particular file >> forces + redo of upload/download + """ + log.debug("Reset site {}:{}".format(repre_ids, site_name)) + for representation_id in repre_ids: + item = self._get_item_by_repre_id(representation_id) + check_progress = self._get_progress(item, site_name, True) + + # do not reset if opposite side is not fully there + if check_progress != 1: + log.debug("Not fully available {} on other side, skipping". + format(check_progress)) + continue + + self.sync_server.reset_provider_for_file( self.model.project, - self.representation_id, - local_site, - True) - self.message_generated.emit("Site {} removed".format(local_site)) - except ValueError as exp: - self.message_generated.emit("Error {}".format(str(exp))) + representation_id, + site_name=site_name, + force=True) + self.model.refresh( load_records=self.model._rec_loaded) - def _reset_local_site(self): - """ - Removes errors or success metadata for particular file >> forces - redo of upload/download - """ - self.sync_server.reset_provider_for_file( - self.model.project, - self.representation_id, - 'local') - self.model.refresh( - load_records=self.model._rec_loaded) + def _open_in_explorer(self, repre_ids=None, site_name=None): + log.debug("Open in Explorer {}:{}".format(repre_ids, site_name)) + for representation_id in repre_ids: + item = self._get_item_by_repre_id(representation_id) + if not item: + return - def _reset_remote_site(self): - """ - Removes errors or success metadata for particular file >> forces - redo of upload/download - """ - self.sync_server.reset_provider_for_file( - self.model.project, - self.representation_id, - 'remote') - self.model.refresh( - load_records=self.model._rec_loaded) + fpath = item.path + project = self.model.project + fpath = self.sync_server.get_local_file_path(project, + site_name, + fpath) - def _open_in_explorer(self, site): - if not self.item: - return + fpath = os.path.normpath(os.path.dirname(fpath)) + if os.path.isdir(fpath): + if 'win' in sys.platform: # windows + subprocess.Popen('explorer "%s"' % fpath) + elif sys.platform == 'darwin': # macOS + subprocess.Popen(['open', fpath]) + else: # linux + try: + subprocess.Popen(['xdg-open', fpath]) + except OSError: + raise OSError('unsupported xdg-open call??') - fpath = self.item.path - project = self.model.project - fpath = self.sync_server.get_local_file_path(project, - site, - fpath) + def _get_progress(self, item, site_name, opposite=False): + """Returns progress value according to site (side)""" + progress = {'local': item.local_progress, + 'remote': item.remote_progress} + side = 'remote' + if site_name == self.model.active_site: + side = 'local' + if opposite: + side = 'remote' if side == 'local' else 'local' - fpath = os.path.normpath(os.path.dirname(fpath)) - if os.path.isdir(fpath): - if 'win' in sys.platform: # windows - subprocess.Popen('explorer "%s"' % fpath) - elif sys.platform == 'darwin': # macOS - subprocess.Popen(['open', fpath]) - else: # linux - try: - subprocess.Popen(['xdg-open', fpath]) - except OSError: - raise OSError('unsupported xdg-open call??') + return progress[side] + + def _get_item_by_repre_id(self, representation_id): + index = self.model.get_index(representation_id) + item = self.model.data(index, lib.FullItemRole) + return item + + def _get_action_kwargs(self, site_name): + """Default format of kwargs for action""" + return {"repre_ids": self._selected_ids, "site_name": site_name} def _save_scrollbar(self): self._scrollbar_pos = self.table_view.verticalScrollBar().value() @@ -599,7 +656,7 @@ class SyncRepresentationDetailWidget(QtWidgets.QWidget): menu = QtWidgets.QMenu() actions_mapping = {} - actions_kwargs_mapping = {} + action_kwarg_map = {} local_site = self.item.local_site local_progress = self.item.local_progress @@ -619,7 +676,7 @@ class SyncRepresentationDetailWidget(QtWidgets.QWidget): action = QtWidgets.QAction("Open in explorer" + txt) if progress == 1: actions_mapping[action] = self._open_in_explorer - actions_kwargs_mapping[action] = {'site': site} + action_kwarg_map[action] = {'site': site} menu.addAction(action) if self.item.status == lib.STATUS[2]: @@ -645,7 +702,7 @@ class SyncRepresentationDetailWidget(QtWidgets.QWidget): result = menu.exec_(QtGui.QCursor.pos()) if result: to_run = actions_mapping[result] - to_run_kwargs = actions_kwargs_mapping.get(result, {}) + to_run_kwargs = action_kwarg_map.get(result, {}) if to_run: to_run(**to_run_kwargs) From 0759b667f6095c859e7c6a043f7a416ab9f5990e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 22 Apr 2021 19:53:53 +0200 Subject: [PATCH 123/180] it is possible to define box and line with different color and transparency --- openpype/plugins/publish/extract_review.py | 82 +++++++++++++++---- .../defaults/project_settings/global.json | 15 +++- .../schemas/schema_global_publish.json | 31 +++++-- 3 files changed, 100 insertions(+), 28 deletions(-) diff --git a/openpype/plugins/publish/extract_review.py b/openpype/plugins/publish/extract_review.py index f6042a5de9..00b671199d 100644 --- a/openpype/plugins/publish/extract_review.py +++ b/openpype/plugins/publish/extract_review.py @@ -717,8 +717,20 @@ class ExtractReview(pyblish.api.InstancePlugin): ratio = letter_box_def["ratio"] state = letter_box_def["state"] - thickness = letter_box_def["thickness"] - color = letter_box_def["color"] + fill_color = letter_box_def["fill_color"] + f_red, f_green, f_blue, f_alpha = fill_color + fill_color_hex = "{0:0>2X}{1:0>2X}{2:0>2X}".format( + f_red, f_green, f_blue + ) + fill_color_alpha = float(f_alpha) / 255 + + line_thickness = letter_box_def["line_thickness"] + line_color = letter_box_def["line_color"] + l_red, l_green, l_blue, l_alpha = line_color + line_color_hex = "{0:0>2X}{1:0>2X}{2:0>2X}".format( + l_red, l_green, l_blue + ) + line_color_alpha = float(l_alpha) / 255 if input_res_ratio == output_res_ratio: ratio /= pixel_aspect @@ -728,28 +740,62 @@ class ExtractReview(pyblish.api.InstancePlugin): ratio /= scale_factor_by_height if state == "letterbox": - top_box = ( - "drawbox=0:0:iw:round((ih-(iw*(1/{})))/2):t={}:c={}" - ).format(ratio, thickness, color) + if fill_color_alpha > 0: + top_box = ( + "drawbox=0:0:iw:round((ih-(iw*(1/{})))/2):t=fill:c={}@{}" + ).format(ratio, fill_color_hex, fill_color_alpha) - bottom_box = ( - "drawbox=0:ih-round((ih-(iw*(1/{0})))/2)" - ":iw:round((ih-(iw*(1/{0})))/2):t={1}:c={2}" - ).format(ratio, thickness, color) + bottom_box = ( + "drawbox=0:ih-round((ih-(iw*(1/{0})))/2)" + ":iw:round((ih-(iw*(1/{0})))/2):t=fill:c={1}@{2}" + ).format(ratio, fill_color_hex, fill_color_alpha) - output.extend([top_box, bottom_box]) + output.extend([top_box, bottom_box]) + + if line_color_alpha > 0 and line_thickness > 0: + top_line = ( + "drawbox=0:round((ih-(iw*(1/{0})))/2)-{1}:iw:{1}:" + "t=fill:c={2}@{3}" + ).format( + ratio, line_thickness, line_color_hex, line_color_alpha + ) + bottom_line = ( + "drawbox=0:ih-round((ih-(iw*(1/{})))/2)" + ":iw:{}:t=fill:c={}@{}" + ).format( + ratio, line_thickness, line_color_hex, line_color_alpha + ) + output.extend([top_line, bottom_line]) elif state == "pillar": - right_box = ( - "drawbox=0:0:round((iw-(ih*{}))/2):ih:t={}:c={}" - ).format(ratio, thickness, color) + if fill_color_alpha > 0: + left_box = ( + "drawbox=0:0:round((iw-(ih*{}))/2):ih:t=fill:c={}@{}" + ).format(ratio, fill_color_hex, fill_color_alpha) - left_box = ( - "drawbox=(round(ih*{0})+round((iw-(ih*{0}))/2))" - ":0:round((iw-(ih*{0}))/2):ih:t={1}:c={2}" - ).format(ratio, thickness, color) + right_box = ( + "drawbox=iw-round((iw-(ih*{0}))/2))" + ":0:round((iw-(ih*{0}))/2):ih:t=fill:c={1}@{2}" + ).format(ratio, fill_color_hex, fill_color_alpha) + + output.extend([left_box, right_box]) + + if line_color_alpha > 0 and line_thickness > 0: + left_line = ( + "drawbox=round((iw-(ih*{}))/2):0:{}:ih:t=fill:c={}@{}" + ).format( + ratio, line_thickness, line_color_hex, line_color_alpha + ) + + right_line = ( + "drawbox=iw-round((iw-(ih*{}))/2))" + ":0:{}:ih:t=fill:c={}@{}" + ).format( + ratio, line_thickness, line_color_hex, line_color_alpha + ) + + output.extend([left_line, right_line]) - output.extend([right_box, left_box]) else: raise ValueError( "Letterbox state \"{}\" is not recognized".format(state) diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 9623ba2c5b..4fb42c6ff3 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -50,8 +50,19 @@ "enabled": false, "ratio": 0.0, "state": "letterbox", - "thickness": "fill", - "color": "black" + "fill_color": [ + 0, + 0, + 0, + 255 + ], + "line_thickness": 0, + "line_color": [ + 255, + 0, + 0, + 255 + ] } } } diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json index 68a88b34af..e6be868068 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json @@ -239,16 +239,31 @@ ] }, { - "key": "thickness", - "label": "Thickness", - "type": "text", - "default": "fill" + "type": "schema_template", + "name": "template_rgba_color", + "template_data": [ + { + "label": "Fill Color", + "name": "fill_color" + } + ] }, { - "key": "color", - "label": "Color", - "type": "text", - "default": "#000000" + "key": "line_thickness", + "label": "Line Thickness", + "type": "number", + "minimum": 0, + "maximum": 1000 + }, + { + "type": "schema_template", + "name": "template_rgba_color", + "template_data": [ + { + "label": "Line Color", + "name": "line_color" + } + ] } ] } From d2a27fd8580cfabbb39f2214bedf7e7b2f0cd9b9 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 23 Apr 2021 10:35:41 +0200 Subject: [PATCH 124/180] SyncServer GUI - multiselect for Detail Refactor --- openpype/modules/sync_server/tray/app.py | 4 +- openpype/modules/sync_server/tray/lib.py | 7 + openpype/modules/sync_server/tray/models.py | 3 + openpype/modules/sync_server/tray/widgets.py | 633 +++++++++---------- 4 files changed, 296 insertions(+), 351 deletions(-) diff --git a/openpype/modules/sync_server/tray/app.py b/openpype/modules/sync_server/tray/app.py index 25fbf0e49a..d91ba76335 100644 --- a/openpype/modules/sync_server/tray/app.py +++ b/openpype/modules/sync_server/tray/app.py @@ -7,7 +7,7 @@ from openpype import resources from openpype.modules.sync_server.tray.widgets import ( SyncProjectListWidget, - SyncRepresentationWidget + SyncRepresentationSummaryWidget ) log = PypeLogger().get_logger("SyncServer") @@ -47,7 +47,7 @@ class SyncServerWindow(QtWidgets.QDialog): left_column_layout.addWidget(self.pause_btn) left_column.setLayout(left_column_layout) - repres = SyncRepresentationWidget( + repres = SyncRepresentationSummaryWidget( sync_server, project=self.projects.current_project, parent=self) diff --git a/openpype/modules/sync_server/tray/lib.py b/openpype/modules/sync_server/tray/lib.py index 5e8a3fdd31..04bd1f568e 100644 --- a/openpype/modules/sync_server/tray/lib.py +++ b/openpype/modules/sync_server/tray/lib.py @@ -129,6 +129,7 @@ class FilterDefinition: type = attr.ib() values = attr.ib(factory=list) + def pretty_size(value, suffix='B'): for unit in ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi']: if abs(value) < 1024.0: @@ -157,3 +158,9 @@ def translate_provider_for_icon(sync_server, project, site): if site == sync_server.DEFAULT_SITE: return sync_server.DEFAULT_SITE return sync_server.get_provider_for_site(project, site) + + +def get_item_by_id(model, object_id): + index = model.get_index(object_id) + item = model.data(index, FullItemRole) + return item diff --git a/openpype/modules/sync_server/tray/models.py b/openpype/modules/sync_server/tray/models.py index 80c6345263..ffd81a1588 100644 --- a/openpype/modules/sync_server/tray/models.py +++ b/openpype/modules/sync_server/tray/models.py @@ -908,6 +908,9 @@ class SyncRepresentationDetailModel(_SyncRepresentationModel): def data(self, index, role): item = self._data[index.row()] + if role == lib.FullItemRole: + return item + header_value = self._header[index.column()] if role == lib.ProviderRole: if header_value == 'local_site': diff --git a/openpype/modules/sync_server/tray/widgets.py b/openpype/modules/sync_server/tray/widgets.py index a0c054a67e..c552904244 100644 --- a/openpype/modules/sync_server/tray/widgets.py +++ b/openpype/modules/sync_server/tray/widgets.py @@ -135,7 +135,7 @@ class SyncProjectListWidget(ProjectListWidget): self.refresh() -class SyncRepresentationWidget(QtWidgets.QWidget): +class _SyncRepresentationWidget(QtWidgets.QWidget): """ Summary dialog with list of representations that matches current settings 'local_site' and 'remote_site'. @@ -143,90 +143,7 @@ class SyncRepresentationWidget(QtWidgets.QWidget): active_changed = QtCore.Signal() # active index changed message_generated = QtCore.Signal(str) - default_widths = ( - ("asset", 190), - ("subset", 170), - ("version", 60), - ("representation", 145), - ("local_site", 160), - ("remote_site", 160), - ("files_count", 50), - ("files_size", 60), - ("priority", 70), - ("status", 110) - ) - - def __init__(self, sync_server, project=None, parent=None): - super(SyncRepresentationWidget, self).__init__(parent) - - self.sync_server = sync_server - - self._selected_ids = [] # keep last selected _id - - self.txt_filter = QtWidgets.QLineEdit() - self.txt_filter.setPlaceholderText("Quick filter representations..") - self.txt_filter.setClearButtonEnabled(True) - self.txt_filter.addAction(qtawesome.icon("fa.filter", color="gray"), - QtWidgets.QLineEdit.LeadingPosition) - - self._scrollbar_pos = None - - top_bar_layout = QtWidgets.QHBoxLayout() - top_bar_layout.addWidget(self.txt_filter) - - self.table_view = QtWidgets.QTableView() - headers = [item[0] for item in self.default_widths] - - model = SyncRepresentationSummaryModel(sync_server, headers, project) - self.table_view.setModel(model) - self.table_view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) - self.table_view.setSelectionMode( - QtWidgets.QAbstractItemView.ExtendedSelection) - self.table_view.setSelectionBehavior( - QtWidgets.QAbstractItemView.SelectRows) - self.table_view.horizontalHeader().setSortIndicator( - -1, Qt.AscendingOrder) - self.table_view.setAlternatingRowColors(True) - self.table_view.verticalHeader().hide() - - column = self.table_view.model().get_header_index("local_site") - delegate = ImageDelegate(self) - self.table_view.setItemDelegateForColumn(column, delegate) - - column = self.table_view.model().get_header_index("remote_site") - delegate = ImageDelegate(self) - self.table_view.setItemDelegateForColumn(column, delegate) - - layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.addLayout(top_bar_layout) - layout.addWidget(self.table_view) - - self.table_view.doubleClicked.connect(self._double_clicked) - self.txt_filter.textChanged.connect(lambda: model.set_word_filter( - self.txt_filter.text())) - self.table_view.customContextMenuRequested.connect( - self._on_context_menu) - - model.refresh_started.connect(self._save_scrollbar) - model.refresh_finished.connect(self._set_scrollbar) - model.modelReset.connect(self._set_selection) - - self.model = model - - self.selection_model = self.table_view.selectionModel() - self.selection_model.selectionChanged.connect(self._selection_changed) - - horizontal_header = HorizontalHeader(self) - - self.table_view.setHorizontalHeader(horizontal_header) - self.table_view.setSortingEnabled(True) - - for column_name, width in self.default_widths: - idx = model.get_header_index(column_name) - self.table_view.setColumnWidth(idx, width) - - def _selection_changed(self, new_selected, all_selected): + def _selection_changed(self, _new_selected, _all_selected): idxs = self.selection_model.selectedRows() self._selected_ids = [] @@ -273,21 +190,36 @@ class SyncRepresentationWidget(QtWidgets.QWidget): if is_multi: index = self.model.get_index(self._selected_ids[0]) - self.item = self.model.data(index, lib.FullItemRole) + item = self.model.data(index, lib.FullItemRole) else: - self.item = self.model.data(point_index, lib.FullItemRole) + item = self.model.data(point_index, lib.FullItemRole) + action_kwarg_map, actions_mapping, menu = self._prepare_menu(item, + is_multi) + + result = menu.exec_(QtGui.QCursor.pos()) + if result: + to_run = actions_mapping[result] + to_run_kwargs = action_kwarg_map.get(result, {}) + if to_run: + to_run(**to_run_kwargs) + + self.model.refresh() + + def _prepare_menu(self, item, is_multi): menu = QtWidgets.QMenu() + actions_mapping = {} action_kwarg_map = {} active_site = self.model.active_site remote_site = self.model.remote_site - local_progress = self.item.local_progress - remote_progress = self.item.remote_progress + local_progress = item.local_progress + remote_progress = item.remote_progress project = self.model.project + for site, progress in {active_site: local_progress, remote_site: remote_progress}.items(): provider = self.sync_server.get_provider_for_site(project, site) @@ -303,24 +235,6 @@ class SyncRepresentationWidget(QtWidgets.QWidget): self._get_action_kwargs(site) menu.addAction(action) - if self.item.status in [lib.STATUS[0], lib.STATUS[1]] or is_multi: - action = QtWidgets.QAction("Pause in queue") - actions_mapping[action] = self._pause - # pause handles which site_name it will pause itself - action_kwarg_map[action] = {"repre_ids": self._selected_ids} - menu.addAction(action) - - if self.item.status == lib.STATUS[3] or is_multi: - action = QtWidgets.QAction("Unpause in queue") - actions_mapping[action] = self._unpause - action_kwarg_map[action] = {"repre_ids": self._selected_ids} - menu.addAction(action) - - # if self.item.status == lib.STATUS[1]: - # action = QtWidgets.QAction("Open error detail") - # actions_mapping[action] = self._show_detail - # menu.addAction(action) - if remote_progress == 1.0 or is_multi: action = QtWidgets.QAction("Re-sync Active site") action_kwarg_map[action] = self._get_action_kwargs(active_site) @@ -350,19 +264,12 @@ class SyncRepresentationWidget(QtWidgets.QWidget): actions_mapping[action] = None menu.addAction(action) - result = menu.exec_(QtGui.QCursor.pos()) - if result: - to_run = actions_mapping[result] - to_run_kwargs = action_kwarg_map.get(result, {}) - if to_run: - to_run(**to_run_kwargs) + return action_kwarg_map, actions_mapping, menu - self.model.refresh() - - def _pause(self, repre_ids=None): - log.debug("Pause {}".format(repre_ids)) - for representation_id in repre_ids: - item = self._get_item_by_repre_id(representation_id) + def _pause(self, selected_ids=None): + log.debug("Pause {}".format(selected_ids)) + for representation_id in selected_ids: + item = lib.get_item_by_id(self.model, representation_id) if item.status not in [lib.STATUS[0], lib.STATUS[1]]: continue for site_name in [self.model.active_site, self.model.remote_site]: @@ -374,10 +281,10 @@ class SyncRepresentationWidget(QtWidgets.QWidget): self.message_generated.emit("Paused {}".format(representation_id)) - def _unpause(self, repre_ids=None): - log.debug("UnPause {}".format(repre_ids)) - for representation_id in repre_ids: - item = self._get_item_by_repre_id(representation_id) + def _unpause(self, selected_ids=None): + log.debug("UnPause {}".format(selected_ids)) + for representation_id in selected_ids: + item = lib.get_item_by_id(self.model, representation_id) if item.status not in lib.STATUS[3]: continue for site_name in [self.model.active_site, self.model.remote_site]: @@ -391,10 +298,10 @@ class SyncRepresentationWidget(QtWidgets.QWidget): self.message_generated.emit("Unpause {}".format(representation_id)) # temporary here for testing, will be removed TODO - def _add_site(self, repre_ids=None, site_name=None): - log.debug("Add site {}:{}".format(repre_ids, site_name)) - for representation_id in repre_ids: - item = self._get_item_by_repre_id(representation_id) + def _add_site(self, selected_ids=None, site_name=None): + log.debug("Add site {}:{}".format(selected_ids, site_name)) + for representation_id in selected_ids: + item = lib.get_item_by_id(self.model, representation_id) if item.local_site == site_name or item.remote_site == site_name: # site already exists skip continue @@ -411,7 +318,7 @@ class SyncRepresentationWidget(QtWidgets.QWidget): except ValueError as exp: self.message_generated.emit("Error {}".format(str(exp))) - def _remove_site(self, repre_ids=None, site_name=None): + def _remove_site(self, selected_ids=None, site_name=None): """ Removes site record AND files. @@ -421,8 +328,8 @@ class SyncRepresentationWidget(QtWidgets.QWidget): This could only happen when artist work on local machine, not connected to studio mounted drives. """ - log.debug("Remove site {}:{}".format(repre_ids, site_name)) - for representation_id in repre_ids: + log.debug("Remove site {}:{}".format(selected_ids, site_name)) + for representation_id in selected_ids: log.info("Removing {}".format(representation_id)) try: self.sync_server.remove_site( @@ -438,14 +345,14 @@ class SyncRepresentationWidget(QtWidgets.QWidget): self.model.refresh( load_records=self.model._rec_loaded) - def _reset_site(self, repre_ids=None, site_name=None): + def _reset_site(self, selected_ids=None, site_name=None): """ Removes errors or success metadata for particular file >> forces redo of upload/download """ - log.debug("Reset site {}:{}".format(repre_ids, site_name)) - for representation_id in repre_ids: - item = self._get_item_by_repre_id(representation_id) + log.debug("Reset site {}:{}".format(selected_ids, site_name)) + for representation_id in selected_ids: + item = lib.get_item_by_id(self.model, representation_id) check_progress = self._get_progress(item, site_name, True) # do not reset if opposite side is not fully there @@ -463,10 +370,10 @@ class SyncRepresentationWidget(QtWidgets.QWidget): self.model.refresh( load_records=self.model._rec_loaded) - def _open_in_explorer(self, repre_ids=None, site_name=None): - log.debug("Open in Explorer {}:{}".format(repre_ids, site_name)) - for representation_id in repre_ids: - item = self._get_item_by_repre_id(representation_id) + def _open_in_explorer(self, selected_ids=None, site_name=None): + log.debug("Open in Explorer {}:{}".format(selected_ids, site_name)) + for selected_id in selected_ids: + item = lib.get_item_by_id(self.model, selected_id) if not item: return @@ -500,14 +407,9 @@ class SyncRepresentationWidget(QtWidgets.QWidget): return progress[side] - def _get_item_by_repre_id(self, representation_id): - index = self.model.get_index(representation_id) - item = self.model.data(index, lib.FullItemRole) - return item - def _get_action_kwargs(self, site_name): """Default format of kwargs for action""" - return {"repre_ids": self._selected_ids, "site_name": site_name} + return {"selected_ids": self._selected_ids, "site_name": site_name} def _save_scrollbar(self): self._scrollbar_pos = self.table_view.verticalScrollBar().value() @@ -517,7 +419,155 @@ class SyncRepresentationWidget(QtWidgets.QWidget): self.table_view.verticalScrollBar().setValue(self._scrollbar_pos) -class SyncRepresentationDetailWidget(QtWidgets.QWidget): +class SyncRepresentationSummaryWidget(_SyncRepresentationWidget): + + default_widths = ( + ("asset", 190), + ("subset", 170), + ("version", 60), + ("representation", 145), + ("local_site", 160), + ("remote_site", 160), + ("files_count", 50), + ("files_size", 60), + ("priority", 70), + ("status", 110) + ) + + def __init__(self, sync_server, project=None, parent=None): + super(SyncRepresentationSummaryWidget, self).__init__(parent) + + self.sync_server = sync_server + + self._selected_ids = [] # keep last selected _id + + txt_filter = QtWidgets.QLineEdit() + txt_filter.setPlaceholderText("Quick filter representations..") + txt_filter.setClearButtonEnabled(True) + txt_filter.addAction(qtawesome.icon("fa.filter", color="gray"), + QtWidgets.QLineEdit.LeadingPosition) + self.txt_filter = txt_filter + + self._scrollbar_pos = None + + top_bar_layout = QtWidgets.QHBoxLayout() + top_bar_layout.addWidget(self.txt_filter) + + table_view = QtWidgets.QTableView() + headers = [item[0] for item in self.default_widths] + + model = SyncRepresentationSummaryModel(sync_server, headers, project) + table_view.setModel(model) + table_view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) + table_view.setSelectionMode( + QtWidgets.QAbstractItemView.ExtendedSelection) + table_view.setSelectionBehavior( + QtWidgets.QAbstractItemView.SelectRows) + table_view.horizontalHeader().setSortIndicator( + -1, Qt.AscendingOrder) + table_view.setAlternatingRowColors(True) + table_view.verticalHeader().hide() + + column = table_view.model().get_header_index("local_site") + delegate = ImageDelegate(self) + table_view.setItemDelegateForColumn(column, delegate) + + column = table_view.model().get_header_index("remote_site") + delegate = ImageDelegate(self) + table_view.setItemDelegateForColumn(column, delegate) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.addLayout(top_bar_layout) + layout.addWidget(table_view) + + self.table_view = table_view + self.model = model + + horizontal_header = HorizontalHeader(self) + + table_view.setHorizontalHeader(horizontal_header) + table_view.setSortingEnabled(True) + + for column_name, width in self.default_widths: + idx = model.get_header_index(column_name) + table_view.setColumnWidth(idx, width) + + table_view.doubleClicked.connect(self._double_clicked) + self.txt_filter.textChanged.connect(lambda: model.set_word_filter( + self.txt_filter.text())) + table_view.customContextMenuRequested.connect(self._on_context_menu) + + model.refresh_started.connect(self._save_scrollbar) + model.refresh_finished.connect(self._set_scrollbar) + model.modelReset.connect(self._set_selection) + + self.selection_model = self.table_view.selectionModel() + self.selection_model.selectionChanged.connect(self._selection_changed) + + + def _prepare_menu(self, item, is_multi): + action_kwarg_map, actions_mapping, menu = \ + super()._prepare_menu(item, is_multi) + + if item.status in [lib.STATUS[0], lib.STATUS[1]] or is_multi: + action = QtWidgets.QAction("Pause in queue") + actions_mapping[action] = self._pause + # pause handles which site_name it will pause itself + action_kwarg_map[action] = {"selected_ids": self._selected_ids} + menu.addAction(action) + + if item.status == lib.STATUS[3] or is_multi: + action = QtWidgets.QAction("Unpause in queue") + actions_mapping[action] = self._unpause + action_kwarg_map[action] = {"selected_ids": self._selected_ids} + menu.addAction(action) + + return action_kwarg_map, actions_mapping, menu + + +class SyncServerDetailWindow(QtWidgets.QDialog): + """Wrapper window for SyncRepresentationDetailWidget + + Creates standalone window with list of files for selected repre_id. + """ + def __init__(self, sync_server, _id, project, parent=None): + log.debug( + "!!! SyncServerDetailWindow _id:: {}".format(_id)) + super(SyncServerDetailWindow, self).__init__(parent) + self.setWindowFlags(QtCore.Qt.Window) + self.setFocusPolicy(QtCore.Qt.StrongFocus) + + self.setStyleSheet(style.load_stylesheet()) + self.setWindowIcon(QtGui.QIcon(style.app_icon_path())) + self.resize(1000, 400) + + body = QtWidgets.QWidget() + footer = QtWidgets.QWidget() + footer.setFixedHeight(20) + + container = SyncRepresentationDetailWidget(sync_server, _id, project, + parent=self) + body_layout = QtWidgets.QHBoxLayout(body) + body_layout.addWidget(container) + body_layout.setContentsMargins(0, 0, 0, 0) + + self.message = QtWidgets.QLabel() + self.message.hide() + + footer_layout = QtWidgets.QVBoxLayout(footer) + footer_layout.addWidget(self.message) + footer_layout.setContentsMargins(0, 0, 0, 0) + + layout = QtWidgets.QVBoxLayout(self) + layout.addWidget(body) + layout.addWidget(footer) + + self.setLayout(body_layout) + self.setWindowTitle("Sync Representation Detail") + + +class SyncRepresentationDetailWidget(_SyncRepresentationWidget): """ Widget to display list of synchronizable files for single repre. @@ -541,13 +591,12 @@ class SyncRepresentationDetailWidget(QtWidgets.QWidget): super(SyncRepresentationDetailWidget, self).__init__(parent) log.debug("Representation_id:{}".format(_id)) - self.representation_id = _id - self.item = None # set to item that mouse was clicked over self.project = project self.sync_server = sync_server - self._selected_id = None + self.representation_id = _id + self._selected_ids = [] self.txt_filter = QtWidgets.QLineEdit() self.txt_filter.setPlaceholderText("Quick filter representation..") @@ -568,7 +617,7 @@ class SyncRepresentationDetailWidget(QtWidgets.QWidget): table_view.setModel(model) table_view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) table_view.setSelectionMode( - QtWidgets.QAbstractItemView.SingleSelection) + QtWidgets.QAbstractItemView.ExtendedSelection) table_view.setSelectionBehavior( QtWidgets.QTableView.SelectRows) table_view.horizontalHeader().setSortIndicator(-1, Qt.AscendingOrder) @@ -613,171 +662,118 @@ class SyncRepresentationDetailWidget(QtWidgets.QWidget): model.refresh_finished.connect(self._set_scrollbar) model.modelReset.connect(self._set_selection) - def _selection_changed(self): - index = self.selection_model.currentIndex() - self._selected_id = self.model.data(index, Qt.UserRole) - - def _set_selection(self): - """ - Sets selection to 'self._selected_id' if exists. - - Keep selection during model refresh. - """ - if self._selected_id: - index = self.model.get_index(self._selected_id) - if index and index.isValid(): - mode = QtCore.QItemSelectionModel.Select | \ - QtCore.QItemSelectionModel.Rows - self.selection_model.setCurrentIndex(index, mode) - else: - self._selected_id = None - - def _show_detail(self): + def _show_detail(self, selected_ids=None): """ Shows windows with error message for failed sync of a file. """ - dt = max(self.item.created_dt, self.item.sync_dt) - detail_window = SyncRepresentationErrorWindow(self.item._id, - self.project, - dt, - self.item.tries, - self.item.error) + detail_window = SyncRepresentationErrorWindow(self.model, selected_ids) + detail_window.exec() - def _on_context_menu(self, point): - """ - Shows menu with loader actions on Right-click. - """ - point_index = self.table_view.indexAt(point) - if not point_index.isValid(): - return + def _prepare_menu(self, item, is_multi): + """Adds view (and model) dependent actions to default ones""" + action_kwarg_map, actions_mapping, menu = \ + super()._prepare_menu(item, is_multi) - self.item = self.model._data[point_index.row()] - - menu = QtWidgets.QMenu() - actions_mapping = {} - action_kwarg_map = {} - - local_site = self.item.local_site - local_progress = self.item.local_progress - remote_site = self.item.remote_site - remote_progress = self.item.remote_progress - - for site, progress in {local_site: local_progress, - remote_site: remote_progress}.items(): - project = self.model.project - provider = self.sync_server.get_provider_for_site(project, - site) - if provider == 'local_drive': - if 'studio' in site: - txt = " studio version" - else: - txt = " local version" - action = QtWidgets.QAction("Open in explorer" + txt) - if progress == 1: - actions_mapping[action] = self._open_in_explorer - action_kwarg_map[action] = {'site': site} - menu.addAction(action) - - if self.item.status == lib.STATUS[2]: + if item.status == lib.STATUS[2] or is_multi: action = QtWidgets.QAction("Open error detail") actions_mapping[action] = self._show_detail + action_kwarg_map[action] = {"selected_ids": self._selected_ids} + menu.addAction(action) - if float(remote_progress) == 1.0: - action = QtWidgets.QAction("Re-sync active site") - actions_mapping[action] = self._reset_local_site - menu.addAction(action) - - if float(local_progress) == 1.0: - action = QtWidgets.QAction("Re-sync remote site") - actions_mapping[action] = self._reset_remote_site - menu.addAction(action) - - if not actions_mapping: - action = QtWidgets.QAction("< No action >") - actions_mapping[action] = None - menu.addAction(action) - - result = menu.exec_(QtGui.QCursor.pos()) - if result: - to_run = actions_mapping[result] - to_run_kwargs = action_kwarg_map.get(result, {}) - if to_run: - to_run(**to_run_kwargs) - - def _reset_local_site(self): + return action_kwarg_map, actions_mapping, menu + + def _reset_site(self, selected_ids=None, site_name=None): """ Removes errors or success metadata for particular file >> forces redo of upload/download """ - self.sync_server.reset_provider_for_file( - self.model.project, - self.representation_id, - 'local', - self.item._id) + for file_id in selected_ids: + item = lib.get_item_by_id(self.model, file_id) + check_progress = self._get_progress(item, site_name, True) + + # do not reset if opposite side is not fully there + if check_progress != 1: + log.debug("Not fully available {} on other side, skipping". + format(check_progress)) + continue + + self.sync_server.reset_provider_for_file( + self.model.project, + self.representation_id, + site_name=site_name, + file_id=file_id, + force=True) self.model.refresh( load_records=self.model._rec_loaded) - def _reset_remote_site(self): - """ - Removes errors or success metadata for particular file >> forces - redo of upload/download - """ - self.sync_server.reset_provider_for_file( - self.model.project, - self.representation_id, - 'remote', - self.item._id) - self.model.refresh( - load_records=self.model._rec_loaded) - def _open_in_explorer(self, site): - if not self.item: - return +class SyncRepresentationErrorWindow(QtWidgets.QDialog): + """Wrapper window to show errors during sync on file(s)""" + def __init__(self, model, selected_ids, parent=None): + super(SyncRepresentationErrorWindow, self).__init__(parent) + self.setWindowFlags(QtCore.Qt.Window) + self.setFocusPolicy(QtCore.Qt.StrongFocus) - fpath = self.item.path - project = self.project - fpath = self.sync_server.get_local_file_path(project, site, fpath) + self.setStyleSheet(style.load_stylesheet()) + self.setWindowIcon(QtGui.QIcon(style.app_icon_path())) + self.resize(900, 150) - fpath = os.path.normpath(os.path.dirname(fpath)) - if os.path.isdir(fpath): - if 'win' in sys.platform: # windows - subprocess.Popen('explorer "%s"' % fpath) - elif sys.platform == 'darwin': # macOS - subprocess.Popen(['open', fpath]) - else: # linux - try: - subprocess.Popen(['xdg-open', fpath]) - except OSError: - raise OSError('unsupported xdg-open call??') + body = QtWidgets.QWidget() - def _save_scrollbar(self): - self._scrollbar_pos = self.table_view.verticalScrollBar().value() + container = SyncRepresentationErrorWidget(model, + selected_ids, + parent=self) + body_layout = QtWidgets.QHBoxLayout(body) + body_layout.addWidget(container) + body_layout.setContentsMargins(0, 0, 0, 0) - def _set_scrollbar(self): - if self._scrollbar_pos: - self.table_view.verticalScrollBar().setValue(self._scrollbar_pos) + message = QtWidgets.QLabel() + message.hide() + + layout = QtWidgets.QVBoxLayout(self) + layout.addWidget(body) + + self.setLayout(body_layout) + self.setWindowTitle("Sync Representation Error Detail") class SyncRepresentationErrorWidget(QtWidgets.QWidget): """ - Dialog to show when sync error happened, prints error message + Dialog to show when sync error happened, prints formatted error message """ - - def __init__(self, _id, dt, tries, msg, parent=None): + def __init__(self, model, selected_ids, parent=None): super(SyncRepresentationErrorWidget, self).__init__(parent) - layout = QtWidgets.QHBoxLayout(self) + layout = QtWidgets.QVBoxLayout(self) - txts = [] - txts.append("{}: {}".format("Last update date", pretty_timestamp(dt))) - txts.append("{}: {}".format("Retries", str(tries))) - txts.append("{}: {}".format("Error message", msg)) + no_errors = True + for file_id in selected_ids: + item = lib.get_item_by_id(model, file_id) + if not item.created_dt or not item.sync_dt or not item.error: + continue - text_area = QtWidgets.QPlainTextEdit("\n\n".join(txts)) - text_area.setReadOnly(True) - layout.addWidget(text_area) + no_errors = False + dt = max(item.created_dt, item.sync_dt) + + txts = [] + txts.append("{}: {}
".format("Last update date", + pretty_timestamp(dt))) + txts.append("{}: {}
".format("Retries", + str(item.tries))) + txts.append("{}: {}
".format("Error message", + item.error)) + + text_area = QtWidgets.QTextEdit("\n\n".join(txts)) + text_area.setReadOnly(True) + layout.addWidget(text_area) + + if no_errors: + text_area = QtWidgets.QTextEdit() + text_area.setText("

No errors located

") + text_area.setReadOnly(True) + layout.addWidget(text_area) class ImageDelegate(QtWidgets.QStyledItemDelegate): @@ -830,72 +826,8 @@ class ImageDelegate(QtWidgets.QStyledItemDelegate): QtGui.QBrush(QtGui.QColor(255, 0, 0, 35))) -class SyncServerDetailWindow(QtWidgets.QDialog): - def __init__(self, sync_server, _id, project, parent=None): - log.debug( - "!!! SyncServerDetailWindow _id:: {}".format(_id)) - super(SyncServerDetailWindow, self).__init__(parent) - self.setWindowFlags(QtCore.Qt.Window) - self.setFocusPolicy(QtCore.Qt.StrongFocus) - - self.setStyleSheet(style.load_stylesheet()) - self.setWindowIcon(QtGui.QIcon(style.app_icon_path())) - self.resize(1000, 400) - - body = QtWidgets.QWidget() - footer = QtWidgets.QWidget() - footer.setFixedHeight(20) - - container = SyncRepresentationDetailWidget(sync_server, _id, project, - parent=self) - body_layout = QtWidgets.QHBoxLayout(body) - body_layout.addWidget(container) - body_layout.setContentsMargins(0, 0, 0, 0) - - self.message = QtWidgets.QLabel() - self.message.hide() - - footer_layout = QtWidgets.QVBoxLayout(footer) - footer_layout.addWidget(self.message) - footer_layout.setContentsMargins(0, 0, 0, 0) - - layout = QtWidgets.QVBoxLayout(self) - layout.addWidget(body) - layout.addWidget(footer) - - self.setLayout(body_layout) - self.setWindowTitle("Sync Representation Detail") - - -class SyncRepresentationErrorWindow(QtWidgets.QDialog): - def __init__(self, _id, project, dt, tries, msg, parent=None): - super(SyncRepresentationErrorWindow, self).__init__(parent) - self.setWindowFlags(QtCore.Qt.Window) - self.setFocusPolicy(QtCore.Qt.StrongFocus) - - self.setStyleSheet(style.load_stylesheet()) - self.setWindowIcon(QtGui.QIcon(style.app_icon_path())) - self.resize(900, 150) - - body = QtWidgets.QWidget() - - container = SyncRepresentationErrorWidget(_id, dt, tries, msg, - parent=self) - body_layout = QtWidgets.QHBoxLayout(body) - body_layout.addWidget(container) - body_layout.setContentsMargins(0, 0, 0, 0) - - message = QtWidgets.QLabel() - message.hide() - - layout = QtWidgets.QVBoxLayout(self) - layout.addWidget(body) - - self.setLayout(body_layout) - self.setWindowTitle("Sync Representation Error Detail") - - class TransparentWidget(QtWidgets.QWidget): + """Used for header cell for resizing to work properly""" clicked = QtCore.Signal(str) def __init__(self, column_name, *args, **kwargs): @@ -911,7 +843,7 @@ class TransparentWidget(QtWidgets.QWidget): class HorizontalHeader(QtWidgets.QHeaderView): - + """Reiplemented QHeaderView to contain clickable changeable button""" def __init__(self, parent=None): super(HorizontalHeader, self).__init__(QtCore.Qt.Horizontal, parent) self._parent = parent @@ -939,6 +871,7 @@ class HorizontalHeader(QtWidgets.QHeaderView): return self._parent.model def init_layout(self): + """Initial preparation of header's content""" for column_idx in range(self.model.columnCount()): column_name, column_label = self.model.get_column(column_idx) filter_rec = self.model.get_filters().get(column_name) @@ -958,6 +891,7 @@ class HorizontalHeader(QtWidgets.QHeaderView): self.filter_buttons[column_name] = button def showEvent(self, event): + """Paint header""" super(HorizontalHeader, self).showEvent(event) for i in range(len(self.header_cells)): @@ -968,6 +902,7 @@ class HorizontalHeader(QtWidgets.QHeaderView): cell_content.show() def _set_filter_icon(self, column_name): + """Set different states of button depending on its engagement""" button = self.filter_buttons.get(column_name) if button: if self.checked_values.get(column_name): From e08745a1d19038a4130c93cf5fc451eb4e237e1f Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 23 Apr 2021 10:39:06 +0200 Subject: [PATCH 125/180] Hound --- openpype/modules/sync_server/tray/widgets.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/openpype/modules/sync_server/tray/widgets.py b/openpype/modules/sync_server/tray/widgets.py index c552904244..21236dc64a 100644 --- a/openpype/modules/sync_server/tray/widgets.py +++ b/openpype/modules/sync_server/tray/widgets.py @@ -310,8 +310,7 @@ class _SyncRepresentationWidget(QtWidgets.QWidget): self.sync_server.add_site( self.model.project, representation_id, - site_name - ) + site_name) self.message_generated.emit( "Site {} added for {}".format(site_name, representation_id)) @@ -444,7 +443,8 @@ class SyncRepresentationSummaryWidget(_SyncRepresentationWidget): txt_filter = QtWidgets.QLineEdit() txt_filter.setPlaceholderText("Quick filter representations..") txt_filter.setClearButtonEnabled(True) - txt_filter.addAction(qtawesome.icon("fa.filter", color="gray"), + txt_filter.addAction( + qtawesome.icon("fa.filter", color="gray"), QtWidgets.QLineEdit.LeadingPosition) self.txt_filter = txt_filter @@ -505,7 +505,6 @@ class SyncRepresentationSummaryWidget(_SyncRepresentationWidget): self.selection_model = self.table_view.selectionModel() self.selection_model.selectionChanged.connect(self._selection_changed) - def _prepare_menu(self, item, is_multi): action_kwarg_map, actions_mapping, menu = \ super()._prepare_menu(item, is_multi) @@ -683,7 +682,7 @@ class SyncRepresentationDetailWidget(_SyncRepresentationWidget): menu.addAction(action) return action_kwarg_map, actions_mapping, menu - + def _reset_site(self, selected_ids=None, site_name=None): """ Removes errors or success metadata for particular file >> forces From c137efbad2d5110138759a8cdd8327f302af1c15 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 23 Apr 2021 12:14:06 +0200 Subject: [PATCH 126/180] Hiero: fixing video file workflow `is_sequence` issue https://github.com/pypeclub/pype/pull/1236#pullrequestreview-642355199 --- openpype/plugins/publish/collect_otio_subset_resources.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/collect_otio_subset_resources.py b/openpype/plugins/publish/collect_otio_subset_resources.py index 47cb0a21a8..fe661b5157 100644 --- a/openpype/plugins/publish/collect_otio_subset_resources.py +++ b/openpype/plugins/publish/collect_otio_subset_resources.py @@ -80,6 +80,7 @@ class CollectOcioSubsetResources(pyblish.api.InstancePlugin): media_ref = otio_clip.media_reference metadata = media_ref.metadata + is_sequence = None # check in two way if it is sequence if hasattr(otio.schema, "ImageSequenceReference"): # for OpenTimelineIO 0.13 and newer @@ -126,7 +127,7 @@ class CollectOcioSubsetResources(pyblish.api.InstancePlugin): dirname, filename = os.path.split(media_ref.target_url) self.staging_dir = dirname - self.log.debug(path) + self.log.debug(filename) repre = self._create_representation( frame_start, frame_end, file=filename) From bf9479aa5319cc04f48f353991ae0066e5e15a32 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 23 Apr 2021 12:25:38 +0200 Subject: [PATCH 127/180] modified pyside installation to be independent on application name stored in settings --- .../hosts/blender/hooks/pre_pyside_install.py | 43 +++++++++++++++++-- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/blender/hooks/pre_pyside_install.py b/openpype/hosts/blender/hooks/pre_pyside_install.py index 088a27566d..613dc154c5 100644 --- a/openpype/hosts/blender/hooks/pre_pyside_install.py +++ b/openpype/hosts/blender/hooks/pre_pyside_install.py @@ -1,4 +1,5 @@ import os +import re import subprocess from openpype.lib import PreLaunchHook @@ -31,10 +32,46 @@ class InstallPySideToBlender(PreLaunchHook): def inner_execute(self): # Get blender's python directory + version_regex = re.compile("^2\.[0-9]{2}$") + executable = self.launch_context.executable.executable_path - # Blender installation contain subfolder named with it's version where - # python binaries are stored. - version_subfolder = self.launch_context.app_name.split("_")[1] + if os.path.basename(executable).lower() != "blender.exe": + self.log.info(( + "Executable does not lead to blender.exe file. Can't determine" + " blender's python to check/install PySide2." + )) + return + + executable_dir = os.path.dirname(executable) + version_subfolders = [] + for name in os.listdir(executable_dir): + fullpath = os.path.join(name, executable_dir) + if not os.path.isdir(fullpath): + continue + + if not version_regex.match(name): + continue + + version_subfolders.append(name) + + if not version_subfolders: + self.log.info( + "Didn't find version subfolder next to Blender executable" + ) + return + + if len(version_subfolders) > 1: + self.log.info(( + "Found more than one version subfolder next" + " to blender executable. {}" + ).format(", ".join([ + '"./{}"'.format(name) + for name in version_subfolders + ]))) + return + + version_subfolder = version_subfolders[0] + pythond_dir = os.path.join( os.path.dirname(executable), version_subfolder, From 2b4811020b9b3df2517edcf75d665987a31f34b9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 23 Apr 2021 12:30:24 +0200 Subject: [PATCH 128/180] fixed regex string --- openpype/hosts/blender/hooks/pre_pyside_install.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/blender/hooks/pre_pyside_install.py b/openpype/hosts/blender/hooks/pre_pyside_install.py index 613dc154c5..6d253300d9 100644 --- a/openpype/hosts/blender/hooks/pre_pyside_install.py +++ b/openpype/hosts/blender/hooks/pre_pyside_install.py @@ -32,7 +32,7 @@ class InstallPySideToBlender(PreLaunchHook): def inner_execute(self): # Get blender's python directory - version_regex = re.compile("^2\.[0-9]{2}$") + version_regex = re.compile(r"^2\.[0-9]{2}$") executable = self.launch_context.executable.executable_path if os.path.basename(executable).lower() != "blender.exe": @@ -102,6 +102,7 @@ class InstallPySideToBlender(PreLaunchHook): # Check if PySide2 is installed and skip if yes if self.is_pyside_installed(python_executable): + self.log.debug("Blender has already installed PySide2.") return # Install PySide2 in blender's python From b16c920b5d5d9a3f8bc10fd8d40d2dc25047a04e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 23 Apr 2021 17:24:25 +0200 Subject: [PATCH 129/180] Added missing RGBA template --- .../schemas/template_rgba_color.json | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 openpype/settings/entities/schemas/projects_schema/schemas/template_rgba_color.json diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/template_rgba_color.json b/openpype/settings/entities/schemas/projects_schema/schemas/template_rgba_color.json new file mode 100644 index 0000000000..ffe530175a --- /dev/null +++ b/openpype/settings/entities/schemas/projects_schema/schemas/template_rgba_color.json @@ -0,0 +1,33 @@ +[ + { + "type": "list-strict", + "key": "{name}", + "label": "{label}", + "object_types": [ + { + "label": "R", + "type": "number", + "minimum": 0, + "maximum": 255 + }, + { + "label": "G", + "type": "number", + "minimum": 0, + "maximum": 255 + }, + { + "label": "B", + "type": "number", + "minimum": 0, + "maximum": 255 + }, + { + "label": "A", + "type": "number", + "minimum": 0, + "maximum": 255 + } + ] + } +] From d3ad0499f0c5a3d6ef9db723d3597d06aee6aa85 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Fri, 23 Apr 2021 17:26:21 +0200 Subject: [PATCH 130/180] remove typo --- tools/build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/build.sh b/tools/build.sh index f64d7e79b4..fc9d6b054a 100755 --- a/tools/build.sh +++ b/tools/build.sh @@ -174,7 +174,7 @@ if command -v create-dmg > /dev/null 2>&1; then --app-drop-link 100 50 \ "$openpype_root/build/OpenPype-Installer.dmg" \ "$openpype_root/build/OpenPype.app" - elseß + else echo create-dmg command is not availableg fi From c6dae784ce313cce0c97dde4fd0270970106e851 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 23 Apr 2021 18:10:41 +0200 Subject: [PATCH 131/180] Hiero: adding callback to fix asset data in our tags if clip name changed --- openpype/hosts/hiero/api/events.py | 25 +++++++++---- openpype/hosts/hiero/api/lib.py | 56 ++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/hiero/api/events.py b/openpype/hosts/hiero/api/events.py index c02e3e2ac4..3df095f9e4 100644 --- a/openpype/hosts/hiero/api/events.py +++ b/openpype/hosts/hiero/api/events.py @@ -2,7 +2,12 @@ import os import hiero.core.events import avalon.api as avalon from openpype.api import Logger -from .lib import sync_avalon_data_to_workfile, launch_workfiles_app +from .lib import ( + sync_avalon_data_to_workfile, + launch_workfiles_app, + selection_changed_timeline, + before_project_save +) from .tags import add_tags_to_workfile from .menu import update_menu_task_label @@ -78,7 +83,7 @@ def register_hiero_events(): "Registering events for: kBeforeNewProjectCreated, " "kAfterNewProjectCreated, kBeforeProjectLoad, kAfterProjectLoad, " "kBeforeProjectSave, kAfterProjectSave, kBeforeProjectClose, " - "kAfterProjectClose, kShutdown, kStartup" + "kAfterProjectClose, kShutdown, kStartup, kSelectionChanged" ) # hiero.core.events.registerInterest( @@ -91,8 +96,8 @@ def register_hiero_events(): hiero.core.events.registerInterest( "kAfterProjectLoad", afterProjectLoad) - # hiero.core.events.registerInterest( - # "kBeforeProjectSave", beforeProjectSaved) + hiero.core.events.registerInterest( + "kBeforeProjectSave", before_project_save) # hiero.core.events.registerInterest( # "kAfterProjectSave", afterProjectSaved) # @@ -104,10 +109,16 @@ def register_hiero_events(): # hiero.core.events.registerInterest("kShutdown", shutDown) # hiero.core.events.registerInterest("kStartup", startupCompleted) - # workfiles - hiero.core.events.registerEventType("kStartWorkfiles") - hiero.core.events.registerInterest("kStartWorkfiles", launch_workfiles_app) + hiero.core.events.registerInterest( + ("kSelectionChanged", "kTimeline"), selection_changed_timeline) + # workfiles + try: + hiero.core.events.registerEventType("kStartWorkfiles") + hiero.core.events.registerInterest( + "kStartWorkfiles", launch_workfiles_app) + except RuntimeError: + pass def register_events(): """ diff --git a/openpype/hosts/hiero/api/lib.py b/openpype/hosts/hiero/api/lib.py index 553741e56d..1d613aff5a 100644 --- a/openpype/hosts/hiero/api/lib.py +++ b/openpype/hosts/hiero/api/lib.py @@ -912,3 +912,59 @@ def get_sequence_pattern_and_padding(file): return found, padding else: return None, None + +def sync_clip_name_to_data_asset(track_items_list): + # loop trough all selected clips + for track_item in track_items_list: + # ignore if parent track is locked or disabled + if track_item.parent().isLocked(): + continue + if not track_item.parent().isEnabled(): + continue + # ignore if the track item is disabled + if not track_item.isEnabled(): + continue + + # get name and data + ti_name = track_item.name() + data = get_track_item_pype_data(track_item) + + # ignore if no data on the clip or not publish instance + if not data: + continue + if data.get("id") != "pyblish.avalon.instance": + continue + + # fix data if wrong name + if data["asset"] != ti_name: + data["asset"] = ti_name + # remove the original tag + tag = get_track_item_pype_tag(track_item) + track_item.removeTag(tag) + # create new tag with updated data + set_track_item_pype_tag(track_item, data) + print("asset was changed in clip: {}".format(ti_name)) + +def selection_changed_timeline(event): + """Callback on timeline to check if asset in data is the same as clip name. + + Args: + event (hiero.core.Event): timeline event + """ + timeline_editor = event.sender + selection = timeline_editor.selection() + + # run checking function + sync_clip_name_to_data_asset(selection) + + +def before_project_save(event): + track_items = get_track_items( + selected=False, + track_type="video", + check_enabled=True, + check_locked=True, + check_tagged=True) + + # run checking function + sync_clip_name_to_data_asset(track_items) \ No newline at end of file From b9d1143257903f6268ef2b4c1136774a65774466 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 23 Apr 2021 18:11:32 +0200 Subject: [PATCH 132/180] Hiero: removing `shot` key from hierarchy data on openpype tag --- openpype/hosts/hiero/api/plugin.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/hosts/hiero/api/plugin.py b/openpype/hosts/hiero/api/plugin.py index 5620fe3cb8..4ca4fa6df1 100644 --- a/openpype/hosts/hiero/api/plugin.py +++ b/openpype/hosts/hiero/api/plugin.py @@ -710,6 +710,7 @@ class PublishClip: self.tag_data["asset"] = new_name else: self.tag_data["asset"] = self.ti_name + self.tag_data["hierarchyData"]["shot"] = self.ti_name if self.tag_data["heroTrack"] and self.review_layer: self.tag_data.update({"reviewTrack": self.review_layer}) @@ -877,6 +878,9 @@ class PublishClip: hierarchy_filled = self.hierarchy.format(**hierarchy_formating_data) clip_name_filled = self.clip_name.format(**hierarchy_formating_data) + # remove shot from hierarchy data: is not needed anymore + hierarchy_formating_data.pop("shot") + return { "newClipName": clip_name_filled, "hierarchy": hierarchy_filled, From 22eeadb62126afd006e2ce7a9081899d6a50ea1d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 23 Apr 2021 18:12:33 +0200 Subject: [PATCH 133/180] Hiero: smarter way of forming instance labels --- .../plugins/publish/precollect_instances.py | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/hiero/plugins/publish/precollect_instances.py b/openpype/hosts/hiero/plugins/publish/precollect_instances.py index beab55052d..89b65d5036 100644 --- a/openpype/hosts/hiero/plugins/publish/precollect_instances.py +++ b/openpype/hosts/hiero/plugins/publish/precollect_instances.py @@ -25,6 +25,7 @@ class PrecollectInstances(pyblish.api.ContextPlugin): for track_item in selected_timeline_items: data = dict() + clip_name = track_item.name() # get openpype tag data tag_data = phiero.get_track_item_pype_data(track_item) @@ -50,8 +51,16 @@ class PrecollectInstances(pyblish.api.ContextPlugin): families = [str(f) for f in tag_data["families"]] families.insert(0, str(family)) + # form label + label = asset + if asset != clip_name: + label += " ({})".format(clip_name) + label += " {}".format(subset) + label += " {}".format("[" + ", ".join(families) + "]") + data.update({ - "name": "{} {} {}".format(asset, subset, families), + "name": "{}_{}".format(asset, subset), + "label": label, "asset": asset, "item": track_item, "families": families, @@ -107,6 +116,9 @@ class PrecollectInstances(pyblish.api.ContextPlugin): def create_shot_instance(self, context, **data): master_layer = data.get("heroTrack") hierarchy_data = data.get("hierarchyData") + asset = data.get("asset") + item = data.get("item") + clip_name = item.name() if not master_layer: return @@ -120,8 +132,16 @@ class PrecollectInstances(pyblish.api.ContextPlugin): # insert family into families family = "shot" + # form label + label = asset + if asset != clip_name: + label += " ({}) ".format(clip_name) + label += " {}".format(subset) + label += " [{}]".format(family) + data.update({ - "name": "{} {} {}".format(asset, subset, family), + "name": "{}_{}".format(asset, subset), + "label": label, "subset": subset, "asset": asset, "family": family, From e5f433fe83f017847863948afbfcdf0031737d54 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 23 Apr 2021 18:14:12 +0200 Subject: [PATCH 134/180] Hound: suggestions --- openpype/hosts/hiero/api/lib.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/hiero/api/lib.py b/openpype/hosts/hiero/api/lib.py index 1d613aff5a..a9982d96c4 100644 --- a/openpype/hosts/hiero/api/lib.py +++ b/openpype/hosts/hiero/api/lib.py @@ -913,6 +913,7 @@ def get_sequence_pattern_and_padding(file): else: return None, None + def sync_clip_name_to_data_asset(track_items_list): # loop trough all selected clips for track_item in track_items_list: @@ -945,6 +946,7 @@ def sync_clip_name_to_data_asset(track_items_list): set_track_item_pype_tag(track_item, data) print("asset was changed in clip: {}".format(ti_name)) + def selection_changed_timeline(event): """Callback on timeline to check if asset in data is the same as clip name. @@ -967,4 +969,4 @@ def before_project_save(event): check_tagged=True) # run checking function - sync_clip_name_to_data_asset(track_items) \ No newline at end of file + sync_clip_name_to_data_asset(track_items) From a647eedf86b7c0c4bbae51fffd74a72ae198f814 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 26 Apr 2021 11:31:11 +0200 Subject: [PATCH 135/180] changed label --- .../schemas/projects_schema/schemas/schema_global_publish.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json index e6be868068..1bd028ac79 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json @@ -218,7 +218,7 @@ }, { "key": "ratio", - "label": "Letter box ratio", + "label": "Ratio", "type": "number", "decimal": 4, "default": 0, From abf0539254b1391576233ba0f0cae21fc2405801 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Mon, 26 Apr 2021 13:27:27 +0200 Subject: [PATCH 136/180] fixed missing call in mongo, few typos and check for submodules --- tools/build.ps1 | 4 ++++ tools/build.sh | 26 +++++++++++++++----------- tools/run_mongo.sh | 1 + 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/tools/build.ps1 b/tools/build.ps1 index 412bb111c1..5283ee4754 100644 --- a/tools/build.ps1 +++ b/tools/build.ps1 @@ -121,6 +121,10 @@ catch { Exit-WithCode 1 } +Write-Host ">>> " -NoNewLine -ForegroundColor green +Write-Host "Making sure submodules are up-to-date ..." +git submodule update --init --recursive + Write-Host ">>> " -NoNewline -ForegroundColor green Write-Host "Building OpenPype [ " -NoNewline -ForegroundColor white Write-host $openpype_version -NoNewline -ForegroundColor green diff --git a/tools/build.sh b/tools/build.sh index fc9d6b054a..9766e0f53c 100755 --- a/tools/build.sh +++ b/tools/build.sh @@ -157,6 +157,9 @@ main () { install_poetry || { echo -e "${BIRed}!!!${RST} Poetry installation failed"; return; } fi + echo -e "${BIGreen}>>>${RST} Making sure submodules are up-to-date ..." + git submodule update --init --recursive + echo -e "${BIGreen}>>>${RST} Building ..." if [[ "$OSTYPE" == "linux-gnu"* ]]; then poetry run python3 "$openpype_root/setup.py" build > "$openpype_root/build/build.log" || { echo -e "${BIRed}!!!${RST} Build failed, see the build log."; return; } @@ -165,17 +168,18 @@ main () { fi poetry run python3 "$openpype_root/tools/build_dependencies.py" - -if command -v create-dmg > /dev/null 2>&1; then - create-dmg \ - --volname "OpenPype Installer" \ - --window-pos 200 120 \ - --window-size 600 300 \ - --app-drop-link 100 50 \ - "$openpype_root/build/OpenPype-Installer.dmg" \ - "$openpype_root/build/OpenPype.app" - else - echo create-dmg command is not availableg + if [[ "$OSTYPE" == "darwin"* ]]; then + if command -v create-dmg > /dev/null 2>&1; then + create-dmg \ + --volname "OpenPype Installer" \ + --window-pos 200 120 \ + --window-size 600 300 \ + --app-drop-link 100 50 \ + "$openpype_root/build/OpenPype-Installer.dmg" \ + "$openpype_root/build/OpenPype.app" + else + echo -e "${BIYellow}!!!${RST} ${BIWhite}create-dmg${RST} command is not available." + fi fi echo -e "${BICyan}>>>${RST} All done. You will find OpenPype and build log in \c" diff --git a/tools/run_mongo.sh b/tools/run_mongo.sh index 1c788abcaf..8c94fcf881 100755 --- a/tools/run_mongo.sh +++ b/tools/run_mongo.sh @@ -82,3 +82,4 @@ main () { echo -e "${BIGreen}>>>${RST} Detached to background." } +main From 32cc290d4a3ad96f0cdf9ebd51928cad0b123338 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 26 Apr 2021 19:14:41 +0200 Subject: [PATCH 137/180] validate loaded mongo url and run igniter if can't connect --- start.py | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/start.py b/start.py index a2a03f112c..469f30fd5b 100644 --- a/start.py +++ b/start.py @@ -123,7 +123,10 @@ else: import igniter # noqa: E402 from igniter import BootstrapRepos # noqa: E402 -from igniter.tools import get_openpype_path_from_db # noqa +from igniter.tools import ( + get_openpype_path_from_db, + validate_mongo_connection +) # noqa from igniter.bootstrap_repos import OpenPypeVersion # noqa: E402 bootstrap = BootstrapRepos() @@ -305,20 +308,32 @@ def _determine_mongodb() -> str: openpype_mongo = os.getenv("OPENPYPE_MONGO", None) if not openpype_mongo: # try system keyring + try: + openpype_mongo = bootstrap.secure_registry.get_item( + "openPypeMongo" + ) + except ValueError: + pass + + if openpype_mongo: + result, msg = validate_mongo_connection(openpype_mongo) + if not result: + print(msg) + openpype_mongo = None + + if not openpype_mongo: + print("*** No DB connection string specified.") + print("--- launching setup UI ...") + import igniter + result = igniter.open_dialog() + if result == 0: + raise RuntimeError("MongoDB URL was not defined") + try: openpype_mongo = bootstrap.secure_registry.get_item( "openPypeMongo") except ValueError: - print("*** No DB connection string specified.") - print("--- launching setup UI ...") - import igniter - igniter.open_dialog() - - try: - openpype_mongo = bootstrap.secure_registry.get_item( - "openPypeMongo") - except ValueError: - raise RuntimeError("missing mongodb url") + raise RuntimeError("Missing MongoDB url") return openpype_mongo From 1bc908f494222e6dc9068c5d6c5a7ee72b81df5f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 26 Apr 2021 19:16:23 +0200 Subject: [PATCH 138/180] remove duplicated import --- start.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/start.py b/start.py index 469f30fd5b..9cc730c592 100644 --- a/start.py +++ b/start.py @@ -324,7 +324,7 @@ def _determine_mongodb() -> str: if not openpype_mongo: print("*** No DB connection string specified.") print("--- launching setup UI ...") - import igniter + result = igniter.open_dialog() if result == 0: raise RuntimeError("MongoDB URL was not defined") From 83a1768aecd20a090a319299cb65f3cb0b033edc Mon Sep 17 00:00:00 2001 From: jezscha Date: Tue, 27 Apr 2021 09:35:25 +0000 Subject: [PATCH 139/180] Create draft PR for #1408 From fdc3ed7d14c6740220961baf4d5e02d0df035b29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Tue, 27 Apr 2021 14:27:11 +0200 Subject: [PATCH 140/180] fix code sign issue on Catalina --- tools/build.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/build.sh b/tools/build.sh index 9766e0f53c..b7cbae5f3e 100755 --- a/tools/build.sh +++ b/tools/build.sh @@ -169,6 +169,8 @@ main () { poetry run python3 "$openpype_root/tools/build_dependencies.py" if [[ "$OSTYPE" == "darwin"* ]]; then + # fix code signing issue + codesign --remote-signature "$openpype_root/build/OpenPype.app/Contents/MacOS/lib/Python" if command -v create-dmg > /dev/null 2>&1; then create-dmg \ --volname "OpenPype Installer" \ From e9f57f64339bacf5ab0b82d64f52c1ce1e88d1c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Tue, 27 Apr 2021 14:45:53 +0200 Subject: [PATCH 141/180] fixed typo --- tools/build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/build.sh b/tools/build.sh index b7cbae5f3e..d0593a2b2f 100755 --- a/tools/build.sh +++ b/tools/build.sh @@ -170,7 +170,7 @@ main () { if [[ "$OSTYPE" == "darwin"* ]]; then # fix code signing issue - codesign --remote-signature "$openpype_root/build/OpenPype.app/Contents/MacOS/lib/Python" + codesign --remove-signature "$openpype_root/build/OpenPype.app/Contents/MacOS/lib/Python" if command -v create-dmg > /dev/null 2>&1; then create-dmg \ --volname "OpenPype Installer" \ From fcd8fb23e418babcc8115151853116e4b2b87ead Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 27 Apr 2021 16:35:14 +0200 Subject: [PATCH 142/180] Hiero: loading plugin settings --- openpype/hosts/hiero/api/plugin.py | 17 +++----- .../hosts/hiero/plugins/load/load_clip.py | 13 +++++-- .../defaults/project_settings/hiero.json | 12 ++++++ .../projects_schema/schema_project_hiero.json | 39 +++++++++++++++++++ 4 files changed, 67 insertions(+), 14 deletions(-) diff --git a/openpype/hosts/hiero/api/plugin.py b/openpype/hosts/hiero/api/plugin.py index 4ca4fa6df1..c46ef9abfa 100644 --- a/openpype/hosts/hiero/api/plugin.py +++ b/openpype/hosts/hiero/api/plugin.py @@ -388,7 +388,8 @@ class ClipLoader: # try to get value from options or evaluate key value for `load_to` self.new_sequence = options.get("newSequence") or bool( "New timeline" in options.get("load_to", "")) - + self.clip_name_template = options.get( + "clipNameTemplate") or "{asset}_{subset}_{representation}" assert self._populate_data(), str( "Cannot Load selected data, look into database " "or call your supervisor") @@ -433,7 +434,7 @@ class ClipLoader: asset = str(repr_cntx["asset"]) subset = str(repr_cntx["subset"]) representation = str(repr_cntx["representation"]) - self.data["clip_name"] = "_".join([asset, subset, representation]) + self.data["clip_name"] = self.clip_name_template.format(**repr_cntx) self.data["track_name"] = "_".join([subset, representation]) self.data["versionData"] = self.context["version"]["data"] # gets file path @@ -544,15 +545,9 @@ class ClipLoader: if "slate" in f), # if nothing was found then use default None # so other bool could be used - None) or bool((( - # put together duration of clip attributes - self.timeline_out - self.timeline_in + 1) \ - + self.handle_start \ - + self.handle_end - # and compare it with meda duration - ) > self.media_duration) - - print("__ slate_on: `{}`".format(slate_on)) + None) or bool(int( + (self.timeline_out - self.timeline_in + 1) + + self.handle_start + self.handle_end) < self.media_duration) # if slate is on then remove the slate frame from begining if slate_on: diff --git a/openpype/hosts/hiero/plugins/load/load_clip.py b/openpype/hosts/hiero/plugins/load/load_clip.py index 4eadf28956..9e12fa360e 100644 --- a/openpype/hosts/hiero/plugins/load/load_clip.py +++ b/openpype/hosts/hiero/plugins/load/load_clip.py @@ -29,13 +29,19 @@ class LoadClip(phiero.SequenceLoader): clip_color_last = "green" clip_color = "red" - def load(self, context, name, namespace, options): + clip_name_template = "{asset}_{subset}_{representation}" + def load(self, context, name, namespace, options): + # add clip name template to options + options.update({ + "clipNameTemplate": self.clip_name_template + }) # in case loader uses multiselection if self.track and self.sequence: options.update({ "sequence": self.sequence, - "track": self.track + "track": self.track, + "clipNameTemplate": self.clip_name_template }) # load clip to timeline and get main variables @@ -45,7 +51,8 @@ class LoadClip(phiero.SequenceLoader): version_data = version.get("data", {}) version_name = version.get("name", None) colorspace = version_data.get("colorspace", None) - object_name = "{}_{}".format(name, namespace) + object_name = self.clip_name_template.format( + **context["representation"]["context"]) # add additional metadata from the version to imprint Avalon knob add_keys = [ diff --git a/openpype/settings/defaults/project_settings/hiero.json b/openpype/settings/defaults/project_settings/hiero.json index b69bc66457..a8d6472c47 100644 --- a/openpype/settings/defaults/project_settings/hiero.json +++ b/openpype/settings/defaults/project_settings/hiero.json @@ -17,6 +17,18 @@ "handleEnd": 10 } }, + "load": { + "LoadClip": { + "enabled": true, + "families": [ + "render2d", "source", "plate", "render", "review" + ], + "representations": [ + "exr", "dpx", "jpg", "jpeg", "png", "h264", "mov" + ], + "clip_name_template": "{asset}_{subset}_{representation}" + } + }, "publish": { "CollectInstanceVersion": { "enabled": false diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json b/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json index d2191a45a0..f717eff7dd 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json @@ -120,6 +120,45 @@ } ] }, + { + "type": "dict", + "collapsible": true, + "key": "load", + "label": "Loader plugins", + "children": [ + { + "type": "dict", + "collapsible": true, + "key": "LoadClip", + "label": "Load Clip", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "list", + "key": "families", + "label": "Families", + "object_type": "text" + }, + { + "type": "list", + "key": "representations", + "label": "Representations", + "object_type": "text" + }, + { + "type": "text", + "key": "clip_name_template", + "label": "Clip name template" + } + ] + } + ] + }, { "type": "dict", "collapsible": true, From ecbed5139f8981ddd94693ce67843bfc0c37a677 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 27 Apr 2021 16:36:42 +0200 Subject: [PATCH 143/180] Hiero: publishing correct handle length label with review --- .../hiero/plugins/publish/precollect_instances.py | 11 ++++++++++- openpype/plugins/publish/collect_otio_review.py | 1 + .../plugins/publish/collect_otio_subset_resources.py | 6 +++--- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/hiero/plugins/publish/precollect_instances.py b/openpype/hosts/hiero/plugins/publish/precollect_instances.py index 89b65d5036..a1dee711b7 100644 --- a/openpype/hosts/hiero/plugins/publish/precollect_instances.py +++ b/openpype/hosts/hiero/plugins/publish/precollect_instances.py @@ -37,6 +37,12 @@ class PrecollectInstances(pyblish.api.ContextPlugin): if tag_data.get("id") != "pyblish.avalon.instance": continue + # solve handles length + tag_data["handleStart"] = min( + tag_data["handleStart"], int(track_item.handleInLength())) + tag_data["handleEnd"] = min( + tag_data["handleEnd"], int(track_item.handleOutLength())) + # add tag data to instance data data.update({ k: v for k, v in tag_data.items() @@ -148,7 +154,10 @@ class PrecollectInstances(pyblish.api.ContextPlugin): "families": [] }) - context.create_instance(**data) + instance = context.create_instance(**data) + self.log.info("Creating instance: {}".format(instance)) + self.log.debug( + "_ instance.data: {}".format(pformat(instance.data))) def get_otio_clip_instance_data(self, otio_timeline, track_item): """ diff --git a/openpype/plugins/publish/collect_otio_review.py b/openpype/plugins/publish/collect_otio_review.py index de85ef5f7d..e2375c70c9 100644 --- a/openpype/plugins/publish/collect_otio_review.py +++ b/openpype/plugins/publish/collect_otio_review.py @@ -88,6 +88,7 @@ class CollectOcioReview(pyblish.api.InstancePlugin): otio_review_clips.append(otio_gap) if otio_review_clips: + instance.data["label"] += " (review)" instance.data["families"] += ["review", "ftrack"] instance.data["otioReviewClips"] = otio_review_clips self.log.info( diff --git a/openpype/plugins/publish/collect_otio_subset_resources.py b/openpype/plugins/publish/collect_otio_subset_resources.py index fe661b5157..d687c1920a 100644 --- a/openpype/plugins/publish/collect_otio_subset_resources.py +++ b/openpype/plugins/publish/collect_otio_subset_resources.py @@ -48,8 +48,8 @@ class CollectOcioSubsetResources(pyblish.api.InstancePlugin): trimmed_media_range) a_frame_start, a_frame_end = openpype.lib.otio_range_to_frame_range( otio_avalable_range) - a_frame_start_h, a_frame_end_h = openpype.lib.otio_range_to_frame_range( - trimmed_media_range_h) + a_frame_start_h, a_frame_end_h = openpype.lib.\ + otio_range_to_frame_range(trimmed_media_range_h) # fix frame_start and frame_end frame to be in range of media if a_frame_start_h < a_frame_start: @@ -117,7 +117,7 @@ class CollectOcioSubsetResources(pyblish.api.InstancePlugin): # `ImageSequenceReference` path = media_ref.target_url collection_data = openpype.lib.make_sequence_collection( - path, trimmed_media_range, metadata) + path, trimmed_media_range_h, metadata) self.staging_dir, collection = collection_data self.log.debug(collection) From 71c33f298c78fe291a310f97d6b0488ba3c0dcb1 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 27 Apr 2021 16:36:59 +0200 Subject: [PATCH 144/180] Nuke: slate to version data --- openpype/hosts/nuke/plugins/publish/collect_slate_node.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/plugins/publish/collect_slate_node.py b/openpype/hosts/nuke/plugins/publish/collect_slate_node.py index 9c7f1b5e95..4257ed3131 100644 --- a/openpype/hosts/nuke/plugins/publish/collect_slate_node.py +++ b/openpype/hosts/nuke/plugins/publish/collect_slate_node.py @@ -34,7 +34,8 @@ class CollectSlate(pyblish.api.InstancePlugin): if slate_node: instance.data["slateNode"] = slate_node instance.data["families"].append("slate") + instance.data["versionData"]["families"].append("slate") self.log.info( "Slate node is in node graph: `{}`".format(slate.name())) self.log.debug( - "__ instance: `{}`".format(instance)) + "__ instance.data: `{}`".format(instance.data)) From 127a1c0f09c070e702a592a8587d5064085b1d3c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 27 Apr 2021 17:01:37 +0200 Subject: [PATCH 145/180] Nuke, Deadline: fix old name of attribute --- .../modules/deadline/plugins/publish/submit_nuke_deadline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py index 2e30e624ef..db016f6125 100644 --- a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py @@ -375,7 +375,7 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): list: captured groups list """ captured_groups = [] - for lg_name, list_node_class in self.deadline_limit_groups.items(): + for lg_name, list_node_class in self.limit_groups.items(): for node_class in list_node_class: for node in nuke.allNodes(recurseGroups=True): # ignore all nodes not member of defined class From 0f0b400158b34d4993459b105de230230de9bcd7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 27 Apr 2021 18:28:15 +0200 Subject: [PATCH 146/180] remove XML because can't be renamed --- .../hosts/houdini/startup/MainMenuCommon.XML | 65 ------------------- 1 file changed, 65 deletions(-) delete mode 100644 openpype/hosts/houdini/startup/MainMenuCommon.XML diff --git a/openpype/hosts/houdini/startup/MainMenuCommon.XML b/openpype/hosts/houdini/startup/MainMenuCommon.XML deleted file mode 100644 index 77ee182e7c..0000000000 --- a/openpype/hosts/houdini/startup/MainMenuCommon.XML +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 00ef6a707d5396adde4e66b7cd9b170987ce7ba2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 27 Apr 2021 18:28:43 +0200 Subject: [PATCH 147/180] added back xml houdini menu with lovered extension --- .../hosts/houdini/startup/MainMenuCommon.xml | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 openpype/hosts/houdini/startup/MainMenuCommon.xml diff --git a/openpype/hosts/houdini/startup/MainMenuCommon.xml b/openpype/hosts/houdini/startup/MainMenuCommon.xml new file mode 100644 index 0000000000..77ee182e7c --- /dev/null +++ b/openpype/hosts/houdini/startup/MainMenuCommon.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 89bb2ea5ca8fc17fba18169c308c526dd2a43107 Mon Sep 17 00:00:00 2001 From: mkolar Date: Tue, 27 Apr 2021 17:04:36 +0000 Subject: [PATCH 148/180] Create draft PR for #1288 From 855043d5bd9e83a70065caf0470e08c087746b19 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Tue, 27 Apr 2021 23:16:44 +0200 Subject: [PATCH 149/180] add config file for innosetup --- inno_setup.iss | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 inno_setup.iss diff --git a/inno_setup.iss b/inno_setup.iss new file mode 100644 index 0000000000..ead9907955 --- /dev/null +++ b/inno_setup.iss @@ -0,0 +1,50 @@ +; Script generated by the Inno Setup Script Wizard. +; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! + + +#define MyAppName "OpenPype" +#define Build GetEnv("BUILD_DIR") +#define AppVer GetEnv("BUILD_VERSION") + + +[Setup] +; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications. +; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) +AppId={{B9E9DF6A-5BDA-42DD-9F35-C09D564C4D93} +AppName={#MyAppName} +AppVersion={#AppVer} +AppVerName={#MyAppName} version {#AppVer} +AppPublisher=Orbi Tools s.r.o +AppPublisherURL=http://pype.club +AppSupportURL=http://pype.club +AppUpdatesURL=http://pype.club +DefaultDirName={autopf}\{#MyAppName} +DisableProgramGroupPage=yes +OutputBaseFilename={#MyAppName}-{#AppVer}-install +AllowCancelDuringInstall=yes +; Uncomment the following line to run in non administrative install mode (install for current user only.) +;PrivilegesRequired=lowest +PrivilegesRequiredOverridesAllowed=dialog +SetupIconFile=igniter\openpype.ico +OutputDir=build\ +Compression=lzma +SolidCompression=yes +WizardStyle=modern + +[Languages] +Name: "english"; MessagesFile: "compiler:Default.isl" + +[Tasks] +Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked + +[Files] +Source: "build\{#build}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs +; NOTE: Don't use "Flags: ignoreversion" on any shared system files + +[Icons] +Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\openpype_gui.exe" +Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\openpype_gui.exe"; Tasks: desktopicon + +[Run] +Filename: "{app}\openpype_gui.exe"; Description: "{cm:LaunchProgram,OpenPype}"; Flags: nowait postinstall skipifsilent + From 086e7a02c3cc1fd3b15557f29616ae85bf7af644 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Tue, 27 Apr 2021 23:17:00 +0200 Subject: [PATCH 150/180] add inno settup helper script --- tools/build_win_installer.ps1 | 140 ++++++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 tools/build_win_installer.ps1 diff --git a/tools/build_win_installer.ps1 b/tools/build_win_installer.ps1 new file mode 100644 index 0000000000..4a4d011258 --- /dev/null +++ b/tools/build_win_installer.ps1 @@ -0,0 +1,140 @@ +<# +.SYNOPSIS + Helper script to build OpenPype. + +.DESCRIPTION + This script will detect Python installation, and build OpenPype to `build` + directory using existing virtual environment created by Poetry (or + by running `/tools/create_venv.ps1`). It will then shuffle dependencies in + build folder to optimize for different Python versions (2/3) in Python host. + +.EXAMPLE + +PS> .\build.ps1 + +#> + +function Start-Progress { + param([ScriptBlock]$code) + $scroll = "/-\|/-\|" + $idx = 0 + $job = Invoke-Command -ComputerName $env:ComputerName -ScriptBlock { $code } -AsJob + + $origpos = $host.UI.RawUI.CursorPosition + + # $origpos.Y -= 1 + + while (($job.State -eq "Running") -and ($job.State -ne "NotStarted")) + { + $host.UI.RawUI.CursorPosition = $origpos + Write-Host $scroll[$idx] -NoNewline + $idx++ + if ($idx -ge $scroll.Length) + { + $idx = 0 + } + Start-Sleep -Milliseconds 100 + } + # It's over - clear the activity indicator. + $host.UI.RawUI.CursorPosition = $origpos + Write-Host ' ' + <# + .SYNOPSIS + Display spinner for running job + .PARAMETER code + Job to display spinner for + #> +} + + +function Exit-WithCode($exitcode) { + # Only exit this host process if it's a child of another PowerShell parent process... + $parentPID = (Get-CimInstance -ClassName Win32_Process -Filter "ProcessId=$PID" | Select-Object -Property ParentProcessId).ParentProcessId + $parentProcName = (Get-CimInstance -ClassName Win32_Process -Filter "ProcessId=$parentPID" | Select-Object -Property Name).Name + if ('powershell.exe' -eq $parentProcName) { $host.SetShouldExit($exitcode) } + + exit $exitcode +} + +function Show-PSWarning() { + if ($PSVersionTable.PSVersion.Major -lt 7) { + Write-Host "!!! " -NoNewline -ForegroundColor Red + Write-Host "You are using old version of PowerShell. $($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor)" + Write-Host "Please update to at least 7.0 - " -NoNewline -ForegroundColor Gray + Write-Host "https://github.com/PowerShell/PowerShell/releases" -ForegroundColor White + Exit-WithCode 1 + } +} + +function Install-Poetry() { + Write-Host ">>> " -NoNewline -ForegroundColor Green + Write-Host "Installing Poetry ... " + (Invoke-WebRequest -Uri https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py -UseBasicParsing).Content | python - + # add it to PATH + $env:PATH = "$($env:PATH);$($env:USERPROFILE)\.poetry\bin" +} + +$art = @" + +▒█▀▀▀█ █▀▀█ █▀▀ █▀▀▄ ▒█▀▀█ █░░█ █▀▀█ █▀▀ ▀█▀ ▀█▀ ▀█▀ +▒█░░▒█ █░░█ █▀▀ █░░█ ▒█▄▄█ █▄▄█ █░░█ █▀▀ ▒█░ ▒█░ ▒█░ +▒█▄▄▄█ █▀▀▀ ▀▀▀ ▀░░▀ ▒█░░░ ▄▄▄█ █▀▀▀ ▀▀▀ ▄█▄ ▄█▄ ▄█▄ + .---= [ by Pype Club ] =---. + https://openpype.io + +"@ + +Write-Host $art -ForegroundColor DarkGreen + +# Enable if PS 7.x is needed. +# Show-PSWarning + +$current_dir = Get-Location +$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent +$openpype_root = (Get-Item $script_dir).parent.FullName + +Set-Location -Path $openpype_root + +$version_file = Get-Content -Path "$($openpype_root)\openpype\version.py" +$result = [regex]::Matches($version_file, '__version__ = "(?\d+\.\d+.\d+.*)"') +$openpype_version = $result[0].Groups['version'].Value +if (-not $openpype_version) { + Write-Host "!!! " -ForegroundColor yellow -NoNewline + Write-Host "Cannot determine OpenPype version." + Exit-WithCode 1 +} +$env:BUILD_VERSION = $openpype_version + +iscc + +Write-Host ">>> " -NoNewline -ForegroundColor green +Write-Host "Creating OpenPype installer ... " -ForegroundColor white + +$build_dir_command = @" +import sys +from distutils.util import get_platform +print('exe.{}-{}'.format(get_platform(), sys.version[0:3])) +"@ + +$build_dir = & python -c $build_dir_command +Write-Host "Build directory ... ${build_dir}" -ForegroundColor white +$env:BUILD_DIR = $build_dir + +if (Get-Command iscc -errorAction SilentlyContinue -ErrorVariable ProcessError) +{ + iscc "$openpype_root\inno_setup.iss" +}else { + Write-Host "!!! Cannot find Inno Setup command" -ForegroundColor red + Write-Host "!!! You can download it at https://jrsoftware.org/" -ForegroundColor red + Exit-WithCode 1 +} + + +Write-Host ">>> " -NoNewline -ForegroundColor green +Write-Host "restoring current directory" +Set-Location -Path $current_dir + +Write-Host "*** " -NoNewline -ForegroundColor Cyan +Write-Host "All done. You will find OpenPype installer in " -NoNewLine +Write-Host "'.\build'" -NoNewline -ForegroundColor Green +Write-Host " directory." From 04c17dd1c06c0dd97c2df958daa9121fe66b77f9 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Wed, 28 Apr 2021 10:10:56 +0200 Subject: [PATCH 151/180] import houdini fps popup from the correct place --- openpype/hosts/houdini/api/lib.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index dd586ca02d..1f0f90811f 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -210,7 +210,7 @@ def validate_fps(): if current_fps != fps: - from ...widgets import popup + from openpype.widgets import popup # Find main window parent = hou.ui.mainQtWindow() @@ -219,8 +219,8 @@ def validate_fps(): else: dialog = popup.Popup2(parent=parent) dialog.setModal(True) - dialog.setWindowTitle("Maya scene not in line with project") - dialog.setMessage("The FPS is out of sync, please fix") + dialog.setWindowTitle("Houdini scene not in line with project") + dialog.setMessage("The FPS is out of sync, please fix it") # Set new text for button (add optional argument for the popup?) toggle = dialog.widgets["toggle"] From 43be4fdb4fc17dde107daf60b83cf503ff79ef01 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 28 Apr 2021 16:10:05 +0200 Subject: [PATCH 152/180] mark in on new file is set to 0 an mark out to mark in + duration --- openpype/hosts/tvpaint/api/lib.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/tvpaint/api/lib.py b/openpype/hosts/tvpaint/api/lib.py index cbc86f7b03..539cebe646 100644 --- a/openpype/hosts/tvpaint/api/lib.py +++ b/openpype/hosts/tvpaint/api/lib.py @@ -77,8 +77,9 @@ def set_context_settings(asset_doc=None): handle_start = handles handle_end = handles - frame_start -= int(handle_start) - frame_end += int(handle_end) + # Always start from 0 Mark In and set only Mark Out + mark_in = 0 + mark_out = mark_in + (frame_end - frame_start) + handle_start + handle_end - execute_george("tv_markin {} set".format(frame_start - 1)) - execute_george("tv_markout {} set".format(frame_end - 1)) + execute_george("tv_markin {} set".format(mark_in)) + execute_george("tv_markout {} set".format(mark_out)) From 8a319926d456fd182d59f3aa1780937fc46b2672 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 28 Apr 2021 16:11:30 +0200 Subject: [PATCH 153/180] added collector to collect instance frame start/end --- .../publish/collect_instance_frames.py | 37 +++++++++++++++++++ .../plugins/publish/collect_instances.py | 3 -- 2 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 openpype/hosts/tvpaint/plugins/publish/collect_instance_frames.py diff --git a/openpype/hosts/tvpaint/plugins/publish/collect_instance_frames.py b/openpype/hosts/tvpaint/plugins/publish/collect_instance_frames.py new file mode 100644 index 0000000000..f291c363b8 --- /dev/null +++ b/openpype/hosts/tvpaint/plugins/publish/collect_instance_frames.py @@ -0,0 +1,37 @@ +import pyblish.api + + +class CollectOutputFrameRange(pyblish.api.ContextPlugin): + """Collect frame start/end from context. + + When instances are collected context does not contain `frameStart` and + `frameEnd` keys yet. They are collected in global plugin + `CollectAvalonEntities`. + """ + label = "Collect output frame range" + order = pyblish.api.CollectorOrder + hosts = ["tvpaint"] + + def process(self, context): + for instance in context: + frame_start = instance.data.get("frameStart") + frame_end = instance.data.get("frameEnd") + if frame_start is not None and frame_end is not None: + self.log.debug( + "Instance {} already has set frames {}-{}".format( + str(instance), frame_start, frame_end + ) + ) + return + + frame_start = context.data.get("frameStart") + frame_end = context.data.get("frameEnd") + + instance.data["frameStart"] = frame_start + instance.data["frameEnd"] = frame_end + + self.log.info( + "Set frames {}-{} on instance {} ".format( + frame_start, frame_end, str(instance) + ) + ) diff --git a/openpype/hosts/tvpaint/plugins/publish/collect_instances.py b/openpype/hosts/tvpaint/plugins/publish/collect_instances.py index cc236734e5..27bd8e9ede 100644 --- a/openpype/hosts/tvpaint/plugins/publish/collect_instances.py +++ b/openpype/hosts/tvpaint/plugins/publish/collect_instances.py @@ -86,9 +86,6 @@ class CollectInstances(pyblish.api.ContextPlugin): instance.data["publish"] = any_visible - instance.data["frameStart"] = context.data["sceneMarkIn"] + 1 - instance.data["frameEnd"] = context.data["sceneMarkOut"] + 1 - self.log.debug("Created instance: {}\n{}".format( instance, json.dumps(instance.data, indent=4) )) From ed97fbdba05e1e270b1f2fb9804388b900372b66 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 28 Apr 2021 16:15:46 +0200 Subject: [PATCH 154/180] modified tvpaint marks validation to not care about frame index but duration --- .../tvpaint/plugins/publish/validate_marks.py | 37 ++++++++++++++----- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/publish/validate_marks.py b/openpype/hosts/tvpaint/plugins/publish/validate_marks.py index 73486d1005..e2ef81e4a4 100644 --- a/openpype/hosts/tvpaint/plugins/publish/validate_marks.py +++ b/openpype/hosts/tvpaint/plugins/publish/validate_marks.py @@ -14,37 +14,54 @@ class ValidateMarksRepair(pyblish.api.Action): def process(self, context, plugin): expected_data = ValidateMarks.get_expected_data(context) - expected_data["markIn"] -= 1 - expected_data["markOut"] -= 1 - - lib.execute_george("tv_markin {} set".format(expected_data["markIn"])) + lib.execute_george( + "tv_markin {} set".format(expected_data["markIn"]) + ) lib.execute_george( "tv_markout {} set".format(expected_data["markOut"]) ) class ValidateMarks(pyblish.api.ContextPlugin): - """Validate mark in and out are enabled.""" + """Validate mark in and out are enabled and it's duration. - label = "Validate Marks" + Mark In/Out does not have to match frameStart and frameEnd but duration is + important. + """ + + label = "Validate Mark In/Out" order = pyblish.api.ValidatorOrder optional = True actions = [ValidateMarksRepair] @staticmethod def get_expected_data(context): + scene_mark_in = context.data["sceneMarkIn"] + + # Data collected in `CollectAvalonEntities` + frame_end = context.data["frameEnd"] + frame_start = context.data["frameStart"] + handle_start = context.data["handleStart"] + handle_end = context.data["handleEnd"] + + # Calculate expeted Mark out (Mark In + duration - 1) + expected_mark_out = ( + scene_mark_in + + (frame_end - frame_start) + + handle_start + handle_end + ) return { - "markIn": int(context.data["frameStart"]), + "markIn": scene_mark_in, "markInState": True, - "markOut": int(context.data["frameEnd"]), + "markOut": expected_mark_out, "markOutState": True } def process(self, context): current_data = { - "markIn": context.data["sceneMarkIn"] + 1, + "markIn": context.data["sceneMarkIn"], "markInState": context.data["sceneMarkInState"], - "markOut": context.data["sceneMarkOut"] + 1, + "markOut": context.data["sceneMarkOut"], "markOutState": context.data["sceneMarkOutState"] } expected_data = self.get_expected_data(context) From b84357061aaf0eeebb3a9ccfe746102805834aa8 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 28 Apr 2021 16:16:30 +0200 Subject: [PATCH 155/180] tvpaint extractor render in mark in/out range but than rename output to correct sequence numbers --- .../plugins/publish/extract_sequence.py | 164 +++++++++++++----- 1 file changed, 120 insertions(+), 44 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py b/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py index 0d125a1a50..f194b5d908 100644 --- a/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py +++ b/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py @@ -45,10 +45,16 @@ class ExtractSequence(pyblish.api.Extractor): ) family_lowered = instance.data["family"].lower() - frame_start = instance.data["frameStart"] - frame_end = instance.data["frameEnd"] + mark_in = instance.context.data["sceneMarkIn"] + mark_out = instance.context.data["sceneMarkOut"] + # Frame start/end may be stored as float + frame_start = int(instance.data["frameStart"]) + frame_end = int(instance.data["frameEnd"]) - filename_template = self._get_filename_template(frame_end) + filename_template = self._get_filename_template( + # Use the biggest number + max(mark_out, frame_end) + ) ext = os.path.splitext(filename_template)[1].replace(".", "") self.log.debug("Using file template \"{}\"".format(filename_template)) @@ -66,22 +72,32 @@ class ExtractSequence(pyblish.api.Extractor): if instance.data["family"] == "review": repre_files, thumbnail_fullpath = self.render_review( - filename_template, output_dir, frame_start, frame_end + filename_template, output_dir, + mark_in, mark_out, + frame_start, frame_end ) else: # Render output repre_files, thumbnail_fullpath = self.render( - filename_template, output_dir, frame_start, frame_end, + filename_template, output_dir, + mark_in, mark_out, + frame_start, frame_end, filtered_layers ) + # Sequence of one frame + if not repre_files: + self.log.warning("Extractor did not create any output.") + return + # Fill tags and new families tags = [] if family_lowered in ("review", "renderlayer"): tags.append("review") # Sequence of one frame - if len(repre_files) == 1: + single_file = len(repre_files) == 1 + if single_file: repre_files = repre_files[0] new_repre = { @@ -89,10 +105,13 @@ class ExtractSequence(pyblish.api.Extractor): "ext": ext, "files": repre_files, "stagingDir": output_dir, - "frameStart": frame_start, - "frameEnd": frame_end, "tags": tags } + + if not single_file: + new_repre["frameStart"] = frame_start + new_repre["frameEnd"] = frame_end + self.log.debug("Creating new representation: {}".format(new_repre)) instance.data["representations"].append(new_repre) @@ -134,7 +153,8 @@ class ExtractSequence(pyblish.api.Extractor): return "{{frame:0>{}}}".format(frame_padding) + ".png" def render_review( - self, filename_template, output_dir, frame_start, frame_end + self, filename_template, output_dir, mark_in, mark_out, + frame_start, frame_end ): """ Export images from TVPaint using `tv_savesequence` command. @@ -154,10 +174,8 @@ class ExtractSequence(pyblish.api.Extractor): self.log.debug("Preparing data for rendering.") first_frame_filepath = os.path.join( output_dir, - filename_template.format(frame=frame_start) + filename_template.format(frame=mark_in) ) - mark_in = frame_start - 1 - mark_out = frame_end - 1 george_script_lines = [ "tv_SaveMode \"PNG\"", @@ -170,13 +188,28 @@ class ExtractSequence(pyblish.api.Extractor): ] lib.execute_george_through_file("\n".join(george_script_lines)) - output = [] + reversed_repre_filepaths = [] + marks_range = range(mark_out, mark_in - 1, -1) + frames_range = range(frame_end, frame_start - 1, -1) + for mark, frame in zip(marks_range, frames_range): + new_filename = filename_template.format(frame=frame) + new_filepath = os.path.join(output_dir, new_filename) + reversed_repre_filepaths.append(new_filepath) + + if mark != frame: + old_filename = filename_template.format(frame=mark) + old_filepath = os.path.join(output_dir, old_filename) + os.rename(old_filepath, new_filepath) + + repre_filepaths = list(reversed(reversed_repre_filepaths)) + repre_files = [ + os.path.basename(path) + for path in repre_filepaths + ] + first_frame_filepath = None - for frame in range(frame_start, frame_end + 1): - filename = filename_template.format(frame=frame) - output.append(filename) - if first_frame_filepath is None: - first_frame_filepath = os.path.join(output_dir, filename) + if repre_filepaths: + first_frame_filepath = repre_filepaths[0] thumbnail_filepath = os.path.join(output_dir, "thumbnail.jpg") if first_frame_filepath and os.path.exists(first_frame_filepath): @@ -184,10 +217,11 @@ class ExtractSequence(pyblish.api.Extractor): thumbnail_obj = Image.new("RGB", source_img.size, (255, 255, 255)) thumbnail_obj.paste(source_img) thumbnail_obj.save(thumbnail_filepath) - return output, thumbnail_filepath + return repre_files, thumbnail_filepath def render( - self, filename_template, output_dir, frame_start, frame_end, layers + self, filename_template, output_dir, mark_in, mark_out, + frame_start, frame_end, layers ): """ Export images from TVPaint. @@ -219,14 +253,11 @@ class ExtractSequence(pyblish.api.Extractor): # Sort layer positions in reverse order sorted_positions = list(reversed(sorted(layers_by_position.keys()))) if not sorted_positions: - return + return [], None self.log.debug("Collecting pre/post behavior of individual layers.") behavior_by_layer_id = lib.get_layers_pre_post_behavior(layer_ids) - mark_in_index = frame_start - 1 - mark_out_index = frame_end - 1 - tmp_filename_template = "pos_{pos}." + filename_template files_by_position = {} @@ -239,25 +270,60 @@ class ExtractSequence(pyblish.api.Extractor): tmp_filename_template, output_dir, behavior, - mark_in_index, - mark_out_index + mark_in, + mark_out ) - files_by_position[position] = files_by_frames + if files_by_frames: + files_by_position[position] = files_by_frames + else: + self.log.warning(( + "Skipped layer \"{}\". Probably out of Mark In/Out range." + ).format(layer["name"])) - output_filepaths = self._composite_files( + if not files_by_position: + layer_names = set(layer["name"] for layer in layers) + joined_names = ", ".join( + ["\"{}\"".format(name) for name in layer_names] + ) + self.log.warning( + "Layers {} do not have content in range {} - {}".format( + joined_names, mark_in, mark_out + ) + ) + return [], None + + output_filepaths_by_frame = self._composite_files( files_by_position, - mark_in_index, - mark_out_index, + mark_in, + mark_out, filename_template, output_dir ) self._cleanup_tmp_files(files_by_position) - thumbnail_src_filepath = None - thumbnail_filepath = None - if output_filepaths: - thumbnail_src_filepath = tuple(sorted(output_filepaths))[0] + reversed_repre_filepaths = [] + marks_range = range(mark_out, mark_in - 1, -1) + frames_range = range(frame_end, frame_start - 1, -1) + for mark, frame in zip(marks_range, frames_range): + new_filename = filename_template.format(frame=frame) + new_filepath = os.path.join(output_dir, new_filename) + reversed_repre_filepaths.append(new_filepath) + if mark != frame: + old_filepath = output_filepaths_by_frame[mark] + os.rename(old_filepath, new_filepath) + + repre_filepaths = list(reversed(reversed_repre_filepaths)) + repre_files = [ + os.path.basename(path) + for path in repre_filepaths + ] + + thumbnail_src_filepath = None + if repre_filepaths: + thumbnail_src_filepath = repre_filepaths[0] + + thumbnail_filepath = None if thumbnail_src_filepath and os.path.exists(thumbnail_src_filepath): source_img = Image.open(thumbnail_src_filepath) thumbnail_filepath = os.path.join(output_dir, "thumbnail.jpg") @@ -265,10 +331,6 @@ class ExtractSequence(pyblish.api.Extractor): thumbnail_obj.paste(source_img) thumbnail_obj.save(thumbnail_filepath) - repre_files = [ - os.path.basename(path) - for path in output_filepaths - ] return repre_files, thumbnail_filepath def _render_layer( @@ -283,6 +345,22 @@ class ExtractSequence(pyblish.api.Extractor): layer_id = layer["layer_id"] frame_start_index = layer["frame_start"] frame_end_index = layer["frame_end"] + + pre_behavior = behavior["pre"] + post_behavior = behavior["post"] + + # Check if layer is before mark in + if frame_end_index < mark_in_index: + # Skip layer if post behavior is "none" + if post_behavior == "none": + return {} + + # Check if layer is after mark out + elif frame_start_index > mark_out_index: + # Skip layer if pre behavior is "none" + if pre_behavior == "none": + return {} + exposure_frames = lib.get_exposure_frames( layer_id, frame_start_index, frame_end_index ) @@ -341,8 +419,6 @@ class ExtractSequence(pyblish.api.Extractor): self.log.debug("Filled frames {}".format(str(_debug_filled_frames))) # Fill frames by pre/post behavior of layer - pre_behavior = behavior["pre"] - post_behavior = behavior["post"] self.log.debug(( "Completing image sequence of layer by pre/post behavior." " PRE: {} | POST: {}" @@ -535,14 +611,14 @@ class ExtractSequence(pyblish.api.Extractor): process_count -= 1 processes = {} - output_filepaths = [] + output_filepaths_by_frame = {} missing_frame_paths = [] random_frame_path = None for frame_idx in sorted(images_by_frame.keys()): image_filepaths = images_by_frame[frame_idx] output_filename = filename_template.format(frame=frame_idx + 1) output_filepath = os.path.join(output_dir, output_filename) - output_filepaths.append(output_filepath) + output_filepaths_by_frame[frame_idx] = output_filepath # Store information about missing frame and skip if not image_filepaths: @@ -566,7 +642,7 @@ class ExtractSequence(pyblish.api.Extractor): random_frame_path = output_filepath self.log.info( - "Running {} compositing processes - this mey take a while.".format( + "Running {} compositing processes - this may take a while.".format( len(processes) ) ) @@ -608,7 +684,7 @@ class ExtractSequence(pyblish.api.Extractor): transparent_filepath = filepath else: self._copy_image(transparent_filepath, filepath) - return output_filepaths + return output_filepaths_by_frame def _cleanup_tmp_files(self, files_by_position): """Remove temporary files that were used for compositing.""" From 87bd8f8b40ac69c9bd4d12882a7cf3f8c51aebb1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 28 Apr 2021 18:02:39 +0200 Subject: [PATCH 156/180] removed multiprocessing of compositing as it breaks pype 3 --- .../plugins/publish/extract_sequence.py | 40 +------------------ 1 file changed, 2 insertions(+), 38 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py b/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py index f194b5d908..32375d7988 100644 --- a/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py +++ b/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py @@ -2,7 +2,6 @@ import os import shutil import time import tempfile -import multiprocessing import pyblish.api from avalon.tvpaint import lib @@ -606,11 +605,6 @@ class ExtractSequence(pyblish.api.Extractor): filepath = position_data[frame_idx] images_by_frame[frame_idx].append(filepath) - process_count = os.cpu_count() - if process_count > 1: - process_count -= 1 - - processes = {} output_filepaths_by_frame = {} missing_frame_paths = [] random_frame_path = None @@ -629,45 +623,15 @@ class ExtractSequence(pyblish.api.Extractor): if len(image_filepaths) == 1: os.rename(image_filepaths[0], output_filepath) - # Prepare process for compositing of images + # Composite images else: - processes[frame_idx] = multiprocessing.Process( - target=composite_images, - args=(image_filepaths, output_filepath) - ) + composite_images(image_filepaths, output_filepath) # Store path of random output image that will 100% exist after all # multiprocessing as mockup for missing frames if random_frame_path is None: random_frame_path = output_filepath - self.log.info( - "Running {} compositing processes - this may take a while.".format( - len(processes) - ) - ) - # Wait until all compositing processes are done - running_processes = {} - while True: - for idx in tuple(running_processes.keys()): - process = running_processes[idx] - if not process.is_alive(): - running_processes.pop(idx).join() - - if processes and len(running_processes) != process_count: - indexes = list(processes.keys()) - for _ in range(process_count - len(running_processes)): - if not indexes: - break - idx = indexes.pop(0) - running_processes[idx] = processes.pop(idx) - running_processes[idx].start() - - if not running_processes and not processes: - break - - time.sleep(0.01) - self.log.debug( "Creating transparent images for frames without render {}.".format( str(missing_frame_paths) From 8ed7ae029da2f55a2836c5f195f31ee55b901da1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 28 Apr 2021 18:03:18 +0200 Subject: [PATCH 157/180] removed unused import --- openpype/hosts/tvpaint/plugins/publish/extract_sequence.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py b/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py index 32375d7988..1ecba08dd4 100644 --- a/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py +++ b/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py @@ -1,6 +1,5 @@ import os import shutil -import time import tempfile import pyblish.api From 6a2b1021a5b2b59dc43de45e8df67fe4dcba5c13 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 28 Apr 2021 18:11:48 +0200 Subject: [PATCH 158/180] Maya in DL - fix injection of env vars --- vendor/deadline/custom/plugins/GlobalJobPreLoad.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/vendor/deadline/custom/plugins/GlobalJobPreLoad.py b/vendor/deadline/custom/plugins/GlobalJobPreLoad.py index d1287dd213..5e64605271 100644 --- a/vendor/deadline/custom/plugins/GlobalJobPreLoad.py +++ b/vendor/deadline/custom/plugins/GlobalJobPreLoad.py @@ -60,7 +60,7 @@ def inject_openpype_environment(deadlinePlugin): with open(export_url) as fp: contents = json.load(fp) for key, value in contents.items(): - deadlinePlugin.SetEnvironmentVariable(key, value) + deadlinePlugin.SetProcessEnvironmentVariable(key, value) os.remove(export_url) @@ -162,4 +162,3 @@ def __main__(deadlinePlugin): inject_openpype_environment(deadlinePlugin) else: pype(deadlinePlugin) # backward compatibility with Pype2 - From 7b688cce720c3ceecd63ba1dd15636d7fc9a683c Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 28 Apr 2021 18:13:12 +0200 Subject: [PATCH 159/180] Maya - fix handling of missing shelf It was throwing error in rendering via DL --- openpype/hosts/maya/startup/userSetup.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/maya/startup/userSetup.py b/openpype/hosts/maya/startup/userSetup.py index d556a89fa3..6d27c66882 100644 --- a/openpype/hosts/maya/startup/userSetup.py +++ b/openpype/hosts/maya/startup/userSetup.py @@ -10,7 +10,6 @@ print("starting OpenPype usersetup") settings = get_project_settings(os.environ['AVALON_PROJECT']) shelf_preset = settings['maya'].get('project_shelf') - if shelf_preset: project = os.environ["AVALON_PROJECT"] @@ -23,7 +22,7 @@ if shelf_preset: print(import_string) exec(import_string) -cmds.evalDeferred("mlib.shelf(name=shelf_preset['name'], iconPath=icon_path, preset=shelf_preset)") + cmds.evalDeferred("mlib.shelf(name=shelf_preset['name'], iconPath=icon_path, preset=shelf_preset)") print("finished OpenPype usersetup") From 5ea7e78ce6e63c9d8b8c557cf749b2dadbad8267 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 28 Apr 2021 18:16:58 +0200 Subject: [PATCH 160/180] Maya in DL - removed MayePype Special plugin is unnecessary in Pype3, as only thing it was doing is injection of env vars. This is currently handled in GlobalJobPreLoad.py --- openpype/hosts/maya/plugins/publish/collect_render.py | 4 +--- .../modules/deadline/plugins/publish/submit_maya_deadline.py | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_render.py b/openpype/hosts/maya/plugins/publish/collect_render.py index 75749a952e..647a46e240 100644 --- a/openpype/hosts/maya/plugins/publish/collect_render.py +++ b/openpype/hosts/maya/plugins/publish/collect_render.py @@ -358,9 +358,7 @@ class CollectMayaRender(pyblish.api.ContextPlugin): options["extendFrames"] = extend_frames options["overrideExistingFrame"] = override_frames - maya_render_plugin = "MayaPype" - if attributes.get("useMayaBatch", True): - maya_render_plugin = "MayaBatch" + maya_render_plugin = "MayaBatch" options["mayaRenderPlugin"] = maya_render_plugin diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 0e92fb38bb..a5841f406c 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -47,7 +47,7 @@ payload_skeleton_template = { "BatchName": None, # Top-level group name "Name": None, # Job name, as seen in Monitor "UserName": None, - "Plugin": "MayaPype", + "Plugin": "MayaBatch", "Frames": "{start}-{end}x{step}", "Comment": None, "Priority": 50, @@ -396,7 +396,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): step=int(self._instance.data["byFrameStep"])) self.payload_skeleton["JobInfo"]["Plugin"] = self._instance.data.get( - "mayaRenderPlugin", "MayaPype") + "mayaRenderPlugin", "MayaBatch") self.payload_skeleton["JobInfo"]["BatchName"] = filename # Job name, as seen in Monitor From 980fd8600414f31efe9d4f96343c701a2fa15184 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 28 Apr 2021 18:36:38 +0200 Subject: [PATCH 161/180] AE - validation for duration was 1 frame shorter --- openpype/hosts/aftereffects/api/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/aftereffects/api/__init__.py b/openpype/hosts/aftereffects/api/__init__.py index 99636e8dda..e914c26435 100644 --- a/openpype/hosts/aftereffects/api/__init__.py +++ b/openpype/hosts/aftereffects/api/__init__.py @@ -98,7 +98,7 @@ def get_asset_settings(): handle_end = asset_data.get("handleEnd") resolution_width = asset_data.get("resolutionWidth") resolution_height = asset_data.get("resolutionHeight") - duration = frame_end + handle_end - max(frame_start - handle_start, 0) + duration = (frame_end - frame_start + 1) + handle_start + handle_end entity_type = asset_data.get("entityType") scene_data = { From 923e9003c74c4dc6e190354f67d80124934f6e63 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 28 Apr 2021 20:52:23 +0200 Subject: [PATCH 162/180] Fix fps must be always str for env vars --- openpype/hosts/maya/api/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index ae2d329a97..a83ff98c99 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -1872,7 +1872,7 @@ def set_context_settings(): # Set project fps fps = asset_data.get("fps", project_data.get("fps", 25)) - api.Session["AVALON_FPS"] = fps + api.Session["AVALON_FPS"] = str(fps) set_scene_fps(fps) # Set project resolution From a8482213faf987879bf40ee2d2317f626b4661bb Mon Sep 17 00:00:00 2001 From: jezscha Date: Thu, 29 Apr 2021 08:57:54 +0000 Subject: [PATCH 163/180] Create draft PR for #1324 From c19dc5543150f0bfd176e5e59af76e54111d90cd Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 29 Apr 2021 12:29:53 +0200 Subject: [PATCH 164/180] render methods do not rename output filenames --- .../plugins/publish/extract_sequence.py | 103 +++++++----------- 1 file changed, 38 insertions(+), 65 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py b/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py index 1ecba08dd4..5ea4d34f28 100644 --- a/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py +++ b/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py @@ -69,22 +69,19 @@ class ExtractSequence(pyblish.api.Extractor): ) if instance.data["family"] == "review": - repre_files, thumbnail_fullpath = self.render_review( - filename_template, output_dir, - mark_in, mark_out, - frame_start, frame_end + output_filenames, thumbnail_fullpath = self.render_review( + filename_template, output_dir, mark_in, mark_out ) else: # Render output - repre_files, thumbnail_fullpath = self.render( + output_filenames, thumbnail_fullpath = self.render( filename_template, output_dir, mark_in, mark_out, - frame_start, frame_end, filtered_layers ) # Sequence of one frame - if not repre_files: + if not output_filenames: self.log.warning("Extractor did not create any output.") return @@ -150,10 +147,7 @@ class ExtractSequence(pyblish.api.Extractor): return "{{frame:0>{}}}".format(frame_padding) + ".png" - def render_review( - self, filename_template, output_dir, mark_in, mark_out, - frame_start, frame_end - ): + def render_review(self, filename_template, output_dir, mark_in, mark_out): """ Export images from TVPaint using `tv_savesequence` command. Args: @@ -162,8 +156,8 @@ class ExtractSequence(pyblish.api.Extractor): keyword argument `{frame}` or index argument (for same value). Extension in template must match `save_mode`. output_dir (str): Directory where files will be stored. - first_frame (int): Starting frame from which export will begin. - last_frame (int): On which frame export will end. + mark_in (int): Starting frame index from which export will begin. + mark_out (int): On which frame index export will end. Retruns: tuple: With 2 items first is list of filenames second is path to @@ -186,28 +180,22 @@ class ExtractSequence(pyblish.api.Extractor): ] lib.execute_george_through_file("\n".join(george_script_lines)) - reversed_repre_filepaths = [] - marks_range = range(mark_out, mark_in - 1, -1) - frames_range = range(frame_end, frame_start - 1, -1) - for mark, frame in zip(marks_range, frames_range): - new_filename = filename_template.format(frame=frame) - new_filepath = os.path.join(output_dir, new_filename) - reversed_repre_filepaths.append(new_filepath) - - if mark != frame: - old_filename = filename_template.format(frame=mark) - old_filepath = os.path.join(output_dir, old_filename) - os.rename(old_filepath, new_filepath) - - repre_filepaths = list(reversed(reversed_repre_filepaths)) - repre_files = [ - os.path.basename(path) - for path in repre_filepaths - ] - first_frame_filepath = None - if repre_filepaths: - first_frame_filepath = repre_filepaths[0] + output_filenames = [] + for frame in range(mark_in, mark_out + 1): + filename = filename_template.format(frame=frame) + output_filenames.append(filename) + + filepath = os.path.join(output_dir, filename) + if not os.path.exists(filepath): + raise AssertionError( + "Output was not rendered. File was not found {}".format( + filepath + ) + ) + + if first_frame_filepath is None: + first_frame_filepath = filepath thumbnail_filepath = os.path.join(output_dir, "thumbnail.jpg") if first_frame_filepath and os.path.exists(first_frame_filepath): @@ -215,12 +203,10 @@ class ExtractSequence(pyblish.api.Extractor): thumbnail_obj = Image.new("RGB", source_img.size, (255, 255, 255)) thumbnail_obj.paste(source_img) thumbnail_obj.save(thumbnail_filepath) - return repre_files, thumbnail_filepath - def render( - self, filename_template, output_dir, mark_in, mark_out, - frame_start, frame_end, layers - ): + return output_filenames, thumbnail_filepath + + def render(self, filename_template, output_dir, mark_in, mark_out, layers): """ Export images from TVPaint. Args: @@ -229,8 +215,8 @@ class ExtractSequence(pyblish.api.Extractor): keyword argument `{frame}` or index argument (for same value). Extension in template must match `save_mode`. output_dir (str): Directory where files will be stored. - first_frame (int): Starting frame from which export will begin. - last_frame (int): On which frame export will end. + mark_in (int): Starting frame index from which export will begin. + mark_out (int): On which frame index export will end. layers (list): List of layers to be exported. Retruns: @@ -290,7 +276,7 @@ class ExtractSequence(pyblish.api.Extractor): ) return [], None - output_filepaths_by_frame = self._composite_files( + output_filepaths = self._composite_files( files_by_position, mark_in, mark_out, @@ -299,27 +285,14 @@ class ExtractSequence(pyblish.api.Extractor): ) self._cleanup_tmp_files(files_by_position) - reversed_repre_filepaths = [] - marks_range = range(mark_out, mark_in - 1, -1) - frames_range = range(frame_end, frame_start - 1, -1) - for mark, frame in zip(marks_range, frames_range): - new_filename = filename_template.format(frame=frame) - new_filepath = os.path.join(output_dir, new_filename) - reversed_repre_filepaths.append(new_filepath) - - if mark != frame: - old_filepath = output_filepaths_by_frame[mark] - os.rename(old_filepath, new_filepath) - - repre_filepaths = list(reversed(reversed_repre_filepaths)) - repre_files = [ - os.path.basename(path) - for path in repre_filepaths + output_filenames = [ + os.path.basename(filepath) + for filepath in output_filepaths ] thumbnail_src_filepath = None - if repre_filepaths: - thumbnail_src_filepath = repre_filepaths[0] + if output_filepaths: + thumbnail_src_filepath = output_filepaths[0] thumbnail_filepath = None if thumbnail_src_filepath and os.path.exists(thumbnail_src_filepath): @@ -329,7 +302,7 @@ class ExtractSequence(pyblish.api.Extractor): thumbnail_obj.paste(source_img) thumbnail_obj.save(thumbnail_filepath) - return repre_files, thumbnail_filepath + return output_filenames, thumbnail_filepath def _render_layer( self, @@ -604,14 +577,14 @@ class ExtractSequence(pyblish.api.Extractor): filepath = position_data[frame_idx] images_by_frame[frame_idx].append(filepath) - output_filepaths_by_frame = {} + output_filepaths = [] missing_frame_paths = [] random_frame_path = None for frame_idx in sorted(images_by_frame.keys()): image_filepaths = images_by_frame[frame_idx] - output_filename = filename_template.format(frame=frame_idx + 1) + output_filename = filename_template.format(frame=frame_idx) output_filepath = os.path.join(output_dir, output_filename) - output_filepaths_by_frame[frame_idx] = output_filepath + output_filepaths.append(output_filepath) # Store information about missing frame and skip if not image_filepaths: @@ -647,7 +620,7 @@ class ExtractSequence(pyblish.api.Extractor): transparent_filepath = filepath else: self._copy_image(transparent_filepath, filepath) - return output_filepaths_by_frame + return output_filepaths def _cleanup_tmp_files(self, files_by_position): """Remove temporary files that were used for compositing.""" From f2510f654c790fbcfdad98edb9ff2624e33953b1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 29 Apr 2021 12:30:36 +0200 Subject: [PATCH 165/180] validation and check of used frames happens before rendering to avoid unnecessary render frames --- .../plugins/publish/extract_sequence.py | 52 ++++++++++++++++++- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py b/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py index 5ea4d34f28..b334a6feb1 100644 --- a/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py +++ b/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py @@ -49,6 +49,54 @@ class ExtractSequence(pyblish.api.Extractor): frame_start = int(instance.data["frameStart"]) frame_end = int(instance.data["frameEnd"]) + # Handles are not stored per instance but on Context + handle_start = instance.context.data["handleStart"] + handle_end = instance.context.data["handleEnd"] + + # --- Fallbacks ---------------------------------------------------- + # This is required if validations of ranges are ignored. + # - all of this code won't change processing if range to render + # match to range of expected output + + # Prepare output frames + output_frame_start = frame_start - handle_start + output_frame_end = frame_end + handle_end + + # Change output frame start to 0 if handles cause it's negative number + if output_frame_start < 0: + self.log.warning(( + "Frame start with handles has negative value." + " Changed to \"0\". Frames start: {}, Handle Start: {}" + ).format(frame_start, handle_start)) + output_frame_start = 0 + + # Check Marks range and output range + output_range = output_frame_end - output_frame_start + marks_range = mark_out - mark_in + + # Lower Mark Out if mark range is bigger than output + # - do not rendered not used frames + if output_range < marks_range: + new_mark_out = mark_out - (marks_range - output_range) + self.log.warning(( + "Lowering render range to {} frames. Changed Mark Out {} -> {}" + ).format(marks_range + 1, mark_out, new_mark_out)) + # Assign new mark out to variable + mark_out = new_mark_out + + # Lower output frame end so representation has right `frameEnd` value + elif output_range > marks_range: + new_output_frame_end = ( + output_frame_end - (output_range - marks_range) + ) + self.log.warning(( + "Lowering representation range to {} frames." + " Changed frame end {} -> {}" + ).format(output_range + 1, mark_out, new_mark_out)) + output_frame_end = new_output_frame_end + + # ------------------------------------------------------------------- + filename_template = self._get_filename_template( # Use the biggest number max(mark_out, frame_end) @@ -104,8 +152,8 @@ class ExtractSequence(pyblish.api.Extractor): } if not single_file: - new_repre["frameStart"] = frame_start - new_repre["frameEnd"] = frame_end + new_repre["frameStart"] = output_frame_start + new_repre["frameEnd"] = output_frame_end self.log.debug("Creating new representation: {}".format(new_repre)) From 6c8f8daacb455cdd39ce661e72be2d2e7c41f8f7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 29 Apr 2021 12:30:52 +0200 Subject: [PATCH 166/180] filenames are renamed after render in one method --- .../plugins/publish/extract_sequence.py | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py b/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py index b334a6feb1..770ff7b006 100644 --- a/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py +++ b/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py @@ -133,6 +133,12 @@ class ExtractSequence(pyblish.api.Extractor): self.log.warning("Extractor did not create any output.") return + repre_files = self._rename_output_files( + filename_template, output_dir, + mark_in, mark_out, + output_frame_start, output_frame_end + ) + # Fill tags and new families tags = [] if family_lowered in ("review", "renderlayer"): @@ -195,6 +201,44 @@ class ExtractSequence(pyblish.api.Extractor): return "{{frame:0>{}}}".format(frame_padding) + ".png" + def _rename_output_files( + self, filename_template, output_dir, + mark_in, mark_out, output_frame_start, output_frame_end + ): + # Use differnet ranges based on Mark In and output Frame Start values + # - this is to make sure that filename renaming won't affect files that + # are not renamed yet + mark_start_is_less = bool(mark_in < output_frame_start) + if mark_start_is_less: + marks_range = range(mark_out, mark_in - 1, -1) + frames_range = range(output_frame_end, output_frame_start - 1, -1) + else: + # This is less possible situation as frame start will be in most + # cases higher than Mark In. + marks_range = range(mark_in, mark_out + 1) + frames_range = range(output_frame_start, output_frame_end + 1) + + repre_filepaths = [] + for mark, frame in zip(marks_range, frames_range): + new_filename = filename_template.format(frame=frame) + new_filepath = os.path.join(output_dir, new_filename) + + repre_filepaths.append(new_filepath) + + if mark != frame: + old_filename = filename_template.format(frame=mark) + old_filepath = os.path.join(output_dir, old_filename) + os.rename(old_filepath, new_filepath) + + # Reverse repre files order if output + if mark_start_is_less: + repre_filepaths = list(reversed(repre_filepaths)) + + return [ + os.path.basename(path) + for path in repre_filepaths + ] + def render_review(self, filename_template, output_dir, mark_in, mark_out): """ Export images from TVPaint using `tv_savesequence` command. From c5d5ba51aacf5a2904b8209666c64f5f4cb05cfb Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 29 Apr 2021 12:31:05 +0200 Subject: [PATCH 167/180] temp directory has prefix --- openpype/hosts/tvpaint/plugins/publish/extract_sequence.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py b/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py index 770ff7b006..007b5c41f1 100644 --- a/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py +++ b/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py @@ -109,7 +109,9 @@ class ExtractSequence(pyblish.api.Extractor): output_dir = instance.data.get("stagingDir") if not output_dir: # Create temp folder if staging dir is not set - output_dir = tempfile.mkdtemp().replace("\\", "/") + output_dir = ( + tempfile.mkdtemp(prefix="tvpaint_render_") + ).replace("\\", "/") instance.data["stagingDir"] = output_dir self.log.debug( From 933b7d9e1e43d4f5ee76e0eb9bb6bce0b80779d5 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 29 Apr 2021 12:40:59 +0200 Subject: [PATCH 168/180] SyncServer - handle 500 error in Gdrive init Introduced recoverable exception ResumableError --- .../modules/sync_server/providers/gdrive.py | 11 +++++-- openpype/modules/sync_server/sync_server.py | 32 +++++++++++-------- openpype/modules/sync_server/utils.py | 5 +++ 3 files changed, 33 insertions(+), 15 deletions(-) diff --git a/openpype/modules/sync_server/providers/gdrive.py b/openpype/modules/sync_server/providers/gdrive.py index f1ea24f601..b67e5a6cfa 100644 --- a/openpype/modules/sync_server/providers/gdrive.py +++ b/openpype/modules/sync_server/providers/gdrive.py @@ -7,7 +7,7 @@ from .abstract_provider import AbstractProvider from googleapiclient.http import MediaFileUpload, MediaIoBaseDownload from openpype.api import Logger from openpype.api import get_system_settings -from ..utils import time_function +from ..utils import time_function, ResumableError import time @@ -63,7 +63,14 @@ class GDriveHandler(AbstractProvider): return self.service = self._get_gd_service() - self.root = self._prepare_root_info() + try: + self.root = self._prepare_root_info() + except errors.HttpError: + log.warning("HttpError in sync loop, " + "trying next loop", + exc_info=True) + raise ResumableError + self._tree = tree self.active = True diff --git a/openpype/modules/sync_server/sync_server.py b/openpype/modules/sync_server/sync_server.py index e97c0e8844..42769ceec2 100644 --- a/openpype/modules/sync_server/sync_server.py +++ b/openpype/modules/sync_server/sync_server.py @@ -8,7 +8,7 @@ from concurrent.futures._base import CancelledError from .providers import lib from openpype.lib import PypeLogger -from .utils import SyncStatus +from .utils import SyncStatus, ResumableError log = PypeLogger().get_logger("SyncServer") @@ -266,8 +266,8 @@ class SyncServerThread(threading.Thread): Returns: """ - try: - while self.is_running and not self.module.is_paused(): + while self.is_running and not self.module.is_paused(): + try: import time start_time = None self.module.set_sync_project_settings() # clean cache @@ -385,16 +385,22 @@ class SyncServerThread(threading.Thread): duration = time.time() - start_time log.debug("One loop took {:.2f}s".format(duration)) await asyncio.sleep(self.module.get_loop_delay(collection)) - except ConnectionResetError: - log.warning("ConnectionResetError in sync loop, trying next loop", - exc_info=True) - except CancelledError: - # just stopping server - pass - except Exception: - self.stop() - log.warning("Unhandled exception in sync loop, stopping server", - exc_info=True) + + except ConnectionResetError: + log.warning("ConnectionResetError in sync loop, " + "trying next loop", + exc_info=True) + except CancelledError: + # just stopping server + pass + except ResumableError: + log.warning("ResumableError in sync loop, " + "trying next loop", + exc_info=True) + except Exception: + self.stop() + log.warning("Unhandled exception in sync loop, stopping server", + exc_info=True) def stop(self): """Sets is_running flag to false, 'check_shutdown' shuts server down""" diff --git a/openpype/modules/sync_server/utils.py b/openpype/modules/sync_server/utils.py index 36f3444399..fa6e63b029 100644 --- a/openpype/modules/sync_server/utils.py +++ b/openpype/modules/sync_server/utils.py @@ -3,6 +3,11 @@ from openpype.api import Logger log = Logger().get_logger("SyncServer") +class ResumableError(Exception): + """Error which could be temporary, skip current loop, try next time""" + pass + + class SyncStatus: DO_NOTHING = 0 DO_UPLOAD = 1 From 9a8b9c1f74085caabcec25778c5fcbb8517fc42f Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 29 Apr 2021 12:42:30 +0200 Subject: [PATCH 169/180] SyncServer - bump up default of processed files in one loop for local_drive --- openpype/modules/sync_server/providers/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/sync_server/providers/lib.py b/openpype/modules/sync_server/providers/lib.py index 58947e115d..01a5d50ba5 100644 --- a/openpype/modules/sync_server/providers/lib.py +++ b/openpype/modules/sync_server/providers/lib.py @@ -92,4 +92,4 @@ factory = ProviderFactory() # 7 denotes number of files that could be synced in single loop - learned by # trial and error factory.register_provider('gdrive', GDriveHandler, 7) -factory.register_provider('local_drive', LocalDriveHandler, 10) +factory.register_provider('local_drive', LocalDriveHandler, 50) From c70ef75042080ae0b208b1daa01c6ba20c5e5ecd Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 29 Apr 2021 12:46:53 +0200 Subject: [PATCH 170/180] Hiero: workfile plugins --- .../hiero/plugins/publish/extract_workfile.py | 50 ------------------- .../integrate_version_up_workfile.py} | 2 +- 2 files changed, 1 insertion(+), 51 deletions(-) delete mode 100644 openpype/hosts/hiero/plugins/publish/extract_workfile.py rename openpype/hosts/hiero/plugins/{publish_old_workflow/version_up_workfile.py => publish/integrate_version_up_workfile.py} (90%) diff --git a/openpype/hosts/hiero/plugins/publish/extract_workfile.py b/openpype/hosts/hiero/plugins/publish/extract_workfile.py deleted file mode 100644 index e3d60465a2..0000000000 --- a/openpype/hosts/hiero/plugins/publish/extract_workfile.py +++ /dev/null @@ -1,50 +0,0 @@ -import os -import pyblish.api -import openpype.api -from openpype.hosts import resolve - - -class ExtractWorkfile(openpype.api.Extractor): - """ - Extractor export DRP workfile file representation - """ - - label = "Extract Workfile" - order = pyblish.api.ExtractorOrder - families = ["workfile"] - hosts = ["resolve"] - - def process(self, instance): - # create representation data - if "representations" not in instance.data: - instance.data["representations"] = [] - - name = instance.data["name"] - project = instance.context.data["activeProject"] - staging_dir = self.staging_dir(instance) - - resolve_workfile_ext = ".drp" - drp_file_name = name + resolve_workfile_ext - drp_file_path = os.path.normpath( - os.path.join(staging_dir, drp_file_name)) - - # write out the drp workfile - resolve.get_project_manager().ExportProject( - project.GetName(), drp_file_path) - - # create drp workfile representation - representation_drp = { - 'name': resolve_workfile_ext[1:], - 'ext': resolve_workfile_ext[1:], - 'files': drp_file_name, - "stagingDir": staging_dir, - } - - instance.data["representations"].append(representation_drp) - - # add sourcePath attribute to instance - if not instance.data.get("sourcePath"): - instance.data["sourcePath"] = drp_file_path - - self.log.info("Added Resolve file representation: {}".format( - representation_drp)) diff --git a/openpype/hosts/hiero/plugins/publish_old_workflow/version_up_workfile.py b/openpype/hosts/hiero/plugins/publish/integrate_version_up_workfile.py similarity index 90% rename from openpype/hosts/hiero/plugins/publish_old_workflow/version_up_workfile.py rename to openpype/hosts/hiero/plugins/publish/integrate_version_up_workfile.py index ae03513d78..934e7112fa 100644 --- a/openpype/hosts/hiero/plugins/publish_old_workflow/version_up_workfile.py +++ b/openpype/hosts/hiero/plugins/publish/integrate_version_up_workfile.py @@ -2,7 +2,7 @@ from pyblish import api import openpype.api as pype -class VersionUpWorkfile(api.ContextPlugin): +class IntegrateVersionUpWorkfile(api.ContextPlugin): """Save as new workfile version""" order = api.IntegratorOrder + 10.1 From 5faa0f9f211166939536d6d00b7d1657c889f097 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 29 Apr 2021 12:47:26 +0200 Subject: [PATCH 171/180] Heiro: generate thumnails for plates, takes and workfiles --- .../plugins/publish/extract_thumbnail.py | 59 +++++++++++++++++++ .../plugins/publish/precollect_workfile.py | 43 +++++++++++++- 2 files changed, 99 insertions(+), 3 deletions(-) create mode 100644 openpype/hosts/hiero/plugins/publish/extract_thumbnail.py diff --git a/openpype/hosts/hiero/plugins/publish/extract_thumbnail.py b/openpype/hosts/hiero/plugins/publish/extract_thumbnail.py new file mode 100644 index 0000000000..7939a1b0cd --- /dev/null +++ b/openpype/hosts/hiero/plugins/publish/extract_thumbnail.py @@ -0,0 +1,59 @@ +import os +import pyblish.api +import openpype.api + + +class ExtractThumnail(openpype.api.Extractor): + """ + Extractor for track item's tumnails + """ + + label = "Extract Thumnail" + order = pyblish.api.ExtractorOrder + # order = pyblish.api.CollectorOrder + families = ["plate", "take"] + hosts = ["hiero"] + + def process(self, instance): + # create representation data + if "representations" not in instance.data: + instance.data["representations"] = [] + + staging_dir = self.staging_dir(instance) + + self.create_thumbnail(staging_dir, instance) + + def create_thumbnail(self, staging_dir, instance): + track_item = instance.data["item"] + track_item_name = track_item.name() + + # frames + duration = track_item.sourceDuration() + frame_start = track_item.sourceIn() + self.log.debug( + "__ frame_start: `{}`, duration: `{}`".format(frame_start, duration)) + + # get thumbnail frame from the middle + thumb_frame = int(frame_start + (duration / 2)) + + thumb_file = "{}thumbnail{}{}".format( + track_item_name, thumb_frame, ".png") + thumb_path = os.path.join(staging_dir, thumb_file) + + thumbnail = track_item.thumbnail(thumb_frame).save( + thumb_path, + format='png' + ) + self.log.debug( + "__ thumb_path: `{}`, frame: `{}`".format(thumbnail, thumb_frame)) + + self.log.info("Thumnail was generated to: {}".format(thumb_path)) + thumb_representation = { + 'files': thumb_file, + 'stagingDir': staging_dir, + 'name': "thumbnail", + 'thumbnail': True, + 'ext': "png" + } + instance.data["representations"].append( + thumb_representation) diff --git a/openpype/hosts/hiero/plugins/publish/precollect_workfile.py b/openpype/hosts/hiero/plugins/publish/precollect_workfile.py index 22201cafe3..6f665e857d 100644 --- a/openpype/hosts/hiero/plugins/publish/precollect_workfile.py +++ b/openpype/hosts/hiero/plugins/publish/precollect_workfile.py @@ -1,10 +1,13 @@ +import os import pyblish.api import hiero.ui from openpype.hosts.hiero import api as phiero from avalon import api as avalon from pprint import pformat from openpype.hosts.hiero.otio import hiero_export - +from Qt.QtGui import QPixmap +import tempfile +import hiero.ui class PrecollectWorkfile(pyblish.api.ContextPlugin): """Inject the current working file into context""" @@ -23,12 +26,46 @@ class PrecollectWorkfile(pyblish.api.ContextPlugin): # adding otio timeline to context otio_timeline = hiero_export.create_otio_timeline() + # get workfile thumnail paths + tmp_staging = tempfile.mkdtemp(prefix="pyblish_tmp_") + thumbnail_name = "workfile_thumbnail.png" + thumbnail_path = os.path.join(tmp_staging, thumbnail_name) + + # search for all windows with name of actual sequence + _windows = [w for w in hiero.ui.windowManager().windows() + if active_timeline.name() in w.windowTitle()] + + # export window to thumb path + QPixmap.grabWidget(_windows[-1]).save(thumbnail_path, 'png') + + # thumbnail + thumb_representation = { + 'files': thumbnail_name, + 'stagingDir': tmp_staging, + 'name': "thumbnail", + 'thumbnail': True, + 'ext': "png" + } + + # get workfile paths + curent_file = project.path() + staging_dir, base_name = os.path.split(curent_file) + + # creating workfile representation + workfile_representation = { + 'name': 'hrox', + 'ext': 'hrox', + 'files': base_name, + "stagingDir": staging_dir, + } + instance_data = { "name": "{}_{}".format(asset, subset), "asset": asset, "subset": "{}{}".format(asset, subset.capitalize()), "item": project, - "family": "workfile" + "family": "workfile", + "representations": [workfile_representation, thumb_representation] } # create instance with workfile @@ -38,7 +75,7 @@ class PrecollectWorkfile(pyblish.api.ContextPlugin): context_data = { "activeProject": project, "otioTimeline": otio_timeline, - "currentFile": project.path(), + "currentFile": curent_file, "fps": fps, } context.data.update(context_data) From 376c4bfa2d8a1a8370e285b34263eef7ae9a4031 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 29 Apr 2021 12:50:08 +0200 Subject: [PATCH 172/180] Hound: suggestions --- openpype/hosts/hiero/plugins/publish/extract_thumbnail.py | 4 ++-- openpype/hosts/hiero/plugins/publish/precollect_workfile.py | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/hiero/plugins/publish/extract_thumbnail.py b/openpype/hosts/hiero/plugins/publish/extract_thumbnail.py index 7939a1b0cd..d12e7665bf 100644 --- a/openpype/hosts/hiero/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/hiero/plugins/publish/extract_thumbnail.py @@ -10,7 +10,6 @@ class ExtractThumnail(openpype.api.Extractor): label = "Extract Thumnail" order = pyblish.api.ExtractorOrder - # order = pyblish.api.CollectorOrder families = ["plate", "take"] hosts = ["hiero"] @@ -31,7 +30,8 @@ class ExtractThumnail(openpype.api.Extractor): duration = track_item.sourceDuration() frame_start = track_item.sourceIn() self.log.debug( - "__ frame_start: `{}`, duration: `{}`".format(frame_start, duration)) + "__ frame_start: `{}`, duration: `{}`".format( + frame_start, duration)) # get thumbnail frame from the middle thumb_frame = int(frame_start + (duration / 2)) diff --git a/openpype/hosts/hiero/plugins/publish/precollect_workfile.py b/openpype/hosts/hiero/plugins/publish/precollect_workfile.py index 6f665e857d..bc4ef7e150 100644 --- a/openpype/hosts/hiero/plugins/publish/precollect_workfile.py +++ b/openpype/hosts/hiero/plugins/publish/precollect_workfile.py @@ -7,7 +7,6 @@ from pprint import pformat from openpype.hosts.hiero.otio import hiero_export from Qt.QtGui import QPixmap import tempfile -import hiero.ui class PrecollectWorkfile(pyblish.api.ContextPlugin): """Inject the current working file into context""" From 9d88f633321de14afd66269e360c873f29ba2930 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 29 Apr 2021 13:02:30 +0200 Subject: [PATCH 173/180] SyncServer - remember last sort in Sync Queue app --- openpype/modules/sync_server/tray/models.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/openpype/modules/sync_server/tray/models.py b/openpype/modules/sync_server/tray/models.py index ffd81a1588..bfe4db32f3 100644 --- a/openpype/modules/sync_server/tray/models.py +++ b/openpype/modules/sync_server/tray/models.py @@ -170,6 +170,8 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): Sort is happening on a DB side, model is reset, db queried again. + It remembers one last sort, adds it as secondary after new sort. + Args: index (int): column index order (int): 0| @@ -184,7 +186,17 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): else: order = -1 - self.sort = {self.SORT_BY_COLUMN[index]: order, '_id': 1} + backup_sort = dict(self.sort) + + self.sort = {self.SORT_BY_COLUMN[index]: order} # reset + # add last one + for key, val in backup_sort.items(): + if key != '_id': + self.sort[key] = val + break + # add default one + self.sort['_id'] = 1 + self.query = self.get_query() # import json # log.debug(json.dumps(self.query, indent=4).\ From d8d241ac5a5f4623a9c70526bd118518195241e7 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 29 Apr 2021 13:03:33 +0200 Subject: [PATCH 174/180] SyncServer - change title of window --- openpype/modules/sync_server/tray/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/sync_server/tray/app.py b/openpype/modules/sync_server/tray/app.py index d91ba76335..2538675c51 100644 --- a/openpype/modules/sync_server/tray/app.py +++ b/openpype/modules/sync_server/tray/app.py @@ -78,7 +78,7 @@ class SyncServerWindow(QtWidgets.QDialog): layout.addWidget(footer) self.setLayout(body_layout) - self.setWindowTitle("Sync Server") + self.setWindowTitle("Sync Queue") self.projects.project_changed.connect( lambda: repres.table_view.model().set_project( From 8b570c7c822b97cd569d0fade2a1515160eb7b37 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 29 Apr 2021 13:33:43 +0200 Subject: [PATCH 175/180] SyncServer - change title of window --- openpype/modules/sync_server/sync_server.py | 18 +++++++++++++++++- .../modules/sync_server/sync_server_module.py | 8 ++++++++ openpype/modules/sync_server/tray/widgets.py | 3 +++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/openpype/modules/sync_server/sync_server.py b/openpype/modules/sync_server/sync_server.py index 42769ceec2..9c85dd497a 100644 --- a/openpype/modules/sync_server/sync_server.py +++ b/openpype/modules/sync_server/sync_server.py @@ -232,6 +232,7 @@ class SyncServerThread(threading.Thread): self.loop = None self.is_running = False self.executor = concurrent.futures.ThreadPoolExecutor(max_workers=3) + self.timer = None def run(self): self.is_running = True @@ -384,7 +385,11 @@ class SyncServerThread(threading.Thread): duration = time.time() - start_time log.debug("One loop took {:.2f}s".format(duration)) - await asyncio.sleep(self.module.get_loop_delay(collection)) + + delay = self.module.get_loop_delay(collection) + log.debug("Waiting for {} seconds to new loop".format(delay)) + self.timer = asyncio.create_task(self.run_timer(delay)) + await asyncio.gather(self.timer) except ConnectionResetError: log.warning("ConnectionResetError in sync loop, " @@ -423,6 +428,17 @@ class SyncServerThread(threading.Thread): await asyncio.sleep(0.07) self.loop.stop() + async def run_timer(self, delay): + """Wait for 'delay' seconds to start next loop""" + await asyncio.sleep(delay) + + def reset_timer(self): + """Called when waiting for next loop should be skipped""" + log.debug("Resetting timer") + if self.timer: + self.timer.cancel() + self.timer = None + def _working_sites(self, collection): if self.module.is_project_paused(collection): log.debug("Both sites same, skipping") diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index 59c3787789..ca4033b385 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -401,6 +401,14 @@ class SyncServerModule(PypeModule, ITrayModule): return remote_site + def reset_timer(self): + """ + Called when waiting for next loop should be skipped. + + In case of user's involvement (reset site), start that right away. + """ + self.sync_server_thread.reset_timer() + """ End of Public API """ def get_local_file_path(self, collection, site_name, file_path): diff --git a/openpype/modules/sync_server/tray/widgets.py b/openpype/modules/sync_server/tray/widgets.py index 21236dc64a..106fc4b8a8 100644 --- a/openpype/modules/sync_server/tray/widgets.py +++ b/openpype/modules/sync_server/tray/widgets.py @@ -316,6 +316,7 @@ class _SyncRepresentationWidget(QtWidgets.QWidget): representation_id)) except ValueError as exp: self.message_generated.emit("Error {}".format(str(exp))) + self.sync_server.reset_timer() def _remove_site(self, selected_ids=None, site_name=None): """ @@ -343,6 +344,7 @@ class _SyncRepresentationWidget(QtWidgets.QWidget): self.model.refresh( load_records=self.model._rec_loaded) + self.sync_server.reset_timer() def _reset_site(self, selected_ids=None, site_name=None): """ @@ -368,6 +370,7 @@ class _SyncRepresentationWidget(QtWidgets.QWidget): self.model.refresh( load_records=self.model._rec_loaded) + self.sync_server.reset_timer() def _open_in_explorer(self, selected_ids=None, site_name=None): log.debug("Open in Explorer {}:{}".format(selected_ids, site_name)) From 1cb47023a103b5e09a19aac1f7eb36a53c8ae7f3 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 29 Apr 2021 14:02:29 +0200 Subject: [PATCH 176/180] Hound --- openpype/modules/sync_server/sync_server.py | 2 +- openpype/modules/sync_server/tray/models.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/modules/sync_server/sync_server.py b/openpype/modules/sync_server/sync_server.py index 9c85dd497a..9b305a1b2e 100644 --- a/openpype/modules/sync_server/sync_server.py +++ b/openpype/modules/sync_server/sync_server.py @@ -404,7 +404,7 @@ class SyncServerThread(threading.Thread): exc_info=True) except Exception: self.stop() - log.warning("Unhandled exception in sync loop, stopping server", + log.warning("Unhandled except. in sync loop, stopping server", exc_info=True) def stop(self): diff --git a/openpype/modules/sync_server/tray/models.py b/openpype/modules/sync_server/tray/models.py index bfe4db32f3..8fdd9487a4 100644 --- a/openpype/modules/sync_server/tray/models.py +++ b/openpype/modules/sync_server/tray/models.py @@ -196,7 +196,7 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): break # add default one self.sort['_id'] = 1 - + self.query = self.get_query() # import json # log.debug(json.dumps(self.query, indent=4).\ From 3e5c4e7b664c557229865cf8885f877d59ec4c56 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 29 Apr 2021 14:04:48 +0200 Subject: [PATCH 177/180] get rid of empty strings from settings --- openpype/plugins/publish/extract_review.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/openpype/plugins/publish/extract_review.py b/openpype/plugins/publish/extract_review.py index 00b671199d..f341ba197f 100644 --- a/openpype/plugins/publish/extract_review.py +++ b/openpype/plugins/publish/extract_review.py @@ -333,10 +333,24 @@ class ExtractReview(pyblish.api.InstancePlugin): # Get FFmpeg arguments from profile presets out_def_ffmpeg_args = output_def.get("ffmpeg_args") or {} - ffmpeg_input_args = out_def_ffmpeg_args.get("input") or [] - ffmpeg_output_args = out_def_ffmpeg_args.get("output") or [] - ffmpeg_video_filters = out_def_ffmpeg_args.get("video_filters") or [] - ffmpeg_audio_filters = out_def_ffmpeg_args.get("audio_filters") or [] + _ffmpeg_input_args = out_def_ffmpeg_args.get("input") or [] + _ffmpeg_output_args = out_def_ffmpeg_args.get("output") or [] + _ffmpeg_video_filters = out_def_ffmpeg_args.get("video_filters") or [] + _ffmpeg_audio_filters = out_def_ffmpeg_args.get("audio_filters") or [] + + # Cleanup empty strings + ffmpeg_input_args = [ + value for value in _ffmpeg_input_args if value.strip() + ] + ffmpeg_output_args = [ + value for value in _ffmpeg_output_args if value.strip() + ] + ffmpeg_video_filters = [ + value for value in _ffmpeg_video_filters if value.strip() + ] + ffmpeg_audio_filters = [ + value for value in _ffmpeg_audio_filters if value.strip() + ] if isinstance(new_repre['files'], list): input_files_urls = [os.path.join(new_repre["stagingDir"], f) for f From 44262ab12774015463857f9714da0dcb0aba965f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 29 Apr 2021 14:50:02 +0200 Subject: [PATCH 178/180] Nuke: addressing https://github.com/pypeclub/client/issues/57 --- openpype/hosts/nuke/plugins/load/load_sequence.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/nuke/plugins/load/load_sequence.py b/openpype/hosts/nuke/plugins/load/load_sequence.py index df7aa55cd1..50c13563b1 100644 --- a/openpype/hosts/nuke/plugins/load/load_sequence.py +++ b/openpype/hosts/nuke/plugins/load/load_sequence.py @@ -139,11 +139,15 @@ class LoadSequence(api.Loader): read_name = self.node_name_template.format(**name_data) # Create the Loader with the filename path set + + # TODO: it might be universal read to img/geo/camera + r = nuke.createNode( + "Read", + "name {}".format(read_name)) + + # to evoid multiple undo steps for rest of process + # we will switch off undo-ing with viewer_update_and_undo_stop(): - # TODO: it might be universal read to img/geo/camera - r = nuke.createNode( - "Read", - "name {}".format(read_name)) r["file"].setValue(file) # Set colorspace defined in version data From cac6a2245366c96224251e5126890375dd410e24 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 29 Apr 2021 14:52:23 +0200 Subject: [PATCH 179/180] Nuke: allow undoing Read node --- openpype/hosts/nuke/plugins/load/load_mov.py | 12 +++++++----- openpype/hosts/nuke/plugins/load/load_sequence.py | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/nuke/plugins/load/load_mov.py b/openpype/hosts/nuke/plugins/load/load_mov.py index 92726913af..8b8c5d0c10 100644 --- a/openpype/hosts/nuke/plugins/load/load_mov.py +++ b/openpype/hosts/nuke/plugins/load/load_mov.py @@ -135,12 +135,14 @@ class LoadMov(api.Loader): read_name = self.node_name_template.format(**name_data) - # Create the Loader with the filename path set + read_node = nuke.createNode( + "Read", + "name {}".format(read_name) + ) + + # to avoid multiple undo steps for rest of process + # we will switch off undo-ing with viewer_update_and_undo_stop(): - read_node = nuke.createNode( - "Read", - "name {}".format(read_name) - ) read_node["file"].setValue(file) read_node["origfirst"].setValue(first) diff --git a/openpype/hosts/nuke/plugins/load/load_sequence.py b/openpype/hosts/nuke/plugins/load/load_sequence.py index 50c13563b1..71f0b8c298 100644 --- a/openpype/hosts/nuke/plugins/load/load_sequence.py +++ b/openpype/hosts/nuke/plugins/load/load_sequence.py @@ -145,7 +145,7 @@ class LoadSequence(api.Loader): "Read", "name {}".format(read_name)) - # to evoid multiple undo steps for rest of process + # to avoid multiple undo steps for rest of process # we will switch off undo-ing with viewer_update_and_undo_stop(): r["file"].setValue(file) From 1ceed8169fa90570e660a3be5aaa93cbe0570486 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 29 Apr 2021 19:19:10 +0200 Subject: [PATCH 180/180] SyncServer - handle if globally enabled, but no projects --- .../modules/sync_server/sync_server_module.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index ca4033b385..a434af9fea 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -409,6 +409,16 @@ class SyncServerModule(PypeModule, ITrayModule): """ self.sync_server_thread.reset_timer() + def get_enabled_projects(self): + """Returns list of projects which have SyncServer enabled.""" + enabled_projects = [] + for project in self.connection.projects(): + project_name = project["name"] + project_settings = self.get_sync_project_setting(project_name) + if project_settings: + enabled_projects.append(project_name) + + return enabled_projects """ End of Public API """ def get_local_file_path(self, collection, site_name, file_path): @@ -421,7 +431,7 @@ class SyncServerModule(PypeModule, ITrayModule): return local_file_path def _get_remote_sites_from_settings(self, sync_settings): - if not self.enabled or not sync_settings['enabled']: + if not self.enabled or not sync_settings.get('enabled'): return [] remote_sites = [self.DEFAULT_SITE, self.LOCAL_SITE] @@ -432,7 +442,7 @@ class SyncServerModule(PypeModule, ITrayModule): def _get_enabled_sites_from_settings(self, sync_settings): sites = [self.DEFAULT_SITE] - if self.enabled and sync_settings['enabled']: + if self.enabled and sync_settings.get('enabled'): sites.append(self.LOCAL_SITE) return sites @@ -453,6 +463,11 @@ class SyncServerModule(PypeModule, ITrayModule): if not self.enabled: return + enabled_projects = self.get_enabled_projects() + if not enabled_projects: + self.enabled = False + return + self.lock = threading.Lock() try: