From ae62e79695e07c1a24865fbec32e460b9c0540ce Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 30 Mar 2021 17:56:52 +0200 Subject: [PATCH 01/68] Global: empty tasks should return empty dict --- pype/plugins/publish/collect_hierarchy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/plugins/publish/collect_hierarchy.py b/pype/plugins/publish/collect_hierarchy.py index 5c5dbf018c..27332976e9 100644 --- a/pype/plugins/publish/collect_hierarchy.py +++ b/pype/plugins/publish/collect_hierarchy.py @@ -50,7 +50,7 @@ class CollectHierarchy(pyblish.api.ContextPlugin): # suppose that all instances are Shots shot_data['entity_type'] = 'Shot' - shot_data['tasks'] = instance.data.get("tasks") or [] + shot_data['tasks'] = instance.data.get("tasks") or {} shot_data["comments"] = instance.data.get("comments", []) shot_data['custom_attributes'] = { From ee6d1db703931faaf987fda61c7388df6d3e7a29 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 30 Mar 2021 18:52:41 +0200 Subject: [PATCH 02/68] Resolve: fixing import path to new `api` format --- pype/hosts/resolve/api/plugin.py | 2 +- pype/hosts/resolve/plugins/create/create_shot_clip.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/hosts/resolve/api/plugin.py b/pype/hosts/resolve/api/plugin.py index 0423f15c2a..974f29cb33 100644 --- a/pype/hosts/resolve/api/plugin.py +++ b/pype/hosts/resolve/api/plugin.py @@ -81,7 +81,7 @@ class CreatorWidget(QtWidgets.QDialog): ok_btn.clicked.connect(self._on_ok_clicked) cancel_btn.clicked.connect(self._on_cancel_clicked) - stylesheet = resolve.menu.load_stylesheet() + stylesheet = resolve.api.menu.load_stylesheet() self.setStyleSheet(stylesheet) def _on_ok_clicked(self): diff --git a/pype/hosts/resolve/plugins/create/create_shot_clip.py b/pype/hosts/resolve/plugins/create/create_shot_clip.py index 09b2b73775..bf860194d3 100644 --- a/pype/hosts/resolve/plugins/create/create_shot_clip.py +++ b/pype/hosts/resolve/plugins/create/create_shot_clip.py @@ -1,6 +1,6 @@ # from pprint import pformat from pype.hosts import resolve -from pype.hosts.resolve import lib +from pype.hosts.resolve.api import lib class CreateShotClip(resolve.Creator): From b24bbe5a5760e055716fbf6f88dfaf8870249ba0 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 30 Mar 2021 18:53:59 +0200 Subject: [PATCH 03/68] Resolve and Global: rename `master layer` to `hero track` --- pype/hosts/resolve/api/plugin.py | 30 +++++++++---------- .../plugins/create/create_shot_clip.py | 2 +- .../plugins/publish/collect_instances.py | 4 +-- pype/plugins/publish/collect_hierarchy.py | 2 +- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/pype/hosts/resolve/api/plugin.py b/pype/hosts/resolve/api/plugin.py index 974f29cb33..ada7549b01 100644 --- a/pype/hosts/resolve/api/plugin.py +++ b/pype/hosts/resolve/api/plugin.py @@ -697,13 +697,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 @@ -717,7 +717,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): @@ -751,33 +751,33 @@ class PublishClip: hierarchy_formating_data ) - tag_hierarchy_data.update({"masterLayer": True}) - if master_layer and self.vertical_sync: - # tag_hierarchy_data.update({"masterLayer": True}) + tag_hierarchy_data.update({"heroTrack": True}) + if hero_track and self.vertical_sync: + # tag_hierarchy_data.update({"heroTrack": True}) 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/resolve/plugins/create/create_shot_clip.py b/pype/hosts/resolve/plugins/create/create_shot_clip.py index bf860194d3..575e9f85a9 100644 --- a/pype/hosts/resolve/plugins/create/create_shot_clip.py +++ b/pype/hosts/resolve/plugins/create/create_shot_clip.py @@ -117,7 +117,7 @@ class CreateShotClip(resolve.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 "order": 1} diff --git a/pype/hosts/resolve/plugins/publish/collect_instances.py b/pype/hosts/resolve/plugins/publish/collect_instances.py index b1eafd512e..e76da13f48 100644 --- a/pype/hosts/resolve/plugins/publish/collect_instances.py +++ b/pype/hosts/resolve/plugins/publish/collect_instances.py @@ -102,10 +102,10 @@ class CollectInstances(pyblish.api.ContextPlugin): }) def create_shot_instance(self, context, timeline_item, **data): - master_layer = data.get("masterLayer") + hero_track = data.get("heroTrack") hierarchy_data = data.get("hierarchyData") - if not master_layer: + if not hero_track: return if not hierarchy_data: diff --git a/pype/plugins/publish/collect_hierarchy.py b/pype/plugins/publish/collect_hierarchy.py index 27332976e9..390ce443b6 100644 --- a/pype/plugins/publish/collect_hierarchy.py +++ b/pype/plugins/publish/collect_hierarchy.py @@ -40,7 +40,7 @@ class CollectHierarchy(pyblish.api.ContextPlugin): continue # exclude if not masterLayer True - if not instance.data.get("masterLayer"): + if not instance.data.get("heroTrack"): continue # get asset build data if any available From ceec8340cf7def24e4305d19d2d1ee66fb1ca238 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 30 Mar 2021 18:54:25 +0200 Subject: [PATCH 04/68] Resolve: fixing renamed function name --- pype/hosts/resolve/plugins/create/create_shot_clip.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/hosts/resolve/plugins/create/create_shot_clip.py b/pype/hosts/resolve/plugins/create/create_shot_clip.py index 575e9f85a9..86851c7074 100644 --- a/pype/hosts/resolve/plugins/create/create_shot_clip.py +++ b/pype/hosts/resolve/plugins/create/create_shot_clip.py @@ -244,7 +244,7 @@ class CreateShotClip(resolve.Creator): sq_markers = self.timeline.GetMarkers() # create media bin for compound clips (trackItems) - mp_folder = resolve.create_current_sequence_media_bin(self.timeline) + mp_folder = resolve.create_bin(self.timeline.GetName()) kwargs = { "ui_inputs": widget.result, From d179e3a246c0fa99f99840b588f1428b326d775f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 30 Mar 2021 18:54:49 +0200 Subject: [PATCH 05/68] Resolve: fixing to new function usage --- pype/hosts/resolve/utility_scripts/OTIO_export.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pype/hosts/resolve/utility_scripts/OTIO_export.py b/pype/hosts/resolve/utility_scripts/OTIO_export.py index a1142f56dd..daa51ea7ac 100644 --- a/pype/hosts/resolve/utility_scripts/OTIO_export.py +++ b/pype/hosts/resolve/utility_scripts/OTIO_export.py @@ -58,9 +58,8 @@ def _close_window(event): def _export_button(event): pm = resolve.GetProjectManager() project = pm.GetCurrentProject() - fps = project.GetSetting("timelineFrameRate") timeline = project.GetCurrentTimeline() - otio_timeline = otio_export.create_otio_timeline(timeline, fps) + otio_timeline = otio_export.create_otio_timeline(project) otio_path = os.path.join( itm["exportfilebttn"].Text, timeline.GetName() + ".otio") From 7354be225584c9f319bd76a65d3287509f1edced Mon Sep 17 00:00:00 2001 From: jezscha Date: Wed, 31 Mar 2021 08:41:12 +0000 Subject: [PATCH 06/68] Create draft PR for #916 From b315df6bbfe09978d11083163008d8d5218f5523 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 31 Mar 2021 11:19:50 +0200 Subject: [PATCH 07/68] Resolve: adding subset manager to menu --- pype/hosts/resolve/api/menu.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pype/hosts/resolve/api/menu.py b/pype/hosts/resolve/api/menu.py index 73ea937513..0b049e4433 100644 --- a/pype/hosts/resolve/api/menu.py +++ b/pype/hosts/resolve/api/menu.py @@ -12,7 +12,8 @@ from avalon.tools import ( creator, loader, sceneinventory, - libraryloader + libraryloader, + subsetmanager ) @@ -64,6 +65,7 @@ class PypeMenu(QtWidgets.QWidget): publish_btn = QtWidgets.QPushButton("Publish", self) load_btn = QtWidgets.QPushButton("Load", self) inventory_btn = QtWidgets.QPushButton("Inventory", self) + subsetm_btn = QtWidgets.QPushButton("Subset Manager", self) libload_btn = QtWidgets.QPushButton("Library", self) rename_btn = QtWidgets.QPushButton("Rename", self) set_colorspace_btn = QtWidgets.QPushButton( @@ -81,6 +83,7 @@ class PypeMenu(QtWidgets.QWidget): layout.addWidget(publish_btn) layout.addWidget(load_btn) layout.addWidget(inventory_btn) + layout.addWidget(subsetm_btn) layout.addWidget(Spacer(15, self)) @@ -102,6 +105,7 @@ class PypeMenu(QtWidgets.QWidget): publish_btn.clicked.connect(self.on_publish_clicked) load_btn.clicked.connect(self.on_load_clicked) inventory_btn.clicked.connect(self.on_inventory_clicked) + subsetm_btn.clicked.connect(self.on_subsetm_clicked) libload_btn.clicked.connect(self.on_libload_clicked) rename_btn.clicked.connect(self.on_rename_clicked) set_colorspace_btn.clicked.connect(self.on_set_colorspace_clicked) @@ -127,6 +131,10 @@ class PypeMenu(QtWidgets.QWidget): print("Clicked Inventory") sceneinventory.show() + def on_subsetm_clicked(self): + print("Clicked Subset Manager") + subsetmanager.show() + def on_libload_clicked(self): print("Clicked Library") libraryloader.show() From 310c9b2b6863f35952e6bc2d9e910141cdbcf1c1 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 31 Mar 2021 11:43:16 +0200 Subject: [PATCH 08/68] Resolve: listing instances --- pype/hosts/resolve/__init__.py | 6 +++++- pype/hosts/resolve/api/pipeline.py | 23 +++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/pype/hosts/resolve/__init__.py b/pype/hosts/resolve/__init__.py index 734e0bc5df..3e49ce3b9b 100644 --- a/pype/hosts/resolve/__init__.py +++ b/pype/hosts/resolve/__init__.py @@ -11,7 +11,9 @@ from .api.pipeline import ( update_container, publish, launch_workfiles_app, - maintained_selection + maintained_selection, + remove_instance, + list_instances ) from .api.lib import ( @@ -73,6 +75,8 @@ __all__ = [ "publish", "launch_workfiles_app", "maintained_selection", + "remove_instance", + "list_instances", # utils "setup", diff --git a/pype/hosts/resolve/api/pipeline.py b/pype/hosts/resolve/api/pipeline.py index 2d08203650..1ee9743518 100644 --- a/pype/hosts/resolve/api/pipeline.py +++ b/pype/hosts/resolve/api/pipeline.py @@ -258,3 +258,26 @@ def on_pyblish_instance_toggled(instance, old_value, new_value): # Whether instances should be passthrough based on new value timeline_item = instance.data["item"] set_publish_attribute(timeline_item, new_value) + + +def remove_instance(instance): + """Remove instance marker from track item.""" + pass + + +def list_instances(): + """List all created instances from current workfile.""" + listed_instances = [] + selected_timeline_items = lib.get_current_timeline_items( + filter=True, selecting_color=lib.publish_clip_color) + + for timeline_item_data in selected_timeline_items: + timeline_item = timeline_item_data["clip"]["item"] + + # get pype tag data + tag_data = lib.get_timeline_item_pype_tag(timeline_item) + + if tag_data: + listed_instances.append(tag_data) + + return listed_instances From b1eb67794cabb0dfad280139f6fdec3cabb4dcf9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 31 Mar 2021 11:43:44 +0200 Subject: [PATCH 09/68] Resolve: renaming plugins to precollect --- .../{collect_instances.py => precollect_instances.py} | 4 ++-- .../{collect_workfile.py => precollect_workfile.py} | 8 +++----- 2 files changed, 5 insertions(+), 7 deletions(-) rename pype/hosts/resolve/plugins/publish/{collect_instances.py => precollect_instances.py} (97%) rename pype/hosts/resolve/plugins/publish/{collect_workfile.py => precollect_workfile.py} (88%) diff --git a/pype/hosts/resolve/plugins/publish/collect_instances.py b/pype/hosts/resolve/plugins/publish/precollect_instances.py similarity index 97% rename from pype/hosts/resolve/plugins/publish/collect_instances.py rename to pype/hosts/resolve/plugins/publish/precollect_instances.py index b1eafd512e..e54bda0a49 100644 --- a/pype/hosts/resolve/plugins/publish/collect_instances.py +++ b/pype/hosts/resolve/plugins/publish/precollect_instances.py @@ -5,11 +5,11 @@ from pype.hosts import resolve from pprint import pformat -class CollectInstances(pyblish.api.ContextPlugin): +class PrecollectInstances(pyblish.api.ContextPlugin): """Collect all Track items selection.""" order = pyblish.api.CollectorOrder - 0.59 - label = "Collect Instances" + label = "Precollect Instances" hosts = ["resolve"] def process(self, context): diff --git a/pype/hosts/resolve/plugins/publish/collect_workfile.py b/pype/hosts/resolve/plugins/publish/precollect_workfile.py similarity index 88% rename from pype/hosts/resolve/plugins/publish/collect_workfile.py rename to pype/hosts/resolve/plugins/publish/precollect_workfile.py index f7f90c9689..3e9a7f26b9 100644 --- a/pype/hosts/resolve/plugins/publish/collect_workfile.py +++ b/pype/hosts/resolve/plugins/publish/precollect_workfile.py @@ -9,10 +9,10 @@ from pype.hosts.resolve.otio import davinci_export reload(davinci_export) -class CollectWorkfile(pyblish.api.ContextPlugin): - """Inject the current working file into context""" +class PrecollectWorkfile(pyblish.api.ContextPlugin): + """Precollect the current working file into context""" - label = "Collect Workfile" + label = "Precollect Workfile" order = pyblish.api.CollectorOrder - 0.6 def process(self, context): @@ -21,8 +21,6 @@ class CollectWorkfile(pyblish.api.ContextPlugin): subset = "workfile" project = resolve.get_current_project() fps = project.GetSetting("timelineFrameRate") - - active_timeline = resolve.get_current_timeline() video_tracks = resolve.get_video_track_names() # adding otio timeline to context From cd2372300a7e979066a1ab2a0fd3a5e5cf143c1b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 31 Mar 2021 11:52:05 +0200 Subject: [PATCH 10/68] Resolve: improving subset manager labels --- pype/hosts/resolve/api/pipeline.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pype/hosts/resolve/api/pipeline.py b/pype/hosts/resolve/api/pipeline.py index 1ee9743518..d1d1b5185a 100644 --- a/pype/hosts/resolve/api/pipeline.py +++ b/pype/hosts/resolve/api/pipeline.py @@ -273,11 +273,15 @@ def list_instances(): for timeline_item_data in selected_timeline_items: timeline_item = timeline_item_data["clip"]["item"] + ti_name = timeline_item.GetName().split(".")[0] # get pype tag data tag_data = lib.get_timeline_item_pype_tag(timeline_item) if tag_data: + asset = tag_data.get("asset") + subset = tag_data.get("subset") + tag_data["label"] = f"{ti_name} [{asset}-{subset}]" listed_instances.append(tag_data) return listed_instances From 00e3b75feb27d5f94a0a1c7daefd140f8334725a Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 31 Mar 2021 12:35:42 +0200 Subject: [PATCH 11/68] Auto stash before merge of "feature/916-resolve-instance-manager" and "3.0/bugfix/resolve-functionality-issues" --- pype/hosts/resolve/api/plugin.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pype/hosts/resolve/api/plugin.py b/pype/hosts/resolve/api/plugin.py index ada7549b01..4ed8c5bc4a 100644 --- a/pype/hosts/resolve/api/plugin.py +++ b/pype/hosts/resolve/api/plugin.py @@ -777,9 +777,11 @@ class PublishClip: # add data to return data dict self.tag_data.update(tag_hierarchy_data) + # add review track only to hero track 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 From a1b1c43d4faf32d23bb219d7ddf166c75ee35b38 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 31 Mar 2021 12:47:42 +0200 Subject: [PATCH 12/68] Resolve: adding removing instance action --- pype/hosts/resolve/api/pipeline.py | 23 ++++++++++++++++++++++- pype/hosts/resolve/api/plugin.py | 4 ++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/pype/hosts/resolve/api/pipeline.py b/pype/hosts/resolve/api/pipeline.py index d1d1b5185a..9a74e50e6f 100644 --- a/pype/hosts/resolve/api/pipeline.py +++ b/pype/hosts/resolve/api/pipeline.py @@ -262,7 +262,28 @@ def on_pyblish_instance_toggled(instance, old_value, new_value): def remove_instance(instance): """Remove instance marker from track item.""" - pass + instance_id = instance.get("uuid") + + selected_timeline_items = lib.get_current_timeline_items( + filter=True, selecting_color=lib.publish_clip_color) + + found_ti = None + for timeline_item_data in selected_timeline_items: + timeline_item = timeline_item_data["clip"]["item"] + + # get pype tag data + tag_data = lib.get_timeline_item_pype_tag(timeline_item) + _ti_id = tag_data.get("uuid") + if _ti_id == instance_id: + found_ti = timeline_item + break + + if found_ti is None: + return + + # removing instance by marker color + print(f"Removing instance: {found_ti.GetName()}") + found_ti.DeleteMarkersByColor(lib.pype_marker_color) def list_instances(): diff --git a/pype/hosts/resolve/api/plugin.py b/pype/hosts/resolve/api/plugin.py index 4ed8c5bc4a..0ba6c5e745 100644 --- a/pype/hosts/resolve/api/plugin.py +++ b/pype/hosts/resolve/api/plugin.py @@ -1,4 +1,5 @@ import re +import uuid from avalon import api import pype.api as pype from pype.hosts import resolve @@ -777,6 +778,9 @@ class PublishClip: # add data to return data dict self.tag_data.update(tag_hierarchy_data) + # add uuid to tag data + self.tag_data["uuid"] = str(uuid.uuid4()) + # add review track only to hero track if hero_track and self.review_layer: self.tag_data.update({"reviewTrack": self.review_layer}) From 9228508dff48bc15a670f5b24a26302851132b92 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 6 Apr 2021 15:11:00 +0200 Subject: [PATCH 13/68] Resolve: merge openpype fixes --- openpype/hosts/resolve/api/menu.py | 52 ++++--- openpype/hosts/resolve/api/pipeline.py | 48 +++++++ openpype/hosts/resolve/api/plugin.py | 35 +++-- .../plugins/create/create_shot_clip.py | 2 +- .../plugins/publish/collect_instances.py | 129 ------------------ .../plugins/publish/collect_workfile.py | 54 -------- .../resolve/utility_scripts/OTIO_export.py | 3 +- 7 files changed, 100 insertions(+), 223 deletions(-) delete mode 100644 openpype/hosts/resolve/plugins/publish/collect_instances.py delete mode 100644 openpype/hosts/resolve/plugins/publish/collect_workfile.py diff --git a/openpype/hosts/resolve/api/menu.py b/openpype/hosts/resolve/api/menu.py index 5ed7aeab34..ecd2708440 100644 --- a/openpype/hosts/resolve/api/menu.py +++ b/openpype/hosts/resolve/api/menu.py @@ -12,7 +12,8 @@ from avalon.tools import ( creator, loader, sceneinventory, - libraryloader + libraryloader, + subsetmanager ) @@ -59,19 +60,20 @@ class OpenPypeMenu(QtWidgets.QWidget): ) self.setWindowTitle("OpenPype") - workfiles_btn = QtWidgets.QPushButton("Workfiles ...", self) - create_btn = QtWidgets.QPushButton("Create ...", self) - publish_btn = QtWidgets.QPushButton("Publish ...", self) - load_btn = QtWidgets.QPushButton("Load ...", self) - inventory_btn = QtWidgets.QPushButton("Inventory ...", self) - libload_btn = QtWidgets.QPushButton("Library ...", self) - # rename_btn = QtWidgets.QPushButton("Rename ...", self) - # set_colorspace_btn = QtWidgets.QPushButton( - # "Set colorspace from presets", self - # ) - # reset_resolution_btn = QtWidgets.QPushButton( - # "Reset Resolution from peresets", self - # ) + workfiles_btn = QtWidgets.QPushButton("Workfiles", self) + create_btn = QtWidgets.QPushButton("Create", self) + publish_btn = QtWidgets.QPushButton("Publish", self) + load_btn = QtWidgets.QPushButton("Load", self) + inventory_btn = QtWidgets.QPushButton("Inventory", self) + subsetm_btn = QtWidgets.QPushButton("Subset Manager", self) + libload_btn = QtWidgets.QPushButton("Library", self) + rename_btn = QtWidgets.QPushButton("Rename", self) + set_colorspace_btn = QtWidgets.QPushButton( + "Set colorspace from presets", self + ) + reset_resolution_btn = QtWidgets.QPushButton( + "Reset Resolution from peresets", self + ) layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(10, 20, 10, 20) @@ -81,19 +83,20 @@ class OpenPypeMenu(QtWidgets.QWidget): layout.addWidget(publish_btn) layout.addWidget(load_btn) layout.addWidget(inventory_btn) + layout.addWidget(subsetm_btn) layout.addWidget(Spacer(15, self)) layout.addWidget(libload_btn) - # layout.addWidget(Spacer(15, self)) + layout.addWidget(Spacer(15, self)) - # layout.addWidget(rename_btn) + layout.addWidget(rename_btn) - # layout.addWidget(Spacer(15, self)) + layout.addWidget(Spacer(15, self)) - # layout.addWidget(set_colorspace_btn) - # layout.addWidget(reset_resolution_btn) + layout.addWidget(set_colorspace_btn) + layout.addWidget(reset_resolution_btn) self.setLayout(layout) @@ -102,10 +105,11 @@ class OpenPypeMenu(QtWidgets.QWidget): publish_btn.clicked.connect(self.on_publish_clicked) load_btn.clicked.connect(self.on_load_clicked) inventory_btn.clicked.connect(self.on_inventory_clicked) + subsetm_btn.clicked.connect(self.on_subsetm_clicked) libload_btn.clicked.connect(self.on_libload_clicked) - # rename_btn.clicked.connect(self.on_rename_clicked) - # set_colorspace_btn.clicked.connect(self.on_set_colorspace_clicked) - # reset_resolution_btn.clicked.connect(self.on_reset_resolution_clicked) + rename_btn.clicked.connect(self.on_rename_clicked) + set_colorspace_btn.clicked.connect(self.on_set_colorspace_clicked) + reset_resolution_btn.clicked.connect(self.on_reset_resolution_clicked) def on_workfile_clicked(self): print("Clicked Workfile") @@ -127,6 +131,10 @@ class OpenPypeMenu(QtWidgets.QWidget): print("Clicked Inventory") sceneinventory.show() + def on_subsetm_clicked(self): + print("Clicked Subset Manager") + subsetmanager.show() + def on_libload_clicked(self): print("Clicked Library") libraryloader.show() diff --git a/openpype/hosts/resolve/api/pipeline.py b/openpype/hosts/resolve/api/pipeline.py index d4d928a7d9..0e6d5aff4d 100644 --- a/openpype/hosts/resolve/api/pipeline.py +++ b/openpype/hosts/resolve/api/pipeline.py @@ -258,3 +258,51 @@ def on_pyblish_instance_toggled(instance, old_value, new_value): # Whether instances should be passthrough based on new value timeline_item = instance.data["item"] set_publish_attribute(timeline_item, new_value) + + +def remove_instance(instance): + """Remove instance marker from track item.""" + instance_id = instance.get("uuid") + + selected_timeline_items = lib.get_current_timeline_items( + filter=True, selecting_color=lib.publish_clip_color) + + found_ti = None + for timeline_item_data in selected_timeline_items: + timeline_item = timeline_item_data["clip"]["item"] + + # get openpype tag data + tag_data = lib.get_timeline_item_pype_tag(timeline_item) + _ti_id = tag_data.get("uuid") + if _ti_id == instance_id: + found_ti = timeline_item + break + + if found_ti is None: + return + + # removing instance by marker color + print(f"Removing instance: {found_ti.GetName()}") + found_ti.DeleteMarkersByColor(lib.pype_marker_color) + + +def list_instances(): + """List all created instances from current workfile.""" + listed_instances = [] + selected_timeline_items = lib.get_current_timeline_items( + filter=True, selecting_color=lib.publish_clip_color) + + for timeline_item_data in selected_timeline_items: + timeline_item = timeline_item_data["clip"]["item"] + ti_name = timeline_item.GetName().split(".")[0] + + # get openpype tag data + tag_data = lib.get_timeline_item_pype_tag(timeline_item) + + if tag_data: + asset = tag_data.get("asset") + subset = tag_data.get("subset") + tag_data["label"] = f"{ti_name} [{asset}-{subset}]" + listed_instances.append(tag_data) + + return listed_instances diff --git a/openpype/hosts/resolve/api/plugin.py b/openpype/hosts/resolve/api/plugin.py index 3833795b96..4712d0a8b9 100644 --- a/openpype/hosts/resolve/api/plugin.py +++ b/openpype/hosts/resolve/api/plugin.py @@ -1,4 +1,5 @@ import re +import uuid from avalon import api import openpype.api as pype from openpype.hosts import resolve @@ -697,13 +698,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 @@ -717,7 +718,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): @@ -751,35 +752,39 @@ class PublishClip: hierarchy_formating_data ) - tag_hierarchy_data.update({"masterLayer": True}) - if master_layer and self.vertical_sync: - # tag_hierarchy_data.update({"masterLayer": True}) + 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: + # add uuid to tag data + self.tag_data["uuid"] = str(uuid.uuid4()) + + # add review track only to hero track + 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/resolve/plugins/create/create_shot_clip.py b/openpype/hosts/resolve/plugins/create/create_shot_clip.py index 2916a52298..41fdbf5c61 100644 --- a/openpype/hosts/resolve/plugins/create/create_shot_clip.py +++ b/openpype/hosts/resolve/plugins/create/create_shot_clip.py @@ -117,7 +117,7 @@ class CreateShotClip(resolve.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 "order": 1} diff --git a/openpype/hosts/resolve/plugins/publish/collect_instances.py b/openpype/hosts/resolve/plugins/publish/collect_instances.py deleted file mode 100644 index f4eeb39754..0000000000 --- a/openpype/hosts/resolve/plugins/publish/collect_instances.py +++ /dev/null @@ -1,129 +0,0 @@ -import pyblish -from openpype.hosts import resolve - -# # developer reload modules -from pprint import pformat - - -class CollectInstances(pyblish.api.ContextPlugin): - """Collect all Track items selection.""" - - order = pyblish.api.CollectorOrder - 0.59 - label = "Collect Instances" - hosts = ["resolve"] - - def process(self, context): - otio_timeline = context.data["otioTimeline"] - selected_timeline_items = resolve.get_current_timeline_items( - filter=True, selecting_color=resolve.publish_clip_color) - - self.log.info( - "Processing enabled track items: {}".format( - len(selected_timeline_items))) - - for timeline_item_data in selected_timeline_items: - - data = dict() - timeline_item = timeline_item_data["clip"]["item"] - - # get openpype tag data - tag_data = resolve.get_timeline_item_pype_tag(timeline_item) - self.log.debug(f"__ tag_data: {pformat(tag_data)}") - - if not tag_data: - continue - - if tag_data.get("id") != "pyblish.avalon.instance": - continue - - media_pool_item = timeline_item.GetMediaPoolItem() - clip_property = media_pool_item.GetClipProperty() - self.log.debug(f"clip_property: {clip_property}") - - # add tag data to instance data - data.update({ - k: v for k, v in tag_data.items() - if k not in ("id", "applieswhole", "label") - }) - - asset = tag_data["asset"] - subset = tag_data["subset"] - - # insert family into families - family = tag_data["family"] - families = [str(f) for f in tag_data["families"]] - families.insert(0, str(family)) - - data.update({ - "name": "{} {} {}".format(asset, subset, families), - "asset": asset, - "item": timeline_item, - "families": families, - "publish": resolve.get_publish_attribute(timeline_item), - "fps": context.data["fps"] - }) - - # otio clip data - otio_data = resolve.get_otio_clip_instance_data( - otio_timeline, timeline_item_data) or {} - data.update(otio_data) - - # add resolution - self.get_resolution_to_data(data, context) - - # create instance - instance = context.create_instance(**data) - - # create shot instance for shot attributes create/update - self.create_shot_instance(context, timeline_item, **data) - - self.log.info("Creating instance: {}".format(instance)) - self.log.debug( - "_ instance.data: {}".format(pformat(instance.data))) - - def get_resolution_to_data(self, data, context): - assert data.get("otioClip"), "Missing `otioClip` data" - - # solve source resolution option - if data.get("sourceResolution", None): - otio_clip_metadata = data[ - "otioClip"].media_reference.metadata - data.update({ - "resolutionWidth": otio_clip_metadata["width"], - "resolutionHeight": otio_clip_metadata["height"], - "pixelAspect": otio_clip_metadata["pixelAspect"] - }) - else: - otio_tl_metadata = context.data["otioTimeline"].metadata - data.update({ - "resolutionWidth": otio_tl_metadata["width"], - "resolutionHeight": otio_tl_metadata["height"], - "pixelAspect": otio_tl_metadata["pixelAspect"] - }) - - def create_shot_instance(self, context, timeline_item, **data): - master_layer = data.get("masterLayer") - hierarchy_data = data.get("hierarchyData") - - if not master_layer: - return - - if not hierarchy_data: - return - - 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": [], - "publish": resolve.get_publish_attribute(timeline_item) - }) - - context.create_instance(**data) diff --git a/openpype/hosts/resolve/plugins/publish/collect_workfile.py b/openpype/hosts/resolve/plugins/publish/collect_workfile.py deleted file mode 100644 index a66284ed02..0000000000 --- a/openpype/hosts/resolve/plugins/publish/collect_workfile.py +++ /dev/null @@ -1,54 +0,0 @@ -import pyblish.api -from openpype.hosts import resolve -from avalon import api as avalon -from pprint import pformat - -# dev -from importlib import reload -from openpype.hosts.resolve.otio import davinci_export -reload(davinci_export) - - -class CollectWorkfile(pyblish.api.ContextPlugin): - """Inject the current working file into context""" - - label = "Collect Workfile" - order = pyblish.api.CollectorOrder - 0.6 - - def process(self, context): - - asset = avalon.Session["AVALON_ASSET"] - subset = "workfile" - project = resolve.get_current_project() - fps = project.GetSetting("timelineFrameRate") - - active_timeline = resolve.get_current_timeline() - video_tracks = resolve.get_video_track_names() - - # adding otio timeline to context - otio_timeline = davinci_export.create_otio_timeline(project) - - instance_data = { - "name": "{}_{}".format(asset, subset), - "asset": asset, - "subset": "{}{}".format(asset, subset.capitalize()), - "item": project, - "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, - "videoTracks": video_tracks, - "currentFile": project.GetName(), - "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))) diff --git a/openpype/hosts/resolve/utility_scripts/OTIO_export.py b/openpype/hosts/resolve/utility_scripts/OTIO_export.py index 91bc2c5700..0431eb7daa 100644 --- a/openpype/hosts/resolve/utility_scripts/OTIO_export.py +++ b/openpype/hosts/resolve/utility_scripts/OTIO_export.py @@ -58,9 +58,8 @@ def _close_window(event): def _export_button(event): pm = resolve.GetProjectManager() project = pm.GetCurrentProject() - fps = project.GetSetting("timelineFrameRate") timeline = project.GetCurrentTimeline() - otio_timeline = otio_export.create_otio_timeline(timeline, fps) + otio_timeline = otio_export.create_otio_timeline(project) otio_path = os.path.join( itm["exportfilebttn"].Text, timeline.GetName() + ".otio") From e46c3a9fa7730e71e679138e4e7861ae3ec14afa Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 6 Apr 2021 15:13:03 +0200 Subject: [PATCH 14/68] Resolve: updating menu to disable unwritten features --- openpype/hosts/resolve/api/menu.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/openpype/hosts/resolve/api/menu.py b/openpype/hosts/resolve/api/menu.py index ecd2708440..c0471ebfbe 100644 --- a/openpype/hosts/resolve/api/menu.py +++ b/openpype/hosts/resolve/api/menu.py @@ -67,13 +67,13 @@ class OpenPypeMenu(QtWidgets.QWidget): inventory_btn = QtWidgets.QPushButton("Inventory", self) subsetm_btn = QtWidgets.QPushButton("Subset Manager", self) libload_btn = QtWidgets.QPushButton("Library", self) - rename_btn = QtWidgets.QPushButton("Rename", self) - set_colorspace_btn = QtWidgets.QPushButton( - "Set colorspace from presets", self - ) - reset_resolution_btn = QtWidgets.QPushButton( - "Reset Resolution from peresets", self - ) + # rename_btn = QtWidgets.QPushButton("Rename", self) + # set_colorspace_btn = QtWidgets.QPushButton( + # "Set colorspace from presets", self + # ) + # reset_resolution_btn = QtWidgets.QPushButton( + # "Reset Resolution from peresets", self + # ) layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(10, 20, 10, 20) @@ -91,12 +91,12 @@ class OpenPypeMenu(QtWidgets.QWidget): layout.addWidget(Spacer(15, self)) - layout.addWidget(rename_btn) + # layout.addWidget(rename_btn) - layout.addWidget(Spacer(15, self)) + # layout.addWidget(Spacer(15, self)) - layout.addWidget(set_colorspace_btn) - layout.addWidget(reset_resolution_btn) + # layout.addWidget(set_colorspace_btn) + # layout.addWidget(reset_resolution_btn) self.setLayout(layout) @@ -107,9 +107,9 @@ class OpenPypeMenu(QtWidgets.QWidget): inventory_btn.clicked.connect(self.on_inventory_clicked) subsetm_btn.clicked.connect(self.on_subsetm_clicked) libload_btn.clicked.connect(self.on_libload_clicked) - rename_btn.clicked.connect(self.on_rename_clicked) - set_colorspace_btn.clicked.connect(self.on_set_colorspace_clicked) - reset_resolution_btn.clicked.connect(self.on_reset_resolution_clicked) + # rename_btn.clicked.connect(self.on_rename_clicked) + # set_colorspace_btn.clicked.connect(self.on_set_colorspace_clicked) + # reset_resolution_btn.clicked.connect(self.on_reset_resolution_clicked) def on_workfile_clicked(self): print("Clicked Workfile") From 9a0f20ef95e18767699a624a575c84e834fd1fa1 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 6 Apr 2021 15:15:02 +0200 Subject: [PATCH 15/68] Resolve: adding correct ux convention to menu --- openpype/hosts/resolve/api/menu.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/resolve/api/menu.py b/openpype/hosts/resolve/api/menu.py index c0471ebfbe..e7be3fc963 100644 --- a/openpype/hosts/resolve/api/menu.py +++ b/openpype/hosts/resolve/api/menu.py @@ -60,13 +60,13 @@ class OpenPypeMenu(QtWidgets.QWidget): ) self.setWindowTitle("OpenPype") - workfiles_btn = QtWidgets.QPushButton("Workfiles", self) - create_btn = QtWidgets.QPushButton("Create", self) - publish_btn = QtWidgets.QPushButton("Publish", self) - load_btn = QtWidgets.QPushButton("Load", self) - inventory_btn = QtWidgets.QPushButton("Inventory", self) - subsetm_btn = QtWidgets.QPushButton("Subset Manager", self) - libload_btn = QtWidgets.QPushButton("Library", self) + workfiles_btn = QtWidgets.QPushButton("Workfiles ...", self) + create_btn = QtWidgets.QPushButton("Create ...", self) + publish_btn = QtWidgets.QPushButton("Publish ...", self) + load_btn = QtWidgets.QPushButton("Load ...", self) + inventory_btn = QtWidgets.QPushButton("Inventory ...", self) + subsetm_btn = QtWidgets.QPushButton("Subset Manager ...", self) + libload_btn = QtWidgets.QPushButton("Library ...", self) # rename_btn = QtWidgets.QPushButton("Rename", self) # set_colorspace_btn = QtWidgets.QPushButton( # "Set colorspace from presets", self @@ -89,7 +89,7 @@ class OpenPypeMenu(QtWidgets.QWidget): layout.addWidget(libload_btn) - layout.addWidget(Spacer(15, self)) + # layout.addWidget(Spacer(15, self)) # layout.addWidget(rename_btn) From d503395818ec8113bb0c402535e1c44aac13d61a Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 7 Apr 2021 10:43:36 +0200 Subject: [PATCH 16/68] resolve: fix transition merge --- openpype/hosts/resolve/hooks/pre_resolve_setup.py | 2 +- .../hosts/resolve/plugins/publish/precollect_instances.py | 2 +- openpype/hosts/resolve/plugins/publish/precollect_workfile.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/resolve/hooks/pre_resolve_setup.py b/openpype/hosts/resolve/hooks/pre_resolve_setup.py index 0ee55d3790..bcb27e24fc 100644 --- a/openpype/hosts/resolve/hooks/pre_resolve_setup.py +++ b/openpype/hosts/resolve/hooks/pre_resolve_setup.py @@ -44,7 +44,7 @@ class ResolvePrelaunch(PreLaunchHook): self.launch_context.env["PRE_PYTHON_SCRIPT"] = pre_py_sc self.log.debug(f"-- pre_py_sc: `{pre_py_sc}`...") try: - __import__("pype.hosts.resolve") + __import__("openpype.hosts.resolve") __import__("pyblish") except ImportError: diff --git a/openpype/hosts/resolve/plugins/publish/precollect_instances.py b/openpype/hosts/resolve/plugins/publish/precollect_instances.py index 4fabe2b8a0..c38cbc4f73 100644 --- a/openpype/hosts/resolve/plugins/publish/precollect_instances.py +++ b/openpype/hosts/resolve/plugins/publish/precollect_instances.py @@ -1,5 +1,5 @@ import pyblish -from pype.hosts import resolve +from openpype.hosts import resolve # # developer reload modules from pprint import pformat diff --git a/openpype/hosts/resolve/plugins/publish/precollect_workfile.py b/openpype/hosts/resolve/plugins/publish/precollect_workfile.py index 3e9a7f26b9..ee05fb6f13 100644 --- a/openpype/hosts/resolve/plugins/publish/precollect_workfile.py +++ b/openpype/hosts/resolve/plugins/publish/precollect_workfile.py @@ -1,11 +1,11 @@ import pyblish.api -from pype.hosts import resolve +from openpype.hosts import resolve from avalon import api as avalon from pprint import pformat # dev from importlib import reload -from pype.hosts.resolve.otio import davinci_export +from openpype.hosts.resolve.otio import davinci_export reload(davinci_export) From 8dbc9d1e5732f6dc1a249ec52d9e575ff61388f4 Mon Sep 17 00:00:00 2001 From: Petr Dvorak Date: Wed, 7 Apr 2021 10:46:11 +0200 Subject: [PATCH 17/68] add ftrack documentation --- website/docs/artist_ftrack.md | 79 +++++++--- website/docs/assets/ftrack/ftrack-api.gif | Bin 0 -> 8153 bytes website/docs/assets/ftrack/ftrack-api.png | Bin 0 -> 9410 bytes website/docs/assets/ftrack/ftrack-api2.png | Bin 0 -> 18743 bytes website/docs/assets/ftrack/ftrack-api3.png | Bin 0 -> 9694 bytes website/docs/assets/ftrack/ftrack-api4.png | Bin 0 -> 13927 bytes .../assets/ftrack/ftrack-delivery-icon.png | Bin 0 -> 3772 bytes .../docs/assets/ftrack/ftrack-login-api.png | Bin 0 -> 5389 bytes website/docs/assets/ftrack/ftrack-login_1.png | Bin 6706 -> 5942 bytes .../docs/assets/ftrack/ftrack-login_50.png | Bin 7943 -> 9002 bytes .../docs/assets/ftrack/ftrack-login_81.png | Bin 61877 -> 5222 bytes website/docs/manager_ftrack.md | 2 +- website/docs/manager_ftrack_actions.md | 136 +++++++++-------- website/docs/module_ftrack.md | 143 ++++++++++-------- 14 files changed, 218 insertions(+), 142 deletions(-) create mode 100644 website/docs/assets/ftrack/ftrack-api.gif create mode 100644 website/docs/assets/ftrack/ftrack-api.png create mode 100644 website/docs/assets/ftrack/ftrack-api2.png create mode 100644 website/docs/assets/ftrack/ftrack-api3.png create mode 100644 website/docs/assets/ftrack/ftrack-api4.png create mode 100644 website/docs/assets/ftrack/ftrack-delivery-icon.png create mode 100644 website/docs/assets/ftrack/ftrack-login-api.png diff --git a/website/docs/artist_ftrack.md b/website/docs/artist_ftrack.md index e42136fa89..df2a7236b3 100644 --- a/website/docs/artist_ftrack.md +++ b/website/docs/artist_ftrack.md @@ -6,55 +6,94 @@ sidebar_label: Artist # How to use Ftrack in OpenPype -## Login to Ftrack module in OpenPype tray (best case scenario) -1. Launch OpenPype tray if not launched yet +## Login to Ftrack module in OpenPype (best case scenario) +1. Launch OpenPype and go to systray OpenPype icon. 2. *Ftrack login* window pop up on start - or press **login** in **Ftrack menu** to pop up *Ftrack login* window ![ftrack-login-2](assets/ftrack/ftrack-login_50.png) -3. Press `Ftrack` button -![Login widget](assets/ftrack/ftrack-login_1.png) -4. Web browser opens -5. Sign in Ftrack if you're requested + - Press `Ftrack` button + + ![Login widget](assets/ftrack/ftrack-login_1.png) + - Web browser opens + + - Sign in Ftrack if you're requested. If you are already sign up to Ftrack via web browser, you can jump to point 6. -![ftrack-login-2](assets/ftrack/ftrack-login_2.png) -6. Message is shown + ![ftrack-login-2](assets/ftrack/ftrack-login_2.png) + +3. Message is shown ![ftrack-login-3](assets/ftrack/ftrack-login_3.png) -7. Close message and you're ready to use actions - continue with [Application launch](#application-launch-best-case-scenario) + +4. Close message and you're ready to use actions - continue with [Application launch](#application-launch-best-case-scenario) + --- + ## Application launch (best case scenario) -1. Make sure OpenPype is running and you passed [Login to Ftrack](#login-to-ftrack-module-in-pype-tray-best-case-scenario) guide +1. Make sure OpenPype is running and you passed [Login to Ftrack](#login-to-ftrack-module-in-openpype-best-case-scenario) guide + 2. Open web browser and go to your studio Ftrack web page *(e.g. https://mystudio.ftrackapp.com/)* + 3. Locate the task on which you want to run the application + 4. Display actions for the task ![ftrack-login-3](assets/ftrack/ftrack-login_60.png) + 5. Select application you want to launch - - application versions may be grouped to one action in that case press the action to reveal versions to choose *(like Maya in the picture)* + - application versions may be grouped to one action in that case press the action to reveal versions to choose *(like Maya in the picture)*, only applications permitted to the particular project are appeared. ![ftrack-login-3](assets/ftrack/ftrack-login_71-small.png) + 6. Work --- + ## Change Ftrack user 1. Log out the previous user from Ftrack Web app *(skip if new is already logged)* -![ftrack-login-3](assets/ftrack/ftrack-login_80-small.png) + ![ftrack-login-3](assets/ftrack/ftrack-login_80-small.png) + 2. Log out the previous user from Ftrack module in tray -![ftrack-login-3](assets/ftrack/ftrack-login_81.png) -3. Follow [Login to Ftrack](#login-to-ftrack-module-in-pype-tray-best-case-scenario) guide + ![ftrack-login-3](assets/ftrack/ftrack-login_81.png) + +3. Follow [Login to Ftrack](#login-to-ftrack-module-in-openpype-best-case-scenario) guide + +--- + +## Where to find API key +- Your API key can be found in Ftrack. In the upper right corner of Ftrack click on the avatar, choose System settings. + + ![ftrack-api](assets/ftrack/ftrack-api.png) + +- Scroll down in left panel and select `API keys`. Then pick `Create` button. + + ![ftrack-api](assets/ftrack/ftrack-api2.png) + +- New window will pop up. Choose the `API role` and press `Save` + + ![ftrack-api](assets/ftrack/ftrack-api3.png) + +- Then your new API will be created. + + ![ftrack-api](assets/ftrack/ftrack-api4.png) + +- Copy them and put it into the Ftrack login window. + + ![ftrack-api](assets/ftrack/ftrack-login-api.png) + --- ## What if... ### Ftrack login window didn't pop up and Ftrack menu is not in tray -**1. possibility - OpenPype tray didn't load properly** +**1. possibility - OpenPype didn't load properly** - try to restart OpenPype **2. possibility - Ftrack is not set in OpenPype** - inform your administrator +- The Ftrack URL can be changed in OpenPype Settings → System → Modules → Ftrack ### Web browser did not open @@ -63,20 +102,22 @@ sidebar_label: Artist **2. possibility - Ftrack URL is not set or is not right** - Check **Ftrack URL** value in *Ftrack login* window -- Inform your administrator if URL is incorrect and launch tray again when administrator fix it +- Inform your administrator if URL is incorrect and launch OpenPype again when administrator fix it +- The Ftrack URL can be changed in OpenPype Settings → System → Modules → Ftrack **3. possibility - Ftrack Web app can't be reached the way OpenPype use it** - Enter your **Username** and [API key](#where-to-find-api-key) in *Ftrack login* window and press **Login** button + ![ftrack-api](assets/ftrack/ftrack-api.gif) ### Ftrack action menu is empty **1. possibility - OpenPype is not running** -- launch OpenPype +- launch OpenPype and check if it is running in systray **2. possibility - You didn't go through Login to Ftrack guide** -- please go through [Login to Ftrack](#login-to-ftrack-module-in-pype-tray-best-case-scenario) guide +- please go through [Login to Ftrack](#login-to-ftrack-module-in-openpype-best-case-scenario) guide **3. possibility - User logged to Ftrack Web is not the same as user logged to Ftrack module in tray** -- Follow [Change user](#change-user) guide +- Follow [Change user](#change-ftrack-user) guide **4. possibility - Project don't have set applications** - ask your Project Manager to check if he set applications for the project diff --git a/website/docs/assets/ftrack/ftrack-api.gif b/website/docs/assets/ftrack/ftrack-api.gif new file mode 100644 index 0000000000000000000000000000000000000000..b2caf6ca98da8ce36cf4cbe4dea255689c71b8a7 GIT binary patch literal 8153 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 literal 0 HcmV?d00001 diff --git a/website/docs/assets/ftrack/ftrack-api.png b/website/docs/assets/ftrack/ftrack-api.png new file mode 100644 index 0000000000000000000000000000000000000000..c69967d78e3b0c34b5efc46fa17a8ccf64994dc5 GIT binary patch literal 9410 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 JRLEI`{68}r0;>Q3 literal 0 HcmV?d00001 diff --git a/website/docs/assets/ftrack/ftrack-api2.png b/website/docs/assets/ftrack/ftrack-api2.png new file mode 100644 index 0000000000000000000000000000000000000000..1653f8f603540d1c57faf8ba74e4041c6d4154e7 GIT binary patch literal 18743 zcmce;cT`i|*Di`8AWcN9AfTd%AVsQl1O%1dI{^XdMN9yJfP#Ri6h%6r_ufk=k)kwd zQbI?hCZR(JB!t`@ear9rzH`UD=lpRTL){tK*?X_G=9=@F&wS<%*Va^Jq+_L{prBxU z{76}sg5nf6aNK;F8u-`!p?@*(--#Exs)`h){Z~=Ihg0?sG#*e;R7BAqSyKU@X+0j9 zyr7^s(@y?-qSHO!mV)BZ_s7Z)^nEPXXJ``ijRIDP6E2&C4d0l#8Eh$W&1SPH^`mMQ zt&`}7lF?I5NzCUeMbD`)1fP5w9sTC)gSR)|oW5ay_S&bjZ?AdwOLlUDV=^)vfAse( zAZ?;<`wUDWWaIq?Z%fO=nf6yme zPyIf~7dgf9`yjpltorYRS5itsCw?EHzposxrV_6H{-9uSEkU8bSCtGP1Rv^5ot>@%xIRE-%^CWsX)^Hh#`O&FZwiLZ`Pzd7Atqhw&fOEV$XZ@xBz< zPK&#O-bnXpk9a=b=~{N_89jb8)fEI=Qk`%AL8n?=nMcXg(Lvmy_T{IX3gjOvw8x1j zTo!eC)2JUXQDRaz3t9Wor;#Gu*Ow|HDCI8T;HQ};iLpeDOPWR=T_RANB>#xDwz{=r z1jPy8>5aFTI}z-5hp@rbH~p>8Y$mn{`8}84%%UpK)Hv}NVd6Dv(HJnpE_eqrPy_8Q zF|QiqATXab4Se`8gz5|(+j*()p!#u`q<23|20B?2uzjn}doCdBrNHOdi7v(=-@T+2 z!Osn6VDL%2sqEYs>^`K&b*`aT#m|9tN9{kyZ{fG+X?ID`^wFUEU^Fj`_)slw(&62m z;b-+3T&j&PRs_dIWAD7uPS2k$GQQ%n*XR4h?)6VrW( zh%dGFUw&F|e2uq+P=+xyN|HV#*0xw@bx!W;*mUOAvK-#pKk?xu{4txvufzhkH75Hp zwn@%)BbPw(s-<7=_1W#c-mW@vnmR&PTVZGy7O+9XZ`xpU>y{USR(>wD&&+&#$Yvu# zqS4I6WkeCu`bI_BfkPf~V5Jx(hFIQ?_vEP`_x_narDsx#G;OrbibxpjqD9D%2Ygv? zPLWoNaA68WUI&LRVQQ94P14(Y3;gNYz?ew5yH;EA25;hx_Rv#v$6C}3x-nLY!$(J- z?j%H;yjY%eU?jomZu?)^(E_f;f*C zs~qR|hU?#G>UB(AicGl+U8X;zlgV{7b==2OrrWWEv zY4)CSNliO!3o(lg_C;E%}rPrP=uSpv2>-Ubi_3pFpJF}Z=;$CYDWhmHe zzNQpkfBLc^gpGAwd)P{`_Y$Y1Q!n3}!Dazb3yJ39de5)?rtqeo$}af|CM)$~B@n1% z{}TCztd*>wo{weRnu*UIsIJ*-d;~q^{E}cC*3$BJU66c&s4W$n`l{uH<4Re9f`zDs2@f&s^Bb?)2DbuL{hILEJAGi8B? z&bT7bQICq*!QlXBJ^01QL%T#3tF=_`t`gRouf#sBfaT+PB&n0Qvx2CE@cVm5@Z)+B zq}aY9M^pEM$;7|s{Xa&3`;LRQL;2H#T!)4r+7Ko}JcpV_(fyY*76Q%7xro{?Duxc# zUrQUpid_~KE!=cSHR9;Z;)BY@=ktM6dyvgd4-#QVOOK1YR(%c=u#cLXY|*xBrh8O9duy{qOQZK@ zEuvxT66hEna`rZ9iL^AmA{VdtJ36G7GOJ&+|6^5h1#kt0);9+$beF-8Rd>>shh*;v zaFmqyicOOmBxNmxG7e)%)s}rLAU@!sXZZyK<}D>K^2F4lgG&g5TOUr}3Z|vHY`US@ zynQG*39qtV**r31oB7D-PMTjSS|c0mE4b>PfI~LH?3#tTMg$QxXQIH&r$x;o{`41q zuD(QM-!M^2a~=MZJX;MHxWuP+(JJcZl2EQ17uT=*63ORJ-z74UhKP%4#9fE8LxaR$ zoTP=-IY!|63NzRLHJcAAl04eu2d$+e)lrRth?kpT%0d~AMw(EsC|db46}1$3V>gMf zBYRr-Vo&*oJtKo4+8Ntn(oSb>B0(3Iu!R|AyoasleoFAm0}q^z_@JF9J$9wje(Z=a z7jeu;cD^I*|FKJW9^G%o1X%=GBl5S40zCpDSW;E`wJYjx-P{V;3}%lEB0-G^0w%4o zgma?`G&*Q^-SqfUAVJN0f9gR39l9nTn=SaMhcgn))5!k6AwQEjl@t_WY-gyIOv-Z2Xvv5fqgE3zC_#pyj4!WSc+3DftV)A_At7p?CfP zHjcrxA#|N;PiDUEczSRDxMifHGhgd6H=pV>Ate>~*6QTF3Z`r1v87x0URLKZ*X~MR z=}UE*-`|+pyeMGl+1vE^6wBLxP?K3iFs(_$vy!PA`!d;MGB82cV5XPe(N*EToIs)v zYYpQVxVg1pZdn37f*>!#nXBr3c_=c@P+N-c+lzWO1s>7*=Z;0sg|^M7;-^2_bk2Of zBC>v%c}s036-scu`EVgDsoubO49-7Q+vvCHZhk9abuug7;@pocEf8PTVeO^|5I$|J zLvx*>aAoD;z025tAzNW{X9t!|;={(h3}!~UOD#kXJ+?z3>z&UdN9>K@7oAU@O!u@(I#nSeme%tw>MDd?3Q}YBl$hHm26v~T1N{SmoJ5~0#=G(;Z^fJ5RMqR^ zW0y9I&LFj0DaHpLc(gZa8$R20tp57Tj&JRyhI?^%+mbwZUX(ni3hjmqYLY5Z$yQu9 zEFF+7V_e+gF0-GK1XWr)lOSwsy3rm|=u1Jgzt7bLrZ$9zz{FAMr?z@nEcwE{p|hi3 z*6(ih>EQ5<=DEi+Hj2F{-Du8g2fN)GS5lHJa<75@qG7`#C6?(AGQL*ut@Vw=h%%}> zrgX-coj>2W#i@pv6dDk##Ki*+R`0t+&EhNf>_{2F851K-boV%*l?_vMq*pVIiowfc=#qm=;{cRk_v`8UbC@4uct zB9Zk?!(%C4aEdg)m*jI$aeKP8{i{k;yD;D$@wNjkMDCPj)FpPo&#?xFVqHlNvuG3V zrs@#@%Zi)M4mSU&s3KbAsgs0EQ{Bls3U~*q0~L1#V3lW%idy zHO%|hJQRBF_i3{;w#aDaIGDzQfRui?!o;jj;Rv{iWdDcF0mILVd}tUr&}S}jKEGg8 zT2C93s~lgTCN-Wa^w0$&rx+BICEg$eTc#T}!sFUuZ7i^|1_fD5~*>0m*3 zuZ^;x?5IXl7P(_O0B2;I$^bMLO0r| zB*2Rf5}s>5Q-bTkbM6#}y-1_vS{Df5WInieslnk+zi9MIEEA+`a{DCwM~waCoLts7 zohd2AbO4NTro=+ZY{D>6(l0VXf3%x57U!FnW>H(XKb)DXZN)YzQNs;kNIpFl$}C9W5z#_%@|1AzQw045zc>pD?8 zlN#2^M4Z7QQk{J-tLM)&KnRigX1ePd5lw@H;gXf9eV@a^QLi7)V<|89ISyUBE&xHE zq4iYdotf8qv73Nnr>8ityZUpimgsDl`>l!B!Ju~cl0fYt7*?mXo8~gqBgIfr_0bo$ z^*!eejV|a=(70)&$Bmka-~u%*t5sV{8W7wJrX|7W%VpSkMotU`96+1{^>ZPZ^fe_$ zio-snk&^<(GnhKZ98O^uBA*WRyB z1Bnltve5h-eT0{`G!_+CQX=#6XprPvikkP^@(h!>`*~|!ANFCGaeQ>&Kf}V0K2X%x zcs{BJ18@#xUpMhtAVK?K@e||Nik>#gA|4d-wHE9oqqSaPF=du6Y0P!_%NeuFH3&o%5wSZ=8OP$v| zSfaQ#1q@SU(<$&RC+bSAM%zn+umW>z5+eJ{u4}w)<1KTe1LNJW-Z0GL+MK8Bd3bJr zdEfn)$e_!zV{>Lp)@C9ppyBrYtiY6`Mm$bA%L06-`sAL{*RNvmhG(^Z{BpELMexV8 zTsnjBCasTi0p1GepM@RwW%LRg1nIPw^h{-swlgI3l4(8 zE*)=bZ%IB?#B42fQ_8$Lx-e!`$adsm8aR-w4v9TBN)(vc!A5`tDQwM(p-Um_9I{L+ z*m%m-7AAPM!uq9<^DXO{;%c59Z#ao(OObO*it(j~cx`{P!i>m-EkgdsmSbH;S*^%~ z!Fx)$rAHD#w4Hnth}=T{>HzWT#w0sGBa=1cRaoS6N?T>(lpQw48X_N{tqLh&{VJijgv+?&o5>)WQ_ZQCNOvWlYooq<_nW-5Fhlta zeMOWdq4H=f2s1JoIv6sqsIhSmK!N9TZQFl^Xz@yYttQEwE0CgLxA zPL*-oJ@kr^ui8}AcYwQ56x13h(Qzx^9&1bMWQyXXb32k%__W?x9)Vdgc$DF%&cm}4 zxLMAar1l3$jM!IfQBYJqh|m`6Ae^~;o}Y#yeejQ=YNr9ww@Vy$XICi|5~pK-v)vOQ z|LFfXCbS3uLMC#18%2u^Ta_&sim3j|8~;#(uWtT1jSNVxg#*y~FJAD`jo;zoH%|HV z2T72RUuCDzuqe%4-V8WUZgjYUmiQZs)wFiV;$(k*Lx{CEAH=g4sTS=+bK9)HA&Q4&u;+2C8bpv-KJ$! zpC0`nibP2B|0+9N96?B5alv6T!%lObefWpFd{ii&uzLJ75cfj_#;<*_>rQ&;1UT)t zif>Ww125(X8%$1D)ny35`{Jw%ML7A@4Z>s^+}gcq^I}uok3RPDY1!R__ob_^-5YU@ zfvd-zX;AqZ|00CLN+=}B;l$hJGzpIev->^~Y`A58?>Qt%RE%>cis=v5+XBcg^ifpF z+LnadbVu_Rn1=q2zsY5F|6{Ed^rYyfmGzsGp-HD$N&*LGBXkn9B@wx}gKY|ANnc}a zDGh1OPaL1wU3c7u^|O29w-n|+DZ`q0?*RM!q9#xMD)EO^GB@45Jwzb;A}1+>KJPaJ z%Ub)PRr!5Sf8gAHdvSZ2$5Y$*?u{Z);9$>rfxDcwLwEh%W7+obmvMdHNW(pa^xUD~ ztM*GE1nGKmE+i5oJ_E&>^%Z{4+sd419~j4=h0nRRcWj8P2Wq4Z*|!j8z0+<)^PK&A zJpfXbbB+L!Xc{IIY(0oOW8rQc(i0Q!`3AUf?tbb*?%we@ami*NZ<$m-_ldXcmPP8O z4HqvsF;swS=u!A_S>z)sW{nnXy#?L5Ss#&0sia_e@bK}>CR`$L6PLC0mg_GNoWsb? zaWuTyr~|v5Cr}gZaq27V6pQJ8_a>=J10~ie&^q{*)?$o#R_(^fyz0yhrlheEC-p&- zitBnrb;5{E9HCz+Euy|Fx9oU3{_Y#&<1$hN0TZ~Rl|qjDwiR%PKn8t`!zB#*gAR6x8}ztY_b4$_#nxbf zg+ZvGsD=c*3C9<)ULim`>Lhgn^ECujnd1hmqLUN%GlZP96Xm zX0lo1u?`(yk6ufxz~ntb{Avv9q+5`05XxCczgZ0(AnAp?Jf!`d#JrkBPgke0Z6EBh z4j;|$bOl~<4@eYj@d0r)7B7Zx*1e0H3lEbcJ*4MqQxip3AxNqu9=vxK)t{YPbAg_- zkVPQs$mlFU{pzpkMr;LKn%H0EJVtB~Nn51)A9MJ12Y^#dAi9&FVA6}%Rwoaw2FsZ@ zJhqvUO`}yqo)kQ`e8^(4`Rb{APHU%({P&EjOH*XMTc)bqN&LfU&1Wf$D%()~6w?>eGIKHWJo9&f`{N`m&ZN zc4ErPYTf3n=WOJOv5~NnR5%R(Xucglb>11pcOPPvAg#(kF`@oK5wc!wX=&qEjwb;$ zAZD?o!Qr^fDnHVJ?s1j&nykK^R9EfDB)|X06(QAiES3tuKIn>H)THMm(|Xjt+JIOf z9P4-`7SoVhWWIZdd{Wl}p4$2nJomT}I7oLyjdjXFYvp9A(Wb($-s7De8{S-D4>&#e`bg50$F>ZIID(Lb(o%oHz zU-2_)2-4hiP~h(7bVMLtiHZnN%-!?=y>=&3Yw(8g<8atK zZSS+b#FZycw2a&NQUsj97o3EK06@F{Qn167hE*79kK2&9Qi&iGx9HmFU`N*d^G4_Q zVEx)~RHn#gq0;#g-f5($m{*;lmohLp^cMwdR7(5v(|~L#1)=?Hs_=~S;Zm>Hkt}5S z1JxH_nU|AbdP&VxsdddXS?AZ=qt3|U))Y7B!fmJ(qk}BcGv~I}r2pi;9FaQL0x%dK z_tmXxj7^$bU>aSHQZQcdisH>fZN)v~;kG4u*kl=gV?*AD=fchREr^!pA_dPMpB+p0 zIvzWVpb15r0U7>LwA@a2nihkt0L!_ezRGZD){NIb6wb8PQ99OsUNKea+9=khf5jQj zi-FRdfs0`zvgO#UFMnj%c0g5*>=(T#2Y-FytX^pS61{f? zxm1!`+NV}#8mz;p>iS;s3hc!0RQl!{DOm{|u(??jFlqIy%j= zzL0WA?~?Rs|5uOQnCrN)9qh>pRzj_=Pih>sDf43gsvl#J;!ndu_dQuQ0M^k{9eEE1 z2I`k~77pusqX_w%6Q7p&TttJVV(^xNW(~XsMymwAV=)zf2cskl`? zd8;jLKXsuK%C;g2Tc^2M1;iFF2<2y$>9CNgCKOtkk3w5@B2ZkIiiM6DysXVA3^{q; z(*e$kTj@{J5?ig7gRp*z*E#4XbX7>6 zNC-en7j5RXRP9@A%=Y#tV7@oT^_^GGE@}jS*C*y<4-aKsJNFW+)+$sWtJ0;L&FFie z+g+WzaupDso3sd=V70S5Bz`Od-jC$_Siq>7Ui#x`tAurHvP~WhJKXC%P-~W^dV9gC50O_N7V>OhWO;=z4(U zSN_e}D1ovrk32VeSb{1n#@;`2v8CfyoELVQb|0^nG{_Y(lv+qM*Np#o^*;Fi1>^i3 zBO9&P3P4=f-a8^hk-m&A1fL#qb_9oVV6g2NnwkJHUhMs~jss7v>p`K*=V|`yJGK(Tv z#%@+~@cC$4#F%sMo6ogkB;2xB@79fFpipRm*h49S$HNAtop8CArgohMy{il`P9XJt z$ZkVbv2MBUz*Tw_{cFF>yK>0Vx6}^e&Z#j_Ef>;es)At^cly94@!YfU81TG+eL4d6 zxxUE~&2(P0IH`OTibv(S;XWyihMqeo0O|q?Y;jTr*&d3WXJY^!(EFAyL82cCEP#wi&aeQYX%rLF57*#4>fC+Mi4fL= zq+IBu7Q^w^v^bsf%B!AdnnM-oA~L(G`$D`1DbYbKO4j-5(OU7Aj{3nQ&D#jIZo5wd zHgllInn?VSbH3Y<33#r8(UAl8kSXtkLd%wQd#Ix{f691O<$asqTd^nYD%}*wsLY~H zJf}wl8$wqSK_<)r#z;IQ0?hb)n1_n-r6EPSiub|9pp4vy)11zt!Et`2>%HQjdiC6I z&1u}ML+NK!cBGzeD{X>HN(I6Pmo6j?xV?(6x6aE$4tgD|OwJ}zv!tpBIv{+p>-JpS zZB=<0i*oPc49>}WwPp8E&iAq1wVPa4lwLgnxn5khXKtNy@+4K(u{7*R;8y%&xhQDh zElJs82`AquT8b_S>z_Z{!__n1yy?Bf3vnWasb?ah(o)@$>DSX~MnY_%x8R?Za6$Kd*Xm>c>$3RIi8Av}n0Cx{%VJrYH%{&<(cr?O|Bq-)|floSCWqUZ!r(=6l55B5D79C=&|oh<5|ac7gtncyn@Ysz9dMztQ|r zy7-VU^Ka@T{=b*y{+ks44|MGq91fIM)}HmJE(ln{7X@NLUSxU8B*1?*=QzAiO(&&VK=hLME~`$OzU#%W6rR_~E9 zc-?2fsn4xX)RE#)11;nI?6Q`>fCATZH4``%ikZ(Pt8^r8IjH&`mNO=f#k;Yg7vz9l zc2TB7-AM!qowjL^Jrl2btOjI9+V}O%*MKU=A-^5n9dWl=rz{@Pq^Cna?8Mui_8(D| z9_`pAdu0@VUmvfTQZaXslLb}V78v-4MO`f@7EP<_B7`zkf!EghE$V;v@J>DSZ20wVDsU?uGmb485k^~a zOMmcb*yi!2Z zy4EJIZ&!IYPJDg7Gqrr7wDxNmsrzEdw08?GpFeMXNF2z{j=`8=VP515fTUm6Gm&!% zKGymlAU>XnknQNp05}2)<}B@Bzfhju%gTweO!0zXa>X$6s`Rth*7q&uhWU)jOq)x5 ziQgw)xP5;UZkW4S6DfXewe;?nm~WVRKY?j8{%D`aIMdi%64C zvuirc`Q$ajq?mQiYhz*|)tT^#u-~pR(?Ut3_xg2ykXf^r1^PL_8HTWyfc(3As@FAJ4#DedG*-o3h*_q7}6>2KXMZy7AHKn(Snm3N_!TPUe>a&Je`6gFa;*@b)@_Iy4K z0AyCGin>E}P|#}$zrEUC9;#}$Ie6K2C=PZepNM|{?{llJSE@na0R zQeukSi#XULR-iQLln89YN-386f%8wNnB8 zAlfb-5Xbd{BK>pU0R z8A)TAF}ebT;-LlZaR;hfg76cODz^^QxX;tAQh$a&&mmEriEz_SELD<*E*B4%xipbl z^)W()3;Bl^`7JDqtQ4)^Zn$S8Z>)-mW~-x0KbdJD_fZgV`F<}~pq+HW;GmKiFx>>t z>Z=s5{Lh9`OMT1~j}^!nJo^&XRUnWsD8qW?@~n-b>y5y3kHGutWf=t814lsDIA~MT zDDE=p;wlCU=c^9A`zrK0!&U$LAMbpQ_Bd3`*)rw2*%nhifBGFYv-(I)1q=5iqXaRZ z!9pZUNcl*H=k5dpH@KvvCHu`e;J)!!`_iu|AR^C7FT}b$DrC<-t0W=fN0f-KMtjl| z+k)K8^H-d>_>G%7+TJZTQvk&59^ZBRV_mBAPKy^!l`bl@M>DV+M$V+%`*Zi?a|G?l zVTZc$w~g6~MFYEl_RqcAZ5G~lrq;KVvg&vh{xT|!Cghe`>l;=d{3i=u!Mcy4sT*D# z{WL%Xegudq(}Dx``$#ubgcI(2&j*pIh=BefkVr9e7B3En^KKaJ2AA& z%va@Z0#b+QsHd%DoJ0Q+kDy_?GZB?Wu4!+X`fG9mK z9&MjW$)~?%JMDuxjtGZ?XwCTCO%-JDQEXl7DKzK*wy5I&9yt6LyZ*zgk|k|qy~i~P zK({=>F34X)YDd*AOkA33$m&#^PPgWAyOaD6TZ+JX=y`8>R3r-%+s%4ZB49t?MRC07 zGn4frV>w`VgE76;jBCRhb&XmI|C25S?w{yF^;>@(k$isw2GsI_RrDqURG?;W4hR|^ zt^jo-fdZSn=${=0dZnYgu0@|A>qAqq7EKPYZ^oUT=nsh-(;@VwN_nLPj%ZsBN#7B6 zNv~7AnSSJo`+|_R08(k}06GXU~5s5>|kf8EVSmFX~Ln74FMICB?Zx=MHDH9P6&n z;H%Z)u7e~L8K#cs4eURTdJT_exc2n?6LT3007&vwtpMav>4Y9XFXqU`kQ6qIooVsJ z76PiGW!_TrisK=m3Rt2Jc~E;2ZBACR15{Fis9h2O3Hkj>Rc0$bodXEp&t}V16zQb} z?e(Oe52Oum1)mC`yp?miSgBwIeL9qJpk&IszEUlIx}#?yHLccpz%p5ty^k?jJTJF5 z=&RZcK%1K9N%+|^aF2W$bw+<^NGn}HW&C>T4RCpw&%BHEf1wzdoZnawph7R5*qQ7H z^y%wjnlMS5`F9)$32?pGhk;aS_ug2bFc|U7wWwAj>OxDtc|VA!%2mCtrF%FxZw6Ry z0R044PkoV_nUFgipo@0yK6|4(>|ADN(nSH#s{U2nbD45ue<;QwW)ac*H^<2uClNNi zS@lvh=oi54*SRfln`ti1=f490gUOe4M_#|Z?&WL{W)jOb(?s=t&6s&?%3A1p;Vu@& zA>_l;dMdgffE%HaS6%HhKGB%{85b1;{H{_bU!X~-6p4--NYP@PjL~a#r5K~}pS^@V zPKRD+Q{z%Ar4zxNAsNDAitQqHQsPdhzP=lEF0KNEKT3x7oezigbNf-%03lc7FSM=L zxL?~h%$MhE+ezmC;Qlh3EK@B&A~sQprf`NIBTbqQ=`T&epZSYQ*L=@^&T`jy=n-=W z?|cqFV8M=TKS~6L#mlw4Yk`=oJt(vuOXY@1X)gHr;77=BZb?6nPiU+{Y3?yl^1sW# zeO?AIi2-x~z=UzX5(UZ{t7<;PZ;Wg$`BgG4tyF*)-fr#qInU*~7|a@%RQfH4BaBf^ z{)VD`qiD8e>uVLoe4B!D1$HeMaUWyZHEDWM& zW5j4YgHhZ?3qN)(V&a|zk|TZqVJMyrxk&d|)B>ZY>a{q&T?zn@8GvNgZGWHk8*uDd zXMG!oW*lKKllCR5fh;g0z)L4PA%VRr{AZ+fi2qmgA+H*bMG*=-6d^U7ck=M&#Op&) z9N2#=zL5nGk4={_vq-6>J9}ReFu6!BZVME=Sdk_;Svh?UW{s3$DN(ovz_a8E_u;P4Bf3!DxtX+{h5OGUPS*#d@s87d4j2`aw9w7B-Mg_AVm~Zj( zwfMsm>7|F|*6r1>i<)aDCVz)Twc?S1?fGEyX0MsZTfABv@`?bX$Lh_O5Umb+0u<79 z$0v}{=2W!s08l#`LVfoo!*Kp-Dmqh@UFW2ebRnlR)x?jQST!=L?CDlUDX_Z)V1JaB z%vDQ5YBlWUF?XT|W%;YmxN^3hdgXtf`ixvk*LgA=%h<`9XzML#V?KY1<%??W;z_Yofh_78dOy?EMD{SW0jJCIJRZp3 zVLet<9lti}K+B&;pw7SRFFSLjjTlKM7_Ry}5ds*PTS{TEHWGi~DAYOGh-dV9&C~M{ z)m$6D#|a>?fW5XTeo6f-O~yK)sO1GKMI-_w%Hhq42~IhuV&Ymd0Ao{KjO73!QRC5# zCR?QwA#P7mo7b6jqccESrvu-KPFx#}kbrE`!65XY0VWp11)~94Zi>Y$<1K9%fP1pb zjk8w3-%sZe7G4V+0)<~UrP@DHoKZDCmzX)PBxRA*!e+s=bLs!LQu3dm{-46i?LV?k z3J%~h%T-LXX}>j>R{x=2_?OxF|B^dkdy%|iPJ0?zofW^;_UTf;qkA}h$t;4karPld z#P@3QZb`7DXXevKoS`rVA~`sZu9{7r_1;~#KwQJ;s7#$JTp zyPyu_w-WEv0YU)cMWETW3WI`pj|9qn7sX|Mf`U0X~jg!6YY&u_^a;v?FR2_g!?j>Izo1 zrn@r*;n0UTKs-f$#5<=%&!1S-tqlqX+(A8{rZhXP?YonW8QWcV>IG!GvysV{1TDxN zP)SU=&;(O~Tvq~e`n6J+tA(i>{=3Q0q3!ONtJ`7E=ulgJhG)rC z_4|H4SR>X0h{n@NST-kvi&@B>K$f8vOSu08BfBI%?&YwnJz1LhlK$evj;W!XNp;8M zT~mQ!KY5`1>|M|#gT>$0O!Wkm%kTTml5riqd$Z1j^EVG&=Aw zb6k&T+FaUH&ibgA1B_go6&#*>KKGiE+dLOu3EspNOYW}8?hyRTcd$UmS8b0>mwPwG z4V8?Juft0l-@}FdILx#%d~>n88{^yOBm(C%d4~<+%^Q42<&c2z)f6Vb9b8~Ic36_S z4@HvZg%95W0-F~SK|tCvS4TN~91MtAHiU^gDlsY%Gt-%ZQ>Vk5AL8mGAL7`^GwqnF zcocgE*eDI3BY@T-NiluM-H~0%-Am)1QF7bhZ^B}_On`*l(znJY7He1eeD|U>b9%o+ znK$0V2IyQn5S#WkO)JzZ&@7iaaQeW>EB9+Tpi)SxtSvq!Ppc8uh@1X#rBB~b&*rvz zH6pf_%OuWCdNdrU(x&=t=NP%l5t~Q4Iq&-<$d+w188!XCB!d4yP=3xwhg}sphNDKsD@pV z>hAKd02Z~ll5!CVDSFS1qn8ps>@aGKN+uZ%K%pxGEm{eCuOXqyq%D(VBbY|WWp{-1HJilM56|HXx&5}7 zwd680c|i3hmDys$kCwSD?W-k9$^d#eisAB8Wq3Vo{#f1+#e5=EzECOf62FM&_XD%B z61-3M&V_=N>!b5zwm8zhQY5M1JEFAq@zj+_(d|8nF~5%j=L=9?=*?n8^Q@XI(ysuE zAG!-H+ZAnq{GA@V+?`hbfZF|$H}{2?T|iLJBo}8kg+{pZsQ*Pc z)ut-eSPws|bLbW(Of#`c-4K&Kv>3$w+?%Kp@9Awx1|nVwPZ<1=_g0Oq2l|AH;g$1#7Yn5=KLV%I?=*43Tj%k+4BJ+#&&unuUCsOY4l z-ZMQ;xGb&d?7Jg7G-n(bBXsRaK&Vn;&nHZrEMed5z9&$)1e7n|XJ5GL-DlcK4hnTF zyX#@LvZg`B^hw}z^tdnG$CgIC9C|T%vW`rr9av}+3wpcsCGrEpM_CTZ9XT<5R*Df) z5MD_6SiHvXox3u?Cg!F0e&a>nJGsXB98UHj*~<^F1AEmeaU8x8SN_zHQYY%dll*f= zHuuKL8Q27qK$t<{ZV@GMaCK#CHYDFVH={J*a`7^9YplGLn&SHCZ91x>RjN@o^y#90 zonSr2PNC_M30qLwEzH%S2~bF(soa$_2k^Uki5$*sdkD_!%VBG`2W;O*R`DWVsauCVl@*EZ;Ef*QVHrh@LT=c=hQM~+$kF2v+|RA^`L=K)mE3jx zWaUi^&^6
6VR74G;-|%Mh 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 HN0I*v`XP(n literal 0 HcmV?d00001 diff --git a/website/docs/assets/ftrack/ftrack-delivery-icon.png b/website/docs/assets/ftrack/ftrack-delivery-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..30775c9a5060f0c6ecf8ac8c20f16deae819ef4d GIT binary patch literal 3772 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 literal 6706 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 Ucf8oS`WHl`t^x*@C|kV!H-vIR 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 M;D}r4zSGzL2Lq2k4gdfE literal 7943 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 GqkjM)R4(oS literal 61877 zcmd3tWm6no*R~;8@Zj#jg9dj(aF+`W4DRkWXo9=DhQS?%;1GfbouGsJ;0*3B_lM^{ zyj9&_x_Wo@UcL6N)yH|RXbm-aYz%S?1OxsBc#!9xZ$j5NHt; zze?-)<{fvp*K;Tat=~tF{B2&p?)Lw0ZdDfVyMok?=|>rae$+pq+^-1vsLP}_SRL;^ zS|Gr2KXelp@j#Qt5$&d_WJopMef>~GXU|7Vt26=XsP4${xozGhrem- z6Va^jQI{KXiH~x5G9OhD0$GUs|9C7Y9TF`t_$gg~#+j`Mw_5vh%2h?pl5X9ND{i9f zeD)>2fo!%yZzY(^KhCN*ncC%(o=Nq`v$H7bft9ul=oG=k>9t>+Tfo2-wSHIfr?RxM zkiWYI#o>cY>G^BQUS11ykNjiay-tUlrv*>92*&>PQ+wCDxTmtYfYix%hZB}@s}##m z7$WSVBb}p(t<2dkPrJ89iw;N6w!q$;DT;;0UK{~Qxm%&ae|rm{X8r1t2^Y@Z^;whH zNka!asqE8vXxURO>pC}UnwDYyzU<3*_Ut}OSp!RT-epJ6GC#Eghf+Ix;X*pJ; zXYc>^qEONuA~%DWF^H@bQbt5a8pjN+I&E3j)JYYW`iZMk#PHdvTN#t{l@y)CEtQAu z{ua{035>#juMsmFiR=@Bk(b0%{>%?MxjhdXI?U+#`3 z8Q~SEw?J&E^b6JUJh_LS;6!44V_S%D98+RBGZH`TRP57PEphdkdU@(AdM(CWXrp`g zJ6S!}d2iK~C7KRl&U%N~;@qW4K%Jgv-L;jm4T7ewFu(^T@VZ+GO{7?84b0HW;EGlxSmpJ*eM@fVyPDBT5&Z6T#2o^aVicT7v>N(=V8;>`o@)bRC!`G zK1lMYREm4J(A9t>?L*y*T)^L72&|1x;)zvn^@oy39B z%`2TEkOoWb)u5-yef+)oi*ec2zd?ri@UHrlMaEt6va}{D^Q1Lx&M$sbfnT<1HcKhz zJL+EF7fuVUWhG*EuIT5(Vb0%uxAuPz%g4o?U(gP9!eNJB%p7A|D^y?!^;#HZwK)aF zY9>W7?P%^gaX*0@2ywTcmIMhB87L!;cE8|pPUYYmHSdj zi8ij2m+CaPSw9m^JX9e&eC~eit*-m=V0#`9frRA9*p#JxsjSrX$MCk7GLvGpsD+`r zSrzAMP?W{}W86&dJJE|al^XTa>FIOY=u9Qq8cD60$vbFo@2pF=1_e`1*K7_-utB3i``g>-H}ITLcKB8qpbDj(s*mx>fqUVg83hka}$GZzK$(H2V{5^ zcXcv1Iwcgi6A7SkAEEiv97=p!C)3&w;z%fT)x4GZ+LQr>7EDV_FLQt{V<8(J8HIRb=|%9R0Exjz9nP@A(BeN;U+ud4=YIO9`gWaD zja4vLO`u%*;E2HF)KTO#|1yQ#)d6OGVJeZ7L~-=P5AV5765Y|kGdDoRx#+Yq;Oa=} z>GYWBh4McCfeXHXINTW=-`F(hd9PHOHpE8dbp6UwPh<9{Md?SE#@a01yR8Dpf}RDm z>>dd!fd>l1q)KkAG7*LTE$T?m8`W>s(gpv74~RW+eo?vDhwhh(bh?q46=v=SBd>j> zRx0z8mLzg!cKxZ7LgI;q*-fmocV{mq@paFo@TxktEAgYhqyn-6MQ?9r$h#nNQn@;b z#D>_7Om)h5&$O(0f(*ZRl4!r&+FAef@+6!FMKNObbD0q=%jz=_JtcLKy<-0C3b@5D zOqh+GrsKPKFD0--97W!8GM7msEZe?wYuk>*cQrQZuS4w6M;gw<0m8ia;(}h&=Ms-~ zuIPQ5MI#E-FnDO18c5h-T=9n|Iqe_-s%M*ke%()W0RF!|WWR}`-C5UtW#TSm{9b6x zVwIfV_W=D)X|fB?iB4w1dr0xa60O#tAm(?ioIfhU5%7@0fy|1f#X-y`wl#Nxtjs~o zRwYcvb+TUrEZiSD62>nyB^ca?N;`EDvKT2_EA{`KSNillDnb|TXA*W zFp;-OHIgxs`@l5QmY7vJwt=>D5#UDXnrVMfBJ+}}EF#k*9$F6zcMY!LL?yb~gDFba zD!WtIkKWO&KcnWf!h5QGAzCn3^Yq@_Ik&XSovfthoG;5OgH?(y(UT zpM$d)$IrBqlpBUP@`rMH$#eC@puUBO;9>gy?`urK{$QwEUqJZP&!TxWcNBAbrgF*WfRB*I z_m0mjk(%+UGo?frlO5@d>a44jVBEoDu7A zNBIjATN3*CT05_g0%HV$1mb}PjiVd8KZuw1k9HZ6dn#kFWLu1u_s5lbqzeu&i10QX zmS!#!Ns&<>sCV9`gZSS1hDQF@qb)+Wt1C`1rbL%12WeZ6D za23W>me{44BgQq-jCJBuaNy-!EI%g61lY8|Q=p;$-L5}d!N>|J#f?`kQ=5iQOKldR0l#2IE0Gn2+J~86lHKUHj>pK}-|_Q#vh!WN zy~qBZ`uhIb4>IzWxA_fjRx;(g?vb-0uO*t-SMfREoDm1v#;@Fo6Ot;Ds%1&oXa3Zw zoRZu5`rsxkR6|C?hdVuuSUR8cX^O`Q%JIN}TFAZN`W;_E+$K0Ll3L#%h%BMs_PM~e z9NEN^g_KVXBsU)_3F=(PXz}{tWVmmEZ6Ga>UD7&~kFRX(R)iC&c(aJ}sdmYNT*EJP zjQ7c`XU9MY*OxlP-)lC}m)>@)lt8HGx6AKM@3i`bew>~7Yr#=LnPT2p{utedo_)Oi zXzE^{uY3ik(eLmKKh$2_(h7A~=Lz}W8mVd_Dl&_yKrp*H5;9fw^CkDU&$>+Q1Ou z_s{SnQCGL@)JU!?7B^Qrk+$keS zZyrA6bW|zse^fjRIJ*CGReLoW+VqV7?p);ALFHP??~+IrrD{a8gEqHL!vNJjOqc17 zt>6TXB}#)e<&cbH`BGPFTWxgc6mIy9JDWrHI@;^=((t2=`~z(|-)jNaH}Tj=kAPdP z0%DDaU9E@oa)jB3#KX0s#G5#dr7fqY21sno9uOx7Y_Of3O6)9ox*Z>R zEdq451=Ye|GlLXJPx=^z#w6P>PXioTe=Gy+nLYO<23;}qJZ)iCRRu^G2$*B4={hpa zkbONTg%$P?4EJ+HtA3G2XGw|Qr&x7q0R7e>0{=2?5~E}1O`8<0yDktIpD`e2_`1au zAqx?wB0y8C|4L%jzP&f^E#+wB*jdKPes%T7DegO6S*hl4-Gk*~{D;S;-=Rv_C-Df` z?+iCiN>_Wjr1sIIa^cu7v8I{qgJ`jFfR4P&2A4Z%Zc66k5TcPJ=%`F0s@KYY8D>KR zOBH*y3@Mbj4{r&&gD0cIx_5k|@Z&}0#I@9JYZUt}8!ZJmG^}LH_?z#;Qs?3#n^45f zyuAAd2U#z_$Urnsm5;aLAk9Put!6$x14H(GRD>LRd4N1Ow+?ed+Z>R2K)}SwGwSE; zCJ}Kf5o7Lg4_>2Y0KmfwIP4Fw_>@HSSFb!zGGJ)5rf8&yRe8QmZ^enNX^xDE#n{8AxBu_{C-n>!&wl!;kPPGh8asncpBsunrj3TYh2&MUMNG<*lj>PS!vBaI zrfdXk+`yJ{{h#!*{Kv1av2Tyq)aU|BQ$tG4%QfG=y#c3g*WCdM*D)r%CctYg6A&m3 z3*m@fOx2j+LAU*+!Cnk2Loi{WvLeO!nXHOmyo_^IuH1AVBeuW-opn5`^ub+(g_(Cq z*^m?Ayl2Sog|^I}Z}V%%A!2D!bW+6#leTU(W2~%ND|F_N^>Qg? zaFjJxH8vY?z9mX?*?%A7S70_K4!utB_UdnrWi)8rC=ThW(~OOq2%4HLaJ}D*>=H84 z*dYT|F4bT(P1W1klz$6)%CnNNDfx^s*RZal0WN-kCjcR`3Cx)^@Y+OxD>yI7tkRpJ z&dzkXDe!`hCam2cW-ZH{gHPV7&r4@U2D>NWCN zL8+|Oa~QS!wLE-Y6PWA>QDDL? zF3z|>biEhhuV>*HF^Sm(L$;zSniD+)f5*7mL2ms!>Pd|DA^V|^8Wq*-_~4&gT%G!n zbJx3_*#O-gFQ7JS7BN}1U&0r&nleAT_DVxT=tAq%(W*=cn43$es$x+dugPi> zByR56-t$)N{^rVCIQuu#@;o(^?MIL8_PE4@n&-w zL5E#`#m6=KAHyLtf zIWDZMi#OC3XK@(hb1bDZ#(dV@w;{9+i5+{nwGY{0fmWgU;*|E3!OoD3LkY$k7bai+Cz1wV)mzU@2<@H0;c~euDGskoE z*R2nq5(N%y8wtoI*wE75WE{|Pk^n+>lr{Q0f`OeatzLexOEwsYF-LR~6hFb0U8^=) z?$OfJ67Sx52uAufUu1)E9AV$wPn@Z;hv}J7e3{{0-#8IrUCLvxsGFBULU3CUW3~

+Y#$V$@5c51{ZB;#Roz(HfHlz5~>WMqM}ZMw>OIxS@q z0e^;S$8j^I88mMBqafza7yOAK1U%Z@1|iYSo#V76^RNPemK{Q}f7X-k9x*vn230xG zPOf{HI-rRan+Jk-(FAuF$LB(vICX^2{T=3)(T zoPdehqsd^?+fep%CX!;D@Y|M8B`Y_H-bi}D2+V^0%;!xOBo;$6FQi$x`VzSLWWpbD zUAJeg-CTzyhiqr&N{2jJH`Oaz1B{m!=}60yKl)KPI_l#_9Us2GgmbAoZ|3_IdYmlPV8~Nq&{`9X(bvl*NrR;&_TNbZ9v!(f!U?ej`yt2@kdf6< z>`de-Ye(ZEqHbwcz({PYW+7?FfovE4F4{x-B@|! zC?Hx@)z3X!^aFq|g=r-snf{n!4}c2>KUxG`t-ny{EiAM+iT|)BZlXat6 z!u9k(T3syp72IouJo+oZ9x-24<5C;G%j#FxcWP(2+R6Y4?bIx4dMBRkEzgrfW$mME zta~shY~XDBUwCcci6Nng-PD?HQ|KTnF6=6#F$H1K_xmWTOFUn()W+qyR{V-hqqO_L zaOi3XtNQ%|6Na?Ps`|8=UpTgp2{K4%@7=rE)eH1%E-N;5y9mPtd~;2)Q-U@$f3~*a zy7LbD($2Ny;;mn;X+31`g*ZiIl@ZFH1um%~f(DL0Ee!`YwY=?hkicP?=29(TZHJD? zmiWOtnSyyi_GyDjOJ1uCE5fP(Kd-va7>4t^30}f+~{Z;yI#8->s>Tn}>?jlw#Ri{l5jmp=rZ#Csl-Ip%I0?WD0J( z#hM1bTC>j8^Ia0ad4oyh>QqNvWO$sG+pV_T3R6=86{|p4!#Jz7a)^Eua#a3j;bze6 z!SC@if#i-$b0w=H>1?brj5NQZRVTY9R706tk)Oz8;`ZK7?I);`aCC#7l3Kx*PHcYS zd>h+USx$fVF;DjxFzO91pI?OGrx}C2aCtnZ2Y&_Wk9U1gk&#$#rQdcxYY@7!&gjh= zX9PjvP1En7%ja{e1(8iB-jd@P%gZKz6qf_nEc_o7IN~~(?7F(m$ao4V@1Gy|Mw<(i zQg<67;xfupcpd%7NbTZha7l}JD?HDHU?CM45?C*KKM2?AIBQl z9_CC*zLIsWCS3bZOkDH-_aFLV>IP{kDYrQr!+p6Ir>I@8B6jXDHx5}Tt`lQ!$`qEj z)-P>o44s5Abruk#S9>Wdwt)m)p}l8D>C9pmZEk+axRFY0u|1&@$LQYYn9-C^M%?@{ z?rp_Ei^WZ>OMwyd9`JiYaZ$SF4Da540qhnIfpNKnhnVU$#jr-ZeQpE9G^8rcj?JB` zK4W>P{&;3?1kh=5o6$ zo#FB+6lt@_xv3M|w)fG_T(Y$)7GtNG^21=Cf@MUEc4JY>K}U~c)Y#X3t&oYCRDu`? zic%*75k>=9hme`OZc;018NVflRVRxIYkY|m7iX50a-3bGt&byvyedXZm?OXRLD+Pc zQJ&d8<@Yt9Emh+_$1Fj(A%@}0@VU4Hyk0D2Q*=m3^(kM1_H0cno}XjxCe*qO)uDr* zcU^c{{OB>K)?Hhs)eLycOWDJ(s_H=GIKOh_obV3}4uDjM{xqCp%I;cGdDxZPCSP^ z5Pypkkw|zTp0t(=aSfPC>OI+Nz|b&F=%rZXkrivv;8XHIrFem{0m{;Ml}@^8iyX9a zn9`rjg!}=EYCg8yl6`I7_5NJ~THXGS%u}^Oy~jd7vSVdoVzvy3Vh0BYTbjg(DDEY* zM+8v;c2_xa&dh~fP#?dd0g|?CL5z+tQz{-78)o(L!a3v;V zVWA5A2z;BB8*__MpWskLB>xqK!oN!armPe?2ds+PAsRTA>Anw}ITJ}t2^v_4*#*)4 zGzo^X0f9jq%vYz!OpYyA8a(!kl9txm0p_HeK!&U2Tbw7|il5uvDNG_d{?P@+KO?0F z&kHQ0na*>mE!9J;@zgf#-L?-RmXTS_~rXM0usw=%zie5pj*;Q1!e$zM5%%8%GbI~05m zi*R`h+$lA-4%~eGa=r?tXc~ZDdYmmHgK(DyqMU&_#gbZqhez{mO~YMX|K|mW#9JLY z_WQfgGmEptV07FsGmW%*U?ls_wkMFZPlR!Y*JQaTEQi6b$7hL<&vI$_Zmb{7rF?h5 z4e&cwI8WOCTN=oiw5UM>G>ANYZ;INFR{9_@wg8)k7JXn7|E-7p?CeC^R+4^}V6Xlx za<=A+dU5q>H0q|pj)8CVgy-S=kgAhOnNy-14g;DO;qH^@gh@7a@+DhNon{x=MDAGh z`jxm!svMIG-F{L5(jLRYw!u$T4sa$ZQ6gcRo!p3s!XJhc-78noE{jwei@@}aM?+Nk zqz2yM@`VE%28pOZz)3h=tPmV$gyHwKn^_crOHu*(zn!n{uFW)vBSZOJajqj78)Rr7 zfe$)zaS9)HQLh->A7(Os(u^r|$SR#3lPN#j#^MQAVihsVKWuYXd_*%rb&ueUa9{gV zK=-`pl_ROU&UvmlQd0kMT>e1*`syV{SkZvaqfh1@!S+493rMDki0&>S%?n|*Jd)Ju z8mbHcJlx-PP%eMC!+E0i^TsoB)NDHc)k7C@gcj>U)g;bS5$*cfZo&Mk7WJY|*M28> z-ApDL#fivP!SLZ_deZxLdO2<1MTvv z`FBWSfwGcGzx(P2)`U*>O4E|(uwWW8$|T`!+Nr%pK?|c^UOd|O=nX63~F1u?J&;= zvGXmWmqxOk%j#__ni*kAtwfQ;i-T{>SIPd1|2V51;}B%Y*gZ4o%rz$3{LTh`65RUz zD!@x<|0!ajx`0+o5y{I6Tfx;% zcL(nn`1%??zh~v5NK79QDF2Fdh=Ob8@db{N&auNBPt#vsxbK+c4SL#Q$y^311R1_) znlw=+1u=h_R`f%EZg|q@!PazVv5+<^TK;{lUU4QN!|%0a=U`NElXcQ9N?+{yp32`L zl2x<8i$N6uw&tva+?d9s(vCB$Pxt7S<0{9oI`ezPF5gw7Z6$g6$6-Nn9K@{nOsdcg z;5toGvgY4~3!%KBaXH3JL?8y=F8ZHc1xa=m0v?f0Ff<190=-6;e0n3tN|a!Raa`5k|D32?$lu5-Fn(#46 z_4768xftw~h&d2Rclj>xuGR}lgLLUXA^TZ=Yto>`wM+WG&aTXFZTu6x&ys#DpbB@W z@CfiM6x=TnD2-Gx8t1HZ9KmG+;7yCBQaTI&U`F7h$%sqSYN%%hw~+jv@3l}>Bk6FP z10T9N9*q)xaTD027|n$plbfioN!jQm;raiLigU})FUTnJ01W+_6!$AYL!C>jn9wt| ze`oGL?}F~C{*tAm|H>m#9*||EuBS37;cMuX03)HdYnHR|dn@CmTnzu7k{j1HvU!*) z>GdHsFq0ylPuCKT+~*i-oWBwM%RP9kSJ53d79Zut0V#gpy9wUc2a70bzqOp1IsYYu5TVE#4#=< z?I0?q;jvQZ+Y^{=5+tUIPjzpXRVoe7xC%pUk67A18IOYz_ilLUw|#U!fz6kbK{zOJ zg1@%)OAIpHoJZBmpM(|_p|x9bZF4|pQhPt*AMg>cKa0JC@-Qk!06H?ud36M2RHsJv zG7px7*$NR&55>V9gO2r$yHudn(wfICUN>gWPr9Y}4RNVpQeiAWUC~>(-QRM!Ayod9}`*oNKm z_lU)V^xHhZX6*x7^Q)WAMq^~HqzTTM^7Rs-*YAP%i1+X&B>pn)xr{e_@OX|gv0Bb| zD>;}U$fBJpCix(Ohi>=P4ALV^oh;gIiotJ@06 zf4TSDGq$=7(r_S}&>gwAByc-k8;jBCHxJN4I|{i5(dW2dU2+D+|0QhezMj|brJB%& zM*FXNwi?J%q_k<8kHhbhZtocSkC{fRN36{69>CMd)%VXFV`tgWe~3wKfL0u1Ml^-i zs3u)ik8`}*7ue-S4g2Dz0!^g4LFVY&7C zswz}?BcZV|>LV$mAZjSS{_je)jm8!F%Byt*Co0l}U@}bw$HU}%)!Z)7QLkDDgx!ubpZ1XuNbkg{6>5TW7)Kl51^}!*g?gG4b9ACXJ4Tk zX0-fc_3u{|0!V<2e;J?cNb3;kZO7wBPnnur^pDBCk0K+}tOh=!Dg?y0k6i$5wT=h@ zHHPvw|DD0zvy=icts{$zM$v&8q(5eXKV>Td)q)yUW|$7U3wf^|GNp^}w~F8f}r@Uh^rfWz3(t$c8l z&cOk$2>XJxjAaHqgJuWksyGh`v16cuZ7MtelYR|G&UM7ZbHR|LI8&d(%MC3@@5p>2 z@qdX-i#w%LcsZUjv}MxpQ&`3<=Bk{fQc*eu+gVP*ldHa%;yV9|FaA9%ME&jx@XA@( z6R1@@d=*0&Atubo@V{K2>Y3PiGCF{vv$1E{3gsGbsuPwspIRIyM}L7~iHnyBJrWPz z`)EKuuk=@NGA>q5`}J=~2??b*1OUd3aR2`nIW`CFcLa zSzFb%T#8HmAL$-N6AK6$`!uyRgT}bkk06C5qK5E+?q*t87h2R2HRngShJHSstr+Tj6yP6fDxdOlQoG~IuOr{c~)h}hUQ`uOzzG85OD zuIrD<&5$>4mGQBSkATW8t4R*-8Lfj+z^XQ?X2vWGg=*Ox#1+rqSAdMhWGIHIR%Ct| zUR>A>s1R_=sYh70?&@$kHcM@%$4-!sB*fCPq9%79eA-Dw9IGqrmz*q2JL}i)DPYxO zSTZka-OO4$X1n~RZ}RAt@~Rf>9;o3hgFyWAp(S!NK$Dqe@T2bcSLYwfXt=oJyw3q) zd;cAIhMLsncYl$?H|R#A_1)rX86M`S5rbS{ud)1kDU}R4d#26=w9)q>C{m|jpGcD& zEV23rg?h^iwrKrzAMZMt4;CPDbm-tBVN@)RVZDEP$1f^hNhy^RD|kOj8%2_BeOjgZ?{A&GG}(mXYc`A-n&C`eR0_eo7pC6?$?F+za9 zGt$A?`D_U+d+FkiT ztV=5^u5D}PpwAiQm`wg-Vo2IfzuMckogu1b-5%RI+{hs#xUh}#ai!$I`|R&>FUk4=7Gijbr3ool*iypfleR{_M zPou#~9a{()2U=iUQ?0wD6y0o+I@xs>E=d8hV2KZuZLoeY!tTHfqY4a_JyR>6@I*W{iM%*HK zD*GVsi{3E}lck{>^snE|1!h5EA@3BQQ>(=rzL((2+EE6Tm*;UBra3Lvms^s<{l|8P zX)do4*-5n}DEQ|#aEMR6Q}Y9f>bdUI z7ge#>L-qTowwGI+EQEsKLiL~{^r36(t32@^m$p!Z43H*pw9gtqk6%rT zO(jD8HbqiG?NDP&I!VGgpum%^HM1OJJq>S7$=5JT5Bk2`S~G4u(ps)2IUl*AI+^!4CvIzj|z(Fs>~7?FDQqDaPJps;lAP zf%T_5=0LY*kfTGD^}xV$X5bC%?N#>*RE>(tmDMtA@}`JCF`@W@Mw{bQ9NcR)tIBu+ zPS0HKmTU^)kSP=QPS))-v8BN1?nxwIRs?fo+OXZMdp!vK^~R1CW%&Ee31s1l$Ku|$ zQEc)KGw*HkfJgcNEvneyUEUX)pxxWxJM28po^s*ev}KSX+JS#r&*IiT;dT-q*_jBl zt#f=ZuSA-^)e#1-8#>>_Oi_3AMx3^*Lr(451Q(yFvX~_3`5q3`Z3-vdV@AJMo_K3a zsjCYWf_07@PN8v2_bK-+0Z$nD#ZBlC^D#$I_6`A)p|%-fI6sk>cGG%hiu}g4-27rHL3#y^>*Is1&{6bHA_7) zcyopB13|qNuzkL}Uko2EvL$I$`LkAuCI&KhY}QM%K4~b;7!HEKe>L+aI8s$KYdCI2 z2W!F8VNMQzgH)SeQoMaoz~~_YkG;Y2o$O#fbK~_!Qc*O(yec%LRz|kP1l5~oL_(5M0 z2=Jn(jsy1*z1{0E30!99qoX?UM+g|_h8CHRYlWQlbShhcHXaZ86ZwD(`7YBFxf5ld z5PTvk*vP#0+_H(K8Wj`WchdrsyeJL6&wUyJbo_V#JU_oq0%D#LgKnp&qJY15B_HsD zFSvrV&+n*$w$XZr8P|c|PdfwmeO|DGH_x8uCHM7$?p?mIx}D|rXcqk`0m8xI#M*0` z>(6OVJ17&D{XygIB*Q1-xoIcH*9O!-qGve*YQ-6XoWz_l$gcT>Ada z$d+R4Ip=yZNxa5DmDv`mk!}ROE;xBkXqvE7QEt!Cu6P4N&6PJbXWlK@*3F}~^P)HZ zsd@$cs1(pRWx3^mc7VS541adiZtU%~T5WNyucQVak7K;uPz%;@X1UF9GHwjkZ_L^= zH9B;kSbatc5O8eEuW52i&PjnB>)kKOtJz)olUn@g_?8vZqXxX$`FXi@n^NHWU?)%Y zNslln8L}0wo^o{b`ow&``Fg`|clp}9!*KT#&F{*taTj}Tycd-ayave&&Yf`lF=oS( zk3$@RrF1=75?A?cmdT-nnOE=9wV2+`%|MYeY zZg*PwIci&1FSB(u(q`>hwbvw^&*T5b)vZMC#P%j_7deV-vW=SmEOXqG;K8KK=PAra+e& zr1e4UO5-KOB+L}95rn1ZSlDxW5SoaQI;G!ZFK4F$Mbzam{8`9bJR4YF&rj;Wt(w0qg@zH27t0Plks4Tc zv9AJ~y{EuIu0dk`vnTfk=CR)ZUK7SqApubc6QY%G{q5RkbX##Lr8PW3rL)uPK28-L zd6@Xb<0`A~1I#Aa?3_eW>S&e$GH&6It9Z}9)v);GHqrJRG;~~Vdx(AV;8B%pV+r}J zBXJZX7_j_IKRN}F-Ze|VF4Mze-{T)s_BNq`|7EiJNGCQ`Q?NL z=iLrDWJK+!2GadtgIOlIAKp*jiXL2rckPhXjsEaE*iql81VVWPV z?|Ex5{aIE5IxQI3uDOwV00s{VLvAd{K>NnCJf2jT4hmNI3$X4UiIB6Oq@q`EBGg)lTmx~(_<>XiY1%!k$2VZME zuwc=Kom)JhGOve9UGHw{7uYT_(XoHHO)!&sBt!au2o0AC@PnFN!|0 zckj9Ga8KX^=G=u^Obw9W(rt=vFB60s^38@(xJ4Bue8HaAOn_0OA0t#noiN2CnE#_6 zPfDYRpEi+Z7%#hG7?U6_M$a)BS0D3x_*bAaS&EmF5~S>P^mxbFkBVP~1hNz32&vyiz>pAdo=yaCr?q&rgr#N*=mIz{P`NfJ3^95$gMH<2%t$yrRE~bWa!;Zy2B7LYE39M-m?eQT+(RbvO0;Od zdaNr0d-TH-s&|icwyn#niR0DTz?!Q_@3Phc(R`LkC{pDx*9qEo3ygDngA)%V8;tKB zaY{PY7}o+ZK^GRgIw6aF|9JB~plPFz25pkyeH_&T1FymkP13fwzzuy&|4hQ4@5S}& zx1JY@(kD;Hd4SjJ^^Ji<$&U-d#4^Th$KDhIxg5}w43OOl15=$+o(E2^{NzH~Rh|pu z&vd+)uC4wW=~A5ydJOeM$$cl-nvvuOe6RD;rC&WI(aJ^XE69skJ@yO_-t1{$qxFNr zN5wIJDqy>`Prx7M#2uQEqlMDejH;^kdMs6K$nrc{MVL1lA`h%+>o=ELdq;|e=CU+{ zY94Q;rmzK{mfGv_0;#I+hi@ed6eEgir!+;162Z5`?h%Q)*mkIey#dMr?^NBX{|r4Q zwhrq3d}}4F<=?lld3tbp66t+#=pFhv#WSy^ON8s=^KE^__4Qn;b={|4oy-yD(`Orx zgj_a2UwwSIiASoq%?{o5VaTt})93CE`UrTJAm_>E2bhlUTd2Mk>LY-LCe8Y9FpR2) z0lwrO(DjMO)g_Lo9w!FjN<;t`4H-?J4E|cH?0cv! zI8;@+<6=Bc5{tH{_uf`=Dvclh>}1!<+eJ`{pKttLH8ZvFN^y&B_2wNi5vx@1As>H|rH=ou41dyMIn~!K_AtD}Il1@m znVvPv(aUGjoo26D&Pph4y|!(Ia+dv~8(C1DtuM!`c!r?&2_Vn+bM6JqrjRFJ%{CWx zTS}8vJdQe@KEG#V{SRV~1-H`YEc+hRH-!=gis`Q03&FqGPm8 z^Q{3${WHjDy?|$R_PWHipEALJ2=X;LJfle=WK9P1?vCMJcqpg66J;_yB z7BpMs(vBozmM;$XRCqBi6y>B@YfpEV5_%8Ut}OGul}XtpLDuV1%!Sir?bgxX7g3hh z6{(I~YWnv#nNshBtjdlw731o-BsI*ZSXHY3ld{28F+8vd2ud)0^U})! z+(K+uwZ6M|urG77;7JXkCb?0mdg`th?|9Y(2eHr69NyJwcOY%flXJ5cc~gBW{rtl{ z{RAz{bAsc6zOJ;Akccp`Mibqi=-HaFJBgsILM?+Ob6Tb<@h8axH~sJ>b+b*w1-H3+ zQDzUur%z+~+L!oi{Nk%QhNRSeJ_XYT#ywiA6{r1Nfvgl%FijlS4z}cb9$vU{QtZFH|gy3EM+3HT19Yd^#m!2D#8gFA5XU{x30kO2GdqqgVpY61i<}wvHC)I zkVYetIkq8e15=%*U%E4@uVdgZj4z5?A#PuImeenXciL<1SHi)ea{KM55fS|1NleP` z(+Rorm0NjdT+4sw?N4H%XdFZrm6yu6(?ccLOFl@?{R*gQV=Ni$#N%Yg;Ny9UF|fAW z(O<*@>!xqq0IPpTRGp7ht&K>3;hKgUPISPt;I94PLxov>IMjT`2PXFKtR8A{C(^6M4)D!@)5w&VVp24NP=D!e@g_9pw!*z zM_%;UBc?;X^Mn^WNLm>|x@{y+@HOl}!Z#+-nL;sQg?EvT(;|Sk z4RYhOBsJJuDYx5wk%a#qX|=|e8Tel8PA@dVeejYUGAxk!K#Ak$&%%AhN@d8 z7pI&*o3Q)pnyEi)MDANO_sC}@(>E5@$PitJ6WSWPUO8^XSzqP5oN)85L*cSt<0Gn=V z6|pfb7iC&eZ;$PK_R-o7?2Vm2v|`yOY&?G}qY{5Twyxv#m%n3lmeQo};P+(|C`~?{ zGF!}HHzm&%>=i06n6FMayIf+od-QC^d^Lylmf37Zt7@9IrfItd+I`$uccgCF|MKMBe?r@wVydc0UO=i7k3})1@IO4M%mKAAcVh zsvJX0H|_MvBx?-5*x^o^$W7)42Xyvp6OZPlYf;KYud-qvrxl7eP3oRq7F6$vPVIU*8Ub&BhSw=(W1mUT3XXB2juyb z<;e@a^-Cu-b;Wjl!|m;cdOBr!vS6Yo%rlLtH{3ncFg@Y?bj~Etpe@MI4ZEsgcYn|NVM9&9d^u-1gWYDsX4`?GjPB9}ePN)O--Ur>#1Y8`N2nu# z`u-l{^j=Q4Zb^&|N1PubS3#3LA~n*7-y%KH?q|A_GB}P9oB^XC7YcT+0Y!2!)FFZr zP@i@xS1oa1NWeu(^_3g-Qz0LJPbnc2AjLBlg;&e*Y7*H8`zGQZKf|^^f6}!!xqPU`*E_wFxGZrSJ zunpDr2CGWc@&i8l;I#X!ovGM9OvygWDd%T=@bLu;lQXe~mX=-JQq6L{ur2py#j_7C zIiFYDzHBM?4Q(q9I6VgDNg4yY==1SG!ucNK|S0N z9ZU2_Yh7M%m(%k)m?r=-1=3EEZ`dfACmiSjf^@;()5c2kCMaozuqx^jHymWCUR0E< zZ`mL-0;qKv{O_j4+7))z0){;*O<`*Zd)+IV$uv#vdY_~kz+_A1T zli8fr*^+%(V%jCEvlY|k4#J~@X%rL(CMDXjO3IB!ay^=`dpc4s4ieF)eK`jsIUy(A zeT@T~(tc#=4=rz(o&?mq`xEdA^KaS7Jy6A=JIzxgdEV`wiA_34=QyuL%#>?D5lBHk z&*<~~^f1C#if@tDgGgus!^kOsQsj?{JEyUj9hURzd!GIbpVv)AKlRtKz(*%upAkTF ze)8ppU>%T|5EBV|{S-51$;egu6EW3RBFgG)GuSvacPJS z!S9}}{6ab9imU=q#G)+{^M-m-O`h-sYx)4IGpbC;hiNSCD%7x zXO7W?Xnz6=d`48i6{nPn-!N~3PzOK)6EN3HYIjg zgMaOD05}LhVytj$PhYv_64n(x2j%riJFXS~dia+9lqf02m!ZzFlmL3k-FGl=P#Qyo zZ(Pa|LllkR=j$1cknTuUlI-6JHye&&MbD=Vp!lBv2{Xqxb~5OFPHG^=@W~1Inm+%SKMvv zdd_e+GlD~3HzgY*i2))?QbStrKQA}%bLP^sp=WE-34Douee`rrlkLgv=m(K@;y;26 z;TRwEd5I{j`#O{aT9v4oOc#^R68#;(%A3|JH`px%6q+obvN*e>$R=D?B~?{J?%xOl z)+jP&ESJyu@Y^To`GRRtva=OU3#~QS)^rE4-2kcGGJr9bwlSE-JtC$I#(h!U7=tw} z+xs=!lT*%4pD|OK-PIMl^^UqV$$Fz~Epm`bANWr(A_4p!O?=caZ62{E_ioHwvt=37}al7~CP%UWe(SzKrH*UD0Wh`b{&HykCQ90BBmt^5^2*^;x%6}f8It*=?%-Ljv~P#epBU(=eFrrNUJ?OB@>PL@lG zSxwXKS>Ny2Y)kgKA>UZ`WkqW&))?Aq%eHK2Y>u)!_PY)DyNcF`h9i~`hR?Lh2I;L+ zaatM?Q2ZX!NIA*0_5e+63w_=rzk?iqtwk9Lqi>g<5Y#%Q!{h00l<2ap$BT5zS(3@Qjdz^(W({P0~N05~*>NlIh9!>hu}@`UBQapYbt7>=sjun+WtR_S+R! zy94@h#D2X;>_*t_x0p?@F&~dGiU(|$&v<@a<2adMvzee>t+8u6ba9O@pFaXP;$|_y zs5|0t*kXI!W4+p;4-M9v5!%%@J-yJ3(2T|i4bUBr*dJf9TD@X-?D8Xc1BulP&GAQu22i^@#M0l3#X%{#NLxiABIcXm!@ec@?H5 z-Lmlh)S!tMiu&w8rPCI}tDxr|WO}O;`xd+97kt{i;;CuU*o%G2ll6&1ej^svn-xMB zVH6Sq_icwZ_5gO+Ez>eH_7Omj?eYtbyCpv9(C%_nmdE5WqzCxXV0wLzcOQPnhsQfi zM|-TEU-7!$;YerGQX8BXv&i0UoRoIR<3pmUlHNui&k?6KVu-%9HPtKW*=NfX9USeC zT^D?yU4GI-*POSCe$WT{haVhw%@sR+v1m_tBtU~&Fb1SddqY4%5l{K*9T&++Wfw(c z%0do_F6ahDF-49}>J?FzUYs}#a^}wO7D<#@n;_&Y87I5V#I`I?f6}ZS4cr;P-9k=lk^GP8Z@I3W>>ekdwhr6#RT2@GoGKn;B|XMAFcakJ}mQugXp!q=-#P3 zV`-k~Y&E3kiB;L~y@K9?mqD+n3k}D^w_g|9{j{N>Fyc`}2Nnh(B2fAP0 z)44HQ#3ie9eoN|U#1die_)>rr+GzEVKa@h#S%mBy1{>#q0$65?K)tR5hvcijxKZ*v z0+MAt?sYBovH^(a%EAd+*%5^eBm=a<_nqx~$THSH<^%;Z$*XvMhjvoh+_XiIS(9=A z(>=2_ZH92$11Bv2P3ScTWSFVZgG{6ZIC!37XCUD$i;EV6O zF8DFyY@D2*(7{b3n%tXy1-M#TWVHqH73>DPtBJZV>nYe}fY1TwMq_S@!PK1BfXXx# z@UVU=lj&41>tq3*Urvi`a_HL7$#m@rT=h9p?@^>zW*mYm3tCrzhWbeSE}CpknP9X< zw^>={;Rb3R&~o>!t8~h9bxvP#D94-aIEB7a4xjV=8d+oCp*tRMIP7t3d$$>r`at?D z+fp{1kin8B`~=WOll~_Od!hF<>JKsr6OGZ!xMYAXg9nqXqyfp6j1XviMH`o2F&<7` zmjg9gR)}&sLm(N4mpRg*KA)ZnGdysPUCX1ov(0GbE}zyrTSlU*v7vyWARzDY7hnzs zkec($LPiLn!+;=ymY0D5(E!76n%m1Z5J?pDY(Vj<(dGiyL(CUcU-un_teZq2251~P zIlt3>RNE8aLS|qwatPM$a7Hsg75!ua%IyFYxeV^TLC?G@Z3~T~hrh>Ts9Fs8r$6PcL5nMZ%<9YBSR7#${jy zk}dM6rP(PI?deMrV^Kz&4M9i^hg3VWtmBO_ce*&INsO?{bu>NFY<5Vk5^O&A>XE+q zyRM5xYN8|0Y;wa;+6o6RDFnz-1#GM=M8PUM_84`QP7#22= zaKn}s1l2X&$}l(xUl>1i-l%4+c`{EUseMBQw}c{^0ljWCh~a#_D!q$LV9UD-AI*KL zU59+?hbc;ZYO=c^??O2#w?A=-I7^K9us9+@)+xgF?1aA5>)fR()_nHle`@ z*_Z}rY^{@eMK+^9ZEJ6<($;WL$k6yB*F`|h0a{NfY&Yn08OVVEKeLxn0@dX*uE(e6 zDHAp*8!p#i9a?QKizf+E8C|Y@)1%Z?Sjx_k+^ZfUW?RCDB}vN_br99~av3Fdm4^aa z8r+~Iv1gL*yUz)Te5LKOEY8L{c=P0x8%cc44*a_DdO|wi6Pa0lPIkKldy>PoU7>+X zmJj8@3E7wiW!nV}DzXhemi>l4sI|@ZI3b(8Ug^B8IUhZ+74_>WTMG@UwzhGm6RR80 z-*;VR`58U1`4>E*GUz#aFi?Uki&a`JQ^y4?B=CGWp)9^@MIq0E1XK=iIH|9kJ?KSi zn`N_l*$HK~V^Ed>P%Qn7mLf$dlZiv^bc!z=z2hs~gs&4Bf}+|0SX5Ubjy=tw(pH`ciNOF^ z#8!NjyU9Ywky0r1a{4o%MFXm6@(Ewlx5zc}_=^$xY?kV4V5rrk-v*!#SNG(yMejw( zAy3fn0P7#25%B_KvhehI2zdH}1_OHYX{E2o#x$tN#xyt~+h7@YMjupUV|{Q!Hl{&E zHm1QDTMG@&*qZyB7p?!}A~j$LXf|lc*|Z+aNx5DSrxeG{lpM8)^YU34X_FmXX`$fC zj-T9AJX!_}DHDramW={wOb3%Z$j|YUfTWtlbvD&ghqusvU@HbWwiACfi_EEvqy32> z4MKGd#zkeZ!EAsiJkoMn^c78H(#cbVVtQ0w^0!YF`y7`c2i(P&=qsG2@z>ilp2xq2 z&ydHT&!^7gU!%`CnyMUc(th1_nT-PV9rG%61TD@$l_Q%NqvmbISr`b6*k+)_D}z#` zHNMs>0gY!b=#-|i8Sr9D^TyN*nE`0f|iQ0KFXjgF3J56X#t|~pFM^Z@c z4fIlaPI}WZgj}yq@A-&q=g-f1eQHM}hV;fqpJb@ois?CrY<$kyTKJr^^(lQ&@EOQM zYDnCm?cn>b%SCG0YLTQ)AHj{MrVe@nKpRo-U?&nI@D6FNWb;V5w4^dYk-%j{FFX=3 zk-RQ%74+YM94xQ=4QRd$g$_XU5-LMTfDW*{lIWqw z2nzYQ8--SFTIc~+mk^d^fEDw99r&E8Q?@h2^tb5?Cwr3z3V`?;9+^)m+gTokKId%a z?iI4V$%AOl)+u#;L!VQ8$sDI3;j69-?tTWF?eYK@fQvaSvYbFFSLXxXuE$F$K!M7E zW;NQ$93m%SYXB*oYp0=uv>8!M*NJcHyl_Gmu9@d)2WWDiE0+4FCPn@Hr*U>Xlr03% zxIdIH(K@dN11`<@BA-TRQqLwGs=Wcf}+XJQXk(sVxMkW#Of%c|5gD6a7Fb%xZn zT-IqGr}>=bVQssp{tbYs@u`8Ir)OIY`P6Oh`FxG7y{*VUPU!Z%*JXDS?>9VfUppFN^Rsjs&dbeuBWWOxg4V=mAie)|9RN$=KWT@FimL{^ysh^F4 z`&)^*Zm_m&M^Cu%ve9Tj^~z-*3O(pUZf3K8nV37fzh`fs#N_#L50v0~6+Yxku*4K(>8Iw!T$ zQdK~nXq7k*CdQRB%TOIiROTGNScXbjj1f&}Xx8yO_`zML`C!=$%U^`_Ti@jv2{0m{ zr|Zdn7FUuZXHdy=LT*ohmlladbS5@P01=(7LdA9oR5+Y*XifG@HZI%KDq>Up1KvMt zc2H!Y**!l+5{bsimg`mNGmV~`LO>XeFd2<7YUC_ckFM+T7t$m8KI#Sx%bd(=kT!sx z+-CY7K~FM6X3iu*lfLLa3P9I&=-Un*p#75=2@nRQ6}6(9F;czZn{G+8jMx!!PjbIQ zK0X&#@X5a|k1dl+W#Vv3*P;4*uM2%5PZF6TbZ_t05?52F2Zl2le{RNp^1<3fMKcLS zN-9UBdYMo88lW^Vd>N3x>@Gm24YM;-$Lpk%bQK|3UP}y%Z23<4NR3*Jbq!{NTu&x> z5l2;sg`INyfo!P{7w1{mmOEx^%E-aJXH0Bg?tiD_q@-(XO`G2t5+mH4XB{*aX-24|sUI!=j1UzdYf~%L`WfEI4uL!8oSh&T16{mS@wrOqXv6mg5K(8Ho-c(6HBc zNCe3qE)sob`>#PQiAAl-7yKe}B#Ig2p}3a)Ks=2F>&u_wRqkUw(Oyhld-?CL@GCqC0G{ z95 z#`X0Kaks_3o#Wkyzv8cdxyL*nuxyUlZP(ZwQncp!pYW%Q_}sk{_6jH_gQ{IlO&G&_ z0YKA?@c8%+_xBH2uU7c{^n~N#prgcaAm|ux{5SIOqt|7YpE)Gqmlk@s;k7yP43TRo z)Ijd_IqPWWS)OO(zuP6JZY-dpY!JiC&gE3~0rcIFUN22_bz~ zpb2z_kL)Q8s1C41d~X9RYF*)5EdOMArvMe*>{)J>3?AXi&Li9;&w{Vgmk(HH&_-%c z0ulpvW#JrEov8pU)&C5bd1nkj7-Mn&h!4N~g2&qt+RY0-?+$3Yi1x6@e!Iiy@eYft zIr`ZIpM)_1GejDysWm^=Xa~c5HMm#0BsCq z&3c$U^wA%39CgY-PQ@t4(Gr5Q2Ztv5SWtH4@nnvhyF1+6PSNgP@adob!0*p%?7BX6 z$M^~#9&UjU&@>H3lPlcc-{Rf<4W{D;{qcbH>t}rVvcwJ(T;Dz5-NP*w(=pH@c0<_U=92J=|d)UvNN=IGJHOzryse!VzOEZmw}X z?Xlf%v0m-bMNz5H)kQ=y;YdorW(R{hqR+EI8Bw|Ar;IW9wnw7|S2x%A<>#Lfe*QUq z9Sgv3A3x%FIJgPw&j7I6b-Axe1^>g0#D2>I-j`WXV2`m&A^>X7oRZqZ@EAk2Ub)Gj zo=Q9fL;~z!ra)^XTGlOi{h5c$TQOKyX>&apun=Ie5Pc*_%t7vnr4=W$4a>xj%cYQWc20Dc5nt1w8Bej8 zk1!h_uzh;Qm*-b3H~aj(%?9HzCnJq9zk7#=Uw_3z4_dCScWF;r(BK z#fOJmEXEDG-3F^KU$7lD2w{Zr^ac;VJYY5%1G`u3N5JfUfo=@UuJ3Snca3@91EB@x zx44;(Fz#Et^f!3?@QBC70bf4-f!*eauImSRwX_Tt8`-LYb6+kGR?xbc?6m*@AOJ~3 zK~z}I4Jh++N)JkJNZWZtG+~6^mHeM|}F@542q;ovGuy&4+BSI zHW@fQWHO3ne-zhu%;pZ{-e|Tg2%*6!Ml}5nhvOdmwnN`Dyo)>mp_$aUCNbND$ z3LuD`2K7WHnJp>7?{_|Zy+D02*zh2`=tK8v6AiM+RQ^cCz@AQz{PTAuzJgrd-CPs6AB6A*l)~FUtaT7Opdl#tPY} z<{%Ld0U^^t@*sKWq#ClU695ju<^3!o$_|ZSlS^S!^<7zk`hy2$p@-D- z04Zo!SeQ}-&Pj{pw&V$AGOI@z2DCcS1m+XbcP$Pb(2Z_zbJya-yA67*vG02H9T1v^ z+LN}U<5LDQtd9KXx)#Ud5q;BRGHEa!H>zWjqfZ|@?m8UzYiwTUSX^D>_WA)=XfSS9 zcv-KoK6GjGprTK7_VDM=)E_8%FLQ17PBfu68XZxUT$_ydzU#2x@3G(S@n8S-U-;YK z{)R74Pw4W}lfsw$w|)+(8t{DUb+IEII&L$d1pyVQK!7GBAmak&7#0;-^U5T`zFaH= zCEDln5_u%BN$o|Aa3qN=SSgWLP#@q(D3jZi$IfC>!9ktNQDq%?Ne7t;rHx`!AgA5+ zOdIE|a((e`m6vvnlWs=mCYP+oP-fOQT9HOG(N%K+uVb9`2o@nk;p4^wAsHKy$AnB~ z^7{;pt5?z>qCf7iT&=O%Jm9CrJ%0JO5pEuL*tb2p;{of{1{0(sNqu*~?ijEbU1Ra# zSN!`;#AGqYWW2*_|BAzLhSAjxX7@kg-^O>??{;aU03D9)3j5bB`hA1N>=y6u8tg_5 zBKmxGF9I>5k3C}7V!K>ob#sgRzrM#THrPKsVY}Mm(Dn6YqJn?9L69o=2?9k9(k4Am zufv%peQ_^~`~}d~>kWSY{Uesk75=WfpGSo=>F<;%v5w+N;YY5^?0&k5SR#P@mz&sA zxqiR%Y@71rK~x^nyqhg!-n|5DsZx`lwa6~&SGXv`5L*o*4r)Pi zl+}pA$_Eikg+`pW@Ap`L`GnsmV~l@#kH@<^yuW>i7<(Ld8+^e>tlB;HtBCD(h1GJ4 zFq`4SyIZ`!oglVHY+pX&<@pm<-2(k&f)5XOcz@NPJ8ZG~^aozM9k$CJyXQ3yn=u}) zuP~d8acqy+t$XbD2OJMO?6zyf-44gDLwnd@yKiyq#<*&>*lt&N-L~kv2=^>?c{o|o z%6>~i-+(`NQl9(~2ml%FWCG0esSn`VmbQ+^BYyksH*9xXtX3<0dHRgQp+%I$LL^y{ zv7v!Rs{eB~3Uo%ce2I&wkRyexAZXA*C-)+f(N8-|=-eZq(J(rt)Qk99KIW_9JZ93(Q6>j-f~Y+~By`;jrJ} zu-{;OTw!l^n{n!m-IpAE+#+|KV?vb{M`_64t?)iuge|j0KgHN`5!M|f;y&@*(dd6 z@WOWWa+?-Xn9+rY{D&P?BZ|`TWww9;go2!Oc6L4vyA8CXbyjLBpSuch$#eLC=MVPJEdk4ZEAeo2Tadv4 zDFZEDk`5o+kqEn%1EOVYh=_fMcDKTpKQ>r?8KKFKI;4ZO9ePCIIoYF+5y!(Go7c~1 zLIC=Ru0Nt{d&JP<_4Ds`FZSp&eQW~IrPRTGw@M$W7SQd}e6Zgw5zt_AeS^pMzu?2; zEvBObR?n|^UCZNHFq;hYR(Gg?mfu_%&}%ehpjEZ0TfWn>HUeGWq3=64MrFd`u^SQ4 z!~it3$Nb}%b|FxcK7jpq@K}x>tYtvsNGLm>LZLamfq?;9@{?Rka#I3m5q3m^WhnrX zJHcju;T*s)<)uqyDVG=EL+VKFle`M#g=UoqjqtGYn@2^U2ixVo^+F$`b+^4t>{S z-?i0Ah-K;f4qe~5Q4kOi`v`;(J(|%B)5RU`-o3-^)dbz@ z6P~|3<8^nWqbB9BhkgMH#Lm6Rkb4ojZR3ET)gHmPjUm^KpE{ zgT{>xMwZ{|v2I*u~L3Z#7@3-j1iG9660v(pK^6#EwAR*lbNy^lyF6msqOKTf`!DX<0_Cc{4?&XxUpdFXFG@@ zt558gkr)5h?ui^x^%oxI^chGIiY;|eJ)_k#Um+mUgP>N=qhoMoWyCwQy@*)yWm+jO z(tf0y*sssgq0A0|EI)@J#>=B}DOx0+)h9homA;^L}P%~9mH>Kc(_69zZ|Jhq7e+E9m@uB=x&dQeW`)y)If zp0Ashpha1sujy9SW4(r#EO!UF%xDtx+hvm|*@Z)%AbEo%Z^-bQVk6NY*K;pWENE@6TI0WBSoNyl$wDIT;bb`;Tz zHzV}<#7GWBe7tP1@TUB&hO6lQ|1p}h4E9xN_}B(OF{Zgm7g_4P;M&$nvqrCMX0(EvYbs}m`d5FOXY!nO4Z>fyC zf|zC?HNHrHyD*Lv4Jt_bP0PF#MNoUeJH2BI&%@qCOJy{Cq+JCd$u(s=rHO>>hauln zkD}S%e!)`=TeF>d2$b)@Rv8eD78uxG08)Bd?KMC@$keBsJLLW4E5kjnIHa$6IG}CU zmVF|dujBa)=qr4@dzbfQxBIj{)s2&akM&kztBS3Cowsc*e5{UW<@xx(YybVe>vGFa zh<_(Q)7MOklfw)xws$XX%rk(rrOOR~ZfIzeW*KQO%MhGll~@5pinuxMYCv*Xme~+l zgsSlo2|cr2C!TuZ+iXUbswNtlD40Q+pIH!W`1-rM!SGA!OsvmU*(91>~2@7ZD&? z!13_;HpQ~)64Ba2VbSw?WiHC2067L0vcF&ITA$fWC9)ByI~f^V$;P=Io4mXu`w1u4 zWf1h8?i`16I6OEUJ<%{9&JPW8-B~xI&(ko~E9qp%R8(>Co;LvUh-Z+~QOHv#*`)+8@3B-b^Nd`N zXluKivV<4e8XdDGP-UH&mib0HysB*SL{#I8BBu$ToYr!~@nJ+@QmBWWu2bHNB=e~{ zWjl$_FiC!nm#qN4jz{8SIkvCYhnc+1LoNOdJV1YaEe|M{ujS$JJ+mzP>3gmVAL$?f z(wQ9qVo?@hpp@>P0;&jca%h=++r5lrBuY40rZ>WX)&ZJaG=mXdXql%AK0+&&$+=LW z%ALdyzCGdX0lnmFz#{Sq8Sl(y32o}71hgOEtlT&7WxVBy2xVFTUw3I;c5d(Jr&cZyF{%7TXft@1b_8_rUCn)0MumB$%AXAgpC7jvz1w$>@ZN1;D( zUGPXpQnPWu5@EZF3}{gQ0n&U{0t5v3^lx*(ayK;9BR4%!W=B3y$Wr9TBC3&B0t=Fj z0ZoyBoL<^eS>WUlHTo!#0DODQBZfkMMrB@l1Ze(I%3^L)HYl9T!p65}deAw!U^}y^ zMxVPf`1bMu*5MlgJEvx#?T)_iG%im_0L8LVHRKXcOe%V>)qfbRt_?)w~(Vw=p&?H^!wjR>J5H8x!AH6QNQGnq4 zK|3MeW>89QAOup`=idwpX-k=UHgG-a3~}+Gb%b}cXd)^MWS+q~ge>nPFBOhvRIBJx zbfQwRY*fjZ2jCBK8e=6@(ASNEV5Z9g1p@Gx2cY|iO<`jOKCeE5wc_-^PWF6gp@-8~ z_+)vI98U<`3ko62!Dx>zL6)(WGj%yM0v-Q~BG|d8lD?pU{`PHi4y}|e5)Dqs#x$tN z#xyt~8`Gd78`Gd7o6k4aWn&tgv9;!wPuRL<4;#RC2J+8?;x>O5V8bTo<*fz;P%W$ z!c&SMfX2Is5Vj14zKj#`vefnY!dQ{TEM{P6GDlt3XE4kWnmHTXyc}sKaJRkAY}{f@8UJsPt5CNKa#e=_@qQ-`2JtQ+3&d1{K*#8dPL+E44TD zK}EJwpCP@swRY2Y*m`IyqSp}&!?Yj7w*M=KZ)Iiz#P4hr*vu4-bdUhoUW7yx=@mu! zzU11Z7ieZEm!_JN%g&_ECLl*;7VKCq&$}tXlngp)E4OF*25nCVfKeOYip*-00_6E| zA7?v~g>?`fL2WFy_jVWEf&(<)x84T1KS*HJO_9%ZlI3&y4;?-&0F7;+kwBKk?Kbo9 zsL|^^rY{_;q$e5>9|eFKz21vXA2P^*-efgC20$n1EBXv=HPi>hXE1q?KBa6meNNI7 zpVHRcFRCsB+jm|U0ks@(LZs!{KjjlLGz0@Z2TB5l@bws|{GjrwOmVAIfDv^{F2Ep` z^@Ja3MzRFgJI1+uf`W5nCSh=cRFJNe+45x(UZ9{qOHj%%M7;rvW|cNT-vo&Wzw-$o z6ww{Le9~{i*V~2pM9)J2mH@}=W!_RQZEAU+a@ka+SN+3)kE!kZqwc^gk5@6@HGs}M zWm!B32LZU86#0kT6FCR3Pbxr=BckCX!eGf0!H`dlrsVS$&2S6>8+8qM;Q?c6QZ)Ygiemq9vjM&y63GqWL> zuk~57ORsX-@~Ife+Li|s0qZjANWW#-6sk0J`b49E69bx2gJwLzID>K@BVv0*+xNCv z18Yp)yqi8y9DqK(b;P|;anzt01)%Hmd(f<%WCPnY2?0RUVAP~{wZ$F^V72$bo&UBc zY^=B(m)%paFX=PQU?K@ao~X#i`AA-s4Cp~GiIhnRKYm^4Yf?P&1+_U3q(w#}13QCU zlR-`aF5x583=|nzKwz^W!-!Uq#CT5RYkPyXWZ*}Y#b({;>xi33@-ut5<{b#AG-6W_ z>0z%N4=S&FZMa)cnfeB3G8n~Hd9cW`J_P7@KY4jcz$};Sh*|!UCzn8zcC<3>il{a= z`3O%Hz$N15(IB=Gd9!Y^ybe}v;4H8S#pOhU0jbcavjJ#EBTR4J;qm<=?iN#w8=yVx zv3Y&M^YaTJ9=&iqI6h z7;vFr=5%8lySlmEzNbm79Q^Vtz&__zIwVga=4MPYbat+pe(CnoXo{P6AMo>k{5#$+ zCK$&KUE5;wI>)5x(f#p??U8^jx800qxc%@RAAXu+`^RT=n*-K;LbYZ*!_E72Z};gL z&!at>*$CZsgY|NQW82dTIMu)V@E(sp&9TdEwOyU43;76k1%9F^su?6w8o6v+Psi^opCZaSAm zo=FU-0`pbi3cvtxX`T@r(I94+k5XVFASnk-DRV44SlKe*nhri=D27RB=qC=dt3E&L zX86d|R-!;k&8uFlhYVbj$0ycwBGLOnKhdp3P8KN&+2vOFq)t)Q_;Cul$GA!Gy~ zj4+yx&@{k){fgJ^9$|Kk#mzgsd)Q#T-r~59=(>nLq%k;Z8jQvhOy@JqudfhR8_X9o zv|)s<>kvj0OlLF9uNS~-i>uoyW_L3jpMiFJKx{((B3e>Ej3=1R=D509AgmUc&*x~v z0iC=cGLJDGeF60-llL=7^UCaBd6MAF}gW|RaD_I*raL|&-iYI%dcj<@)@;Xoxjp$!0VIKoA%#Lq5sgLl2x$3PeZyDiEBf&$m&t^j z$T^|vG%TFILn_lDQ<`(_pfbDvR?KWa| zv%unJf_NBX+%yS2eKn9`QNeURlh=2#Sf|B$~C@evtt%7M0$uZybr_A2g zJj=povmCBa1etukaBxyT(hmj}{Zdu*;FvTGo>t1U*BXF(z05!jQnnee*K*T`kXhza z2~Egs8ch9S`OA8M%v9>DHqGtzAukmNqaN;WxAYVQl7XSin0lUw>v{TNJ+qojbm?i1 z9t~zUxA?G#c>Vm0zUy)O;SrBNUE%m;3p7(q#|zBvZgIEhfOy1qy};!6FX(e57QzVA z#R!YL8Jc5{(c}sre*Fm#S2G0kIBwT?{^Jw&qY0*yE6i^1a5wJ}!U4PG6b)MJ53ks@ z9Sy-DTD*-7r*d9pEpEs28RXN>a2e9qIQ_Y(cCqh%`j7Ss_Q-&fK*2{W>ZCa$vauL7 zatOwtD66zr;j8Tp>X~zyJjNqcGuhlBOj-7N^fF^;Q5*oqAo5~bN_tC644p2jqG9VM z1lo+ST$oD$= z0@sXX(GTc4^3v%S84o1g0a(lAO<_JxI%AS=JAk8hRV-xU!Z4lg2Qby$I zht5U3ymZV!>TN}|*=oAfb$qqUNRMo1TIxc;2R0!@a!LRwPs{UpCkJ2<$ANWNh28<8 z%HLozUEucq0Utg*;&$F(zx<5nmsjk&Y_leyL4%O=at8Sx;~_^Fs0RZy$^r^-fSj3wMi2@ZDC#MpwI|?^mgX5a zlr|PXB_rHqGY3~`OU4BJ3{)nI=&a3axybnm2<6_(G>Sgr#eZf+1pQ%omw%oi3)xha(J}&xW8u?pSpERDFZ+_c#!JJwWRJ$Q7 zCYu5VHZ@@3m{se+C&5qXAoaBUSjyrji2|*)szt@@U~CgogFMpu$HqbGl{EPK zNaI`V5rQimJmMriKt9D^6PKl?IkUB`yP$oG!*Pf9%P0Kt-~R{C>s>l0Hon45KSCJa z;qh^X>sgD*%^Y*|*!4;OfEjLXKA`!xJB;RYOlAk{Ry(wzNe6N@cNh_4gUMuu`FMnJ z1B?(bYQ~t38|?E~>Jign-k9Ihm$#~A!${5>S`O9hD+aN%ec2#^jB|p9>ka5DH2NH8 zh5k)G6rA|$`vDEOcEd?0o5@ zL6J-X41YX9d`x5ux;YUohwWtZGmq}v30PAY*M<+%U=AOJ~3K~&mKCovfUxQLs_ zRduIRY&nw|=rq}m)|Na5jizxykj++%$ zn>E(W2;IEHetSUM_rQLK{bqxBIG}B5(u2UDOU(34kUl{7vFSpa1SmA08abz&%h zZIG9%$YwOTy-2?K17IfC<(8iz?>^Bq?xG1C5Rn*A2~N-*PCwROP?ZIh0Sz zf+38TF)*MBkeE&S2s8~Sa)pE$(4}~v>Jh_?GWHQ>S;EOVH5 zju84I_RBALdR+hkLH@p>oy@RZk8s=^(WAj;Jx06hF>3Y*agX_I4D?6rH%q*{EU^nC zOjl#Hs|~jM1DfY2^oK3NZiRK5AJ>WuAVbhX2|yUqoC7TxSH5izS_lT|{ylj%?fwjRA0MUwB!-;554OTjDy)JMjN+#e#pe2+?7h&mT8fei|j1 zh&{U4qiYlJZ#FB0Fv2JV06qG)LmPVld+e5JA_9QT5{O*@npZ5J#|iim5xX`W>;({( z0J@lHRFK5-3w&fqU{O|NH%`VPcZ=0 zMkDpB(D2RIWuE;EG^6zPP;(h6A=JUFD2D z=xhgJLqRnJpkx3a4YDlU9>rNFb&xhg;ONfBcN$mLY@(yr?BMGOXq7-3sxq7JMPZK6 zm<2^A`?h=$-Mdn|C%-a;QyrYSSJ3B=kJh!N=>&f8y$|SPOXD!u1VDN@T^M2LchLSeTj+0{C<}TTX{G&ef=`Wx z`DoOvIy4qRSA6JOuZtY%up~P52Lpjjx`B@bq&mZ8W2gX7jbDO);cHG;{NS}C zJHtgWhXB@`yg-1V%a-yIDfz#ZZ-6e}xqnJI-Ms*mJKnxd<`c!haiFP7U*tlKRN(Kq z=%gYWPZm*ijjp{~^dgdJiUyLXH=&%&tZB$g!IIwfSwSb5C5{@cXNEj7y^K3Zu8yrK zm&jxM|Af`&h0dwD@gH(B9@(?3TrM_D9=(9fL^>wagOt`z@dsF> z2d=q8g%6jD9QFzpDpL}2H$`;@)&f{!Bn-(pG#YpgzM`~G?j&a6(GdmE#D&bt3{z$n zPiuW)^?cNpZQles_7YVd^Me41!%~?H=yI^5D9LgMr~`ybEiTboiYg3r-szcI#+bgd z|D-eqNT~1`Zl(;loYiMIZ4CJsAcg^t@6pHomHC*h-sGbC3@5*HJ>`Fu;QBq+g%00> zz`=k)=SUwuBu5p;AO}}cz9f;a5Vqv<(2n-Dcf3<>VRJA`W&Bz76X}Sz{~PO>p_28 zOq8{WQEh1a^M9lXqW4jQsm*-KI;9Nw8hW}X?Y&7~%KA0*rL12=e@@qePfee%w#Qf4 z>XbgUzCWjr{wew0l5<*n?&*s%Mo_d+AwI%Z1fm`C}*- z@<51=M-Vzyg%JVt7pnHIndJKk@^)+-?DO2?Y50tbj29Gi+F)(y|ovudj zme|6RY#`M`mMATW32@8)!0?g}@bYhiL|UrmF(6~dz4l0D@yk?;kg_~A`hq55uy1=n zUy;pC3^jV9K}ELVYoJc+Gq6=fA5>)f7JbgyT5Wa4)=r;1&SK_iTK(8{q2*`pkmyA| zm7G6WmTpu)u80(3-7pT&r1Nr_O&+|gOODyokno*OIwxJAm`pvd1fw#PO zmL70_zeUf094qaeq%ZpI6uqlc$o4JzoU_${US*54yZN?0lJ1D{EA65BcpBGtnI^^< zLiq7m?Lx~>b6$>6azPPZ41MlW*K%m&^ehO?v=RJDa}OFGlEkJm;H-j6#b;o$d=y(sihN?1 z?NaMc)mNG~HI$E%<~~)~q+M1&|CV60)Q9Vp#~TJlsogt8Uy*_7HTbRYsop!GPwn2D z_CN{5UqSzdK2`d2x}KsxqpRALYn>QFUwd8d!z)2HoW;pQc8;{g>u2`poRoW-jme=g z1ZW%}2IwxN?zAOh`zQrDY6Qs#T_zUk`Ru^a<-7nni<1PD^I#f1xiSw_3K^kNCVcZ& zcu`FeBAl`(#3)6XqB9M!NZJOorPAe8oZvq{u;zaE@&G<^m% zPTB6;z$x44E5Mdz6Fz3^bKnHTH}p9NPA6^kF9fG^wmzq;L$>n2-+5i;NC*99Ktm*F zh5?3i1Py!jpHvpDEd{0QlD5gfc{+CTpiYpSS7Z_6b5DUfiz7LUvSQXtzGg7aQvigLT)6gWK3_+9p5|-xImhSRF7c4Z_wn(zs?ZR?p5ppv zUl;qBaT?q?LP9=W2-eoTPhl!n22UgqL2xqw`=dr|fQwLU7RzP29L&6<;6pxoK=q<) zFRI@UR*1q_)$$IG-he>#HQ9zIvy@hOm~shR-Ga~oHAw%VzOawN&>1%2!^^}dMins1 zfGa0uISpM#Xx=UyEiQH`EsI^1sp<2zwpRKqo6!&Xl=?`Ay9e3jS?C|UE*7bIhhZ>? zy32CvF&}Hg$}~~V4rv~pY7yXwqk6tWSsc%zJ&Q7Urh`SF2o-Q(c>#GG=NzDO84wT} zAokG)SQSVUP}zar0C}RIcfj)XeBD%v8egA(WWH8b(^r@@)+dGOSvFbmTAqk^4Cx;E zo34^`(}Op65DjYdL<7{@F3*USvM~)#(ThD!$i_6N$i_4{A)DpB>asBnDzY(6)m8-! z&e)pg4jC~z;)4JCd#{U~Q&Z(Lc%6N=Q$W4~a`i`~PS>*reLamX3fi)~CW{A_?C@xe zj@WrfHgNZ{$e%;q7+0gnClS^FL@N46%6UnI7C~i+uP#>ufn2U9{U+@RAurfUz7AX= zvoYLkpw61h)LBWc5}hLaF@u3OqgYNU@{#;{kCtkVK1s`;Q7rik>4$u3OEph#`k=z6 zPVf16T~EsR=krZ+wYqBUE2Ti5hCWosVK)|MYn-*a7P_fxWI;-xYPLowI_E#$OBnHm}KETk||gQb_Pj4sH=^Kf|(0* z%-#col;P0-Pje9(mnJwtGg#)!Z>}c@X9M}WuM6#d2F_h(G(#xCOHf{P6Ni=~3YvT3 zw@n22DC0zXQkDsav>AFi@y-AdV*v#4ASeAY*!3I8rjP+m&KKYuh>7UzAAQPdWLgP| zN&u2W!(1;oL^D~0XF#GuepZzCaZ3<(HejGuJ5}njUEClMepxF1I|kS1l1i|n6;WwG zzpXt_1O07n59nRnOkc>#^hAS-Y)pd_vKjqQwo)HdWMdkfkj>NAZ7no7W9zCtDz?Tc zeE`~|N)@9&sPbLc<#KA#Kv=+9BO-E^^dA8lMFZ#b&~sSv#L|=88uBnGePuvh@A*T1YCpKgqKv}RMs{6Tg!}_ ze+D<{mX?h{m(H5s3eoL`Qh+wFy;+?2az_V}BmWj?RfluqhWZClWh5hMJx5>g(QiAz z^ho1X*BTj+Dxe4I$NbxW45k8lMcZx#F#s*2LD@Fb*Fdi#4^GI&G^ohNGy~A9*{UMj z5a5Fi;~%^(^ocx&N|wiwe>2J@Y>4JlO|TqGtsb>6_jSif>M7W88MKULb9p*(2u42T zNU=@ub@U*vF>yduMqaM95K(@YlL5^V*A!U@$U%17Ow9VuZ!no9wJ_AsCl>n6&BXFvL9cbFUU^Sw26f($r%Hd~-hd|p z^@(I*gZfVl5lxgYufPQQf$K7$mbFx$6^n6FxG;)*Sxg>wE&E-rK))hcJ*bmr(*~pB zT}}mr9N*Yid!peX0`ZP+Mlr~x2#P{Q!!C+;NCUsz3|5Z@wwh4?_MoZYq``ZQF9B1? zU_`(Mi6Hd6S~8%5YxaOB!sG@+{=3Q-04*)7k2Bw1mU~fas|{2=YOm_~zS5gC0oG^k z3Ju1yE8N{b;_=}Mv+)=R9S*wvg6=O@mR>U^E`1?>cl{ zC!J1)U;_p6fklufhQGD?edB!CswdfV3PKjwy>Xp zVZFh@E;Q*|<7+xth>$iJtnXp0ZMp~j4%r3Vt_<;&$y4O2MM^N>R)7u-n#l||?>^u! z|Mm+$+}>b1ZV+RO-S!pDafkJ4gJTbLG-5jC}ff>nMi-T8@(Hy{CiC6-V2G)~F?em)nSl zI0|^&LVOA&eR{XfO(C{KOvUx;B67AfOpFm`tWv+}`2-ae;Oe(QS8V zBhZXT=@nTqqHog|tU?GFjmDl=*QH2rJi<7PGOG0G+7?}xwuKa632=D^RzOpD%d3Jry6~xA{v?erjrSN{pDBuumAjCST2|N```bD-#`9=!(k700!T`( z%b$_PKSu^O(I>~!=pht!L_K#;oZiaCo}n3`i9Pn4SN!qY6PDXOj>iKIhaPb>!|nY$ zynAb!+g@9L5t0yO&>&Fu5k<#EUvC`b3Mjr8i3fL zk3Hgej@!FCJUrZDF&zW#0qd71ysY*JS9f^0zr}ny295`;UOwabb%TC-gNMgQ+$<)T z1fV_av3mZ5r{`Dfjy(jDD5*vEvZG9CnQF!|kn%Q0$Ps7W#A14(;nh$xYH)RZjbDEL z89)F0Ge+YP0s?;f_|YOZ9mXj*alrW}VB6(DjU6(;4d@sxDv*PE0CXu~tCRE|TO0r6 zZ1837teR{o=Qi_jSC0%CkfM!nSR7n}tA)-Xd#>%sCCIh6cbJ|bD;hQWDGb2n)g?FS zMs*pJG}58mh_oLmv^TakABQ0I3ci_lVyQ3+i<5%m%S(z=`s#sWktvYQGMVc!0YW4h zIFeU_-%WJ6n-etYH)yN{#L%Pf4%lt>*c@)Km_OpDU#Ga*Eb;R6jMsR?`0hP^`S)M& z)58N6lQCj@#PWKM$@mHF%^g1cv23D@%tZtpzk_AsND6fQFr~i_!rt!8cgyYfF_X%Fl6>O*N!<(8ZJ2p(U?X=L`gGA z0xxMlFk5h>C6R$eXax_pe-SAqvf=Rw1B1fBDOxaX)94w1b+%QKFF9uU8l+gI(~bZ? ze=GJnwX8L(k*5go>oE(`*9@PXDKCw)fV!<8#B!_K3K{_5B^D-3o^$VtO@2H@`+Rp5yUuig;M!^XDxN z`vbPy7L)r&{QT2B=A#Kh*CS5nxOw*;?Ge~^6U-YRwp%RM8?0ALysXyPAEPGshX_Oy zo%A(nNHEP&wBXf zrF~V(FJV(Xf~6`g&%`_k1&j%QBv5gnZnCpI2VaGkJ~C;f=FkSNmXxWGd4fp-1_bo^ zCFD`VS^zFuj?UI;GAkoEtEc&(f`%hFmuupF>^tmMPx#dLIBvJt++X45<_h!spYR@Q zG>bbtEUqy|kHeuykAQLCVB9nap~JD;VY~W_|NiIicxfki|Ce7ed4G-hZ3o0D=92^V zn;j0*38n~W##3C4=D2EFJU@TLKmYNB)xO8*`aS;R=NsHFt}#Nyao-{~4bY7+3O%~^ zfL+(283BEaX&IaI%o=wSQt&c;un8gNfyEr<(q5z5U~Ox_QJS~0$+nLX$Kw&p{_A3(#w!dT6_jN@im^Jh14Pbe^~fQ$o;k8acLr!>#0Oso z_Q96Kqb6^#NF;ME+tV^yu6lqqzS_0QBeTe4yfimZmXSsFBT(0wx?I}Bw(jk3wux=1 zXxvr-S7abjJHhbD=_ZjG>7hCY&-!lrGojUDc9Z*g^dhxb?4 zn8hOwn+^8u0iA%5fVVqK$9@3tIY1Qkm_BG9`g&D*09_w(*zfUqzsLXnum6p||Lt%1 z{PdKTo(hE4ZX^2ye-5Tya-$$gEAP`!mjH%2vkXRk&5C?aFObf3?#UYCKM2qmAT&xz zysMyz^kwYyFpclbShP(-NsPpTApN78(5xCoKBR8AUgr-nW_8) zUyY`GJ&wBE;OEFq>J7lUBA>{pX6FvA>mfW8>~j1jGrC1X2W5g#6}F`rBkMk7qG78r#+w)+i^=rFk+W1OSPh!$P9#o^du+V$xA9x?VQ zdTNi@ELYfV-r@1r8%+8mUVi(G?Rt;VZI9Te9NzxWV0Cwc#r@Ct>-ZMO?E%}}9_?X= z*WC=$9*Cg_(4p&gICMS2XpZ@4gh>-H3IQXGFddIE8T*HWEaGDDmR^O%b-cO4W_0v! zjG7}#+dOpouETn>#>bDpW4T)5?|=V0e*gFp$Kyf$I^>Ai0Ms_%{sc_BB&f*%MK+p6 zUR=jHQ7~ZQ*Rv=XlCv1LMQ)CY1T;L5q8RvmCd~Rcx(01$i1eZxKL6$&-svj19KCFu zuphpaBq-L)Ue}-=+XDs;DPVC%F_~e9JFE3rKITLB-P=pDUIRV6#cUjqUD90_X3>)6 z)O9Ma<}c4*ymN$RJjc!bJN)$XBd+IDG$EjE57;a};pORfth+08lL>x)xW?V%0x`DO zts1oZBli0}wi{r-J)(_0&_^7%ORUzf*z9Jw!UpTj8tdHwv)vxMO~7uw#%i?%W)pmP zxWk9*dvv=EmY;sdr_~pHnN9Hi;TrdkS3o>qyK2xLBf8N&K74q@^>iRtL++_ zednT6o&0LRmO9t~hHX;JBc5Jn@}e&Z*p9~|KK}L_cAG6$>m|NCeZk>ygdMM<-Uyt&}Drs=?qB+>RUysiEB6L{aLCK%!c<2_B?%xvio+2^~)3f z7#;CC86$*+1$x)1kou^VtaKTI_dAJU_i)wQtc&|L6e%Phk`5_=CI92{ zHYF`=d8JO8+XT{sPcDf}^`t$CeufcBpbi-t+dU?dy;FBR%>W9*{#{_^zJN_uI{Qg` zB8dZ1_*Hn6K^IkCm%Ge*SkTJZGS$?jUj~~3GK15=wEn@He9NzqOZyy23&NR$skTrd z3dgH?jK6n^-gN8_TfBZbVzc~&M#qmGu#K_B@@bFF%V#uA&i};@-7zhf*Q)?@J^H@S zlR=B!@)^HxUJ+tia&|p{*A|Br(xKc491eSIUO(r|o{Ll=8syT$7H6UL298a>)$ zhuAE!eEtJXE|2+9q^^%TCW@hFTJt^bLz|0f!mQkr{+B)r6&{G_+K9e8*hF#$KpPrY z0>&2l;k&L2MQW^g)QGAf1IK{$&ao!Y%r1(FC&7(T4 zaIHam1Zdh9Nw_rgs}cs94xEuT3$m5mK~IW27^EcO%uiuNXPY6tCBLo3zCB{!`9Q+z zM)X~au4@TG$@ZP;D8seyTg0C193ny=(8(t{GI$^NEn`g9rA5~rA>8zS-(x?hThR@c zbCeuy8C4CBwpjXwX{v1(H2gT2$wT0GYHq1rC9;0wb=j{;xrhajk*u2nGMEE&85-0P z^30^>BF&i=b;okcFd7jVZv5#0p>-~v08N07V-fp{otz_(pz?XJp?XpAM|B+lb=D&= z4=D>KSVWX_%4)w#kSU(!fGpUBP%Iew-^{Oa+hPU# z_Y{p~GRk^pLy<9IU(_YCzZZ+~fI&ng^g7;Q2VQjg(EkkEOMIhtm)wn|4Su8Y0S+Y4U+M%K8N!xmB*9pb#|fVn}%NJpE8AeGgC zPXQ+aF}*hdMMzQ2p*cCkNmK{#;5r_b%A7j@03ZNKL_t&()3{f?!f{xn<(>z!Y;5M? z1YwRO71=e}CL&K5+Rvp9pa5`gA{odWNxrOH5GB>YBudRw0QC(!Ozj1dvVKp^714Ed2X5h>$PY_@k%Jy|HMU2^Jt)8d%`joPXJf7amCNv;E=U%9Z6Z#Co=ZZccKBcYM zRyd`Pj@O`|&$;sNy)Fl8VuO$YBYN>IawCrxr-aN_U+qV}7YcO* z4*=C;aPJhoqbzi-=o9rPZ_v=aQ}oUXLwTz9FsaVl>J3|0ZB^Kshn7pV{NQ!D53c~j zoK>;R@gOH0 zv(;&R0@*&+h*8+S(5JL@O`o#wYC+ zDJ)J&IVq6C5EoZg=9+{N1C>MzL~HY+pKWwkGZ0rwlt*JSz)KmFsf_C-;2V??yn<(F z;Vi%sGT+`$5T+N+x^CJJR7^65;FqB?cSq>*!p*MQeD0&3ESizt2XYSm6%c*iAeyLL zzw%onc-}30gT@SKHjzz+I!&tQUb9owv-5T~o@~$vJ#>=f$cFn!cIfkpeApg_^g|pd zjY_N%aLz>gTx zw?7@&n6v1c8C*1$EamD&<59|E%YOh)W^x&a@&NL)n<@{iADNfXi=*;%tUR14LjIqD z7Jn?4Kk{LY>760b&mGWmP11|(|d-zB~O8wz|wnk-2ue@90zHZr|f za9Vomz3AFYU9O71VGC)G_D;3Qf+pMA>y?0BkA~=kO)%>b`kSwd9_f(ZVo4k1=DgWb z8p`UjEY~w2iSUmZ$r3Q5zAWn?8vxKqB*52Gk!ab$OQhN|)Q>#t5c5p|Q^JAXD2C_o z;CNUlZ@EFu-IySdB(cNV{Es^@Ym6NamPLnH4lqjflv0)nY2R3}P?l3Q&{R9a=H=u# z9B*g^vvH{WZW^79LlbBW1zIx0e&_xJs4t~{F4N@`(KggxqwO`r_L^Y)3t4JBe0u>6 zwWq&@e{Qd-=%zdxU&iCLrf=C^O_p!AUo*5{DN9N~Ib9qr73 z^iF!_DS{}vN6$mb1e6T#%>)8AqAT$ZYELM?^3_NTq@^A@cb0&zbP; zrO2R{ds$cMmp;pC2b;+`bP@{s|1krdr5iFRG>ti?%nVIqz#;rIt*ss+g0v_91=8Cx za(#WYO?Xf}Yp_s75+*S7^@zi2}Am zfTj&Pcq3It_$Rq%QhK|6gnJp-HdUA)f;6h!cLtF*tK_7CPlMMXU^=`|Vq#tG!4I$7C%?44 zD8E+oBf2P#GiZz-Z3Vo)_j>$y6gX`Nw<6XEbM8h85OInkLNTOI_M2`wf4}?alg`P| z?IYlszoQ}`?LiVn_wC0$L=nJUk+fcrL*Ty)nw|%vUX^E58AZ#XLjev(lm`G!O+pDL zCP*GlJ>RKd6g44^-<7wQ0Zu@@!?vxV1LQ(;HOx81B}C8TjW#ZR9&RQ7hA( z2!al9{j^=lM9Wcs7Fsasgc)nYi=qR-DliV8L%WWC4fxj2KnU%j+E)DornPDRc>Z<& zx@}d?2KrSh#LGZG%9rwXfEMeY^ho-MI`EME0xc3G2o*w%v7jQ*e~;^tH{V(rb-*Y? zb;Dwm8HqxKVpgJ&=$#O;CJ{x9TEwWt1XJ+S_jI0)C~%_E1w##jPp5C8uJma=Uv|Hq zjWT-pJ>4ld13(pi!w=1oiNX0H3g6yLAL?VtcxN#~%k#rN6~aLK^}5 zEjDdd%_j73HvHyRFO^(E`|4Y#j^a3Rwo=Z9`ntV+`DwYRJv@rvE)8FhMY2I!w`YV% zH8Q{NdL&RYK{`%ANu02^auQ>w`0vAps)40bS4C)a`iPbq0Yj9~*}Mr+r9IT1N89N6 zbYz0_j0fn|z!%=3BVH}Er}#)&jp7U*QdZ+S;3{Q=2ivJj1X7!{7m7WAt+C%AgARy< zeu>i#C#y8VO&NUn3hEw!Q{5E06!6-;%I=pigYA|Hms;7hF&%QKT#PofA$E=OK)Hi; zc(9x-+~^g{=!r}K3=R6OSS6sk(y2n70%1}{tOH~J-$ z)QQzp1Zaf?6L|IKNQQFpodTq}!LV`}4Md_;1zqu*O@(XitfM<`N=tfu$Tux-jGs=juVs@#|A zOFve|bPpKf=P&UiZ6B=PapGYS;y^!%Jd_NQ-yW*3XcK$Zhx+)EJRa)f0a<^sj|1&+K+a!tJ=!T9-J~KYefNdpedSer3wp}r3PpgI z`FjXq0sID1u{}M)+Hpxg4ur&*<0Ll0D&=Jic5mxxlDAW|gq5TemWai?HUy)0f4i%# zVWn6iAk(RC^-ZXsluO2~b1wj{Z%@fHluO1Ts&X2*d2P@Nl0+xU z!AV=4Y7aeu(0vdjDqbuXBd3S^i7K$)&mQO;=qHVw4)>GH!aDuLNb+HRQdu4DNB?xF zpTCu#L$cPYX1-KM|E6;Tzw>&e>8C<+9bX!?2S{M#fKeWPhEEwr_+~;M(qA%NlY6=k zFVGMdYVr~s@J6pOKxOmj5;+H%3h>48byUXOS0s**`bnd$!~KM* z^B4HpmzAzhC(_pRpc?!7>+$xfiQ-Bw#fU)?6tnww_7otrA$+-9Xg{fU_@1tR_)(|B z<@SI!4LOnpowTi(`Ed&c0qysA_*?jRhzHsJ;5UZ&GM!D#S|L>*$<_(AKgx9L?eRwV zVgE5Uv)9K%yht;9Pc|Gb7EK~}{V&&fs2#GLA}=WN2>h*_M%y$U*~QNSTVkGiHC|z^ z7F!qUoO2gBCr008E#^|weRK+tI(=!ObZL`1UGW{_hXb^AU=a?`rpK{cFSCRl9bGK{ z+UpS?ld{SKN*EorPofct&?MQdqXJ(f3?c#z+WQ+3tG}lV`p`Y=-A$a;s#VV*`54Y8 zQ%574HGR#_vbU6xd+vrCVXaIW9QODyr0)d?%^mJH3MyiyHu5x+Xi~XI_-T=SzoC&B zwTVMH)UTLvt~YL&Bm=UNAg5E~dd z`*k$%{mpguM)uza)gJMQf=;;a|7w9-pD!}UIp52$15j zB9KO!wfE}+T2dyE=IFP+PZ>)6)+gPmY}(cKnm$oL)C1)qlf0Ns4H*srxa8?#A2J=@ zmrHcRhronHqe1aB?lIsZl)ZX~>VJWsq+X{^I*#tU+T8T- zay{}B1zlKhmjH{PNQ&d`bJz>x5y+*K(&$0ja$QMelkTpT*1BNmY&1rYHV|eyz+_Yo z$gd^_up!-6f()$aZR)`zBZp}Tz~Xr|9UkJ;6+$i}X;0gDj4HcRzP%ipCCEEIRbhkP z?QN7jz=v~UR(GPt*0J8IaTC7vJ3r&%5`mFJt9j|@>pfek{NsRVW0gHu8AZQ`$t}Cp zoY`{0u5PJzTbA<`Wt))~In*sSGZ>RoTg=c$ZJllpRDZ;ZMZMkeW58F~N`iK46m2N|$v%-X=E zy#zFDgfNYCLR&E8Zd7SgNDqAkAx2Nvjf#YlWIKkGA-%c}63f5QlLnzAya~UFj5U$Q zD!QW<3%@Gfi69;u4i1Izh?ufe^;tHP#z(65>%^CW==-GQa0@O?U9nzFx%>E$+0MOa zrLhIWqbpAPHQUvSZ8Kmv9@DoKHV0^+7;^ILDW_*g6uTwM=?&L6x6GC$jXYEpj9kDv zc#SloQTMgS>_%8$s_#HcxAEor1kg&Ozc1BK&X@D$y3r#C`VrcU{FceSOb?#F^LpF| z+KCFRLCNWF7YN*yOmo7$2tdINh>3qGLQDEk9_~<5+Z&2({?*Tr240egCq___4QWqm zktE4O839VwZ{Rh;Cju_rXspXycRNPXugI*m2|uFFdw{7zq2I-?7ot3b9r#O}LcrBk(r$1x@5Oovskr^jxMboQ>|zD95{3|6zX8MKyl+^=Q-206Sf~@Ine%p(LP6!3I)5lebVE z0y#r`AY9r@=@IYXzjez+4C%Z+kC?QVmaMCgJYc_Zq7p#^!mWL< z)=okIf<%6CAFx&;tI#Bh(}cR|-a!)#FY;!RG1IxlAkS8m*YT?XN9Yd65lI_r+O1dm z^lsW%ZE#tpNOfJ`7z?0kIV%QE~O5z7im{ zq8s}QDP7uvKU4i2%+-lxNmQs4_Ert0GQ^l?Qig_#MASeEZw`E= zPrEOJFGOged>-im@Kix4~mo%+aQ?lp~#r>rqq4R+efRp!V9;ma|wyrO{C7_qq;Q(#jI)`XW z>OVl6)ZqYa0sJyiyXYLh7SQ0aPrj9@mTeXKyX)Y|8hg_ypCpC7=_8*{U0M$0+Ty}Nq$BX4UA!o?>F^OpEs4Op@Drw%=r)P%DnklLramVTTezQo zkA{~y!?g`fy<<6_a{uuY^Ib_*H`H}QF)GON9P$i|Av2jfI8j4XYiX@JmDgnZG$q$w zBy^aMZk>+u{%W25MUwqGdl>RJ_oM3nN*xX4DV>%WuSJLVdvNU$b86yw!a+HaVg%GfGoa^d_92 zShj_6@7=I1O>HpkfTN=eo<7~t+B>GJEloqjRN=!!T}|?7K|Tk5d#FC7g6Y&hluv{7 zw{AY{VEs-%>50T?edRwpys9rrd)}Gq{=O2`9&t-Y>Q@gPaHs(dIv}SMxLYnsJbR$R z3&)jnEl3GtR9y{d)&rVR3mMX;?pHo_ta<>Igzt;KY#zZ~PJvg@~kmEo#zJ*EK zjP~m~f1`{|@h{vZZOfh3Qdbpi1H;J)lku3gg=)8Ae!rwL1A5hh>D`nqL(Y#U^!t6v zx?;PwY&RR$Tg&L^l<{c7+1Uw;#nR=~jM9lNNTI&NfpQAD{Qckve?y($+D{rq{@?3I zk4$p^D*+82d!HKKEJ8FQAT0(2)^DN61l2X1UC(<- zz4FD2}4t|vo-BKRI43jRZ!J6 z+i`)dmMqsL^}#8&`W(Di=kvkKOVP5w=n*{!<;JL-%J7kUt@VJWjn_|(Oswa+5JN}}HMD14-x($XsZ}wM1`X1fQBP(=8Q(-V zsqflyy$gtFIz%p-UTvL8UH@D6@g-w5UrNXZB~Q|TO3@Pb9qH9z0StLNi&azeL;VKu zihYMR6F_(WwEi~(v;vNP57geYk{DsR>5m6oKdm;MI!W~QKpktqv^Cr19oL%$x4H2r zJs7NQXdCB3GTTzy7VMVYW`Qwnh$3lQT5Bk`D;Al{8@EkE-9|?u!hA3%s1Ucy>x1=^ zeJQcr!}ZfK)be?_ew3B&Cy8{`H{H+P-n^tAlD?p0OJ8|C(n5g=D}~nohsD3%%!UX} zcny)uUV=y6%c3DA(@?3R9i)jg9BR*^QC$F+ zXu28~H&j&ziQaVR>C|D5w*5LBpiSy%+v0we zJcF&Zv~5FMx3qZ+t;>BhL_uUWqcv`IRQPlr+%xQjk{LtmkCF}LOn6awP=*Xc8}_hA zf7SZJvU=HgkZfrqV0rplRTm9bxgwN@4p^r`e~gJRAt3< zI^+8GmVU1n6z##&H7ssr((1cufEuSd=E3%&f=YX$Ci{J)CuBGSbRY*lJWM4oeR}1V zJvU*~tL7`vho55a^B{m3a$ErrLvu;rwpFW~f^i`t6Yr>>sq}BGv*I;1eG!bcJQK-_ zOa%B0iQJ`qjC=Q_drO)Q7d$u{2Wa$^QGx@tiRv(o;s@hwWAt-8?QYx?(!;uKgjfd(Zgh^^}1xy2H zz9UpQsq5iGDy>#aUcY|LZd-cv6=_)FTnqL#e1@riEEga5QBeYExwO9N)CawRl>_~R zg^Dlq6CVZI?Eg2(qFVc_Vw$_gaLaw#C{ zGW+uE)(NSegMB$HzluZ{mdlnFM( zS6z>~`dQWuv&EKml_9&hLo!r}XwMLT<-Uk5wKMg*0ePC>!4-B;(m#Zda@)ilh%Vr4 z)Z<1vN!H!$=i`J04;mg2qvFNd;`r<#^j2VPyBgthXjXj(pNH|4^k)GemF`pA7?oR5 zQJnDV+Y-o?ev(1~h@qD?46>Rcw-}p&&!_$^5kQ<$J@i}G$dm~k_$`UhJi!lvT~c4k z=!<}|SEg(9UusXuSgA~OOzD->S57uTv;*z!M^p#ue6gJR%znAOXc`}E1l{|6fCi8H zK)dyN>i- zy6-mW-;YHY1lMMmWO_Q{M6?Y?VS!8lLPa`~#8)jMSr;Kmq8-O-i%d5{F3+Zf1!GN6 z%D34nuPSu||9J2cW?O%{t9AK2++v3jKtaY(n`(3_p+llfb;*!l3bIu~7K1nHerPFL z#IMx}J}-bE5p)!wNgMGu)tmYp^h`<`vsWjy+G#^THPRXgirgQl6F3&W6xjiww3Hr_ zU$?&Kfug=>3MXvoh)&_CoU$8$LouExZ8Y;xH{A8~ ziavDb7LlG9k%WeI4juC3dc;Vri}HY&tJUSwshhtk z#XAXD4Jp2W|KoxZn;4q0YG;D#Hd%d+RP6x+2q?zrG8$Ag=IFJRhx3VvaI{VsNDn%} z2o+sIOV^9a)U4x0r=TqW03ZNKL_t)YrIHPbdte}vsf1PiC`sxY3gd#ZhM!E3b*LbP z;}7aL6N|VI8m!Kvx_WsA)@Bh5J@6Wv1sbG1d00M9p9TB~b+h2y2z5;;XM*D-85^B% zmGZG}1kU}snW9CvXORB(+e2m%nK)qO^fTl3b9L?+RnGc*kyf|Z<@nCReIqFYe^1(R z`^{{0F6uYewg;t5%T2W{i)bFW+)!qIADWVbPius>52a&%=k-XS7V}{`N92D>tyo>@ zrrLlw{%>lK-4E7+cX|P&f{8_2A!$SzLX1=nxDOQ}C6S2~?_M@m(r;cD&=T($rg;KX z9!LZrof1giDMnA-4Ui!pr433HP62B4o*fjKUMkeKYTX`p~1u~3n-B<`<5`oZK z2S{0lFXI-4{Cjr%(dDfJRg=Y+Ct2(J(;JH-pDu0G23mjb^=PY~VcURMg9 zmgvN5wyPilc*??pmr!wp6j6wl1bEsDyI*6$(0doqR0I|iQK7uXhA>9qiwS{JiXQ?v z5%^-{7ZthtfcD;Lw&Al?v>A+X%A8IEg9&vw+y}HSh;^^{yBoI*>myPne}c0QEy;>m zO9nQh3HxUZa_G0)0%Mc0N6AIobvjN;<`0$YW~4*Z^U&|82w%YD;Wi@)`|X4%Eqv@W zr055$n&|fHr-NC}XgFjr0oyJl(dbiEHCdifBHD~Msnlb({RUdIlekUZ?J#i{l*as1r zwq@ciO_)aOh%2CE_^k38(vLR|Kf4GKokTLpE_^+D8Z7aD1kg=7g>*c&NTh2#4{8X* z&^0!rw799I7IWKTX|RpOwvE5*pID$fEhOfJRMZmR)}?Ygq*K6T_;wLV>8Bi%+= z73Ue9N)S!xn+WCsr=x6*cc>Pd&?%UezJxip73?F>V=3~SUcblDXvFE+8ROBA)p|p( zS1=q5Sg$waMM0h!Ztw1?>zdQ^OY$P)*50zbt?2b~j*d?`Jvm{va1Y{dcQu24pQGbb z&M(eb%$MA?Q;ttgI6FIGx!PcD%jLy6#uzr69oIM4Z13g_Mq{p?JYg~!lVygRn>$wP zPpo%Uh{!@rAUW~YQr9hwULpI+>yeN1j+aD7F13YT=H%@0M-3Ugc%G( zCz`cqPZGXS21%fXH$p+D1Q-cCCie)C70M*}$RHY(BlKIwlfc@B*a-{NWO2$V7Hkcz z2VZxlsLHFF`go;_zY9I-8HS_-PRVVL(hK0W2h)l&w8FKO&p19h;mOk{JbijaQS?}^)~q%= zX0rvmUBl#f%<1Vl7|Z>1fysNEoVfGiR;w*0%lYQ{GtSOVS+CdB4K!_z$@}yNLrzc5 zc=6&Jo;D4e^@d)*$7ZwR{(iw|JmT!^jP16fwU(mjF&K?GIhrt@j4{AuJYuz}7z{^b z`3`H^AScJSQxap{bKwu>v46w$XgRg8!Ondpys4>+X*Sl;(SrOe;3d=5Z zrt*j~B}}Hx3cCUK$e5R_L|Ltov~F~4G0!XGqV%8x^a)?+@o_&`E6+7{(8{Xs>AW`n z?J(gYNO59BCzcu3WyS5?oIEdh_WT*oo;|~6Im6){*0%JDg3)9`FVC19O(=SOj*m|n zj|QBdpL2P2$=T^CWmPhrEt$6)srWjo}E(^IqUTXgJE)X#ALFj*B_D>1-1@`=rq&dqx6=n$uZgQkJcXj zkVv^S1f}=lWs{Ol5uCxwRE-2ELu6oLoEGvY`oZ`;bP*%5Vb8}QUm+AoUT2SRAAN}`JySZT8Xt!vj+GI2NsN?r3c}wH z2Cn2|voIX~Hy;}KE{DSb{r-UEdIvXCPR~!dy1eB4?40p72T5$@82B$g-R~FR&J7^CcfYedPXj#=DQ7+3YG8 znGO1YTPS!s8Zo{av)%5vyPGi>^yv2o^m=`YJjY}OCa;5o4EFCgZJI0#Mi3Wde*5*P zPZS)?4fp|SE&GV(qm2MO;kSK2NjzE7Unfjb31*k0QqhcxBx01LwJ)9e=tQ*ZOO@?- z^FT`iXt8KZXS(OVr%7zZpOjO-$D=>I!4e}{3k-S%-@m-%hwol89*+X}7(W1Rol!+2 zoe08O*lsrb_4PY`d4J2UY=D$5R5gIYz%X_4Rn6_) zlz;u-zwqhP4eMRa>G9YDyIZ+jES79{C1U7dcSKWUVF3GI~WpS(RAe`gYE)YWyQso{!8Y z(7l}V%y7|zS4Z%4l+ibqtOi<3)*5!T<-Rn$-NEMuwtfuaOQcn#2wqkWtn$ooemdc& zA71j0|K%r+j>i1!zy67vyE$du(l2uQy`DeM&5&goqh3K>H`Gm|wr;oq5|xsS=Ac*b z-ODS!fBBrr(HLuCx7+dQ`i{D8xx6@`sv6#X_{@jT_n0i_<+C#`&Q6&xSA6(%M_%N7 z`|Of$o?kH<4QN}-e6iy7+mF2ebkDA~VS&VmvV_nK^<8-|6&>f9;b=19+n3Kdnv7}M zmfO2~-oAOyYO|qN80Pa0r)Nh@#v`sS&M^jV?q=NH-t*+@f}+T{xH#wJc*1(Kqv#b( zr%R@b4RzD7Ty2=mX3Xac=8HA+bxGS=>bhaIS~FiPSj?B)+}<-B^k|!go7+1+U*D4D z8T0vqu=PSyxV!2#0pRbtBS7CwMIS(~+vvIFzn05S) z>+wL%B!B$)`Q&YQ{96@LAQXT=PfM(t8A%D*-=luhGeT+GSk;lOg}l|M;(rC*v@R z`_ZY3&Yle6n-fD}3{?v?hIQ*6($8Bs&fuhP7#qu34g&+*24*X`HL~qidviy_pqKN* z%PW5T;ag6Qk2yX);rRH7s;YnjL&nwR3Exk~ELK|beQzFVkR2gCKf=S}>C7M$i%GWLKM#KfnIS zhflW@c}`?K%Bl&Yy(=5ue7ND`^*wcC9q6yNe7c#@v@K0z*=$Na-b`ughF#^8 z8(~-_BKK(r_nR^DFz|ndNKJAz!B8@ZeqhfHB_UH9!6kVzb;&d^6!p)H=# zRT7O-9r}W5j;Lj>yGY`&(3S?l*t~#Dw?P%h0fs!wI6oQl#~)tucR&5e^QTX+z+^He z&oln~*SFj+*3?Z)3<`Z-OwlP>skXJ0RmpC*qqQxW$uL<)S=B688?L9zJ!c>rTa+{sIU~kaSxsi4bO*`(=AL}0kxQ3r<~j4lia-7J4U5&5qR45QhSs+9 ziXKHTBik7oBh-s?iPD$Ip{g71@2C9fFK@WLy@xF0*|R6qbq$%}G(rhc%+X{oscrClKWogRLcXw48EW4_q^njFXR<@R1)lyb%0NJK)F?Aap z^Uk*H>L$_2WdEIwsU;VvD8>C9*W>2Y6jteIY5!jm)X;n;fM#Q~7y1#UkZG#~Kg7UG z$^@l>;3*~QO1H+Tg#8{*ViXc<>M*fWHepMMk=0Zu6r+SxJTJN>7mZoXlCe7`R!LU!S*GG;EvevJh zhKg=fY$6b=IY=zCwlLqq=cT2{Tm)t@n6;&6VbEB1z{tRH&ocIr+Bk<%ZpbR#evEPr zhN8$AjYjnQJ*M|lUcY_M_02u6UOngh>=@g^W?Rx)2X6OMxAmYXa_Y*#dQoH)S?+@K z+JvbPC}-vs(3p&VzsJS-8Groo6=$atZtvz;3uRTY+f`Uvo<6x^yW3IZ1sL3F<77-x z6wDV(rn5O0XD3{opJJ_b)6dz`MLw;7f}7^+w1JgvHWCSVC5+Z!i2E1SO0>eK;Gg_G z{q$uBO+|C9Ud)qOkBuwT3iuMg7Z%>4BMR0mU3mS|ndm5Yp;SD(GTN`W9)C-R-aa6r zlaLy3Dgk^XM@0mg4x{^9I=phk0&(MLGH3(^a(T0KZdEO6{TXF6N4-d5BBR1{KpeiI!H1UkXQ zC@)C%_cKh2=*ALz}uDJeu!(V@S&CkEQXH!;zPE6eC zo*C_IVssEVH-4-<+V2`m*%}H1MQ$1G3_W1fT599BOXRMns%}}7hWm|W*%)e(i9{eq z$!5<>BqI})LK}wD7eBxu6=2fuA(V-?!O}LCUAbeu-tzI|M{e)#na$>$ogTZX-*(66 z&o`KF3@4{YWLZv86rrs=%P5M1U0HE+d&kw)1(z4+41KOn)WQwcR~* z-KBr+bTP!kKxI}UI!M(0ZXA`klmMo(Q$UJpV2t(=XD!~Lgq7O}zQH*LMMBy8*IkbT zYD$m-I!O?r`2;m{6h!f!Jr)aPqZg6DR(oGT5QSWa>;h3Pb)-*-w49cweS3Pu+glST z2oF4~j&$9mouCCOe+2Z4oNu09^1~0`a(Z&ibmjo<&%d~63=7u20}Ma?^gX91r~L5! zw=7p{*4rJMUCg}*4uPSpYJPct!>%kD4M$8SV{UHm_{-04SuWQcPX-*1hru{RgkU(H zjQj>l*I>F8NA=??S)gt$)18Zoz8P7bO)|!r+s?5ymbrzpG5lo(U>7M4Jqw3&Y z==TerJbl7H{o_wu-%aWDoEjUGadL9RV9;kaUoxAoX#8`dZUc6P9D|dNM;gLJ9ki5X z#ohgsKmYX&w|7%AzvJCtY1)R{`x(7{!P)5v=VvG6c@99i+kt`8lM_ykCroDxrn5Om zM@OEQ2&!SK85G0ojYK^`RYKte%&ewcwi<;6hB)Di$H2#XnK9vnr<5kYea6$4$w^01 z{R7ZNN@Ca&9xan{N^b&BGPqiKx<>vL*Wvua@VG*5DjmW*DJF&+)bvz(jT zJAVH84S#w4!R5^wXHfUEHGls3Ef&iU-+xP<6$7F`luuq2J z=Jt-;`#Bbu>~9+P^g-RUj#r|*5tryA7p)0JQE+~K!cTwrj^|G=m@n59ML}yVwzaG` z8$Nva%+=)uPp>X1dO2;|FrCdgK0e~~I3{WhE|%jg$Q4t3pNE%b_v!ar@Xs~dV*#&B3L=;gE)s;XgA z*3|y-rQEpOWuCdm8vA)p(a)iUT~)K)RagvzUcs>Mo=T~zhV`zZX)T4x==TeX+)$S8 z2@khNFk>+2W3X(in%%CZYFcG9YK`DPPWrc8kMlC)U%2nHjN}lrQKBg zZg9YJeRB`|h1F)q$4_qWP^}J|s$0wbe8r#s`j*vd!+1R4`ud)-t^=WZ;+`gDozoZ$ z20VRw#bh#~ZQP?|{eF+(aL8`EV^@~p5J3#|i#}z!W4+lrL$Y*R53<(|Vo<>G2*xAB z{mSxw3pXn$D_GWW*TC9B6FeK}@I%rN>utr)zr4p-I5|Dx_~eB1(=$fnA^o14_TApi z*lczz*IU+Gcji}B)qo|-+$w0@G=9gu#A)5o-6iukn=MsUay*$ZdevjK+4AwzHC0u+ zO{L2fo9&kAY{~hXcN`y27!QZc=L^buJ2f^*Ksqd z-sc$MT={5Ak>#A9jCu9)2~V#s$TGw2{hYV&KJ)2zhQ)ApH00@%3wphr>HVC>!qcl$ zM#CZ3H}`zFzUTa8!gt?3$jiyd^e}6y`8Ec=l4xM|ASUP<&I2Y(y5CLQCt9^1dLDv-(f!F3IZag zkdXjoawB?56`{$#FL%peGuv4{62prMC<${#R9L)7mwylcwFc_C;oavc*Y^wRx}mIF zcVtV*xj6N&TbM33tT!Jha>K4_sOmO0=T=7&Kn)JTTDH3#v)PPKpRNJ7H|yDxCk%%J z=8GlQ*Edvk^GoWOds;g{F%SZ&Kh1o42+*o(y-jnvOGiUBk^%vyN6g|}-<*}6xi zYE1H&SdgVwbPKz>=9hQZT;I+)8V`8+@;U$TcYomI^c6*((PFu|ow440CM$Xj2Lqb6 zp{gqSy@I01*la5msp+UXt{=7*R_iUV-@L=xmZQlCtf8!S+}_PuZ+Cq9^qKqllG$v< zu4?$xU*E7TOGbkMpKtH@`01XTyD5vsk|&q$sGqW~xxb(B%iGVa*1PbugLZzBV-w4; zSaOqbG#T*2ci-^$KmEwn)dhKGxSuV^^PJ7LWLMRE^ZbfG{P-Q2G29&8G8&C|`SLl# zL7#v7)6Z;o70;es@Wc1tadmZ0p5;tubFwUFy(=lpnitP6`NI$2Q4~3w?Ut*Hb1pAV z$+C>;Y)R4UQC1ay{KI!Vxx6IL9UVo{yx73VKVVOJjM-qk_P*gPFqBK9HB2~;}!)fCA z(?}~!EmI33&D}-7mDQx=6)!&Lqh<`C2SX)3Q}j{{NDjj*v$gJojH-5_dkk8-(S&QR zwU%w^cE{W9A&SXF%769y1KXx$yWQ~d<2A3}zT@)p+<9cgnLuk>)~gL~-+$uzc1qPW zG2}U^u_n*rgY{+w^Tme0{_>tYbLYdkDO^q6wyENH zCEm{tyXoNN`3Wz+dB*X{G4sWe+!#iqG0&bp;riyD`EtY6c(d$uFEqC`bcDo&?XD3X?6D}^#IePz@vR89;aUKqV+-^&5?`Lc_8%|G8m`uhz zd;XNBJ=k~)000pvNkl^3yK`ZTvK4t#t19P-g{y@!CQCA6}^~D zmo$^81iHM@-9@(X?d*hdvU*k54a?Pv>2%I~v0%{e^TMC0H5v}E29A%8D2jsVWX9e7 zjKzA(YEy;&QZQ07kbIB_xW*c0JIh~g;C2O{7nW(s!DE}{Y%N)}3)XJrktsr|rEXg8 z=4<}#&#zgpH=G=e`1tvj)y_T4UD<}JZZHN$gFd#kY^p80T@|8U+2Y|qRH7ss^mMnY z*_E{l>>Vv(|CZ2gp=zMslq$|}f0uQ|YFEV>gEfTLBqHjkqc!F1{N>I~o4BVZ>V|jk zK7xVoUwzAXGU4R-h&<0ZIy$0h8}9C=e7?THwhg^rpR=$z`lq5RnkaH2WO>1z@PYH4RwVuQ>G0H zY>WvJOVH6II8@sW@I0dOi6MB5NEkL^cG_}1JMP>t0uf`qZ0r>a5>^z1M?F8G==`GW!u1})(Zl$Nq6c~ z8nbDj4?Oz(Ytyvc&ep6pZ+$YnbvZ?R#E%2dvZ`rUYX^2!wRgy_>G0&n55jb?^(&`J zXAaF8Bs$j7dyqo{cUpJUN28+B&K7|@Gh})07J`g_-Z?Y$dp(B30fRxGE&bTZ+Mes%}^<*Q|FXPoM4>jfS+g<-@xhCP!l?<1yA6vK;a(C(E)J zb(@SquSd}<+!>(BE(N0Uf93VaZ>bm?EJ#Ds-vCO9;s9O>SwGErB9F2>kP zMXhnWgYtvnj+3|D(+P_M@AYO!x!ZAmdcv!3UtqDE93Qh-Ea?yWj6AU2-A%cl&e`lL z#$_E=Mnxa`Mb0-*&iUc{Z#h0WVzb_GKbw(f8E0oF4EufV@25aszD?hcRweIn&rlD#5<90wxmDcO}f&d-i{_3bxc45ud(=JS<%6IC}K z4J1eLf5r8PZ<8|7rTJSHHA@s@vMA3N6$<6SlTEK`YN;Z26+yYbdJ96N$o;FU8AEuJ zR~OKNM-TKy!y%5%ultx3^8z^7Bs_~{qVYvQ>uyVtXskP3J(NClNq4uzFGaUi9kyqL z2qmmQTGg(O6eFzyWP5a?1d9LT-x_6v1Cd-W&w2jroIm{diYHfBlx4;HcOUtDeaq>|F-J#7 z?5dL4bk25L^6cq3fBf+kS67#mWy!~n*VJ{*@yRhoUQlG2dly2M)7qA{m0bm*XVx7q z`SBCSlM$z<$Byni$JzyT)9yP7(c#~5J^Eu(&a;{LhH^EA)})^kxREFzehuC@iKvu^ z)k+rKE^(CAmJ%2J3TR4OQo-Nw=pZ(b0EToS9>=6zNv_CR0rJka5XskAg?FKS5sppC z(#u9MBnbJT@I7H$5ld7cd2TQ6N{^9PGBY@+A&Nq6PwbdM{$ss}P^rlpp~%{SE=W{G>aHC*SIIJ2mnilw%ZYRaC2xEkt;o zWhEb_Pv>iXdHo(}d3trl<)u5_cD-@0ts4yn+)t<6-c8x;NEP7P#J zv;rG>CnymZ0zimgl;pQlaHHA`$llQ^Y1>;FhcyhjFmPtt9fB(5UwB z$pV5*(}?^;^8DC&;0-@`HZoG$v~P)WSB9U+Te&y^49$-45f&ooMCCf$>giUP88}gn zkWQPS!^EM5$z{+ZCG|vq^f)Kp0z)Qy2>X4CBF6%&)tcpUNtR_aO~Z6L=YFzaaf&PE|)ab8I$pl?XKkb|Yl z<{9o3;2w8(_e^Il`m(Kc`8VrQ6a9X{VBqPtuw1RUxw++jx}Z|qYtrlM01X}+)WoQb z;E5VqrGOHZg%zeWfC!DrE`QMW8#Og5r{r!d)@EK&Wx9v59I^cFz1W}TC1OgPu0>ml zQHiAcbRZN!RdP=O*HU9lzIX%DZKYmK?PD|I4wUIAcJ_@<)1?5dLxq@2*14CI3na%9 z0r-rr8OwAdln#;VtA@z~#r7JBWy7pMei498IEeIW0JQ2i4aNaola9R;6x>sg8Fa;< z7JZanb)+H?+v^ovoS*RG#dFTjPASWp51&4>T&}pdIA^(Z=en&ny8vQ!)39Exna&oh z*DH*1k3|(l&c*pDCr1;uyB&+w7HeTPUvPIfWxFd~V&52gMaISX87D^*_w>Z48)ox4 zqhX(`%X6%?+}z#s?%hZ3?iUziSgzO1XLDAoH5kLi*$FRRM!Mg<|G=BK?g<(9X{!j;&5O`eAup#?(L79XE8$b zd`4uA`Kd6K?O;%p!QS#pD3m^I?KqIUl+_J&g{I%=Bu>zm((k#|FzzB@nLeY0sRzRZ zkk&9NU=^l^8oshD0roWdMS)XXjo0zs{uYI(N1>A_out0#w)Nq9S-STGyng+To10rU z+a1?8_l!nk#^WJ(w^MHKX8!Cm$H!{3<3%7@_$PCljJ^iAG1)8S8n1cSG z$IZ`ccr)jC9kB6FenZ~-SY{+^0c+oi=>iaHQAR6>1y#1Io6#vNYWILF%2V?c z;80ZC#_cP+s^QItTW;>=6j?^ow$!cV?0CX*wPe0nGo7#8D!K1+Th_e$c*m!kdz!}5 zw3g|7#o(`R$&8beF&UG|h{!0ZldgPv_wrTxSD-G|nJO29G zJ({*H8}|}q>sD&@X}MKx*_EE|+)y``&CYG!j!~=#K&JfvNg}mFR~i6VZ(y?O7TF4K zP7gkigl(HPq(Y>qC^}kd4P>$?V0DX~`eICBDyJ1!fuF#D&W|NTc}isCrmt`C@7I(* zLwWbcApi1(16MR9*;0^B`IKyfC+Glahk8XNA(=1HmI9ivDr(X;4y{@R)0ZgNbimRR z9T$C^L_D&_tr8tkn#{Q8CXIF{X|yA&)mEZW;!x{^eZoZ`BVajqH zJIxH-%~s@jMpL&`t;>bgZOdX)VK!wLt5m(+L+l}E>vgfkQr4ETE`z*wmC)@So40fA z5%v!u{rW5W^&8@)c%v=E6465?777 zxEHNiXpgn6Q0jxZP)-fI@GE8gM9&wBIUgHrA~dTwHa`65L^q)zJND;C1;=gpo#ky%ZL zZ?l!g0(OZ5BAnN$X7^uxJ>uJ>gnwIM*}7=J8f%S>%iIo|{2A)rpSEH&#)KnnTVsv! z$Ddh@HU7@R)Ek?Ag!@24F=;L6ZF$l zD^xhcaCEnkZZNWR;ZrvuMl;U#fgikZG8_S%0F3f!1;5a!syJ4-c6$%N$~(lw zT?wVWf2$70wvw*1=yh)C!;P||O?vkG=+qum`Q7&+ii5!_eMExN0Ha=yMGeF7`OA;C z$hWoD8l~@UuXo^5z5P(6E(qjRrgi_=P}sML1;rRGQ{^5yTjVbVw?Xg(@&=j zww*TB_MdU^H3$K|x7A?eAp{WX4&b7)CBega!!QJEl$nRz^Vmmb1kl{TnpD+l5Iq@Tx;!-N;5g z(awy3_EN&eH3%WBRYAzl| zEQIjlRKf(ltJ)T0GbEr82j1@5di%d@+s>@9jY#Wjz~=t|>j^xZ9^7{O00000NkvXX Hu0mjflPm$m diff --git a/website/docs/manager_ftrack.md b/website/docs/manager_ftrack.md index 69faf6ae9d..511f5c04c0 100644 --- a/website/docs/manager_ftrack.md +++ b/website/docs/manager_ftrack.md @@ -31,7 +31,7 @@ This process is how data from Ftrack will get into Avalon database. ### How to synchronize You can do synchronization with [Sync To Avalon](manager_ftrack_actions#sync-to-avalon) action. -Synchronization can be automated with OpenPype's [event server](#event-server) and synchronization events. If your Ftrack is [prepared for OpenPype](#prepare-ftrack-for-pype), the project should have custom attribute `Avalon auto-sync`. Check the custom attribute to allow auto-updates with event server. +Synchronization can be automated with OpenPype's [event server](#event-server) and synchronization events. If your Ftrack is [prepared for OpenPype](#prepare-ftrack-for-openpype), the project should have custom attribute `Avalon auto-sync`. Check the custom attribute to allow auto-updates with event server. :::important Always use `Sync To Avalon` action before you enable `Avalon auto-sync`! diff --git a/website/docs/manager_ftrack_actions.md b/website/docs/manager_ftrack_actions.md index 6349d4357f..ce1c0466b9 100644 --- a/website/docs/manager_ftrack_actions.md +++ b/website/docs/manager_ftrack_actions.md @@ -28,6 +28,9 @@ In most cases actions filtered by entity type: So if you do not see action you need to use check if action is available for selected *entity type* or ask *administrator* to check if you have permissions to use it. + +Actions can be highly customized according to specific client's requests. + :::important Filtering can be more complicated for example a lot of actions can be shown only when one particular entity is selected. ::: @@ -39,33 +42,12 @@ Filtering can be more complicated for example a lot of actions can be shown only * Entity types: Task * User roles: All -These actions *launch application with OpenPype initiated* and *start timer* for the selected Task. We recommend you to launch application this way. +These actions *launch application with OpenPype * and *start timer* for the selected Task. We recommend you to launch application this way. :::important -Project Manager or Supervisor must set project's applications during project preparation otherwise you won't see them. +Project Manager or Supervisor must set project's applications during project preparation otherwise you won't see them. Applications can be added even if the project is in progress. ::: -### RV -* Entity types: All -* User roles: All - -You can launch RV player with playable components from selected entities. You can choose which components will be played. - -:::important -You must have RV player installed and licensed and have correct RV environments set to be able use this action. -::: - -### DJV View -* Entity types: Task, Asset Version -* User roles: All - -You can launch DJV View with one playable component from selected entities. You can choose which component will be played. - -:::important -You must have DJV View installed and configured in studio-config to be able use this action. -::: - ----

@@ -79,11 +61,11 @@ You must have DJV View installed and configured in studio-config to be able use A group of actions that are used for OpenPype Administration. -### Create/Update Avalon Attributes +### Create Update Avalon Attributes * Entity types: All * User roles: Pypeclub, Administrator -Action creates and updates Ftrack's Custom Attributes that are needed to manage and run OpenPype within Ftrack. Most of custom attribute configurations are stored in OpenPype presets (*Pype Settings → Project → Anatomy → Attributes*). It is not recommended to modify values stored in the file unless your studio used completely custom configuration. +Action creates and updates Ftrack's Custom Attributes that are needed to manage and run OpenPype within Ftrack. Most of custom attribute configurations are stored in OpenPype settings (*click on the systray OpenPype icon → Settings → Project → Anatomy → Attributes*). It is not recommended to modify values stored in the file unless your studio used completely custom configuration. ### Sync to Avalon * Entity types: Project, Typed Context @@ -91,8 +73,8 @@ Action creates and updates Ftrack's Custom Attributes that are needed to manage Synchronization to Avalon is key process to keep OpenPype data updated. Action updates selected entities (Project, Shot, Sequence, etc.) and all nested entities to Avalon database. If action is successfully finished [Sync Hier Attrs](#sync-hier-attrs) action is triggered. -There are 2 versions of **Sync to Avalon** first labeled as **server** second as **local**. -* **server** version will be processed with [event server](admin_ftrack#event-server) +There are 2 versions of **Sync to Avalon**, first labeled as **server** second as **local**. +* **server** version will be processed with [event server](module_ftrack#event-server) * **local** version will be processed with user's OpenPype tray application It is recommended to use **local** version if possible to avoid unnecessary deceleration of event server. @@ -101,11 +83,11 @@ It is recommended to use **local** version if possible to avoid unnecessary dece * Entity types: Project, Typed Context * User roles: Pypeclub, Administrator, Project manager -Synchronization to Avalon of Ftrack's hierarchical Custom attributes is a bit complicated so we decided to split synchronization process into 2 actions. This action updates hierarchical Custom attributes of selected entities (Project, Shot, Sequence, etc.) and all their nested entities to pipeline database. This action is also triggered automatically after successfully finished [Sync To Avalon](#sync-to-avalon) action. +Synchronization to Avalon of Ftrack's hierarchical Custom attributes is a bit complicated so we decided to split synchronization process into 2 actions. This action updates hierarchical Custom attributes of selected entities (Project, Shot, Sequence, etc.) and all their nested entities to pipeline database. This action is also triggered automatically after successfully finished [Sync To Avalon](#sync-to-avalon) action. There are 2 versions of **Sync Hier Attrs** first labeled as **server** second as **local**. -* **server** version will be processed with [event server](admin_ftrack#event-server) -* **local** version will be processed with user's OpenPype tray application +* **server** version will be processed with [event server](module_ftrack#event-server) +* **local** version will be processed with user's OpenPype application It is recommended to use **local** version if possible to avoid unnecessary deceleration of event server. @@ -127,26 +109,6 @@ With this action it's possible to delete up to 15 entities at once from active p
-## Thumbnail -
-
- -![thumbnail-icon](assets/ftrack/ftrack-thumbnail-icon.png) -
-
- -A group of actions for thumbnail management. - -### Thumbnail to Parent -Propagates the thumbnail of the selected entity to its parent. - -### Thumbnail to Children -Propagates the thumbnail of the selected entity to its first direct children entities. - ---- -
-
- ## Prepare Project
@@ -158,7 +120,7 @@ Propagates the thumbnail of the selected entity to its first direct children ent * Entity types: Project * User roles: Pypeclub, Administrator, Project manager -Allows project managers and coordinator to *set basic project attributes* needed for OpenPype to operate, *Create project folders* if you want and especially *prepare Project specific [anatomy](admin_config#anatomy) and [presets](admin_config#presets)* files for you. +Allows project managers and coordinator to *set basic project attributes* needed for OpenPype to operate, *Create project folders* if you want and especially prepare project specific [anatomy](admin_settings_project_anatomy) or [settings](admin_settings_project). :::tip It is possible to use this action during the lifetime of a project but we recommend using it only once at the start of the project. @@ -220,12 +182,30 @@ You should use this action if you need to delete Entities or Asset Versions othe *Create Project Structure* helps to create basic folder structure and may create the main ftrack entities for the project. -Structure is loaded from [presets](admin_config#presets) *(Pype Settings → Project → Global → Project Folder Structure)*. You should examine that preset to see how it works. Preset may contain dictionaries of nested dictionaries where each key represents a folder name. Key and all it's parents will be also created in Ftrack if the key ends with `[ftrack]`. Default Ftrack entity type is *Folder* but entity type can be specified using `[ftrack.{entity type}]`. To create *Sequence* with name *Seq_001* key should look like `Seq_001[ftrack.Sequence]`. +Structure is loaded from settings *(OpenPype Settings → Project → Global → Project Folder Structure)*. You should examine these settings to see how it works. Settings may contain dictionaries of nested dictionaries where each key represents a folder name. Key and all it's parents will be also created in Ftrack if the key ends with `[ftrack]`. Default Ftrack entity type is *Folder* but entity type can be specified using `[ftrack.{entity type}]`. To create *Sequence* with name *Seq_001* key should look like `Seq_001[ftrack.Sequence]`. :::note Please keep in mind this action is meant to make your project setup faster at the very beginning, but it does not create folders for each shot and asset. For creating asset folder refer to `Create Folders` Action ::: +--- +
+
+ +## Delivery +
+
+ +![ftrack-delivery-icon](assets/ftrack/ftrack-delivery-icon.png) +
+
+ +* Entity types: Task +* User roles: Pypeclub, Project manager, Administrator + +Collects approved hires files and copy them into a folder. It usually creates h.264 files for preview and mov for editorial. All files are then copied according to predefined naming convention to a specific folder. + + ---
@@ -241,21 +221,55 @@ Please keep in mind this action is meant to make your project setup faster at th * Entity types: Typed Context, Task * User roles: All -Creates folders for a selected asset in based on project templates. - It is usually not necessary to launch this action because folders are created automatically every time you start working on a task. However it can be handy if you need to create folders before any work begins or you want to use applications that don't have pipeline implementation. - - - ---
+## Thumbnail +
+
+ +![thumbnail-icon](assets/ftrack/ftrack-thumbnail-icon.png) +
+
+ +A group of actions for thumbnail management. + +### Thumbnail to Parent +Propagates the thumbnail of the selected entity to its parent. + +### Thumbnail to Children +Propagates the thumbnail of the selected entity to its first direct children entities. + +--- +### RV +* Entity types: All +* User roles: All + +You can launch RV player with playable components from selected entities. You can choose which components will be played. + +:::important +You must have RV player installed and licensed and have correct RV environments set to be able use this action. +::: + +--- +### DJV View +* Entity types: Task, Asset Version +* User roles: All + +You can launch DJV View with one playable component from selected entities. You can choose which component will be played. + +:::important +You must have DJV View installed and configured in studio-config to be able use this action. +::: + + +
+
+ +--- ## Open File
diff --git a/website/docs/module_ftrack.md b/website/docs/module_ftrack.md index 436594b64c..9f31eac21e 100644 --- a/website/docs/module_ftrack.md +++ b/website/docs/module_ftrack.md @@ -8,38 +8,33 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -Ftrack is currently the main project management option for Pype. This documentation assumes that you are familiar with Ftrack and it's basic principles. If you're new to Ftrack, we recommend having a thorough look at [Ftrack Official Documentation](http://ftrack.rtd.ftrack.com/en/stable/). +Ftrack is currently the main project management option for OpenPype. This documentation assumes that you are familiar with Ftrack and it's basic principles. If you're new to Ftrack, we recommend having a thorough look at [Ftrack Official Documentation](http://ftrack.rtd.ftrack.com/en/stable/). ## Prepare Ftrack for OpenPype ### Server URL -If you want to connect Ftrack to OpenPype you might need to make few changes in Ftrack settings. These changes would take a long time to do manually, so we prepared a few Ftrack actions to help you out. First, you'll need to launch OpenPype settings, enable [Ftrack module](admin_settings_system#Ftrack), and enter the address to your ftrack server. +If you want to connect Ftrack to OpenPype you might need to make few changes in Ftrack settings. These changes would take a long time to do manually, so we prepared a few Ftrack actions to help you out. First, you'll need to launch OpenPype settings, enable [Ftrack module](admin_settings_system#Ftrack), and enter the address to your Ftrack server. ### Login -Once your server is configured, restart OpenPype and you should be prompted to enter your [Ftrack credentials](#credentials) to be able to run our Ftrack actions. If you are already logged in to ftrack in your browser, it is enough to press `ftrack login` and it will connect automatically. +Once your server is configured, restart OpenPype and you should be prompted to enter your [Ftrack credentials](artist_ftrack#How-to-use-Ftrack-in-OpenPype) to be able to run our Ftrack actions. If you are already logged in to Ftrack in your browser, it is enough to press `Ftrack login` and it will connect automatically. -For more details step by step on how to login to ftrack in OpenPype to go [artist ftrack login](#artist_ftrack#first-use-best-case-scenario) documentation. +For more details step by step on how to login to Ftrack in OpenPype to go [artist Ftrack login](artist_ftrack#How-to-use-Ftrack-in-OpenPype) documentation. -You can only use our Ftrack Actions and publish to ftrack if each artist is logged in. +You can only use our Ftrack Actions and publish to Ftrack if each artist is logged in. ### Custom Attributes -After successfully connecting OpenPype with you ftrack, you can right on any project in ftrack and you should see a bunch of actions available. The most important one is called `OpenPype Admin` and contains multiple options inside. - -To prepare ftrack for working with OpenPype you'll need to run [OpenPype Admin - Create/Update Avalon Attributes](manager_ftrack_actions#create-update-avalon-attributes), which creates and sets the Custom Attributes necessary for OpenPype to function. - - +After successfully connecting OpenPype with you Ftrack, you can right on any project in Ftrack and you should see a bunch of actions available. The most important one is called `OpenPype Admin` and contains multiple options inside. +To prepare Ftrack for working with OpenPype you'll need to run [OpenPype Admin - Create/Update Avalon Attributes](manager_ftrack_actions#create-update-avalon-attributes), which creates and sets the Custom Attributes necessary for OpenPype to function. ## Event Server - Ftrack Event Server is the key to automation of many tasks like _status change_, _thumbnail update_, _automatic synchronization to Avalon database_ and many more. Event server should run at all times to perform the required processing as it is not possible to catch some of them retrospectively with enough certainty. ### Running event server - -There are specific launch arguments for event server. With `openpype eventserver` you can launch event server but without prior preparation it will terminate immediately. The reason is that event server requires 3 pieces of information: _Ftrack server url_, _paths to events_ and _Credentials (Username and API key)_. Ftrack server URL and Event path are set from OpenPype's environments by default, but the credentials must be done separatelly for security reasons. +There are specific launch arguments for event server. With `openpype eventserver` you can launch event server but without prior preparation it will terminate immediately. The reason is that event server requires 3 pieces of information: _Ftrack server url_, _paths to events_ and _credentials (Username and API key)_. Ftrack server URL and Event path are set from OpenPype's environments by default, but the credentials must be done separatelly for security reasons. @@ -61,7 +56,7 @@ There are specific launch arguments for event server. With `openpype eventserver - `--ftrack-url "https://yourdomain.ftrackapp.com/"` : Ftrack server URL _(it is not needed to enter if you have set `FTRACK_SERVER` in OpenPype' environments)_ - `--ftrack-events-path "//Paths/To/Events/"` : Paths to events folder. May contain multiple paths separated by `;`. _(it is not needed to enter if you have set `FTRACK_EVENTS_PATH` in OpenPype' environments)_ -So if you want to use OpenPype's environments then you can launch event server for first time with these arguments `$PYPE_SETUP/pype eventserver --ftrack-user "my.username" --ftrack-api-key "00000aaa-11bb-22cc-33dd-444444eeeee" --store-credentials`. Since that time, if everything was entered correctly, you can launch event server with `$PYPE_SETUP/pype eventserver`. +So if you want to use OpenPype's environments then you can launch event server for first time with these arguments `$OPENPYPE_SETUP/openpype eventserver --ftrack-user "my.username" --ftrack-api-key "00000aaa-11bb-22cc-33dd-444444eeeee" --store-credentials`. Since that time, if everything was entered correctly, you can launch event server with `$OPENPYPE_SETUP/openpype eventserver`. @@ -77,12 +72,12 @@ So if you want to use OpenPype's environments then you can launch event server f ::: :::caution -We do not recommend setting your ftrack user and api key environments in a persistent way, for security reasons. Option 1. passing them as arguments is substantially safer. +We do not recommend setting your Ftrack user and api key environments in a persistent way, for security reasons. Option 1. passing them as arguments is substantially safer. ::: ### Where to run event server -We recommend you to run event server on stable server machine with ability to connect to Avalon database and Ftrack web server. Best practice we recommend is to run event server as service. +We recommend you to run event server on stable server machine with ability to connect to Avalon database and Ftrack web server. Best practice we recommend is to run event server as service. It can be Windows or Linux. :::important Event server should **not** run more than once! It may cause major issues. @@ -91,34 +86,40 @@ Event server should **not** run more than once! It may cause major issues. ### Which user to use - must have at least `Administrator` role -- same user should not be used by an artist +- the same user should not be used by an artist -### Run Linux service - step by step -1. create file: +:::note How to create Eventserver service + + + + +- create file: `sudo vi /opt/OpenPype/run_event_server.sh` - -2. add content to the file: - +- add content to the file: ```sh -export PYPE_DEBUG=3 -pushd /mnt/pipeline/prod/pype-setup -. pype eventserver --ftrack-user --ftrack-api-key +#!\usr\bin\env +export OPENPYPE_DEBUG=3 +pushd /mnt/pipeline/prod/openpype-setup +. openpype eventserver --ftrack-user --ftrack-api-key ``` - -3. create service file: +- create service file: `sudo vi /etc/systemd/system/openpype-ftrack-event-server.service` - -4. add content to the service file +- add content to the service file ```toml [Unit] -Description=Run Pype Ftrack Event Server Service +Description=Run OpenPype Ftrack Event Server Service After=network.target [Service] Type=idle -ExecStart=/opt/pype/run_event_server.sh +ExecStart=/opt/openpype/run_event_server.sh Restart=on-failure RestartSec=10s @@ -126,34 +127,42 @@ RestartSec=10s WantedBy=multi-user.target ``` -5. change file permission: +- change file permission: `sudo chmod 0755 /etc/systemd/system/openpype-ftrack-event-server.service` -6. enable service: +- enable service: `sudo systemctl enable openpype-ftrack-event-server` -7. start service: +- start service: `sudo systemctl start openpype-ftrack-event-server` + + + +- create service file: `openpype-ftrack-eventserver.bat` +- add content to the service file: +```sh +@echo off +set OPENPYPE_DEBUG=3 +pushd \\path\to\file\ +call openpype.bat eventserver --ftrack-user --ftrack-api-key +``` +- download and install `nssm.cc` +- create Windows service according to nssm.cc manual +- you can also run eventserver as a standard Schedule task +- be aware of using UNC path + + + +::: + * * * ## Ftrack events -Events are helpers for automation. They react to Ftrack Web Server events like change entity attribute, create of entity, etc. . +Events are helpers for automation. They react to Ftrack Web Server events like change entity attribute, create of entity, etc. -### Delete Avalon ID from new entity _(DelAvalonIdFromNew)_ - -Is used to remove value from `Avalon/Mongo Id` Custom Attribute when entity is created. - -`Avalon/Mongo Id` Custom Attribute stores id of synchronized entities in pipeline database. When user _Copy -> Paste_ selection of entities to create similar hierarchy entities, values from Custom Attributes are copied too. That causes issues during synchronization because there are multiple entities with same value of `Avalon/Mongo Id`. To avoid this error we preventively remove these values when entity is created. - -### Next Task update _(NextTaskUpdate)_ - -Change status of next task from `Not started` to `Ready` when previous task is approved. - -Multiple detailed rules for next task update can be configured in the presets. - -### Synchronization to Avalon database _(Sync_to_Avalon)_ +### Sync to Avalon Automatic [synchronization to pipeline database](manager_ftrack#synchronization-to-avalon-database). @@ -163,33 +172,45 @@ This event updates entities on their changes Ftrack. When new entity is created Deleting an entity by Ftrack's default is not processed for security reasons _(to delete entity use [Delete Asset/Subset action](manager_ftrack_actions#delete-asset-subset))_. ::: -### Synchronize hierarchical attributes _(SyncHierarchicalAttrs)_ +### Synchronize Hierarchical and Entity Attributes Auto-synchronization of hierarchical attributes from Ftrack entities. -Related to [Synchronize to Avalon database](#synchronization-to-avalon-database) event _(without it, it makes no sense to use this event)_. Hierarchical attributes must be synchronized with special way so we needed to split synchronization into 2 parts. There are [synchronization rules](manager_ftrack#synchronization-rules) for hierarchical attributes that must be met otherwise interface with messages about not meeting conditions is shown to user. +Related to [Synchronize to Avalon database](manager_ftrack#synchronization-to-avalon-database) event _(without it, it makes no sense to use this event)_. Hierarchical attributes must be synchronized with special way so we needed to split synchronization into 2 parts. There are [synchronization rules](manager_ftrack#synchronization-rules) for hierarchical attributes that must be met otherwise interface with messages about not meeting conditions is shown to user. -### Thumbnails update _(ThumbnailEvents)_ +### Update Hierarchy thumbnails -Updates thumbnail of Task and it's parent when new Asset Version with thumbnail is created. +Push thumbnails from version, up through multiple hierarchy levels -This is normally done by Ftrack Web server when Asset Version is created with Drag&Drop but not when created with Ftrack API. +### Update status on task action -### Version to Task status _(VersionToTaskStatus)_ +Change status of next task from `Not started` to `Ready` when previous task is approved. -Updates Task status based on status changes on it's `AssetVersion`. +Multiple detailed rules for next task update can be configured in the presets. + +### Delete Avalon ID from new entity + +Is used to remove value from `Avalon/Mongo Id` Custom Attribute when entity is created. + +`Avalon/Mongo Id` Custom Attribute stores id of synchronized entities in pipeline database. When user _Copy → Paste_ selection of entities to create similar hierarchy entities, values from Custom Attributes are copied too. That causes issues during synchronization because there are multiple entities with same value of `Avalon/Mongo Id`. To avoid this error we preventively remove these values when entity is created. + +### Sync status from Task to Parent + +List of parent boject types where this is triggered ("Shot", "Asset build", etc. Skipped if it is empty) + +### Sync status from Version to Task + +Updates Task status based on status changes on its Asset Version. The issue this solves is when Asset version's status is changed but the artist assigned to Task is looking at the task status, thus not noticing the review. This event makes sure statuses Asset Version get synced to it's task. After changing a status on version, this event first tries to set identical status to version's parent (usually task). But this behavior can be tweaked in settings. +### Sync status on first created version -### Update First Version status _(FirstVersionStatus)_ - -This event handler allows setting of different status to a first created Asset Version in ftrack. +This event handler allows setting of different status to a first created Asset Version in Ftrack. This is usefull for example if first version publish doesn't contain any actual reviewable work, but is only used for roundtrip conform check, in which case this version could receive status `pending conform` instead of standard `pending review` -Behavior can be filtered by `name` or `type` of the task assigned to the Asset Version. Configuration can be found in [ftrack presets](admin_presets_ftrack#first_version_status-dict) - -* * * +### Update status on next task +Change status on next task by task types order when task status state changed to "Done". All tasks with the same Task mapping of next task status changes From → To. Some status can be ignored. From e06ab4b7091be854aad9e006bccf9033cceb9d82 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Wed, 7 Apr 2021 14:46:18 +0200 Subject: [PATCH 18/68] some small grammar fixes --- website/docs/artist_ftrack.md | 36 ++++++++++++------- website/docs/assets/ftrack/ftrack_logout.gif | Bin 0 -> 41717 bytes website/docs/manager_ftrack.md | 8 ++--- 3 files changed, 28 insertions(+), 16 deletions(-) create mode 100644 website/docs/assets/ftrack/ftrack_logout.gif diff --git a/website/docs/artist_ftrack.md b/website/docs/artist_ftrack.md index df2a7236b3..44d8b0c007 100644 --- a/website/docs/artist_ftrack.md +++ b/website/docs/artist_ftrack.md @@ -8,8 +8,7 @@ sidebar_label: Artist ## Login to Ftrack module in OpenPype (best case scenario) 1. Launch OpenPype and go to systray OpenPype icon. -2. *Ftrack login* window pop up on start - - or press **login** in **Ftrack menu** to pop up *Ftrack login* window +2. *Ftrack login* window pop up on start or press **login** in **Ftrack menu** to pop up *Ftrack login* window ![ftrack-login-2](assets/ftrack/ftrack-login_50.png) @@ -18,7 +17,7 @@ sidebar_label: Artist ![Login widget](assets/ftrack/ftrack-login_1.png) - Web browser opens - - Sign in Ftrack if you're requested. If you are already sign up to Ftrack via web browser, you can jump to point 6. + - Sign into Ftrack if requested. If you are already signed in to Ftrack via web browser, you can jump to [Application launch](#application-launch-best-case-scenario) ![ftrack-login-2](assets/ftrack/ftrack-login_2.png) @@ -34,18 +33,18 @@ sidebar_label: Artist ## Application launch (best case scenario) 1. Make sure OpenPype is running and you passed [Login to Ftrack](#login-to-ftrack-module-in-openpype-best-case-scenario) guide -2. Open web browser and go to your studio Ftrack web page *(e.g. https://mystudio.ftrackapp.com/)* +2. Open Web browser and go to your studio Ftrack web page *(e.g. https://mystudio.ftrackapp.com/)* -3. Locate the task on which you want to run the application +3. Locate the task to run the application on. 4. Display actions for the task ![ftrack-login-3](assets/ftrack/ftrack-login_60.png) 5. Select application you want to launch - - application versions may be grouped to one action in that case press the action to reveal versions to choose *(like Maya in the picture)*, only applications permitted to the particular project are appeared. + - application versions may be grouped to one action. In that case, press the action to reveal versions to choose from *(like Maya in the picture)*, only applications permitted on the particular project will appear. ![ftrack-login-3](assets/ftrack/ftrack-login_71-small.png) -6. Work +6. Start working ;) --- @@ -54,9 +53,22 @@ sidebar_label: Artist ![ftrack-login-3](assets/ftrack/ftrack-login_80-small.png) -2. Log out the previous user from Ftrack module in tray +2. Log out the previous user from Ftrack module in OpenPype tray - ![ftrack-login-3](assets/ftrack/ftrack-login_81.png) +
+
+ +![ftrack-login-3](assets/ftrack/ftrack_logout.gif) + +
+
+ +![ftrack-login-3](assets/ftrack/ftrack-login_81.png) + +
+
+ +

3. Follow [Login to Ftrack](#login-to-ftrack-module-in-openpype-best-case-scenario) guide @@ -64,6 +76,7 @@ sidebar_label: Artist ## Where to find API key - Your API key can be found in Ftrack. In the upper right corner of Ftrack click on the avatar, choose System settings. +- You shouldn't need to use your personal API key if previous steps went through correctly ![ftrack-api](assets/ftrack/ftrack-api.png) @@ -92,8 +105,7 @@ sidebar_label: Artist - try to restart OpenPype **2. possibility - Ftrack is not set in OpenPype** -- inform your administrator -- The Ftrack URL can be changed in OpenPype Settings → System → Modules → Ftrack +- inform your administrator or supervisor ### Web browser did not open @@ -119,5 +131,5 @@ sidebar_label: Artist **3. possibility - User logged to Ftrack Web is not the same as user logged to Ftrack module in tray** - Follow [Change user](#change-ftrack-user) guide -**4. possibility - Project don't have set applications** +**4. possibility - Project doesn't have applications set correctly** - ask your Project Manager to check if he set applications for the project diff --git a/website/docs/assets/ftrack/ftrack_logout.gif b/website/docs/assets/ftrack/ftrack_logout.gif new file mode 100644 index 0000000000000000000000000000000000000000..81088075c4af1bb829948366cc179a61879587b8 GIT binary patch literal 41717 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 Date: Wed, 7 Apr 2021 14:46:36 +0200 Subject: [PATCH 19/68] change action icon to new format (example) --- website/docs/manager_ftrack_actions.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/website/docs/manager_ftrack_actions.md b/website/docs/manager_ftrack_actions.md index ce1c0466b9..ad5bfe3a9c 100644 --- a/website/docs/manager_ftrack_actions.md +++ b/website/docs/manager_ftrack_actions.md @@ -48,18 +48,22 @@ These actions *launch application with OpenPype * and *start timer* for the sele Project Manager or Supervisor must set project's applications during project preparation otherwise you won't see them. Applications can be added even if the project is in progress. ::: -
-
-## OpenPype Admin -
-
+
+
![pype_admin-icon](assets/ftrack/ftrack-pype_admin-icon.png) + +
+
+ +## OpenPype Admin +
-A group of actions that are used for OpenPype Administration. + +#### A group of actions that are used for OpenPype Administration. ### Create Update Avalon Attributes * Entity types: All From 403a3149cc03c00f9dbe8e4ffec12c5412a9c68a Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Wed, 7 Apr 2021 14:51:22 +0200 Subject: [PATCH 20/68] flip title and icon --- website/docs/manager_ftrack_actions.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/website/docs/manager_ftrack_actions.md b/website/docs/manager_ftrack_actions.md index ad5bfe3a9c..968ad170ad 100644 --- a/website/docs/manager_ftrack_actions.md +++ b/website/docs/manager_ftrack_actions.md @@ -50,15 +50,15 @@ Project Manager or Supervisor must set project's applications during project pre
-
- -![pype_admin-icon](assets/ftrack/ftrack-pype_admin-icon.png) - -
-
+
## OpenPype Admin +
+
+ +![pype_admin-icon](assets/ftrack/ftrack-pype_admin-icon.png) +
From d1a55e39230590e729ec71370e63c21b05d0e680 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Wed, 7 Apr 2021 15:31:59 +0200 Subject: [PATCH 21/68] add openpype_root to pythonpath --- start.py | 1 + 1 file changed, 1 insertion(+) diff --git a/start.py b/start.py index 1f946a705c..23f019ab76 100644 --- a/start.py +++ b/start.py @@ -326,6 +326,7 @@ def _initialize_environment(openpype_version: OpenPypeVersion) -> None: # TODO move additional paths to `boot` part when OPENPYPE_ROOT will point # to same hierarchy from code and from frozen OpenPype additional_paths = [ + os.environ["OPENPYPE_ROOT"], # add OpenPype tools os.path.join(os.environ["OPENPYPE_ROOT"], "openpype", "tools"), # add common OpenPype vendor From 9e3d02ca8f00c0aa38d0b42e5045967df39c46aa Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Wed, 7 Apr 2021 15:36:42 +0200 Subject: [PATCH 22/68] remove comma from build workflow --- .github/workflows/test_build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_build.yml b/.github/workflows/test_build.yml index e17d75c453..6e1e38d0b2 100644 --- a/.github/workflows/test_build.yml +++ b/.github/workflows/test_build.yml @@ -8,7 +8,7 @@ on: branches: [develop] types: [review_requested, ready_for_review] paths-ignore: - - 'docs/**', + - 'docs/**' - 'website/**' - 'vendor/**' From 2e1548b0b37705322a648af46f37d5681f0df726 Mon Sep 17 00:00:00 2001 From: mkolar Date: Wed, 7 Apr 2021 14:40:05 +0000 Subject: [PATCH 23/68] Create draft PR for #1173 From 5a1889e413273bf3dbdbf2ee2d4eb4f080f397e2 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 7 Apr 2021 16:49:27 +0100 Subject: [PATCH 24/68] Fix for the workflow between Blender and Unreal for OpenPype 3.0 --- openpype/hooks/pre_python2_vendor.py | 2 +- openpype/hosts/blender/api/__init__.py | 13 +++++++++++-- openpype/hosts/blender/plugins/load/load_layout.py | 5 ++++- openpype/hosts/unreal/__init__.py | 0 .../hosts/unreal/hooks/pre_workfile_preparation.py | 8 ++++---- openpype/lib/avalon_context.py | 1 + .../ftrack/launch_hooks/pre_python2_vendor.py | 2 +- 7 files changed, 22 insertions(+), 9 deletions(-) create mode 100644 openpype/hosts/unreal/__init__.py diff --git a/openpype/hooks/pre_python2_vendor.py b/openpype/hooks/pre_python2_vendor.py index 7aaf713dec..815682fef8 100644 --- a/openpype/hooks/pre_python2_vendor.py +++ b/openpype/hooks/pre_python2_vendor.py @@ -6,7 +6,7 @@ class PrePython2Vendor(PreLaunchHook): """Prepend python 2 dependencies for py2 hosts.""" # WARNING This hook will probably be deprecated in OpenPype 3 - kept for test order = 10 - app_groups = ["hiero", "nuke", "nukex"] + app_groups = ["hiero", "nuke", "nukex", "unreal"] def execute(self): # Prepare vendor dir path diff --git a/openpype/hosts/blender/api/__init__.py b/openpype/hosts/blender/api/__init__.py index 55c5b63f60..c5b0a44072 100644 --- a/openpype/hosts/blender/api/__init__.py +++ b/openpype/hosts/blender/api/__init__.py @@ -51,9 +51,18 @@ def set_start_end_frames(): "name": asset_name }) - bpy.context.scene.frame_start = asset_doc["data"]["frameStart"] - bpy.context.scene.frame_end = asset_doc["data"]["frameEnd"] + # Default frame start/end + frameStart = 0 + frameEnd = 100 + # Check if frameStart/frameEnd are set + if asset_doc["data"]["frameStart"]: + frameStart = asset_doc["data"]["frameStart"] + if asset_doc["data"]["frameEnd"]: + frameEnd = asset_doc["data"]["frameEnd"] + + bpy.context.scene.frame_start = frameStart + bpy.context.scene.frame_end = frameEnd def on_new(arg1, arg2): set_start_end_frames() diff --git a/openpype/hosts/blender/plugins/load/load_layout.py b/openpype/hosts/blender/plugins/load/load_layout.py index 73b12d8c25..f1f2fdcddd 100644 --- a/openpype/hosts/blender/plugins/load/load_layout.py +++ b/openpype/hosts/blender/plugins/load/load_layout.py @@ -292,6 +292,9 @@ class UnrealLayoutLoader(plugin.AssetLoader): icon = "code-fork" color = "orange" + animation_creator_name = "CreateAnimation" + setdress_creator_name = "CreateSetDress" + def _remove_objects(self, objects): for obj in list(objects): if obj.type == 'ARMATURE': @@ -368,7 +371,7 @@ class UnrealLayoutLoader(plugin.AssetLoader): location.get('z') ) obj.rotation_euler = ( - rotation.get('x'), + rotation.get('x') + math.pi / 2, -rotation.get('y'), -rotation.get('z') ) diff --git a/openpype/hosts/unreal/__init__.py b/openpype/hosts/unreal/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openpype/hosts/unreal/hooks/pre_workfile_preparation.py b/openpype/hosts/unreal/hooks/pre_workfile_preparation.py index 5945d0486b..c698be63de 100644 --- a/openpype/hosts/unreal/hooks/pre_workfile_preparation.py +++ b/openpype/hosts/unreal/hooks/pre_workfile_preparation.py @@ -23,8 +23,8 @@ class UnrealPrelaunchHook(PreLaunchHook): def execute(self): asset_name = self.data["asset_name"] task_name = self.data["task_name"] - workdir = self.env["AVALON_WORKDIR"] - engine_version = self.app_name.split("_")[-1] + workdir = self.launch_context.env["AVALON_WORKDIR"] + engine_version = self.app_name.split("_")[-1].replace("-", ".") unreal_project_name = f"{asset_name}_{task_name}" # Unreal is sensitive about project names longer then 20 chars @@ -81,8 +81,8 @@ class UnrealPrelaunchHook(PreLaunchHook): # Set "AVALON_UNREAL_PLUGIN" to current process environment for # execution of `create_unreal_project` env_key = "AVALON_UNREAL_PLUGIN" - if self.env.get(env_key): - os.environ[env_key] = self.env[env_key] + if self.launch_context.env.get(env_key): + os.environ[env_key] = self.launch_context.env[env_key] unreal_lib.create_unreal_project( unreal_project_name, diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index 1f7c693b85..2d8726352a 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -1123,6 +1123,7 @@ class BuildWorkfile: return output +@with_avalon def get_creator_by_name(creator_name, case_sensitive=False): """Find creator plugin by name. diff --git a/openpype/modules/ftrack/launch_hooks/pre_python2_vendor.py b/openpype/modules/ftrack/launch_hooks/pre_python2_vendor.py index 7826d833ac..f14857bc98 100644 --- a/openpype/modules/ftrack/launch_hooks/pre_python2_vendor.py +++ b/openpype/modules/ftrack/launch_hooks/pre_python2_vendor.py @@ -9,7 +9,7 @@ class PrePython2Support(PreLaunchHook): Path to vendor modules is added to the beggining of PYTHONPATH. """ # There will be needed more granular filtering in future - app_groups = ["maya", "nuke", "nukex", "hiero", "nukestudio"] + app_groups = ["maya", "nuke", "nukex", "hiero", "nukestudio", "unreal"] def execute(self): # Prepare vendor dir path From 3627c91b8ae84bc87e206bd3f1c74f94fb28fecb Mon Sep 17 00:00:00 2001 From: Petr Dvorak Date: Wed, 7 Apr 2021 22:25:10 +0200 Subject: [PATCH 25/68] Column width adjustment --- website/docs/manager_ftrack_actions.md | 73 ++++++++++++++++---------- 1 file changed, 44 insertions(+), 29 deletions(-) diff --git a/website/docs/manager_ftrack_actions.md b/website/docs/manager_ftrack_actions.md index 968ad170ad..950f667f9a 100644 --- a/website/docs/manager_ftrack_actions.md +++ b/website/docs/manager_ftrack_actions.md @@ -48,7 +48,7 @@ These actions *launch application with OpenPype * and *start timer* for the sele Project Manager or Supervisor must set project's applications during project preparation otherwise you won't see them. Applications can be added even if the project is in progress. ::: - +---
@@ -110,14 +110,16 @@ This action gives ability to *stop running jobs*. When action is triggered, an i With this action it's possible to delete up to 15 entities at once from active project in pipeline database. Entered names must match exactly the names stored in database. These entities also must not have children entities *(Sequence must not have Shots but Task is not entity)*. --- -
-
+
+
## Prepare Project +
-
+
![prepare_project-icon](assets/ftrack/ftrack-prepare_project-icon.png) +
@@ -133,14 +135,16 @@ It is possible to use this action during the lifetime of a project but we recomm ![prepare_project_1](assets/ftrack/ftrack-prepare_project_1-small.png) --- -
-
+
+
## Multiple Notes +
-
+
![multiple_notes-icon](assets/ftrack/ftrack-multiple_notes-icon.png) +
@@ -151,14 +155,16 @@ You can add same note to multiple Asset Versions at once with this action. ![multiple_notes_1](assets/ftrack/ftrack-multiple_notes_1-small.png) --- -
-
+
+
## Delete Asset/Subset +
-
+
![delete_asset-icon](assets/ftrack/ftrack-delete_asset-icon.png) +
@@ -170,14 +176,16 @@ Action deletes Entities and Asset Versions from Ftrack and Avalon database. You should use this action if you need to delete Entities or Asset Versions otherwise deletion will not take effect in Avalon database. Currently the action allows to only delete one entity at the time. Entity also must not have any children. --- -
-
+
+
## Create Project Structure +
-
+
![create_project_folders-icon](assets/ftrack/ftrack-create_project_folders-icon.png) +
@@ -193,14 +201,16 @@ Please keep in mind this action is meant to make your project setup faster at th ::: --- -
-
+
+
## Delivery +
-
+
![ftrack-delivery-icon](assets/ftrack/ftrack-delivery-icon.png) +
@@ -211,14 +221,16 @@ Collects approved hires files and copy them into a folder. It usually creates h. --- -
-
+
+
## Create Folders +
-
+
![create_folders-icon](assets/ftrack/ftrack-create_folders-icon.png) +
@@ -228,14 +240,16 @@ Collects approved hires files and copy them into a folder. It usually creates h. It is usually not necessary to launch this action because folders are created automatically every time you start working on a task. However it can be handy if you need to create folders before any work begins or you want to use applications that don't have pipeline implementation. --- -
-
+
+
## Thumbnail +
-
+
![thumbnail-icon](assets/ftrack/ftrack-thumbnail-icon.png) +
@@ -248,7 +262,7 @@ Propagates the thumbnail of the selected entity to its parent. Propagates the thumbnail of the selected entity to its first direct children entities. --- -### RV +## RV * Entity types: All * User roles: All @@ -259,7 +273,7 @@ You must have RV player installed and licensed and have correct RV environments ::: --- -### DJV View +## DJV View * Entity types: Task, Asset Version * User roles: All @@ -269,16 +283,17 @@ You can launch DJV View with one playable component from selected entities. You You must have DJV View installed and configured in studio-config to be able use this action. ::: - -
-
- --- +
+
+ ## Open File +
-
+
![component_open-icon](assets/ftrack/ftrack-component_open-icon.png) +
From 73abae2f265f604bce9c7ffa6b405eed3796beef Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Apr 2021 09:59:58 +0200 Subject: [PATCH 26/68] removed unused function --- openpype/modules/ftrack/lib/credentials.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/openpype/modules/ftrack/lib/credentials.py b/openpype/modules/ftrack/lib/credentials.py index 16b1fb25fb..6ca9b730da 100644 --- a/openpype/modules/ftrack/lib/credentials.py +++ b/openpype/modules/ftrack/lib/credentials.py @@ -113,13 +113,6 @@ def set_env(ft_user=None, ft_api_key=None): os.environ["FTRACK_API_KEY"] = ft_api_key or "" -def get_env_credentials(): - return ( - os.environ.get("FTRACK_API_USER"), - os.environ.get("FTRACK_API_KEY") - ) - - def check_credentials(ft_user, ft_api_key, ftrack_server=None): if not ftrack_server: ftrack_server = os.environ["FTRACK_SERVER"] From d1e047d40c1d604db61db0022d280db897961ca2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Apr 2021 10:32:46 +0200 Subject: [PATCH 27/68] moved one method from credentials.py to ftrack module --- openpype/modules/ftrack/ftrack_module.py | 4 ++++ openpype/modules/ftrack/lib/credentials.py | 5 ----- openpype/modules/ftrack/tray/ftrack_tray.py | 6 +++--- openpype/modules/ftrack/tray/login_dialog.py | 6 ++++-- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/openpype/modules/ftrack/ftrack_module.py b/openpype/modules/ftrack/ftrack_module.py index e1af9c15a7..cd383cbdc6 100644 --- a/openpype/modules/ftrack/ftrack_module.py +++ b/openpype/modules/ftrack/ftrack_module.py @@ -210,3 +210,7 @@ class FtrackModule( def tray_exit(self): return self.tray_module.stop_action_server() + + def set_credentials_to_env(self, username, api_key): + os.environ["FTRACK_API_USER"] = username or "" + os.environ["FTRACK_API_KEY"] = api_key or "" diff --git a/openpype/modules/ftrack/lib/credentials.py b/openpype/modules/ftrack/lib/credentials.py index 6ca9b730da..3d9aa75e84 100644 --- a/openpype/modules/ftrack/lib/credentials.py +++ b/openpype/modules/ftrack/lib/credentials.py @@ -108,11 +108,6 @@ def clear_credentials(ft_user=None, ftrack_server=None, user=None): file.write(json.dumps(content_json)) -def set_env(ft_user=None, ft_api_key=None): - os.environ["FTRACK_API_USER"] = ft_user or "" - os.environ["FTRACK_API_KEY"] = ft_api_key or "" - - def check_credentials(ft_user, ft_api_key, ftrack_server=None): if not ftrack_server: ftrack_server = os.environ["FTRACK_SERVER"] diff --git a/openpype/modules/ftrack/tray/ftrack_tray.py b/openpype/modules/ftrack/tray/ftrack_tray.py index 9da5db835b..ee27d8b730 100644 --- a/openpype/modules/ftrack/tray/ftrack_tray.py +++ b/openpype/modules/ftrack/tray/ftrack_tray.py @@ -30,7 +30,7 @@ class FtrackTrayWrapper: self.bool_action_thread_running = False self.bool_timer_event = False - self.widget_login = login_dialog.CredentialsDialog() + self.widget_login = login_dialog.CredentialsDialog(module) self.widget_login.login_changed.connect(self.on_login_change) self.widget_login.logout_signal.connect(self.on_logout) @@ -56,7 +56,7 @@ class FtrackTrayWrapper: validation = credentials.check_credentials(ft_user, ft_api_key) if validation: self.widget_login.set_credentials(ft_user, ft_api_key) - credentials.set_env(ft_user, ft_api_key) + self.module.set_credentials_to_env(ft_user, ft_api_key) log.info("Connected to Ftrack successfully") self.on_login_change() @@ -337,7 +337,7 @@ class FtrackTrayWrapper: def changed_user(self): self.stop_action_server() - credentials.set_env() + self.module.set_credentials_to_env(None, None) self.validate() def start_timer_manager(self, data): diff --git a/openpype/modules/ftrack/tray/login_dialog.py b/openpype/modules/ftrack/tray/login_dialog.py index ca409ebcaa..ce91c6d012 100644 --- a/openpype/modules/ftrack/tray/login_dialog.py +++ b/openpype/modules/ftrack/tray/login_dialog.py @@ -14,11 +14,13 @@ class CredentialsDialog(QtWidgets.QDialog): login_changed = QtCore.Signal() logout_signal = QtCore.Signal() - def __init__(self, parent=None): + def __init__(self, module, parent=None): super(CredentialsDialog, self).__init__(parent) self.setWindowTitle("OpenPype - Ftrack Login") + self._module = module + self._login_server_thread = None self._is_logged = False self._in_advance_mode = False @@ -268,7 +270,7 @@ class CredentialsDialog(QtWidgets.QDialog): verification = credentials.check_credentials(username, api_key) if verification: credentials.save_credentials(username, api_key, False) - credentials.set_env(username, api_key) + self._module.set_credentials_to_env(username, api_key) self.set_credentials(username, api_key) self.login_changed.emit() return verification From 6339ed1b308eed1820f6a33935f674e554e3b244 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Thu, 8 Apr 2021 11:30:21 +0200 Subject: [PATCH 28/68] update packages to include coolname --- poetry.lock | 322 +++++++++++++++++++++++++------------------------ pyproject.toml | 1 + 2 files changed, 167 insertions(+), 156 deletions(-) diff --git a/poetry.lock b/poetry.lock index 6695a7bcca..767aeee500 100644 --- a/poetry.lock +++ b/poetry.lock @@ -80,7 +80,7 @@ python-dateutil = ">=2.7.0" [[package]] name = "astroid" -version = "2.5.1" +version = "2.5.2" description = "An abstract syntax tree for Python with inference support." category = "dev" optional = false @@ -123,14 +123,14 @@ tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (> [[package]] name = "autopep8" -version = "1.5.5" +version = "1.5.6" description = "A tool that automatically formats Python code to conform to the PEP 8 style guide" category = "dev" optional = false python-versions = "*" [package.dependencies] -pycodestyle = ">=2.6.0" +pycodestyle = ">=2.7.0" toml = "*" [[package]] @@ -232,6 +232,14 @@ python-versions = "*" [package.extras] test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"] +[[package]] +name = "coolname" +version = "1.1.0" +description = "Random name and slug generator" +category = "main" +optional = false +python-versions = "*" + [[package]] name = "coverage" version = "5.5" @@ -245,7 +253,7 @@ toml = ["toml"] [[package]] name = "cryptography" -version = "3.4.6" +version = "3.4.7" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." category = "main" optional = false @@ -290,7 +298,7 @@ trio = ["trio (>=0.14.0)", "sniffio (>=1.1)"] [[package]] name = "docutils" -version = "0.16" +version = "0.17" description = "Docutils -- Python Documentation Utilities" category = "dev" optional = false @@ -306,17 +314,17 @@ python-versions = "*" [[package]] name = "flake8" -version = "3.8.4" +version = "3.9.0" description = "the modular source code checker: pep8 pyflakes and co" category = "dev" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [package.dependencies] importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} mccabe = ">=0.6.0,<0.7.0" -pycodestyle = ">=2.6.0a1,<2.7.0" -pyflakes = ">=2.2.0,<2.3.0" +pycodestyle = ">=2.7.0,<2.8.0" +pyflakes = ">=2.3.0,<2.4.0" [[package]] name = "ftrack-python-api" @@ -346,7 +354,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "google-api-core" -version = "1.26.1" +version = "1.26.3" description = "Google API client core library" category = "main" optional = false @@ -384,7 +392,7 @@ uritemplate = ">=3.0.0,<4dev" [[package]] name = "google-auth" -version = "1.27.1" +version = "1.28.0" description = "Google Authentication Library" category = "main" optional = false @@ -429,7 +437,7 @@ grpc = ["grpcio (>=1.0.0)"] [[package]] name = "httplib2" -version = "0.19.0" +version = "0.19.1" description = "A comprehensive HTTP client library." category = "main" optional = false @@ -456,7 +464,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "importlib-metadata" -version = "3.7.2" +version = "3.10.0" description = "Read metadata from Python packages" category = "main" optional = false @@ -468,7 +476,7 @@ zipp = ">=0.5" [package.extras] docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] [[package]] name = "iniconfig" @@ -480,7 +488,7 @@ python-versions = "*" [[package]] name = "isort" -version = "5.7.0" +version = "5.8.0" description = "A Python utility / library to sort Python imports." category = "dev" optional = false @@ -579,11 +587,11 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pyt [[package]] name = "lazy-object-proxy" -version = "1.5.2" +version = "1.6.0" description = "A fast and thorough lazy object proxy." category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [[package]] name = "log4mongo" @@ -653,7 +661,7 @@ pyparsing = ">=2.0.2" [[package]] name = "parso" -version = "0.8.1" +version = "0.8.2" description = "A Python Parser" category = "dev" optional = false @@ -676,7 +684,7 @@ six = "*" [[package]] name = "pillow" -version = "8.1.2" +version = "8.2.0" description = "Python Imaging Library (Fork)" category = "main" optional = false @@ -698,7 +706,7 @@ dev = ["pre-commit", "tox"] [[package]] name = "protobuf" -version = "3.15.6" +version = "3.15.7" description = "Protocol Buffers" category = "main" optional = false @@ -752,7 +760,7 @@ python-versions = "*" [[package]] name = "pycodestyle" -version = "2.6.0" +version = "2.7.0" description = "Python style guide checker" category = "dev" optional = false @@ -780,7 +788,7 @@ snowballstemmer = "*" [[package]] name = "pyflakes" -version = "2.2.0" +version = "2.3.1" description = "passive checker of Python programs" category = "dev" optional = false @@ -796,14 +804,14 @@ python-versions = ">=3.5" [[package]] name = "pylint" -version = "2.7.2" +version = "2.7.4" description = "python code static checker" category = "dev" optional = false python-versions = "~=3.6" [package.dependencies] -astroid = ">=2.5.1,<2.6" +astroid = ">=2.5.2,<2.7" colorama = {version = "*", markers = "sys_platform == \"win32\""} isort = ">=4.2.5,<6" mccabe = ">=0.6,<0.7" @@ -921,7 +929,7 @@ python-versions = ">=3.5" [[package]] name = "pytest" -version = "6.2.2" +version = "6.2.3" description = "pytest: simple powerful testing with Python" category = "dev" optional = false @@ -1112,7 +1120,7 @@ python-versions = "*" [[package]] name = "sphinx" -version = "3.5.2" +version = "3.5.3" description = "Python documentation generator" category = "dev" optional = false @@ -1271,7 +1279,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "tqdm" -version = "4.59.0" +version = "4.60.0" description = "Fast, Extensible Progress Meter" category = "dev" optional = false @@ -1308,16 +1316,16 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "urllib3" -version = "1.26.3" +version = "1.26.4" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" [package.extras] -brotli = ["brotlipy (>=0.6.0)"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +brotli = ["brotlipy (>=0.6.0)"] [[package]] name = "wcwidth" @@ -1348,7 +1356,7 @@ python-versions = "*" [[package]] name = "wsrpc-aiohttp" -version = "3.1.1" +version = "3.1.2" description = "WSRPC is the RPC over WebSocket for aiohttp" category = "main" optional = false @@ -1391,7 +1399,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pyt [metadata] lock-version = "1.1" python-versions = "3.7.*" -content-hash = "4905515073ad2bf2a8517d513d68e48669b6a829f24e540b2dd60bc70cbea26b" +content-hash = "a8c9915ce3096b74b9328a632911a759780844d368fa1d6d0fbd7c5d7d4536cf" [metadata.files] acre = [] @@ -1455,8 +1463,8 @@ arrow = [ {file = "arrow-0.17.0.tar.gz", hash = "sha256:ff08d10cda1d36c68657d6ad20d74fbea493d980f8b2d45344e00d6ed2bf6ed4"}, ] astroid = [ - {file = "astroid-2.5.1-py3-none-any.whl", hash = "sha256:21d735aab248253531bb0f1e1e6d068f0ee23533e18ae8a6171ff892b98297cf"}, - {file = "astroid-2.5.1.tar.gz", hash = "sha256:cfc35498ee64017be059ceffab0a25bedf7548ab76f2bea691c5565896e7128d"}, + {file = "astroid-2.5.2-py3-none-any.whl", hash = "sha256:cd80bf957c49765dce6d92c43163ff9d2abc43132ce64d4b1b47717c6d2522df"}, + {file = "astroid-2.5.2.tar.gz", hash = "sha256:6b0ed1af831570e500e2437625979eaa3b36011f66ddfc4ce930128610258ca9"}, ] async-timeout = [ {file = "async-timeout-3.0.1.tar.gz", hash = "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f"}, @@ -1471,8 +1479,8 @@ attrs = [ {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, ] autopep8 = [ - {file = "autopep8-1.5.5-py2.py3-none-any.whl", hash = "sha256:9e136c472c475f4ee4978b51a88a494bfcd4e3ed17950a44a988d9e434837bea"}, - {file = "autopep8-1.5.5.tar.gz", hash = "sha256:cae4bc0fb616408191af41d062d7ec7ef8679c7f27b068875ca3a9e2878d5443"}, + {file = "autopep8-1.5.6-py2.py3-none-any.whl", hash = "sha256:f01b06a6808bc31698db907761e5890eb2295e287af53f6693b39ce55454034a"}, + {file = "autopep8-1.5.6.tar.gz", hash = "sha256:5454e6e9a3d02aae38f866eec0d9a7de4ab9f93c10a273fb0340f3d6d09f7514"}, ] babel = [ {file = "Babel-2.9.0-py2.py3-none-any.whl", hash = "sha256:9d35c22fcc79893c3ecc85ac4a56cde1ecf3f19c540bba0922308a6c06ca6fa5"}, @@ -1549,6 +1557,10 @@ commonmark = [ {file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"}, {file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"}, ] +coolname = [ + {file = "coolname-1.1.0-py2.py3-none-any.whl", hash = "sha256:e6a83a0ac88640f4f3d2070438dbe112fe80cfebc119c93bd402976ec84c0978"}, + {file = "coolname-1.1.0.tar.gz", hash = "sha256:410fe6ea9999bf96f2856ef0c726d5f38782bbefb7bb1aca0e91e0dc98ed09e3"}, +] coverage = [ {file = "coverage-5.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf"}, {file = "coverage-5.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b"}, @@ -1604,18 +1616,18 @@ coverage = [ {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"}, ] cryptography = [ - {file = "cryptography-3.4.6-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:57ad77d32917bc55299b16d3b996ffa42a1c73c6cfa829b14043c561288d2799"}, - {file = "cryptography-3.4.6-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:4169a27b818de4a1860720108b55a2801f32b6ae79e7f99c00d79f2a2822eeb7"}, - {file = "cryptography-3.4.6-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:93cfe5b7ff006de13e1e89830810ecbd014791b042cbe5eec253be11ac2b28f3"}, - {file = "cryptography-3.4.6-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:5ecf2bcb34d17415e89b546dbb44e73080f747e504273e4d4987630493cded1b"}, - {file = "cryptography-3.4.6-cp36-abi3-manylinux2014_x86_64.whl", hash = "sha256:fec7fb46b10da10d9e1d078d1ff8ed9e05ae14f431fdbd11145edd0550b9a964"}, - {file = "cryptography-3.4.6-cp36-abi3-win32.whl", hash = "sha256:df186fcbf86dc1ce56305becb8434e4b6b7504bc724b71ad7a3239e0c9d14ef2"}, - {file = "cryptography-3.4.6-cp36-abi3-win_amd64.whl", hash = "sha256:66b57a9ca4b3221d51b237094b0303843b914b7d5afd4349970bb26518e350b0"}, - {file = "cryptography-3.4.6-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:066bc53f052dfeda2f2d7c195cf16fb3e5ff13e1b6b7415b468514b40b381a5b"}, - {file = "cryptography-3.4.6-pp36-pypy36_pp73-manylinux2014_x86_64.whl", hash = "sha256:600cf9bfe75e96d965509a4c0b2b183f74a4fa6f5331dcb40fb7b77b7c2484df"}, - {file = "cryptography-3.4.6-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:0923ba600d00718d63a3976f23cab19aef10c1765038945628cd9be047ad0336"}, - {file = "cryptography-3.4.6-pp37-pypy37_pp73-manylinux2014_x86_64.whl", hash = "sha256:9e98b452132963678e3ac6c73f7010fe53adf72209a32854d55690acac3f6724"}, - {file = "cryptography-3.4.6.tar.gz", hash = "sha256:2d32223e5b0ee02943f32b19245b61a62db83a882f0e76cc564e1cec60d48f87"}, + {file = "cryptography-3.4.7-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:3d8427734c781ea5f1b41d6589c293089704d4759e34597dce91014ac125aad1"}, + {file = "cryptography-3.4.7-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:8e56e16617872b0957d1c9742a3f94b43533447fd78321514abbe7db216aa250"}, + {file = "cryptography-3.4.7-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:37340614f8a5d2fb9aeea67fd159bfe4f5f4ed535b1090ce8ec428b2f15a11f2"}, + {file = "cryptography-3.4.7-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:240f5c21aef0b73f40bb9f78d2caff73186700bf1bc6b94285699aff98cc16c6"}, + {file = "cryptography-3.4.7-cp36-abi3-manylinux2014_x86_64.whl", hash = "sha256:1e056c28420c072c5e3cb36e2b23ee55e260cb04eee08f702e0edfec3fb51959"}, + {file = "cryptography-3.4.7-cp36-abi3-win32.whl", hash = "sha256:0f1212a66329c80d68aeeb39b8a16d54ef57071bf22ff4e521657b27372e327d"}, + {file = "cryptography-3.4.7-cp36-abi3-win_amd64.whl", hash = "sha256:de4e5f7f68220d92b7637fc99847475b59154b7a1b3868fb7385337af54ac9ca"}, + {file = "cryptography-3.4.7-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:26965837447f9c82f1855e0bc8bc4fb910240b6e0d16a664bb722df3b5b06873"}, + {file = "cryptography-3.4.7-pp36-pypy36_pp73-manylinux2014_x86_64.whl", hash = "sha256:eb8cc2afe8b05acbd84a43905832ec78e7b3873fb124ca190f574dca7389a87d"}, + {file = "cryptography-3.4.7-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:7ec5d3b029f5fa2b179325908b9cd93db28ab7b85bb6c1db56b10e0b54235177"}, + {file = "cryptography-3.4.7-pp37-pypy37_pp73-manylinux2014_x86_64.whl", hash = "sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9"}, + {file = "cryptography-3.4.7.tar.gz", hash = "sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713"}, ] cx-freeze = [ {file = "cx_Freeze-6.5.3-cp36-cp36m-win32.whl", hash = "sha256:0a1babae574546b622303da53e1a9829aa3a7e53e62b41eb260250220f83164b"}, @@ -1633,15 +1645,15 @@ dnspython = [ {file = "dnspython-2.1.0.zip", hash = "sha256:e4a87f0b573201a0f3727fa18a516b055fd1107e0e5477cded4a2de497df1dd4"}, ] docutils = [ - {file = "docutils-0.16-py2.py3-none-any.whl", hash = "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af"}, - {file = "docutils-0.16.tar.gz", hash = "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"}, + {file = "docutils-0.17-py2.py3-none-any.whl", hash = "sha256:a71042bb7207c03d5647f280427f14bfbd1a65c9eb84f4b341d85fafb6bb4bdf"}, + {file = "docutils-0.17.tar.gz", hash = "sha256:e2ffeea817964356ba4470efba7c2f42b6b0de0b04e66378507e3e2504bbff4c"}, ] evdev = [ {file = "evdev-1.4.0.tar.gz", hash = "sha256:8782740eb1a86b187334c07feb5127d3faa0b236e113206dfe3ae8f77fb1aaf1"}, ] flake8 = [ - {file = "flake8-3.8.4-py2.py3-none-any.whl", hash = "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839"}, - {file = "flake8-3.8.4.tar.gz", hash = "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b"}, + {file = "flake8-3.9.0-py2.py3-none-any.whl", hash = "sha256:12d05ab02614b6aee8df7c36b97d1a3b2372761222b19b58621355e82acddcff"}, + {file = "flake8-3.9.0.tar.gz", hash = "sha256:78873e372b12b093da7b5e5ed302e8ad9e988b38b063b61ad937f26ca58fc5f0"}, ] ftrack-python-api = [ {file = "ftrack-python-api-2.0.0.tar.gz", hash = "sha256:dd6f02c31daf5a10078196dc9eac4671e4297c762fbbf4df98de668ac12281d9"}, @@ -1651,16 +1663,16 @@ future = [ {file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"}, ] google-api-core = [ - {file = "google-api-core-1.26.1.tar.gz", hash = "sha256:23b0df512c4cc8729793f8992edb350e3211f5fd0ec007afb1599864b421beef"}, - {file = "google_api_core-1.26.1-py2.py3-none-any.whl", hash = "sha256:c383206f0f87545d3e658c4f8dc3b18a8457610fdbd791a15757c5b42d1e0e7f"}, + {file = "google-api-core-1.26.3.tar.gz", hash = "sha256:b914345c7ea23861162693a27703bab804a55504f7e6e9abcaff174d80df32ac"}, + {file = "google_api_core-1.26.3-py2.py3-none-any.whl", hash = "sha256:099762d4b4018cd536bcf85136bf337957da438807572db52f21dc61251be089"}, ] google-api-python-client = [ {file = "google-api-python-client-1.12.8.tar.gz", hash = "sha256:f3b9684442eec2cfe9f9bb48e796ef919456b82142c7528c5fd527e5224f08bb"}, {file = "google_api_python_client-1.12.8-py2.py3-none-any.whl", hash = "sha256:3c4c4ca46b5c21196bec7ee93453443e477d82cbfa79234d1ce0645f81170eaf"}, ] google-auth = [ - {file = "google-auth-1.27.1.tar.gz", hash = "sha256:d8958af6968e4ecd599f82357ebcfeb126f826ed0656126ad68416f810f7531e"}, - {file = "google_auth-1.27.1-py2.py3-none-any.whl", hash = "sha256:63a5636d7eacfe6ef5b7e36e112b3149fa1c5b5ad77dd6df54910459bcd6b89f"}, + {file = "google-auth-1.28.0.tar.gz", hash = "sha256:9bd436d19ab047001a1340720d2b629eb96dd503258c524921ec2af3ee88a80e"}, + {file = "google_auth-1.28.0-py2.py3-none-any.whl", hash = "sha256:dcaba3aa9d4e0e96fd945bf25a86b6f878fcb05770b67adbeb50a63ca4d28a5e"}, ] google-auth-httplib2 = [ {file = "google-auth-httplib2-0.1.0.tar.gz", hash = "sha256:a07c39fd632becacd3f07718dfd6021bf396978f03ad3ce4321d060015cc30ac"}, @@ -1671,8 +1683,8 @@ googleapis-common-protos = [ {file = "googleapis_common_protos-1.53.0-py2.py3-none-any.whl", hash = "sha256:f6d561ab8fb16b30020b940e2dd01cd80082f4762fa9f3ee670f4419b4b8dbd0"}, ] httplib2 = [ - {file = "httplib2-0.19.0-py3-none-any.whl", hash = "sha256:749c32603f9bf16c1277f59531d502e8f1c2ca19901ae653b49c4ed698f0820e"}, - {file = "httplib2-0.19.0.tar.gz", hash = "sha256:e0d428dad43c72dbce7d163b7753ffc7a39c097e6788ef10f4198db69b92f08e"}, + {file = "httplib2-0.19.1-py3-none-any.whl", hash = "sha256:2ad195faf9faf079723f6714926e9a9061f694d07724b846658ce08d40f522b4"}, + {file = "httplib2-0.19.1.tar.gz", hash = "sha256:0b12617eeca7433d4c396a100eaecfa4b08ee99aa881e6df6e257a7aad5d533d"}, ] idna = [ {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, @@ -1683,16 +1695,16 @@ imagesize = [ {file = "imagesize-1.2.0.tar.gz", hash = "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"}, ] importlib-metadata = [ - {file = "importlib_metadata-3.7.2-py3-none-any.whl", hash = "sha256:407d13f55dc6f2a844e62325d18ad7019a436c4bfcaee34cda35f2be6e7c3e34"}, - {file = "importlib_metadata-3.7.2.tar.gz", hash = "sha256:18d5ff601069f98d5d605b6a4b50c18a34811d655c55548adc833e687289acde"}, + {file = "importlib_metadata-3.10.0-py3-none-any.whl", hash = "sha256:d2d46ef77ffc85cbf7dac7e81dd663fde71c45326131bea8033b9bad42268ebe"}, + {file = "importlib_metadata-3.10.0.tar.gz", hash = "sha256:c9db46394197244adf2f0b08ec5bc3cf16757e9590b02af1fca085c16c0d600a"}, ] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] isort = [ - {file = "isort-5.7.0-py3-none-any.whl", hash = "sha256:fff4f0c04e1825522ce6949973e83110a6e907750cd92d128b0d14aaaadbffdc"}, - {file = "isort-5.7.0.tar.gz", hash = "sha256:c729845434366216d320e936b8ad6f9d681aab72dc7cbc2d51bedc3582f3ad1e"}, + {file = "isort-5.8.0-py3-none-any.whl", hash = "sha256:2bb1680aad211e3c9944dbce1d4ba09a989f04e238296c87fe2139faa26d655d"}, + {file = "isort-5.8.0.tar.gz", hash = "sha256:0a943902919f65c5684ac4e0154b1ad4fac6dcaa5d9f3426b732f1c8b5419be6"}, ] jedi = [ {file = "jedi-0.13.3-py2.py3-none-any.whl", hash = "sha256:2c6bcd9545c7d6440951b12b44d373479bf18123a401a52025cf98563fbd826c"}, @@ -1719,30 +1731,28 @@ keyring = [ {file = "keyring-22.4.0.tar.gz", hash = "sha256:d981e02d134cc3d636a716fbc3ca967bc9609bae5dc21b0063e4409355993ddf"}, ] lazy-object-proxy = [ - {file = "lazy-object-proxy-1.5.2.tar.gz", hash = "sha256:5944a9b95e97de1980c65f03b79b356f30a43de48682b8bdd90aa5089f0ec1f4"}, - {file = "lazy_object_proxy-1.5.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:e960e8be509e8d6d618300a6c189555c24efde63e85acaf0b14b2cd1ac743315"}, - {file = "lazy_object_proxy-1.5.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:522b7c94b524389f4a4094c4bf04c2b02228454ddd17c1a9b2801fac1d754871"}, - {file = "lazy_object_proxy-1.5.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:3782931963dc89e0e9a0ae4348b44762e868ea280e4f8c233b537852a8996ab9"}, - {file = "lazy_object_proxy-1.5.2-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:429c4d1862f3fc37cd56304d880f2eae5bd0da83bdef889f3bd66458aac49128"}, - {file = "lazy_object_proxy-1.5.2-cp35-cp35m-win32.whl", hash = "sha256:cd1bdace1a8762534e9a36c073cd54e97d517a17d69a17985961265be6d22847"}, - {file = "lazy_object_proxy-1.5.2-cp35-cp35m-win_amd64.whl", hash = "sha256:ddbdcd10eb999d7ab292677f588b658372aadb9a52790f82484a37127a390108"}, - {file = "lazy_object_proxy-1.5.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ecb5dd5990cec6e7f5c9c1124a37cb2c710c6d69b0c1a5c4aa4b35eba0ada068"}, - {file = "lazy_object_proxy-1.5.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:b6577f15d5516d7d209c1a8cde23062c0f10625f19e8dc9fb59268859778d7d7"}, - {file = "lazy_object_proxy-1.5.2-cp36-cp36m-win32.whl", hash = "sha256:c8fe2d6ff0ff583784039d0255ea7da076efd08507f2be6f68583b0da32e3afb"}, - {file = "lazy_object_proxy-1.5.2-cp36-cp36m-win_amd64.whl", hash = "sha256:fa5b2dee0e231fa4ad117be114251bdfe6afe39213bd629d43deb117b6a6c40a"}, - {file = "lazy_object_proxy-1.5.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:1d33d6f789697f401b75ce08e73b1de567b947740f768376631079290118ad39"}, - {file = "lazy_object_proxy-1.5.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:57fb5c5504ddd45ed420b5b6461a78f58cbb0c1b0cbd9cd5a43ad30a4a3ee4d0"}, - {file = "lazy_object_proxy-1.5.2-cp37-cp37m-win32.whl", hash = "sha256:e7273c64bccfd9310e9601b8f4511d84730239516bada26a0c9846c9697617ef"}, - {file = "lazy_object_proxy-1.5.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6f4e5e68b7af950ed7fdb594b3f19a0014a3ace0fedb86acb896e140ffb24302"}, - {file = "lazy_object_proxy-1.5.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:cadfa2c2cf54d35d13dc8d231253b7985b97d629ab9ca6e7d672c35539d38163"}, - {file = "lazy_object_proxy-1.5.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:e7428977763150b4cf83255625a80a23dfdc94d43be7791ce90799d446b4e26f"}, - {file = "lazy_object_proxy-1.5.2-cp38-cp38-win32.whl", hash = "sha256:2f2de8f8ac0be3e40d17730e0600619d35c78c13a099ea91ef7fb4ad944ce694"}, - {file = "lazy_object_proxy-1.5.2-cp38-cp38-win_amd64.whl", hash = "sha256:38c3865bd220bd983fcaa9aa11462619e84a71233bafd9c880f7b1cb753ca7fa"}, - {file = "lazy_object_proxy-1.5.2-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:8a44e9901c0555f95ac401377032f6e6af66d8fc1fbfad77a7a8b1a826e0b93c"}, - {file = "lazy_object_proxy-1.5.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:fa7fb7973c622b9e725bee1db569d2c2ee64d2f9a089201c5e8185d482c7352d"}, - {file = "lazy_object_proxy-1.5.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:71a1ef23f22fa8437974b2d60fedb947c99a957ad625f83f43fd3de70f77f458"}, - {file = "lazy_object_proxy-1.5.2-cp39-cp39-win32.whl", hash = "sha256:ef3f5e288aa57b73b034ce9c1f1ac753d968f9069cd0742d1d69c698a0167166"}, - {file = "lazy_object_proxy-1.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:37d9c34b96cca6787fe014aeb651217944a967a5b165e2cacb6b858d2997ab84"}, + {file = "lazy-object-proxy-1.6.0.tar.gz", hash = "sha256:489000d368377571c6f982fba6497f2aa13c6d1facc40660963da62f5c379726"}, + {file = "lazy_object_proxy-1.6.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:c6938967f8528b3668622a9ed3b31d145fab161a32f5891ea7b84f6b790be05b"}, + {file = "lazy_object_proxy-1.6.0-cp27-cp27m-win32.whl", hash = "sha256:ebfd274dcd5133e0afae738e6d9da4323c3eb021b3e13052d8cbd0e457b1256e"}, + {file = "lazy_object_proxy-1.6.0-cp27-cp27m-win_amd64.whl", hash = "sha256:ed361bb83436f117f9917d282a456f9e5009ea12fd6de8742d1a4752c3017e93"}, + {file = "lazy_object_proxy-1.6.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d900d949b707778696fdf01036f58c9876a0d8bfe116e8d220cfd4b15f14e741"}, + {file = "lazy_object_proxy-1.6.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5743a5ab42ae40caa8421b320ebf3a998f89c85cdc8376d6b2e00bd12bd1b587"}, + {file = "lazy_object_proxy-1.6.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:bf34e368e8dd976423396555078def5cfc3039ebc6fc06d1ae2c5a65eebbcde4"}, + {file = "lazy_object_proxy-1.6.0-cp36-cp36m-win32.whl", hash = "sha256:b579f8acbf2bdd9ea200b1d5dea36abd93cabf56cf626ab9c744a432e15c815f"}, + {file = "lazy_object_proxy-1.6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:4f60460e9f1eb632584c9685bccea152f4ac2130e299784dbaf9fae9f49891b3"}, + {file = "lazy_object_proxy-1.6.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d7124f52f3bd259f510651450e18e0fd081ed82f3c08541dffc7b94b883aa981"}, + {file = "lazy_object_proxy-1.6.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:22ddd618cefe54305df49e4c069fa65715be4ad0e78e8d252a33debf00f6ede2"}, + {file = "lazy_object_proxy-1.6.0-cp37-cp37m-win32.whl", hash = "sha256:9d397bf41caad3f489e10774667310d73cb9c4258e9aed94b9ec734b34b495fd"}, + {file = "lazy_object_proxy-1.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:24a5045889cc2729033b3e604d496c2b6f588c754f7a62027ad4437a7ecc4837"}, + {file = "lazy_object_proxy-1.6.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:17e0967ba374fc24141738c69736da90e94419338fd4c7c7bef01ee26b339653"}, + {file = "lazy_object_proxy-1.6.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:410283732af311b51b837894fa2f24f2c0039aa7f220135192b38fcc42bd43d3"}, + {file = "lazy_object_proxy-1.6.0-cp38-cp38-win32.whl", hash = "sha256:85fb7608121fd5621cc4377a8961d0b32ccf84a7285b4f1d21988b2eae2868e8"}, + {file = "lazy_object_proxy-1.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:d1c2676e3d840852a2de7c7d5d76407c772927addff8d742b9808fe0afccebdf"}, + {file = "lazy_object_proxy-1.6.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:b865b01a2e7f96db0c5d12cfea590f98d8c5ba64ad222300d93ce6ff9138bcad"}, + {file = "lazy_object_proxy-1.6.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:4732c765372bd78a2d6b2150a6e99d00a78ec963375f236979c0626b97ed8e43"}, + {file = "lazy_object_proxy-1.6.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:9698110e36e2df951c7c36b6729e96429c9c32b3331989ef19976592c5f3c77a"}, + {file = "lazy_object_proxy-1.6.0-cp39-cp39-win32.whl", hash = "sha256:1fee665d2638491f4d6e55bd483e15ef21f6c8c2095f235fef72601021e64f61"}, + {file = "lazy_object_proxy-1.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:f5144c75445ae3ca2057faac03fda5a902eff196702b0a24daf1d6ce0650514b"}, ] log4mongo = [ {file = "log4mongo-1.7.0.tar.gz", hash = "sha256:dc374617206162a0b14167fbb5feac01dbef587539a235dadba6200362984a68"}, @@ -1850,73 +1860,73 @@ packaging = [ {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, ] parso = [ - {file = "parso-0.8.1-py2.py3-none-any.whl", hash = "sha256:15b00182f472319383252c18d5913b69269590616c947747bc50bf4ac768f410"}, - {file = "parso-0.8.1.tar.gz", hash = "sha256:8519430ad07087d4c997fda3a7918f7cfa27cb58972a8c89c2a0295a1c940e9e"}, + {file = "parso-0.8.2-py2.py3-none-any.whl", hash = "sha256:a8c4922db71e4fdb90e0d0bc6e50f9b273d3397925e5e60a717e719201778d22"}, + {file = "parso-0.8.2.tar.gz", hash = "sha256:12b83492c6239ce32ff5eed6d3639d6a536170723c6f3f1506869f1ace413398"}, ] pathlib2 = [ {file = "pathlib2-2.3.5-py2.py3-none-any.whl", hash = "sha256:0ec8205a157c80d7acc301c0b18fbd5d44fe655968f5d947b6ecef5290fc35db"}, {file = "pathlib2-2.3.5.tar.gz", hash = "sha256:6cd9a47b597b37cc57de1c05e56fb1a1c9cc9fab04fe78c29acd090418529868"}, ] pillow = [ - {file = "Pillow-8.1.2-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:5cf03b9534aca63b192856aa601c68d0764810857786ea5da652581f3a44c2b0"}, - {file = "Pillow-8.1.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:f91b50ad88048d795c0ad004abbe1390aa1882073b1dca10bfd55d0b8cf18ec5"}, - {file = "Pillow-8.1.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5762ebb4436f46b566fc6351d67a9b5386b5e5de4e58fdaa18a1c83e0e20f1a8"}, - {file = "Pillow-8.1.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:e2cd8ac157c1e5ae88b6dd790648ee5d2777e76f1e5c7d184eaddb2938594f34"}, - {file = "Pillow-8.1.2-cp36-cp36m-win32.whl", hash = "sha256:72027ebf682abc9bafd93b43edc44279f641e8996fb2945104471419113cfc71"}, - {file = "Pillow-8.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:d1d6bca39bb6dd94fba23cdb3eeaea5e30c7717c5343004d900e2a63b132c341"}, - {file = "Pillow-8.1.2-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:90882c6f084ef68b71bba190209a734bf90abb82ab5e8f64444c71d5974008c6"}, - {file = "Pillow-8.1.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:89e4c757a91b8c55d97c91fa09c69b3677c227b942fa749e9a66eef602f59c28"}, - {file = "Pillow-8.1.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:8c4e32218c764bc27fe49b7328195579581aa419920edcc321c4cb877c65258d"}, - {file = "Pillow-8.1.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:a01da2c266d9868c4f91a9c6faf47a251f23b9a862dce81d2ff583135206f5be"}, - {file = "Pillow-8.1.2-cp37-cp37m-win32.whl", hash = "sha256:30d33a1a6400132e6f521640dd3f64578ac9bfb79a619416d7e8802b4ce1dd55"}, - {file = "Pillow-8.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:71b01ee69e7df527439d7752a2ce8fb89e19a32df484a308eca3e81f673d3a03"}, - {file = "Pillow-8.1.2-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:5a2d957eb4aba9d48170b8fe6538ec1fbc2119ffe6373782c03d8acad3323f2e"}, - {file = "Pillow-8.1.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:87f42c976f91ca2fc21a3293e25bd3cd895918597db1b95b93cbd949f7d019ce"}, - {file = "Pillow-8.1.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:15306d71a1e96d7e271fd2a0737038b5a92ca2978d2e38b6ced7966583e3d5af"}, - {file = "Pillow-8.1.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:71f31ee4df3d5e0b366dd362007740106d3210fb6a56ec4b581a5324ba254f06"}, - {file = "Pillow-8.1.2-cp38-cp38-win32.whl", hash = "sha256:98afcac3205d31ab6a10c5006b0cf040d0026a68ec051edd3517b776c1d78b09"}, - {file = "Pillow-8.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:328240f7dddf77783e72d5ed79899a6b48bc6681f8d1f6001f55933cb4905060"}, - {file = "Pillow-8.1.2-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:bead24c0ae3f1f6afcb915a057943ccf65fc755d11a1410a909c1fefb6c06ad1"}, - {file = "Pillow-8.1.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:81b3716cc9744ffdf76b39afb6247eae754186838cedad0b0ac63b2571253fe6"}, - {file = "Pillow-8.1.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:63cd413ac52ee3f67057223d363f4f82ce966e64906aea046daf46695e3c8238"}, - {file = "Pillow-8.1.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:8565355a29655b28fdc2c666fd9a3890fe5edc6639d128814fafecfae2d70910"}, - {file = "Pillow-8.1.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:1940fc4d361f9cc7e558d6f56ff38d7351b53052fd7911f4b60cd7bc091ea3b1"}, - {file = "Pillow-8.1.2-cp39-cp39-win32.whl", hash = "sha256:46c2bcf8e1e75d154e78417b3e3c64e96def738c2a25435e74909e127a8cba5e"}, - {file = "Pillow-8.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:aeab4cd016e11e7aa5cfc49dcff8e51561fa64818a0be86efa82c7038e9369d0"}, - {file = "Pillow-8.1.2-pp36-pypy36_pp73-macosx_10_10_x86_64.whl", hash = "sha256:74cd9aa648ed6dd25e572453eb09b08817a1e3d9f8d1bd4d8403d99e42ea790b"}, - {file = "Pillow-8.1.2-pp36-pypy36_pp73-manylinux2010_i686.whl", hash = "sha256:e5739ae63636a52b706a0facec77b2b58e485637e1638202556156e424a02dc2"}, - {file = "Pillow-8.1.2-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:903293320efe2466c1ab3509a33d6b866dc850cfd0c5d9cc92632014cec185fb"}, - {file = "Pillow-8.1.2-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:5daba2b40782c1c5157a788ec4454067c6616f5a0c1b70e26ac326a880c2d328"}, - {file = "Pillow-8.1.2-pp37-pypy37_pp73-manylinux2010_i686.whl", hash = "sha256:1f93f2fe211f1ef75e6f589327f4d4f8545d5c8e826231b042b483d8383e8a7c"}, - {file = "Pillow-8.1.2-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:6efac40344d8f668b6c4533ae02a48d52fd852ef0654cc6f19f6ac146399c733"}, - {file = "Pillow-8.1.2-pp37-pypy37_pp73-win32.whl", hash = "sha256:f36c3ff63d6fc509ce599a2f5b0d0732189eed653420e7294c039d342c6e204a"}, - {file = "Pillow-8.1.2.tar.gz", hash = "sha256:b07c660e014852d98a00a91adfbe25033898a9d90a8f39beb2437d22a203fc44"}, + {file = "Pillow-8.2.0-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:dc38f57d8f20f06dd7c3161c59ca2c86893632623f33a42d592f097b00f720a9"}, + {file = "Pillow-8.2.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a013cbe25d20c2e0c4e85a9daf438f85121a4d0344ddc76e33fd7e3965d9af4b"}, + {file = "Pillow-8.2.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8bb1e155a74e1bfbacd84555ea62fa21c58e0b4e7e6b20e4447b8d07990ac78b"}, + {file = "Pillow-8.2.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c5236606e8570542ed424849f7852a0ff0bce2c4c8d0ba05cc202a5a9c97dee9"}, + {file = "Pillow-8.2.0-cp36-cp36m-win32.whl", hash = "sha256:12e5e7471f9b637762453da74e390e56cc43e486a88289995c1f4c1dc0bfe727"}, + {file = "Pillow-8.2.0-cp36-cp36m-win_amd64.whl", hash = "sha256:5afe6b237a0b81bd54b53f835a153770802f164c5570bab5e005aad693dab87f"}, + {file = "Pillow-8.2.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:cb7a09e173903541fa888ba010c345893cd9fc1b5891aaf060f6ca77b6a3722d"}, + {file = "Pillow-8.2.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0d19d70ee7c2ba97631bae1e7d4725cdb2ecf238178096e8c82ee481e189168a"}, + {file = "Pillow-8.2.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:083781abd261bdabf090ad07bb69f8f5599943ddb539d64497ed021b2a67e5a9"}, + {file = "Pillow-8.2.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:c6b39294464b03457f9064e98c124e09008b35a62e3189d3513e5148611c9388"}, + {file = "Pillow-8.2.0-cp37-cp37m-win32.whl", hash = "sha256:01425106e4e8cee195a411f729cff2a7d61813b0b11737c12bd5991f5f14bcd5"}, + {file = "Pillow-8.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3b570f84a6161cf8865c4e08adf629441f56e32f180f7aa4ccbd2e0a5a02cba2"}, + {file = "Pillow-8.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:031a6c88c77d08aab84fecc05c3cde8414cd6f8406f4d2b16fed1e97634cc8a4"}, + {file = "Pillow-8.2.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:66cc56579fd91f517290ab02c51e3a80f581aba45fd924fcdee01fa06e635812"}, + {file = "Pillow-8.2.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6c32cc3145928c4305d142ebec682419a6c0a8ce9e33db900027ddca1ec39178"}, + {file = "Pillow-8.2.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:624b977355cde8b065f6d51b98497d6cd5fbdd4f36405f7a8790e3376125e2bb"}, + {file = "Pillow-8.2.0-cp38-cp38-win32.whl", hash = "sha256:5cbf3e3b1014dddc45496e8cf38b9f099c95a326275885199f427825c6522232"}, + {file = "Pillow-8.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:463822e2f0d81459e113372a168f2ff59723e78528f91f0bd25680ac185cf797"}, + {file = "Pillow-8.2.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:95d5ef984eff897850f3a83883363da64aae1000e79cb3c321915468e8c6add5"}, + {file = "Pillow-8.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b91c36492a4bbb1ee855b7d16fe51379e5f96b85692dc8210831fbb24c43e484"}, + {file = "Pillow-8.2.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d68cb92c408261f806b15923834203f024110a2e2872ecb0bd2a110f89d3c602"}, + {file = "Pillow-8.2.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f217c3954ce5fd88303fc0c317af55d5e0204106d86dea17eb8205700d47dec2"}, + {file = "Pillow-8.2.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:5b70110acb39f3aff6b74cf09bb4169b167e2660dabc304c1e25b6555fa781ef"}, + {file = "Pillow-8.2.0-cp39-cp39-win32.whl", hash = "sha256:a7d5e9fad90eff8f6f6106d3b98b553a88b6f976e51fce287192a5d2d5363713"}, + {file = "Pillow-8.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:238c197fc275b475e87c1453b05b467d2d02c2915fdfdd4af126145ff2e4610c"}, + {file = "Pillow-8.2.0-pp36-pypy36_pp73-macosx_10_10_x86_64.whl", hash = "sha256:0e04d61f0064b545b989126197930807c86bcbd4534d39168f4aa5fda39bb8f9"}, + {file = "Pillow-8.2.0-pp36-pypy36_pp73-manylinux2010_i686.whl", hash = "sha256:63728564c1410d99e6d1ae8e3b810fe012bc440952168af0a2877e8ff5ab96b9"}, + {file = "Pillow-8.2.0-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:c03c07ed32c5324939b19e36ae5f75c660c81461e312a41aea30acdd46f93a7c"}, + {file = "Pillow-8.2.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:4d98abdd6b1e3bf1a1cbb14c3895226816e666749ac040c4e2554231068c639b"}, + {file = "Pillow-8.2.0-pp37-pypy37_pp73-manylinux2010_i686.whl", hash = "sha256:aac00e4bc94d1b7813fe882c28990c1bc2f9d0e1aa765a5f2b516e8a6a16a9e4"}, + {file = "Pillow-8.2.0-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:22fd0f42ad15dfdde6c581347eaa4adb9a6fc4b865f90b23378aa7914895e120"}, + {file = "Pillow-8.2.0-pp37-pypy37_pp73-win32.whl", hash = "sha256:e98eca29a05913e82177b3ba3d198b1728e164869c613d76d0de4bde6768a50e"}, + {file = "Pillow-8.2.0.tar.gz", hash = "sha256:a787ab10d7bb5494e5f76536ac460741788f1fbce851068d73a87ca7c35fc3e1"}, ] pluggy = [ {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, ] protobuf = [ - {file = "protobuf-3.15.6-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1771ef20e88759c4d81db213e89b7a1fc53937968e12af6603c658ee4bcbfa38"}, - {file = "protobuf-3.15.6-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:1a66261a402d05c8ad8c1fde8631837307bf8d7e7740a4f3941fc3277c2e1528"}, - {file = "protobuf-3.15.6-cp35-cp35m-macosx_10_9_intel.whl", hash = "sha256:eac23a3e56175b710f3da9a9e8e2aa571891fbec60e0c5a06db1c7b1613b5cfd"}, - {file = "protobuf-3.15.6-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9ec220d90eda8bb7a7a1434a8aed4fe26d7e648c1a051c2885f3f5725b6aa71a"}, - {file = "protobuf-3.15.6-cp35-cp35m-win32.whl", hash = "sha256:88d8f21d1ac205eedb6dea943f8204ed08201b081dba2a966ab5612788b9bb1e"}, - {file = "protobuf-3.15.6-cp35-cp35m-win_amd64.whl", hash = "sha256:eaada29bbf087dea7d8bce4d1d604fc768749e8809e9c295922accd7c8fce4d5"}, - {file = "protobuf-3.15.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:256c0b2e338c1f3228d3280707606fe5531fde85ab9d704cde6fdeb55112531f"}, - {file = "protobuf-3.15.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:b9069e45b6e78412fba4a314ea38b4a478686060acf470d2b131b3a2c50484ec"}, - {file = "protobuf-3.15.6-cp36-cp36m-win32.whl", hash = "sha256:24f4697f57b8520c897a401b7f9a5ae45c369e22c572e305dfaf8053ecb49687"}, - {file = "protobuf-3.15.6-cp36-cp36m-win_amd64.whl", hash = "sha256:d9ed0955b794f1e5f367e27f8a8ff25501eabe34573f003f06639c366ca75f73"}, - {file = "protobuf-3.15.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:822ac7f87fc2fb9b24edd2db390538b60ef50256e421ca30d65250fad5a3d477"}, - {file = "protobuf-3.15.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:74ac159989e2b02d761188a2b6f4601ff5e494d9b9d863f5ad6e98e5e0c54328"}, - {file = "protobuf-3.15.6-cp37-cp37m-win32.whl", hash = "sha256:30fe4249a364576f9594180589c3f9c4771952014b5f77f0372923fc7bafbbe2"}, - {file = "protobuf-3.15.6-cp37-cp37m-win_amd64.whl", hash = "sha256:45a91fc6f9aa86d3effdeda6751882b02de628519ba06d7160daffde0c889ff8"}, - {file = "protobuf-3.15.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:83c7c7534f050cb25383bb817159416601d1cc46c40bc5e851ec8bbddfc34a2f"}, - {file = "protobuf-3.15.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:9ec20a6ded7d0888e767ad029dbb126e604e18db744ac0a428cf746e040ccecd"}, - {file = "protobuf-3.15.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0f2da2fcc4102b6c3b57f03c9d8d5e37c63f8bc74deaa6cb54e0cc4524a77247"}, - {file = "protobuf-3.15.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:70054ae1ce5dea7dec7357db931fcf487f40ea45b02cb719ee6af07eb1e906fb"}, - {file = "protobuf-3.15.6-py2.py3-none-any.whl", hash = "sha256:1655fc0ba7402560d749de13edbfca1ac45d1753d8f4e5292989f18f5a00c215"}, - {file = "protobuf-3.15.6.tar.gz", hash = "sha256:2b974519a2ae83aa1e31cff9018c70bbe0e303a46a598f982943c49ae1d4fcd3"}, + {file = "protobuf-3.15.7-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a14141d5c967362d2eedff8825d2b69cc36a5b3ed6b1f618557a04e58a3cf787"}, + {file = "protobuf-3.15.7-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d54d78f621852ec4fdd1484d1263ca04d4bf5ffdf7abffdbb939e444b6ff3385"}, + {file = "protobuf-3.15.7-cp35-cp35m-macosx_10_9_intel.whl", hash = "sha256:462085acdb410b06335315fe7e63cb281a1902856e0f4657f341c283cedc1d56"}, + {file = "protobuf-3.15.7-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:849c92ce112e1ef648705c29ce044248e350f71d9d54a2026830623198f0bd38"}, + {file = "protobuf-3.15.7-cp35-cp35m-win32.whl", hash = "sha256:1f6083382f7714700deadf3014e921711e2f807de7f27e40c32b744701ae5b99"}, + {file = "protobuf-3.15.7-cp35-cp35m-win_amd64.whl", hash = "sha256:e17f60f00081adcb32068ee0bb51e418f6474acf83424244ff3512ffd2166385"}, + {file = "protobuf-3.15.7-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c75e563c6fb2ca5b8f21dd75c15659aa2c4a0025b9da3a7711ae661cd6a488d"}, + {file = "protobuf-3.15.7-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d939f41b4108350841c4790ebbadb61729e1363522fdb8434eb4e6f2065d0db1"}, + {file = "protobuf-3.15.7-cp36-cp36m-win32.whl", hash = "sha256:24f14c09d4c0a3641f1b0e9b552d026361de65b01686fdd3e5fdf8f9512cd79b"}, + {file = "protobuf-3.15.7-cp36-cp36m-win_amd64.whl", hash = "sha256:1247170191bcb2a8d978d11a58afe391004ec6c2184e4d961baf8102d43ff500"}, + {file = "protobuf-3.15.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:364cadaeec0756afdc099cbd88cb5659bd1bb7d547168d063abcb0272ccbb2f6"}, + {file = "protobuf-3.15.7-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:0c3a6941b1e6e6e22d812a8e5c46bfe83082ea60d262a46f2cfb22d9b9fb17db"}, + {file = "protobuf-3.15.7-cp37-cp37m-win32.whl", hash = "sha256:eb5668f3f6a83b6603ca2e09be5b20de89521ea5914aabe032cce981e4129cc8"}, + {file = "protobuf-3.15.7-cp37-cp37m-win_amd64.whl", hash = "sha256:1001e671cf8476edce7fb72778358d026390649cc35a79d47b2a291684ccfbb2"}, + {file = "protobuf-3.15.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a5ba7dd6f97964655aa7b234c95d80886425a31b7010764f042cdeb985314d18"}, + {file = "protobuf-3.15.7-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:46674bd6fcf8c63b4b9869ba579685db67cf51ae966443dd6bd9a8fa00fcef62"}, + {file = "protobuf-3.15.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4c4399156fb27e3768313b7a59352c861a893252bda6fb9f3643beb3ebb7047e"}, + {file = "protobuf-3.15.7-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:85cd29faf056036167d87445d5a5059034c298881c044e71a73d3b61a4be1c23"}, + {file = "protobuf-3.15.7-py2.py3-none-any.whl", hash = "sha256:22054432b923c0086f9cf1e1c0c52d39bf3c6e31014ea42eec2dabc22ee26d78"}, + {file = "protobuf-3.15.7.tar.gz", hash = "sha256:2d03fc2591543cd2456d0b72230b50c4519546a8d379ac6fd3ecd84c6df61e5d"}, ] py = [ {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, @@ -1960,8 +1970,8 @@ pyblish-base = [ {file = "pyblish_base-1.8.8-py2.py3-none-any.whl", hash = "sha256:67ea253a05d007ab4a175e44e778928ea7bdb0e9707573e1100417bbf0451a53"}, ] pycodestyle = [ - {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"}, - {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"}, + {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, + {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, ] pycparser = [ {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, @@ -1973,16 +1983,16 @@ pydocstyle = [ {file = "pydocstyle-3.0.0.tar.gz", hash = "sha256:5741c85e408f9e0ddf873611085e819b809fca90b619f5fd7f34bd4959da3dd4"}, ] pyflakes = [ - {file = "pyflakes-2.2.0-py2.py3-none-any.whl", hash = "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92"}, - {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"}, + {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, + {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, ] pygments = [ {file = "Pygments-2.8.1-py3-none-any.whl", hash = "sha256:534ef71d539ae97d4c3a4cf7d6f110f214b0e687e92f9cb9d2a3b0d3101289c8"}, {file = "Pygments-2.8.1.tar.gz", hash = "sha256:2656e1a6edcdabf4275f9a3640db59fd5de107d88e8663c5d4e9a0fa62f77f94"}, ] pylint = [ - {file = "pylint-2.7.2-py3-none-any.whl", hash = "sha256:d09b0b07ba06bcdff463958f53f23df25e740ecd81895f7d2699ec04bbd8dc3b"}, - {file = "pylint-2.7.2.tar.gz", hash = "sha256:0e21d3b80b96740909d77206d741aa3ce0b06b41be375d92e1f3244a274c1f8a"}, + {file = "pylint-2.7.4-py3-none-any.whl", hash = "sha256:209d712ec870a0182df034ae19f347e725c1e615b2269519ab58a35b3fcbbe7a"}, + {file = "pylint-2.7.4.tar.gz", hash = "sha256:bd38914c7731cdc518634a8d3c5585951302b6e2b6de60fbb3f7a0220e21eeee"}, ] pymongo = [ {file = "pymongo-3.11.3-cp27-cp27m-macosx_10_14_intel.whl", hash = "sha256:4d959e929cec805c2bf391418b1121590b4e7d5cb00af7b1ba521443d45a0918"}, @@ -2123,8 +2133,8 @@ pyrsistent = [ {file = "pyrsistent-0.17.3.tar.gz", hash = "sha256:2e636185d9eb976a18a8a8e96efce62f2905fea90041958d8cc2a189756ebf3e"}, ] pytest = [ - {file = "pytest-6.2.2-py3-none-any.whl", hash = "sha256:b574b57423e818210672e07ca1fa90aaf194a4f63f3ab909a2c67ebb22913839"}, - {file = "pytest-6.2.2.tar.gz", hash = "sha256:9d1edf9e7d0b84d72ea3dbcdfd22b35fb543a5e8f2a60092dd578936bf63d7f9"}, + {file = "pytest-6.2.3-py3-none-any.whl", hash = "sha256:6ad9c7bdf517a808242b998ac20063c41532a570d088d77eec1ee12b0b5574bc"}, + {file = "pytest-6.2.3.tar.gz", hash = "sha256:671238a46e4df0f3498d1c3270e5deb9b32d25134c99b7d75370a68cfbe9b634"}, ] pytest-cov = [ {file = "pytest-cov-2.11.1.tar.gz", hash = "sha256:359952d9d39b9f822d9d29324483e7ba04a3a17dd7d05aa6beb7ea01e359e5f7"}, @@ -2198,8 +2208,8 @@ speedcopy = [ {file = "speedcopy-2.1.0.tar.gz", hash = "sha256:8bb1a6c735900b83901a7be84ba2175ed3887c13c6786f97dea48f2ea7d504c2"}, ] sphinx = [ - {file = "Sphinx-3.5.2-py3-none-any.whl", hash = "sha256:ef64a814576f46ec7de06adf11b433a0d6049be007fefe7fd0d183d28b581fac"}, - {file = "Sphinx-3.5.2.tar.gz", hash = "sha256:672cfcc24b6b69235c97c750cb190a44ecd72696b4452acaf75c2d9cc78ca5ff"}, + {file = "Sphinx-3.5.3-py3-none-any.whl", hash = "sha256:3f01732296465648da43dec8fb40dc451ba79eb3e2cc5c6d79005fd98197107d"}, + {file = "Sphinx-3.5.3.tar.gz", hash = "sha256:ce9c228456131bab09a3d7d10ae58474de562a6f79abb3dc811ae401cf8c1abc"}, ] sphinx-qt-documentation = [ {file = "sphinx_qt_documentation-0.3-py3-none-any.whl", hash = "sha256:bee247cb9e4fc03fc496d07adfdb943100e1103320c3e5e820e0cfa7c790d9b6"}, @@ -2245,8 +2255,8 @@ toml = [ {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] tqdm = [ - {file = "tqdm-4.59.0-py2.py3-none-any.whl", hash = "sha256:9fdf349068d047d4cfbe24862c425883af1db29bcddf4b0eeb2524f6fbdb23c7"}, - {file = "tqdm-4.59.0.tar.gz", hash = "sha256:d666ae29164da3e517fcf125e41d4fe96e5bb375cd87ff9763f6b38b5592fe33"}, + {file = "tqdm-4.60.0-py2.py3-none-any.whl", hash = "sha256:daec693491c52e9498632dfbe9ccfc4882a557f5fa08982db1b4d3adbe0887c3"}, + {file = "tqdm-4.60.0.tar.gz", hash = "sha256:ebdebdb95e3477ceea267decfc0784859aa3df3e27e22d23b83e9b272bf157ae"}, ] typed-ast = [ {file = "typed_ast-1.4.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:7703620125e4fb79b64aa52427ec192822e9f45d37d4b6625ab37ef403e1df70"}, @@ -2290,8 +2300,8 @@ uritemplate = [ {file = "uritemplate-3.0.1.tar.gz", hash = "sha256:5af8ad10cec94f215e3f48112de2022e1d5a37ed427fbd88652fa908f2ab7cae"}, ] urllib3 = [ - {file = "urllib3-1.26.3-py2.py3-none-any.whl", hash = "sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80"}, - {file = "urllib3-1.26.3.tar.gz", hash = "sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73"}, + {file = "urllib3-1.26.4-py2.py3-none-any.whl", hash = "sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df"}, + {file = "urllib3-1.26.4.tar.gz", hash = "sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937"}, ] wcwidth = [ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, @@ -2305,8 +2315,8 @@ wrapt = [ {file = "wrapt-1.12.1.tar.gz", hash = "sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7"}, ] wsrpc-aiohttp = [ - {file = "wsrpc-aiohttp-3.1.1.tar.gz", hash = "sha256:a17e1d91624a437e759d4f276b73de1db2071b1681e992cade025e91d31b2a9f"}, - {file = "wsrpc_aiohttp-3.1.1-py3-none-any.whl", hash = "sha256:f3f1ee31aed5145a7fafe8d6c778b914b7e6ec131500395c9c85b0d8676f7302"}, + {file = "wsrpc-aiohttp-3.1.2.tar.gz", hash = "sha256:891164dfe06a8d8d846b485d04b1e56b2c397ff1b46ef0348e6f62bd8efb1693"}, + {file = "wsrpc_aiohttp-3.1.2-py3-none-any.whl", hash = "sha256:4ba64e02b12dcbc09d02544f35bceba49bd04cbc496db47aa8559ae4609ada8e"}, ] yarl = [ {file = "yarl-1.6.3-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:0355a701b3998dcd832d0dc47cc5dedf3874f966ac7f870e0f3a6788d802d434"}, diff --git a/pyproject.toml b/pyproject.toml index ec2d9c7e3b..6df6db5a18 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,7 @@ acre = { git = "https://github.com/pypeclub/acre.git" } opentimelineio = { version = "0.14.0.dev1", source = "openpype" } appdirs = "^1.4.3" blessed = "^1.17" # openpype terminal formatting +coolname = "*" clique = "1.5.*" Click = "^7" dnspython = "^2.1.0" From cf2dc1f70e5507f466998e610e0faf76f5882916 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Apr 2021 11:31:04 +0200 Subject: [PATCH 29/68] renamed PypeSettingsRegistry to OpenPypeSettingsRegistry --- openpype/lib/__init__.py | 4 ++-- openpype/lib/local_settings.py | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/openpype/lib/__init__.py b/openpype/lib/__init__.py index 554c0d8ec3..7b2f533921 100644 --- a/openpype/lib/__init__.py +++ b/openpype/lib/__init__.py @@ -104,7 +104,7 @@ from .plugin_tools import ( from .local_settings import ( IniSettingRegistry, JSONSettingRegistry, - PypeSettingsRegistry, + OpenPypeSettingsRegistry, get_local_site_id, change_openpype_mongo_url ) @@ -217,7 +217,7 @@ __all__ = [ "IniSettingRegistry", "JSONSettingRegistry", - "PypeSettingsRegistry", + "OpenPypeSettingsRegistry", "get_local_site_id", "change_openpype_mongo_url", diff --git a/openpype/lib/local_settings.py b/openpype/lib/local_settings.py index 82507cb0c0..2095f1253e 100644 --- a/openpype/lib/local_settings.py +++ b/openpype/lib/local_settings.py @@ -452,7 +452,7 @@ class JSONSettingRegistry(ASettingRegistry): json.dump(data, cfg, indent=4) -class PypeSettingsRegistry(JSONSettingRegistry): +class OpenPypeSettingsRegistry(JSONSettingRegistry): """Class handling Pype general settings registry. Attributes: @@ -463,9 +463,9 @@ class PypeSettingsRegistry(JSONSettingRegistry): def __init__(self): self.vendor = "pypeclub" - self.product = "pype" + self.product = "openpype" path = appdirs.user_data_dir(self.product, self.vendor) - super(PypeSettingsRegistry, self).__init__("pype_settings", path) + super(OpenPypeSettingsRegistry, self).__init__("openpype_settings", path) def _create_local_site_id(registry=None): @@ -473,7 +473,7 @@ def _create_local_site_id(registry=None): from uuid import uuid4 if registry is None: - registry = PypeSettingsRegistry() + registry = OpenPypeSettingsRegistry() new_id = str(uuid4()) @@ -489,7 +489,7 @@ def get_local_site_id(): Identifier is created if does not exists yet. """ - registry = PypeSettingsRegistry() + registry = OpenPypeSettingsRegistry() try: return registry.get_item("localId") except ValueError: @@ -504,5 +504,5 @@ def change_openpype_mongo_url(new_mongo_url): """ validate_mongo_connection(new_mongo_url) - registry = PypeSettingsRegistry() - registry.set_secure_item("pypeMongo", new_mongo_url) + registry = OpenPypeSettingsRegistry() + registry.set_secure_item("openPypeMongo", new_mongo_url) From a6ac99c7558273f093a7c41a95fc68b37778beeb Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Apr 2021 11:31:41 +0200 Subject: [PATCH 30/68] added ability to define name of registry --- igniter/user_settings.py | 7 ++++--- openpype/lib/local_settings.py | 6 ++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/igniter/user_settings.py b/igniter/user_settings.py index 77fb8b5ae5..f9bbad4e5e 100644 --- a/igniter/user_settings.py +++ b/igniter/user_settings.py @@ -459,9 +459,10 @@ class OpenPypeSettingsRegistry(JSONSettingRegistry): """ - def __init__(self): + def __init__(self, name=None): self.vendor = "pypeclub" self.product = "openpype" + if name is None: + name = "openpype_settings" path = appdirs.user_data_dir(self.product, self.vendor) - super(OpenPypeSettingsRegistry, self).__init__( - "openpype_settings", path) + super(OpenPypeSettingsRegistry, self).__init__(name, path) diff --git a/openpype/lib/local_settings.py b/openpype/lib/local_settings.py index 2095f1253e..6386a69026 100644 --- a/openpype/lib/local_settings.py +++ b/openpype/lib/local_settings.py @@ -461,11 +461,13 @@ class OpenPypeSettingsRegistry(JSONSettingRegistry): """ - def __init__(self): + def __init__(self, name=None): self.vendor = "pypeclub" self.product = "openpype" + if name is None: + name = "openpype_settings" path = appdirs.user_data_dir(self.product, self.vendor) - super(OpenPypeSettingsRegistry, self).__init__("openpype_settings", path) + super(OpenPypeSettingsRegistry, self).__init__(name, path) def _create_local_site_id(registry=None): From 11fe828809c7399f2aeba8a97a68736db2b41ae5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Apr 2021 11:52:59 +0200 Subject: [PATCH 31/68] fix tests --- tests/igniter/test_bootstrap_repos.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/igniter/test_bootstrap_repos.py b/tests/igniter/test_bootstrap_repos.py index 75996b4026..6c70380ab6 100644 --- a/tests/igniter/test_bootstrap_repos.py +++ b/tests/igniter/test_bootstrap_repos.py @@ -11,7 +11,7 @@ import pytest from igniter.bootstrap_repos import BootstrapRepos from igniter.bootstrap_repos import PypeVersion -from pype.lib import PypeSettingsRegistry +from pype.lib import OpenPypeSettingsRegistry @pytest.fixture @@ -348,7 +348,7 @@ def test_find_pype(fix_bootstrap, tmp_path_factory, monkeypatch, printer): return d_path.as_posix() monkeypatch.setattr(appdirs, "user_data_dir", mock_user_data_dir) - fix_bootstrap.registry = PypeSettingsRegistry() + fix_bootstrap.registry = OpenPypeSettingsRegistry() fix_bootstrap.registry.set_item("pypePath", d_path.as_posix()) result = fix_bootstrap.find_pype(include_zips=True) From b742f3a9c56000d4d23693b05600267c9c5adb91 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Apr 2021 12:42:53 +0200 Subject: [PATCH 32/68] credentials are not stored per user --- openpype/modules/ftrack/lib/credentials.py | 29 +++++----------------- 1 file changed, 6 insertions(+), 23 deletions(-) diff --git a/openpype/modules/ftrack/lib/credentials.py b/openpype/modules/ftrack/lib/credentials.py index 3d9aa75e84..c2812083ba 100644 --- a/openpype/modules/ftrack/lib/credentials.py +++ b/openpype/modules/ftrack/lib/credentials.py @@ -17,8 +17,6 @@ CREDENTIALS_FOLDER = os.path.dirname(CREDENTIALS_PATH) if not os.path.isdir(CREDENTIALS_FOLDER): os.makedirs(CREDENTIALS_FOLDER) -USER_GETTER = None - def get_ftrack_hostname(ftrack_server=None): if not ftrack_server: @@ -30,13 +28,7 @@ def get_ftrack_hostname(ftrack_server=None): return urlparse(ftrack_server).hostname -def get_user(): - if USER_GETTER: - return USER_GETTER() - return getpass.getuser() - - -def get_credentials(ftrack_server=None, user=None): +def get_credentials(ftrack_server=None): credentials = {} if not os.path.exists(CREDENTIALS_PATH): with open(CREDENTIALS_PATH, "w") as file: @@ -48,28 +40,22 @@ def get_credentials(ftrack_server=None, user=None): content = file.read() hostname = get_ftrack_hostname(ftrack_server) - if not user: - user = get_user() content_json = json.loads(content or "{}") - credentials = content_json.get(hostname, {}).get(user) or {} + credentials = content_json.get(hostname) or {} return credentials -def save_credentials(ft_user, ft_api_key, ftrack_server=None, user=None): +def save_credentials(ft_user, ft_api_key, ftrack_server=None): hostname = get_ftrack_hostname(ftrack_server) - if not user: - user = get_user() with open(CREDENTIALS_PATH, "r") as file: content = file.read() content_json = json.loads(content or "{}") - if hostname not in content_json: - content_json[hostname] = {} - content_json[hostname][user] = { + content_json[hostname] = { "username": ft_user, "api_key": ft_api_key } @@ -84,7 +70,7 @@ def save_credentials(ft_user, ft_api_key, ftrack_server=None, user=None): file.write(json.dumps(content_json, indent=4)) -def clear_credentials(ft_user=None, ftrack_server=None, user=None): +def clear_credentials(ft_user=None, ftrack_server=None): if not ft_user: ft_user = os.environ.get("FTRACK_API_USER") @@ -92,9 +78,6 @@ def clear_credentials(ft_user=None, ftrack_server=None, user=None): return hostname = get_ftrack_hostname(ftrack_server) - if not user: - user = get_user() - with open(CREDENTIALS_PATH, "r") as file: content = file.read() @@ -102,7 +85,7 @@ def clear_credentials(ft_user=None, ftrack_server=None, user=None): if hostname not in content_json: content_json[hostname] = {} - content_json[hostname].pop(user, None) + content_json.pop(hostname, None) with open(CREDENTIALS_PATH, "w") as file: file.write(json.dumps(content_json)) From 5a7d19337cdc8efee1e60f6f59fb72c603206f41 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Apr 2021 12:43:27 +0200 Subject: [PATCH 33/68] added helper function to get secure item key the same way --- openpype/modules/ftrack/lib/credentials.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/modules/ftrack/lib/credentials.py b/openpype/modules/ftrack/lib/credentials.py index c2812083ba..7a0d93f120 100644 --- a/openpype/modules/ftrack/lib/credentials.py +++ b/openpype/modules/ftrack/lib/credentials.py @@ -28,6 +28,11 @@ def get_ftrack_hostname(ftrack_server=None): return urlparse(ftrack_server).hostname +def _get_ftrack_secure_key(hostname): + """Secure item key for entered hostname.""" + return "/".join(("ftrack", hostname)) + + def get_credentials(ftrack_server=None): credentials = {} if not os.path.exists(CREDENTIALS_PATH): From 153160f85d1ae2a994462eabfdc0cb9f224f9ee6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Apr 2021 12:58:45 +0200 Subject: [PATCH 34/68] use OpenPypeRegistry to set, get and remove ftrack credentials --- openpype/modules/ftrack/lib/credentials.py | 94 +++++++--------------- 1 file changed, 27 insertions(+), 67 deletions(-) diff --git a/openpype/modules/ftrack/lib/credentials.py b/openpype/modules/ftrack/lib/credentials.py index 7a0d93f120..05a74c0875 100644 --- a/openpype/modules/ftrack/lib/credentials.py +++ b/openpype/modules/ftrack/lib/credentials.py @@ -1,21 +1,16 @@ import os -import json import ftrack_api -import appdirs -import getpass + try: from urllib.parse import urlparse except ImportError: from urlparse import urlparse -CONFIG_PATH = os.path.normpath(appdirs.user_data_dir("pype-app", "pype")) -CREDENTIALS_FILE_NAME = "ftrack_cred.json" -CREDENTIALS_PATH = os.path.join(CONFIG_PATH, CREDENTIALS_FILE_NAME) -CREDENTIALS_FOLDER = os.path.dirname(CREDENTIALS_PATH) +from openpype.lib import OpenPypeSettingsRegistry -if not os.path.isdir(CREDENTIALS_FOLDER): - os.makedirs(CREDENTIALS_FOLDER) +USERNAME_KEY = "username" +API_KEY_KEY = "api_key" def get_ftrack_hostname(ftrack_server=None): @@ -34,84 +29,49 @@ def _get_ftrack_secure_key(hostname): def get_credentials(ftrack_server=None): - credentials = {} - if not os.path.exists(CREDENTIALS_PATH): - with open(CREDENTIALS_PATH, "w") as file: - file.write(json.dumps(credentials)) - file.close() - return credentials - - with open(CREDENTIALS_PATH, "r") as file: - content = file.read() - hostname = get_ftrack_hostname(ftrack_server) + secure_key = _get_ftrack_secure_key(hostname) - content_json = json.loads(content or "{}") - credentials = content_json.get(hostname) or {} - - return credentials - - -def save_credentials(ft_user, ft_api_key, ftrack_server=None): - hostname = get_ftrack_hostname(ftrack_server) - - with open(CREDENTIALS_PATH, "r") as file: - content = file.read() - - content_json = json.loads(content or "{}") - - content_json[hostname] = { - "username": ft_user, - "api_key": ft_api_key + registry = OpenPypeSettingsRegistry(secure_key) + return { + USERNAME_KEY: registry.get_secure_item(USERNAME_KEY, None), + API_KEY_KEY: registry.get_secure_item(API_KEY_KEY, None) } - # Deprecated keys - if "username" in content_json: - content_json.pop("username") - if "apiKey" in content_json: - content_json.pop("apiKey") - - with open(CREDENTIALS_PATH, "w") as file: - file.write(json.dumps(content_json, indent=4)) - - -def clear_credentials(ft_user=None, ftrack_server=None): - if not ft_user: - ft_user = os.environ.get("FTRACK_API_USER") - - if not ft_user: - return +def save_credentials(username, api_key, ftrack_server=None): hostname = get_ftrack_hostname(ftrack_server) - with open(CREDENTIALS_PATH, "r") as file: - content = file.read() + secure_key = _get_ftrack_secure_key(hostname) - content_json = json.loads(content or "{}") - if hostname not in content_json: - content_json[hostname] = {} - - content_json.pop(hostname, None) - - with open(CREDENTIALS_PATH, "w") as file: - file.write(json.dumps(content_json)) + registry = OpenPypeSettingsRegistry(secure_key) + registry.set_secure_item(USERNAME_KEY, username) + registry.set_secure_item(API_KEY_KEY, api_key) -def check_credentials(ft_user, ft_api_key, ftrack_server=None): +def clear_credentials(ftrack_server=None): + hostname = get_ftrack_hostname(ftrack_server) + secure_key = _get_ftrack_secure_key(hostname) + + registry = OpenPypeSettingsRegistry(secure_key) + registry.delete_secure_item(USERNAME_KEY) + registry.delete_secure_item(API_KEY_KEY) + + +def check_credentials(username, api_key, ftrack_server=None): if not ftrack_server: ftrack_server = os.environ["FTRACK_SERVER"] - if not ft_user or not ft_api_key: + if not username or not api_key: return False try: session = ftrack_api.Session( server_url=ftrack_server, - api_key=ft_api_key, - api_user=ft_user + api_key=api_key, + api_user=username ) session.close() except Exception: return False - return True From 983b9264ec38c024fcfaab47b8b416a5c1b551f1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Apr 2021 14:11:22 +0200 Subject: [PATCH 35/68] clockify is using keyring to store and receive api key --- openpype/modules/clockify/clockify_api.py | 26 +++++++++-------------- openpype/modules/clockify/constants.py | 9 ++------ 2 files changed, 12 insertions(+), 23 deletions(-) diff --git a/openpype/modules/clockify/clockify_api.py b/openpype/modules/clockify/clockify_api.py index d88b2ef8df..e2de726f39 100644 --- a/openpype/modules/clockify/clockify_api.py +++ b/openpype/modules/clockify/clockify_api.py @@ -1,13 +1,17 @@ import os import re import time -import requests import json import datetime +import requests from .constants import ( - CLOCKIFY_ENDPOINT, ADMIN_PERMISSION_NAMES, CREDENTIALS_JSON_PATH + CLOCKIFY_ENDPOINT, + ADMIN_PERMISSION_NAMES ) +# from openpype.lib import OpenPypeSettingsRegistry +from openpype.lib.local_settings import OpenPypeSecureRegistry as OpenPypeSettingsRegistry + def time_check(obj): if obj.request_counter < 10: @@ -31,6 +35,8 @@ class ClockifyAPI: self.request_counter = 0 self.request_time = time.time() + self.secure_registry = OpenPypeSettingsRegistry("clockify") + @property def headers(self): return {"X-Api-Key": self.api_key} @@ -129,22 +135,10 @@ class ClockifyAPI: return False def get_api_key(self): - api_key = None - try: - file = open(CREDENTIALS_JSON_PATH, 'r') - api_key = json.load(file).get('api_key', None) - if api_key == '': - api_key = None - except Exception: - file = open(CREDENTIALS_JSON_PATH, 'w') - file.close() - return api_key + return self.secure_registry.get_secure_item("api_key", None) def save_api_key(self, api_key): - data = {'api_key': api_key} - file = open(CREDENTIALS_JSON_PATH, 'w') - file.write(json.dumps(data)) - file.close() + self.secure_registry.set_secure_item("api_key", api_key) def get_workspaces(self): action_url = 'workspaces/' diff --git a/openpype/modules/clockify/constants.py b/openpype/modules/clockify/constants.py index 38ad4b64cf..66f6cb899a 100644 --- a/openpype/modules/clockify/constants.py +++ b/openpype/modules/clockify/constants.py @@ -1,17 +1,12 @@ import os -import appdirs CLOCKIFY_FTRACK_SERVER_PATH = os.path.join( - os.path.dirname(__file__), "ftrack", "server" + os.path.dirname(os.path.abspath(__file__)), "ftrack", "server" ) CLOCKIFY_FTRACK_USER_PATH = os.path.join( - os.path.dirname(__file__), "ftrack", "user" + os.path.dirname(os.path.abspath(__file__)), "ftrack", "user" ) -CREDENTIALS_JSON_PATH = os.path.normpath(os.path.join( - appdirs.user_data_dir("pype-app", "pype"), - "clockify.json" -)) ADMIN_PERMISSION_NAMES = ["WORKSPACE_OWN", "WORKSPACE_ADMIN"] CLOCKIFY_ENDPOINT = "https://api.clockify.me/api/" From 68fe4c8a934ffac8df4307d2cbd3768bb3feb6ae Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Apr 2021 15:32:32 +0200 Subject: [PATCH 36/68] implemented OpenPypeSecureRegistry handling keyring information --- igniter/user_settings.py | 93 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/igniter/user_settings.py b/igniter/user_settings.py index f9bbad4e5e..1d4fcd04eb 100644 --- a/igniter/user_settings.py +++ b/igniter/user_settings.py @@ -28,6 +28,99 @@ import platform import appdirs import six +_PLACEHOLDER = object() + + +class OpenPypeSecureRegistry: + def __init__(self, name): + try: + import keyring + + except Exception: + raise NotImplementedError( + "Python module `keyring` is not available." + ) + + # hack for cx_freeze and Windows keyring backend + if platform.system().lower() == "windows": + from keyring.backends import Windows + + keyring.set_keyring(Windows.WinVaultKeyring()) + + # Force "OpenPype" prefix + self._name = "/".join(("OpenPype", name)) + + def set_item(self, name, value): + # type: (str, str) -> None + """Set sensitive item into system's keyring. + + This uses `Keyring module`_ to save sensitive stuff into system's + keyring. + + Args: + name (str): Name of the item. + value (str): Value of the item. + + .. _Keyring module: + https://github.com/jaraco/keyring + + """ + import keyring + + keyring.set_password(self._name, name, value) + + @lru_cache(maxsize=32) + def get_item(self, name, default=_PLACEHOLDER): + """Get value of sensitive item from system's keyring. + + See also `Keyring module`_ + + Args: + name (str): Name of the item. + default (Any): Default value if item is not available. + + Returns: + value (str): Value of the item. + + Raises: + ValueError: If item doesn't exist and default is not defined. + + .. _Keyring module: + https://github.com/jaraco/keyring + + """ + import keyring + + value = keyring.get_password(self._name, name) + if value: + return value + + if default is not _PLACEHOLDER: + return default + + # NOTE Should raise `KeyError` + raise ValueError( + "Item {}:{} does not exist in keyring.".format(self._name, name) + ) + + def delete_item(self, name): + # type: (str) -> None + """Delete value stored in system's keyring. + + See also `Keyring module`_ + + Args: + name (str): Name of the item to be deleted. + + .. _Keyring module: + https://github.com/jaraco/keyring + + """ + import keyring + + self.get_item.cache_clear() + keyring.delete_password(self._name, name) + @six.add_metaclass(ABCMeta) class ASettingRegistry(): From d08fde832a2b4c7d5fccee8772c3646cbc3be47a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Apr 2021 15:34:30 +0200 Subject: [PATCH 37/68] bootstrap repos also has secure_registry which is used for mongo url storage --- igniter/bootstrap_repos.py | 6 +++++- igniter/install_thread.py | 6 +++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/igniter/bootstrap_repos.py b/igniter/bootstrap_repos.py index 2f305e24e3..51dec7f51e 100644 --- a/igniter/bootstrap_repos.py +++ b/igniter/bootstrap_repos.py @@ -14,7 +14,10 @@ from zipfile import ZipFile, BadZipFile from appdirs import user_data_dir from speedcopy import copyfile -from .user_settings import OpenPypeSettingsRegistry +from .user_settings import ( + OpenPypeSecureRegistry, + OpenPypeSettingsRegistry +) from .tools import get_openpype_path_from_db @@ -239,6 +242,7 @@ class BootstrapRepos: self._app = "openpype" self._log = log.getLogger(str(__class__)) self.data_dir = Path(user_data_dir(self._app, self._vendor)) + self.secure_registry = OpenPypeSecureRegistry("Settings") self.registry = OpenPypeSettingsRegistry() self.zip_filter = [".pyc", "__pycache__"] self.openpype_filter = [ diff --git a/igniter/install_thread.py b/igniter/install_thread.py index bf5d541056..df8b830209 100644 --- a/igniter/install_thread.py +++ b/igniter/install_thread.py @@ -71,7 +71,7 @@ class InstallThread(QThread): if not os.getenv("OPENPYPE_MONGO"): # try to get it from settings registry try: - self._mongo = bs.registry.get_secure_item( + self._mongo = bs.secure_registry.get_item( "openPypeMongo") except ValueError: self.message.emit( @@ -82,7 +82,7 @@ class InstallThread(QThread): self._mongo = os.getenv("OPENPYPE_MONGO") else: self.message.emit("Saving mongo connection string ...", False) - bs.registry.set_secure_item("openPypeMongo", self._mongo) + bs.secure_registry.set_item("openPypeMongo", self._mongo) os.environ["OPENPYPE_MONGO"] = self._mongo @@ -169,7 +169,7 @@ class InstallThread(QThread): f"!!! invalid mongo url {self._mongo}", True) self.finished.emit(InstallResult(-1)) return - bs.registry.set_secure_item("openPypeMongo", self._mongo) + bs.secure_registry.set_item("openPypeMongo", self._mongo) os.environ["OPENPYPE_MONGO"] = self._mongo self.message.emit(f"processing {self._path}", True) From 41beb27a693e7d808da72595c83c7ca003d98309 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Apr 2021 15:35:15 +0200 Subject: [PATCH 38/68] secure registry is also used in install dialog --- igniter/install_dialog.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/igniter/install_dialog.py b/igniter/install_dialog.py index 2cc0ed8448..dab00079a5 100644 --- a/igniter/install_dialog.py +++ b/igniter/install_dialog.py @@ -13,7 +13,7 @@ from .tools import ( validate_mongo_connection, get_openpype_path_from_db ) -from .user_settings import OpenPypeSettingsRegistry +from .user_settings import OpenPypeSecureRegistry from .version import __version__ @@ -42,13 +42,13 @@ class InstallDialog(QtWidgets.QDialog): def __init__(self, parent=None): super(InstallDialog, self).__init__(parent) - self.registry = OpenPypeSettingsRegistry() + self.secure_registry = OpenPypeSecureRegistry("Settings") self.mongo_url = "" try: self.mongo_url = ( os.getenv("OPENPYPE_MONGO", "") - or self.registry.get_secure_item("openPypeMongo") + or self.secure_registry.get_item("openPypeMongo") ) except ValueError: pass From 3cc8599684a7374c98a9cd375fce588e382d91aa Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Apr 2021 15:36:22 +0200 Subject: [PATCH 39/68] removed secure getter and setters from ASettingRegistry --- igniter/user_settings.py | 83 +--------------------------------------- 1 file changed, 2 insertions(+), 81 deletions(-) diff --git a/igniter/user_settings.py b/igniter/user_settings.py index 1d4fcd04eb..bb5336a9ab 100644 --- a/igniter/user_settings.py +++ b/igniter/user_settings.py @@ -25,8 +25,8 @@ except ImportError: import platform -import appdirs import six +import appdirs _PLACEHOLDER = object() @@ -139,13 +139,6 @@ class ASettingRegistry(): # type: (str) -> ASettingRegistry super(ASettingRegistry, self).__init__() - if six.PY3: - import keyring - # hack for cx_freeze and Windows keyring backend - if platform.system() == "Windows": - from keyring.backends import Windows - keyring.set_keyring(Windows.WinVaultKeyring()) - self._name = name self._items = {} @@ -220,78 +213,6 @@ class ASettingRegistry(): del self._items[name] self._delete_item(name) - def set_secure_item(self, name, value): - # type: (str, str) -> None - """Set sensitive item into system's keyring. - - This uses `Keyring module`_ to save sensitive stuff into system's - keyring. - - Args: - name (str): Name of the item. - value (str): Value of the item. - - .. _Keyring module: - https://github.com/jaraco/keyring - - """ - if six.PY2: - raise NotImplementedError( - "Keyring not available on Python 2 hosts") - import keyring - keyring.set_password(self._name, name, value) - - @lru_cache(maxsize=32) - def get_secure_item(self, name): - # type: (str) -> str - """Get value of sensitive item from system's keyring. - - See also `Keyring module`_ - - Args: - name (str): Name of the item. - - Returns: - value (str): Value of the item. - - Raises: - ValueError: If item doesn't exist. - - .. _Keyring module: - https://github.com/jaraco/keyring - - """ - if six.PY2: - raise NotImplementedError( - "Keyring not available on Python 2 hosts") - import keyring - value = keyring.get_password(self._name, name) - if not value: - raise ValueError( - "Item {}:{} does not exist in keyring.".format( - self._name, name)) - return value - - def delete_secure_item(self, name): - # type: (str) -> None - """Delete value stored in system's keyring. - - See also `Keyring module`_ - - Args: - name (str): Name of the item to be deleted. - - .. _Keyring module: - https://github.com/jaraco/keyring - - """ - if six.PY2: - raise NotImplementedError( - "Keyring not available on Python 2 hosts") - import keyring - self.get_secure_item.cache_clear() - keyring.delete_password(self._name, name) - class IniSettingRegistry(ASettingRegistry): """Class using :mod:`configparser`. @@ -555,7 +476,7 @@ class OpenPypeSettingsRegistry(JSONSettingRegistry): def __init__(self, name=None): self.vendor = "pypeclub" self.product = "openpype" - if name is None: + if not name: name = "openpype_settings" path = appdirs.user_data_dir(self.product, self.vendor) super(OpenPypeSettingsRegistry, self).__init__(name, path) From 454bd9d1b51b4b4782bff4e2afbaba2d7102aad4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Apr 2021 15:40:21 +0200 Subject: [PATCH 40/68] copy pasted secure registry logic from igniter to pype lib --- openpype/lib/local_settings.py | 195 ++++++++++++++++++--------------- 1 file changed, 105 insertions(+), 90 deletions(-) diff --git a/openpype/lib/local_settings.py b/openpype/lib/local_settings.py index 6386a69026..ec76b57cfd 100644 --- a/openpype/lib/local_settings.py +++ b/openpype/lib/local_settings.py @@ -5,6 +5,7 @@ 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 try: from functools import lru_cache @@ -25,11 +26,104 @@ except ImportError: import platform -import appdirs import six +import appdirs from .import validate_mongo_connection +_PLACEHOLDER = object() + + +class OpenPypeSecureRegistry: + def __init__(self, name): + try: + import keyring + + except Exception: + raise NotImplementedError( + "Python module `keyring` is not available." + ) + + # hack for cx_freeze and Windows keyring backend + if platform.system().lower() == "windows": + from keyring.backends import Windows + + keyring.set_keyring(Windows.WinVaultKeyring()) + + # Force "OpenPype" prefix + self._name = "/".join(("OpenPype", name)) + + def set_item(self, name, value): + # type: (str, str) -> None + """Set sensitive item into system's keyring. + + This uses `Keyring module`_ to save sensitive stuff into system's + keyring. + + Args: + name (str): Name of the item. + value (str): Value of the item. + + .. _Keyring module: + https://github.com/jaraco/keyring + + """ + import keyring + + keyring.set_password(self._name, name, value) + + @lru_cache(maxsize=32) + def get_item(self, name, default=_PLACEHOLDER): + """Get value of sensitive item from system's keyring. + + See also `Keyring module`_ + + Args: + name (str): Name of the item. + default (Any): Default value if item is not available. + + Returns: + value (str): Value of the item. + + Raises: + ValueError: If item doesn't exist and default is not defined. + + .. _Keyring module: + https://github.com/jaraco/keyring + + """ + import keyring + + value = keyring.get_password(self._name, name) + if value: + return value + + if default is not _PLACEHOLDER: + return default + + # NOTE Should raise `KeyError` + raise ValueError( + "Item {}:{} does not exist in keyring.".format(self._name, name) + ) + + def delete_item(self, name): + # type: (str) -> None + """Delete value stored in system's keyring. + + See also `Keyring module`_ + + Args: + name (str): Name of the item to be deleted. + + .. _Keyring module: + https://github.com/jaraco/keyring + + """ + import keyring + + self.get_item.cache_clear() + keyring.delete_password(self._name, name) + @six.add_metaclass(ABCMeta) class ASettingRegistry(): @@ -48,13 +142,6 @@ class ASettingRegistry(): # type: (str) -> ASettingRegistry super(ASettingRegistry, self).__init__() - if six.PY3: - import keyring - # hack for cx_freeze and Windows keyring backend - if platform.system() == "Windows": - from keyring.backends import Windows - keyring.set_keyring(Windows.WinVaultKeyring()) - self._name = name self._items = {} @@ -120,7 +207,7 @@ class ASettingRegistry(): """Delete item from settings. Note: - see :meth:`pype.lib.local_settings.ARegistrySettings.delete_item` + see :meth:`openpype.lib.user_settings.ARegistrySettings.delete_item` """ pass @@ -129,78 +216,6 @@ class ASettingRegistry(): del self._items[name] self._delete_item(name) - def set_secure_item(self, name, value): - # type: (str, str) -> None - """Set sensitive item into system's keyring. - - This uses `Keyring module`_ to save sensitive stuff into system's - keyring. - - Args: - name (str): Name of the item. - value (str): Value of the item. - - .. _Keyring module: - https://github.com/jaraco/keyring - - """ - if six.PY2: - raise NotImplementedError( - "Keyring not available on Python 2 hosts") - import keyring - keyring.set_password(self._name, name, value) - - @lru_cache(maxsize=32) - def get_secure_item(self, name): - # type: (str) -> str - """Get value of sensitive item from system's keyring. - - See also `Keyring module`_ - - Args: - name (str): Name of the item. - - Returns: - value (str): Value of the item. - - Raises: - ValueError: If item doesn't exist. - - .. _Keyring module: - https://github.com/jaraco/keyring - - """ - if six.PY2: - raise NotImplementedError( - "Keyring not available on Python 2 hosts") - import keyring - value = keyring.get_password(self._name, name) - if not value: - raise ValueError( - "Item {}:{} does not exist in keyring.".format( - self._name, name)) - return value - - def delete_secure_item(self, name): - # type: (str) -> None - """Delete value stored in system's keyring. - - See also `Keyring module`_ - - Args: - name (str): Name of the item to be deleted. - - .. _Keyring module: - https://github.com/jaraco/keyring - - """ - if six.PY2: - raise NotImplementedError( - "Keyring not available on Python 2 hosts") - import keyring - self.get_secure_item.cache_clear() - keyring.delete_password(self._name, name) - class IniSettingRegistry(ASettingRegistry): """Class using :mod:`configparser`. @@ -218,7 +233,7 @@ class IniSettingRegistry(ASettingRegistry): if not os.path.exists(self._registry_file): with open(self._registry_file, mode="w") as cfg: print("# Settings registry", cfg) - print("# Generated by Pype {}".format(version), cfg) + print("# Generated by OpenPype {}".format(version), cfg) now = datetime.now().strftime("%d/%m/%Y %H:%M:%S") print("# {}".format(now), cfg) @@ -352,7 +367,7 @@ class IniSettingRegistry(ASettingRegistry): """Delete item from default section. Note: - See :meth:`~pype.lib.IniSettingsRegistry.delete_item_from_section` + See :meth:`~openpype.lib.IniSettingsRegistry.delete_item_from_section` """ self.delete_item_from_section("MAIN", name) @@ -369,7 +384,7 @@ class JSONSettingRegistry(ASettingRegistry): now = datetime.now().strftime("%d/%m/%Y %H:%M:%S") header = { "__metadata__": { - "pype-version": os.getenv("OPENPYPE_VERSION", "N/A"), + "openpype-version": os.getenv("OPENPYPE_VERSION", "N/A"), "generated": now }, "registry": {} @@ -387,7 +402,7 @@ class JSONSettingRegistry(ASettingRegistry): """Get item value from registry json. Note: - See :meth:`pype.lib.JSONSettingRegistry.get_item` + See :meth:`openpype.lib.JSONSettingRegistry.get_item` """ with open(self._registry_file, mode="r") as cfg: @@ -420,7 +435,7 @@ class JSONSettingRegistry(ASettingRegistry): """Set item value to registry json. Note: - See :meth:`pype.lib.JSONSettingRegistry.set_item` + See :meth:`openpype.lib.JSONSettingRegistry.set_item` """ with open(self._registry_file, "r+") as cfg: @@ -453,7 +468,7 @@ class JSONSettingRegistry(ASettingRegistry): class OpenPypeSettingsRegistry(JSONSettingRegistry): - """Class handling Pype general settings registry. + """Class handling OpenPype general settings registry. Attributes: vendor (str): Name used for path construction. @@ -464,7 +479,7 @@ class OpenPypeSettingsRegistry(JSONSettingRegistry): def __init__(self, name=None): self.vendor = "pypeclub" self.product = "openpype" - if name is None: + if not name: name = "openpype_settings" path = appdirs.user_data_dir(self.product, self.vendor) super(OpenPypeSettingsRegistry, self).__init__(name, path) @@ -506,5 +521,5 @@ def change_openpype_mongo_url(new_mongo_url): """ validate_mongo_connection(new_mongo_url) - registry = OpenPypeSettingsRegistry() - registry.set_secure_item("openPypeMongo", new_mongo_url) + registry = OpenPypeSecureRegistry("Settings") + registry.set_item("openPypeMongo", new_mongo_url) From 3785e9b53c5fc90bcfb32ea6dc39afb965b9e130 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Apr 2021 16:54:03 +0200 Subject: [PATCH 41/68] fix tests --- tests/pype/lib/test_user_settings.py | 32 ++++++++++++++++++---------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/tests/pype/lib/test_user_settings.py b/tests/pype/lib/test_user_settings.py index 7f0f400f59..02342abbc9 100644 --- a/tests/pype/lib/test_user_settings.py +++ b/tests/pype/lib/test_user_settings.py @@ -1,10 +1,20 @@ import pytest -from pype.lib import IniSettingRegistry -from pype.lib import JSONSettingRegistry +from pype.lib import ( + IniSettingRegistry, + JSONSettingRegistry, + OpenPypeSecureRegistry +) from uuid import uuid4 import configparser +@pytest.fixture +def secure_registry(tmpdir): + name = "pypetest_{}".format(str(uuid4())) + r = OpenPypeSecureRegistry(name, tmpdir) + yield r + + @pytest.fixture def json_registry(tmpdir): name = "pypetest_{}".format(str(uuid4())) @@ -19,21 +29,21 @@ def ini_registry(tmpdir): yield r -def test_keyring(json_registry): - json_registry.set_secure_item("item1", "foo") - json_registry.set_secure_item("item2", "bar") - result1 = json_registry.get_secure_item("item1") - result2 = json_registry.get_secure_item("item2") +def test_keyring(secure_registry): + secure_registry.set_item("item1", "foo") + secure_registry.set_item("item2", "bar") + result1 = secure_registry.get_item("item1") + result2 = secure_registry.get_item("item2") assert result1 == "foo" assert result2 == "bar" - json_registry.delete_secure_item("item1") - json_registry.delete_secure_item("item2") + secure_registry.delete_item("item1") + secure_registry.delete_item("item2") with pytest.raises(ValueError): - json_registry.get_secure_item("item1") - json_registry.get_secure_item("item2") + secure_registry.get_item("item1") + secure_registry.get_item("item2") def test_ini_registry(ini_registry): From ee93c229fef4ed0b3cde55b6384bd8b704b7deac Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Apr 2021 16:54:32 +0200 Subject: [PATCH 42/68] added some docstring to OpenPypeSecureRegistry --- igniter/user_settings.py | 11 +++++++++++ openpype/lib/local_settings.py | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/igniter/user_settings.py b/igniter/user_settings.py index bb5336a9ab..2a406f83dd 100644 --- a/igniter/user_settings.py +++ b/igniter/user_settings.py @@ -32,6 +32,17 @@ _PLACEHOLDER = object() class OpenPypeSecureRegistry: + """Store information using keyring. + + Registry should be used for private data that should be available only for + user. + + All passed registry names will have added prefix `OpenPype/` to easier + identify which data were created by OpenPype. + + Args: + name(str): Name of registry used as identifier for data. + """ def __init__(self, name): try: import keyring diff --git a/openpype/lib/local_settings.py b/openpype/lib/local_settings.py index ec76b57cfd..1bd8540acb 100644 --- a/openpype/lib/local_settings.py +++ b/openpype/lib/local_settings.py @@ -35,6 +35,17 @@ _PLACEHOLDER = object() class OpenPypeSecureRegistry: + """Store information using keyring. + + Registry should be used for private data that should be available only for + user. + + All passed registry names will have added prefix `OpenPype/` to easier + identify which data were created by OpenPype. + + Args: + name(str): Name of registry used as identifier for data. + """ def __init__(self, name): try: import keyring From e81c9dd5b5ad7020d1da8ebfe39ab86854e2aafb Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Apr 2021 16:54:55 +0200 Subject: [PATCH 43/68] fix bootstrap attribute access --- start.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/start.py b/start.py index 1f946a705c..36e4c36d21 100644 --- a/start.py +++ b/start.py @@ -296,7 +296,7 @@ def _determine_mongodb() -> str: if not openpype_mongo: # try system keyring try: - openpype_mongo = bootstrap.registry.get_secure_item( + openpype_mongo = bootstrap.secure_registry.get_item( "openPypeMongo") except ValueError: print("*** No DB connection string specified.") @@ -305,7 +305,7 @@ def _determine_mongodb() -> str: igniter.open_dialog() try: - openpype_mongo = bootstrap.registry.get_secure_item( + openpype_mongo = bootstrap.secure_registry.get_item( "openPypeMongo") except ValueError: raise RuntimeError("missing mongodb url") From 787d037ca02a1a7e295a446bd459b7c7b6ec8e5b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Apr 2021 17:08:04 +0200 Subject: [PATCH 44/68] imported OpenPypeSecureRegistry in pype.lib.__init__ --- openpype/lib/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/lib/__init__.py b/openpype/lib/__init__.py index 7b2f533921..ce8f8ec2b6 100644 --- a/openpype/lib/__init__.py +++ b/openpype/lib/__init__.py @@ -104,6 +104,7 @@ from .plugin_tools import ( from .local_settings import ( IniSettingRegistry, JSONSettingRegistry, + OpenPypeSecureRegistry, OpenPypeSettingsRegistry, get_local_site_id, change_openpype_mongo_url @@ -217,6 +218,7 @@ __all__ = [ "IniSettingRegistry", "JSONSettingRegistry", + "OpenPypeSecureRegistry", "OpenPypeSettingsRegistry", "get_local_site_id", "change_openpype_mongo_url", From 261e1db2c0751b29df870c190e9ce75cda9eeadf Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Apr 2021 17:15:26 +0200 Subject: [PATCH 45/68] changed keyring key of mongodb data to OpenPype/mongodb --- igniter/bootstrap_repos.py | 2 +- igniter/install_dialog.py | 2 +- openpype/lib/local_settings.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/igniter/bootstrap_repos.py b/igniter/bootstrap_repos.py index 51dec7f51e..f624b96125 100644 --- a/igniter/bootstrap_repos.py +++ b/igniter/bootstrap_repos.py @@ -242,7 +242,7 @@ class BootstrapRepos: self._app = "openpype" self._log = log.getLogger(str(__class__)) self.data_dir = Path(user_data_dir(self._app, self._vendor)) - self.secure_registry = OpenPypeSecureRegistry("Settings") + self.secure_registry = OpenPypeSecureRegistry("mongodb") self.registry = OpenPypeSettingsRegistry() self.zip_filter = [".pyc", "__pycache__"] self.openpype_filter = [ diff --git a/igniter/install_dialog.py b/igniter/install_dialog.py index dab00079a5..27b2d1fe37 100644 --- a/igniter/install_dialog.py +++ b/igniter/install_dialog.py @@ -42,7 +42,7 @@ class InstallDialog(QtWidgets.QDialog): def __init__(self, parent=None): super(InstallDialog, self).__init__(parent) - self.secure_registry = OpenPypeSecureRegistry("Settings") + self.secure_registry = OpenPypeSecureRegistry("mongodb") self.mongo_url = "" try: diff --git a/openpype/lib/local_settings.py b/openpype/lib/local_settings.py index 1bd8540acb..c043a2f837 100644 --- a/openpype/lib/local_settings.py +++ b/openpype/lib/local_settings.py @@ -532,5 +532,5 @@ def change_openpype_mongo_url(new_mongo_url): """ validate_mongo_connection(new_mongo_url) - registry = OpenPypeSecureRegistry("Settings") + registry = OpenPypeSecureRegistry("mongodb") registry.set_item("openPypeMongo", new_mongo_url) From cbd40cd73b1dfc86f3d35a9920ba4c715b60dc41 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Apr 2021 19:20:56 +0200 Subject: [PATCH 46/68] old mongo value is removed before change --- openpype/lib/local_settings.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openpype/lib/local_settings.py b/openpype/lib/local_settings.py index c043a2f837..5d2955532a 100644 --- a/openpype/lib/local_settings.py +++ b/openpype/lib/local_settings.py @@ -106,7 +106,7 @@ class OpenPypeSecureRegistry: import keyring value = keyring.get_password(self._name, name) - if value: + if value is not None: return value if default is not _PLACEHOLDER: @@ -532,5 +532,9 @@ def change_openpype_mongo_url(new_mongo_url): """ validate_mongo_connection(new_mongo_url) + key = "openPypeMongo" registry = OpenPypeSecureRegistry("mongodb") - registry.set_item("openPypeMongo", new_mongo_url) + existing_value = registry.get_item(key, None) + if existing_value is not None: + registry.delete_item(key) + registry.set_item(key, new_mongo_url) From 864e8b882cf881b9675bba439fc3f3b67d5796a6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Apr 2021 19:42:31 +0200 Subject: [PATCH 47/68] idle manager is always enabled and starts thread only if has registered callbacks --- openpype/modules/idle_manager/idle_module.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/modules/idle_manager/idle_module.py b/openpype/modules/idle_manager/idle_module.py index ddccf07f6a..c06dbed78c 100644 --- a/openpype/modules/idle_manager/idle_module.py +++ b/openpype/modules/idle_manager/idle_module.py @@ -40,8 +40,7 @@ class IdleManager(PypeModule, ITrayService): name = "idle_manager" def initialize(self, module_settings): - idle_man_settings = module_settings[self.name] - self.enabled = idle_man_settings["enabled"] + self.enabled = True self.time_callbacks = collections.defaultdict(list) self.idle_thread = None @@ -50,7 +49,8 @@ class IdleManager(PypeModule, ITrayService): return def tray_start(self): - self.start_thread() + if self.time_callbacks: + self.start_thread() def tray_exit(self): self.stop_thread() From f0b47e1c7ac493ae2f958a70685e21c1dc1589d7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Apr 2021 19:42:52 +0200 Subject: [PATCH 48/68] removed idle manager from settings schema --- .../settings/defaults/system_settings/modules.json | 3 --- .../schemas/system_schema/schema_modules.json | 14 -------------- 2 files changed, 17 deletions(-) diff --git a/openpype/settings/defaults/system_settings/modules.json b/openpype/settings/defaults/system_settings/modules.json index 00e98aa8de..3453bf72ec 100644 --- a/openpype/settings/defaults/system_settings/modules.json +++ b/openpype/settings/defaults/system_settings/modules.json @@ -165,8 +165,5 @@ }, "standalonepublish_tool": { "enabled": true - }, - "idle_manager": { - "enabled": true } } \ No newline at end of file diff --git a/openpype/settings/entities/schemas/system_schema/schema_modules.json b/openpype/settings/entities/schemas/system_schema/schema_modules.json index 8bfb0e90dc..ad58411c9e 100644 --- a/openpype/settings/entities/schemas/system_schema/schema_modules.json +++ b/openpype/settings/entities/schemas/system_schema/schema_modules.json @@ -176,20 +176,6 @@ "label": "Enabled" } ] - }, - { - "type": "dict", - "key": "idle_manager", - "label": "Idle Manager", - "collapsible": true, - "checkbox_key": "enabled", - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - } - ] } ] } From 84fb977558f66ac1ef80145d3ad0b05d513baa55 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Apr 2021 19:45:59 +0200 Subject: [PATCH 49/68] added auto stop attribute to timers manager --- openpype/modules/timers_manager/timers_manager.py | 5 +++++ openpype/settings/defaults/system_settings/modules.json | 1 + .../entities/schemas/system_schema/schema_modules.json | 5 +++++ 3 files changed, 11 insertions(+) diff --git a/openpype/modules/timers_manager/timers_manager.py b/openpype/modules/timers_manager/timers_manager.py index a8ea5799e6..92edd5aeaa 100644 --- a/openpype/modules/timers_manager/timers_manager.py +++ b/openpype/modules/timers_manager/timers_manager.py @@ -45,11 +45,13 @@ class TimersManager(PypeModule, ITrayService, IIdleManager, IWebServerRoutes): timers_settings = modules_settings[self.name] self.enabled = timers_settings["enabled"] + auto_stop = timers_settings["auto_stop"] # When timer will stop if idle manager is running (minutes) full_time = int(timers_settings["full_time"] * 60) # How many minutes before the timer is stopped will popup the message message_time = int(timers_settings["message_time"] * 60) + self.auto_stop = auto_stop self.time_show_message = full_time - message_time self.time_stop_timer = full_time @@ -160,6 +162,9 @@ class TimersManager(PypeModule, ITrayService, IIdleManager, IWebServerRoutes): def callbacks_by_idle_time(self): """Implementation of IIdleManager interface.""" # Time when message is shown + if not self.auto_stop: + return {} + callbacks = collections.defaultdict(list) callbacks[self.time_show_message].append(lambda: self.time_callback(0)) diff --git a/openpype/settings/defaults/system_settings/modules.json b/openpype/settings/defaults/system_settings/modules.json index 3453bf72ec..b3065058a1 100644 --- a/openpype/settings/defaults/system_settings/modules.json +++ b/openpype/settings/defaults/system_settings/modules.json @@ -126,6 +126,7 @@ }, "timers_manager": { "enabled": true, + "auto_stop": true, "full_time": 15.0, "message_time": 0.5 }, diff --git a/openpype/settings/entities/schemas/system_schema/schema_modules.json b/openpype/settings/entities/schemas/system_schema/schema_modules.json index ad58411c9e..8512514ff3 100644 --- a/openpype/settings/entities/schemas/system_schema/schema_modules.json +++ b/openpype/settings/entities/schemas/system_schema/schema_modules.json @@ -42,6 +42,11 @@ "key": "enabled", "label": "Enabled" }, + { + "type": "boolean", + "key": "auto_stop", + "label": "Auto stop timer" + }, { "type": "number", "decimal": 2, From 5af4b1682816e45a51a86f5dc00349f624257208 Mon Sep 17 00:00:00 2001 From: Petr Dvorak Date: Thu, 8 Apr 2021 21:15:21 +0200 Subject: [PATCH 50/68] some text correction --- website/docs/artist_ftrack.md | 1 - website/docs/manager_ftrack_actions.md | 15 +++++---------- website/docs/module_ftrack.md | 6 +++--- 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/website/docs/artist_ftrack.md b/website/docs/artist_ftrack.md index 44d8b0c007..2210615160 100644 --- a/website/docs/artist_ftrack.md +++ b/website/docs/artist_ftrack.md @@ -115,7 +115,6 @@ sidebar_label: Artist **2. possibility - Ftrack URL is not set or is not right** - Check **Ftrack URL** value in *Ftrack login* window - Inform your administrator if URL is incorrect and launch OpenPype again when administrator fix it -- The Ftrack URL can be changed in OpenPype Settings → System → Modules → Ftrack **3. possibility - Ftrack Web app can't be reached the way OpenPype use it** - Enter your **Username** and [API key](#where-to-find-api-key) in *Ftrack login* window and press **Login** button diff --git a/website/docs/manager_ftrack_actions.md b/website/docs/manager_ftrack_actions.md index 950f667f9a..aa4c554614 100644 --- a/website/docs/manager_ftrack_actions.md +++ b/website/docs/manager_ftrack_actions.md @@ -28,8 +28,9 @@ In most cases actions filtered by entity type: So if you do not see action you need to use check if action is available for selected *entity type* or ask *administrator* to check if you have permissions to use it. - -Actions can be highly customized according to specific client's requests. +:::note +Actions can be heavily customised by your studio, so this guide might not fit 100 %. +::: :::important Filtering can be more complicated for example a lot of actions can be shown only when one particular entity is selected. @@ -65,12 +66,6 @@ Project Manager or Supervisor must set project's applications during project pre #### A group of actions that are used for OpenPype Administration. -### Create Update Avalon Attributes -* Entity types: All -* User roles: Pypeclub, Administrator - -Action creates and updates Ftrack's Custom Attributes that are needed to manage and run OpenPype within Ftrack. Most of custom attribute configurations are stored in OpenPype settings (*click on the systray OpenPype icon → Settings → Project → Anatomy → Attributes*). It is not recommended to modify values stored in the file unless your studio used completely custom configuration. - ### Sync to Avalon * Entity types: Project, Typed Context * User roles: Pypeclub, Administrator, Project manager @@ -126,7 +121,7 @@ With this action it's possible to delete up to 15 entities at once from active p * Entity types: Project * User roles: Pypeclub, Administrator, Project manager -Allows project managers and coordinator to *set basic project attributes* needed for OpenPype to operate, *Create project folders* if you want and especially prepare project specific [anatomy](admin_settings_project_anatomy) or [settings](admin_settings_project). +Allows project managers and coordinator to *set basic project attributes* needed for OpenPype to operate, *Create project folders* if you want and especially prepare project specific [settings](admin_settings_project). :::tip It is possible to use this action during the lifetime of a project but we recommend using it only once at the start of the project. @@ -217,7 +212,7 @@ Please keep in mind this action is meant to make your project setup faster at th * Entity types: Task * User roles: Pypeclub, Project manager, Administrator -Collects approved hires files and copy them into a folder. It usually creates h.264 files for preview and mov for editorial. All files are then copied according to predefined naming convention to a specific folder. +Collects approved hires files and copy them into a folder. It takes any components of any versions and copies and renames them correctly. --- diff --git a/website/docs/module_ftrack.md b/website/docs/module_ftrack.md index 9f31eac21e..bd0dbaef4f 100644 --- a/website/docs/module_ftrack.md +++ b/website/docs/module_ftrack.md @@ -24,7 +24,7 @@ You can only use our Ftrack Actions and publish to Ftrack if each artist is logg ### Custom Attributes -After successfully connecting OpenPype with you Ftrack, you can right on any project in Ftrack and you should see a bunch of actions available. The most important one is called `OpenPype Admin` and contains multiple options inside. +After successfully connecting OpenPype with you Ftrack, you can right click on any project in Ftrack and you should see a bunch of actions available. The most important one is called `OpenPype Admin` and contains multiple options inside. To prepare Ftrack for working with OpenPype you'll need to run [OpenPype Admin - Create/Update Avalon Attributes](manager_ftrack_actions#create-update-avalon-attributes), which creates and sets the Custom Attributes necessary for OpenPype to function. @@ -56,7 +56,7 @@ There are specific launch arguments for event server. With `openpype eventserver - `--ftrack-url "https://yourdomain.ftrackapp.com/"` : Ftrack server URL _(it is not needed to enter if you have set `FTRACK_SERVER` in OpenPype' environments)_ - `--ftrack-events-path "//Paths/To/Events/"` : Paths to events folder. May contain multiple paths separated by `;`. _(it is not needed to enter if you have set `FTRACK_EVENTS_PATH` in OpenPype' environments)_ -So if you want to use OpenPype's environments then you can launch event server for first time with these arguments `$OPENPYPE_SETUP/openpype eventserver --ftrack-user "my.username" --ftrack-api-key "00000aaa-11bb-22cc-33dd-444444eeeee" --store-credentials`. Since that time, if everything was entered correctly, you can launch event server with `$OPENPYPE_SETUP/openpype eventserver`. +So if you want to use OpenPype's environments then you can launch event server for first time with these arguments `openpype.exe eventserver --ftrack-user "my.username" --ftrack-api-key "00000aaa-11bb-22cc-33dd-444444eeeee" --store-credentials`. Since that time, if everything was entered correctly, you can launch event server with `openpype.exe eventserver`. @@ -186,7 +186,7 @@ Push thumbnails from version, up through multiple hierarchy levels Change status of next task from `Not started` to `Ready` when previous task is approved. -Multiple detailed rules for next task update can be configured in the presets. +Multiple detailed rules for next task update can be configured in the settings. ### Delete Avalon ID from new entity From 96eec587dbb48fb93b44d17a48481cc9db109cc8 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Fri, 9 Apr 2021 09:54:07 +0200 Subject: [PATCH 51/68] change ID generation to coolname --- openpype/lib/local_settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/lib/local_settings.py b/openpype/lib/local_settings.py index 5d2955532a..56bdd047c9 100644 --- a/openpype/lib/local_settings.py +++ b/openpype/lib/local_settings.py @@ -498,12 +498,12 @@ class OpenPypeSettingsRegistry(JSONSettingRegistry): def _create_local_site_id(registry=None): """Create a local site identifier.""" - from uuid import uuid4 + from coolname import generate_slug if registry is None: registry = OpenPypeSettingsRegistry() - new_id = str(uuid4()) + new_id = generate_slug(3) print("Created local site id \"{}\"".format(new_id)) From 31bd9616ec631d2bf88a81514331d3d8e7bb77f9 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Fri, 9 Apr 2021 10:05:16 +0200 Subject: [PATCH 52/68] extra space in info widget --- openpype/tools/tray/pype_info_widget.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/tray/pype_info_widget.py b/openpype/tools/tray/pype_info_widget.py index dbff36eca7..a70a360378 100644 --- a/openpype/tools/tray/pype_info_widget.py +++ b/openpype/tools/tray/pype_info_widget.py @@ -363,7 +363,7 @@ class PypeInfoWidget(QtWidgets.QWidget): "version_value": "OpenPype version:", "executable": "OpenPype executable:", "pype_root": "OpenPype location:", - "mongo_url": "OpenPype Mongo URL:" + "mongo_url": "OpenPype Mongo URL:" } # Prepare keys order keys_order = ["version_value", "executable", "pype_root", "mongo_url"] From a9857ee49262d767d0f8c032088a05baaf301391 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Fri, 9 Apr 2021 10:30:21 +0200 Subject: [PATCH 53/68] remove local site label from local settings --- .../settings/local_settings/general_widget.py | 22 +++++-------------- .../tools/settings/local_settings/window.py | 13 ++++++----- 2 files changed, 12 insertions(+), 23 deletions(-) diff --git a/openpype/tools/settings/local_settings/general_widget.py b/openpype/tools/settings/local_settings/general_widget.py index 7732157122..e820d8ab8b 100644 --- a/openpype/tools/settings/local_settings/general_widget.py +++ b/openpype/tools/settings/local_settings/general_widget.py @@ -5,28 +5,16 @@ class LocalGeneralWidgets(QtWidgets.QWidget): def __init__(self, parent): super(LocalGeneralWidgets, self).__init__(parent) - local_site_name_input = QtWidgets.QLineEdit(self) - - layout = QtWidgets.QFormLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - - layout.addRow("Local site label", local_site_name_input) - - self.local_site_name_input = local_site_name_input def update_local_settings(self, value): - site_label = "" - if value: - site_label = value.get("site_label", site_label) - self.local_site_name_input.setText(site_label) + return + + # RETURNING EARLY TO HIDE WIDGET WITHOUT CONTENT def settings_value(self): # Add changed # If these have changed then output = {} - local_site_name = self.local_site_name_input.text() - if local_site_name: - output["site_label"] = local_site_name - # Do not return output yet since we don't have mechanism to save or - # load these data through api calls + # TEMPORARILY EMPTY AS THERE IS NOTHING TO PUT HERE + return output diff --git a/openpype/tools/settings/local_settings/window.py b/openpype/tools/settings/local_settings/window.py index b6ca56d348..a12a2289b5 100644 --- a/openpype/tools/settings/local_settings/window.py +++ b/openpype/tools/settings/local_settings/window.py @@ -80,6 +80,7 @@ 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) @@ -126,9 +127,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) ) @@ -138,9 +139,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 065b64d1e08868ad063dcf6b2308cbfafff9d9c7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 9 Apr 2021 10:36:20 +0200 Subject: [PATCH 54/68] use OpenPypeSecureRegistry --- openpype/modules/clockify/clockify_api.py | 9 ++-- openpype/modules/ftrack/lib/credentials.py | 48 +++++++++++++++------- 2 files changed, 37 insertions(+), 20 deletions(-) diff --git a/openpype/modules/clockify/clockify_api.py b/openpype/modules/clockify/clockify_api.py index e2de726f39..29de5de0c9 100644 --- a/openpype/modules/clockify/clockify_api.py +++ b/openpype/modules/clockify/clockify_api.py @@ -9,8 +9,7 @@ from .constants import ( ADMIN_PERMISSION_NAMES ) -# from openpype.lib import OpenPypeSettingsRegistry -from openpype.lib.local_settings import OpenPypeSecureRegistry as OpenPypeSettingsRegistry +from openpype.lib.local_settings import OpenPypeSecureRegistry def time_check(obj): @@ -35,7 +34,7 @@ class ClockifyAPI: self.request_counter = 0 self.request_time = time.time() - self.secure_registry = OpenPypeSettingsRegistry("clockify") + self.secure_registry = OpenPypeSecureRegistry("clockify") @property def headers(self): @@ -135,10 +134,10 @@ class ClockifyAPI: return False def get_api_key(self): - return self.secure_registry.get_secure_item("api_key", None) + return self.secure_registry.get_item("api_key", None) def save_api_key(self, api_key): - self.secure_registry.set_secure_item("api_key", api_key) + self.secure_registry.set_item("api_key", api_key) def get_workspaces(self): action_url = 'workspaces/' diff --git a/openpype/modules/ftrack/lib/credentials.py b/openpype/modules/ftrack/lib/credentials.py index 05a74c0875..2d719347e7 100644 --- a/openpype/modules/ftrack/lib/credentials.py +++ b/openpype/modules/ftrack/lib/credentials.py @@ -7,7 +7,7 @@ except ImportError: from urlparse import urlparse -from openpype.lib import OpenPypeSettingsRegistry +from openpype.lib import OpenPypeSecureRegistry USERNAME_KEY = "username" API_KEY_KEY = "api_key" @@ -23,38 +23,56 @@ def get_ftrack_hostname(ftrack_server=None): return urlparse(ftrack_server).hostname -def _get_ftrack_secure_key(hostname): +def _get_ftrack_secure_key(hostname, key): """Secure item key for entered hostname.""" - return "/".join(("ftrack", hostname)) + return "/".join(("ftrack", hostname, key)) def get_credentials(ftrack_server=None): hostname = get_ftrack_hostname(ftrack_server) - secure_key = _get_ftrack_secure_key(hostname) + username_name = _get_ftrack_secure_key(hostname, USERNAME_KEY) + api_key_name = _get_ftrack_secure_key(hostname, API_KEY_KEY) + + username_registry = OpenPypeSecureRegistry(username_name) + api_key_registry = OpenPypeSecureRegistry(api_key_name) - registry = OpenPypeSettingsRegistry(secure_key) return { - USERNAME_KEY: registry.get_secure_item(USERNAME_KEY, None), - API_KEY_KEY: registry.get_secure_item(API_KEY_KEY, None) + USERNAME_KEY: username_registry.get_item(USERNAME_KEY, None), + API_KEY_KEY: api_key_registry.get_item(API_KEY_KEY, None) } def save_credentials(username, api_key, ftrack_server=None): hostname = get_ftrack_hostname(ftrack_server) - secure_key = _get_ftrack_secure_key(hostname) + username_name = _get_ftrack_secure_key(hostname, USERNAME_KEY) + api_key_name = _get_ftrack_secure_key(hostname, API_KEY_KEY) - registry = OpenPypeSettingsRegistry(secure_key) - registry.set_secure_item(USERNAME_KEY, username) - registry.set_secure_item(API_KEY_KEY, api_key) + # Clear credentials + clear_credentials(ftrack_server) + + username_registry = OpenPypeSecureRegistry(username_name) + api_key_registry = OpenPypeSecureRegistry(api_key_name) + + username_registry.set_item(USERNAME_KEY, username) + api_key_registry.set_item(API_KEY_KEY, api_key) def clear_credentials(ftrack_server=None): hostname = get_ftrack_hostname(ftrack_server) - secure_key = _get_ftrack_secure_key(hostname) + username_name = _get_ftrack_secure_key(hostname, USERNAME_KEY) + api_key_name = _get_ftrack_secure_key(hostname, API_KEY_KEY) - registry = OpenPypeSettingsRegistry(secure_key) - registry.delete_secure_item(USERNAME_KEY) - registry.delete_secure_item(API_KEY_KEY) + username_registry = OpenPypeSecureRegistry(username_name) + api_key_registry = OpenPypeSecureRegistry(api_key_name) + + current_username = username_registry.get_item(USERNAME_KEY, None) + current_api_key = api_key_registry.get_item(API_KEY_KEY, None) + + if current_username is not None: + username_registry.delete_item(USERNAME_KEY) + + if current_api_key is not None: + api_key_registry.delete_item(API_KEY_KEY) def check_credentials(username, api_key, ftrack_server=None): From 8e183b05e982f2d7063493610cbc25f4e8a280fc Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 9 Apr 2021 15:29:28 +0200 Subject: [PATCH 55/68] added default task type mapping settings to ftrack module --- .../defaults/system_settings/modules.json | 16 ++++++++++++++++ .../module_settings/schema_ftrack.json | 6 ++++++ 2 files changed, 22 insertions(+) diff --git a/openpype/settings/defaults/system_settings/modules.json b/openpype/settings/defaults/system_settings/modules.json index b3065058a1..09198a3ad9 100644 --- a/openpype/settings/defaults/system_settings/modules.json +++ b/openpype/settings/defaults/system_settings/modules.json @@ -21,6 +21,22 @@ }, "default": "-" }, + "task_short_names": { + "Generic": "gener", + "Art": "art", + "Modeling": "mdl", + "Texture": "tex", + "Lookdev": "look", + "Rigging": "rig", + "Edit": "edit", + "Layout": "lay", + "Setdress": "dress", + "Animation": "anim", + "FX": "fx", + "Lighting": "lgt", + "Paint": "paint", + "Compositing": "comp" + }, "custom_attributes": { "show": { "avalon_auto_sync": { diff --git a/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json b/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json index 50ec330a11..2ddb3be795 100644 --- a/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json +++ b/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json @@ -66,6 +66,12 @@ } ] }, + { + "type": "dict-modifiable", + "key": "task_short_names", + "label": "Default task short names (by Task type)", + "object_type": "text" + }, { "key": "custom_attributes", "label": "Custom Attributes", From 3eab325ca83956c71993f7f21448e958c588b363 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 9 Apr 2021 15:30:41 +0200 Subject: [PATCH 56/68] use short code mapping during synchronization --- openpype/modules/ftrack/lib/avalon_sync.py | 15 +++++++++++---- openpype/modules/ftrack/lib/settings.py | 2 +- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/openpype/modules/ftrack/lib/avalon_sync.py b/openpype/modules/ftrack/lib/avalon_sync.py index 7511c2627b..8a453f8935 100644 --- a/openpype/modules/ftrack/lib/avalon_sync.py +++ b/openpype/modules/ftrack/lib/avalon_sync.py @@ -26,6 +26,8 @@ from pymongo import UpdateOne import ftrack_api from openpype.lib import ApplicationManager +from .settings import get_ftrack_settings + log = Logger.get_logger(__name__) @@ -1221,6 +1223,12 @@ class SyncEntitiesFactory: def prepare_ftrack_ent_data(self): not_set_ids = [] + # Prepare short task type mapping + _task_short_names = get_ftrack_settings()["task_short_names"] + task_short_names = { + key.lower(): value + for key, value in _task_short_names.items() + } for id, entity_dict in self.entities_dict.items(): entity = entity_dict["entity"] if entity is None: @@ -1259,11 +1267,10 @@ class SyncEntitiesFactory: tasks = {} for task_type in task_types: task_type_name = task_type["name"] - # Set short name to empty string - # QUESTION Maybe better would be to lower and remove spaces - # from task type name. + # Set short name to mapping from settings or empty string + short_name = task_short_names.get(task_type_name.lower()) tasks[task_type_name] = { - "short_name": "" + "short_name": short_name or "" } current_project_anatomy_data = get_anatomy_settings( diff --git a/openpype/modules/ftrack/lib/settings.py b/openpype/modules/ftrack/lib/settings.py index f6967411db..027356edc6 100644 --- a/openpype/modules/ftrack/lib/settings.py +++ b/openpype/modules/ftrack/lib/settings.py @@ -1,6 +1,7 @@ import os from openpype.api import get_system_settings + def get_ftrack_settings(): return get_system_settings()["modules"]["ftrack"] @@ -10,7 +11,6 @@ def get_ftrack_url_from_settings(): def get_ftrack_event_mongo_info(): - ftrack_settings = get_ftrack_settings() database_name = os.environ["OPENPYPE_DATABASE_NAME"] collection_name = "ftrack_events" return database_name, collection_name From 1023f82139996f6a58dd0a586d717a83e4704038 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 9 Apr 2021 16:22:04 +0200 Subject: [PATCH 57/68] removed added settings --- .../defaults/system_settings/modules.json | 16 ---------------- .../module_settings/schema_ftrack.json | 6 ------ 2 files changed, 22 deletions(-) diff --git a/openpype/settings/defaults/system_settings/modules.json b/openpype/settings/defaults/system_settings/modules.json index 09198a3ad9..b3065058a1 100644 --- a/openpype/settings/defaults/system_settings/modules.json +++ b/openpype/settings/defaults/system_settings/modules.json @@ -21,22 +21,6 @@ }, "default": "-" }, - "task_short_names": { - "Generic": "gener", - "Art": "art", - "Modeling": "mdl", - "Texture": "tex", - "Lookdev": "look", - "Rigging": "rig", - "Edit": "edit", - "Layout": "lay", - "Setdress": "dress", - "Animation": "anim", - "FX": "fx", - "Lighting": "lgt", - "Paint": "paint", - "Compositing": "comp" - }, "custom_attributes": { "show": { "avalon_auto_sync": { diff --git a/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json b/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json index 2ddb3be795..50ec330a11 100644 --- a/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json +++ b/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json @@ -66,12 +66,6 @@ } ] }, - { - "type": "dict-modifiable", - "key": "task_short_names", - "label": "Default task short names (by Task type)", - "object_type": "text" - }, { "key": "custom_attributes", "label": "Custom Attributes", From 1359109e44dd6f0caa8c67ebcb8ca87c5e48ed5e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 9 Apr 2021 16:59:49 +0200 Subject: [PATCH 58/68] Use default anatomy task types instead of empty on project sync --- openpype/modules/ftrack/lib/avalon_sync.py | 25 ++++++++++------------ 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/openpype/modules/ftrack/lib/avalon_sync.py b/openpype/modules/ftrack/lib/avalon_sync.py index 8a453f8935..b9702c1560 100644 --- a/openpype/modules/ftrack/lib/avalon_sync.py +++ b/openpype/modules/ftrack/lib/avalon_sync.py @@ -1223,12 +1223,6 @@ class SyncEntitiesFactory: def prepare_ftrack_ent_data(self): not_set_ids = [] - # Prepare short task type mapping - _task_short_names = get_ftrack_settings()["task_short_names"] - task_short_names = { - key.lower(): value - for key, value in _task_short_names.items() - } for id, entity_dict in self.entities_dict.items(): entity = entity_dict["entity"] if entity is None: @@ -1264,18 +1258,21 @@ class SyncEntitiesFactory: if not msg or not items: continue self.report_items["warning"][msg] = items - tasks = {} - for task_type in task_types: - task_type_name = task_type["name"] - # Set short name to mapping from settings or empty string - short_name = task_short_names.get(task_type_name.lower()) - tasks[task_type_name] = { - "short_name": short_name or "" - } current_project_anatomy_data = get_anatomy_settings( project_name, exclude_locals=True ) + anatomy_tasks = current_project_anatomy_data["tasks"] + tasks = {} + default_type_data = { + "short_name": "" + } + for task_type in task_types: + task_type_name = task_type["name"] + tasks[task_type_name] = copy.deepcopy( + anatomy_tasks.get(task_type_name) + or default_type_data + ) project_config = { "tasks": tasks, From 667b977fca277d11afd70e4121bfa5f3d72fec21 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 9 Apr 2021 19:05:23 +0200 Subject: [PATCH 59/68] removed unused import --- openpype/modules/ftrack/lib/avalon_sync.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/modules/ftrack/lib/avalon_sync.py b/openpype/modules/ftrack/lib/avalon_sync.py index b9702c1560..fbe65efb35 100644 --- a/openpype/modules/ftrack/lib/avalon_sync.py +++ b/openpype/modules/ftrack/lib/avalon_sync.py @@ -26,8 +26,6 @@ from pymongo import UpdateOne import ftrack_api from openpype.lib import ApplicationManager -from .settings import get_ftrack_settings - log = Logger.get_logger(__name__) From a0c98549ebd32ad1ae24b6aa85f6e9905a135f46 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 12 Apr 2021 14:49:28 +0200 Subject: [PATCH 60/68] implemented method that can queyr custom attribute values in chunks --- openpype/modules/ftrack/lib/avalon_sync.py | 35 ++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/openpype/modules/ftrack/lib/avalon_sync.py b/openpype/modules/ftrack/lib/avalon_sync.py index fbe65efb35..c2c3f0bd0d 100644 --- a/openpype/modules/ftrack/lib/avalon_sync.py +++ b/openpype/modules/ftrack/lib/avalon_sync.py @@ -891,6 +891,41 @@ class SyncEntitiesFactory: self.entities_dict[parent_id]["children"].remove(id) + def _query_custom_attributes(self, session, conf_ids, entity_ids): + output = [] + # Prepare values to query + attributes_joined = ", ".join([ + "\"{}\"".format(conf_id) for conf_id in conf_ids + ]) + attributes_len = len(conf_ids) + chunk_size = int(5000 / attributes_len) + if chunk_size < 1: + chunk_size = 1 + for idx in range(0, attributes_len, chunk_size): + _entity_ids = entity_ids[idx:idx + chunk_size] + if not _entity_ids: + continue + entity_ids_joined = ", ".join([ + "\"{}\"".format(entity_id) + for entity_id in _entity_ids + ]) + + call_expr = [{ + "action": "query", + "expression": ( + "select value, entity_id from ContextCustomAttributeValue " + "where entity_id in ({}) and configuration_id in ({})" + ).format(entity_ids_joined, attributes_joined) + }] + if hasattr(session, "call"): + [result] = session.call(call_expr) + else: + [result] = session._call(call_expr) + + for item in result["data"]: + output.append(item) + return output + def set_cutom_attributes(self): self.log.debug("* Preparing custom attributes") # Get custom attributes and values From b1563b51ddf5e71972e4d049d946e2b599882dad Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 12 Apr 2021 14:49:48 +0200 Subject: [PATCH 61/68] use `_query_custom_attributes` to get custom attribute values --- openpype/modules/ftrack/lib/avalon_sync.py | 56 +++++----------------- 1 file changed, 12 insertions(+), 44 deletions(-) diff --git a/openpype/modules/ftrack/lib/avalon_sync.py b/openpype/modules/ftrack/lib/avalon_sync.py index c2c3f0bd0d..2411d8da82 100644 --- a/openpype/modules/ftrack/lib/avalon_sync.py +++ b/openpype/modules/ftrack/lib/avalon_sync.py @@ -1035,31 +1035,13 @@ class SyncEntitiesFactory: copy.deepcopy(prepared_avalon_attr_ca_id) ) - # TODO query custom attributes by entity_id - entity_ids_joined = ", ".join([ - "\"{}\"".format(id) for id in sync_ids - ]) - attributes_joined = ", ".join([ - "\"{}\"".format(attr_id) for attr_id in attribute_key_by_id.keys() - ]) - - cust_attr_query = ( - "select value, configuration_id, entity_id" - " from ContextCustomAttributeValue" - " where entity_id in ({}) and configuration_id in ({})" + items = self._query_custom_attributes( + self.session, + list(attribute_key_by_id.keys()), + sync_ids ) - call_expr = [{ - "action": "query", - "expression": cust_attr_query.format( - entity_ids_joined, attributes_joined - ) - }] - if hasattr(self.session, "call"): - [values] = self.session.call(call_expr) - else: - [values] = self.session._call(call_expr) - for item in values["data"]: + for item in items: entity_id = item["entity_id"] attr_id = item["configuration_id"] key = attribute_key_by_id[attr_id] @@ -1141,28 +1123,14 @@ class SyncEntitiesFactory: for key, val in prepare_dict_avalon.items(): entity_dict["avalon_attrs"][key] = val - # Prepare values to query - entity_ids_joined = ", ".join([ - "\"{}\"".format(id) for id in sync_ids - ]) - attributes_joined = ", ".join([ - "\"{}\"".format(attr_id) for attr_id in attribute_key_by_id.keys() - ]) - avalon_hier = [] - call_expr = [{ - "action": "query", - "expression": ( - "select value, entity_id, configuration_id" - " from ContextCustomAttributeValue" - " where entity_id in ({}) and configuration_id in ({})" - ).format(entity_ids_joined, attributes_joined) - }] - if hasattr(self.session, "call"): - [values] = self.session.call(call_expr) - else: - [values] = self.session._call(call_expr) + items = self._query_custom_attributes( + self.session, + list(attribute_key_by_id.keys()), + sync_ids + ) - for item in values["data"]: + avalon_hier = [] + for item in items: value = item["value"] # WARNING It is not possible to propage enumerate hierachical # attributes with multiselection 100% right. Unseting all values From 46963fdddf9e3cea3da9780bd7ee171c827dfd24 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 12 Apr 2021 16:55:15 +0200 Subject: [PATCH 62/68] fix custom attribute query --- openpype/modules/ftrack/lib/avalon_sync.py | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/openpype/modules/ftrack/lib/avalon_sync.py b/openpype/modules/ftrack/lib/avalon_sync.py index 2411d8da82..79e1366a0d 100644 --- a/openpype/modules/ftrack/lib/avalon_sync.py +++ b/openpype/modules/ftrack/lib/avalon_sync.py @@ -894,21 +894,13 @@ class SyncEntitiesFactory: def _query_custom_attributes(self, session, conf_ids, entity_ids): output = [] # Prepare values to query - attributes_joined = ", ".join([ - "\"{}\"".format(conf_id) for conf_id in conf_ids - ]) + attributes_joined = join_query_keys(conf_ids) attributes_len = len(conf_ids) chunk_size = int(5000 / attributes_len) - if chunk_size < 1: - chunk_size = 1 - for idx in range(0, attributes_len, chunk_size): - _entity_ids = entity_ids[idx:idx + chunk_size] - if not _entity_ids: - continue - entity_ids_joined = ", ".join([ - "\"{}\"".format(entity_id) - for entity_id in _entity_ids - ]) + for idx in range(0, len(entity_ids), chunk_size): + entity_ids_joined = join_query_keys( + entity_ids[idx:idx + chunk_size] + ) call_expr = [{ "action": "query", From 6d68cfa579ea07b52c310f7e2d9013f05de28c46 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Tue, 13 Apr 2021 10:12:00 +0100 Subject: [PATCH 63/68] Let Blender load scene settings from Ftrack --- openpype/hosts/blender/api/__init__.py | 40 +++++++++++++++++++------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/blender/api/__init__.py b/openpype/hosts/blender/api/__init__.py index c5b0a44072..66102a2ae1 100644 --- a/openpype/hosts/blender/api/__init__.py +++ b/openpype/hosts/blender/api/__init__.py @@ -51,18 +51,38 @@ def set_start_end_frames(): "name": asset_name }) - # Default frame start/end - frameStart = 0 - frameEnd = 100 + scene = bpy.context.scene - # Check if frameStart/frameEnd are set - if asset_doc["data"]["frameStart"]: - frameStart = asset_doc["data"]["frameStart"] - if asset_doc["data"]["frameEnd"]: - frameEnd = asset_doc["data"]["frameEnd"] + # Default scene settings + frameStart = scene.frame_start + frameEnd = scene.frame_end + fps = scene.render.fps + resolution_x = scene.render.resolution_x + resolution_y = scene.render.resolution_y + + # Check if settings are set + data = asset_doc.get("data") + + if not data: + return + + if data.get("frameStart"): + frameStart = data.get("frameStart") + if data.get("frameEnd"): + frameEnd = data.get("frameEnd") + if data.get("fps"): + fps = data.get("fps") + if data.get("resolutionWidth"): + resolution_x = data.get("resolutionWidth") + if data.get("resolutionHeight"): + resolution_y = data.get("resolutionHeight") + + scene.frame_start = frameStart + scene.frame_end = frameEnd + scene.render.fps = fps + scene.render.resolution_x = resolution_x + scene.render.resolution_y = resolution_y - bpy.context.scene.frame_start = frameStart - bpy.context.scene.frame_end = frameEnd def on_new(arg1, arg2): set_start_end_frames() From db777ee3f46254fc17939357c8fae9f6c98aaff5 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Tue, 13 Apr 2021 10:12:26 +0100 Subject: [PATCH 64/68] Added new validator for Object Mode for objects in the scene --- .../plugins/publish/validate_object_mode.py | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 openpype/hosts/blender/plugins/publish/validate_object_mode.py diff --git a/openpype/hosts/blender/plugins/publish/validate_object_mode.py b/openpype/hosts/blender/plugins/publish/validate_object_mode.py new file mode 100644 index 0000000000..97456a581e --- /dev/null +++ b/openpype/hosts/blender/plugins/publish/validate_object_mode.py @@ -0,0 +1,36 @@ +from typing import List + +import bpy + +import pyblish.api +import openpype.hosts.blender.api.action + + +class ValidateObjectIsInObjectMode(pyblish.api.InstancePlugin): + """Validate that the current object is in Object Mode.""" + + order = pyblish.api.ValidatorOrder - 0.01 + hosts = ["blender"] + families = ["model", "rig"] + category = "geometry" + label = "Object is in Object Mode" + actions = [openpype.hosts.blender.api.action.SelectInvalidAction] + optional = True + + @classmethod + def get_invalid(cls, instance) -> List: + invalid = [] + for obj in [obj for obj in instance]: + try: + if obj.type == 'MESH' or obj.type == 'ARMATURE': + # Check if the object is in object mode. + if not obj.mode == 'OBJECT': + invalid.append(obj) + except: + continue + return invalid + + def process(self, instance): + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError(f"Object found in instance is not in Object Mode: {invalid}") From 2a872de0df42bf088fc3680a9ff0ea6bee9efb1b Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Tue, 13 Apr 2021 10:23:30 +0100 Subject: [PATCH 65/68] Fixed Hound violations --- .../hosts/blender/plugins/publish/validate_object_mode.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/validate_object_mode.py b/openpype/hosts/blender/plugins/publish/validate_object_mode.py index 97456a581e..1c82628c1c 100644 --- a/openpype/hosts/blender/plugins/publish/validate_object_mode.py +++ b/openpype/hosts/blender/plugins/publish/validate_object_mode.py @@ -1,7 +1,5 @@ from typing import List -import bpy - import pyblish.api import openpype.hosts.blender.api.action @@ -26,11 +24,12 @@ class ValidateObjectIsInObjectMode(pyblish.api.InstancePlugin): # Check if the object is in object mode. if not obj.mode == 'OBJECT': invalid.append(obj) - except: + except Exception: continue return invalid def process(self, instance): invalid = self.get_invalid(instance) if invalid: - raise RuntimeError(f"Object found in instance is not in Object Mode: {invalid}") + raise RuntimeError( + f"Object found in instance is not in Object Mode: {invalid}") From 1d0f18666fb6f075248d3d90140615de87b5632a Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 9 Apr 2021 11:02:11 +0100 Subject: [PATCH 66/68] Validate project settings - resolution - framerate - pixel aspect - frame range # Conflicts: # openpype/hosts/tvpaint/plugins/publish/validate_project_settings.py # pype/plugins/tvpaint/publish/collect_instances.py --- .../plugins/publish/collect_instances.py | 11 ++++-- .../plugins/publish/collect_workfile_data.py | 10 +++--- .../publish/validate_project_settings.py | 36 +++++++++++++++++++ 3 files changed, 49 insertions(+), 8 deletions(-) create mode 100644 openpype/hosts/tvpaint/plugins/publish/validate_project_settings.py diff --git a/openpype/hosts/tvpaint/plugins/publish/collect_instances.py b/openpype/hosts/tvpaint/plugins/publish/collect_instances.py index 57602d9610..68c142c005 100644 --- a/openpype/hosts/tvpaint/plugins/publish/collect_instances.py +++ b/openpype/hosts/tvpaint/plugins/publish/collect_instances.py @@ -18,7 +18,7 @@ class CollectInstances(pyblish.api.ContextPlugin): )) for instance_data in workfile_instances: - instance_data["fps"] = context.data["fps"] + instance_data["fps"] = context.data["sceneFps"] # Store workfile instance data to instance data instance_data["originData"] = copy.deepcopy(instance_data) @@ -32,6 +32,11 @@ class CollectInstances(pyblish.api.ContextPlugin): subset_name = instance_data["subset"] name = instance_data.get("name", subset_name) instance_data["name"] = name + instance_data["label"] = "{} [{}-{}]".format( + name, + context.data["sceneFrameStart"], + context.data["sceneFrameEnd"] + ) active = instance_data.get("active", True) instance_data["active"] = active @@ -73,8 +78,8 @@ class CollectInstances(pyblish.api.ContextPlugin): if instance is None: continue - instance.data["frameStart"] = context.data["frameStart"] - instance.data["frameEnd"] = context.data["frameEnd"] + instance.data["frameStart"] = context.data["sceneFrameStart"] + instance.data["frameEnd"] = context.data["sceneFrameEnd"] self.log.debug("Created instance: {}\n{}".format( instance, json.dumps(instance.data, indent=4) diff --git a/openpype/hosts/tvpaint/plugins/publish/collect_workfile_data.py b/openpype/hosts/tvpaint/plugins/publish/collect_workfile_data.py index 7965112136..e683c66ea9 100644 --- a/openpype/hosts/tvpaint/plugins/publish/collect_workfile_data.py +++ b/openpype/hosts/tvpaint/plugins/publish/collect_workfile_data.py @@ -127,11 +127,11 @@ class CollectWorkfileData(pyblish.api.ContextPlugin): "currentFile": workfile_path, "sceneWidth": width, "sceneHeight": height, - "pixelAspect": pixel_apsect, - "frameStart": frame_start, - "frameEnd": frame_end, - "fps": frame_rate, - "fieldOrder": field_order + "scenePixelAspect": pixel_apsect, + "sceneFrameStart": frame_start, + "sceneFrameEnd": frame_end, + "sceneFps": frame_rate, + "sceneFieldOrder": field_order } self.log.debug( "Scene data: {}".format(json.dumps(scene_data, indent=4)) diff --git a/openpype/hosts/tvpaint/plugins/publish/validate_project_settings.py b/openpype/hosts/tvpaint/plugins/publish/validate_project_settings.py new file mode 100644 index 0000000000..fead3393ae --- /dev/null +++ b/openpype/hosts/tvpaint/plugins/publish/validate_project_settings.py @@ -0,0 +1,36 @@ +import json + +import pyblish.api + + +class ValidateProjectSettings(pyblish.api.ContextPlugin): + """Validate project settings against database. + """ + + label = "Validate Project Settings" + order = pyblish.api.ValidatorOrder + optional = True + + def process(self, context): + scene_data = { + "frameStart": context.data.get("sceneFrameStart"), + "frameEnd": context.data.get("sceneFrameEnd"), + "fps": context.data.get("sceneFps"), + "resolutionWidth": context.data.get("sceneWidth"), + "resolutionHeight": context.data.get("sceneHeight"), + "pixelAspect": context.data.get("scenePixelAspect") + } + invalid = {} + for k in scene_data.keys(): + expected_value = context.data["assetEntity"]["data"][k] + if scene_data[k] != expected_value: + invalid[k] = { + "current": scene_data[k], "expected": expected_value + } + + if invalid: + raise AssertionError( + "Project settings does not match database:\n{}".format( + json.dumps(invalid, sort_keys=True, indent=4) + ) + ) From 12d714ce749191b0928d0ed6f4bdf3b1e06deb57 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 13 Apr 2021 13:30:52 +0200 Subject: [PATCH 67/68] added schemas for tvpaint plugins --- .../schemas/projects_schema/schema_main.json | 4 +++ .../schema_project_tvpaint.json | 32 +++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json diff --git a/openpype/settings/entities/schemas/projects_schema/schema_main.json b/openpype/settings/entities/schemas/projects_schema/schema_main.json index 565500edd2..6bc158aa60 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_main.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_main.json @@ -82,6 +82,10 @@ "type": "schema", "name": "schema_project_harmony" }, + { + "type": "schema", + "name": "schema_project_tvpaint" + }, { "type": "schema", "name": "schema_project_celaction" diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json b/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json new file mode 100644 index 0000000000..b9fe26a57c --- /dev/null +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json @@ -0,0 +1,32 @@ +{ + "type": "dict", + "collapsible": true, + "key": "tvpaint", + "label": "TVPaint", + "is_file": true, + "children": [ + { + "type": "dict", + "collapsible": true, + "key": "publish", + "label": "Publish plugins", + "is_file": true, + "children": [ + { + "type": "schema_template", + "name": "template_publish_plugin", + "template_data": [ + { + "key": "ValidateMissingLayers", + "label": "ValidateMissingLayers" + } + ] + } + ] + }, + { + "type": "schema", + "name": "schema_publish_gui_filter" + } + ] +} From f7c8b818f88b19aa5c3a6fc2f8f284fe5dda525e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 13 Apr 2021 13:31:05 +0200 Subject: [PATCH 68/68] saved default tvpaint project settings --- .../settings/defaults/project_settings/tvpaint.json | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 openpype/settings/defaults/project_settings/tvpaint.json diff --git a/openpype/settings/defaults/project_settings/tvpaint.json b/openpype/settings/defaults/project_settings/tvpaint.json new file mode 100644 index 0000000000..d4130c88be --- /dev/null +++ b/openpype/settings/defaults/project_settings/tvpaint.json @@ -0,0 +1,10 @@ +{ + "publish": { + "ValidateMissingLayers": { + "enabled": true, + "optional": true, + "active": true + } + }, + "filters": {} +} \ No newline at end of file