diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index 60ce608b21..258458e2d4 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -33,7 +33,7 @@ jobs: id: version if: steps.version_type.outputs.type != 'skip' run: | - RESULT=$(python ./tools/ci_tools.py --nightly) + RESULT=$(python ./tools/ci_tools.py --nightly --github_token ${{ secrets.GITHUB_TOKEN }}) echo ::set-output name=next_tag::$RESULT diff --git a/CHANGELOG.md b/CHANGELOG.md index 57e6dbc71c..9a3571eca6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,86 +1,99 @@ # Changelog -## [3.6.0-nightly.4](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.6.2-nightly.1](https://github.com/pypeclub/OpenPype/tree/HEAD) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.5.0...HEAD) - -**🆕 New features** - -- Maya : Colorspace configuration [\#2170](https://github.com/pypeclub/OpenPype/pull/2170) -- Blender: Added support for audio [\#2168](https://github.com/pypeclub/OpenPype/pull/2168) -- Flame: a host basic integration [\#2165](https://github.com/pypeclub/OpenPype/pull/2165) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.6.1...HEAD) **🚀 Enhancements** -- Ftrack: Replace Queue with deque in event handlers logic [\#2204](https://github.com/pypeclub/OpenPype/pull/2204) -- Maya : Validate mesh ngons [\#2199](https://github.com/pypeclub/OpenPype/pull/2199) -- Delivery: Check 'frame' key in template for sequence delivery [\#2196](https://github.com/pypeclub/OpenPype/pull/2196) -- Usage of tools code [\#2185](https://github.com/pypeclub/OpenPype/pull/2185) -- Settings: Dictionary based on project roots [\#2184](https://github.com/pypeclub/OpenPype/pull/2184) -- Subset name: Be able to pass asset document to get subset name [\#2179](https://github.com/pypeclub/OpenPype/pull/2179) -- Tools: Experimental tools [\#2167](https://github.com/pypeclub/OpenPype/pull/2167) -- Loader: Refactor and use OpenPype stylesheets [\#2166](https://github.com/pypeclub/OpenPype/pull/2166) -- Add loader for linked smart objects in photoshop [\#2149](https://github.com/pypeclub/OpenPype/pull/2149) -- Burnins: DNxHD profiles handling [\#2142](https://github.com/pypeclub/OpenPype/pull/2142) -- Tools: Single access point for host tools [\#2139](https://github.com/pypeclub/OpenPype/pull/2139) +- Tools: SceneInventory in OpenPype [\#2255](https://github.com/pypeclub/OpenPype/pull/2255) +- Tools: Tasks widget [\#2251](https://github.com/pypeclub/OpenPype/pull/2251) +- Added endpoint for configured extensions [\#2221](https://github.com/pypeclub/OpenPype/pull/2221) **🐛 Bug fixes** +- Burnins: Support mxf metadata [\#2247](https://github.com/pypeclub/OpenPype/pull/2247) + +## [3.6.1](https://github.com/pypeclub/OpenPype/tree/3.6.1) (2021-11-16) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.6.1-nightly.1...3.6.1) + +**🐛 Bug fixes** + +- Loader doesn't allow changing of version before loading [\#2254](https://github.com/pypeclub/OpenPype/pull/2254) + +## [3.6.0](https://github.com/pypeclub/OpenPype/tree/3.6.0) (2021-11-15) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.6.0-nightly.6...3.6.0) + +### 📖 Documentation + +- Add alternative sites for Site Sync [\#2206](https://github.com/pypeclub/OpenPype/pull/2206) +- Add command line way of running site sync server [\#2188](https://github.com/pypeclub/OpenPype/pull/2188) + +**🆕 New features** + +- Add validate active site button to sync queue on a project [\#2176](https://github.com/pypeclub/OpenPype/pull/2176) +- Maya : Colorspace configuration [\#2170](https://github.com/pypeclub/OpenPype/pull/2170) +- Blender: Added support for audio [\#2168](https://github.com/pypeclub/OpenPype/pull/2168) + +**🚀 Enhancements** + +- Tools: Subset manager in OpenPype [\#2243](https://github.com/pypeclub/OpenPype/pull/2243) +- General: Skip module directories without init file [\#2239](https://github.com/pypeclub/OpenPype/pull/2239) +- General: Static interfaces [\#2238](https://github.com/pypeclub/OpenPype/pull/2238) +- Style: Fix transparent image in style [\#2235](https://github.com/pypeclub/OpenPype/pull/2235) +- Add a "following workfile versioning" option on publish [\#2225](https://github.com/pypeclub/OpenPype/pull/2225) +- Modules: Module can add cli commands [\#2224](https://github.com/pypeclub/OpenPype/pull/2224) +- Webpublisher: Separate webpublisher logic [\#2222](https://github.com/pypeclub/OpenPype/pull/2222) +- Add both side availability on Site Sync sites to Loader [\#2220](https://github.com/pypeclub/OpenPype/pull/2220) +- Tools: Center loader and library loader on show [\#2219](https://github.com/pypeclub/OpenPype/pull/2219) +- Maya : Validate shape zero [\#2212](https://github.com/pypeclub/OpenPype/pull/2212) +- Maya : validate unique names [\#2211](https://github.com/pypeclub/OpenPype/pull/2211) +- Tools: OpenPype stylesheet in workfiles tool [\#2208](https://github.com/pypeclub/OpenPype/pull/2208) +- Ftrack: Replace Queue with deque in event handlers logic [\#2204](https://github.com/pypeclub/OpenPype/pull/2204) +- Tools: New select context dialog [\#2200](https://github.com/pypeclub/OpenPype/pull/2200) +- Maya : Validate mesh ngons [\#2199](https://github.com/pypeclub/OpenPype/pull/2199) +- Dirmap in Nuke [\#2198](https://github.com/pypeclub/OpenPype/pull/2198) +- Delivery: Check 'frame' key in template for sequence delivery [\#2196](https://github.com/pypeclub/OpenPype/pull/2196) +- Settings: Site sync project settings improvement [\#2193](https://github.com/pypeclub/OpenPype/pull/2193) +- Usage of tools code [\#2185](https://github.com/pypeclub/OpenPype/pull/2185) +- Settings: Dictionary based on project roots [\#2184](https://github.com/pypeclub/OpenPype/pull/2184) +- Subset name: Be able to pass asset document to get subset name [\#2179](https://github.com/pypeclub/OpenPype/pull/2179) + +**🐛 Bug fixes** + +- Ftrack: Sync project ftrack id cache issue [\#2250](https://github.com/pypeclub/OpenPype/pull/2250) +- Ftrack: Session creation and Prepare project [\#2245](https://github.com/pypeclub/OpenPype/pull/2245) +- Added queue for studio processing in PS [\#2237](https://github.com/pypeclub/OpenPype/pull/2237) +- Python 2: Unicode to string conversion [\#2236](https://github.com/pypeclub/OpenPype/pull/2236) +- Fix - enum for color coding in PS [\#2234](https://github.com/pypeclub/OpenPype/pull/2234) +- Pyblish Tool: Fix targets handling [\#2232](https://github.com/pypeclub/OpenPype/pull/2232) +- Ftrack: Base event fix of 'get\_project\_from\_entity' method [\#2214](https://github.com/pypeclub/OpenPype/pull/2214) +- Maya : multiple subsets review broken [\#2210](https://github.com/pypeclub/OpenPype/pull/2210) +- Fix - different command used for Linux and Mac OS [\#2207](https://github.com/pypeclub/OpenPype/pull/2207) - Tools: Workfiles tool don't use avalon widgets [\#2205](https://github.com/pypeclub/OpenPype/pull/2205) - Ftrack: Fill missing ftrack id on mongo project [\#2203](https://github.com/pypeclub/OpenPype/pull/2203) - Project Manager: Fix copying of tasks [\#2191](https://github.com/pypeclub/OpenPype/pull/2191) -- StandalonePublisher: Source validator don't expect representations [\#2190](https://github.com/pypeclub/OpenPype/pull/2190) - Blender: Fix trying to pack an image when the shader node has no texture [\#2183](https://github.com/pypeclub/OpenPype/pull/2183) -- MacOS: Launching of applications may cause Permissions error [\#2175](https://github.com/pypeclub/OpenPype/pull/2175) +- Maya: review viewport settings [\#2177](https://github.com/pypeclub/OpenPype/pull/2177) - Maya: Aspect ratio [\#2174](https://github.com/pypeclub/OpenPype/pull/2174) -- Blender: Fix 'Deselect All' with object not in 'Object Mode' [\#2163](https://github.com/pypeclub/OpenPype/pull/2163) -- Tools: Stylesheets are applied after tool show [\#2161](https://github.com/pypeclub/OpenPype/pull/2161) -- Maya: Collect render - fix UNC path support 🐛 [\#2158](https://github.com/pypeclub/OpenPype/pull/2158) -- Maya: Fix hotbox broken by scriptsmenu [\#2151](https://github.com/pypeclub/OpenPype/pull/2151) -- Ftrack: Ignore save warnings exception in Prepare project action [\#2150](https://github.com/pypeclub/OpenPype/pull/2150) -- Added validator for source files for Standalone Publisher [\#2138](https://github.com/pypeclub/OpenPype/pull/2138) - -**Merged pull requests:** - -- Settings: Site sync project settings improvement [\#2193](https://github.com/pypeclub/OpenPype/pull/2193) -- Add validate active site button to sync queue on a project [\#2176](https://github.com/pypeclub/OpenPype/pull/2176) -- Bump pillow from 8.2.0 to 8.3.2 [\#2162](https://github.com/pypeclub/OpenPype/pull/2162) -- Bump axios from 0.21.1 to 0.21.4 in /website [\#2059](https://github.com/pypeclub/OpenPype/pull/2059) ## [3.5.0](https://github.com/pypeclub/OpenPype/tree/3.5.0) (2021-10-17) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.5.0-nightly.8...3.5.0) -**Deprecated:** - -- Maya: Change mayaAscii family to mayaScene [\#2106](https://github.com/pypeclub/OpenPype/pull/2106) - **🆕 New features** - Added project and task into context change message in Maya [\#2131](https://github.com/pypeclub/OpenPype/pull/2131) - Add ExtractBurnin to photoshop review [\#2124](https://github.com/pypeclub/OpenPype/pull/2124) - PYPE-1218 - changed namespace to contain subset name in Maya [\#2114](https://github.com/pypeclub/OpenPype/pull/2114) -- Added running configurable disk mapping command before start of OP [\#2091](https://github.com/pypeclub/OpenPype/pull/2091) -- SFTP provider [\#2073](https://github.com/pypeclub/OpenPype/pull/2073) -- Houdini: simple HDA workflow [\#2072](https://github.com/pypeclub/OpenPype/pull/2072) -- Maya: Validate setdress top group [\#2068](https://github.com/pypeclub/OpenPype/pull/2068) **🚀 Enhancements** - Maya: make rig validators configurable in settings [\#2137](https://github.com/pypeclub/OpenPype/pull/2137) - Settings: Updated readme for entity types in settings [\#2132](https://github.com/pypeclub/OpenPype/pull/2132) - Nuke: unified clip loader [\#2128](https://github.com/pypeclub/OpenPype/pull/2128) -- Settings UI: Project model refreshing and sorting [\#2104](https://github.com/pypeclub/OpenPype/pull/2104) -- Create Read From Rendered - Disable Relative paths by default [\#2093](https://github.com/pypeclub/OpenPype/pull/2093) -- Added choosing different dirmap mapping if workfile synched locally [\#2088](https://github.com/pypeclub/OpenPype/pull/2088) -- General: Remove IdleManager module [\#2084](https://github.com/pypeclub/OpenPype/pull/2084) -- Tray UI: Message box about missing settings defaults [\#2080](https://github.com/pypeclub/OpenPype/pull/2080) -- Tray UI: Show menu where first click happened [\#2079](https://github.com/pypeclub/OpenPype/pull/2079) -- Global: add global validators to settings [\#2078](https://github.com/pypeclub/OpenPype/pull/2078) -- Use CRF for burnin when available [\#2070](https://github.com/pypeclub/OpenPype/pull/2070) -- Project manager: Filter first item after selection of project [\#2069](https://github.com/pypeclub/OpenPype/pull/2069) -- Nuke: Adding `still` image family workflow [\#2064](https://github.com/pypeclub/OpenPype/pull/2064) -- Maya: validate authorized loaded plugins [\#2062](https://github.com/pypeclub/OpenPype/pull/2062) **🐛 Bug fixes** @@ -88,36 +101,11 @@ - Fix - oiiotool wasn't recognized even if present [\#2129](https://github.com/pypeclub/OpenPype/pull/2129) - General: Disk mapping group [\#2120](https://github.com/pypeclub/OpenPype/pull/2120) - Hiero: publishing effect first time makes wrong resources path [\#2115](https://github.com/pypeclub/OpenPype/pull/2115) -- Add startup script for Houdini Core. [\#2110](https://github.com/pypeclub/OpenPype/pull/2110) -- TVPaint: Behavior name of loop also accept repeat [\#2109](https://github.com/pypeclub/OpenPype/pull/2109) -- Ftrack: Project settings save custom attributes skip unknown attributes [\#2103](https://github.com/pypeclub/OpenPype/pull/2103) -- Blender: Fix NoneType error when animation\_data is missing for a rig [\#2101](https://github.com/pypeclub/OpenPype/pull/2101) -- Fix broken import in sftp provider [\#2100](https://github.com/pypeclub/OpenPype/pull/2100) -- Global: Fix docstring on publish plugin extract review [\#2097](https://github.com/pypeclub/OpenPype/pull/2097) -- Delivery Action Files Sequence fix [\#2096](https://github.com/pypeclub/OpenPype/pull/2096) -- General: Cloud mongo ca certificate issue [\#2095](https://github.com/pypeclub/OpenPype/pull/2095) -- TVPaint: Creator use context from workfile [\#2087](https://github.com/pypeclub/OpenPype/pull/2087) -- Blender: fix texture missing when publishing blend files [\#2085](https://github.com/pypeclub/OpenPype/pull/2085) -- General: Startup validations oiio tool path fix on linux [\#2083](https://github.com/pypeclub/OpenPype/pull/2083) -- Deadline: Collect deadline server does not check existence of deadline key [\#2082](https://github.com/pypeclub/OpenPype/pull/2082) -- Blender: fixed Curves with modifiers in Rigs [\#2081](https://github.com/pypeclub/OpenPype/pull/2081) -- Nuke UI scaling [\#2077](https://github.com/pypeclub/OpenPype/pull/2077) -- Maya: Fix multi-camera renders [\#2065](https://github.com/pypeclub/OpenPype/pull/2065) -- Fix Sync Queue when project disabled [\#2063](https://github.com/pypeclub/OpenPype/pull/2063) - -**Merged pull requests:** - -- Bump pywin32 from 300 to 301 [\#2086](https://github.com/pypeclub/OpenPype/pull/2086) ## [3.4.1](https://github.com/pypeclub/OpenPype/tree/3.4.1) (2021-09-23) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.4.1-nightly.1...3.4.1) -**🐛 Bug fixes** - -- Timers manger: Typo fix [\#2058](https://github.com/pypeclub/OpenPype/pull/2058) -- Hiero: Editorial fixes [\#2057](https://github.com/pypeclub/OpenPype/pull/2057) - ## [3.4.0](https://github.com/pypeclub/OpenPype/tree/3.4.0) (2021-09-17) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.4.0-nightly.6...3.4.0) diff --git a/openpype/cli.py b/openpype/cli.py index 512bd4663b..3194723d4c 100644 --- a/openpype/cli.py +++ b/openpype/cli.py @@ -57,6 +57,17 @@ def tray(debug=False): PypeCommands().launch_tray(debug) +@PypeCommands.add_modules +@main.group(help="Run command line arguments of OpenPype modules") +@click.pass_context +def module(ctx): + """Module specific commands created dynamically. + + These commands are generated dynamically by currently loaded addon/modules. + """ + pass + + @main.command() @click.option("-d", "--debug", is_flag=True, help="Print debug messages") @click.option("--ftrack-url", envvar="FTRACK_SERVER", @@ -147,7 +158,9 @@ def extractenvironments(output_json_path, project, asset, task, app): @click.option("-d", "--debug", is_flag=True, help="Print debug messages") @click.option("-t", "--targets", help="Targets module", default=None, multiple=True) -def publish(debug, paths, targets): +@click.option("-g", "--gui", is_flag=True, + help="Show Publish UI", default=False) +def publish(debug, paths, targets, gui): """Start CLI publishing. Publish collects json from paths provided as an argument. @@ -155,7 +168,7 @@ def publish(debug, paths, targets): """ if debug: os.environ['OPENPYPE_DEBUG'] = '3' - PypeCommands.publish(list(paths), targets) + PypeCommands.publish(list(paths), targets, gui) @main.command() @@ -166,7 +179,7 @@ def publish(debug, paths, targets): @click.option("-p", "--project", help="Project") @click.option("-t", "--targets", help="Targets", default=None, multiple=True) -def remotepublishfromapp(debug, project, path, host, targets=None, user=None): +def remotepublishfromapp(debug, project, path, host, user=None, targets=None): """Start CLI publishing. Publish collects json from paths provided as an argument. @@ -174,18 +187,19 @@ def remotepublishfromapp(debug, project, path, host, targets=None, user=None): """ if debug: os.environ['OPENPYPE_DEBUG'] = '3' - PypeCommands.remotepublishfromapp(project, path, host, user, - targets=targets) + PypeCommands.remotepublishfromapp( + project, path, host, user, targets=targets + ) + @main.command() @click.argument("path") @click.option("-d", "--debug", is_flag=True, help="Print debug messages") -@click.option("-h", "--host", help="Host") @click.option("-u", "--user", help="User email address") @click.option("-p", "--project", help="Project") @click.option("-t", "--targets", help="Targets", default=None, multiple=True) -def remotepublish(debug, project, path, host, targets=None, user=None): +def remotepublish(debug, project, path, user=None, targets=None): """Start CLI publishing. Publish collects json from paths provided as an argument. @@ -193,7 +207,7 @@ def remotepublish(debug, project, path, host, targets=None, user=None): """ if debug: os.environ['OPENPYPE_DEBUG'] = '3' - PypeCommands.remotepublish(project, path, host, user, targets=targets) + PypeCommands.remotepublish(project, path, user, targets=targets) @main.command() @@ -345,3 +359,28 @@ def run(script): def runtests(folder, mark, pyargs): """Run all automatic tests after proper initialization via start.py""" PypeCommands().run_tests(folder, mark, pyargs) + + +@main.command() +@click.option("-d", "--debug", + is_flag=True, help=("Run process in debug mode")) +@click.option("-a", "--active_site", required=True, + help="Name of active stie") +def syncserver(debug, active_site): + """Run sync site server in background. + + Some Site Sync use cases need to expose site to another one. + For example if majority of artists work in studio, they are not using + SS at all, but if you want to expose published assets to 'studio' site + to SFTP for only a couple of artists, some background process must + mark published assets to live on multiple sites (they might be + physically in same location - mounted shared disk). + + Process mimics OP Tray with specific 'active_site' name, all + configuration for this "dummy" user comes from Setting or Local + Settings (configured by starting OP Tray with env + var OPENPYPE_LOCAL_ID set to 'active_site'. + """ + if debug: + os.environ['OPENPYPE_DEBUG'] = '3' + PypeCommands().syncserver(active_site) diff --git a/openpype/hosts/maya/api/__init__.py b/openpype/hosts/maya/api/__init__.py index 0a8370eafc..e330904abf 100644 --- a/openpype/hosts/maya/api/__init__.py +++ b/openpype/hosts/maya/api/__init__.py @@ -13,6 +13,7 @@ from pyblish import api as pyblish from openpype.lib import any_outdated import openpype.hosts.maya from openpype.hosts.maya.lib import copy_workspace_mel +from openpype.lib.path_tools import HostDirmap from . import menu, lib log = logging.getLogger("openpype.hosts.maya") @@ -30,7 +31,8 @@ def install(): project_settings = get_project_settings(os.getenv("AVALON_PROJECT")) # process path mapping - process_dirmap(project_settings) + dirmap_processor = MayaDirmap("maya", project_settings) + dirmap_processor.process_dirmap() pyblish.register_plugin_path(PUBLISH_PATH) avalon.register_plugin_path(avalon.Loader, LOAD_PATH) @@ -60,110 +62,6 @@ def install(): avalon.data["familiesStateToggled"] = ["imagesequence"] -def process_dirmap(project_settings): - # type: (dict) -> None - """Go through all paths in Settings and set them using `dirmap`. - - If artists has Site Sync enabled, take dirmap mapping directly from - Local Settings when artist is syncing workfile locally. - - Args: - project_settings (dict): Settings for current project. - - """ - local_mapping = _get_local_sync_dirmap(project_settings) - if not project_settings["maya"].get("maya-dirmap") and not local_mapping: - return - - mapping = local_mapping or \ - project_settings["maya"]["maya-dirmap"]["paths"] \ - or {} - mapping_enabled = project_settings["maya"]["maya-dirmap"]["enabled"] \ - or bool(local_mapping) - - if not mapping or not mapping_enabled: - return - if mapping.get("source-path") and mapping_enabled is True: - log.info("Processing directory mapping ...") - cmds.dirmap(en=True) - for k, sp in enumerate(mapping["source-path"]): - try: - print("{} -> {}".format(sp, mapping["destination-path"][k])) - cmds.dirmap(m=(sp, mapping["destination-path"][k])) - cmds.dirmap(m=(mapping["destination-path"][k], sp)) - except IndexError: - # missing corresponding destination path - log.error(("invalid dirmap mapping, missing corresponding" - " destination directory.")) - break - except RuntimeError: - log.error("invalid path {} -> {}, mapping not registered".format( - sp, mapping["destination-path"][k] - )) - continue - - -def _get_local_sync_dirmap(project_settings): - """ - Returns dirmap if synch to local project is enabled. - - Only valid mapping is from roots of remote site to local site set in - Local Settings. - - Args: - project_settings (dict) - Returns: - dict : { "source-path": [XXX], "destination-path": [YYYY]} - """ - import json - mapping = {} - - if not project_settings["global"]["sync_server"]["enabled"]: - log.debug("Site Sync not enabled") - return mapping - - from openpype.settings.lib import get_site_local_overrides - from openpype.modules import ModulesManager - - manager = ModulesManager() - sync_module = manager.modules_by_name["sync_server"] - - project_name = os.getenv("AVALON_PROJECT") - sync_settings = sync_module.get_sync_project_setting( - os.getenv("AVALON_PROJECT"), exclude_locals=False, cached=False) - log.debug(json.dumps(sync_settings, indent=4)) - - active_site = sync_module.get_local_normalized_site( - sync_module.get_active_site(project_name)) - remote_site = sync_module.get_local_normalized_site( - sync_module.get_remote_site(project_name)) - log.debug("active {} - remote {}".format(active_site, remote_site)) - - if active_site == "local" \ - and project_name in sync_module.get_enabled_projects()\ - and active_site != remote_site: - overrides = get_site_local_overrides(os.getenv("AVALON_PROJECT"), - active_site) - for root_name, value in overrides.items(): - if os.path.isdir(value): - try: - mapping["destination-path"] = [value] - mapping["source-path"] = [sync_settings["sites"]\ - [remote_site]\ - ["root"]\ - [root_name]] - except IndexError: - # missing corresponding destination path - log.debug("overrides".format(overrides)) - log.error( - ("invalid dirmap mapping, missing corresponding" - " destination directory.")) - break - - log.debug("local sync mapping:: {}".format(mapping)) - return mapping - - def uninstall(): pyblish.deregister_plugin_path(PUBLISH_PATH) avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH) @@ -326,3 +224,12 @@ def before_workfile_save(workfile_path): workdir = os.path.dirname(workfile_path) copy_workspace_mel(workdir) + + +class MayaDirmap(HostDirmap): + def on_enable_dirmap(self): + cmds.dirmap(en=True) + + def dirmap_routine(self, source_path, destination_path): + cmds.dirmap(m=(source_path, destination_path)) + cmds.dirmap(m=(destination_path, source_path)) diff --git a/openpype/hosts/maya/plugins/publish/extract_playblast.py b/openpype/hosts/maya/plugins/publish/extract_playblast.py index 57e3f478f1..b233a57453 100644 --- a/openpype/hosts/maya/plugins/publish/extract_playblast.py +++ b/openpype/hosts/maya/plugins/publish/extract_playblast.py @@ -45,9 +45,12 @@ class ExtractPlayblast(openpype.api.Extractor): # get cameras camera = instance.data['review_camera'] + override_viewport_options = ( + self.capture_preset['Viewport Options'] + ['override_viewport_options'] + ) preset = lib.load_capture_preset(data=self.capture_preset) - preset['camera'] = camera preset['start_frame'] = start preset['end_frame'] = end @@ -92,6 +95,12 @@ class ExtractPlayblast(openpype.api.Extractor): self.log.info('using viewport preset: {}'.format(preset)) + # Update preset with current panel setting + # if override_viewport_options is turned off + if not override_viewport_options: + panel_preset = capture.parse_active_view() + preset.update(panel_preset) + path = capture.capture(**preset) self.log.debug("playblast path {}".format(path)) diff --git a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py index aa8adc3986..c2cefc56f1 100644 --- a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py @@ -32,6 +32,9 @@ class ExtractThumbnail(openpype.api.Extractor): capture_preset = ( instance.context.data["project_settings"]['maya']['publish']['ExtractPlayblast']['capture_preset'] ) + override_viewport_options = ( + capture_preset['Viewport Options']['override_viewport_options'] + ) try: preset = lib.load_capture_preset(data=capture_preset) @@ -86,6 +89,12 @@ class ExtractThumbnail(openpype.api.Extractor): # playblast and viewer preset['viewer'] = False + # Update preset with current panel setting + # if override_viewport_options is turned off + if not override_viewport_options: + panel_preset = capture.parse_active_view() + preset.update(panel_preset) + path = capture.capture(**preset) playblast = self._fix_playblast_output_path(path) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 9ee3a4464b..6d593ca588 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -24,6 +24,10 @@ from openpype.api import ( ApplicationManager ) from openpype.tools.utils import host_tools +from openpype.lib.path_tools import HostDirmap +from openpype.settings import get_project_settings +from openpype.modules import ModulesManager + import nuke from .utils import set_context_favorites @@ -1795,3 +1799,69 @@ def recreate_instance(origin_node, avalon_data=None): dn.setInput(0, new_node) return new_node + + +class NukeDirmap(HostDirmap): + def __init__(self, host_name, project_settings, sync_module, file_name): + """ + Args: + host_name (str): Nuke + project_settings (dict): settings of current project + sync_module (SyncServerModule): to limit reinitialization + file_name (str): full path of referenced file from workfiles + """ + self.host_name = host_name + self.project_settings = project_settings + self.file_name = file_name + self.sync_module = sync_module + + self._mapping = None # cache mapping + + def on_enable_dirmap(self): + pass + + def dirmap_routine(self, source_path, destination_path): + log.debug("{}: {}->{}".format(self.file_name, + source_path, destination_path)) + source_path = source_path.lower().replace(os.sep, '/') + destination_path = destination_path.lower().replace(os.sep, '/') + if platform.system().lower() == "windows": + self.file_name = self.file_name.lower().replace( + source_path, destination_path) + else: + self.file_name = self.file_name.replace( + source_path, destination_path) + + +class DirmapCache: + """Caching class to get settings and sync_module easily and only once.""" + _project_settings = None + _sync_module = None + + @classmethod + def project_settings(cls): + if cls._project_settings is None: + cls._project_settings = get_project_settings( + os.getenv("AVALON_PROJECT")) + return cls._project_settings + + @classmethod + def sync_module(cls): + if cls._sync_module is None: + cls._sync_module = ModulesManager().modules_by_name["sync_server"] + return cls._sync_module + + +def dirmap_file_name_filter(file_name): + """Nuke callback function with single full path argument. + + Checks project settings for potential mapping from source to dest. + """ + dirmap_processor = NukeDirmap("nuke", + DirmapCache.project_settings(), + DirmapCache.sync_module(), + file_name) + dirmap_processor.process_dirmap() + if os.path.exists(dirmap_processor.file_name): + return dirmap_processor.file_name + return file_name diff --git a/openpype/hosts/nuke/startup/menu.py b/openpype/hosts/nuke/startup/menu.py index c452acb709..b7ed35b3b4 100644 --- a/openpype/hosts/nuke/startup/menu.py +++ b/openpype/hosts/nuke/startup/menu.py @@ -6,10 +6,10 @@ from openpype.hosts.nuke.api.lib import ( import nuke from openpype.api import Logger +from openpype.hosts.nuke.api.lib import dirmap_file_name_filter log = Logger().get_logger(__name__) - # fix ffmpeg settings on script nuke.addOnScriptLoad(on_script_load) @@ -20,4 +20,6 @@ nuke.addOnScriptSave(check_inventory_versions) # # set apply all workfile settings on script load and save nuke.addOnScriptLoad(WorkfileSettings().set_context_settings) +nuke.addFilenameFilter(dirmap_file_name_filter) + log.info('Automatic syncing of write file knob to script version') diff --git a/openpype/hosts/photoshop/plugins/publish/closePS.py b/openpype/hosts/photoshop/plugins/publish/closePS.py index 19994a0db8..2f0eab0ee5 100644 --- a/openpype/hosts/photoshop/plugins/publish/closePS.py +++ b/openpype/hosts/photoshop/plugins/publish/closePS.py @@ -17,11 +17,10 @@ class ClosePS(pyblish.api.ContextPlugin): active = True hosts = ["photoshop"] + targets = ["remotepublish"] def process(self, context): self.log.info("ClosePS") - if not os.environ.get("IS_HEADLESS"): - return stub = photoshop.stub() self.log.info("Shutting down PS") diff --git a/openpype/hosts/photoshop/plugins/publish/collect_remote_instances.py b/openpype/hosts/photoshop/plugins/publish/collect_remote_instances.py index 12f9fa5ab5..c76e15484e 100644 --- a/openpype/hosts/photoshop/plugins/publish/collect_remote_instances.py +++ b/openpype/hosts/photoshop/plugins/publish/collect_remote_instances.py @@ -21,6 +21,7 @@ class CollectRemoteInstances(pyblish.api.ContextPlugin): label = "Instances" order = pyblish.api.CollectorOrder hosts = ["photoshop"] + targets = ["remotepublish"] # configurable by Settings color_code_mapping = [] @@ -28,9 +29,6 @@ class CollectRemoteInstances(pyblish.api.ContextPlugin): def process(self, context): self.log.info("CollectRemoteInstances") self.log.info("mapping:: {}".format(self.color_code_mapping)) - if not os.environ.get("IS_HEADLESS"): - self.log.debug("Not headless publishing, skipping.") - return # parse variant if used in webpublishing, comes from webpublisher batch batch_dir = os.environ.get("OPENPYPE_PUBLISH_DATA") diff --git a/openpype/hosts/webpublisher/plugins/publish/collect_batch_data.py b/openpype/hosts/webpublisher/plugins/publish/collect_batch_data.py new file mode 100644 index 0000000000..a710fcb3e8 --- /dev/null +++ b/openpype/hosts/webpublisher/plugins/publish/collect_batch_data.py @@ -0,0 +1,84 @@ +"""Loads batch context from json and continues in publish process. + +Provides: + context -> Loaded batch file. +""" + +import os + +import pyblish.api +from avalon import io +from openpype.lib.plugin_tools import ( + parse_json, + get_batch_asset_task_info +) +from openpype.lib.remote_publish import get_webpublish_conn + + +class CollectBatchData(pyblish.api.ContextPlugin): + """Collect batch data from json stored in 'OPENPYPE_PUBLISH_DATA' env dir. + + The directory must contain 'manifest.json' file where batch data should be + stored. + """ + # must be really early, context values are only in json file + order = pyblish.api.CollectorOrder - 0.495 + label = "Collect batch data" + host = ["webpublisher"] + + def process(self, context): + batch_dir = os.environ.get("OPENPYPE_PUBLISH_DATA") + + assert batch_dir, ( + "Missing `OPENPYPE_PUBLISH_DATA`") + + assert os.path.exists(batch_dir), \ + "Folder {} doesn't exist".format(batch_dir) + + project_name = os.environ.get("AVALON_PROJECT") + if project_name is None: + raise AssertionError( + "Environment `AVALON_PROJECT` was not found." + "Could not set project `root` which may cause issues." + ) + + batch_data = parse_json(os.path.join(batch_dir, "manifest.json")) + + context.data["batchDir"] = batch_dir + context.data["batchData"] = batch_data + + asset_name, task_name, task_type = get_batch_asset_task_info( + batch_data["context"] + ) + + os.environ["AVALON_ASSET"] = asset_name + io.Session["AVALON_ASSET"] = asset_name + os.environ["AVALON_TASK"] = task_name + io.Session["AVALON_TASK"] = task_name + + context.data["asset"] = asset_name + context.data["task"] = task_name + context.data["taskType"] = task_type + + self._set_ctx_path(batch_data) + + def _set_ctx_path(self, batch_data): + dbcon = get_webpublish_conn() + + batch_id = batch_data["batch"] + ctx_path = batch_data["context"]["path"] + self.log.info("ctx_path: {}".format(ctx_path)) + self.log.info("batch_id: {}".format(batch_id)) + if ctx_path and batch_id: + self.log.info("Updating log record") + dbcon.update_one( + { + "batch_id": batch_id, + "status": "in_progress" + }, + { + "$set": { + "path": ctx_path + } + } + ) diff --git a/openpype/hosts/webpublisher/plugins/publish/collect_fps.py b/openpype/hosts/webpublisher/plugins/publish/collect_fps.py index 79fe53176a..b5e665c761 100644 --- a/openpype/hosts/webpublisher/plugins/publish/collect_fps.py +++ b/openpype/hosts/webpublisher/plugins/publish/collect_fps.py @@ -20,9 +20,8 @@ class CollectFPS(pyblish.api.InstancePlugin): hosts = ["webpublisher"] def process(self, instance): - fps = instance.context.data["fps"] + instance_fps = instance.data.get("fps") + if instance_fps is None: + instance.data["fps"] = instance.context.data["fps"] - instance.data.update({ - "fps": fps - }) self.log.debug(f"instance.data: {pformat(instance.data)}") diff --git a/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py b/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py index ecd65ebae4..d2754b3df3 100644 --- a/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py +++ b/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py @@ -1,21 +1,19 @@ -"""Loads publishing context from json and continues in publish process. +"""Create instances from batch data and continues in publish process. Requires: - anatomy -> context["anatomy"] *(pyblish.api.CollectorOrder - 0.11) + CollectBatchData Provides: context, instances -> All data from previous publishing process. """ import os -import json import clique import tempfile - -import pyblish.api from avalon import io +import pyblish.api from openpype.lib import prepare_template_data -from openpype.lib.plugin_tools import parse_json, get_batch_asset_task_info +from openpype.lib.plugin_tools import parse_json class CollectPublishedFiles(pyblish.api.ContextPlugin): @@ -28,28 +26,28 @@ class CollectPublishedFiles(pyblish.api.ContextPlugin): order = pyblish.api.CollectorOrder - 0.490 label = "Collect rendered frames" host = ["webpublisher"] - - _context = None + targets = ["filespublish"] # from Settings task_type_to_family = {} - def _process_batch(self, dir_url): - task_subfolders = [ - os.path.join(dir_url, o) - for o in os.listdir(dir_url) - if os.path.isdir(os.path.join(dir_url, o))] + def process(self, context): + batch_dir = context.data["batchDir"] + task_subfolders = [] + for folder_name in os.listdir(batch_dir): + full_path = os.path.join(batch_dir, folder_name) + if os.path.isdir(full_path): + task_subfolders.append(full_path) + self.log.info("task_sub:: {}".format(task_subfolders)) + + asset_name = context.data["asset"] + task_name = context.data["task"] + task_type = context.data["taskType"] for task_dir in task_subfolders: task_data = parse_json(os.path.join(task_dir, "manifest.json")) self.log.info("task_data:: {}".format(task_data)) - ctx = task_data["context"] - - asset, task_name, task_type = get_batch_asset_task_info(ctx) - - if task_name: - os.environ["AVALON_TASK"] = task_name is_sequence = len(task_data["files"]) > 1 @@ -60,26 +58,20 @@ class CollectPublishedFiles(pyblish.api.ContextPlugin): is_sequence, extension.replace(".", '')) - subset = self._get_subset_name(family, subset_template, task_name, - task_data["variant"]) + subset = self._get_subset_name( + family, subset_template, task_name, task_data["variant"] + ) + version = self._get_last_version(asset_name, subset) + 1 - os.environ["AVALON_ASSET"] = asset - io.Session["AVALON_ASSET"] = asset - - instance = self._context.create_instance(subset) - instance.data["asset"] = asset + instance = context.create_instance(subset) + instance.data["asset"] = asset_name instance.data["subset"] = subset instance.data["family"] = family instance.data["families"] = families - instance.data["version"] = \ - self._get_last_version(asset, subset) + 1 + instance.data["version"] = version instance.data["stagingDir"] = tempfile.mkdtemp() instance.data["source"] = "webpublisher" - # to store logging info into DB openpype.webpublishes - instance.data["ctx_path"] = ctx["path"] - instance.data["batch_id"] = task_data["batch"] - # to convert from email provided into Ftrack username instance.data["user_email"] = task_data["user"] @@ -230,23 +222,3 @@ class CollectPublishedFiles(pyblish.api.ContextPlugin): return version[0].get("version") or 0 else: return 0 - - def process(self, context): - self._context = context - - batch_dir = os.environ.get("OPENPYPE_PUBLISH_DATA") - - assert batch_dir, ( - "Missing `OPENPYPE_PUBLISH_DATA`") - - assert os.path.exists(batch_dir), \ - "Folder {} doesn't exist".format(batch_dir) - - project_name = os.environ.get("AVALON_PROJECT") - if project_name is None: - raise AssertionError( - "Environment `AVALON_PROJECT` was not found." - "Could not set project `root` which may cause issues." - ) - - self._process_batch(batch_dir) diff --git a/openpype/hosts/webpublisher/plugins/publish/integrate_context_to_log.py b/openpype/hosts/webpublisher/plugins/publish/integrate_context_to_log.py deleted file mode 100644 index 419c065e16..0000000000 --- a/openpype/hosts/webpublisher/plugins/publish/integrate_context_to_log.py +++ /dev/null @@ -1,38 +0,0 @@ -import os - -import pyblish.api -from openpype.lib import OpenPypeMongoConnection - - -class IntegrateContextToLog(pyblish.api.ContextPlugin): - """ Adds context information to log document for displaying in front end""" - - label = "Integrate Context to Log" - order = pyblish.api.IntegratorOrder - 0.1 - hosts = ["webpublisher"] - - def process(self, context): - self.log.info("Integrate Context to Log") - - mongo_client = OpenPypeMongoConnection.get_mongo_client() - database_name = os.environ["OPENPYPE_DATABASE_NAME"] - dbcon = mongo_client[database_name]["webpublishes"] - - for instance in context: - self.log.info("ctx_path: {}".format(instance.data.get("ctx_path"))) - self.log.info("batch_id: {}".format(instance.data.get("batch_id"))) - if instance.data.get("ctx_path") and instance.data.get("batch_id"): - self.log.info("Updating log record") - dbcon.update_one( - { - "batch_id": instance.data.get("batch_id"), - "status": "in_progress" - }, - {"$set": - { - "path": instance.data.get("ctx_path") - - }} - ) - - return diff --git a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py index 920ed042dc..d474c96ff9 100644 --- a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py +++ b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py @@ -11,7 +11,8 @@ from avalon.api import AvalonMongoDB from openpype.lib import OpenPypeMongoConnection from openpype_modules.avalon_apps.rest_api import _RestApiEndpoint -from openpype.lib.plugin_tools import parse_json +from openpype.lib.remote_publish import get_task_data +from openpype.settings import get_project_settings from openpype.lib import PypeLogger @@ -20,11 +21,16 @@ log = PypeLogger.get_logger("WebServer") class RestApiResource: """Resource carrying needed info and Avalon DB connection for publish.""" - def __init__(self, server_manager, executable, upload_dir): + def __init__(self, server_manager, executable, upload_dir, + studio_task_queue=None): self.server_manager = server_manager self.upload_dir = upload_dir self.executable = executable + if studio_task_queue is None: + studio_task_queue = collections.deque().dequeu + self.studio_task_queue = studio_task_queue + self.dbcon = AvalonMongoDB() self.dbcon.install() @@ -34,6 +40,8 @@ class RestApiResource: return value.isoformat() if isinstance(value, ObjectId): return str(value) + if isinstance(value, set): + return list(value) raise TypeError(value) @classmethod @@ -176,71 +184,95 @@ class TaskNode(Node): class WebpublisherBatchPublishEndpoint(_RestApiEndpoint): """Triggers headless publishing of batch.""" async def post(self, request) -> Response: - # for postprocessing in host, currently only PS - host_map = {"photoshop": [".psd", ".psb"]} - - output = {} - log.info("WebpublisherBatchPublishEndpoint called") - content = await request.json() - - batch_path = os.path.join(self.resource.upload_dir, - content["batch"]) - - add_args = { - "host": "webpublisher", - "project": content["project_name"], - "user": content["user"] - } - - command = "remotepublish" - - if content.get("studio_processing"): - log.info("Post processing called") - - batch_data = parse_json(os.path.join(batch_path, "manifest.json")) - if not batch_data: - raise ValueError( - "Cannot parse batch meta in {} folder".format(batch_path)) - task_dir_name = batch_data["tasks"][0] - task_data = parse_json(os.path.join(batch_path, task_dir_name, - "manifest.json")) - if not task_data: - raise ValueError( - "Cannot parse batch meta in {} folder".format(task_data)) - - command = "remotepublishfromapp" - for host, extensions in host_map.items(): - for ext in extensions: - for file_name in task_data["files"]: - if ext in file_name: - add_args["host"] = host - break - - if not add_args.get("host"): - raise ValueError( - "Couldn't discern host from {}".format(task_data["files"])) - + # Validate existence of openpype executable openpype_app = self.resource.executable - args = [ - openpype_app, - command, - batch_path - ] - if not openpype_app or not os.path.exists(openpype_app): msg = "Non existent OpenPype executable {}".format(openpype_app) raise RuntimeError(msg) + log.info("WebpublisherBatchPublishEndpoint called") + content = await request.json() + + # Each filter have extensions which are checked on first task item + # - first filter with extensions that are on first task is used + # - filter defines command and can extend arguments dictionary + # This is used only if 'studio_processing' is enabled on batch + studio_processing_filters = [ + # Photoshop filter + { + "extensions": [".psd", ".psb"], + "command": "remotepublishfromapp", + "arguments": { + # Command 'remotepublishfromapp' requires --host argument + "host": "photoshop", + # Make sure targets are set to None for cases that default + # would change + # - targets argument is not used in 'remotepublishfromapp' + "targets": ["remotepublish"] + }, + # does publish need to be handled by a queue, eg. only + # single process running concurrently? + "add_to_queue": True + } + ] + + batch_dir = os.path.join(self.resource.upload_dir, content["batch"]) + + # Default command and arguments + command = "remotepublish" + add_args = { + # All commands need 'project' and 'user' + "project": content["project_name"], + "user": content["user"], + + "targets": ["filespublish"] + } + + add_to_queue = False + if content.get("studio_processing"): + log.info("Post processing called for {}".format(batch_dir)) + + task_data = get_task_data(batch_dir) + + for process_filter in studio_processing_filters: + filter_extensions = process_filter.get("extensions") or [] + for file_name in task_data["files"]: + file_ext = os.path.splitext(file_name)[-1].lower() + if file_ext in filter_extensions: + # Change command + command = process_filter["command"] + # Update arguments + add_args.update( + process_filter.get("arguments") or {} + ) + add_to_queue = process_filter["add_to_queue"] + break + + args = [ + openpype_app, + command, + batch_dir + ] + for key, value in add_args.items(): - args.append("--{}".format(key)) - args.append(value) + # Skip key values where value is None + if value is not None: + args.append("--{}".format(key)) + # Extend list into arguments (targets can be a list) + if isinstance(value, (tuple, list)): + args.extend(value) + else: + args.append(value) log.info("args:: {}".format(args)) + if add_to_queue: + log.debug("Adding to queue") + self.resource.studio_task_queue.append(args) + else: + subprocess.call(args) - subprocess.call(args) return Response( status=200, - body=self.resource.encode(output), content_type="application/json" ) @@ -277,3 +309,36 @@ class PublishesStatusEndpoint(_RestApiEndpoint): body=self.resource.encode(output), content_type="application/json" ) + + +class ConfiguredExtensionsEndpoint(_RestApiEndpoint): + """Returns dict of extensions which have mapping to family. + + Returns: + { + "file_exts": [], + "sequence_exts": [] + } + """ + async def get(self, project_name=None) -> Response: + sett = get_project_settings(project_name) + + configured = { + "file_exts": set(), + "sequence_exts": set(), + # workfiles that could have "Studio Procesing" hardcoded for now + "studio_exts": set(["psd", "psb", "tvpp", "tvp"]) + } + collect_conf = sett["webpublisher"]["publish"]["CollectPublishedFiles"] + for _, mapping in collect_conf.get("task_type_to_family", {}).items(): + for _family, config in mapping.items(): + if config["is_sequence"]: + configured["sequence_exts"].update(config["extensions"]) + else: + configured["file_exts"].update(config["extensions"]) + + return Response( + status=200, + body=self.resource.encode(dict(configured)), + content_type="application/json" + ) diff --git a/openpype/hosts/webpublisher/webserver_service/webserver_cli.py b/openpype/hosts/webpublisher/webserver_service/webserver_cli.py index d00d269059..c96ad8e110 100644 --- a/openpype/hosts/webpublisher/webserver_service/webserver_cli.py +++ b/openpype/hosts/webpublisher/webserver_service/webserver_cli.py @@ -1,8 +1,10 @@ +import collections import time import os from datetime import datetime import requests import json +import subprocess from openpype.lib import PypeLogger @@ -14,7 +16,8 @@ from .webpublish_routes import ( WebpublisherHiearchyEndpoint, WebpublisherProjectsEndpoint, BatchStatusEndpoint, - PublishesStatusEndpoint + PublishesStatusEndpoint, + ConfiguredExtensionsEndpoint ) @@ -31,10 +34,13 @@ def run_webserver(*args, **kwargs): port = kwargs.get("port") or 8079 server_manager = webserver_module.create_new_server_manager(port, host) webserver_url = server_manager.url + # queue for remotepublishfromapp tasks + studio_task_queue = collections.deque() resource = RestApiResource(server_manager, upload_dir=kwargs["upload_dir"], - executable=kwargs["executable"]) + executable=kwargs["executable"], + studio_task_queue=studio_task_queue) projects_endpoint = WebpublisherProjectsEndpoint(resource) server_manager.add_route( "GET", @@ -49,6 +55,13 @@ def run_webserver(*args, **kwargs): hiearchy_endpoint.dispatch ) + configured_ext_endpoint = ConfiguredExtensionsEndpoint(resource) + server_manager.add_route( + "GET", + "/api/webpublish/configured_ext/{project_name}", + configured_ext_endpoint.dispatch + ) + # triggers publish webpublisher_task_publish_endpoint = \ WebpublisherBatchPublishEndpoint(resource) @@ -88,6 +101,10 @@ def run_webserver(*args, **kwargs): if time.time() - last_reprocessed > 20: reprocess_failed(kwargs["upload_dir"], webserver_url) last_reprocessed = time.time() + if studio_task_queue: + args = studio_task_queue.popleft() + subprocess.call(args) # blocking call + time.sleep(1.0) diff --git a/openpype/lib/local_settings.py b/openpype/lib/local_settings.py index 66dad279de..97e99b4b5a 100644 --- a/openpype/lib/local_settings.py +++ b/openpype/lib/local_settings.py @@ -522,6 +522,11 @@ def get_local_site_id(): Identifier is created if does not exists yet. """ + # override local id from environment + # used for background syncing + if os.environ.get("OPENPYPE_LOCAL_ID"): + return os.environ["OPENPYPE_LOCAL_ID"] + registry = OpenPypeSettingsRegistry() try: return registry.get_item("localId") diff --git a/openpype/lib/path_tools.py b/openpype/lib/path_tools.py index 048bf0eda0..6fd0ad0dfe 100644 --- a/openpype/lib/path_tools.py +++ b/openpype/lib/path_tools.py @@ -2,6 +2,8 @@ import json import logging import os import re +import abc +import six from .anatomy import Anatomy @@ -196,3 +198,159 @@ def get_project_basic_paths(project_name): if isinstance(folder_structure, str): folder_structure = json.loads(folder_structure) return _list_path_items(folder_structure) + + +@six.add_metaclass(abc.ABCMeta) +class HostDirmap: + """ + Abstract class for running dirmap on a workfile in a host. + + Dirmap is used to translate paths inside of host workfile from one + OS to another. (Eg. arstist created workfile on Win, different artists + opens same file on Linux.) + + Expects methods to be implemented inside of host: + on_dirmap_enabled: run host code for enabling dirmap + do_dirmap: run host code to do actual remapping + """ + def __init__(self, host_name, project_settings, sync_module=None): + self.host_name = host_name + self.project_settings = project_settings + self.sync_module = sync_module # to limit reinit of Modules + + self._mapping = None # cache mapping + + @abc.abstractmethod + def on_enable_dirmap(self): + """ + Run host dependent operation for enabling dirmap if necessary. + """ + + @abc.abstractmethod + def dirmap_routine(self, source_path, destination_path): + """ + Run host dependent remapping from source_path to destination_path + """ + + def process_dirmap(self): + # type: (dict) -> None + """Go through all paths in Settings and set them using `dirmap`. + + If artists has Site Sync enabled, take dirmap mapping directly from + Local Settings when artist is syncing workfile locally. + + Args: + project_settings (dict): Settings for current project. + + """ + if not self._mapping: + self._mapping = self.get_mappings(self.project_settings) + if not self._mapping: + return + + log.info("Processing directory mapping ...") + self.on_enable_dirmap() + log.info("mapping:: {}".format(self._mapping)) + + for k, sp in enumerate(self._mapping["source-path"]): + try: + print("{} -> {}".format(sp, + self._mapping["destination-path"][k])) + self.dirmap_routine(sp, + self._mapping["destination-path"][k]) + except IndexError: + # missing corresponding destination path + log.error(("invalid dirmap mapping, missing corresponding" + " destination directory.")) + break + except RuntimeError: + log.error("invalid path {} -> {}, mapping not registered".format( # noqa: E501 + sp, self._mapping["destination-path"][k] + )) + continue + + def get_mappings(self, project_settings): + """Get translation from source-path to destination-path. + + It checks if Site Sync is enabled and user chose to use local + site, in that case configuration in Local Settings takes precedence + """ + local_mapping = self._get_local_sync_dirmap(project_settings) + dirmap_label = "{}-dirmap".format(self.host_name) + if not self.project_settings[self.host_name].get(dirmap_label) and \ + not local_mapping: + return [] + mapping = local_mapping or \ + self.project_settings[self.host_name][dirmap_label]["paths"] or {} + enbled = self.project_settings[self.host_name][dirmap_label]["enabled"] + mapping_enabled = enbled or bool(local_mapping) + + if not mapping or not mapping_enabled or \ + not mapping.get("destination-path") or \ + not mapping.get("source-path"): + return [] + return mapping + + def _get_local_sync_dirmap(self, project_settings): + """ + Returns dirmap if synch to local project is enabled. + + Only valid mapping is from roots of remote site to local site set + in Local Settings. + + Args: + project_settings (dict) + Returns: + dict : { "source-path": [XXX], "destination-path": [YYYY]} + """ + import json + mapping = {} + + if not project_settings["global"]["sync_server"]["enabled"]: + log.debug("Site Sync not enabled") + return mapping + + from openpype.settings.lib import get_site_local_overrides + + if not self.sync_module: + from openpype.modules import ModulesManager + manager = ModulesManager() + self.sync_module = manager.modules_by_name["sync_server"] + + project_name = os.getenv("AVALON_PROJECT") + + active_site = self.sync_module.get_local_normalized_site( + self.sync_module.get_active_site(project_name)) + remote_site = self.sync_module.get_local_normalized_site( + self.sync_module.get_remote_site(project_name)) + log.debug("active {} - remote {}".format(active_site, remote_site)) + + if active_site == "local" \ + and project_name in self.sync_module.get_enabled_projects()\ + and active_site != remote_site: + + sync_settings = self.sync_module.get_sync_project_setting( + os.getenv("AVALON_PROJECT"), exclude_locals=False, + cached=False) + + active_overrides = get_site_local_overrides( + os.getenv("AVALON_PROJECT"), active_site) + remote_overrides = get_site_local_overrides( + os.getenv("AVALON_PROJECT"), remote_site) + + log.debug("local overrides".format(active_overrides)) + log.debug("remote overrides".format(remote_overrides)) + for root_name, active_site_dir in active_overrides.items(): + remote_site_dir = remote_overrides.get(root_name) or\ + sync_settings["sites"][remote_site]["root"][root_name] + if os.path.isdir(active_site_dir): + if not mapping.get("destination-path"): + mapping["destination-path"] = [] + mapping["destination-path"].append(active_site_dir) + + if not mapping.get("source-path"): + mapping["source-path"] = [] + mapping["source-path"].append(remote_site_dir) + + log.debug("local sync mapping:: {}".format(mapping)) + return mapping diff --git a/openpype/lib/python_module_tools.py b/openpype/lib/python_module_tools.py index cb5f285ddd..69da4cc661 100644 --- a/openpype/lib/python_module_tools.py +++ b/openpype/lib/python_module_tools.py @@ -22,6 +22,9 @@ def import_filepath(filepath, module_name=None): if module_name is None: module_name = os.path.splitext(os.path.basename(filepath))[0] + # Make sure it is not 'unicode' in Python 2 + module_name = str(module_name) + # Prepare module object where content of file will be parsed module = types.ModuleType(module_name) diff --git a/openpype/lib/remote_publish.py b/openpype/lib/remote_publish.py index 51007cfad2..d7db4d1ab9 100644 --- a/openpype/lib/remote_publish.py +++ b/openpype/lib/remote_publish.py @@ -8,6 +8,7 @@ import pyblish.api from openpype import uninstall from openpype.lib.mongo import OpenPypeMongoConnection +from openpype.lib.plugin_tools import parse_json def get_webpublish_conn(): @@ -31,7 +32,8 @@ def start_webpublish_log(dbcon, batch_id, user): "batch_id": batch_id, "start_date": datetime.now(), "user": user, - "status": "in_progress" + "status": "in_progress", + "progress": 0.0 }).inserted_id @@ -157,3 +159,28 @@ def _get_close_plugin(close_plugin_name, log): return plugin log.warning("Close plugin not found, app might not close.") + + +def get_task_data(batch_dir): + """Return parsed data from first task manifest.json + + Used for `remotepublishfromapp` command where batch contains only + single task with publishable workfile. + + Returns: + (dict) + Throws: + (ValueError) if batch or task manifest not found or broken + """ + batch_data = parse_json(os.path.join(batch_dir, "manifest.json")) + if not batch_data: + raise ValueError( + "Cannot parse batch meta in {} folder".format(batch_dir)) + task_dir_name = batch_data["tasks"][0] + task_data = parse_json(os.path.join(batch_dir, task_dir_name, + "manifest.json")) + if not task_data: + raise ValueError( + "Cannot parse batch meta in {} folder".format(task_data)) + + return task_data diff --git a/openpype/modules/README.md b/openpype/modules/README.md index 5716324365..86afdb9d91 100644 --- a/openpype/modules/README.md +++ b/openpype/modules/README.md @@ -22,6 +22,10 @@ OpenPype modules should contain separated logic of specific kind of implementati - `__init__` should not be overridden and `initialize` should not do time consuming part but only prepare base data about module - also keep in mind that they may be initialized in headless mode - connection with other modules is made with help of interfaces +- `cli` method - add cli commands specific for the module + - command line arguments are handled using `click` python module + - `cli` method should expect single argument which is click group on which can be called any group specific methods (e.g. `add_command` to add another click group as children see `ExampleAddon`) + - it is possible to add trigger cli commands using `./openpype_console module *args` ## Addon class `OpenPypeAddOn` - inherits from `OpenPypeModule` but is enabled by default and doesn't have to implement `initialize` and `connect_with_modules` methods @@ -140,4 +144,4 @@ class ClockifyModule( ### TrayModulesManager - inherits from `ModulesManager` -- has specific implementation for Pype Tray tool and handle `ITrayModule` methods \ No newline at end of file +- has specific implementation for Pype Tray tool and handle `ITrayModule` methods diff --git a/openpype/modules/base.py b/openpype/modules/base.py index 7779fff6ec..6f9ddb2fd4 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -107,12 +107,9 @@ class _InterfacesClass(_ModuleClass): if attr_name in ("__path__", "__file__"): return None - # Fake Interface if is not missing - self.__attributes__[attr_name] = type( - attr_name, - (MissingInteface, ), - {} - ) + raise ImportError(( + "cannot import name '{}' from 'openpype_interfaces'" + ).format(attr_name)) return self.__attributes__[attr_name] @@ -212,54 +209,17 @@ def _load_interfaces(): _InterfacesClass(modules_key) ) - log = PypeLogger.get_logger("InterfacesLoader") + from . import interfaces - dirpaths = get_module_dirs() - - interface_paths = [] - interface_paths.append( - os.path.join(get_default_modules_dir(), "interfaces.py") - ) - for dirpath in dirpaths: - if not os.path.exists(dirpath): + for attr_name in dir(interfaces): + attr = getattr(interfaces, attr_name) + if ( + not inspect.isclass(attr) + or attr is OpenPypeInterface + or not issubclass(attr, OpenPypeInterface) + ): continue - - for filename in os.listdir(dirpath): - if filename in ("__pycache__", ): - continue - - full_path = os.path.join(dirpath, filename) - if not os.path.isdir(full_path): - continue - - interfaces_path = os.path.join(full_path, "interfaces.py") - if os.path.exists(interfaces_path): - interface_paths.append(interfaces_path) - - for full_path in interface_paths: - if not os.path.exists(full_path): - continue - - try: - # Prepare module object where content of file will be parsed - module = import_filepath(full_path) - - except Exception: - log.warning( - "Failed to load path: \"{0}\"".format(full_path), - exc_info=True - ) - continue - - for attr_name in dir(module): - attr = getattr(module, attr_name) - if ( - not inspect.isclass(attr) - or attr is OpenPypeInterface - or not issubclass(attr, OpenPypeInterface) - ): - continue - setattr(openpype_interfaces, attr_name, attr) + setattr(openpype_interfaces, attr_name, attr) def load_modules(force=False): @@ -333,6 +293,15 @@ def _load_modules(): # - check manifest and content of manifest try: if os.path.isdir(fullpath): + # Module without init file can't be used as OpenPype module + # because the module class could not be imported + init_file = os.path.join(fullpath, "__init__.py") + if not os.path.exists(init_file): + log.info(( + "Skipping module directory because of" + " missing \"__init__.py\" file. \"{}\"" + ).format(fullpath)) + continue import_module_from_dirpath(dirpath, filename, modules_key) elif ext in (".py", ): @@ -369,14 +338,6 @@ class OpenPypeInterface: pass -class MissingInteface(OpenPypeInterface): - """Class representing missing interface class. - - Used when interface is not available from currently registered paths. - """ - pass - - @six.add_metaclass(ABCMeta) class OpenPypeModule: """Base class of pype module. @@ -431,6 +392,28 @@ class OpenPypeModule: """ return {} + def cli(self, module_click_group): + """Add commands to click group. + + The best practise is to create click group for whole module which is + used to separate commands. + + class MyPlugin(OpenPypeModule): + ... + def cli(self, module_click_group): + module_click_group.add_command(cli_main) + + + @click.group(, help="") + def cli_main(): + pass + + @cli_main.command() + def mycommand(): + print("my_command") + """ + pass + class OpenPypeAddOn(OpenPypeModule): # Enable Addon by default diff --git a/openpype/modules/default_modules/ftrack/event_handlers_server/event_sync_to_avalon.py b/openpype/modules/default_modules/ftrack/event_handlers_server/event_sync_to_avalon.py index 178dfc74c7..a4982627ff 100644 --- a/openpype/modules/default_modules/ftrack/event_handlers_server/event_sync_to_avalon.py +++ b/openpype/modules/default_modules/ftrack/event_handlers_server/event_sync_to_avalon.py @@ -194,6 +194,7 @@ class SyncToAvalonEvent(BaseEvent): ftrack_id = proj["data"].get("ftrackId") if ftrack_id is None: ftrack_id = self._update_project_ftrack_id() + proj["data"]["ftrackId"] = ftrack_id self._avalon_ents_by_ftrack_id[ftrack_id] = proj for ent in ents: ftrack_id = ent["data"].get("ftrackId") @@ -584,6 +585,10 @@ class SyncToAvalonEvent(BaseEvent): continue ftrack_id = ftrack_id[0] + # Skip deleted projects + if action == "remove" and entityType == "show": + return True + # task modified, collect parent id of task, handle separately if entity_type.lower() == "task": changes = ent_info.get("changes") or {} diff --git a/openpype/modules/default_modules/ftrack/ftrack_module.py b/openpype/modules/default_modules/ftrack/ftrack_module.py index 73a4dfee82..8a7525d65b 100644 --- a/openpype/modules/default_modules/ftrack/ftrack_module.py +++ b/openpype/modules/default_modules/ftrack/ftrack_module.py @@ -1,8 +1,10 @@ import os import json import collections -from openpype.modules import OpenPypeModule +import click + +from openpype.modules import OpenPypeModule from openpype_interfaces import ( ITrayModule, IPluginPaths, @@ -224,8 +226,8 @@ class FtrackModule( if not project_name: return - attributes_changes = changes.get("attributes") - if not attributes_changes: + new_attr_values = new_value.get("attributes") + if not new_attr_values: return import ftrack_api @@ -275,7 +277,7 @@ class FtrackModule( failed = {} missing = {} - for key, value in attributes_changes.items(): + for key, value in new_attr_values.items(): if key not in ca_keys: continue @@ -349,12 +351,24 @@ class FtrackModule( if "server_url" not in session_kwargs: session_kwargs["server_url"] = self.ftrack_url - if "api_key" not in session_kwargs or "api_user" not in session_kwargs: + api_key = session_kwargs.get("api_key") + api_user = session_kwargs.get("api_user") + # First look into environments + # - both OpenPype tray and ftrack event server should have set them + # - ftrack event server may crash when credentials are tried to load + # from keyring + if not api_key or not api_user: + api_key = os.environ.get("FTRACK_API_KEY") + api_user = os.environ.get("FTRACK_API_USER") + + if not api_key or not api_user: from .lib import credentials cred = credentials.get_credentials() - session_kwargs["api_user"] = cred.get("username") - session_kwargs["api_key"] = cred.get("api_key") + api_user = cred.get("username") + api_key = cred.get("api_key") + session_kwargs["api_user"] = api_user + session_kwargs["api_key"] = api_key return ftrack_api.Session(**session_kwargs) def tray_init(self): @@ -409,3 +423,62 @@ class FtrackModule( return 0 hours_logged = (task_entity["time_logged"] / 60) / 60 return hours_logged + + def get_credentials(self): + # type: () -> tuple + """Get local Ftrack credentials.""" + from .lib import credentials + + cred = credentials.get_credentials(self.ftrack_url) + return cred.get("username"), cred.get("api_key") + + def cli(self, click_group): + click_group.add_command(cli_main) + + +@click.group(FtrackModule.name, help="Ftrack module related commands.") +def cli_main(): + pass + + +@cli_main.command() +@click.option("-d", "--debug", is_flag=True, help="Print debug messages") +@click.option("--ftrack-url", envvar="FTRACK_SERVER", + help="Ftrack server url") +@click.option("--ftrack-user", envvar="FTRACK_API_USER", + help="Ftrack api user") +@click.option("--ftrack-api-key", envvar="FTRACK_API_KEY", + help="Ftrack api key") +@click.option("--legacy", is_flag=True, + help="run event server without mongo storing") +@click.option("--clockify-api-key", envvar="CLOCKIFY_API_KEY", + help="Clockify API key.") +@click.option("--clockify-workspace", envvar="CLOCKIFY_WORKSPACE", + help="Clockify workspace") +def eventserver( + debug, + ftrack_url, + ftrack_user, + ftrack_api_key, + legacy, + clockify_api_key, + clockify_workspace +): + """Launch ftrack event server. + + This should be ideally used by system service (such us systemd or upstart + on linux and window service). + """ + if debug: + os.environ["OPENPYPE_DEBUG"] = "3" + + from .ftrack_server.event_server_cli import run_event_server + + return run_event_server( + ftrack_url, + ftrack_user, + ftrack_api_key, + legacy, + clockify_api_key, + clockify_workspace + ) diff --git a/openpype/modules/default_modules/ftrack/lib/ftrack_base_handler.py b/openpype/modules/default_modules/ftrack/lib/ftrack_base_handler.py index a457b886ac..2130abc20c 100644 --- a/openpype/modules/default_modules/ftrack/lib/ftrack_base_handler.py +++ b/openpype/modules/default_modules/ftrack/lib/ftrack_base_handler.py @@ -570,9 +570,15 @@ class BaseHandler(object): if low_entity_type == "assetversion": asset = entity["asset"] + parent = None if asset: parent = asset["parent"] - if parent: + + if parent: + if parent.entity_type.lower() == "project": + return parent + + if "project" in parent: return parent["project"] project_data = entity["link"][0] diff --git a/openpype/modules/default_modules/ftrack/plugins/publish/collect_local_ftrack_creds.py b/openpype/modules/default_modules/ftrack/plugins/publish/collect_local_ftrack_creds.py new file mode 100644 index 0000000000..2093ebf18a --- /dev/null +++ b/openpype/modules/default_modules/ftrack/plugins/publish/collect_local_ftrack_creds.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +"""Collect default Deadline server.""" +import pyblish.api +import os + + +class CollectLocalFtrackCreds(pyblish.api.ContextPlugin): + """Collect default Royal Render path.""" + + order = pyblish.api.CollectorOrder + 0.01 + label = "Collect local ftrack credentials" + targets = ["rr_control"] + + def process(self, context): + if os.getenv("FTRACK_API_USER") and os.getenv("FTRACK_API_KEY") and \ + os.getenv("FTRACK_SERVER"): + return + ftrack_module = context.data["openPypeModules"]["ftrack"] + if ftrack_module.enabled: + creds = ftrack_module.get_credentials() + os.environ["FTRACK_API_USER"] = creds[0] + os.environ["FTRACK_API_KEY"] = creds[1] + os.environ["FTRACK_SERVER"] = ftrack_module.ftrack_url diff --git a/openpype/modules/default_modules/ftrack/plugins/publish/collect_username.py b/openpype/modules/default_modules/ftrack/plugins/publish/collect_username.py index 844a397066..a5187dd52b 100644 --- a/openpype/modules/default_modules/ftrack/plugins/publish/collect_username.py +++ b/openpype/modules/default_modules/ftrack/plugins/publish/collect_username.py @@ -27,16 +27,12 @@ class CollectUsername(pyblish.api.ContextPlugin): order = pyblish.api.CollectorOrder - 0.488 label = "Collect ftrack username" hosts = ["webpublisher", "photoshop"] + targets = ["remotepublish", "filespublish"] _context = None def process(self, context): self.log.info("CollectUsername") - # photoshop could be triggered remotely in webpublisher fashion - if os.environ["AVALON_APP"] == "photoshop": - if not os.environ.get("IS_HEADLESS"): - self.log.debug("Regular process, skipping") - return os.environ["FTRACK_API_USER"] = os.environ["FTRACK_BOT_API_USER"] os.environ["FTRACK_API_KEY"] = os.environ["FTRACK_BOT_API_KEY"] diff --git a/openpype/modules/default_modules/royal_render/__init__.py b/openpype/modules/default_modules/royal_render/__init__.py new file mode 100644 index 0000000000..cc92e3b50d --- /dev/null +++ b/openpype/modules/default_modules/royal_render/__init__.py @@ -0,0 +1,6 @@ +from .royal_render_module import RoyalRenderModule + + +__all__ = ( + "RoyalRenderModule", +) diff --git a/openpype/modules/default_modules/royal_render/api.py b/openpype/modules/default_modules/royal_render/api.py new file mode 100644 index 0000000000..ed9e71f240 --- /dev/null +++ b/openpype/modules/default_modules/royal_render/api.py @@ -0,0 +1,199 @@ +# -*- coding: utf-8 -*- +"""Wrapper around Royal Render API.""" +import sys +import os + +from openpype.settings import get_project_settings +from openpype.lib.local_settings import OpenPypeSettingsRegistry +from openpype.lib import PypeLogger, run_subprocess +from .rr_job import RRJob, SubmitFile, SubmitterParameter + + +log = PypeLogger.get_logger("RoyalRender") + + +class Api: + + _settings = None + RR_SUBMIT_CONSOLE = 1 + RR_SUBMIT_API = 2 + + def __init__(self, settings, project=None): + self._settings = settings + self._initialize_rr(project) + + def _initialize_rr(self, project=None): + # type: (str) -> None + """Initialize RR Path. + + Args: + project (str, Optional): Project name to set RR api in + context. + + """ + if project: + project_settings = get_project_settings(project) + rr_path = ( + project_settings + ["royalrender"] + ["rr_paths"] + ) + else: + rr_path = ( + self._settings + ["modules"] + ["royalrender"] + ["rr_path"] + ["default"] + ) + os.environ["RR_ROOT"] = rr_path + self._rr_path = rr_path + + def _get_rr_bin_path(self, rr_root=None): + # type: (str) -> str + """Get path to RR bin folder.""" + rr_root = rr_root or self._rr_path + is_64bit_python = sys.maxsize > 2 ** 32 + + rr_bin_path = "" + if sys.platform.lower() == "win32": + rr_bin_path = "/bin/win64" + if not is_64bit_python: + # we are using 64bit python + rr_bin_path = "/bin/win" + rr_bin_path = rr_bin_path.replace( + "/", os.path.sep + ) + + if sys.platform.lower() == "darwin": + rr_bin_path = "/bin/mac64" + if not is_64bit_python: + rr_bin_path = "/bin/mac" + + if sys.platform.lower() == "linux": + rr_bin_path = "/bin/lx64" + + return os.path.join(rr_root, rr_bin_path) + + def _initialize_module_path(self): + # type: () -> None + """Set RR modules for Python.""" + # default for linux + rr_bin = self._get_rr_bin_path() + rr_module_path = os.path.join(rr_bin, "lx64/lib") + + if sys.platform.lower() == "win32": + rr_module_path = rr_bin + rr_module_path = rr_module_path.replace( + "/", os.path.sep + ) + + if sys.platform.lower() == "darwin": + rr_module_path = os.path.join(rr_bin, "lib/python/27") + + sys.path.append(os.path.join(self._rr_path, rr_module_path)) + + def create_submission(self, jobs, submitter_attributes, file_name=None): + # type: (list[RRJob], list[SubmitterParameter], str) -> SubmitFile + """Create jobs submission file. + + Args: + jobs (list): List of :class:`RRJob` + submitter_attributes (list): List of submitter attributes + :class:`SubmitterParameter` for whole submission batch. + file_name (str), optional): File path to write data to. + + Returns: + str: XML data of job submission files. + + """ + raise NotImplementedError + + def submit_file(self, file, mode=RR_SUBMIT_CONSOLE): + # type: (SubmitFile, int) -> None + if mode == self.RR_SUBMIT_CONSOLE: + self._submit_using_console(file) + + # RR v7 supports only Python 2.7 so we bail out in fear + # until there is support for Python 3 😰 + raise NotImplementedError( + "Submission via RoyalRender API is not supported yet") + # self._submit_using_api(file) + + def _submit_using_console(self, file): + # type: (SubmitFile) -> bool + rr_console = os.path.join( + self._get_rr_bin_path(), + "rrSubmitterconsole" + ) + + if sys.platform.lower() == "darwin": + if "/bin/mac64" in rr_console: + rr_console = rr_console.replace("/bin/mac64", "/bin/mac") + + if sys.platform.lower() == "win32": + if "/bin/win64" in rr_console: + rr_console = rr_console.replace("/bin/win64", "/bin/win") + rr_console += ".exe" + + args = [rr_console, file] + run_subprocess(" ".join(args), logger=log) + + def _submit_using_api(self, file): + # type: (SubmitFile) -> None + """Use RR API to submit jobs. + + Args: + file (SubmitFile): Submit jobs definition. + + Throws: + RoyalRenderException: When something fails. + + """ + self._initialize_module_path() + import libpyRR2 as rrLib # noqa + from rrJob import getClass_JobBasics # noqa + import libpyRR2 as _RenderAppBasic # noqa + + tcp = rrLib._rrTCP("") # noqa + rr_server = tcp.getRRServer() + + if len(rr_server) == 0: + log.info("Got RR IP address {}".format(rr_server)) + + # TODO: Port is hardcoded in RR? If not, move it to Settings + if not tcp.setServer(rr_server, 7773): + log.error( + "Can not set RR server: {}".format(tcp.errorMessage())) + raise RoyalRenderException(tcp.errorMessage()) + + # TODO: This need UI and better handling of username/password. + # We can't store password in keychain as it is pulled multiple + # times and users on linux must enter keychain password every time. + # Probably best way until we setup our own user management would be + # to encrypt password and save it to json locally. Not bulletproof + # but at least it is not stored in plaintext. + reg = OpenPypeSettingsRegistry() + try: + rr_user = reg.get_item("rr_username") + rr_password = reg.get_item("rr_password") + except ValueError: + # user has no rr credentials set + pass + else: + # login to RR + tcp.setLogin(rr_user, rr_password) + + job = getClass_JobBasics() + renderer = _RenderAppBasic() + + # iterate over SubmitFile, set _JobBasic (job) and renderer + # and feed it to jobSubmitNew() + # not implemented yet + job.renderer = renderer + tcp.jobSubmitNew(job) + + +class RoyalRenderException(Exception): + """Exception used in various error states coming from RR.""" + pass diff --git a/openpype/modules/default_modules/royal_render/plugins/publish/collect_default_rr_path.py b/openpype/modules/default_modules/royal_render/plugins/publish/collect_default_rr_path.py new file mode 100644 index 0000000000..cdca03bef0 --- /dev/null +++ b/openpype/modules/default_modules/royal_render/plugins/publish/collect_default_rr_path.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +"""Collect default Deadline server.""" +import pyblish.api + + +class CollectDefaultRRPath(pyblish.api.ContextPlugin): + """Collect default Royal Render path.""" + + order = pyblish.api.CollectorOrder + 0.01 + label = "Default Royal Render Path" + + def process(self, context): + try: + rr_module = context.data.get( + "openPypeModules")["royalrender"] + except AttributeError: + msg = "Cannot get OpenPype Royal Render module." + self.log.error(msg) + raise AssertionError(msg) + + # get default deadline webservice url from deadline module + self.log.debug(rr_module.rr_paths) + context.data["defaultRRPath"] = rr_module.rr_paths["default"] # noqa: E501 diff --git a/openpype/modules/default_modules/royal_render/plugins/publish/collect_rr_path_from_instance.py b/openpype/modules/default_modules/royal_render/plugins/publish/collect_rr_path_from_instance.py new file mode 100644 index 0000000000..fb27a76d11 --- /dev/null +++ b/openpype/modules/default_modules/royal_render/plugins/publish/collect_rr_path_from_instance.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +import pyblish.api + + +class CollectRRPathFromInstance(pyblish.api.InstancePlugin): + """Collect RR Path from instance.""" + + order = pyblish.api.CollectorOrder + label = "Royal Render Path from the Instance" + families = ["rendering"] + + def process(self, instance): + instance.data["rrPath"] = self._collect_rr_path(instance) + self.log.info( + "Using {} for submission.".format(instance.data["rrPath"])) + + @staticmethod + def _collect_rr_path(render_instance): + # type: (pyblish.api.Instance) -> str + """Get Royal Render path from render instance.""" + rr_settings = ( + render_instance.context.data + ["system_settings"] + ["modules"] + ["royalrender"] + ) + try: + default_servers = rr_settings["rr_paths"] + project_servers = ( + render_instance.context.data + ["project_settings"] + ["royalrender"] + ["rr_paths"] + ) + rr_servers = { + k: default_servers[k] + for k in project_servers + if k in default_servers + } + + except AttributeError: + # Handle situation were we had only one url for deadline. + return render_instance.context.data["defaultRRPath"] + + return rr_servers[ + list(rr_servers.keys())[ + int(render_instance.data.get("rrPaths")) + ] + ] diff --git a/openpype/modules/default_modules/royal_render/plugins/publish/collect_sequences_from_job.py b/openpype/modules/default_modules/royal_render/plugins/publish/collect_sequences_from_job.py new file mode 100644 index 0000000000..d2754d1f92 --- /dev/null +++ b/openpype/modules/default_modules/royal_render/plugins/publish/collect_sequences_from_job.py @@ -0,0 +1,209 @@ +# -*- coding: utf-8 -*- +"""Collect sequences from Royal Render Job.""" +import os +import re +import copy +import json +from pprint import pformat + +import pyblish.api +from avalon import api + + +def collect(root, + regex=None, + exclude_regex=None, + frame_start=None, + frame_end=None): + """Collect sequence collections in root""" + + from avalon.vendor import clique + + files = [] + for filename in os.listdir(root): + + # Must have extension + ext = os.path.splitext(filename)[1] + if not ext: + continue + + # Only files + if not os.path.isfile(os.path.join(root, filename)): + continue + + # Include and exclude regex + if regex and not re.search(regex, filename): + continue + if exclude_regex and re.search(exclude_regex, filename): + continue + + files.append(filename) + + # Match collections + # Support filenames like: projectX_shot01_0010.tiff with this regex + pattern = r"(?P(?P0*)\d+)\.\D+\d?$" + collections, remainder = clique.assemble(files, + patterns=[pattern], + minimum_items=1) + + # Ignore any remainders + if remainder: + print("Skipping remainder {}".format(remainder)) + + # Exclude any frames outside start and end frame. + for collection in collections: + for index in list(collection.indexes): + if frame_start is not None and index < frame_start: + collection.indexes.discard(index) + continue + if frame_end is not None and index > frame_end: + collection.indexes.discard(index) + continue + + # Keep only collections that have at least a single frame + collections = [c for c in collections if c.indexes] + + return collections + + +class CollectSequencesFromJob(pyblish.api.ContextPlugin): + """Gather file sequences from job directory. + + When "OPENPYPE_PUBLISH_DATA" environment variable is set these paths + (folders or .json files) are parsed for image sequences. Otherwise the + current working directory is searched for file sequences. + + """ + order = pyblish.api.CollectorOrder + targets = ["rr_control"] + label = "Collect Rendered Frames" + + def process(self, context): + if os.environ.get("OPENPYPE_PUBLISH_DATA"): + self.log.debug(os.environ.get("OPENPYPE_PUBLISH_DATA")) + paths = os.environ["OPENPYPE_PUBLISH_DATA"].split(os.pathsep) + self.log.info("Collecting paths: {}".format(paths)) + else: + cwd = context.get("workspaceDir", os.getcwd()) + paths = [cwd] + + for path in paths: + + self.log.info("Loading: {}".format(path)) + + if path.endswith(".json"): + # Search using .json configuration + with open(path, "r") as f: + try: + data = json.load(f) + except Exception as exc: + self.log.error("Error loading json: " + "{} - Exception: {}".format(path, exc)) + raise + + cwd = os.path.dirname(path) + root_override = data.get("root") + if root_override: + if os.path.isabs(root_override): + root = root_override + else: + root = os.path.join(cwd, root_override) + else: + root = cwd + + metadata = data.get("metadata") + if metadata: + session = metadata.get("session") + if session: + self.log.info("setting session using metadata") + api.Session.update(session) + os.environ.update(session) + + else: + # Search in directory + data = {} + root = path + + self.log.info("Collecting: {}".format(root)) + regex = data.get("regex") + if regex: + self.log.info("Using regex: {}".format(regex)) + + collections = collect(root=root, + regex=regex, + exclude_regex=data.get("exclude_regex"), + frame_start=data.get("frameStart"), + frame_end=data.get("frameEnd")) + + self.log.info("Found collections: {}".format(collections)) + + if data.get("subset") and len(collections) > 1: + self.log.error("Forced subset can only work with a single " + "found sequence") + raise RuntimeError("Invalid sequence") + + fps = data.get("fps", 25) + + # Get family from the data + families = data.get("families", ["render"]) + if "render" not in families: + families.append("render") + if "ftrack" not in families: + families.append("ftrack") + if "review" not in families: + families.append("review") + + for collection in collections: + instance = context.create_instance(str(collection)) + self.log.info("Collection: %s" % list(collection)) + + # Ensure each instance gets a unique reference to the data + data = copy.deepcopy(data) + + # If no subset provided, get it from collection's head + subset = data.get("subset", collection.head.rstrip("_. ")) + + # If no start or end frame provided, get it from collection + indices = list(collection.indexes) + start = data.get("frameStart", indices[0]) + end = data.get("frameEnd", indices[-1]) + + # root = os.path.normpath(root) + # self.log.info("Source: {}}".format(data.get("source", ""))) + + ext = list(collection)[0].split('.')[-1] + + instance.data.update({ + "name": str(collection), + "family": families[0], # backwards compatibility / pyblish + "families": list(families), + "subset": subset, + "asset": data.get("asset", api.Session["AVALON_ASSET"]), + "stagingDir": root, + "frameStart": start, + "frameEnd": end, + "fps": fps, + "source": data.get('source', '') + }) + instance.append(collection) + instance.context.data['fps'] = fps + + if "representations" not in instance.data: + instance.data["representations"] = [] + + representation = { + 'name': ext, + 'ext': '{}'.format(ext), + 'files': list(collection), + "stagingDir": root, + "anatomy_template": "render", + "fps": fps, + "tags": ['review'] + } + instance.data["representations"].append(representation) + + if data.get('user'): + context.data["user"] = data['user'] + + self.log.debug("Collected instance:\n" + "{}".format(pformat(instance.data))) diff --git a/openpype/modules/default_modules/royal_render/royal_render_module.py b/openpype/modules/default_modules/royal_render/royal_render_module.py new file mode 100644 index 0000000000..4f72860ad6 --- /dev/null +++ b/openpype/modules/default_modules/royal_render/royal_render_module.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +"""Module providing support for Royal Render.""" +import os +import openpype.modules +from openpype.modules import OpenPypeModule +from openpype_interfaces import IPluginPaths + + +class RoyalRenderModule(OpenPypeModule, IPluginPaths): + """Class providing basic Royal Render implementation logic.""" + name = "royalrender" + + @property + def api(self): + if not self._api: + # import royal render modules + from . import api as rr_api + self._api = rr_api.Api(self.settings) + + return self._api + + def __init__(self, manager, settings): + # type: (openpype.modules.base.ModulesManager, dict) -> None + self.rr_paths = {} + self._api = None + self.settings = settings + super(RoyalRenderModule, self).__init__(manager, settings) + + def initialize(self, module_settings): + # type: (dict) -> None + rr_settings = module_settings[self.name] + self.enabled = rr_settings["enabled"] + self.rr_paths = rr_settings.get("rr_paths") + + @staticmethod + def get_plugin_paths(): + # type: () -> dict + """Royal Render plugin paths. + + Returns: + dict: Dictionary of plugin paths for RR. + """ + current_dir = os.path.dirname(os.path.abspath(__file__)) + return { + "publish": [os.path.join(current_dir, "plugins", "publish")] + } diff --git a/openpype/modules/default_modules/royal_render/rr_job.py b/openpype/modules/default_modules/royal_render/rr_job.py new file mode 100644 index 0000000000..c660eceac7 --- /dev/null +++ b/openpype/modules/default_modules/royal_render/rr_job.py @@ -0,0 +1,256 @@ +# -*- coding: utf-8 -*- +"""Python wrapper for RoyalRender XML job file.""" +from xml.dom import minidom as md +import attr +from collections import namedtuple, OrderedDict + + +CustomAttribute = namedtuple("CustomAttribute", ["name", "value"]) + + +@attr.s +class RRJob: + """Mapping of Royal Render job file to a data class.""" + + # Required + # -------- + + # Name of your render application. Same as in the render config file. + # (Maya, Softimage) + Software = attr.ib() # type: str + + # The OS the scene was created on, all texture paths are set on + # that OS. Possible values are windows, linux, osx + SceneOS = attr.ib() # type: str + + # Renderer you use. Same as in the render config file + # (VRay, Mental Ray, Arnold) + Renderer = attr.ib() # type: str + + # Version you want to render with. (5.11, 2010, 12) + Version = attr.ib() # type: str + + # Name of the scene file with full path. + SceneName = attr.ib() # type: str + + # Is the job enabled for submission? + # enabled by default + IsActive = attr.ib() # type: str + + # Sequence settings of this job + SeqStart = attr.ib() # type: int + SeqEnd = attr.ib() # type: int + SeqStep = attr.ib() # type: int + SeqFileOffset = attr.ib() # type: int + + # If you specify ImageDir, then ImageFilename has no path. If you do + # NOT specify ImageDir, then ImageFilename has to include the path. + # Same for ImageExtension. + # Important: Do not forget any _ or . in front or after the frame + # numbering. Usually ImageExtension always starts with a . (.tga, .exr) + ImageDir = attr.ib() # type: str + ImageFilename = attr.ib() # type: str + ImageExtension = attr.ib() # type: str + + # Some applications always add a . or _ in front of the frame number. + # Set this variable to that character. The user can then change + # the filename at the rrSubmitter and the submitter keeps + # track of this character. + ImagePreNumberLetter = attr.ib() # type: str + + # If you render a single file, e.g. Quicktime or Avi, then you have to + # set this value. Videos have to be rendered at once on one client. + ImageSingleOutputFile = attr.ib(default="false") # type: str + + # Semi-Required (required for some render applications) + # ----------------------------------------------------- + + # The database of your scene file. In Maya and XSI called "project", + # in Lightwave "content dir" + SceneDatabaseDir = attr.ib(default=None) # type: str + + # Required if you want to split frames on multiple clients + ImageWidth = attr.ib(default=None) # type: int + ImageHeight = attr.ib(default=None) # type: int + Camera = attr.ib(default=None) # type: str + Layer = attr.ib(default=None) # type: str + Channel = attr.ib(default=None) # type: str + + # Optional + # -------- + + # Used for the RR render license function. + # E.g. If you render with mentalRay, then add mentalRay. If you render + # with Nuke and you use Furnace plugins in your comp, add Furnace. + # TODO: determine how this work for multiple plugins + RequiredPlugins = attr.ib(default=None) # type: str + + # Frame Padding of the frame number in the rendered filename. + # Some render config files are setting the padding at render time. + ImageFramePadding = attr.ib(default=None) # type: str + + # Some render applications support overriding the image format at + # the render commandline. + OverrideImageFormat = attr.ib(default=None) # type: str + + # rrControl can display the name of additonal channels that are + # rendered. Each channel requires these two values. ChannelFilename + # contains the full path. + ChannelFilename = attr.ib(default=None) # type: str + ChannelExtension = attr.ib(default=None) # type: str + + # A value between 0 and 255. Each job gets the Pre ID attached as small + # letter to the main ID. A new main ID is generated for every machine + # for every 5/1000s. + PreID = attr.ib(default=None) # type: int + + # When the job is received by the server, the server checks for other + # jobs send from this machine. If a job with the PreID was found, then + # this jobs waits for the other job. Note: This flag can be used multiple + # times to wait for multiple jobs. + WaitForPreID = attr.ib(default=None) # type: int + + # List of submitter options per job + # list item must be of `SubmitterParameter` type + SubmitterParameters = attr.ib(factory=list) # type: list + + # List of Custom job attributes + # Royal Render support custom attributes in format or + # + # list item must be of `CustomAttribute` named tuple + CustomAttributes = attr.ib(factory=list) # type: list + + # Additional information for subsequent publish script and + # for better display in rrControl + UserName = attr.ib(default=None) # type: str + CustomSeQName = attr.ib(default=None) # type: str + CustomSHotName = attr.ib(default=None) # type: str + CustomVersionName = attr.ib(default=None) # type: str + CustomUserInfo = attr.ib(default=None) # type: str + SubmitMachine = attr.ib(default=None) # type: str + Color_ID = attr.ib(default=2) # type: int + + RequiredLicenses = attr.ib(default=None) # type: str + + # Additional frame info + Priority = attr.ib(default=50) # type: int + TotalFrames = attr.ib(default=None) # type: int + Tiled = attr.ib(default=None) # type: str + + +class SubmitterParameter: + """Wrapper for Submitter Parameters.""" + def __init__(self, parameter, *args): + # type: (str, list) -> None + self._parameter = parameter + self._values = args + + def serialize(self): + # type: () -> str + """Serialize submitter parameter as a string value. + + This can be later on used as text node in job xml file. + + Returns: + str: concatenated string of parameter values. + + """ + return '"{param}={val}"'.format( + param=self._parameter, val="~".join(self._values)) + + +@attr.s +class SubmitFile: + """Class wrapping Royal Render submission XML file.""" + + # Syntax version of the submission file. + syntax_version = attr.ib(default="6.0") # type: str + + # Delete submission file after processing + DeleteXML = attr.ib(default=1) # type: int + + # List of submitter options per job + # list item must be of `SubmitterParameter` type + SubmitterParameters = attr.ib(factory=list) # type: list + + # List of job is submission batch. + # list item must be of type `RRJob` + Jobs = attr.ib(factory=list) # type: list + + @staticmethod + def _process_submitter_parameters(parameters, dom, append_to): + # type: (list[SubmitterParameter], md.Document, md.Element) -> None + """Take list of :class:`SubmitterParameter` and process it as XML. + + This will take :class:`SubmitterParameter`, create XML element + for them and convert value to Royal Render compatible string + (options and values separated by ~) + + Args: + parameters (list of SubmitterParameter): List of parameters. + dom (xml.dom.minidom.Document): XML Document + append_to (xml.dom.minidom.Element): Element to append to. + + """ + for param in parameters: + if not isinstance(param, SubmitterParameter): + raise AttributeError( + "{} is not of type `SubmitterParameter`".format(param)) + xml_parameter = dom.createElement("SubmitterParameter") + xml_parameter.appendChild(dom.createTextNode(param.serialize())) + append_to.appendChild(xml_parameter) + + def serialize(self): + # type: () -> str + """Return all data serialized as XML. + + Returns: + str: XML data as string. + + """ + def filter_data(a, v): + """Skip private attributes.""" + if a.name.startswith("_"): + return False + if v is None: + return False + return True + + root = md.Document() + # root element: + job_file = root.createElement('RR_Job_File') + job_file.setAttribute("syntax_version", self.syntax_version) + + # handle Submitter Parameters for batch + # foo=bar~baz~goo + self._process_submitter_parameters( + self.SubmitterParameters, root, job_file) + + for job in self.Jobs: # type: RRJob + if not isinstance(job, RRJob): + raise AttributeError( + "{} is not of type `SubmitterParameter`".format(job)) + xml_job = root.createElement("Job") + # handle Submitter Parameters for job + self._process_submitter_parameters( + job.SubmitterParameters, root, xml_job + ) + job_custom_attributes = job.CustomAttributes + + serialized_job = attr.asdict( + job, dict_factory=OrderedDict, filter=filter_data) + serialized_job.pop("CustomAttributes") + serialized_job.pop("SubmitterParameters") + + for custom_attr in job_custom_attributes: # type: CustomAttribute + serialized_job["Custom{}".format( + custom_attr.name)] = custom_attr.value + + for item, value in serialized_job.items(): + xml_attr = root.create(item) + xml_attr.appendChild( + root.createTextNode(value) + ) + xml_job.appendChild(xml_attr) + + return root.toprettyxml(indent="\t") diff --git a/openpype/modules/default_modules/royal_render/rr_root/README.md b/openpype/modules/default_modules/royal_render/rr_root/README.md new file mode 100644 index 0000000000..0a9777833e --- /dev/null +++ b/openpype/modules/default_modules/royal_render/rr_root/README.md @@ -0,0 +1,5 @@ +## OpenPype RoyalRender integration plugins + +### Installation + +Copy content of this folder to your `RR_ROOT` (place where RoyalRender studio wide installation is). \ No newline at end of file diff --git a/openpype/modules/default_modules/royal_render/rr_root/plugins/control_job/perjob/m50__openpype_publish_render.py b/openpype/modules/default_modules/royal_render/rr_root/plugins/control_job/perjob/m50__openpype_publish_render.py new file mode 100644 index 0000000000..17e4fb38d1 --- /dev/null +++ b/openpype/modules/default_modules/royal_render/rr_root/plugins/control_job/perjob/m50__openpype_publish_render.py @@ -0,0 +1,170 @@ +# -*- coding: utf-8 -*- +"""This is RR control plugin that runs on the job by user interaction. + +It asks user for context to publish, getting it from OpenPype. In order to +run it needs `OPENPYPE_ROOT` to be set to know where to execute OpenPype. + +""" +import rr # noqa +import rrGlobal # noqa +import subprocess +import os +import glob +import platform +import tempfile +import json + + +class OpenPypeContextSelector: + """Class to handle publishing context determination in RR.""" + + def __init__(self): + self.job = rr.getJob() + self.context = None + + self.openpype_executable = "openpype_gui" + if platform.system().lower() == "windows": + self.openpype_executable = "{}.exe".format( + self.openpype_executable) + + op_path = os.environ.get("OPENPYPE_ROOT") + print("initializing ... {}".format(op_path)) + if not op_path: + print("Warning: OpenPype root is not found.") + + if platform.system().lower() == "windows": + print(" * trying to find OpenPype on local computer.") + op_path = os.path.join( + os.environ.get("PROGRAMFILES"), + "OpenPype", "openpype_console.exe" + ) + if os.path.exists(op_path): + print(" - found OpenPype installation {}".format(op_path)) + else: + # try to find in user local context + op_path = os.path.join( + os.environ.get("LOCALAPPDATA"), + "Programs", + "OpenPype", "openpype_console.exe" + ) + if os.path.exists(op_path): + print( + " - found OpenPype installation {}".format( + op_path)) + else: + raise Exception("Error: OpenPype was not found.") + + self.openpype_root = op_path + + # TODO: this should try to find metadata file. Either using + # jobs custom attributes or using environment variable + # or just using plain existence of file. + # self.context = self._process_metadata_file() + + def _process_metadata_file(self): + """Find and process metadata file. + + Try to find metadata json file in job folder to get context from. + + Returns: + dict: Context from metadata json file. + + """ + image_dir = self.job.imageDir + metadata_files = glob.glob( + "{}{}*_metadata.json".format(image_dir, os.path.sep)) + if not metadata_files: + return {} + + raise NotImplementedError( + "Processing existing metadata not implemented yet.") + + def process_job(self): + """Process selected job. + + This should process selected job. If context can be determined + automatically, no UI will be show and publishing will directly + proceed. + """ + if not self.context: + self.show() + + self.context["user"] = self.job.userName + self.run_publish() + + def show(self): + """Show UI for context selection. + + Because of RR UI limitations, this must be done using OpenPype + itself. + + """ + tf = tempfile.TemporaryFile(delete=False) + context_file = tf.name + op_args = [os.path.join(self.openpype_root, self.openpype_executable), + "contextselection", tf.name] + + tf.close() + print(">>> running {}".format(" ".join(op_args))) + + subprocess.call(op_args) + + with open(context_file, "r") as cf: + self.context = json.load(cf) + + os.unlink(context_file) + print(">>> context: {}".format(self.context)) + + if not self.context or \ + not self.context.get("project") or \ + not self.context.get("asset") or \ + not self.context.get("task"): + self._show_rr_warning("Context selection failed.") + return + + # self.context["app_name"] = self.job.renderer.name + self.context["app_name"] = "maya/2020" + + @staticmethod + def _show_rr_warning(text): + warning_dialog = rrGlobal.getGenericUI() + warning_dialog.addItem(rrGlobal.genUIType.label, "infoLabel", "") + warning_dialog.setText("infoLabel", text) + warning_dialog.addItem( + rrGlobal.genUIType.layoutH, "btnLayout", "") + warning_dialog.addItem( + rrGlobal.genUIType.closeButton, "Ok", "btnLayout") + warning_dialog.execute() + del warning_dialog + + def run_publish(self): + """Run publish process.""" + env = {'AVALON_PROJECT': str(self.context.get("project")), + "AVALON_ASSET": str(self.context.get("asset")), + "AVALON_TASK": str(self.context.get("task")), + "AVALON_APP_NAME": str(self.context.get("app_name"))} + + print(">>> setting environment:") + for k, v in env.items(): + print(" {}: {}".format(k, v)) + + args = [os.path.join(self.openpype_root, self.openpype_executable), + 'publish', '-t', "rr_control", "--gui", + os.path.join(self.job.imageDir, + os.path.dirname(self.job.imageFileName)) + ] + + print(">>> running {}".format(" ".join(args))) + orig = os.environ.copy() + orig.update(env) + try: + subprocess.call(args, env=orig) + except subprocess.CalledProcessError as e: + self._show_rr_warning(" Publish failed [ {} ]".format( + e.returncode + )) + + +print("running selector") +selector = OpenPypeContextSelector() +selector.process_job() diff --git a/openpype/modules/default_modules/settings_module/interfaces.py b/openpype/modules/default_modules/settings_module/interfaces.py deleted file mode 100644 index 42db395649..0000000000 --- a/openpype/modules/default_modules/settings_module/interfaces.py +++ /dev/null @@ -1,30 +0,0 @@ -from abc import abstractmethod -from openpype.modules import OpenPypeInterface - - -class ISettingsChangeListener(OpenPypeInterface): - """Module has plugin paths to return. - - Expected result is dictionary with keys "publish", "create", "load" or - "actions" and values as list or string. - { - "publish": ["path/to/publish_plugins"] - } - """ - @abstractmethod - def on_system_settings_save( - self, old_value, new_value, changes, new_value_metadata - ): - pass - - @abstractmethod - def on_project_settings_save( - self, old_value, new_value, changes, project_name, new_value_metadata - ): - pass - - @abstractmethod - def on_project_anatomy_save( - self, old_value, new_value, changes, project_name, new_value_metadata - ): - pass diff --git a/openpype/modules/default_modules/sync_server/providers/dropbox.py b/openpype/modules/default_modules/sync_server/providers/dropbox.py index 2bc7a83a5b..90d7d44bb8 100644 --- a/openpype/modules/default_modules/sync_server/providers/dropbox.py +++ b/openpype/modules/default_modules/sync_server/providers/dropbox.py @@ -24,25 +24,19 @@ class DropboxHandler(AbstractProvider): ) return - provider_presets = self.presets.get(self.CODE) - if not provider_presets: - msg = "Sync Server: No provider presets for {}".format(self.CODE) - log.info(msg) - return - - token = self.presets[self.CODE].get("token", "") + token = self.presets.get("token", "") if not token: msg = "Sync Server: No access token for dropbox provider" log.info(msg) return - team_folder_name = self.presets[self.CODE].get("team_folder_name", "") + team_folder_name = self.presets.get("team_folder_name", "") if not team_folder_name: msg = "Sync Server: No team folder name for dropbox provider" log.info(msg) return - acting_as_member = self.presets[self.CODE].get("acting_as_member", "") + acting_as_member = self.presets.get("acting_as_member", "") if not acting_as_member: msg = ( "Sync Server: No acting member for dropbox provider" @@ -51,13 +45,15 @@ class DropboxHandler(AbstractProvider): return self.dbx = None - try: - self.dbx = self._get_service( - token, acting_as_member, team_folder_name - ) - except Exception as e: - log.info("Could not establish dropbox object: {}".format(e)) - return + + if self.presets["enabled"]: + try: + self.dbx = self._get_service( + token, acting_as_member, team_folder_name + ) + except Exception as e: + log.info("Could not establish dropbox object: {}".format(e)) + return super(AbstractProvider, self).__init__() @@ -101,12 +97,12 @@ class DropboxHandler(AbstractProvider): }, # roots could be overriden only on Project level, User cannot { - "key": "roots", + "key": "root", "label": "Roots", "type": "dict-roots", "object_type": { "type": "path", - "multiplatform": True, + "multiplatform": False, "multipath": False } } @@ -169,7 +165,7 @@ class DropboxHandler(AbstractProvider): Returns: (boolean) """ - return self.dbx is not None + return self.presets["enabled"] and self.dbx is not None @classmethod def get_configurable_items(cls): diff --git a/openpype/modules/default_modules/sync_server/providers/gdrive.py b/openpype/modules/default_modules/sync_server/providers/gdrive.py index 8c8447f8f0..d43e2b3d61 100644 --- a/openpype/modules/default_modules/sync_server/providers/gdrive.py +++ b/openpype/modules/default_modules/sync_server/providers/gdrive.py @@ -73,13 +73,7 @@ class GDriveHandler(AbstractProvider): format(site_name)) return - provider_presets = self.presets.get(self.CODE) - if not provider_presets: - msg = "Sync Server: No provider presets for {}".format(self.CODE) - log.info(msg) - return - - cred_path = self.presets[self.CODE].get("credentials_url", {}).\ + cred_path = self.presets.get("credentials_url", {}).\ get(platform.system().lower()) or '' if not os.path.exists(cred_path): msg = "Sync Server: No credentials for gdrive provider " + \ @@ -87,10 +81,12 @@ class GDriveHandler(AbstractProvider): log.info(msg) return - self.service = self._get_gd_service(cred_path) + self.service = None + if self.presets["enabled"]: + self.service = self._get_gd_service(cred_path) - self._tree = tree - self.active = True + self._tree = tree + self.active = True def is_active(self): """ @@ -98,7 +94,7 @@ class GDriveHandler(AbstractProvider): Returns: (boolean) """ - return self.service is not None + return self.presets["enabled"] and self.service is not None @classmethod def get_system_settings_schema(cls): @@ -125,18 +121,20 @@ class GDriveHandler(AbstractProvider): editable = [ # credentials could be overriden on Project or User level { - 'key': "credentials_url", - 'label': "Credentials url", - 'type': 'text' + "type": "path", + "key": "credentials_url", + "label": "Credentials url", + "multiplatform": True, + "placeholder": "Credentials url" }, # roots could be overriden only on Project leve, User cannot { - "key": "roots", + "key": "root", "label": "Roots", "type": "dict-roots", "object_type": { "type": "path", - "multiplatform": True, + "multiplatform": False, "multipath": False } } diff --git a/openpype/modules/default_modules/sync_server/providers/local_drive.py b/openpype/modules/default_modules/sync_server/providers/local_drive.py index e6c62f2daa..68f604b39c 100644 --- a/openpype/modules/default_modules/sync_server/providers/local_drive.py +++ b/openpype/modules/default_modules/sync_server/providers/local_drive.py @@ -50,7 +50,7 @@ class LocalDriveHandler(AbstractProvider): # for non 'studio' sites, 'studio' is configured in Anatomy editable = [ { - "key": "roots", + "key": "root", "label": "Roots", "type": "dict-roots", "object_type": { @@ -73,7 +73,7 @@ class LocalDriveHandler(AbstractProvider): """ editable = [ { - 'key': "roots", + 'key': "root", 'label': "Roots", 'type': 'dict' } @@ -89,6 +89,7 @@ class LocalDriveHandler(AbstractProvider): if not os.path.isfile(source_path): raise FileNotFoundError("Source file {} doesn't exist." .format(source_path)) + if overwrite: thread = threading.Thread(target=self._copy, args=(source_path, target_path)) @@ -181,7 +182,10 @@ class LocalDriveHandler(AbstractProvider): def _copy(self, source_path, target_path): print("copying {}->{}".format(source_path, target_path)) - shutil.copy(source_path, target_path) + try: + shutil.copy(source_path, target_path) + except shutil.SameFileError: + print("same files, skipping") def _mark_progress(self, collection, file, representation, server, site, source_path, target_path, direction): diff --git a/openpype/modules/default_modules/sync_server/providers/sftp.py b/openpype/modules/default_modules/sync_server/providers/sftp.py index d737849cdc..4f505ae016 100644 --- a/openpype/modules/default_modules/sync_server/providers/sftp.py +++ b/openpype/modules/default_modules/sync_server/providers/sftp.py @@ -1,8 +1,6 @@ import os import os.path import time -import sys -import six import threading import platform @@ -14,6 +12,7 @@ log = Logger().get_logger("SyncServer") pysftp = None try: import pysftp + import paramiko except (ImportError, SyntaxError): pass @@ -37,7 +36,6 @@ class SFTPHandler(AbstractProvider): def __init__(self, project_name, site_name, tree=None, presets=None): self.presets = None - self.active = False self.project_name = project_name self.site_name = site_name self.root = None @@ -49,22 +47,15 @@ class SFTPHandler(AbstractProvider): format(site_name)) return - provider_presets = self.presets.get(self.CODE) - if not provider_presets: - msg = "Sync Server: No provider presets for {}".format(self.CODE) - log.warning(msg) - return - # store to instance for reconnect - self.sftp_host = provider_presets["sftp_host"] - self.sftp_port = provider_presets["sftp_port"] - self.sftp_user = provider_presets["sftp_user"] - self.sftp_pass = provider_presets["sftp_pass"] - self.sftp_key = provider_presets["sftp_key"] - self.sftp_key_pass = provider_presets["sftp_key_pass"] + self.sftp_host = presets["sftp_host"] + self.sftp_port = presets["sftp_port"] + self.sftp_user = presets["sftp_user"] + self.sftp_pass = presets["sftp_pass"] + self.sftp_key = presets["sftp_key"] + self.sftp_key_pass = presets["sftp_key_pass"] self._tree = None - self.active = True @property def conn(self): @@ -80,7 +71,7 @@ class SFTPHandler(AbstractProvider): Returns: (boolean) """ - return self.conn is not None + return self.presets["enabled"] and self.conn is not None @classmethod def get_system_settings_schema(cls): @@ -108,7 +99,7 @@ class SFTPHandler(AbstractProvider): editable = [ # credentials could be overriden on Project or User level { - 'key': "sftp_server", + 'key': "sftp_host", 'label': "SFTP host name", 'type': 'text' }, @@ -130,7 +121,8 @@ class SFTPHandler(AbstractProvider): { 'key': "sftp_key", 'label': "SFTP user ssh key", - 'type': 'path' + 'type': 'path', + "multiplatform": True }, { 'key': "sftp_key_pass", @@ -139,12 +131,12 @@ class SFTPHandler(AbstractProvider): }, # roots could be overriden only on Project leve, User cannot { - "key": "roots", + "key": "root", "label": "Roots", "type": "dict-roots", "object_type": { "type": "path", - "multiplatform": True, + "multiplatform": False, "multipath": False } } @@ -176,7 +168,8 @@ class SFTPHandler(AbstractProvider): { 'key': "sftp_key", 'label': "SFTP user ssh key", - 'type': 'path' + 'type': 'path', + "multiplatform": True }, { 'key': "sftp_key_pass", @@ -199,7 +192,7 @@ class SFTPHandler(AbstractProvider): Format is importing for usage of python's format ** approach """ # roots cannot be locally overridden - return self.presets['root'] + return self.presets['roots'] def get_tree(self): """ @@ -426,7 +419,10 @@ class SFTPHandler(AbstractProvider): if self.sftp_key_pass: conn_params['private_key_pass'] = self.sftp_key_pass - return pysftp.Connection(**conn_params) + try: + return pysftp.Connection(**conn_params) + except paramiko.ssh_exception.SSHException: + log.warning("Couldn't connect", exc_info=True) def _mark_progress(self, collection, file, representation, server, site, source_path, target_path, direction): diff --git a/openpype/modules/default_modules/sync_server/sync_server.py b/openpype/modules/default_modules/sync_server/sync_server.py index 6aca2460e3..22eed01ef3 100644 --- a/openpype/modules/default_modules/sync_server/sync_server.py +++ b/openpype/modules/default_modules/sync_server/sync_server.py @@ -80,6 +80,10 @@ async def upload(module, collection, file, representation, provider_name, remote_site_name, True ) + + module.handle_alternate_site(collection, representation, remote_site_name, + file["_id"], file_id) + return file_id @@ -131,6 +135,10 @@ async def download(module, collection, file, representation, provider_name, local_site, True ) + + module.handle_alternate_site(collection, representation, local_site, + file["_id"], file_id) + return file_id @@ -246,6 +254,7 @@ class SyncServerThread(threading.Thread): asyncio.ensure_future(self.check_shutdown(), loop=self.loop) asyncio.ensure_future(self.sync_loop(), loop=self.loop) + log.info("Sync Server Started") self.loop.run_forever() except Exception: log.warning( diff --git a/openpype/modules/default_modules/sync_server/sync_server_module.py b/openpype/modules/default_modules/sync_server/sync_server_module.py index d60147a989..cd29d93384 100644 --- a/openpype/modules/default_modules/sync_server/sync_server_module.py +++ b/openpype/modules/default_modules/sync_server/sync_server_module.py @@ -109,6 +109,7 @@ class SyncServerModule(OpenPypeModule, ITrayModule): # some parts of code need to run sequentially, not in async self.lock = None + self._sync_system_settings = None # settings for all enabled projects for sync self._sync_project_settings = None self.sync_server_thread = None # asyncio requires new thread @@ -152,9 +153,9 @@ class SyncServerModule(OpenPypeModule, ITrayModule): if not site_name: site_name = self.DEFAULT_SITE - self.reset_provider_for_file(collection, - representation_id, - site_name=site_name, force=force) + self.reset_site_on_representation(collection, + representation_id, + site_name=site_name, force=force) # public facing API def remove_site(self, collection, representation_id, site_name, @@ -176,10 +177,10 @@ class SyncServerModule(OpenPypeModule, ITrayModule): if not self.get_sync_project_setting(collection): raise ValueError("Project not configured") - self.reset_provider_for_file(collection, - representation_id, - site_name=site_name, - remove=True) + self.reset_site_on_representation(collection, + representation_id, + site_name=site_name, + remove=True) if remove_local_files: self._remove_local_file(collection, representation_id, site_name) @@ -314,8 +315,8 @@ class SyncServerModule(OpenPypeModule, ITrayModule): """ log.info("Pausing SyncServer for {}".format(representation_id)) self._paused_representations.add(representation_id) - self.reset_provider_for_file(collection, representation_id, - site_name=site_name, pause=True) + self.reset_site_on_representation(collection, representation_id, + site_name=site_name, pause=True) def unpause_representation(self, collection, representation_id, site_name): """ @@ -334,8 +335,8 @@ class SyncServerModule(OpenPypeModule, ITrayModule): except KeyError: pass # self.paused_representations is not persistent - self.reset_provider_for_file(collection, representation_id, - site_name=site_name, pause=False) + self.reset_site_on_representation(collection, representation_id, + site_name=site_name, pause=False) def is_representation_paused(self, representation_id, check_parents=False, project_name=None): @@ -769,6 +770,58 @@ class SyncServerModule(OpenPypeModule, ITrayModule): enabled_projects.append(project_name) return enabled_projects + + def handle_alternate_site(self, collection, representation, processed_site, + file_id, synced_file_id): + """ + For special use cases where one site vendors another. + + Current use case is sftp site vendoring (exposing) same data as + regular site (studio). Each site is accessible for different + audience. 'studio' for artists in a studio, 'sftp' for externals. + + Change of file status on one site actually means same change on + 'alternate' site. (eg. artists publish to 'studio', 'sftp' is using + same location >> file is accesible on 'sftp' site right away. + + Args: + collection (str): name of project + representation (dict) + processed_site (str): real site_name of published/uploaded file + file_id (ObjectId): DB id of file handled + synced_file_id (str): id of the created file returned + by provider + """ + sites = self.sync_system_settings.get("sites", {}) + sites[self.DEFAULT_SITE] = {"provider": "local_drive", + "alternative_sites": []} + + alternate_sites = [] + for site_name, site_info in sites.items(): + conf_alternative_sites = site_info.get("alternative_sites", []) + if processed_site in conf_alternative_sites: + alternate_sites.append(site_name) + continue + if processed_site == site_name and conf_alternative_sites: + alternate_sites.extend(conf_alternative_sites) + continue + + alternate_sites = set(alternate_sites) + + for alt_site in alternate_sites: + query = { + "_id": representation["_id"] + } + elem = {"name": alt_site, + "created_dt": datetime.now(), + "id": synced_file_id} + + self.log.debug("Adding alternate {} to {}".format( + alt_site, representation["_id"])) + self._add_site(collection, query, + [representation], elem, + alt_site, file_id=file_id, force=True) + """ End of Public API """ def get_local_file_path(self, collection, site_name, file_path): @@ -799,12 +852,19 @@ class SyncServerModule(OpenPypeModule, ITrayModule): def tray_init(self): """ - Actual initialization of Sync Server. + Actual initialization of Sync Server for Tray. Called when tray is initialized, it checks if module should be enabled. If not, no initialization necessary. """ - # import only in tray, because of Python2 hosts + self.server_init() + + from .tray.app import SyncServerWindow + self.widget = SyncServerWindow(self) + + def server_init(self): + """Actual initialization of Sync Server.""" + # import only in tray or Python3, because of Python2 hosts from .sync_server import SyncServerThread if not self.enabled: @@ -816,6 +876,7 @@ class SyncServerModule(OpenPypeModule, ITrayModule): return self.lock = threading.Lock() + self.sync_server_thread = SyncServerThread(self) def tray_start(self): @@ -829,6 +890,9 @@ class SyncServerModule(OpenPypeModule, ITrayModule): Returns: None """ + self.server_start() + + def server_start(self): if self.sync_project_settings and self.enabled: self.sync_server_thread.start() else: @@ -841,6 +905,9 @@ class SyncServerModule(OpenPypeModule, ITrayModule): Called from Module Manager """ + self.server_exit() + + def server_exit(self): if not self.sync_server_thread: return @@ -850,6 +917,7 @@ class SyncServerModule(OpenPypeModule, ITrayModule): log.info("Stopping sync server server") self.sync_server_thread.is_running = False self.sync_server_thread.stop() + log.info("Sync server stopped") except Exception: log.warning( "Error has happened during Killing sync server", @@ -892,6 +960,14 @@ class SyncServerModule(OpenPypeModule, ITrayModule): return self._connection + @property + def sync_system_settings(self): + if self._sync_system_settings is None: + self._sync_system_settings = get_system_settings()["modules"].\ + get("sync_server") + + return self._sync_system_settings + @property def sync_project_settings(self): if self._sync_project_settings is None: @@ -977,9 +1053,7 @@ class SyncServerModule(OpenPypeModule, ITrayModule): (dict): {'studio': {'provider':'local_drive'...}, 'MY_LOCAL': {'provider':....}} """ - sys_sett = get_system_settings() - sync_sett = sys_sett["modules"].get("sync_server") - + sync_sett = self.sync_system_settings project_enabled = True if project_name: project_enabled = project_name in self.get_enabled_projects() @@ -1037,10 +1111,9 @@ class SyncServerModule(OpenPypeModule, ITrayModule): if provider: return provider - sys_sett = get_system_settings() - sync_sett = sys_sett["modules"].get("sync_server") - for site, detail in sync_sett.get("sites", {}).items(): - sites[site] = detail.get("provider") + sync_sett = self.sync_system_settings + for conf_site, detail in sync_sett.get("sites", {}).items(): + sites[conf_site] = detail.get("provider") return sites.get(site, 'N/A') @@ -1319,9 +1392,9 @@ class SyncServerModule(OpenPypeModule, ITrayModule): return -1, None - def reset_provider_for_file(self, collection, representation_id, - side=None, file_id=None, site_name=None, - remove=False, pause=None, force=False): + def reset_site_on_representation(self, collection, representation_id, + side=None, file_id=None, site_name=None, + remove=False, pause=None, force=False): """ Reset information about synchronization for particular 'file_id' and provider. @@ -1407,9 +1480,12 @@ class SyncServerModule(OpenPypeModule, ITrayModule): update = { "$set": {"files.$[f].sites.$[s]": elem} } + if not isinstance(file_id, ObjectId): + file_id = ObjectId(file_id) + arr_filter = [ {'s.name': site_name}, - {'f._id': ObjectId(file_id)} + {'f._id': file_id} ] self._update_site(collection, query, update, arr_filter) diff --git a/openpype/modules/default_modules/sync_server/tray/delegates.py b/openpype/modules/default_modules/sync_server/tray/delegates.py index 461b9fffb3..5ab809a816 100644 --- a/openpype/modules/default_modules/sync_server/tray/delegates.py +++ b/openpype/modules/default_modules/sync_server/tray/delegates.py @@ -4,6 +4,18 @@ from Qt import QtCore, QtWidgets, QtGui from openpype.lib import PypeLogger from . import lib +from openpype.tools.utils.constants import ( + LOCAL_PROVIDER_ROLE, + REMOTE_PROVIDER_ROLE, + LOCAL_PROGRESS_ROLE, + REMOTE_PROGRESS_ROLE, + LOCAL_DATE_ROLE, + REMOTE_DATE_ROLE, + LOCAL_FAILED_ROLE, + REMOTE_FAILED_ROLE, + EDIT_ICON_ROLE +) + log = PypeLogger().get_logger("SyncServer") @@ -14,7 +26,7 @@ class PriorityDelegate(QtWidgets.QStyledItemDelegate): if option.widget.selectionModel().isSelected(index) or \ option.state & QtWidgets.QStyle.State_MouseOver: - edit_icon = index.data(lib.EditIconRole) + edit_icon = index.data(EDIT_ICON_ROLE) if not edit_icon: return @@ -38,7 +50,7 @@ class PriorityDelegate(QtWidgets.QStyledItemDelegate): editor = PriorityLineEdit( parent, option.widget.selectionModel().selectedRows()) - editor.setFocus(True) + editor.setFocus() return editor def setModelData(self, editor, model, index): @@ -71,19 +83,30 @@ class ImageDelegate(QtWidgets.QStyledItemDelegate): Prints icon of site and progress of synchronization """ - def __init__(self, parent=None): + def __init__(self, parent=None, side=None): super(ImageDelegate, self).__init__(parent) self.icons = {} + self.side = side def paint(self, painter, option, index): super(ImageDelegate, self).paint(painter, option, index) option = QtWidgets.QStyleOptionViewItem(option) option.showDecorationSelected = True - provider = index.data(lib.ProviderRole) - value = index.data(lib.ProgressRole) - date_value = index.data(lib.DateRole) - is_failed = index.data(lib.FailedRole) + if not self.side: + log.warning("No side provided, delegate won't work") + return + + if self.side == 'local': + provider = index.data(LOCAL_PROVIDER_ROLE) + value = index.data(LOCAL_PROGRESS_ROLE) + date_value = index.data(LOCAL_DATE_ROLE) + is_failed = index.data(LOCAL_FAILED_ROLE) + else: + provider = index.data(REMOTE_PROVIDER_ROLE) + value = index.data(REMOTE_PROGRESS_ROLE) + date_value = index.data(REMOTE_DATE_ROLE) + is_failed = index.data(REMOTE_FAILED_ROLE) if not self.icons.get(provider): resource_path = os.path.dirname(__file__) diff --git a/openpype/modules/default_modules/sync_server/tray/lib.py b/openpype/modules/default_modules/sync_server/tray/lib.py index 25c600abd2..87344be634 100644 --- a/openpype/modules/default_modules/sync_server/tray/lib.py +++ b/openpype/modules/default_modules/sync_server/tray/lib.py @@ -1,4 +1,3 @@ -from Qt import QtCore import attr import abc import six @@ -19,14 +18,6 @@ STATUS = { DUMMY_PROJECT = "No project configured" -ProviderRole = QtCore.Qt.UserRole + 2 -ProgressRole = QtCore.Qt.UserRole + 4 -DateRole = QtCore.Qt.UserRole + 6 -FailedRole = QtCore.Qt.UserRole + 8 -HeaderNameRole = QtCore.Qt.UserRole + 10 -FullItemRole = QtCore.Qt.UserRole + 12 -EditIconRole = QtCore.Qt.UserRole + 14 - @six.add_metaclass(abc.ABCMeta) class AbstractColumnFilter: @@ -161,7 +152,7 @@ def translate_provider_for_icon(sync_server, project, site): return sync_server.get_provider_for_site(site=site) -def get_item_by_id(model, object_id): +def get_value_from_id_by_role(model, object_id, role): + """Return value from item with 'object_id' with 'role'.""" index = model.get_index(object_id) - item = model.data(index, FullItemRole) - return item + return model.data(index, role) diff --git a/openpype/modules/default_modules/sync_server/tray/models.py b/openpype/modules/default_modules/sync_server/tray/models.py index 713e167a6a..80f41992cb 100644 --- a/openpype/modules/default_modules/sync_server/tray/models.py +++ b/openpype/modules/default_modules/sync_server/tray/models.py @@ -13,6 +13,23 @@ from openpype.api import get_local_site_id from . import lib +from openpype.tools.utils.constants import ( + LOCAL_PROVIDER_ROLE, + REMOTE_PROVIDER_ROLE, + LOCAL_PROGRESS_ROLE, + REMOTE_PROGRESS_ROLE, + HEADER_NAME_ROLE, + EDIT_ICON_ROLE, + LOCAL_DATE_ROLE, + REMOTE_DATE_ROLE, + LOCAL_FAILED_ROLE, + REMOTE_FAILED_ROLE, + STATUS_ROLE, + PATH_ROLE, + ERROR_ROLE, + TRIES_ROLE +) + log = PypeLogger().get_logger("SyncServer") @@ -68,10 +85,68 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): if orientation == Qt.Horizontal: return self.COLUMN_LABELS[section][1] - if role == lib.HeaderNameRole: + if role == HEADER_NAME_ROLE: if orientation == Qt.Horizontal: return self.COLUMN_LABELS[section][0] # return name + def data(self, index, role): + item = self._data[index.row()] + + header_value = self._header[index.column()] + if role == LOCAL_PROVIDER_ROLE: + return item.local_provider + + if role == REMOTE_PROVIDER_ROLE: + return item.remote_provider + + if role == LOCAL_PROGRESS_ROLE: + return item.local_progress + + if role == REMOTE_PROGRESS_ROLE: + return item.remote_progress + + if role == LOCAL_DATE_ROLE: + if item.created_dt: + return pretty_timestamp(item.created_dt) + + if role == REMOTE_DATE_ROLE: + if item.sync_dt: + return pretty_timestamp(item.sync_dt) + + if role == LOCAL_FAILED_ROLE: + return item.status == lib.STATUS[2] and \ + item.local_progress < 1 + + if role == REMOTE_FAILED_ROLE: + return item.status == lib.STATUS[2] and \ + item.remote_progress < 1 + + if role in (Qt.DisplayRole, Qt.EditRole): + # because of ImageDelegate + if header_value in ['remote_site', 'local_site']: + return "" + + return attr.asdict(item)[self._header[index.column()]] + + if role == EDIT_ICON_ROLE: + if self.can_edit and header_value in self.EDITABLE_COLUMNS: + return self.edit_icon + + if role == PATH_ROLE: + return item.path + + if role == ERROR_ROLE: + return item.error + + if role == TRIES_ROLE: + return item.tries + + if role == STATUS_ROLE: + return item.status + + if role == Qt.UserRole: + return item._id + @property def can_edit(self): """Returns true if some site is user local site, eg. could edit""" @@ -456,55 +531,6 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): self.timer.timeout.connect(self.tick) self.timer.start(self.REFRESH_SEC) - def data(self, index, role): - item = self._data[index.row()] - - if role == lib.FullItemRole: - return item - - header_value = self._header[index.column()] - if role == lib.ProviderRole: - if header_value == 'local_site': - return item.local_provider - if header_value == 'remote_site': - return item.remote_provider - - if role == lib.ProgressRole: - if header_value == 'local_site': - return item.local_progress - if header_value == 'remote_site': - return item.remote_progress - - if role == lib.DateRole: - if header_value == 'local_site': - if item.created_dt: - return pretty_timestamp(item.created_dt) - if header_value == 'remote_site': - if item.sync_dt: - return pretty_timestamp(item.sync_dt) - - if role == lib.FailedRole: - if header_value == 'local_site': - return item.status == lib.STATUS[2] and \ - item.local_progress < 1 - if header_value == 'remote_site': - return item.status == lib.STATUS[2] and \ - item.remote_progress < 1 - - if role in (Qt.DisplayRole, Qt.EditRole): - # because of ImageDelegate - if header_value in ['remote_site', 'local_site']: - return "" - - return attr.asdict(item)[self._header[index.column()]] - - if role == lib.EditIconRole: - if self.can_edit and header_value in self.EDITABLE_COLUMNS: - return self.edit_icon - - if role == Qt.UserRole: - return item._id - def add_page_records(self, local_site, remote_site, representations): """ Process all records from 'representation' and add them to storage. @@ -985,55 +1011,6 @@ class SyncRepresentationDetailModel(_SyncRepresentationModel): self.timer.timeout.connect(self.tick) self.timer.start(SyncRepresentationSummaryModel.REFRESH_SEC) - def data(self, index, role): - item = self._data[index.row()] - - if role == lib.FullItemRole: - return item - - header_value = self._header[index.column()] - if role == lib.ProviderRole: - if header_value == 'local_site': - return item.local_provider - if header_value == 'remote_site': - return item.remote_provider - - if role == lib.ProgressRole: - if header_value == 'local_site': - return item.local_progress - if header_value == 'remote_site': - return item.remote_progress - - if role == lib.DateRole: - if header_value == 'local_site': - if item.created_dt: - return pretty_timestamp(item.created_dt) - if header_value == 'remote_site': - if item.sync_dt: - return pretty_timestamp(item.sync_dt) - - if role == lib.FailedRole: - if header_value == 'local_site': - return item.status == lib.STATUS[2] and \ - item.local_progress < 1 - if header_value == 'remote_site': - return item.status == lib.STATUS[2] and \ - item.remote_progress < 1 - - if role in (Qt.DisplayRole, Qt.EditRole): - # because of ImageDelegate - if header_value in ['remote_site', 'local_site']: - return "" - - return attr.asdict(item)[self._header[index.column()]] - - if role == lib.EditIconRole: - if self.can_edit and header_value in self.EDITABLE_COLUMNS: - return self.edit_icon - - if role == Qt.UserRole: - return item._id - def add_page_records(self, local_site, remote_site, representations): """ Process all records from 'representation' and add them to storage. diff --git a/openpype/modules/default_modules/sync_server/tray/widgets.py b/openpype/modules/default_modules/sync_server/tray/widgets.py index 5e368f9e0b..18487b3d11 100644 --- a/openpype/modules/default_modules/sync_server/tray/widgets.py +++ b/openpype/modules/default_modules/sync_server/tray/widgets.py @@ -22,6 +22,20 @@ from .models import ( from . import lib from . import delegates +from openpype.tools.utils.constants import ( + LOCAL_PROGRESS_ROLE, + REMOTE_PROGRESS_ROLE, + HEADER_NAME_ROLE, + STATUS_ROLE, + PATH_ROLE, + LOCAL_SITE_NAME_ROLE, + REMOTE_SITE_NAME_ROLE, + LOCAL_DATE_ROLE, + REMOTE_DATE_ROLE, + ERROR_ROLE, + TRIES_ROLE +) + log = PypeLogger().get_logger("SyncServer") @@ -140,7 +154,7 @@ class SyncProjectListWidget(QtWidgets.QWidget): selected_index.isValid() and \ not self._selection_changed: mode = QtCore.QItemSelectionModel.Select | \ - QtCore.QItemSelectionModel.Rows + QtCore.QItemSelectionModel.Rows self.project_list.selectionModel().select(selected_index, mode) if self.current_project: @@ -289,14 +303,19 @@ class _SyncRepresentationWidget(QtWidgets.QWidget): if is_multi: index = self.model.get_index(list(self._selected_ids)[0]) - item = self.model.data(index, lib.FullItemRole) + local_progress = self.model.data(index, LOCAL_PROGRESS_ROLE) + remote_progress = self.model.data(index, REMOTE_PROGRESS_ROLE) + status = self.model.data(index, STATUS_ROLE) else: - item = self.model.data(point_index, lib.FullItemRole) + local_progress = self.model.data(point_index, LOCAL_PROGRESS_ROLE) + remote_progress = self.model.data(point_index, + REMOTE_PROGRESS_ROLE) + status = self.model.data(point_index, STATUS_ROLE) + can_edit = self.model.can_edit - action_kwarg_map, actions_mapping, menu = self._prepare_menu(item, - is_multi, - can_edit) + action_kwarg_map, actions_mapping, menu = self._prepare_menu( + local_progress, remote_progress, is_multi, can_edit, status) result = menu.exec_(QtGui.QCursor.pos()) if result: @@ -307,7 +326,8 @@ class _SyncRepresentationWidget(QtWidgets.QWidget): self.model.refresh() - def _prepare_menu(self, item, is_multi, can_edit): + def _prepare_menu(self, local_progress, remote_progress, + is_multi, can_edit, status=None): menu = QtWidgets.QMenu(self) actions_mapping = {} @@ -316,11 +336,6 @@ class _SyncRepresentationWidget(QtWidgets.QWidget): active_site = self.model.active_site remote_site = self.model.remote_site - local_progress = item.local_progress - remote_progress = item.remote_progress - - project = self.model.project - for site, progress in {active_site: local_progress, remote_site: remote_progress}.items(): provider = self.sync_server.get_provider_for_site(site=site) @@ -360,12 +375,6 @@ class _SyncRepresentationWidget(QtWidgets.QWidget): actions_mapping[action] = self._change_priority menu.addAction(action) - # # temp for testing only !!! - # action = QtWidgets.QAction("Download") - # action_kwarg_map[action] = self._get_action_kwargs(active_site) - # actions_mapping[action] = self._add_site - # menu.addAction(action) - if not actions_mapping: action = QtWidgets.QAction("< No action >") actions_mapping[action] = None @@ -376,11 +385,15 @@ class _SyncRepresentationWidget(QtWidgets.QWidget): def _pause(self, selected_ids=None): log.debug("Pause {}".format(selected_ids)) for representation_id in selected_ids: - item = lib.get_item_by_id(self.model, representation_id) - if item.status not in [lib.STATUS[0], lib.STATUS[1]]: + status = lib.get_value_from_id_by_role(self.model, + representation_id, + STATUS_ROLE) + if status not in [lib.STATUS[0], lib.STATUS[1]]: continue for site_name in [self.model.active_site, self.model.remote_site]: - check_progress = self._get_progress(item, site_name) + check_progress = self._get_progress(self.model, + representation_id, + site_name) if check_progress < 1: self.sync_server.pause_representation(self.model.project, representation_id, @@ -391,11 +404,15 @@ class _SyncRepresentationWidget(QtWidgets.QWidget): def _unpause(self, selected_ids=None): log.debug("UnPause {}".format(selected_ids)) for representation_id in selected_ids: - item = lib.get_item_by_id(self.model, representation_id) - if item.status not in lib.STATUS[3]: + status = lib.get_value_from_id_by_role(self.model, + representation_id, + STATUS_ROLE) + if status not in lib.STATUS[3]: continue for site_name in [self.model.active_site, self.model.remote_site]: - check_progress = self._get_progress(item, site_name) + check_progress = self._get_progress(self.model, + representation_id, + site_name) if check_progress < 1: self.sync_server.unpause_representation( self.model.project, @@ -408,8 +425,11 @@ class _SyncRepresentationWidget(QtWidgets.QWidget): def _add_site(self, selected_ids=None, site_name=None): log.debug("Add site {}:{}".format(selected_ids, site_name)) for representation_id in selected_ids: - item = lib.get_item_by_id(self.model, representation_id) - if item.local_site == site_name or item.remote_site == site_name: + item_local_site = lib.get_value_from_id_by_role( + self.model, representation_id, LOCAL_SITE_NAME_ROLE) + item_remote_site = lib.get_value_from_id_by_role( + self.model, representation_id, REMOTE_SITE_NAME_ROLE) + if site_name in [item_local_site, item_remote_site]: # site already exists skip continue @@ -460,8 +480,8 @@ class _SyncRepresentationWidget(QtWidgets.QWidget): """ log.debug("Reset site {}:{}".format(selected_ids, site_name)) for representation_id in selected_ids: - item = lib.get_item_by_id(self.model, representation_id) - check_progress = self._get_progress(item, site_name, True) + check_progress = self._get_progress(self.model, representation_id, + site_name, True) # do not reset if opposite side is not fully there if check_progress != 1: @@ -469,7 +489,7 @@ class _SyncRepresentationWidget(QtWidgets.QWidget): format(check_progress)) continue - self.sync_server.reset_provider_for_file( + self.sync_server.reset_site_on_representation( self.model.project, representation_id, site_name=site_name, @@ -482,11 +502,8 @@ class _SyncRepresentationWidget(QtWidgets.QWidget): def _open_in_explorer(self, selected_ids=None, site_name=None): log.debug("Open in Explorer {}:{}".format(selected_ids, site_name)) for selected_id in selected_ids: - item = lib.get_item_by_id(self.model, selected_id) - if not item: - return - - fpath = item.path + fpath = lib.get_value_from_id_by_role(self.model, selected_id, + PATH_ROLE) project = self.model.project fpath = self.sync_server.get_local_file_path(project, site_name, @@ -514,10 +531,17 @@ class _SyncRepresentationWidget(QtWidgets.QWidget): self.model.is_editing = True self.table_view.openPersistentEditor(real_index) - def _get_progress(self, item, site_name, opposite=False): + def _get_progress(self, model, representation_id, + site_name, opposite=False): """Returns progress value according to site (side)""" - progress = {'local': item.local_progress, - 'remote': item.remote_progress} + local_progress = lib.get_value_from_id_by_role(model, + representation_id, + LOCAL_PROGRESS_ROLE) + remote_progress = lib.get_value_from_id_by_role(model, + representation_id, + REMOTE_PROGRESS_ROLE) + progress = {'local': local_progress, + 'remote': remote_progress} side = 'remote' if site_name == self.model.active_site: side = 'local' @@ -591,11 +615,11 @@ class SyncRepresentationSummaryWidget(_SyncRepresentationWidget): table_view.viewport().setAttribute(QtCore.Qt.WA_Hover, True) column = table_view.model().get_header_index("local_site") - delegate = delegates.ImageDelegate(self) + delegate = delegates.ImageDelegate(self, side="local") table_view.setItemDelegateForColumn(column, delegate) column = table_view.model().get_header_index("remote_site") - delegate = delegates.ImageDelegate(self) + delegate = delegates.ImageDelegate(self, side="remote") table_view.setItemDelegateForColumn(column, delegate) column = table_view.model().get_header_index("priority") @@ -631,19 +655,21 @@ class SyncRepresentationSummaryWidget(_SyncRepresentationWidget): self.selection_model = self.table_view.selectionModel() self.selection_model.selectionChanged.connect(self._selection_changed) - def _prepare_menu(self, item, is_multi, can_edit): + def _prepare_menu(self, local_progress, remote_progress, + is_multi, can_edit, status=None): action_kwarg_map, actions_mapping, menu = \ - super()._prepare_menu(item, is_multi, can_edit) + super()._prepare_menu(local_progress, remote_progress, + is_multi, can_edit) if can_edit and ( - item.status in [lib.STATUS[0], lib.STATUS[1]] or is_multi): + status in [lib.STATUS[0], lib.STATUS[1]] or is_multi): action = QtWidgets.QAction("Pause in queue") actions_mapping[action] = self._pause # pause handles which site_name it will pause itself action_kwarg_map[action] = {"selected_ids": self._selected_ids} menu.addAction(action) - if can_edit and (item.status == lib.STATUS[3] or is_multi): + if can_edit and (status == lib.STATUS[3] or is_multi): action = QtWidgets.QAction("Unpause in queue") actions_mapping[action] = self._unpause action_kwarg_map[action] = {"selected_ids": self._selected_ids} @@ -753,11 +779,11 @@ class SyncRepresentationDetailWidget(_SyncRepresentationWidget): table_view.verticalHeader().hide() column = model.get_header_index("local_site") - delegate = delegates.ImageDelegate(self) + delegate = delegates.ImageDelegate(self, side="local") table_view.setItemDelegateForColumn(column, delegate) column = model.get_header_index("remote_site") - delegate = delegates.ImageDelegate(self) + delegate = delegates.ImageDelegate(self, side="remote") table_view.setItemDelegateForColumn(column, delegate) if model.can_edit: @@ -815,12 +841,14 @@ class SyncRepresentationDetailWidget(_SyncRepresentationWidget): detail_window.exec() - def _prepare_menu(self, item, is_multi, can_edit): + def _prepare_menu(self, local_progress, remote_progress, + is_multi, can_edit, status=None): """Adds view (and model) dependent actions to default ones""" action_kwarg_map, actions_mapping, menu = \ - super()._prepare_menu(item, is_multi, can_edit) + super()._prepare_menu(local_progress, remote_progress, + is_multi, can_edit, status) - if item.status == lib.STATUS[2] or is_multi: + if status == lib.STATUS[2] or is_multi: action = QtWidgets.QAction("Open error detail") actions_mapping[action] = self._show_detail action_kwarg_map[action] = {"selected_ids": self._selected_ids} @@ -835,8 +863,8 @@ class SyncRepresentationDetailWidget(_SyncRepresentationWidget): redo of upload/download """ for file_id in selected_ids: - item = lib.get_item_by_id(self.model, file_id) - check_progress = self._get_progress(item, site_name, True) + check_progress = self._get_progress(self.model, file_id, + site_name, True) # do not reset if opposite side is not fully there if check_progress != 1: @@ -844,7 +872,7 @@ class SyncRepresentationDetailWidget(_SyncRepresentationWidget): format(check_progress)) continue - self.sync_server.reset_provider_for_file( + self.sync_server.reset_site_on_representation( self.model.project, self.representation_id, site_name=site_name, @@ -895,20 +923,28 @@ class SyncRepresentationErrorWidget(QtWidgets.QWidget): no_errors = True for file_id in selected_ids: - item = lib.get_item_by_id(model, file_id) - if not item.created_dt or not item.sync_dt or not item.error: + created_dt = lib.get_value_from_id_by_role(model, file_id, + LOCAL_DATE_ROLE) + sync_dt = lib.get_value_from_id_by_role(model, file_id, + REMOTE_DATE_ROLE) + errors = lib.get_value_from_id_by_role(model, file_id, + ERROR_ROLE) + if not created_dt or not sync_dt or not errors: continue + tries = lib.get_value_from_id_by_role(model, file_id, + TRIES_ROLE) + no_errors = False - dt = max(item.created_dt, item.sync_dt) + dt = max(created_dt, sync_dt) txts = [] txts.append("{}: {}
".format("Last update date", pretty_timestamp(dt))) txts.append("{}: {}
".format("Retries", - str(item.tries))) + str(tries))) txts.append("{}: {}
".format("Error message", - item.error)) + errors)) text_area = QtWidgets.QTextEdit("\n\n".join(txts)) text_area.setReadOnly(True) @@ -1162,7 +1198,7 @@ class HorizontalHeader(QtWidgets.QHeaderView): column_name = self.model.headerData(column_idx, QtCore.Qt.Horizontal, - lib.HeaderNameRole) + HEADER_NAME_ROLE) button = self.filter_buttons.get(column_name) if not button: continue diff --git a/openpype/modules/default_modules/timers_manager/timers_manager.py b/openpype/modules/default_modules/timers_manager/timers_manager.py index 7687d056f8..1aeccbb958 100644 --- a/openpype/modules/default_modules/timers_manager/timers_manager.py +++ b/openpype/modules/default_modules/timers_manager/timers_manager.py @@ -1,10 +1,7 @@ import os import platform from openpype.modules import OpenPypeModule -from openpype_interfaces import ( - ITimersManager, - ITrayService -) +from openpype_interfaces import ITrayService from avalon.api import AvalonMongoDB diff --git a/openpype/modules/example_addons/example_addon/addon.py b/openpype/modules/example_addons/example_addon/addon.py index 5573e33cc1..50554b1e43 100644 --- a/openpype/modules/example_addons/example_addon/addon.py +++ b/openpype/modules/example_addons/example_addon/addon.py @@ -8,14 +8,15 @@ in global space here until are required or used. """ import os +import click from openpype.modules import ( JsonFilesSettingsDef, - OpenPypeAddOn + OpenPypeAddOn, + ModulesManager ) # Import interface defined by this addon to be able find other addons using it from openpype_interfaces import ( - IExampleInterface, IPluginPaths, ITrayAction ) @@ -75,19 +76,6 @@ class ExampleAddon(OpenPypeAddOn, IPluginPaths, ITrayAction): self._create_dialog() - def connect_with_modules(self, enabled_modules): - """Method where you should find connected modules. - - It is triggered by OpenPype modules manager at the best possible time. - Some addons and modules may required to connect with other modules - before their main logic is executed so changes would require to restart - whole process. - """ - self._connected_modules = [] - for module in enabled_modules: - if isinstance(module, IExampleInterface): - self._connected_modules.append(module) - def _create_dialog(self): # Don't recreate dialog if already exists if self._dialog is not None: @@ -106,8 +94,6 @@ class ExampleAddon(OpenPypeAddOn, IPluginPaths, ITrayAction): """ # Make sure dialog is created self._create_dialog() - # Change value of dialog by current state - self._dialog.set_connected_modules(self.get_connected_modules()) # Show dialog self._dialog.open() @@ -130,3 +116,32 @@ class ExampleAddon(OpenPypeAddOn, IPluginPaths, ITrayAction): return { "publish": [os.path.join(current_dir, "plugins", "publish")] } + + def cli(self, click_group): + click_group.add_command(cli_main) + + +@click.group(ExampleAddon.name, help="Example addon dynamic cli commands.") +def cli_main(): + pass + + +@cli_main.command() +def nothing(): + """Does nothing but print a message.""" + print("You've triggered \"nothing\" command.") + + +@cli_main.command() +def show_dialog(): + """Show ExampleAddon dialog. + + We don't have access to addon directly through cli so we have to create + it again. + """ + from openpype.tools.utils.lib import qt_app_context + + manager = ModulesManager() + example_addon = manager.modules_by_name[ExampleAddon.name] + with qt_app_context(): + example_addon.show_dialog() diff --git a/openpype/modules/example_addons/example_addon/interfaces.py b/openpype/modules/example_addons/example_addon/interfaces.py deleted file mode 100644 index 371536efc7..0000000000 --- a/openpype/modules/example_addons/example_addon/interfaces.py +++ /dev/null @@ -1,28 +0,0 @@ -""" Using interfaces is one way of connecting multiple OpenPype Addons/Modules. - -Interfaces must be in `interfaces.py` file (or folder). Interfaces should not -import module logic or other module in global namespace. That is because -all of them must be imported before all OpenPype AddOns and Modules. - -Ideally they should just define abstract and helper methods. If interface -require any logic or connection it should be defined in module. - -Keep in mind that attributes and methods will be added to other addon -attributes and methods so they should be unique and ideally contain -addon name in it's name. -""" - -from abc import abstractmethod -from openpype.modules import OpenPypeInterface - - -class IExampleInterface(OpenPypeInterface): - """Example interface of addon.""" - _example_module = None - - def get_example_module(self): - return self._example_module - - @abstractmethod - def example_method_of_example_interface(self): - pass diff --git a/openpype/modules/example_addons/example_addon/widgets.py b/openpype/modules/example_addons/example_addon/widgets.py index 0acf238409..c0a0a7e510 100644 --- a/openpype/modules/example_addons/example_addon/widgets.py +++ b/openpype/modules/example_addons/example_addon/widgets.py @@ -9,7 +9,8 @@ class MyExampleDialog(QtWidgets.QDialog): self.setWindowTitle("Connected modules") - label_widget = QtWidgets.QLabel(self) + msg = "This is example dialog of example addon." + label_widget = QtWidgets.QLabel(msg, self) ok_btn = QtWidgets.QPushButton("OK", self) btns_layout = QtWidgets.QHBoxLayout() @@ -28,12 +29,3 @@ class MyExampleDialog(QtWidgets.QDialog): def _on_ok_clicked(self): self.done(1) - - def set_connected_modules(self, connected_modules): - if connected_modules: - message = "\n".join(connected_modules) - else: - message = ( - "Other enabled modules/addons are not using my interface." - ) - self._label_widget.setText(message) diff --git a/openpype/modules/default_modules/interfaces.py b/openpype/modules/interfaces.py similarity index 91% rename from openpype/modules/default_modules/interfaces.py rename to openpype/modules/interfaces.py index a60c5fa606..e6e84a0d42 100644 --- a/openpype/modules/default_modules/interfaces.py +++ b/openpype/modules/interfaces.py @@ -263,3 +263,31 @@ class ITrayService(ITrayModule): """Change icon of an QAction to orange circle.""" if self.menu_action: self.menu_action.setIcon(self.get_icon_idle()) + + +class ISettingsChangeListener(OpenPypeInterface): + """Module has plugin paths to return. + + Expected result is dictionary with keys "publish", "create", "load" or + "actions" and values as list or string. + { + "publish": ["path/to/publish_plugins"] + } + """ + @abstractmethod + def on_system_settings_save( + self, old_value, new_value, changes, new_value_metadata + ): + pass + + @abstractmethod + def on_project_settings_save( + self, old_value, new_value, changes, project_name, new_value_metadata + ): + pass + + @abstractmethod + def on_project_anatomy_save( + self, old_value, new_value, changes, project_name, new_value_metadata + ): + pass diff --git a/openpype/plugins/publish/collect_anatomy_instance_data.py b/openpype/plugins/publish/collect_anatomy_instance_data.py index 4fd657167c..e0eb1618b5 100644 --- a/openpype/plugins/publish/collect_anatomy_instance_data.py +++ b/openpype/plugins/publish/collect_anatomy_instance_data.py @@ -38,6 +38,8 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin): order = pyblish.api.CollectorOrder + 0.49 label = "Collect Anatomy Instance data" + follow_workfile_version = False + def process(self, context): self.log.info("Collecting anatomy data for all instances.") @@ -213,7 +215,10 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin): context_asset_doc = context.data["assetEntity"] for instance in context: - version_number = instance.data.get("version") + if self.follow_workfile_version: + version_number = context.data('version') + else: + version_number = instance.data.get("version") # If version is not specified for instance or context if version_number is None: # TODO we should be able to change default version by studio diff --git a/openpype/plugins/publish/extract_review.py b/openpype/plugins/publish/extract_review.py index 7284483f5f..264b362558 100644 --- a/openpype/plugins/publish/extract_review.py +++ b/openpype/plugins/publish/extract_review.py @@ -649,6 +649,8 @@ class ExtractReview(pyblish.api.InstancePlugin): AssertionError: if more then one collection is obtained. """ + start_frame = int(start_frame) + end_frame = int(end_frame) collections = clique.assemble(files)[0] assert len(collections) == 1, "Multiple collections found." col = collections[0] diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 753ed78083..7ff7466a2a 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -1029,29 +1029,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): """ local_site = 'studio' # default remote_site = None - sync_server_presets = None - - if (instance.context.data["system_settings"] - ["modules"] - ["sync_server"] - ["enabled"]): - sync_server_presets = (instance.context.data["project_settings"] - ["global"] - ["sync_server"]) - - local_site_id = openpype.api.get_local_site_id() - if sync_server_presets["enabled"]: - local_site = sync_server_presets["config"].\ - get("active_site", "studio").strip() - if local_site == 'local': - local_site = local_site_id - - remote_site = sync_server_presets["config"].get("remote_site") - if remote_site == local_site: - remote_site = None - - if remote_site == 'local': - remote_site = local_site_id + always_accesible = [] + sync_project_presets = None rec = { "_id": io.ObjectId(), @@ -1066,12 +1045,93 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): if sites: rec["sites"] = sites else: + system_sync_server_presets = ( + instance.context.data["system_settings"] + ["modules"] + ["sync_server"]) + log.debug("system_sett:: {}".format(system_sync_server_presets)) + + if system_sync_server_presets["enabled"]: + sync_project_presets = ( + instance.context.data["project_settings"] + ["global"] + ["sync_server"]) + + if sync_project_presets and sync_project_presets["enabled"]: + local_site, remote_site = self._get_sites(sync_project_presets) + + always_accesible = sync_project_presets["config"]. \ + get("always_accessible_on", []) + + already_attached_sites = {} meta = {"name": local_site, "created_dt": datetime.now()} rec["sites"] = [meta] + already_attached_sites[meta["name"]] = meta["created_dt"] - if remote_site: + if sync_project_presets and sync_project_presets["enabled"]: + # add remote meta = {"name": remote_site.strip()} rec["sites"].append(meta) + already_attached_sites[meta["name"]] = None + + # add skeleton for site where it should be always synced to + for always_on_site in always_accesible: + if always_on_site not in already_attached_sites.keys(): + meta = {"name": always_on_site.strip()} + rec["sites"].append(meta) + already_attached_sites[meta["name"]] = None + + # add alternative sites + rec = self._add_alternative_sites(system_sync_server_presets, + already_attached_sites, + rec) + + log.debug("final sites:: {}".format(rec["sites"])) + + return rec + + def _get_sites(self, sync_project_presets): + """Returns tuple (local_site, remote_site)""" + local_site_id = openpype.api.get_local_site_id() + local_site = sync_project_presets["config"]. \ + get("active_site", "studio").strip() + + if local_site == 'local': + local_site = local_site_id + + remote_site = sync_project_presets["config"].get("remote_site") + if remote_site == local_site: + remote_site = None + + if remote_site == 'local': + remote_site = local_site_id + + return local_site, remote_site + + def _add_alternative_sites(self, + system_sync_server_presets, + already_attached_sites, + rec): + """Loop through all configured sites and add alternatives. + + See SyncServerModule.handle_alternate_site + """ + conf_sites = system_sync_server_presets.get("sites", {}) + + for site_name, site_info in conf_sites.items(): + alt_sites = set(site_info.get("alternative_sites", [])) + already_attached_keys = list(already_attached_sites.keys()) + for added_site in already_attached_keys: + if added_site in alt_sites: + if site_name in already_attached_keys: + continue + meta = {"name": site_name} + real_created = already_attached_sites[added_site] + # alt site inherits state of 'created_dt' + if real_created: + meta["created_dt"] = real_created + rec["sites"].append(meta) + already_attached_sites[meta["name"]] = real_created return rec diff --git a/openpype/plugins/publish/validate_version.py b/openpype/plugins/publish/validate_version.py index 927e024476..e48ce6e3c3 100644 --- a/openpype/plugins/publish/validate_version.py +++ b/openpype/plugins/publish/validate_version.py @@ -21,8 +21,9 @@ class ValidateVersion(pyblish.api.InstancePlugin): if latest_version is not None: msg = ( - "Version `{0}` that you are trying to publish, already exists" - " in the database. Version in database: `{1}`. Please version " - "up your workfile to a higher version number than: `{1}`." - ).format(version, latest_version) + "Version `{0}` from instance `{1}` that you are trying to" + " publish, already exists in the database. Version in" + " database: `{2}`. Please version up your workfile to a higher" + " version number than: `{2}`." + ).format(version, instance.data["name"], latest_version) assert (int(version) > int(latest_version)), msg diff --git a/openpype/pype_commands.py b/openpype/pype_commands.py index 5fac5cacc7..f0ba9a997e 100644 --- a/openpype/pype_commands.py +++ b/openpype/pype_commands.py @@ -13,7 +13,8 @@ from openpype.lib.remote_publish import ( start_webpublish_log, publish_and_log, fail_batch, - find_variant_key + find_variant_key, + get_task_data ) @@ -41,6 +42,25 @@ class PypeCommands: user_role = "manager" settings.main(user_role) + @staticmethod + def add_modules(click_func): + """Modules/Addons can add their cli commands dynamically.""" + from openpype.modules import ModulesManager + + manager = ModulesManager() + log = PypeLogger.get_logger("AddModulesCLI") + for module in manager.modules: + try: + module.cli(click_func) + + except Exception: + log.warning( + "Failed to add cli command for module \"{}\"".format( + module.name + ) + ) + return click_func + @staticmethod def launch_eventservercli(*args): from openpype_modules.ftrack.ftrack_server.event_server_cli import ( @@ -60,7 +80,7 @@ class PypeCommands: standalonepublish.main() @staticmethod - def publish(paths, targets=None): + def publish(paths, targets=None, gui=False): """Start headless publishing. Publish use json from passed paths argument. @@ -69,20 +89,35 @@ class PypeCommands: paths (list): Paths to jsons. targets (string): What module should be targeted (to choose validator for example) + gui (bool): Show publish UI. Raises: RuntimeError: When there is no path to process. """ - if not any(paths): - raise RuntimeError("No publish paths specified") - + from openpype.modules import ModulesManager from openpype import install, uninstall from openpype.api import Logger + from openpype.tools.utils.host_tools import show_publish + from openpype.tools.utils.lib import qt_app_context # Register target and host import pyblish.api import pyblish.util + log = Logger.get_logger() + + install() + + manager = ModulesManager() + + publish_paths = manager.collect_plugin_paths()["publish"] + + for path in publish_paths: + pyblish.api.register_plugin_path(path) + + if not any(paths): + raise RuntimeError("No publish paths specified") + env = get_app_environments_for_context( os.environ["AVALON_PROJECT"], os.environ["AVALON_ASSET"], @@ -91,35 +126,43 @@ class PypeCommands: ) os.environ.update(env) - log = Logger.get_logger() - - install() - - pyblish.api.register_target("filesequence") pyblish.api.register_host("shell") if targets: for target in targets: + print(f"setting target: {target}") pyblish.api.register_target(target) + else: + pyblish.api.register_target("filesequence") os.environ["OPENPYPE_PUBLISH_DATA"] = os.pathsep.join(paths) log.info("Running publish ...") - # Error exit as soon as any error occurs. - error_format = "Failed {plugin.__name__}: {error} -- {error.traceback}" + plugins = pyblish.api.discover() + print("Using plugins:") + for plugin in plugins: + print(plugin) - for result in pyblish.util.publish_iter(): - if result["error"]: - log.error(error_format.format(**result)) - uninstall() - sys.exit(1) + if gui: + with qt_app_context(): + show_publish() + else: + # Error exit as soon as any error occurs. + error_format = ("Failed {plugin.__name__}: " + "{error} -- {error.traceback}") + + for result in pyblish.util.publish_iter(): + if result["error"]: + log.error(error_format.format(**result)) + # uninstall() + sys.exit(1) log.info("Publish finished.") - uninstall() @staticmethod - def remotepublishfromapp(project, batch_dir, host, user, targets=None): + def remotepublishfromapp(project, batch_dir, host_name, + user, targets=None): """Opens installed variant of 'host' and run remote publish there. Currently implemented and tested for Photoshop where customer @@ -134,67 +177,37 @@ class PypeCommands: Runs publish process as user would, in automatic fashion. """ - SLEEP = 5 # seconds for another loop check for concurrently runs - WAIT_FOR = 300 # seconds to wait for conc. runs - - from openpype import install, uninstall + import pyblish.api from openpype.api import Logger + from openpype.lib import ApplicationManager log = Logger.get_logger() log.info("remotepublishphotoshop command") - install() - - from openpype.lib import ApplicationManager - application_manager = ApplicationManager() - - found_variant_key = find_variant_key(application_manager, host) - - app_name = "{}/{}".format(host, found_variant_key) - - batch_data = None - if batch_dir and os.path.exists(batch_dir): - batch_data = parse_json(os.path.join(batch_dir, "manifest.json")) - - if not batch_data: - raise ValueError( - "Cannot parse batch meta in {} folder".format(batch_dir)) - - asset, task_name, _task_type = get_batch_asset_task_info( - batch_data["context"]) - - # processing from app expects JUST ONE task in batch and 1 workfile - task_dir_name = batch_data["tasks"][0] - task_data = parse_json(os.path.join(batch_dir, task_dir_name, - "manifest.json")) + task_data = get_task_data(batch_dir) workfile_path = os.path.join(batch_dir, - task_dir_name, + task_data["task"], task_data["files"][0]) print("workfile_path {}".format(workfile_path)) - _, batch_id = os.path.split(batch_dir) + batch_id = task_data["batch"] dbcon = get_webpublish_conn() # safer to start logging here, launch might be broken altogether _id = start_webpublish_log(dbcon, batch_id, user) - in_progress = True - slept_times = 0 - while in_progress: - batches_in_progress = list(dbcon.find({ - "status": "in_progress" - })) - if len(batches_in_progress) > 1: - if slept_times * SLEEP >= WAIT_FOR: - fail_batch(_id, batches_in_progress, dbcon) + batches_in_progress = list(dbcon.find({"status": "in_progress"})) + if len(batches_in_progress) > 1: + fail_batch(_id, batches_in_progress, dbcon) + print("Another batch running, probably stuck, ask admin for help") - print("Another batch running, sleeping for a bit") - time.sleep(SLEEP) - slept_times += 1 - else: - in_progress = False + asset, task_name, _ = get_batch_asset_task_info(task_data["context"]) + + application_manager = ApplicationManager() + found_variant_key = find_variant_key(application_manager, host_name) + app_name = "{}/{}".format(host_name, found_variant_key) # must have for proper launch of app env = get_app_environments_for_context( @@ -206,9 +219,21 @@ class PypeCommands: os.environ.update(env) os.environ["OPENPYPE_PUBLISH_DATA"] = batch_dir - os.environ["IS_HEADLESS"] = "true" # must pass identifier to update log lines for a batch os.environ["BATCH_LOG_ID"] = str(_id) + os.environ["HEADLESS_PUBLISH"] = 'true' # to use in app lib + + pyblish.api.register_host(host_name) + if targets: + if isinstance(targets, str): + targets = [targets] + current_targets = os.environ.get("PYBLISH_TARGETS", "").split( + os.pathsep) + for target in targets: + current_targets.append(target) + + os.environ["PYBLISH_TARGETS"] = os.pathsep.join( + set(current_targets)) data = { "last_workfile_path": workfile_path, @@ -220,10 +245,8 @@ class PypeCommands: while launched_app.poll() is None: time.sleep(0.5) - uninstall() - @staticmethod - def remotepublish(project, batch_path, host, user, targets=None): + def remotepublish(project, batch_path, user, targets=None): """Start headless publishing. Used to publish rendered assets, workfiles etc. @@ -235,10 +258,9 @@ class PypeCommands: per call of remotepublish batch_path (str): Path batch folder. Contains subfolders with resources (workfile, another subfolder 'renders' etc.) - targets (string): What module should be targeted - (to choose validator for example) - host (string) user (string): email address for webpublisher + targets (list): Pyblish targets + (to choose validator for example) Raises: RuntimeError: When there is no path to process. @@ -246,21 +268,22 @@ class PypeCommands: if not batch_path: raise RuntimeError("No publish paths specified") - from openpype import install, uninstall - from openpype.api import Logger - # Register target and host import pyblish.api import pyblish.util + import avalon.api + from openpype.hosts.webpublisher import api as webpublisher - log = Logger.get_logger() + log = PypeLogger.get_logger() log.info("remotepublish command") - install() + host_name = "webpublisher" + os.environ["OPENPYPE_PUBLISH_DATA"] = batch_path + os.environ["AVALON_PROJECT"] = project + os.environ["AVALON_APP"] = host_name - if host: - pyblish.api.register_host(host) + pyblish.api.register_host(host_name) if targets: if isinstance(targets, str): @@ -268,13 +291,6 @@ class PypeCommands: for target in targets: pyblish.api.register_target(target) - os.environ["OPENPYPE_PUBLISH_DATA"] = batch_path - os.environ["AVALON_PROJECT"] = project - os.environ["AVALON_APP"] = host - - import avalon.api - from openpype.hosts.webpublisher import api as webpublisher - avalon.api.install(webpublisher) log.info("Running publish ...") @@ -286,7 +302,6 @@ class PypeCommands: publish_and_log(dbcon, _id, log) log.info("Publish finished.") - uninstall() @staticmethod def extractenvironments(output_json_path, project, asset, task, app): @@ -352,3 +367,28 @@ class PypeCommands: cmd = "pytest {} {} {}".format(folder, mark_str, pyargs_str) print("Running {}".format(cmd)) subprocess.run(cmd) + + def syncserver(self, active_site): + """Start running sync_server in background.""" + import signal + os.environ["OPENPYPE_LOCAL_ID"] = active_site + + def signal_handler(sig, frame): + print("You pressed Ctrl+C. Process ended.") + sync_server_module.server_exit() + sys.exit(0) + + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + + from openpype.modules import ModulesManager + + manager = ModulesManager() + sync_server_module = manager.modules_by_name["sync_server"] + + sync_server_module.server_init() + sync_server_module.server_start() + + import time + while True: + time.sleep(1.0) diff --git a/openpype/scripts/otio_burnin.py b/openpype/scripts/otio_burnin.py index 206abfc0b4..68f4728bc7 100644 --- a/openpype/scripts/otio_burnin.py +++ b/openpype/scripts/otio_burnin.py @@ -37,7 +37,7 @@ TIMECODE_KEY = "{timecode}" SOURCE_TIMECODE_KEY = "{source_timecode}" -def _streams(source): +def _get_ffprobe_data(source): """Reimplemented from otio burnins to be able use full path to ffprobe :param str source: source media file :rtype: [{}, ...] @@ -47,7 +47,7 @@ def _streams(source): out = proc.communicate()[0] if proc.returncode != 0: raise RuntimeError("Failed to run: %s" % command) - return json.loads(out)['streams'] + return json.loads(out) def get_fps(str_value): @@ -69,10 +69,10 @@ def get_fps(str_value): return str(fps) -def _prores_codec_args(ffprobe_data, source_ffmpeg_cmd): +def _prores_codec_args(stream_data, source_ffmpeg_cmd): output = [] - tags = ffprobe_data.get("tags") or {} + tags = stream_data.get("tags") or {} encoder = tags.get("encoder") or "" if encoder.endswith("prores_ks"): codec_name = "prores_ks" @@ -85,7 +85,7 @@ def _prores_codec_args(ffprobe_data, source_ffmpeg_cmd): output.extend(["-codec:v", codec_name]) - pix_fmt = ffprobe_data.get("pix_fmt") + pix_fmt = stream_data.get("pix_fmt") if pix_fmt: output.extend(["-pix_fmt", pix_fmt]) @@ -99,7 +99,7 @@ def _prores_codec_args(ffprobe_data, source_ffmpeg_cmd): "ap4h": "4444", "ap4x": "4444xq" } - codec_tag_str = ffprobe_data.get("codec_tag_string") + codec_tag_str = stream_data.get("codec_tag_string") if codec_tag_str: profile = codec_tag_to_profile_map.get(codec_tag_str) if profile: @@ -108,7 +108,7 @@ def _prores_codec_args(ffprobe_data, source_ffmpeg_cmd): return output -def _h264_codec_args(ffprobe_data, source_ffmpeg_cmd): +def _h264_codec_args(stream_data, source_ffmpeg_cmd): output = ["-codec:v", "h264"] # Use arguments from source if are available source arguments @@ -125,7 +125,7 @@ def _h264_codec_args(ffprobe_data, source_ffmpeg_cmd): if arg in copy_args: output.extend([arg, args[idx + 1]]) - pix_fmt = ffprobe_data.get("pix_fmt") + pix_fmt = stream_data.get("pix_fmt") if pix_fmt: output.extend(["-pix_fmt", pix_fmt]) @@ -135,11 +135,11 @@ def _h264_codec_args(ffprobe_data, source_ffmpeg_cmd): return output -def _dnxhd_codec_args(ffprobe_data, source_ffmpeg_cmd): +def _dnxhd_codec_args(stream_data, source_ffmpeg_cmd): output = ["-codec:v", "dnxhd"] # Use source profile (profiles in metadata are not usable in args directly) - profile = ffprobe_data.get("profile") or "" + profile = stream_data.get("profile") or "" # Lower profile and replace space with underscore cleaned_profile = profile.lower().replace(" ", "_") dnx_profiles = { @@ -153,7 +153,7 @@ def _dnxhd_codec_args(ffprobe_data, source_ffmpeg_cmd): if cleaned_profile in dnx_profiles: output.extend(["-profile:v", cleaned_profile]) - pix_fmt = ffprobe_data.get("pix_fmt") + pix_fmt = stream_data.get("pix_fmt") if pix_fmt: output.extend(["-pix_fmt", pix_fmt]) @@ -162,28 +162,29 @@ def _dnxhd_codec_args(ffprobe_data, source_ffmpeg_cmd): def get_codec_args(ffprobe_data, source_ffmpeg_cmd): - codec_name = ffprobe_data.get("codec_name") + stream_data = ffprobe_data["streams"][0] + codec_name = stream_data.get("codec_name") # Codec "prores" if codec_name == "prores": - return _prores_codec_args(ffprobe_data, source_ffmpeg_cmd) + return _prores_codec_args(stream_data, source_ffmpeg_cmd) # Codec "h264" if codec_name == "h264": - return _h264_codec_args(ffprobe_data, source_ffmpeg_cmd) + return _h264_codec_args(stream_data, source_ffmpeg_cmd) # Coded DNxHD if codec_name == "dnxhd": - return _dnxhd_codec_args(ffprobe_data, source_ffmpeg_cmd) + return _dnxhd_codec_args(stream_data, source_ffmpeg_cmd) output = [] if codec_name: output.extend(["-codec:v", codec_name]) - bit_rate = ffprobe_data.get("bit_rate") + bit_rate = stream_data.get("bit_rate") if bit_rate: output.extend(["-b:v", bit_rate]) - pix_fmt = ffprobe_data.get("pix_fmt") + pix_fmt = stream_data.get("pix_fmt") if pix_fmt: output.extend(["-pix_fmt", pix_fmt]) @@ -244,15 +245,16 @@ class ModifiedBurnins(ffmpeg_burnins.Burnins): } def __init__( - self, source, streams=None, options_init=None, first_frame=None + self, source, ffprobe_data=None, options_init=None, first_frame=None ): - if not streams: - streams = _streams(source) + if not ffprobe_data: + ffprobe_data = _get_ffprobe_data(source) + self.ffprobe_data = ffprobe_data self.first_frame = first_frame self.input_args = [] - super().__init__(source, streams) + super().__init__(source, ffprobe_data["streams"]) if options_init: self.options_init.update(options_init) @@ -492,8 +494,6 @@ def example(input_path, output_path): 'bg_opacity': 0.5, 'font_size': 52 } - # First frame in burnin - start_frame = 2000 # Options init sets burnin look burnin = ModifiedBurnins(input_path, options_init=options_init) # Static text @@ -564,11 +564,11 @@ def burnins_from_data( "shot": "sh0010" } """ - streams = None + ffprobe_data = None if full_input_path: - streams = _streams(full_input_path) + ffprobe_data = _get_ffprobe_data(full_input_path) - burnin = ModifiedBurnins(input_path, streams, options, first_frame) + burnin = ModifiedBurnins(input_path, ffprobe_data, options, first_frame) frame_start = data.get("frame_start") frame_end = data.get("frame_end") @@ -595,6 +595,14 @@ def burnins_from_data( if source_timecode is None: source_timecode = stream.get("tags", {}).get("timecode") + if source_timecode is None: + # Use "format" key from ffprobe data + # - this is used e.g. in mxf extension + input_format = burnin.ffprobe_data.get("format") or {} + source_timecode = input_format.get("timecode") + if source_timecode is None: + source_timecode = input_format.get("tags", {}).get("timecode") + if source_timecode is not None: data[SOURCE_TIMECODE_KEY[1:-1]] = SOURCE_TIMECODE_KEY @@ -684,8 +692,9 @@ def burnins_from_data( ffmpeg_args.append("-g 1") else: - ffprobe_data = burnin._streams[0] - ffmpeg_args.extend(get_codec_args(ffprobe_data, source_ffmpeg_cmd)) + ffmpeg_args.extend( + get_codec_args(burnin.ffprobe_data, source_ffmpeg_cmd) + ) # Use group one (same as `-intra` argument, which is deprecated) ffmpeg_args_str = " ".join(ffmpeg_args) diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 45c1a59d17..9622f85a8e 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -1,5 +1,8 @@ { "publish": { + "CollectAnatomyInstanceData": { + "follow_workfile_version": false + }, "ValidateEditorialAssetName": { "enabled": true, "optional": false @@ -315,10 +318,11 @@ }, "project_folder_structure": "{\"__project_root__\": {\"prod\": {}, \"resources\": {\"footage\": {\"plates\": {}, \"offline\": {}}, \"audio\": {}, \"art_dept\": {}}, \"editorial\": {}, \"assets[ftrack.Library]\": {\"characters[ftrack]\": {}, \"locations[ftrack]\": {}}, \"shots[ftrack.Sequence]\": {\"scripts\": {}, \"editorial[ftrack.Folder]\": {}}}}", "sync_server": { - "enabled": true, + "enabled": false, "config": { "retry_cnt": "3", "loop_delay": "60", + "always_accessible_on": [], "active_site": "studio", "remote_site": "studio" }, diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index e4570fcdbc..689d6418ba 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -8,16 +8,10 @@ "yetiRig": "ma" }, "maya-dirmap": { - "enabled": true, + "enabled": false, "paths": { - "source-path": [ - "foo1", - "foo2" - ], - "destination-path": [ - "bar1", - "bar2" - ] + "source-path": [], + "destination-path": [] } }, "scriptsmenu": { diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index e3c7834e4a..069994d0e8 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -8,6 +8,13 @@ "build_workfile": "ctrl+alt+b" } }, + "nuke-dirmap": { + "enabled": false, + "paths": { + "source-path": [], + "destination-path": [] + } + }, "create": { "CreateWriteRender": { "fpath_template": "{work}/renders/nuke/{subset}/{subset}.{frame}.{ext}", @@ -130,8 +137,7 @@ }, "LoadClip": { "enabled": true, - "_representations": [ - ], + "_representations": [], "node_name_template": "{class_name}_{ext}" } }, diff --git a/openpype/settings/defaults/project_settings/photoshop.json b/openpype/settings/defaults/project_settings/photoshop.json index eb9f96e348..0c24c943ec 100644 --- a/openpype/settings/defaults/project_settings/photoshop.json +++ b/openpype/settings/defaults/project_settings/photoshop.json @@ -30,8 +30,7 @@ }, "ExtractReview": { "jpg_options": { - "tags": [ - ] + "tags": [] }, "mov_options": { "tags": [ diff --git a/openpype/settings/defaults/system_settings/modules.json b/openpype/settings/defaults/system_settings/modules.json index beb1eb4f24..9c72598ff2 100644 --- a/openpype/settings/defaults/system_settings/modules.json +++ b/openpype/settings/defaults/system_settings/modules.json @@ -167,6 +167,16 @@ "ffmpeg": 48 } }, + "royalrender": { + "enabled": false, + "rr_paths": { + "default": { + "windows": "", + "darwin": "", + "linux": "" + } + } + }, "log_viewer": { "enabled": true }, diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 0cb8827991..5f1c172f31 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -762,6 +762,17 @@ class SyncServerProviders(DictConditionalEntity): enum_children = [] for provider_code, configurables in system_settings_schema.items(): + # any site could be exposed or vendorized by different site + # eg studio site content could be mapped on sftp site, single file + # accessible via 2 different protocols (sites) + configurables.append( + { + "type": "list", + "key": "alternative_sites", + "label": "Alternative sites", + "object_type": "text" + } + ) label = provider_code_to_label.get(provider_code) or provider_code enum_children.append({ diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json b/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json index e0b21f4037..22cb8a4ea3 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json @@ -46,6 +46,39 @@ } ] }, + { + "type": "dict", + "collapsible": true, + "checkbox_key": "enabled", + "key": "nuke-dirmap", + "label": "Nuke Directory Mapping", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "dict", + "key": "paths", + "children": [ + { + "type": "list", + "object_type": "text", + "key": "source-path", + "label": "Source Path" + }, + { + "type": "list", + "object_type": "text", + "key": "destination-path", + "label": "Destination Path" + } + ] + } + ] + }, { "type": "dict", "collapsible": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json index f00bf78fe4..ca388de60c 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json @@ -67,7 +67,17 @@ "type": "list", "key": "color_code", "label": "Color codes for layers", - "object_type": "text" + "type": "enum", + "multiselection": true, + "enum_items": [ + { "red": "red" }, + { "orange": "orange" }, + { "yellowColor": "yellow" }, + { "grain": "green" }, + { "blue": "blue" }, + { "violet": "violet" }, + { "gray": "gray" } + ] }, { "type": "list", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_syncserver.json b/openpype/settings/entities/schemas/projects_schema/schema_project_syncserver.json index 3211babd43..7e1b0114f5 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_syncserver.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_syncserver.json @@ -26,113 +26,33 @@ "key": "loop_delay", "label": "Loop Delay" }, + { + "type": "list", + "key": "always_accessible_on", + "label": "Always accessible on sites", + "object_type": "text" + }, + { + "type": "splitter" + }, { "type": "text", "key": "active_site", - "label": "Active Site" + "label": "User Default Active Site" }, { "type": "text", "key": "remote_site", - "label": "Remote Site" + "label": "User Default Remote Site" } ] }, { - "type": "dict-modifiable", + "type": "sync-server-sites", "collapsible": true, "key": "sites", "label": "Sites", - "collapsible_key": false, - "object_type": { - "type": "dict", - "children": [ - { - "type": "dict", - "key": "gdrive", - "label": "Google Drive", - "collapsible": true, - "children": [ - { - "type": "path", - "key": "credentials_url", - "label": "Credentials url", - "multiplatform": true - } - ] - }, - { - "type": "dict", - "key": "dropbox", - "label": "Dropbox", - "collapsible": true, - "children": [ - { - "type": "text", - "key": "token", - "label": "Access Token" - }, - { - "type": "text", - "key": "team_folder_name", - "label": "Team Folder Name" - }, - { - "type": "text", - "key": "acting_as_member", - "label": "Acting As Member" - } - ] - }, - { - "type": "dict", - "key": "sftp", - "label": "SFTP", - "collapsible": true, - "children": [ - { - "type": "text", - "key": "sftp_host", - "label": "SFTP host" - }, - { - "type": "number", - "key": "sftp_port", - "label": "SFTP port" - }, - { - "type": "text", - "key": "sftp_user", - "label": "SFTP user" - }, - { - "type": "text", - "key": "sftp_pass", - "label": "SFTP pass" - }, - { - "type": "path", - "key": "sftp_key", - "label": "SFTP user ssh key", - "multiplatform": true - }, - { - "type": "text", - "key": "sftp_key_pass", - "label": "SFTP user ssh key password" - } - ] - }, - { - "type": "dict-modifiable", - "key": "root", - "label": "Roots", - "collapsable": false, - "collapsable_key": false, - "object_type": "text" - } - ] - } + "collapsible_key": false } ] } diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json index c50f383f02..375f0c26da 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json @@ -4,6 +4,20 @@ "key": "publish", "label": "Publish plugins", "children": [ + { + "type": "dict", + "collapsible": true, + "key": "CollectAnatomyInstanceData", + "label": "Collect Anatomy Instance Data", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "follow_workfile_version", + "label": "Follow workfile version" + } + ] + }, { "type": "dict", "collapsible": true, diff --git a/openpype/settings/entities/schemas/system_schema/schema_modules.json b/openpype/settings/entities/schemas/system_schema/schema_modules.json index a2b31772e9..aab1eea750 100644 --- a/openpype/settings/entities/schemas/system_schema/schema_modules.json +++ b/openpype/settings/entities/schemas/system_schema/schema_modules.json @@ -180,6 +180,31 @@ } ] }, + { + "type": "dict", + "key": "royalrender", + "label": "Royal Render", + "require_restart": true, + "collapsible": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "dict-modifiable", + "object_type": { + "type": "path", + "multiplatform": true + }, + "key": "rr_paths", + "required_keys": ["default"], + "label": "Royal Render Root Paths" + } + ] + }, { "type": "dict", "key": "log_viewer", diff --git a/openpype/style/images/transparent.png b/openpype/style/images/transparent.png index 0f2e143b39..bf9514e88e 100644 Binary files a/openpype/style/images/transparent.png and b/openpype/style/images/transparent.png differ diff --git a/openpype/style/pyqt5_resources.py b/openpype/style/pyqt5_resources.py index 1ac4547ac2..55d4e3efcc 100644 --- a/openpype/style/pyqt5_resources.py +++ b/openpype/style/pyqt5_resources.py @@ -8,132 +8,8 @@ from PyQt5 import QtCore + qt_resource_data = b"\ -\x00\x00\x01\xfc\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x30\x00\x00\x00\x30\x08\x06\x00\x00\x00\x57\x02\xf9\x87\ -\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\ -\x01\x00\x9a\x9c\x18\x00\x00\x01\xae\x49\x44\x41\x54\x68\x81\xed\ -\x9a\xbd\x4a\x03\x41\x14\x46\x4f\x36\x1b\xb0\xd0\x4a\xf1\x01\x14\ -\xab\x68\x91\xc6\x2a\x58\xb8\x16\xb2\x88\x76\x0b\xe9\x7d\x01\x1f\ -\x40\x8b\xf8\x00\xbe\x80\x85\x9d\x30\x62\xa3\x32\x55\xc6\x42\xf2\ -\x02\x42\x92\x46\x83\x7d\x48\x27\x36\xf9\x01\x8b\xdd\x40\x12\xb2\ -\x89\x6b\x7e\x66\x37\xcc\xe9\x76\xef\x14\xdf\x59\xee\x0c\x0b\x77\ -\x52\x04\x38\xae\xb7\x01\x5c\x01\x79\x60\x17\xc8\x10\x2f\xda\x40\ -\x05\x28\x03\x45\x25\x45\x13\x20\x05\xe0\xb8\xde\x21\x70\x0f\x6c\ -\x6a\x8b\x17\x8d\x06\x50\x50\x52\xbc\xa6\x82\x2f\x5f\x25\x39\xe1\ -\x7b\x34\x80\xac\x85\xdf\x36\x49\x0b\x0f\x7e\xe6\x4b\x1b\xbf\xe7\ -\x87\xe9\x2e\x38\xcc\x5f\x49\x0f\x3d\xe7\x6d\xfc\x0d\xdb\x4f\x57\ -\x49\x61\x2f\x28\x50\x24\x1c\xd7\xeb\x30\x28\xb1\x67\x11\xbf\xd3\ -\x26\x0a\x19\x4b\x77\x82\x69\x31\x02\xba\x31\x02\xba\x31\x02\xba\ -\x31\x02\xba\x31\x02\xba\x31\x02\xba\x99\xf8\xdb\xec\xb8\xde\x11\ -\xb0\x0f\xac\xce\x3f\xce\x00\x1d\xa0\x06\x3c\x2b\x29\x7e\xc2\x16\ -\x85\x0a\x38\xae\x67\x03\x8f\xc0\xe9\xec\xb3\x45\xa2\xee\xb8\xde\ -\xb1\x92\xe2\x73\x54\x71\x5c\x0b\x5d\xa0\x3f\x3c\xc0\x36\x70\x1b\ -\x56\x1c\x27\x70\x32\xfb\x2c\xff\xe6\xc0\x71\xbd\xb5\x51\x85\xc4\ -\x6f\xe2\x71\x02\x2f\x0b\x4b\x31\x99\x37\x25\xc5\xf7\xa8\xc2\x38\ -\x81\x1b\xe0\x69\x3e\x79\x22\x51\x07\xce\xc3\x8a\xa1\xa7\x90\x92\ -\xa2\x03\x9c\x25\xf6\x18\xed\xa1\xa4\x28\x01\xa5\x19\x06\x9b\x29\ -\x4b\xbd\x89\x13\x81\x11\xd0\x8d\x11\xd0\x8d\x11\xd0\x8d\x11\xd0\ -\x8d\x11\xd0\xcd\x52\x08\xb4\x75\x87\x98\x82\x96\x8d\x3f\xbe\xcf\ -\xf5\xbd\x4c\x07\xd3\xc0\x38\x32\x3c\x66\xad\xd8\xf8\x77\x0f\x72\ -\x13\x16\xc6\x95\xb2\x05\x14\xf1\xc7\xf6\x49\xa3\x01\x5c\x5b\xc1\ -\xad\x8f\x02\xc9\x92\xe8\x5d\xf6\x68\xa6\x01\xbe\x3e\xaa\x5f\x5b\ -\x3b\xd9\x3b\x60\x05\x7f\xf0\xbd\x4e\xfc\xda\xa8\x05\xbc\x03\x0f\ -\x80\xa7\xa4\xa8\x01\xfc\x02\x51\xab\x5c\x8a\x3f\xde\xe3\x59\x00\ -\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x00\xa6\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x09\x00\x00\x00\x06\x08\x04\x00\x00\x00\xbb\xce\x7c\x4e\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x02\x62\x4b\x47\x44\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09\x70\ -\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ -\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdc\x08\x17\x08\x15\x3b\xdc\ -\x3b\x0c\x9b\x00\x00\x00\x2a\x49\x44\x41\x54\x08\xd7\x63\x60\xc0\ -\x00\x8c\x0c\x0c\x73\x3e\x20\x0b\xa4\x08\x30\x32\x30\x20\x0b\xa6\ -\x08\x30\x30\x30\x42\x98\x10\xc1\x14\x01\x14\x13\x50\xb5\xa3\x01\ -\x00\xc6\xb9\x07\x90\x5d\x66\x1f\x83\x00\x00\x00\x00\x49\x45\x4e\ -\x44\xae\x42\x60\x82\ -\x00\x00\x03\xfb\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x30\x00\x00\x00\x30\x08\x06\x00\x00\x00\x57\x02\xf9\x87\ -\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\ -\x01\x00\x9a\x9c\x18\x00\x00\x03\xad\x49\x44\x41\x54\x68\x81\xed\ -\x9a\x4f\xa8\x15\x55\x1c\xc7\x3f\xf7\xbe\xab\xf2\x20\xa3\x22\x6d\ -\xa1\x7c\x21\x09\x2a\x4b\x28\xda\x44\xb9\x88\x52\x4c\xcc\x6a\x51\ -\xf9\xa4\x85\xf1\xa0\x16\x51\xe4\x2e\x10\x74\x51\x44\x8b\x16\x15\ -\x25\x81\xb5\x28\x04\xad\xc0\xe2\x61\x64\xf6\x97\xe0\x45\x10\xb4\ -\xa9\x27\x44\x45\xc4\x17\xa2\x12\x35\x0a\x2a\xff\x3c\xab\xc5\x99\ -\x5b\xd7\x79\x33\x73\xce\xdc\x6b\x6f\xee\x05\x3f\xbb\x39\xf3\x3b\ -\xbf\xf3\xfb\x9d\x33\xe7\x9c\xef\x9c\x99\x16\x19\xb6\x2f\x06\x76\ -\x00\xab\x81\xab\x81\x05\x0c\x17\xa7\x80\x19\x60\x1a\x78\x4c\xd2\ -\x11\x80\x16\x80\xed\x9b\x81\xbd\xc0\xd2\xc6\xc2\xab\xc7\x61\x60\ -\xb3\xa4\x0f\x5b\x59\xcf\x1f\x62\x74\x82\xef\x72\x18\xb8\xaa\x4d\ -\x78\x6c\x46\x2d\x78\x08\x31\x6f\xef\x10\x9e\xf9\x3c\xa7\xe7\x39\ -\x98\x54\xc6\x72\xd7\xab\x3b\x84\x09\xdb\xcb\x69\x49\x9d\x79\x0a\ -\xa8\x16\xb6\x67\x39\x33\x89\x55\x6d\x86\x6f\xb5\xa9\xc3\x82\x76\ -\xd3\x11\x0c\xca\xb9\x04\x9a\xe6\x5c\x02\xff\x07\xb6\xef\xb6\xbd\ -\xd7\xf6\xda\x98\xed\xd0\x25\x60\xfb\x11\xe0\x75\x60\x02\x38\x68\ -\x7b\xa7\xed\xd2\x95\x72\xa8\x12\xb0\x3d\x01\x3c\xdd\x53\xd4\x02\ -\x1e\x04\x5e\x2c\xab\x33\x34\x1b\x96\xed\x35\xc0\x2b\x64\x02\x33\ -\xc7\x16\xdb\x5f\x16\xd5\x1b\x8a\x11\xb0\x7d\x1d\xf0\x06\xb0\xb0\ -\xc2\x6c\x7d\x51\x61\xe3\x09\xd8\xbe\x0c\x78\x1b\x58\x1c\x31\x7d\ -\xa9\xa8\xb0\xd1\x04\x6c\x5f\x02\x1c\x24\xae\x86\x9f\x92\xf4\x6a\ -\xd1\x8d\xc6\x12\xb0\xbd\x18\x38\x00\xac\x88\x98\xee\x06\x1e\x2d\ -\xbb\xd9\x48\x02\xb6\x17\x02\x6f\x02\xd7\x46\x4c\xdf\x01\x26\x25\ -\xfd\x5d\x66\x30\xef\x09\xd8\x6e\x13\x7a\xf5\x96\x88\xe9\x67\xc0\ -\x5d\x92\x66\xab\x8c\x9a\x18\x81\x67\x80\x7b\x22\x36\x5f\x03\x1b\ -\x24\xfd\x1e\x73\x96\x94\x80\xed\x56\xd6\x73\x03\x61\x7b\x1b\xf0\ -\x70\xc4\xec\x47\x60\x5d\xf7\xd4\x21\x46\x65\x50\x59\xe0\x5b\x81\ -\x3f\x81\x19\xdb\xab\x92\x22\x2d\xf6\x35\x09\x3c\x11\x31\xfb\x15\ -\xb8\x55\xd2\xf7\xa9\x7e\x4b\x13\xc8\xf4\xc7\x14\x61\x6b\x5f\x04\ -\x5c\x09\xbc\x6f\xfb\xf2\x54\xe7\x3d\xbe\x36\x02\xbb\x22\x66\x27\ -\x80\x3b\x24\x7d\x51\xc7\x77\xd5\x08\x3c\x0f\x6c\xcc\x95\x2d\x05\ -\x3e\xb0\x7d\x69\x6a\x03\xb6\x6f\x00\x5e\x63\xee\x0b\x79\x2f\x7f\ -\x01\xf7\x4a\xfa\x38\xd5\x6f\x97\xc2\x04\xb2\x1e\x7b\xa0\xa4\xce\ -\x32\xc2\x48\x2c\x8b\x39\xb7\xbd\x12\xd8\x0f\x8c\x47\x4c\x1f\x92\ -\xb4\x2f\xe6\xaf\x88\xb2\x11\xb8\x28\x52\x6f\x05\x21\x89\x25\x65\ -\x06\xb6\x97\x13\x76\xd9\x98\xaf\xc7\x25\xbd\x10\xb1\x29\xa5\x2c\ -\x81\x3d\x84\x75\xb8\x8a\x2b\x80\x77\x6d\x5f\x90\xbf\x61\xfb\x42\ -\x42\xf0\xcb\x23\x3e\x76\x49\xda\x11\x8d\xb2\x82\xc2\x04\x24\x9d\ -\x22\xa8\xbf\x42\x09\xdb\xc3\x35\xc0\x01\xdb\xe7\x75\x0b\x6c\x8f\ -\x13\x1e\x9b\x95\x91\xba\x53\x04\xad\x3f\x10\xa5\x93\x58\xd2\x31\ -\x60\x2d\xf0\x4d\xc4\xc7\xf5\xc0\x7e\xdb\xe3\xb6\xc7\x08\x13\xf6\ -\xc6\x48\x9d\x69\x60\x42\xd2\xc0\x27\x80\x95\xfb\x80\xa4\x9f\x81\ -\x35\x80\x23\x7e\x6e\x02\xf6\x11\x96\xca\xfc\xca\x95\x67\x06\xb8\ -\x5d\xd2\xf1\xc4\x18\x2b\x89\xee\xae\x92\x4c\xd0\x2d\x3f\x45\x4c\ -\xd7\x03\x93\x11\x1b\x13\x36\xaa\x5f\xd2\xc2\x8b\x93\x24\x0f\x24\ -\x7d\x4b\x18\x89\xa3\x03\xb4\x75\x8c\x20\x11\x7e\x18\xc0\xc7\x1c\ -\x92\xf5\x8d\xa4\x43\xc0\x3a\xe0\xb7\x3e\xda\xf9\x03\xb8\x4d\xd2\ -\x57\x7d\xd4\xad\xa4\x96\x40\x93\xf4\x39\xb0\x21\x0b\x28\x95\x59\ -\x60\x93\xa4\x4f\xeb\xb4\x95\x4a\x6d\x85\x29\x69\x1a\xb8\x93\xa0\ -\x5d\x52\xb8\x5f\xd2\x5b\x75\xdb\x49\xa5\x2f\x89\x2c\xe9\x3d\x60\ -\x13\xa1\x77\xab\xd8\x26\xe9\xe5\x7e\xda\x48\xa5\x6f\x8d\x2f\x69\ -\x0a\xd8\x42\x10\x62\x45\x3c\x27\xe9\xc9\x7e\xfd\xa7\x32\xd0\x4b\ -\x8a\xa4\x3d\xc0\x7d\xcc\x9d\x13\x3b\x81\xad\x83\xf8\x4e\x65\xe0\ -\x93\x39\x49\xbb\x6d\x7f\x42\xd8\xc0\xce\x07\x3e\xca\xe6\xc9\xbc\ -\x70\x56\x8e\x16\x25\x7d\x07\x3c\x7b\x36\x7c\xd5\xa5\x4d\xf8\x02\ -\x3e\xaa\x9c\xec\x10\xb4\x49\xef\xf9\xcc\x58\xf6\x35\x70\x18\xc9\ -\xbf\xd5\xcd\x74\x08\xca\x30\x7f\xc0\x54\xf5\xfa\x37\x4c\x4c\x8f\ -\xfe\xaf\x06\xd9\xf9\xcb\xe6\xac\x60\x54\xe8\xfe\xec\x71\xe4\xdf\ -\x8f\x09\xd9\x48\x6c\xe7\xbf\xdf\x6d\xaa\xce\xea\x9b\xe0\x24\x67\ -\xfe\x6e\x73\x14\xe0\x1f\x0a\x43\x12\x6b\x4f\xfd\x3f\x13\x00\x00\ -\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x00\x9f\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x09\x00\x00\x00\x06\x08\x04\x00\x00\x00\xbb\xce\x7c\x4e\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x02\x62\x4b\x47\x44\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09\x70\ -\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ -\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdc\x08\x17\x08\x14\x1f\xf9\ -\x23\xd9\x0b\x00\x00\x00\x23\x49\x44\x41\x54\x08\xd7\x63\x60\xc0\ -\x0d\xe6\x7c\x80\xb1\x18\x91\x05\x52\x04\xe0\x42\x08\x15\x29\x02\ -\x0c\x0c\x8c\xc8\x02\x08\x95\x68\x00\x00\xac\xac\x07\x90\x4e\x65\ -\x34\xac\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x07\xad\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ @@ -259,243 +135,6 @@ qt_resource_data = b"\ \x5e\x78\xa2\x9e\x0e\xa7\x20\x74\x47\x39\x1d\xf6\xe1\x95\x2b\xd6\ \xb1\x44\x8e\x0e\xcb\x58\xf0\x0f\x52\x8a\x79\x18\xdc\xe2\x02\x70\ \x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x01\xef\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x30\x00\x00\x00\x30\x08\x06\x00\x00\x00\x57\x02\xf9\x87\ -\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\ -\x01\x00\x9a\x9c\x18\x00\x00\x01\xa1\x49\x44\x41\x54\x68\x81\xed\ -\x9a\xbf\x4e\xc2\x50\x14\x87\xbf\x96\xe2\xa4\x9b\x71\xbc\x8b\x1b\ -\xea\xc0\xe2\x44\x1c\x8c\x83\x83\xd1\x81\x89\x84\xd1\x17\xf0\x01\ -\x70\xc0\x07\xf0\x05\x1c\x49\x9c\xba\xa8\x23\x71\x30\x3c\x02\x76\ -\x92\xe5\x8e\x04\x27\xe3\xc2\x9f\xc4\xa1\x6d\x04\x42\x8b\x95\xc2\ -\xa1\xe4\x7e\x5b\x7b\xee\xf0\xfb\x9a\x7b\x6f\x9a\x9c\x63\x11\x50\ -\x75\xbd\x5d\xe0\x16\x28\x01\x87\x40\x9e\xf5\x62\x00\xb4\x81\x16\ -\x50\x6f\x94\x0b\x3d\x00\x0b\xa0\xea\x7a\xa7\xc0\x23\xb0\x27\x16\ -\x2f\x19\x5d\xa0\xd2\x28\x17\x5e\xad\xe0\xcb\xbf\x93\x9d\xf0\x21\ -\x5d\xe0\xc0\xc6\xdf\x36\x59\x0b\x0f\x7e\xe6\x9a\x83\xbf\xe7\xa7\ -\x19\xad\x38\xcc\x5f\xc9\x4d\x3d\x97\x1c\xfc\x03\x3b\xce\xa8\x51\ -\x2e\x38\x2b\x0a\x94\x88\xaa\xeb\x0d\x99\x94\x38\xb2\x59\xbf\xdb\ -\x26\x09\x79\x5b\x3a\xc1\xa2\x18\x01\x69\x8c\x80\x34\x46\x40\x1a\ -\x23\x20\x8d\x11\x90\xc6\x08\x48\x33\xf7\xb7\x59\x6b\x7d\x06\x1c\ -\x03\xdb\xcb\x8f\x33\xc1\x10\xf0\x80\x67\xa5\xd4\x77\xd4\xa2\x48\ -\x01\xad\xb5\x03\xb8\xc0\x65\xfa\xd9\x12\xd1\xd1\x5a\x9f\x2b\xa5\ -\x3e\x66\x15\xe3\xb6\xd0\x0d\xf2\xe1\x01\xf6\x81\x87\xa8\x62\x9c\ -\xc0\x45\xfa\x59\xfe\xcd\x89\xd6\x7a\x67\x56\x21\xf3\x87\x38\x4e\ -\xe0\x65\x65\x29\xe6\xf3\xa6\x94\xfa\x9a\x55\x88\x13\xb8\x07\x9e\ -\x96\x93\x27\x11\x1d\xe0\x3a\xaa\x18\x79\x0b\x29\xa5\x86\xc0\x55\ -\x66\xaf\xd1\x10\xa5\x54\x13\x68\xa6\x18\x2c\x55\x36\xfa\x10\x67\ -\x02\x23\x20\x8d\x11\x90\xc6\x08\x48\x63\x04\xa4\x31\x02\xd2\x6c\ -\x84\xc0\x40\x3a\xc4\x02\xf4\x1d\xfc\xf6\x7d\x71\xec\x65\x2e\xe8\ -\x06\xae\x23\xd3\x6d\xd6\xb6\x83\x3f\x7b\x50\x9c\xb3\x70\x5d\x69\ -\xd9\x40\x1d\xbf\x6d\x9f\x35\xba\xc0\x9d\x1d\x4c\x7d\x54\xc8\x96\ -\x44\x38\xec\xd1\xb3\xc2\x37\xc1\xd0\x47\x8d\xdf\x71\x9b\x2d\xa1\ -\x70\x51\xf4\x99\x1c\xb7\xf9\x04\xf8\x01\x6f\xed\x58\x63\x2d\xfd\ -\xb2\x59\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x03\xff\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x30\x00\x00\x00\x30\x08\x06\x00\x00\x00\x57\x02\xf9\x87\ -\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\ -\x01\x00\x9a\x9c\x18\x00\x00\x03\xb1\x49\x44\x41\x54\x68\x81\xed\ -\x9a\x4f\x68\x1e\x45\x18\xc6\x7f\x9b\x26\x85\x82\x15\x15\xab\x42\ -\xcb\x03\x06\x05\xa9\x0a\x8a\xb7\x52\x3c\xd4\x96\xaa\xb5\xe0\x41\ -\xad\xc5\x43\x25\xa0\x07\x51\xcc\x4d\x28\xb4\x07\x45\x3c\x78\xb0\ -\xa0\x52\x50\x0f\x8a\x50\xf5\x50\xa5\x28\xc6\xbf\xa8\x34\x20\x08\ -\x9e\x4c\x41\xc5\x83\x3c\x20\xd2\x90\x2a\x0a\xfe\x69\x92\x3a\x1e\ -\x66\x8d\x5f\xd6\xdd\x9d\xfd\xf6\x8b\xd9\x2f\xd0\xdf\xed\x9b\x79\ -\xe7\x9d\xe7\x9d\xd9\x99\x79\x67\xbf\xcd\xc8\x09\x21\x5c\x0a\x1c\ -\x06\xb6\x03\xd7\x01\x63\x0c\x17\x0b\xc0\x0c\x30\x0d\x3c\x9e\x65\ -\xd9\xdc\x52\x4d\x08\x61\x47\x08\xe1\x74\x58\x3b\x9c\x0e\x21\xec\ -\x00\xc8\x42\x1c\xf9\x53\xc0\x65\x5d\x0c\xeb\x00\xcc\x02\xd7\x8e\ -\x10\x1f\x9b\xb5\x26\x1e\xa2\xe6\x43\xa3\xc4\x67\xbe\xc8\xb9\x55\ -\x16\xd3\x94\x75\x85\xdf\xdb\xb3\x10\xc2\x3c\xcb\x17\xec\xb9\x2c\ -\xcb\x46\x57\x51\x54\x63\x42\x08\x8b\x2c\x0f\x62\x61\x84\xe1\xdb\ -\x6d\xfa\x61\x6c\xa4\x6b\x05\x83\x72\x3e\x80\xae\x39\x1f\xc0\xff\ -\x81\xed\xbb\x6d\xbf\x66\x7b\x57\xca\x36\x0b\x21\x84\x42\x59\xa7\ -\xdb\xa8\xed\x47\x81\x23\xf9\xcf\x00\x1c\x05\x26\x25\x2d\x94\x6c\ -\xa3\xc3\x35\x03\xb6\xef\x05\x9e\xe9\x29\xca\x80\x87\x80\x17\xab\ -\xda\x0c\xcd\x81\x65\x7b\x27\xf0\x0a\x51\x74\x91\x03\xb6\xbf\x2a\ -\x6b\x37\x14\x33\x60\xfb\x26\xe0\x4d\x60\x7d\x8d\xd9\x6d\x65\x85\ -\x9d\x07\x60\xfb\x2a\xe0\x5d\x60\x63\xc2\xf4\xa5\xb2\xc2\x4e\x03\ -\xb0\x7d\x39\xf0\x3e\xe9\x6c\xf8\x69\x49\xaf\x97\x55\x74\x16\x80\ -\xed\x8d\xc0\x14\x30\x9e\x30\x7d\x15\x78\xac\xaa\xb2\x93\x00\x6c\ -\xaf\x07\xde\x02\x6e\x4c\x98\xbe\x07\x4c\x48\x2a\x6e\xf5\x4b\xac\ -\x7a\x00\xb6\x47\x88\xa3\x7a\x4b\xc2\xf4\x0b\xe0\x2e\x49\x8b\x75\ -\x46\x5d\xcc\xc0\x11\xe0\x9e\x84\xcd\xb7\xc0\x1e\x49\xbf\xa5\x9c\ -\x35\x0a\xc0\x76\x96\x8f\xdc\x40\xd8\x3e\x08\x3c\x92\x30\xfb\x11\ -\xd8\x2d\x69\x2e\x61\x07\x24\x02\xc8\x85\x4f\x02\x7f\x00\x33\xb6\ -\xaf\x6f\xa4\xb4\xdc\xd7\x04\xf0\x64\xc2\xec\x17\xe0\x56\x49\xdf\ -\x37\xf5\x5b\x99\x0b\xd9\x1e\x03\x8e\x03\x7b\x7b\xea\x66\x81\x9b\ -\x25\x7d\xd3\xb4\x03\x00\xdb\x7b\x89\x8b\xb6\x78\xa7\xed\xe5\x2c\ -\x71\xe4\x3f\xab\x32\xe8\x37\x17\x7a\x8e\xe5\xe2\x21\xee\xd7\x1f\ -\xdb\xbe\xb2\x56\x71\x0f\xb6\xb7\x01\x6f\x14\x3b\x2e\xf0\x17\x70\ -\x5f\x9d\xf8\x2a\x4a\x03\xc8\x47\xec\xc1\x8a\x36\x9b\x81\x8f\x6c\ -\x6f\x4e\x39\xb7\xbd\x15\x78\x1b\xd8\x90\x30\x7d\x58\xd2\xf1\x94\ -\xbf\x32\xaa\x66\xe0\x92\x44\xbb\x71\x62\x10\x9b\xaa\x0c\x6c\x6f\ -\x21\x9e\xb2\x29\x5f\x4f\x48\x3a\x9a\xb0\xa9\xa4\x2a\x80\x63\xc4\ -\x7d\xb8\x8e\x6b\x80\x0f\x6c\x5f\x54\xac\xb0\x7d\x31\x51\xfc\x96\ -\x84\x8f\x17\x24\x1d\x4e\xaa\xac\xa1\x34\x00\x49\x0b\xc4\xec\xaf\ -\x34\x85\xed\xe1\x06\x60\xca\xf6\x05\xff\x14\xd8\xde\x40\x7c\x6c\ -\xb6\x26\xda\x9e\x20\xe6\xfa\x03\x51\x7b\x23\xcb\x93\xad\x93\xc0\ -\xd5\x09\x3f\x9f\x02\xb7\x03\xf3\xc4\xdd\xa6\xb8\xf8\x8b\x4c\x03\ -\xbb\x24\xfd\xd9\x8f\xd8\xb2\x5d\x28\x79\xa5\xb4\x2d\x62\x10\x4a\ -\xf8\x9f\x22\x1e\x42\x13\x09\xbb\x19\xe2\x56\xfc\x73\x23\xd5\x3d\ -\xb4\x0a\x00\x96\x72\xf6\x93\xc0\x15\xfd\x76\x5a\xc0\xc0\x36\x49\ -\x3f\xb4\x69\xdc\xfa\x4e\x2c\xe9\x3b\x60\x27\x70\xa6\x4d\xc7\x39\ -\x3f\x11\x0f\xaa\x56\xe2\xab\x68\x9c\xdf\x48\x3a\x05\xec\x06\x7e\ -\x6d\xd1\xcf\xef\xc0\x1d\x92\xbe\x6e\xd1\xb6\x96\xbe\x12\x34\x49\ -\x5f\x02\x7b\x72\x41\x4d\x59\x04\xf6\x49\xfa\xbc\x9f\xbe\x9a\xd2\ -\x77\x86\x29\x69\x1a\xb8\x93\x98\xbb\x34\xe1\x01\x49\xef\xf4\xdb\ -\x4f\x53\x5a\xa5\xc8\x92\x3e\x04\xf6\x11\x47\xb7\x8e\x83\x92\x5e\ -\x6e\xd3\x47\x53\x5a\xe7\xf8\x92\x4e\x00\x07\x88\x89\x58\x19\xcf\ -\x4a\x7a\xaa\xad\xff\xa6\x0c\x74\x49\x91\x74\x0c\xb8\x9f\xff\xae\ -\x89\xe7\x81\xc9\x41\x7c\x37\x65\x45\xde\x8d\xda\x1e\x27\x9e\xbe\ -\x17\x02\x9f\xe4\xeb\x64\xc5\x69\x7d\x90\x0d\x0b\x55\x07\xd9\x42\ -\x37\x72\x56\x84\xf9\x51\x62\x6e\xd2\xfb\x7e\x66\x5d\x1e\xe9\x30\ -\x52\xbc\xd5\xcd\x8c\x12\x33\xc3\xe2\x0b\xa6\xba\xeb\xdf\x30\x31\ -\xbd\xf6\x3f\x35\xc8\xbf\xfa\xd8\x9f\x17\xac\x15\x66\x81\xfd\x59\ -\x96\xcd\x2d\xfd\x99\x90\xcf\xc4\x21\xfe\xfd\xdc\xa6\xee\x5d\x7d\ -\x17\xcc\xb3\xfc\x73\x9b\x33\x00\x7f\x03\xd9\x1a\xfb\xdb\xbb\xa7\ -\x8f\x07\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x04\x12\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x30\x00\x00\x00\x30\x08\x06\x00\x00\x00\x57\x02\xf9\x87\ -\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\ -\x01\x00\x9a\x9c\x18\x00\x00\x03\xc4\x49\x44\x41\x54\x68\x81\xed\ -\x9a\x5f\x88\x94\x55\x18\xc6\x7f\x33\x3b\x1a\x0b\x19\x15\x66\x17\ -\xca\x03\x49\x50\x4d\x09\x4a\x37\x51\x5e\x44\x29\x26\x66\x05\x5b\ -\xb9\xd2\x82\xb1\x50\x17\x91\x24\x74\x11\x08\x7a\xa1\x44\x17\x5d\ -\x54\x94\x04\xd6\x45\xe1\xa2\x15\x4c\xb1\x18\x99\xfd\x25\xd8\x08\ -\x82\x6e\x6a\x5d\xa4\x22\xe2\x81\xa8\x96\xd5\x28\xe8\x9f\xae\xd5\ -\xc5\xf9\xb6\xc6\xd9\xf9\xbe\x73\x66\xc6\x76\x66\xc0\xdf\xdd\x9c\ -\xef\x3d\xef\x79\x9f\x73\xe6\x9c\xf3\x7e\xdf\x39\x25\x32\x46\x6a\ -\x53\x4b\x81\xdd\xc0\x5a\xe0\x3a\x60\x11\xbd\xc5\x69\x60\x12\x98\ -\x00\xf6\x8c\x0d\x55\x67\x00\x4a\x00\x23\xb5\xa9\x5b\x80\x43\xc0\ -\xb2\xae\x85\xd7\x1a\xd3\xc0\xd6\xb1\xa1\xea\x07\xa5\xac\xe7\x8f\ -\xd1\x3f\xc1\xcf\x31\x0d\x5c\x5b\x26\xfc\x6d\xfa\x2d\x78\x08\x31\ -\xef\xaa\x10\xfe\xf3\x8d\x9c\x59\xe0\x60\x52\x19\x68\xf8\xbd\xb6\ -\x42\x98\xb0\xf5\x9c\x19\x1b\xaa\x56\x16\x28\xa0\x96\x18\xa9\x4d\ -\xcd\x72\xb6\x88\x55\x65\x7a\x6f\xb5\x69\x85\x45\xe5\x6e\x47\xd0\ -\x29\xe7\x05\x74\x9b\xf3\x02\xfe\x0f\x6c\xdf\x63\xfb\x90\xed\xf5\ -\x31\xdb\x9e\x13\x60\xfb\x11\xe0\x35\x60\x18\x38\x6a\x7b\x9f\xed\ -\xdc\x95\xb2\xa7\x04\xd8\x1e\x06\x9e\xaa\x2b\x2a\x01\x0f\x01\x2f\ -\xe4\xd5\xe9\x19\x01\xb6\xd7\x01\x2f\x93\x25\x98\x0d\x6c\xb3\xfd\ -\x68\xb3\x7a\x3d\x21\xc0\xf6\xf5\xc0\xeb\xc0\xe2\x02\xb3\x8d\xcd\ -\x0a\xbb\x2e\xc0\xf6\x95\xc0\x5b\xc0\x92\x88\xe9\x8b\xcd\x0a\xbb\ -\x2a\xc0\xf6\xe5\xc0\x51\xe2\xd9\xf0\x93\x92\x5e\x69\xf6\xa0\x6b\ -\x02\x6c\x2f\x01\x8e\x00\x2b\x23\xa6\x07\x80\xc7\xf2\x1e\x76\x45\ -\x80\xed\xc5\xc0\x1b\xc0\x9a\x88\xe9\xdb\xc0\xa8\xa4\xbf\xf3\x0c\ -\x16\x5c\x80\xed\x32\xa1\x57\x6f\x8d\x98\x7e\x0a\xdc\x2d\x69\xb6\ -\xc8\xa8\x1b\x23\xf0\x34\x70\x6f\xc4\xe6\x4b\x60\x93\xa4\x5f\x63\ -\xce\x92\x04\xd8\x2e\x65\x3d\xd7\x11\xb6\x77\x02\xdb\x23\x66\xdf\ -\x03\x1b\x24\xcd\xa4\xf8\x2c\x0c\x2a\x0b\x7c\x07\xf0\x3b\x30\x69\ -\x7b\x55\x52\xa4\xcd\x7d\x8d\x02\x8f\x47\xcc\x7e\x06\x6e\x93\xf4\ -\x6d\xaa\xdf\x5c\x01\x59\xfe\x31\x4e\xd8\xda\x2f\x00\xae\x01\xde\ -\xb3\x7d\x55\xaa\xf3\x3a\x5f\x9b\x81\xfd\x11\xb3\x3f\x81\x3b\x25\ -\x7d\xde\x8a\xef\xa2\x11\x78\x0e\xd8\xdc\x50\xb6\x0c\x78\xdf\xf6\ -\x15\xa9\x0d\xd8\xbe\x11\x78\x95\xf9\x2f\xe4\xf5\xfc\x05\xdc\x27\ -\xe9\xa3\x54\xbf\x73\x34\x15\x90\xf5\xd8\x83\x39\x75\x96\x13\x46\ -\x62\x79\xcc\xb9\xed\x2a\x70\x18\x18\x8c\x98\x3e\x2c\xa9\x16\xf3\ -\xd7\x8c\xbc\x11\xb8\x34\x52\x6f\x25\x41\xc4\x65\x79\x06\xb6\x57\ -\x10\x76\xd9\x98\xaf\xbd\x92\x9e\x8f\xd8\xe4\x92\x27\xe0\x20\x61\ -\x1d\x2e\xe2\x6a\xe0\x1d\xdb\x17\x37\x3e\xb0\x7d\x09\x21\xf8\x15\ -\x11\x1f\xfb\x25\xed\x8e\x46\x59\x40\x53\x01\x92\x4e\x13\xb2\xbf\ -\x2f\x22\xf5\x57\x03\x47\x6c\x5f\x38\x57\x60\x7b\x90\xf0\xb7\xa9\ -\x46\xea\x8e\x13\x72\xfd\x8e\xc8\x9d\xc4\x92\x4e\x02\xeb\x81\xaf\ -\x22\x3e\x6e\x00\x0e\xdb\x1e\xb4\x3d\x40\x98\xb0\x37\x45\xea\x4c\ -\x00\xc3\x92\x3a\xfe\x02\x58\xb8\x0f\x48\xfa\x11\x58\x07\x38\xe2\ -\xe7\x66\xa0\x46\x58\x2a\x1b\x57\xae\x46\x26\x81\x3b\x24\xfd\x91\ -\x18\x63\x21\xd1\xdd\x55\x92\x09\x79\xcb\x0f\x11\xd3\x8d\xc0\x68\ -\xc4\xc6\x84\x8d\xea\xa7\xb4\xf0\xe2\x24\xa5\x07\x92\xbe\x26\x8c\ -\xc4\x89\x0e\xda\x3a\x49\x48\x11\xbe\xeb\xc0\xc7\x3c\x92\xf3\x1b\ -\x49\xc7\x80\x0d\xc0\x2f\x6d\xb4\xf3\x1b\x70\xbb\xa4\xe3\x6d\xd4\ -\x2d\xa4\xa5\x04\x4d\xd2\x67\xc0\xa6\x2c\xa0\x54\x66\x81\x2d\x92\ -\x3e\x69\xa5\xad\x54\x5a\xce\x30\x25\x4d\x00\x77\x11\x72\x97\x14\ -\x1e\x90\xf4\x66\xab\xed\xa4\xd2\x56\x8a\x2c\xe9\x5d\x60\x0b\xa1\ -\x77\x8b\xd8\x29\xe9\xa5\x76\xda\x48\xa5\xed\x1c\x5f\xd2\x38\xb0\ -\x8d\x90\x88\x35\xe3\x59\x49\x4f\xb4\xeb\x3f\x95\x8e\x5e\x52\x24\ -\x1d\x04\xee\x67\xfe\x9c\xd8\x07\xec\xe8\xc4\x77\x2a\x1d\x1f\x25\ -\x49\x3a\x60\xfb\x63\xc2\x06\x76\x11\xf0\x61\x36\x4f\x16\x84\x73\ -\x72\x16\x26\xe9\x1b\xe0\x99\x73\xe1\xab\x55\xca\x84\x13\xf0\x7e\ -\xe5\x54\x85\x90\x9b\xd4\x7f\x9f\x19\xc8\x4e\x03\x7b\x91\xc6\xb7\ -\xba\xc9\x0a\x21\x33\x6c\xfc\xc0\x54\xf4\xfa\xd7\x4b\x4c\x94\x81\ -\x3d\x84\x63\xfb\x7e\x63\x1a\xd8\x5b\xce\x6e\x7d\x6c\xa5\xbf\x44\ -\xcc\x5d\xf6\x98\xf9\xf7\x30\x21\xbb\xf4\xb1\x8b\xff\xae\xdb\x14\ -\x7d\xab\xef\x06\xa7\x38\xfb\xba\xcd\x09\x80\x7f\x00\xc4\x1e\x10\ -\x29\x33\x5b\x85\xf7\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\ -\x82\ -\x00\x00\x01\x69\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x30\x00\x00\x00\x30\x08\x06\x00\x00\x00\x57\x02\xf9\x87\ -\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\ -\x01\x00\x9a\x9c\x18\x00\x00\x01\x1b\x49\x44\x41\x54\x68\x81\xed\ -\xda\xb1\x6d\xc2\x40\x14\x87\xf1\xcf\xc7\x91\x09\x50\x86\x70\x42\ -\x41\x4f\xc5\x0a\xae\x90\xbc\x0a\x29\xc8\x2a\x96\x52\x79\x05\x2a\ -\x46\x20\x1e\xc2\x82\x05\x48\x90\x52\xdc\x59\x01\x4b\x51\x62\x45\ -\xe2\xef\x93\xde\xaf\xb3\x45\xf1\x3e\xcb\xa6\xb9\x97\x11\x95\x75\ -\x33\x03\x5e\x80\x25\xf0\x0c\x4c\x19\x97\x0f\xe0\x00\xec\x81\x6d\ -\x55\xe4\x47\x80\x0c\xa0\xac\x9b\x15\xf0\x06\x3c\xca\xc6\x1b\xa6\ -\x05\xd6\x55\x91\xef\xb2\xf8\xe4\xdf\x49\x67\xf8\x4e\x0b\x3c\x39\ -\xc2\x6b\x93\xda\xf0\x10\x66\xde\x78\xc2\x3b\xdf\x77\xb9\xf3\x30\ -\x7f\x35\xe9\x5d\x2f\x3d\xe1\x83\xbd\x76\xa9\x8a\xdc\xdf\x69\xa0\ -\x41\xca\xba\xf9\xe4\x36\x62\xee\x18\xdf\xbf\xcd\x10\x53\xa7\x9e\ -\xe0\xbf\x2c\x40\xcd\x02\xd4\x2c\x40\xcd\x02\xd4\x2c\x40\xcd\x02\ -\xd4\x2c\x40\xcd\x02\xd4\x2c\x40\xcd\x02\xd4\x2c\x40\xcd\x02\xd4\ -\x2c\x40\xcd\x02\xd4\x2c\x40\xcd\x02\xd4\x2c\x40\xcd\x11\x4e\xc0\ -\x53\x75\xf6\x84\xe3\xfb\xc5\xd5\xcd\x49\x3c\x0d\x1c\xa3\xfe\x31\ -\xeb\xc1\x13\x76\x0f\x16\xbf\xfc\x70\xac\xf6\x0e\xd8\x12\x8e\xed\ -\x53\xd3\x02\xaf\x2e\x6e\x7d\xac\x49\x2b\xa2\x5b\xf6\x38\x66\xdd\ -\x9d\xb8\xf4\xb1\xe1\x7b\xdd\xe6\x41\x34\xdc\x4f\xce\xdc\xae\xdb\ -\x9c\x00\xbe\x00\x9f\xf6\x34\x3e\x36\x4f\x37\x81\x00\x00\x00\x00\ -\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x01\xdc\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x30\x00\x00\x00\x30\x08\x06\x00\x00\x00\x57\x02\xf9\x87\ -\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\ -\x01\x00\x9a\x9c\x18\x00\x00\x01\x8e\x49\x44\x41\x54\x68\x81\xed\ -\x9a\xaf\x4e\xc3\x50\x14\x87\xbf\x6e\x1d\x0a\x1c\x41\x1e\x83\x04\ -\xc4\x0c\x6a\x41\x10\x04\x82\x80\x9e\xe7\x05\x78\x80\x21\x78\x01\ -\x5e\x00\x8f\xc2\x00\x72\x41\x90\x3d\xc2\x40\x31\x73\xe4\x32\x14\ -\xc1\xec\x4f\x82\x68\x1b\xb6\x65\xed\x28\xeb\x76\xda\xe5\x7e\xae\ -\xf7\x5c\xf1\xfb\xda\x7b\x6f\x9a\xdc\xe3\x11\xa2\xaa\xdb\xc0\x35\ -\x50\x03\xf6\x81\x0a\xf9\x62\x00\xb4\x81\x16\x70\x23\x22\x3d\x00\ -\x0f\x40\x55\x8f\x81\x7b\x60\xc7\x2c\x5e\x3a\xba\x40\x5d\x44\x5e\ -\xbc\xf0\xcd\xbf\x51\x9c\xf0\x11\x5d\x60\xaf\x44\xb0\x6c\x8a\x16\ -\x1e\x82\xcc\x0d\x9f\x60\xcd\x4f\x33\x5a\x71\x98\xbf\x52\x9e\x7a\ -\xae\xf9\x04\x1b\x76\x9c\x91\x88\xf8\x2b\x0a\x94\x0a\x55\x1d\x32\ -\x29\x71\x50\x22\x7f\xa7\x4d\x1a\x2a\x25\xeb\x04\x8b\xe2\x04\xac\ -\x71\x02\xd6\x38\x01\x6b\x9c\x80\x35\x4e\xc0\x1a\x27\x60\xcd\xdc\ -\xdf\x66\x55\x3d\x01\x0e\x81\xcd\xe5\xc7\x99\x60\x08\xbc\x03\x4f\ -\x22\xf2\x1d\x37\x29\x56\x40\x55\x7d\xe0\x01\x38\xcf\x3e\x5b\x2a\ -\x3a\xaa\x7a\x2a\x22\x1f\xb3\x8a\x49\x4b\xe8\x0a\xfb\xf0\x00\xbb\ -\xc0\x5d\x5c\x31\x49\xe0\x2c\xfb\x2c\xff\xe6\x48\x55\xb7\x66\x15\ -\x0a\xbf\x89\x93\x04\x9e\x57\x96\x62\x3e\xaf\x22\xf2\x35\xab\x90\ -\x24\x70\x0b\x3c\x2e\x27\x4f\x2a\x3a\xc0\x65\x5c\x31\xf6\x14\x12\ -\x91\x21\x70\x51\xd8\x63\x34\x42\x44\x9a\x40\x33\xc3\x60\x99\xb2\ -\xd6\x9b\xb8\x10\x38\x01\x6b\x9c\x80\x35\x4e\xc0\x1a\x27\x60\x8d\ -\x13\xb0\x66\x2d\x04\x06\xd6\x21\x16\xa0\xef\x13\x5c\xdf\x57\xc7\ -\x06\xcb\xe1\x6d\x60\x1e\x99\xbe\x66\x6d\xfb\x04\xbd\x07\xd5\x39\ -\x13\xf3\x4a\xab\xf8\xad\x06\x61\xd7\x47\x3d\x1c\x28\x0a\x51\xb3\ -\x47\xcf\x8b\x46\xc2\x2f\xd1\xe0\xb7\xdd\x66\xc3\x28\x5c\x1c\x7d\ -\x26\xdb\x6d\x3e\x01\x7e\x00\x25\xf8\x5a\x43\x55\x4e\x3a\x7f\x00\ -\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x00\xa6\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x09\x00\x00\x00\x06\x08\x04\x00\x00\x00\xbb\xce\x7c\x4e\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x02\x62\x4b\x47\x44\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09\x70\ -\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ -\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdc\x08\x17\x08\x15\x3b\xdc\ -\x3b\x0c\x9b\x00\x00\x00\x2a\x49\x44\x41\x54\x08\xd7\x63\x60\xc0\ -\x00\x8c\x0c\x0c\x73\x3e\x20\x0b\xa4\x08\x30\x32\x30\x20\x0b\xa6\ -\x08\x30\x30\x30\x42\x98\x10\xc1\x14\x01\x14\x13\x50\xb5\xa3\x01\ -\x00\xc6\xb9\x07\x90\x5d\x66\x1f\x83\x00\x00\x00\x00\x49\x45\x4e\ -\x44\xae\x42\x60\x82\ \x00\x00\x07\x06\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ @@ -611,19 +250,71 @@ qt_resource_data = b"\ \x85\x77\xf8\x5b\xe9\xf0\xbb\x9f\xfa\xd2\x83\x39\xdc\xa3\x5b\xf3\ \x19\x2e\xa8\x89\xb5\x30\xf7\x43\xa0\x00\x00\x00\x00\x49\x45\x4e\ \x44\xae\x42\x60\x82\ -\x00\x00\x00\xa6\ +\x00\x00\x01\xdc\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x06\x00\x00\x00\x09\x08\x04\x00\x00\x00\xbb\x93\x95\x16\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x02\x62\x4b\x47\x44\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09\x70\ -\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ -\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdc\x08\x17\x14\x1f\x20\xb9\ -\x8d\x77\xe9\x00\x00\x00\x2a\x49\x44\x41\x54\x08\xd7\x63\x60\xc0\ -\x06\xe6\x7c\x60\x60\x60\x42\x30\xa1\x1c\x08\x93\x81\x81\x09\xc1\ -\x64\x60\x60\x62\x60\x48\x11\x40\xe2\x20\x73\x19\x90\x8d\x40\x02\ -\x00\x23\xed\x08\xaf\x64\x9f\x0f\x15\x00\x00\x00\x00\x49\x45\x4e\ -\x44\xae\x42\x60\x82\ +\x00\x00\x30\x00\x00\x00\x30\x08\x06\x00\x00\x00\x57\x02\xf9\x87\ +\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\ +\x01\x00\x9a\x9c\x18\x00\x00\x01\x8e\x49\x44\x41\x54\x68\x81\xed\ +\x9a\xaf\x4e\xc3\x50\x14\x87\xbf\x6e\x1d\x0a\x1c\x41\x1e\x83\x04\ +\xc4\x0c\x6a\x41\x10\x04\x82\x80\x9e\xe7\x05\x78\x80\x21\x78\x01\ +\x5e\x00\x8f\xc2\x00\x72\x41\x90\x3d\xc2\x40\x31\x73\xe4\x32\x14\ +\xc1\xec\x4f\x82\x68\x1b\xb6\x65\xed\x28\xeb\x76\xda\xe5\x7e\xae\ +\xf7\x5c\xf1\xfb\xda\x7b\x6f\x9a\xdc\xe3\x11\xa2\xaa\xdb\xc0\x35\ +\x50\x03\xf6\x81\x0a\xf9\x62\x00\xb4\x81\x16\x70\x23\x22\x3d\x00\ +\x0f\x40\x55\x8f\x81\x7b\x60\xc7\x2c\x5e\x3a\xba\x40\x5d\x44\x5e\ +\xbc\xf0\xcd\xbf\x51\x9c\xf0\x11\x5d\x60\xaf\x44\xb0\x6c\x8a\x16\ +\x1e\x82\xcc\x0d\x9f\x60\xcd\x4f\x33\x5a\x71\x98\xbf\x52\x9e\x7a\ +\xae\xf9\x04\x1b\x76\x9c\x91\x88\xf8\x2b\x0a\x94\x0a\x55\x1d\x32\ +\x29\x71\x50\x22\x7f\xa7\x4d\x1a\x2a\x25\xeb\x04\x8b\xe2\x04\xac\ +\x71\x02\xd6\x38\x01\x6b\x9c\x80\x35\x4e\xc0\x1a\x27\x60\xcd\xdc\ +\xdf\x66\x55\x3d\x01\x0e\x81\xcd\xe5\xc7\x99\x60\x08\xbc\x03\x4f\ +\x22\xf2\x1d\x37\x29\x56\x40\x55\x7d\xe0\x01\x38\xcf\x3e\x5b\x2a\ +\x3a\xaa\x7a\x2a\x22\x1f\xb3\x8a\x49\x4b\xe8\x0a\xfb\xf0\x00\xbb\ +\xc0\x5d\x5c\x31\x49\xe0\x2c\xfb\x2c\xff\xe6\x48\x55\xb7\x66\x15\ +\x0a\xbf\x89\x93\x04\x9e\x57\x96\x62\x3e\xaf\x22\xf2\x35\xab\x90\ +\x24\x70\x0b\x3c\x2e\x27\x4f\x2a\x3a\xc0\x65\x5c\x31\xf6\x14\x12\ +\x91\x21\x70\x51\xd8\x63\x34\x42\x44\x9a\x40\x33\xc3\x60\x99\xb2\ +\xd6\x9b\xb8\x10\x38\x01\x6b\x9c\x80\x35\x4e\xc0\x1a\x27\x60\x8d\ +\x13\xb0\x66\x2d\x04\x06\xd6\x21\x16\xa0\xef\x13\x5c\xdf\x57\xc7\ +\x06\xcb\xe1\x6d\x60\x1e\x99\xbe\x66\x6d\xfb\x04\xbd\x07\xd5\x39\ +\x13\xf3\x4a\xab\xf8\xad\x06\x61\xd7\x47\x3d\x1c\x28\x0a\x51\xb3\ +\x47\xcf\x8b\x46\xc2\x2f\xd1\xe0\xb7\xdd\x66\xc3\x28\x5c\x1c\x7d\ +\x26\xdb\x6d\x3e\x01\x7e\x00\x25\xf8\x5a\x43\x55\x4e\x3a\x7f\x00\ +\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x01\xef\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x30\x00\x00\x00\x30\x08\x06\x00\x00\x00\x57\x02\xf9\x87\ +\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\ +\x01\x00\x9a\x9c\x18\x00\x00\x01\xa1\x49\x44\x41\x54\x68\x81\xed\ +\x9a\xbf\x4e\xc2\x50\x14\x87\xbf\x96\xe2\xa4\x9b\x71\xbc\x8b\x1b\ +\xea\xc0\xe2\x44\x1c\x8c\x83\x83\xd1\x81\x89\x84\xd1\x17\xf0\x01\ +\x70\xc0\x07\xf0\x05\x1c\x49\x9c\xba\xa8\x23\x71\x30\x3c\x02\x76\ +\x92\xe5\x8e\x04\x27\xe3\xc2\x9f\xc4\xa1\x6d\x04\x42\x8b\x95\xc2\ +\xa1\xe4\x7e\x5b\x7b\xee\xf0\xfb\x9a\x7b\x6f\x9a\x9c\x63\x11\x50\ +\x75\xbd\x5d\xe0\x16\x28\x01\x87\x40\x9e\xf5\x62\x00\xb4\x81\x16\ +\x50\x6f\x94\x0b\x3d\x00\x0b\xa0\xea\x7a\xa7\xc0\x23\xb0\x27\x16\ +\x2f\x19\x5d\xa0\xd2\x28\x17\x5e\xad\xe0\xcb\xbf\x93\x9d\xf0\x21\ +\x5d\xe0\xc0\xc6\xdf\x36\x59\x0b\x0f\x7e\xe6\x9a\x83\xbf\xe7\xa7\ +\x19\xad\x38\xcc\x5f\xc9\x4d\x3d\x97\x1c\xfc\x03\x3b\xce\xa8\x51\ +\x2e\x38\x2b\x0a\x94\x88\xaa\xeb\x0d\x99\x94\x38\xb2\x59\xbf\xdb\ +\x26\x09\x79\x5b\x3a\xc1\xa2\x18\x01\x69\x8c\x80\x34\x46\x40\x1a\ +\x23\x20\x8d\x11\x90\xc6\x08\x48\x33\xf7\xb7\x59\x6b\x7d\x06\x1c\ +\x03\xdb\xcb\x8f\x33\xc1\x10\xf0\x80\x67\xa5\xd4\x77\xd4\xa2\x48\ +\x01\xad\xb5\x03\xb8\xc0\x65\xfa\xd9\x12\xd1\xd1\x5a\x9f\x2b\xa5\ +\x3e\x66\x15\xe3\xb6\xd0\x0d\xf2\xe1\x01\xf6\x81\x87\xa8\x62\x9c\ +\xc0\x45\xfa\x59\xfe\xcd\x89\xd6\x7a\x67\x56\x21\xf3\x87\x38\x4e\ +\xe0\x65\x65\x29\xe6\xf3\xa6\x94\xfa\x9a\x55\x88\x13\xb8\x07\x9e\ +\x96\x93\x27\x11\x1d\xe0\x3a\xaa\x18\x79\x0b\x29\xa5\x86\xc0\x55\ +\x66\xaf\xd1\x10\xa5\x54\x13\x68\xa6\x18\x2c\x55\x36\xfa\x10\x67\ +\x02\x23\x20\x8d\x11\x90\xc6\x08\x48\x63\x04\xa4\x31\x02\xd2\x6c\ +\x84\xc0\x40\x3a\xc4\x02\xf4\x1d\xfc\xf6\x7d\x71\xec\x65\x2e\xe8\ +\x06\xae\x23\xd3\x6d\xd6\xb6\x83\x3f\x7b\x50\x9c\xb3\x70\x5d\x69\ +\xd9\x40\x1d\xbf\x6d\x9f\x35\xba\xc0\x9d\x1d\x4c\x7d\x54\xc8\x96\ +\x44\x38\xec\xd1\xb3\xc2\x37\xc1\xd0\x47\x8d\xdf\x71\x9b\x2d\xa1\ +\x70\x51\xf4\x99\x1c\xb7\xf9\x04\xf8\x01\x6f\xed\x58\x63\x2d\xfd\ +\xb2\x59\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x00\xa5\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ @@ -637,6 +328,82 @@ qt_resource_data = b"\ \x32\x30\x30\x42\x98\x10\x41\x46\x43\x14\x13\x50\xb5\xa3\x01\x00\ \xd6\x10\x07\xd2\x2f\x48\xdf\x4a\x00\x00\x00\x00\x49\x45\x4e\x44\ \xae\x42\x60\x82\ +\x00\x00\x00\xa0\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x06\x00\x00\x00\x09\x08\x04\x00\x00\x00\xbb\x93\x95\x16\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x02\x62\x4b\x47\x44\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09\x70\ +\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ +\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdc\x08\x17\x14\x1c\x1f\x24\ +\xc6\x09\x17\x00\x00\x00\x24\x49\x44\x41\x54\x08\xd7\x63\x60\x40\ +\x05\xff\xcf\xc3\x58\x4c\xc8\x5c\x26\x64\x59\x26\x64\xc5\x70\x0e\ +\xa3\x21\x9c\xc3\x68\x88\x61\x1a\x0a\x00\x00\x6d\x84\x09\x75\x37\ +\x9e\xd9\x23\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x00\xa5\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x09\x00\x00\x00\x06\x08\x04\x00\x00\x00\xbb\xce\x7c\x4e\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x02\x62\x4b\x47\x44\x00\x9c\x53\x34\xfc\x5d\x00\x00\x00\x09\x70\ +\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ +\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdc\x08\x17\x0b\x02\x04\x6d\ +\x98\x1b\x69\x00\x00\x00\x29\x49\x44\x41\x54\x08\xd7\x63\x60\xc0\ +\x00\x8c\x0c\x0c\xff\xcf\xa3\x08\x18\x32\x32\x30\x20\x0b\x32\x1a\ +\x32\x30\x30\x42\x98\x10\x41\x46\x43\x14\x13\x50\xb5\xa3\x01\x00\ +\xd6\x10\x07\xd2\x2f\x48\xdf\x4a\x00\x00\x00\x00\x49\x45\x4e\x44\ +\xae\x42\x60\x82\ +\x00\x00\x01\x69\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x30\x00\x00\x00\x30\x08\x06\x00\x00\x00\x57\x02\xf9\x87\ +\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\ +\x01\x00\x9a\x9c\x18\x00\x00\x01\x1b\x49\x44\x41\x54\x68\x81\xed\ +\xda\xb1\x6d\xc2\x40\x14\x87\xf1\xcf\xc7\x91\x09\x50\x86\x70\x42\ +\x41\x4f\xc5\x0a\xae\x90\xbc\x0a\x29\xc8\x2a\x96\x52\x79\x05\x2a\ +\x46\x20\x1e\xc2\x82\x05\x48\x90\x52\xdc\x59\x01\x4b\x51\x62\x45\ +\xe2\xef\x93\xde\xaf\xb3\x45\xf1\x3e\xcb\xa6\xb9\x97\x11\x95\x75\ +\x33\x03\x5e\x80\x25\xf0\x0c\x4c\x19\x97\x0f\xe0\x00\xec\x81\x6d\ +\x55\xe4\x47\x80\x0c\xa0\xac\x9b\x15\xf0\x06\x3c\xca\xc6\x1b\xa6\ +\x05\xd6\x55\x91\xef\xb2\xf8\xe4\xdf\x49\x67\xf8\x4e\x0b\x3c\x39\ +\xc2\x6b\x93\xda\xf0\x10\x66\xde\x78\xc2\x3b\xdf\x77\xb9\xf3\x30\ +\x7f\x35\xe9\x5d\x2f\x3d\xe1\x83\xbd\x76\xa9\x8a\xdc\xdf\x69\xa0\ +\x41\xca\xba\xf9\xe4\x36\x62\xee\x18\xdf\xbf\xcd\x10\x53\xa7\x9e\ +\xe0\xbf\x2c\x40\xcd\x02\xd4\x2c\x40\xcd\x02\xd4\x2c\x40\xcd\x02\ +\xd4\x2c\x40\xcd\x02\xd4\x2c\x40\xcd\x02\xd4\x2c\x40\xcd\x02\xd4\ +\x2c\x40\xcd\x02\xd4\x2c\x40\xcd\x02\xd4\x2c\x40\xcd\x11\x4e\xc0\ +\x53\x75\xf6\x84\xe3\xfb\xc5\xd5\xcd\x49\x3c\x0d\x1c\xa3\xfe\x31\ +\xeb\xc1\x13\x76\x0f\x16\xbf\xfc\x70\xac\xf6\x0e\xd8\x12\x8e\xed\ +\x53\xd3\x02\xaf\x2e\x6e\x7d\xac\x49\x2b\xa2\x5b\xf6\x38\x66\xdd\ +\x9d\xb8\xf4\xb1\xe1\x7b\xdd\xe6\x41\x34\xdc\x4f\xce\xdc\xae\xdb\ +\x9c\x00\xbe\x00\x9f\xf6\x34\x3e\x36\x4f\x37\x81\x00\x00\x00\x00\ +\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x00\xa6\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x09\x00\x00\x00\x06\x08\x04\x00\x00\x00\xbb\xce\x7c\x4e\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x02\x62\x4b\x47\x44\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09\x70\ +\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ +\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdc\x08\x17\x08\x15\x3b\xdc\ +\x3b\x0c\x9b\x00\x00\x00\x2a\x49\x44\x41\x54\x08\xd7\x63\x60\xc0\ +\x00\x8c\x0c\x0c\x73\x3e\x20\x0b\xa4\x08\x30\x32\x30\x20\x0b\xa6\ +\x08\x30\x30\x30\x42\x98\x10\xc1\x14\x01\x14\x13\x50\xb5\xa3\x01\ +\x00\xc6\xb9\x07\x90\x5d\x66\x1f\x83\x00\x00\x00\x00\x49\x45\x4e\ +\x44\xae\x42\x60\x82\ +\x00\x00\x00\xa6\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x09\x00\x00\x00\x06\x08\x04\x00\x00\x00\xbb\xce\x7c\x4e\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x02\x62\x4b\x47\x44\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09\x70\ +\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ +\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdc\x08\x17\x08\x15\x3b\xdc\ +\x3b\x0c\x9b\x00\x00\x00\x2a\x49\x44\x41\x54\x08\xd7\x63\x60\xc0\ +\x00\x8c\x0c\x0c\x73\x3e\x20\x0b\xa4\x08\x30\x32\x30\x20\x0b\xa6\ +\x08\x30\x30\x30\x42\x98\x10\xc1\x14\x01\x14\x13\x50\xb5\xa3\x01\ +\x00\xc6\xb9\x07\x90\x5d\x66\x1f\x83\x00\x00\x00\x00\x49\x45\x4e\ +\x44\xae\x42\x60\x82\ \x00\x00\x04\x33\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ @@ -707,46 +474,40 @@ qt_resource_data = b"\ \x9b\x46\x27\x81\xcf\x80\xd7\x01\xa7\xe2\xbf\x00\xf8\x17\x5d\x81\ \x0b\x38\xb3\xfa\x20\x9c\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\ \x60\x82\ -\x00\x00\x01\xe1\ +\x00\x00\x01\xfc\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x30\x00\x00\x00\x30\x08\x06\x00\x00\x00\x57\x02\xf9\x87\ \x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\ -\x01\x00\x9a\x9c\x18\x00\x00\x01\x93\x49\x44\x41\x54\x68\x81\xed\ -\x9a\x3b\x4e\xc3\x40\x10\x86\xbf\x71\x1c\x2a\xe8\x10\xe5\x36\x94\ -\xd0\xa4\xa1\x8a\x28\x22\x0a\x0a\x44\x9f\x9e\x0b\x70\x80\x50\x70\ -\x01\x2e\xc0\x15\x68\x80\x13\xa0\x1c\x21\x50\x91\x66\xbb\x44\xa1\ -\x42\x34\x79\x68\x28\x6c\x1e\xb1\xfc\x48\x08\xc9\xda\xd2\x7e\x9d\ -\x77\x5c\xfc\x9f\xb3\x1e\x39\xda\x11\x62\x54\x75\x17\xb8\x02\x9a\ -\xc0\x21\x50\xa7\x5c\x4c\x80\x1e\xd0\x05\xae\x45\x64\xf4\x5d\x51\ -\xd5\x96\xaa\x0e\xb4\x3a\x0c\x54\xb5\x05\x20\x1a\x3d\xf9\x67\x60\ -\xcf\xc5\x63\x5d\x81\x21\x70\x10\x10\x6d\x9b\xaa\x85\x87\x28\x73\ -\x27\x24\xda\xf3\x49\x66\x1b\x0e\xb3\x28\xb5\xc4\x75\x53\x54\x75\ -\xcc\xfc\x0b\x3b\x13\x91\x70\x83\xa1\x16\x46\x55\xa7\xcc\x4b\x4c\ -\x02\xca\xd7\x6d\x96\xa1\x1e\xb8\x4e\xb0\x2a\x5e\xc0\x35\x5e\xc0\ -\x35\x5e\xc0\x35\x5e\xc0\x35\x5e\xc0\x35\x5e\xc0\x35\x85\x9f\xcd\ -\xd6\xda\x13\xe0\x08\xd8\x5e\x7f\x9c\x39\xa6\xc0\x0b\xf0\x60\x8c\ -\xf9\xc8\xba\x49\x54\x55\x13\x6b\x33\x11\x09\xad\xb5\x21\x70\x07\ -\x9c\xaf\x31\xe4\x22\xf4\x81\x53\x63\xcc\x6b\xca\xff\x81\xdc\x2d\ -\x74\x89\xfb\xf0\x00\xfb\xc0\x6d\x56\x31\x4f\xe0\xec\xff\xb3\xfc\ -\x99\x63\x6b\xed\x4e\x5a\xa1\xf2\x2f\x71\x9e\xc0\xe3\xc6\x52\x14\ -\xf3\x64\x8c\x79\x4f\x2b\xe4\x09\xdc\x00\xf7\xeb\xc9\xb3\x14\x7d\ -\xe0\x22\xab\x98\xd9\x85\xbe\x2e\xca\xd4\x46\xd3\xba\x50\xa1\x40\ -\x99\x58\xb6\x8d\x56\x02\x2f\xe0\x1a\x2f\xe0\x1a\x2f\xe0\x1a\x2f\ -\xe0\x1a\x2f\xe0\x1a\x2f\xe0\x9a\x80\xe8\x04\xbc\xaa\x8c\x43\xa2\ -\xe3\xfb\xc6\xaf\xc5\x5a\xfc\xd9\x5a\x46\x92\xc7\xac\xbd\x90\x68\ -\xf6\xa0\x51\x70\x63\x59\xe9\x56\x7f\xd4\x20\x9e\xfa\x68\xc7\x0b\ -\x55\x61\x08\xb4\x45\x64\x24\x5f\x2b\xf1\x2f\xd1\xe1\x67\xdc\x66\ -\xcb\x51\xb8\x2c\xc6\xcc\x8f\xdb\xbc\x01\x7c\x02\x6d\x77\x23\xb3\ -\xd4\x95\x53\x76\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\ -\x00\x00\x00\x45\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x01\x00\x00\x00\x01\x08\x02\x00\x00\x00\x90\x77\x53\xde\ -\x00\x00\x00\x0c\x49\x44\x41\x54\x08\x99\x63\x60\x60\x60\x00\x00\ -\x00\x04\x00\x01\xa3\x0a\x15\xe3\x00\x00\x00\x00\x49\x45\x4e\x44\ -\xae\x42\x60\x82\ +\x01\x00\x9a\x9c\x18\x00\x00\x01\xae\x49\x44\x41\x54\x68\x81\xed\ +\x9a\xbd\x4a\x03\x41\x14\x46\x4f\x36\x1b\xb0\xd0\x4a\xf1\x01\x14\ +\xab\x68\x91\xc6\x2a\x58\xb8\x16\xb2\x88\x76\x0b\xe9\x7d\x01\x1f\ +\x40\x8b\xf8\x00\xbe\x80\x85\x9d\x30\x62\xa3\x32\x55\xc6\x42\xf2\ +\x02\x42\x92\x46\x83\x7d\x48\x27\x36\xf9\x01\x8b\xdd\x40\x12\xb2\ +\x89\x6b\x7e\x66\x37\xcc\xe9\x76\xef\x14\xdf\x59\xee\x0c\x0b\x77\ +\x52\x04\x38\xae\xb7\x01\x5c\x01\x79\x60\x17\xc8\x10\x2f\xda\x40\ +\x05\x28\x03\x45\x25\x45\x13\x20\x05\xe0\xb8\xde\x21\x70\x0f\x6c\ +\x6a\x8b\x17\x8d\x06\x50\x50\x52\xbc\xa6\x82\x2f\x5f\x25\x39\xe1\ +\x7b\x34\x80\xac\x85\xdf\x36\x49\x0b\x0f\x7e\xe6\x4b\x1b\xbf\xe7\ +\x87\xe9\x2e\x38\xcc\x5f\x49\x0f\x3d\xe7\x6d\xfc\x0d\xdb\x4f\x57\ +\x49\x61\x2f\x28\x50\x24\x1c\xd7\xeb\x30\x28\xb1\x67\x11\xbf\xd3\ +\x26\x0a\x19\x4b\x77\x82\x69\x31\x02\xba\x31\x02\xba\x31\x02\xba\ +\x31\x02\xba\x31\x02\xba\x31\x02\xba\x99\xf8\xdb\xec\xb8\xde\x11\ +\xb0\x0f\xac\xce\x3f\xce\x00\x1d\xa0\x06\x3c\x2b\x29\x7e\xc2\x16\ +\x85\x0a\x38\xae\x67\x03\x8f\xc0\xe9\xec\xb3\x45\xa2\xee\xb8\xde\ +\xb1\x92\xe2\x73\x54\x71\x5c\x0b\x5d\xa0\x3f\x3c\xc0\x36\x70\x1b\ +\x56\x1c\x27\x70\x32\xfb\x2c\xff\xe6\xc0\x71\xbd\xb5\x51\x85\xc4\ +\x6f\xe2\x71\x02\x2f\x0b\x4b\x31\x99\x37\x25\xc5\xf7\xa8\xc2\x38\ +\x81\x1b\xe0\x69\x3e\x79\x22\x51\x07\xce\xc3\x8a\xa1\xa7\x90\x92\ +\xa2\x03\x9c\x25\xf6\x18\xed\xa1\xa4\x28\x01\xa5\x19\x06\x9b\x29\ +\x4b\xbd\x89\x13\x81\x11\xd0\x8d\x11\xd0\x8d\x11\xd0\x8d\x11\xd0\ +\x8d\x11\xd0\xcd\x52\x08\xb4\x75\x87\x98\x82\x96\x8d\x3f\xbe\xcf\ +\xf5\xbd\x4c\x07\xd3\xc0\x38\x32\x3c\x66\xad\xd8\xf8\x77\x0f\x72\ +\x13\x16\xc6\x95\xb2\x05\x14\xf1\xc7\xf6\x49\xa3\x01\x5c\x5b\xc1\ +\xad\x8f\x02\xc9\x92\xe8\x5d\xf6\x68\xa6\x01\xbe\x3e\xaa\x5f\x5b\ +\x3b\xd9\x3b\x60\x05\x7f\xf0\xbd\x4e\xfc\xda\xa8\x05\xbc\x03\x0f\ +\x80\xa7\xa4\xa8\x01\xfc\x02\x51\xab\x5c\x8a\x3f\xde\xe3\x59\x00\ +\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x00\xa6\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ @@ -754,64 +515,12 @@ qt_resource_data = b"\ \x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ \x02\x62\x4b\x47\x44\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09\x70\ \x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ -\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdc\x08\x17\x14\x1d\x00\xb0\ -\xd5\x35\xa3\x00\x00\x00\x2a\x49\x44\x41\x54\x08\xd7\x63\x60\xc0\ -\x06\xfe\x9f\x67\x60\x60\x42\x30\xa1\x1c\x08\x93\x81\x81\x09\xc1\ -\x64\x60\x60\x62\x60\x60\x34\x44\xe2\x20\x73\x19\x90\x8d\x40\x02\ -\x00\x64\x40\x09\x75\x86\xb3\xad\x9c\x00\x00\x00\x00\x49\x45\x4e\ +\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdc\x08\x17\x14\x1f\x20\xb9\ +\x8d\x77\xe9\x00\x00\x00\x2a\x49\x44\x41\x54\x08\xd7\x63\x60\xc0\ +\x06\xe6\x7c\x60\x60\x60\x42\x30\xa1\x1c\x08\x93\x81\x81\x09\xc1\ +\x64\x60\x60\x62\x60\x48\x11\x40\xe2\x20\x73\x19\x90\x8d\x40\x02\ +\x00\x23\xed\x08\xaf\x64\x9f\x0f\x15\x00\x00\x00\x00\x49\x45\x4e\ \x44\xae\x42\x60\x82\ -\x00\x00\x00\xa6\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x06\x00\x00\x00\x09\x08\x04\x00\x00\x00\xbb\x93\x95\x16\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x02\x62\x4b\x47\x44\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09\x70\ -\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ -\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdc\x08\x17\x14\x1d\x00\xb0\ -\xd5\x35\xa3\x00\x00\x00\x2a\x49\x44\x41\x54\x08\xd7\x63\x60\xc0\ -\x06\xfe\x9f\x67\x60\x60\x42\x30\xa1\x1c\x08\x93\x81\x81\x09\xc1\ -\x64\x60\x60\x62\x60\x60\x34\x44\xe2\x20\x73\x19\x90\x8d\x40\x02\ -\x00\x64\x40\x09\x75\x86\xb3\xad\x9c\x00\x00\x00\x00\x49\x45\x4e\ -\x44\xae\x42\x60\x82\ -\x00\x00\x01\x76\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x30\x00\x00\x00\x30\x08\x06\x00\x00\x00\x57\x02\xf9\x87\ -\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\ -\x01\x00\x9a\x9c\x18\x00\x00\x01\x28\x49\x44\x41\x54\x68\x81\xed\ -\xda\xb1\x4a\xc3\x50\x14\x87\xf1\x2f\x37\xb7\xe0\xae\xf8\x00\x82\ -\x53\x75\xe8\xde\xc9\x6c\x79\x80\x40\x1f\x46\x87\xfa\x22\x6e\x42\ -\xdc\xb3\xc5\xa9\x2f\x20\xb4\x5d\x3a\x74\x0f\x7d\x82\x6a\xc1\xe1\ -\xa6\x50\xb3\x68\x10\xfa\xcf\x85\xf3\xdb\x52\x3a\x9c\xaf\xdc\x66\ -\x39\x37\xa1\x95\xe5\xc5\x15\xf0\x04\x4c\x81\x3b\x60\xc4\xb0\x7c\ -\x02\x4b\x60\x01\xcc\xeb\xaa\xdc\x01\x24\x00\x59\x5e\x3c\x00\xaf\ -\xc0\xb5\x6c\xbc\x7e\x1a\x60\x56\x57\xe5\x7b\xd2\xfe\xf2\x2b\xe2\ -\x19\xfe\xa8\x01\xc6\x8e\x70\x6c\x62\x1b\x1e\xc2\xcc\x8f\x9e\x70\ -\xe6\xbb\x0e\x67\x1e\xe6\xaf\xd2\xce\xf3\xd4\x13\xfe\xb0\xa7\x0e\ -\x75\x55\xfa\x33\x0d\xd4\x4b\x96\x17\x5f\xfc\x8c\xb8\x77\x0c\xef\ -\x6d\xd3\xc7\xc8\xa9\x27\xf8\x2f\x0b\x50\xb3\x00\x35\x0b\x50\xb3\ -\x00\x35\x0b\x50\xb3\x00\x35\x0b\x50\xb3\x00\x35\x0b\x50\xb3\x00\ -\x35\x0b\x50\xb3\x00\x35\x0b\x50\xb3\x00\x35\x0b\x50\xb3\x00\x35\ -\x0b\x50\x73\x84\x0d\x78\xac\xf6\x9e\xb0\xbe\x9f\x9c\x7c\x98\xb6\ -\xdb\xc0\x21\xea\xae\x59\x97\x9e\x70\xf7\x60\xf2\xcb\x17\x87\x6a\ -\xe1\x80\x39\x61\x6d\x1f\x9b\x06\x78\x76\xed\xad\x8f\x19\x71\x45\ -\x1c\x2f\x7b\xec\x52\x80\xed\x66\xb5\xbd\xb9\x1d\xbf\x00\x17\x84\ -\xc5\xf7\x25\xc3\x3b\x46\x7b\xe0\x03\x78\x03\x8a\xba\x2a\xd7\x00\ -\xdf\xa4\xb5\x36\xa2\xca\x99\x74\x47\x00\x00\x00\x00\x49\x45\x4e\ -\x44\xae\x42\x60\x82\ -\x00\x00\x00\xa5\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x09\x00\x00\x00\x06\x08\x04\x00\x00\x00\xbb\xce\x7c\x4e\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x02\x62\x4b\x47\x44\x00\x9c\x53\x34\xfc\x5d\x00\x00\x00\x09\x70\ -\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ -\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdc\x08\x17\x0b\x02\x04\x6d\ -\x98\x1b\x69\x00\x00\x00\x29\x49\x44\x41\x54\x08\xd7\x63\x60\xc0\ -\x00\x8c\x0c\x0c\xff\xcf\xa3\x08\x18\x32\x32\x30\x20\x0b\x32\x1a\ -\x32\x30\x30\x42\x98\x10\x41\x46\x43\x14\x13\x50\xb5\xa3\x01\x00\ -\xd6\x10\x07\xd2\x2f\x48\xdf\x4a\x00\x00\x00\x00\x49\x45\x4e\x44\ -\xae\x42\x60\x82\ \x00\x00\x07\x30\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ @@ -929,92 +638,72 @@ qt_resource_data = b"\ \x23\xe0\x23\x37\xe1\xa8\x4f\x0e\x7f\xda\x60\xd7\xaf\x9f\xb9\x09\ \xdf\x63\x05\xff\xe5\x75\x4c\x65\xf5\xcc\x1f\x0d\x33\x2c\x83\xb6\ \x06\x44\x83\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x00\xa6\ +\x00\x00\x03\xfb\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x09\x00\x00\x00\x06\x08\x04\x00\x00\x00\xbb\xce\x7c\x4e\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x02\x62\x4b\x47\x44\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09\x70\ -\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ -\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdc\x08\x17\x08\x15\x3b\xdc\ -\x3b\x0c\x9b\x00\x00\x00\x2a\x49\x44\x41\x54\x08\xd7\x63\x60\xc0\ -\x00\x8c\x0c\x0c\x73\x3e\x20\x0b\xa4\x08\x30\x32\x30\x20\x0b\xa6\ -\x08\x30\x30\x30\x42\x98\x10\xc1\x14\x01\x14\x13\x50\xb5\xa3\x01\ -\x00\xc6\xb9\x07\x90\x5d\x66\x1f\x83\x00\x00\x00\x00\x49\x45\x4e\ -\x44\xae\x42\x60\x82\ -\x00\x00\x00\x9e\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x09\x00\x00\x00\x06\x08\x04\x00\x00\x00\xbb\xce\x7c\x4e\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x02\x62\x4b\x47\x44\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09\x70\ -\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ -\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdc\x08\x17\x08\x15\x0f\xfd\ -\x8f\xf8\x2e\x00\x00\x00\x22\x49\x44\x41\x54\x08\xd7\x63\x60\xc0\ -\x0d\xfe\x9f\x87\xb1\x18\x91\x05\x18\x0d\xe1\x42\x48\x2a\x0c\x19\ -\x18\x18\x91\x05\x10\x2a\xd1\x00\x00\xca\xb5\x07\xd2\x76\xbb\xb2\ -\xc5\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x00\xa5\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x09\x00\x00\x00\x06\x08\x04\x00\x00\x00\xbb\xce\x7c\x4e\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x02\x62\x4b\x47\x44\x00\x9c\x53\x34\xfc\x5d\x00\x00\x00\x09\x70\ -\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ -\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdc\x08\x17\x0b\x02\x04\x6d\ -\x98\x1b\x69\x00\x00\x00\x29\x49\x44\x41\x54\x08\xd7\x63\x60\xc0\ -\x00\x8c\x0c\x0c\xff\xcf\xa3\x08\x18\x32\x32\x30\x20\x0b\x32\x1a\ -\x32\x30\x30\x42\x98\x10\x41\x46\x43\x14\x13\x50\xb5\xa3\x01\x00\ -\xd6\x10\x07\xd2\x2f\x48\xdf\x4a\x00\x00\x00\x00\x49\x45\x4e\x44\ -\xae\x42\x60\x82\ -\x00\x00\x00\xa0\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x06\x00\x00\x00\x09\x08\x04\x00\x00\x00\xbb\x93\x95\x16\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x02\x62\x4b\x47\x44\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09\x70\ -\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ -\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdc\x08\x17\x14\x1c\x1f\x24\ -\xc6\x09\x17\x00\x00\x00\x24\x49\x44\x41\x54\x08\xd7\x63\x60\x40\ -\x05\xff\xcf\xc3\x58\x4c\xc8\x5c\x26\x64\x59\x26\x64\xc5\x70\x0e\ -\xa3\x21\x9c\xc3\x68\x88\x61\x1a\x0a\x00\x00\x6d\x84\x09\x75\x37\ -\x9e\xd9\x23\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x00\xa0\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x06\x00\x00\x00\x09\x08\x04\x00\x00\x00\xbb\x93\x95\x16\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x02\x62\x4b\x47\x44\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09\x70\ -\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ -\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdc\x08\x17\x14\x1c\x1f\x24\ -\xc6\x09\x17\x00\x00\x00\x24\x49\x44\x41\x54\x08\xd7\x63\x60\x40\ -\x05\xff\xcf\xc3\x58\x4c\xc8\x5c\x26\x64\x59\x26\x64\xc5\x70\x0e\ -\xa3\x21\x9c\xc3\x68\x88\x61\x1a\x0a\x00\x00\x6d\x84\x09\x75\x37\ -\x9e\xd9\x23\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x00\xa0\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x06\x00\x00\x00\x09\x08\x04\x00\x00\x00\xbb\x93\x95\x16\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x02\x62\x4b\x47\x44\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09\x70\ -\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ -\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdc\x08\x17\x14\x1f\x0d\xfc\ -\x52\x2b\x9c\x00\x00\x00\x24\x49\x44\x41\x54\x08\xd7\x63\x60\x40\ -\x05\x73\x3e\xc0\x58\x4c\xc8\x5c\x26\x64\x59\x26\x64\xc5\x70\x4e\ -\x8a\x00\x9c\x93\x22\x80\x61\x1a\x0a\x00\x00\x29\x95\x08\xaf\x88\ -\xac\xba\x34\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x00\x9e\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x09\x00\x00\x00\x06\x08\x04\x00\x00\x00\xbb\xce\x7c\x4e\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x02\x62\x4b\x47\x44\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09\x70\ -\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ -\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdc\x08\x17\x08\x15\x0f\xfd\ -\x8f\xf8\x2e\x00\x00\x00\x22\x49\x44\x41\x54\x08\xd7\x63\x60\xc0\ -\x0d\xfe\x9f\x87\xb1\x18\x91\x05\x18\x0d\xe1\x42\x48\x2a\x0c\x19\ -\x18\x18\x91\x05\x10\x2a\xd1\x00\x00\xca\xb5\x07\xd2\x76\xbb\xb2\ -\xc5\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x30\x00\x00\x00\x30\x08\x06\x00\x00\x00\x57\x02\xf9\x87\ +\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\ +\x01\x00\x9a\x9c\x18\x00\x00\x03\xad\x49\x44\x41\x54\x68\x81\xed\ +\x9a\x4f\xa8\x15\x55\x1c\xc7\x3f\xf7\xbe\xab\xf2\x20\xa3\x22\x6d\ +\xa1\x7c\x21\x09\x2a\x4b\x28\xda\x44\xb9\x88\x52\x4c\xcc\x6a\x51\ +\xf9\xa4\x85\xf1\xa0\x16\x51\xe4\x2e\x10\x74\x51\x44\x8b\x16\x15\ +\x25\x81\xb5\x28\x04\xad\xc0\xe2\x61\x64\xf6\x97\xe0\x45\x10\xb4\ +\xa9\x27\x44\x45\xc4\x17\xa2\x12\x35\x0a\x2a\xff\x3c\xab\xc5\x99\ +\x5b\xd7\x79\x33\x73\xce\xdc\x6b\x6f\xee\x05\x3f\xbb\x39\xf3\x3b\ +\xbf\xf3\xfb\x9d\x33\xe7\x9c\xef\x9c\x99\x16\x19\xb6\x2f\x06\x76\ +\x00\xab\x81\xab\x81\x05\x0c\x17\xa7\x80\x19\x60\x1a\x78\x4c\xd2\ +\x11\x80\x16\x80\xed\x9b\x81\xbd\xc0\xd2\xc6\xc2\xab\xc7\x61\x60\ +\xb3\xa4\x0f\x5b\x59\xcf\x1f\x62\x74\x82\xef\x72\x18\xb8\xaa\x4d\ +\x78\x6c\x46\x2d\x78\x08\x31\x6f\xef\x10\x9e\xf9\x3c\xa7\xe7\x39\ +\x98\x54\xc6\x72\xd7\xab\x3b\x84\x09\xdb\xcb\x69\x49\x9d\x79\x0a\ +\xa8\x16\xb6\x67\x39\x33\x89\x55\x6d\x86\x6f\xb5\xa9\xc3\x82\x76\ +\xd3\x11\x0c\xca\xb9\x04\x9a\xe6\x5c\x02\xff\x07\xb6\xef\xb6\xbd\ +\xd7\xf6\xda\x98\xed\xd0\x25\x60\xfb\x11\xe0\x75\x60\x02\x38\x68\ +\x7b\xa7\xed\xd2\x95\x72\xa8\x12\xb0\x3d\x01\x3c\xdd\x53\xd4\x02\ +\x1e\x04\x5e\x2c\xab\x33\x34\x1b\x96\xed\x35\xc0\x2b\x64\x02\x33\ +\xc7\x16\xdb\x5f\x16\xd5\x1b\x8a\x11\xb0\x7d\x1d\xf0\x06\xb0\xb0\ +\xc2\x6c\x7d\x51\x61\xe3\x09\xd8\xbe\x0c\x78\x1b\x58\x1c\x31\x7d\ +\xa9\xa8\xb0\xd1\x04\x6c\x5f\x02\x1c\x24\xae\x86\x9f\x92\xf4\x6a\ +\xd1\x8d\xc6\x12\xb0\xbd\x18\x38\x00\xac\x88\x98\xee\x06\x1e\x2d\ +\xbb\xd9\x48\x02\xb6\x17\x02\x6f\x02\xd7\x46\x4c\xdf\x01\x26\x25\ +\xfd\x5d\x66\x30\xef\x09\xd8\x6e\x13\x7a\xf5\x96\x88\xe9\x67\xc0\ +\x5d\x92\x66\xab\x8c\x9a\x18\x81\x67\x80\x7b\x22\x36\x5f\x03\x1b\ +\x24\xfd\x1e\x73\x96\x94\x80\xed\x56\xd6\x73\x03\x61\x7b\x1b\xf0\ +\x70\xc4\xec\x47\x60\x5d\xf7\xd4\x21\x46\x65\x50\x59\xe0\x5b\x81\ +\x3f\x81\x19\xdb\xab\x92\x22\x2d\xf6\x35\x09\x3c\x11\x31\xfb\x15\ +\xb8\x55\xd2\xf7\xa9\x7e\x4b\x13\xc8\xf4\xc7\x14\x61\x6b\x5f\x04\ +\x5c\x09\xbc\x6f\xfb\xf2\x54\xe7\x3d\xbe\x36\x02\xbb\x22\x66\x27\ +\x80\x3b\x24\x7d\x51\xc7\x77\xd5\x08\x3c\x0f\x6c\xcc\x95\x2d\x05\ +\x3e\xb0\x7d\x69\x6a\x03\xb6\x6f\x00\x5e\x63\xee\x0b\x79\x2f\x7f\ +\x01\xf7\x4a\xfa\x38\xd5\x6f\x97\xc2\x04\xb2\x1e\x7b\xa0\xa4\xce\ +\x32\xc2\x48\x2c\x8b\x39\xb7\xbd\x12\xd8\x0f\x8c\x47\x4c\x1f\x92\ +\xb4\x2f\xe6\xaf\x88\xb2\x11\xb8\x28\x52\x6f\x05\x21\x89\x25\x65\ +\x06\xb6\x97\x13\x76\xd9\x98\xaf\xc7\x25\xbd\x10\xb1\x29\xa5\x2c\ +\x81\x3d\x84\x75\xb8\x8a\x2b\x80\x77\x6d\x5f\x90\xbf\x61\xfb\x42\ +\x42\xf0\xcb\x23\x3e\x76\x49\xda\x11\x8d\xb2\x82\xc2\x04\x24\x9d\ +\x22\xa8\xbf\x42\x09\xdb\xc3\x35\xc0\x01\xdb\xe7\x75\x0b\x6c\x8f\ +\x13\x1e\x9b\x95\x91\xba\x53\x04\xad\x3f\x10\xa5\x93\x58\xd2\x31\ +\x60\x2d\xf0\x4d\xc4\xc7\xf5\xc0\x7e\xdb\xe3\xb6\xc7\x08\x13\xf6\ +\xc6\x48\x9d\x69\x60\x42\xd2\xc0\x27\x80\x95\xfb\x80\xa4\x9f\x81\ +\x35\x80\x23\x7e\x6e\x02\xf6\x11\x96\xca\xfc\xca\x95\x67\x06\xb8\ +\x5d\xd2\xf1\xc4\x18\x2b\x89\xee\xae\x92\x4c\xd0\x2d\x3f\x45\x4c\ +\xd7\x03\x93\x11\x1b\x13\x36\xaa\x5f\xd2\xc2\x8b\x93\x24\x0f\x24\ +\x7d\x4b\x18\x89\xa3\x03\xb4\x75\x8c\x20\x11\x7e\x18\xc0\xc7\x1c\ +\x92\xf5\x8d\xa4\x43\xc0\x3a\xe0\xb7\x3e\xda\xf9\x03\xb8\x4d\xd2\ +\x57\x7d\xd4\xad\xa4\x96\x40\x93\xf4\x39\xb0\x21\x0b\x28\x95\x59\ +\x60\x93\xa4\x4f\xeb\xb4\x95\x4a\x6d\x85\x29\x69\x1a\xb8\x93\xa0\ +\x5d\x52\xb8\x5f\xd2\x5b\x75\xdb\x49\xa5\x2f\x89\x2c\xe9\x3d\x60\ +\x13\xa1\x77\xab\xd8\x26\xe9\xe5\x7e\xda\x48\xa5\x6f\x8d\x2f\x69\ +\x0a\xd8\x42\x10\x62\x45\x3c\x27\xe9\xc9\x7e\xfd\xa7\x32\xd0\x4b\ +\x8a\xa4\x3d\xc0\x7d\xcc\x9d\x13\x3b\x81\xad\x83\xf8\x4e\x65\xe0\ +\x93\x39\x49\xbb\x6d\x7f\x42\xd8\xc0\xce\x07\x3e\xca\xe6\xc9\xbc\ +\x70\x56\x8e\x16\x25\x7d\x07\x3c\x7b\x36\x7c\xd5\xa5\x4d\xf8\x02\ +\x3e\xaa\x9c\xec\x10\xb4\x49\xef\xf9\xcc\x58\xf6\x35\x70\x18\xc9\ +\xbf\xd5\xcd\x74\x08\xca\x30\x7f\xc0\x54\xf5\xfa\x37\x4c\x4c\x8f\ +\xfe\xaf\x06\xd9\xf9\xcb\xe6\xac\x60\x54\xe8\xfe\xec\x71\xe4\xdf\ +\x8f\x09\xd9\x48\x6c\xe7\xbf\xdf\x6d\xaa\xce\xea\x9b\xe0\x24\x67\ +\xfe\x6e\x73\x14\xe0\x1f\x0a\x43\x12\x6b\x4f\xfd\x3f\x13\x00\x00\ +\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x01\x5b\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ @@ -1167,6 +856,56 @@ qt_resource_data = b"\ \x71\x5b\x73\x5c\x40\x48\xa5\xdd\x61\x81\x0d\x9e\x6b\x8e\xff\xfd\ \xcf\x3f\xcc\x31\xe9\x01\x1c\x00\x73\x52\x2d\x71\xe4\x4a\x1b\x69\ \x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x00\x9e\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x09\x00\x00\x00\x06\x08\x04\x00\x00\x00\xbb\xce\x7c\x4e\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x02\x62\x4b\x47\x44\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09\x70\ +\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ +\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdc\x08\x17\x08\x15\x0f\xfd\ +\x8f\xf8\x2e\x00\x00\x00\x22\x49\x44\x41\x54\x08\xd7\x63\x60\xc0\ +\x0d\xfe\x9f\x87\xb1\x18\x91\x05\x18\x0d\xe1\x42\x48\x2a\x0c\x19\ +\x18\x18\x91\x05\x10\x2a\xd1\x00\x00\xca\xb5\x07\xd2\x76\xbb\xb2\ +\xc5\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x00\xa6\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x06\x00\x00\x00\x09\x08\x04\x00\x00\x00\xbb\x93\x95\x16\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x02\x62\x4b\x47\x44\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09\x70\ +\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ +\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdc\x08\x17\x14\x1d\x00\xb0\ +\xd5\x35\xa3\x00\x00\x00\x2a\x49\x44\x41\x54\x08\xd7\x63\x60\xc0\ +\x06\xfe\x9f\x67\x60\x60\x42\x30\xa1\x1c\x08\x93\x81\x81\x09\xc1\ +\x64\x60\x60\x62\x60\x60\x34\x44\xe2\x20\x73\x19\x90\x8d\x40\x02\ +\x00\x64\x40\x09\x75\x86\xb3\xad\x9c\x00\x00\x00\x00\x49\x45\x4e\ +\x44\xae\x42\x60\x82\ +\x00\x00\x00\xa5\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x09\x00\x00\x00\x06\x08\x04\x00\x00\x00\xbb\xce\x7c\x4e\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x02\x62\x4b\x47\x44\x00\x9c\x53\x34\xfc\x5d\x00\x00\x00\x09\x70\ +\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ +\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdc\x08\x17\x0b\x02\x04\x6d\ +\x98\x1b\x69\x00\x00\x00\x29\x49\x44\x41\x54\x08\xd7\x63\x60\xc0\ +\x00\x8c\x0c\x0c\xff\xcf\xa3\x08\x18\x32\x32\x30\x20\x0b\x32\x1a\ +\x32\x30\x30\x42\x98\x10\x41\x46\x43\x14\x13\x50\xb5\xa3\x01\x00\ +\xd6\x10\x07\xd2\x2f\x48\xdf\x4a\x00\x00\x00\x00\x49\x45\x4e\x44\ +\xae\x42\x60\x82\ +\x00\x00\x00\x9e\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x09\x00\x00\x00\x06\x08\x04\x00\x00\x00\xbb\xce\x7c\x4e\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x02\x62\x4b\x47\x44\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09\x70\ +\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ +\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdc\x08\x17\x08\x15\x0f\xfd\ +\x8f\xf8\x2e\x00\x00\x00\x22\x49\x44\x41\x54\x08\xd7\x63\x60\xc0\ +\x0d\xfe\x9f\x87\xb1\x18\x91\x05\x18\x0d\xe1\x42\x48\x2a\x0c\x19\ +\x18\x18\x91\x05\x10\x2a\xd1\x00\x00\xca\xb5\x07\xd2\x76\xbb\xb2\ +\xc5\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x01\x57\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ @@ -1191,6 +930,351 @@ qt_resource_data = b"\ \x97\xd8\xf0\x7d\xdc\xe6\xce\x34\xdc\x6f\x3a\xfa\xc7\x6d\xde\x00\ \x3e\x00\x47\xd7\xea\xb1\xad\x69\xe1\xd6\x00\x00\x00\x00\x49\x45\ \x4e\x44\xae\x42\x60\x82\ +\x00\x00\x04\x12\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x30\x00\x00\x00\x30\x08\x06\x00\x00\x00\x57\x02\xf9\x87\ +\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\ +\x01\x00\x9a\x9c\x18\x00\x00\x03\xc4\x49\x44\x41\x54\x68\x81\xed\ +\x9a\x5f\x88\x94\x55\x18\xc6\x7f\x33\x3b\x1a\x0b\x19\x15\x66\x17\ +\xca\x03\x49\x50\x4d\x09\x4a\x37\x51\x5e\x44\x29\x26\x66\x05\x5b\ +\xb9\xd2\x82\xb1\x50\x17\x91\x24\x74\x11\x08\x7a\xa1\x44\x17\x5d\ +\x54\x94\x04\xd6\x45\xe1\xa2\x15\x4c\xb1\x18\x99\xfd\x25\xd8\x08\ +\x82\x6e\x6a\x5d\xa4\x22\xe2\x81\xa8\x96\xd5\x28\xe8\x9f\xae\xd5\ +\xc5\xf9\xb6\xc6\xd9\xf9\xbe\x73\x66\xc6\x76\x66\xc0\xdf\xdd\x9c\ +\xef\x3d\xef\x79\x9f\x73\xe6\x9c\xf3\x7e\xdf\x39\x25\x32\x46\x6a\ +\x53\x4b\x81\xdd\xc0\x5a\xe0\x3a\x60\x11\xbd\xc5\x69\x60\x12\x98\ +\x00\xf6\x8c\x0d\x55\x67\x00\x4a\x00\x23\xb5\xa9\x5b\x80\x43\xc0\ +\xb2\xae\x85\xd7\x1a\xd3\xc0\xd6\xb1\xa1\xea\x07\xa5\xac\xe7\x8f\ +\xd1\x3f\xc1\xcf\x31\x0d\x5c\x5b\x26\xfc\x6d\xfa\x2d\x78\x08\x31\ +\xef\xaa\x10\xfe\xf3\x8d\x9c\x59\xe0\x60\x52\x19\x68\xf8\xbd\xb6\ +\x42\x98\xb0\xf5\x9c\x19\x1b\xaa\x56\x16\x28\xa0\x96\x18\xa9\x4d\ +\xcd\x72\xb6\x88\x55\x65\x7a\x6f\xb5\x69\x85\x45\xe5\x6e\x47\xd0\ +\x29\xe7\x05\x74\x9b\xf3\x02\xfe\x0f\x6c\xdf\x63\xfb\x90\xed\xf5\ +\x31\xdb\x9e\x13\x60\xfb\x11\xe0\x35\x60\x18\x38\x6a\x7b\x9f\xed\ +\xdc\x95\xb2\xa7\x04\xd8\x1e\x06\x9e\xaa\x2b\x2a\x01\x0f\x01\x2f\ +\xe4\xd5\xe9\x19\x01\xb6\xd7\x01\x2f\x93\x25\x98\x0d\x6c\xb3\xfd\ +\x68\xb3\x7a\x3d\x21\xc0\xf6\xf5\xc0\xeb\xc0\xe2\x02\xb3\x8d\xcd\ +\x0a\xbb\x2e\xc0\xf6\x95\xc0\x5b\xc0\x92\x88\xe9\x8b\xcd\x0a\xbb\ +\x2a\xc0\xf6\xe5\xc0\x51\xe2\xd9\xf0\x93\x92\x5e\x69\xf6\xa0\x6b\ +\x02\x6c\x2f\x01\x8e\x00\x2b\x23\xa6\x07\x80\xc7\xf2\x1e\x76\x45\ +\x80\xed\xc5\xc0\x1b\xc0\x9a\x88\xe9\xdb\xc0\xa8\xa4\xbf\xf3\x0c\ +\x16\x5c\x80\xed\x32\xa1\x57\x6f\x8d\x98\x7e\x0a\xdc\x2d\x69\xb6\ +\xc8\xa8\x1b\x23\xf0\x34\x70\x6f\xc4\xe6\x4b\x60\x93\xa4\x5f\x63\ +\xce\x92\x04\xd8\x2e\x65\x3d\xd7\x11\xb6\x77\x02\xdb\x23\x66\xdf\ +\x03\x1b\x24\xcd\xa4\xf8\x2c\x0c\x2a\x0b\x7c\x07\xf0\x3b\x30\x69\ +\x7b\x55\x52\xa4\xcd\x7d\x8d\x02\x8f\x47\xcc\x7e\x06\x6e\x93\xf4\ +\x6d\xaa\xdf\x5c\x01\x59\xfe\x31\x4e\xd8\xda\x2f\x00\xae\x01\xde\ +\xb3\x7d\x55\xaa\xf3\x3a\x5f\x9b\x81\xfd\x11\xb3\x3f\x81\x3b\x25\ +\x7d\xde\x8a\xef\xa2\x11\x78\x0e\xd8\xdc\x50\xb6\x0c\x78\xdf\xf6\ +\x15\xa9\x0d\xd8\xbe\x11\x78\x95\xf9\x2f\xe4\xf5\xfc\x05\xdc\x27\ +\xe9\xa3\x54\xbf\x73\x34\x15\x90\xf5\xd8\x83\x39\x75\x96\x13\x46\ +\x62\x79\xcc\xb9\xed\x2a\x70\x18\x18\x8c\x98\x3e\x2c\xa9\x16\xf3\ +\xd7\x8c\xbc\x11\xb8\x34\x52\x6f\x25\x41\xc4\x65\x79\x06\xb6\x57\ +\x10\x76\xd9\x98\xaf\xbd\x92\x9e\x8f\xd8\xe4\x92\x27\xe0\x20\x61\ +\x1d\x2e\xe2\x6a\xe0\x1d\xdb\x17\x37\x3e\xb0\x7d\x09\x21\xf8\x15\ +\x11\x1f\xfb\x25\xed\x8e\x46\x59\x40\x53\x01\x92\x4e\x13\xb2\xbf\ +\x2f\x22\xf5\x57\x03\x47\x6c\x5f\x38\x57\x60\x7b\x90\xf0\xb7\xa9\ +\x46\xea\x8e\x13\x72\xfd\x8e\xc8\x9d\xc4\x92\x4e\x02\xeb\x81\xaf\ +\x22\x3e\x6e\x00\x0e\xdb\x1e\xb4\x3d\x40\x98\xb0\x37\x45\xea\x4c\ +\x00\xc3\x92\x3a\xfe\x02\x58\xb8\x0f\x48\xfa\x11\x58\x07\x38\xe2\ +\xe7\x66\xa0\x46\x58\x2a\x1b\x57\xae\x46\x26\x81\x3b\x24\xfd\x91\ +\x18\x63\x21\xd1\xdd\x55\x92\x09\x79\xcb\x0f\x11\xd3\x8d\xc0\x68\ +\xc4\xc6\x84\x8d\xea\xa7\xb4\xf0\xe2\x24\xa5\x07\x92\xbe\x26\x8c\ +\xc4\x89\x0e\xda\x3a\x49\x48\x11\xbe\xeb\xc0\xc7\x3c\x92\xf3\x1b\ +\x49\xc7\x80\x0d\xc0\x2f\x6d\xb4\xf3\x1b\x70\xbb\xa4\xe3\x6d\xd4\ +\x2d\xa4\xa5\x04\x4d\xd2\x67\xc0\xa6\x2c\xa0\x54\x66\x81\x2d\x92\ +\x3e\x69\xa5\xad\x54\x5a\xce\x30\x25\x4d\x00\x77\x11\x72\x97\x14\ +\x1e\x90\xf4\x66\xab\xed\xa4\xd2\x56\x8a\x2c\xe9\x5d\x60\x0b\xa1\ +\x77\x8b\xd8\x29\xe9\xa5\x76\xda\x48\xa5\xed\x1c\x5f\xd2\x38\xb0\ +\x8d\x90\x88\x35\xe3\x59\x49\x4f\xb4\xeb\x3f\x95\x8e\x5e\x52\x24\ +\x1d\x04\xee\x67\xfe\x9c\xd8\x07\xec\xe8\xc4\x77\x2a\x1d\x1f\x25\ +\x49\x3a\x60\xfb\x63\xc2\x06\x76\x11\xf0\x61\x36\x4f\x16\x84\x73\ +\x72\x16\x26\xe9\x1b\xe0\x99\x73\xe1\xab\x55\xca\x84\x13\xf0\x7e\ +\xe5\x54\x85\x90\x9b\xd4\x7f\x9f\x19\xc8\x4e\x03\x7b\x91\xc6\xb7\ +\xba\xc9\x0a\x21\x33\x6c\xfc\xc0\x54\xf4\xfa\xd7\x4b\x4c\x94\x81\ +\x3d\x84\x63\xfb\x7e\x63\x1a\xd8\x5b\xce\x6e\x7d\x6c\xa5\xbf\x44\ +\xcc\x5d\xf6\x98\xf9\xf7\x30\x21\xbb\xf4\xb1\x8b\xff\xae\xdb\x14\ +\x7d\xab\xef\x06\xa7\x38\xfb\xba\xcd\x09\x80\x7f\x00\xc4\x1e\x10\ +\x29\x33\x5b\x85\xf7\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\ +\x82\ +\x00\x00\x01\xe1\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x30\x00\x00\x00\x30\x08\x06\x00\x00\x00\x57\x02\xf9\x87\ +\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\ +\x01\x00\x9a\x9c\x18\x00\x00\x01\x93\x49\x44\x41\x54\x68\x81\xed\ +\x9a\x3b\x4e\xc3\x40\x10\x86\xbf\x71\x1c\x2a\xe8\x10\xe5\x36\x94\ +\xd0\xa4\xa1\x8a\x28\x22\x0a\x0a\x44\x9f\x9e\x0b\x70\x80\x50\x70\ +\x01\x2e\xc0\x15\x68\x80\x13\xa0\x1c\x21\x50\x91\x66\xbb\x44\xa1\ +\x42\x34\x79\x68\x28\x6c\x1e\xb1\xfc\x48\x08\xc9\xda\xd2\x7e\x9d\ +\x77\x5c\xfc\x9f\xb3\x1e\x39\xda\x11\x62\x54\x75\x17\xb8\x02\x9a\ +\xc0\x21\x50\xa7\x5c\x4c\x80\x1e\xd0\x05\xae\x45\x64\xf4\x5d\x51\ +\xd5\x96\xaa\x0e\xb4\x3a\x0c\x54\xb5\x05\x20\x1a\x3d\xf9\x67\x60\ +\xcf\xc5\x63\x5d\x81\x21\x70\x10\x10\x6d\x9b\xaa\x85\x87\x28\x73\ +\x27\x24\xda\xf3\x49\x66\x1b\x0e\xb3\x28\xb5\xc4\x75\x53\x54\x75\ +\xcc\xfc\x0b\x3b\x13\x91\x70\x83\xa1\x16\x46\x55\xa7\xcc\x4b\x4c\ +\x02\xca\xd7\x6d\x96\xa1\x1e\xb8\x4e\xb0\x2a\x5e\xc0\x35\x5e\xc0\ +\x35\x5e\xc0\x35\x5e\xc0\x35\x5e\xc0\x35\x5e\xc0\x35\x85\x9f\xcd\ +\xd6\xda\x13\xe0\x08\xd8\x5e\x7f\x9c\x39\xa6\xc0\x0b\xf0\x60\x8c\ +\xf9\xc8\xba\x49\x54\x55\x13\x6b\x33\x11\x09\xad\xb5\x21\x70\x07\ +\x9c\xaf\x31\xe4\x22\xf4\x81\x53\x63\xcc\x6b\xca\xff\x81\xdc\x2d\ +\x74\x89\xfb\xf0\x00\xfb\xc0\x6d\x56\x31\x4f\xe0\xec\xff\xb3\xfc\ +\x99\x63\x6b\xed\x4e\x5a\xa1\xf2\x2f\x71\x9e\xc0\xe3\xc6\x52\x14\ +\xf3\x64\x8c\x79\x4f\x2b\xe4\x09\xdc\x00\xf7\xeb\xc9\xb3\x14\x7d\ +\xe0\x22\xab\x98\xd9\x85\xbe\x2e\xca\xd4\x46\xd3\xba\x50\xa1\x40\ +\x99\x58\xb6\x8d\x56\x02\x2f\xe0\x1a\x2f\xe0\x1a\x2f\xe0\x1a\x2f\ +\xe0\x1a\x2f\xe0\x1a\x2f\xe0\x9a\x80\xe8\x04\xbc\xaa\x8c\x43\xa2\ +\xe3\xfb\xc6\xaf\xc5\x5a\xfc\xd9\x5a\x46\x92\xc7\xac\xbd\x90\x68\ +\xf6\xa0\x51\x70\x63\x59\xe9\x56\x7f\xd4\x20\x9e\xfa\x68\xc7\x0b\ +\x55\x61\x08\xb4\x45\x64\x24\x5f\x2b\xf1\x2f\xd1\xe1\x67\xdc\x66\ +\xcb\x51\xb8\x2c\xc6\xcc\x8f\xdb\xbc\x01\x7c\x02\x6d\x77\x23\xb3\ +\xd4\x95\x53\x76\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\ +\x00\x00\x01\x76\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x30\x00\x00\x00\x30\x08\x06\x00\x00\x00\x57\x02\xf9\x87\ +\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\ +\x01\x00\x9a\x9c\x18\x00\x00\x01\x28\x49\x44\x41\x54\x68\x81\xed\ +\xda\xb1\x4a\xc3\x50\x14\x87\xf1\x2f\x37\xb7\xe0\xae\xf8\x00\x82\ +\x53\x75\xe8\xde\xc9\x6c\x79\x80\x40\x1f\x46\x87\xfa\x22\x6e\x42\ +\xdc\xb3\xc5\xa9\x2f\x20\xb4\x5d\x3a\x74\x0f\x7d\x82\x6a\xc1\xe1\ +\xa6\x50\xb3\x68\x10\xfa\xcf\x85\xf3\xdb\x52\x3a\x9c\xaf\xdc\x66\ +\x39\x37\xa1\x95\xe5\xc5\x15\xf0\x04\x4c\x81\x3b\x60\xc4\xb0\x7c\ +\x02\x4b\x60\x01\xcc\xeb\xaa\xdc\x01\x24\x00\x59\x5e\x3c\x00\xaf\ +\xc0\xb5\x6c\xbc\x7e\x1a\x60\x56\x57\xe5\x7b\xd2\xfe\xf2\x2b\xe2\ +\x19\xfe\xa8\x01\xc6\x8e\x70\x6c\x62\x1b\x1e\xc2\xcc\x8f\x9e\x70\ +\xe6\xbb\x0e\x67\x1e\xe6\xaf\xd2\xce\xf3\xd4\x13\xfe\xb0\xa7\x0e\ +\x75\x55\xfa\x33\x0d\xd4\x4b\x96\x17\x5f\xfc\x8c\xb8\x77\x0c\xef\ +\x6d\xd3\xc7\xc8\xa9\x27\xf8\x2f\x0b\x50\xb3\x00\x35\x0b\x50\xb3\ +\x00\x35\x0b\x50\xb3\x00\x35\x0b\x50\xb3\x00\x35\x0b\x50\xb3\x00\ +\x35\x0b\x50\xb3\x00\x35\x0b\x50\xb3\x00\x35\x0b\x50\xb3\x00\x35\ +\x0b\x50\x73\x84\x0d\x78\xac\xf6\x9e\xb0\xbe\x9f\x9c\x7c\x98\xb6\ +\xdb\xc0\x21\xea\xae\x59\x97\x9e\x70\xf7\x60\xf2\xcb\x17\x87\x6a\ +\xe1\x80\x39\x61\x6d\x1f\x9b\x06\x78\x76\xed\xad\x8f\x19\x71\x45\ +\x1c\x2f\x7b\xec\x52\x80\xed\x66\xb5\xbd\xb9\x1d\xbf\x00\x17\x84\ +\xc5\xf7\x25\xc3\x3b\x46\x7b\xe0\x03\x78\x03\x8a\xba\x2a\xd7\x00\ +\xdf\xa4\xb5\x36\xa2\xca\x99\x74\x47\x00\x00\x00\x00\x49\x45\x4e\ +\x44\xae\x42\x60\x82\ +\x00\x00\x00\xa0\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x06\x00\x00\x00\x09\x08\x04\x00\x00\x00\xbb\x93\x95\x16\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x02\x62\x4b\x47\x44\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09\x70\ +\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ +\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdc\x08\x17\x14\x1f\x0d\xfc\ +\x52\x2b\x9c\x00\x00\x00\x24\x49\x44\x41\x54\x08\xd7\x63\x60\x40\ +\x05\x73\x3e\xc0\x58\x4c\xc8\x5c\x26\x64\x59\x26\x64\xc5\x70\x4e\ +\x8a\x00\x9c\x93\x22\x80\x61\x1a\x0a\x00\x00\x29\x95\x08\xaf\x88\ +\xac\xba\x34\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x05\x7e\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1f\x15\xc4\x89\ +\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\ +\x01\x00\x9a\x9c\x18\x00\x00\x05\x17\x69\x54\x58\x74\x58\x4d\x4c\ +\x3a\x63\x6f\x6d\x2e\x61\x64\x6f\x62\x65\x2e\x78\x6d\x70\x00\x00\ +\x00\x00\x00\x3c\x3f\x78\x70\x61\x63\x6b\x65\x74\x20\x62\x65\x67\ +\x69\x6e\x3d\x22\xef\xbb\xbf\x22\x20\x69\x64\x3d\x22\x57\x35\x4d\ +\x30\x4d\x70\x43\x65\x68\x69\x48\x7a\x72\x65\x53\x7a\x4e\x54\x63\ +\x7a\x6b\x63\x39\x64\x22\x3f\x3e\x20\x3c\x78\x3a\x78\x6d\x70\x6d\ +\x65\x74\x61\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x3d\x22\x61\x64\x6f\ +\x62\x65\x3a\x6e\x73\x3a\x6d\x65\x74\x61\x2f\x22\x20\x78\x3a\x78\ +\x6d\x70\x74\x6b\x3d\x22\x41\x64\x6f\x62\x65\x20\x58\x4d\x50\x20\ +\x43\x6f\x72\x65\x20\x37\x2e\x31\x2d\x63\x30\x30\x30\x20\x37\x39\ +\x2e\x37\x61\x37\x61\x32\x33\x36\x2c\x20\x32\x30\x32\x31\x2f\x30\ +\x38\x2f\x31\x32\x2d\x30\x30\x3a\x32\x35\x3a\x32\x30\x20\x20\x20\ +\x20\x20\x20\x20\x20\x22\x3e\x20\x3c\x72\x64\x66\x3a\x52\x44\x46\ +\x20\x78\x6d\x6c\x6e\x73\x3a\x72\x64\x66\x3d\x22\x68\x74\x74\x70\ +\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\ +\x39\x39\x2f\x30\x32\x2f\x32\x32\x2d\x72\x64\x66\x2d\x73\x79\x6e\ +\x74\x61\x78\x2d\x6e\x73\x23\x22\x3e\x20\x3c\x72\x64\x66\x3a\x44\ +\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x20\x72\x64\x66\x3a\x61\ +\x62\x6f\x75\x74\x3d\x22\x22\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6d\ +\x70\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x6e\x73\x2e\x61\x64\x6f\ +\x62\x65\x2e\x63\x6f\x6d\x2f\x78\x61\x70\x2f\x31\x2e\x30\x2f\x22\ +\x20\x78\x6d\x6c\x6e\x73\x3a\x64\x63\x3d\x22\x68\x74\x74\x70\x3a\ +\x2f\x2f\x70\x75\x72\x6c\x2e\x6f\x72\x67\x2f\x64\x63\x2f\x65\x6c\ +\x65\x6d\x65\x6e\x74\x73\x2f\x31\x2e\x31\x2f\x22\x20\x78\x6d\x6c\ +\x6e\x73\x3a\x78\x6d\x70\x4d\x4d\x3d\x22\x68\x74\x74\x70\x3a\x2f\ +\x2f\x6e\x73\x2e\x61\x64\x6f\x62\x65\x2e\x63\x6f\x6d\x2f\x78\x61\ +\x70\x2f\x31\x2e\x30\x2f\x6d\x6d\x2f\x22\x20\x78\x6d\x6c\x6e\x73\ +\x3a\x73\x74\x45\x76\x74\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x6e\ +\x73\x2e\x61\x64\x6f\x62\x65\x2e\x63\x6f\x6d\x2f\x78\x61\x70\x2f\ +\x31\x2e\x30\x2f\x73\x54\x79\x70\x65\x2f\x52\x65\x73\x6f\x75\x72\ +\x63\x65\x45\x76\x65\x6e\x74\x23\x22\x20\x78\x6d\x6c\x6e\x73\x3a\ +\x70\x68\x6f\x74\x6f\x73\x68\x6f\x70\x3d\x22\x68\x74\x74\x70\x3a\ +\x2f\x2f\x6e\x73\x2e\x61\x64\x6f\x62\x65\x2e\x63\x6f\x6d\x2f\x70\ +\x68\x6f\x74\x6f\x73\x68\x6f\x70\x2f\x31\x2e\x30\x2f\x22\x20\x78\ +\x6d\x70\x3a\x43\x72\x65\x61\x74\x6f\x72\x54\x6f\x6f\x6c\x3d\x22\ +\x41\x64\x6f\x62\x65\x20\x50\x68\x6f\x74\x6f\x73\x68\x6f\x70\x20\ +\x32\x32\x2e\x35\x20\x28\x57\x69\x6e\x64\x6f\x77\x73\x29\x22\x20\ +\x78\x6d\x70\x3a\x43\x72\x65\x61\x74\x65\x44\x61\x74\x65\x3d\x22\ +\x32\x30\x32\x31\x2d\x31\x31\x2d\x31\x30\x54\x31\x37\x3a\x33\x39\ +\x3a\x30\x32\x2b\x30\x31\x3a\x30\x30\x22\x20\x78\x6d\x70\x3a\x4d\ +\x65\x74\x61\x64\x61\x74\x61\x44\x61\x74\x65\x3d\x22\x32\x30\x32\ +\x31\x2d\x31\x31\x2d\x31\x30\x54\x31\x37\x3a\x33\x39\x3a\x30\x32\ +\x2b\x30\x31\x3a\x30\x30\x22\x20\x78\x6d\x70\x3a\x4d\x6f\x64\x69\ +\x66\x79\x44\x61\x74\x65\x3d\x22\x32\x30\x32\x31\x2d\x31\x31\x2d\ +\x31\x30\x54\x31\x37\x3a\x33\x39\x3a\x30\x32\x2b\x30\x31\x3a\x30\ +\x30\x22\x20\x64\x63\x3a\x66\x6f\x72\x6d\x61\x74\x3d\x22\x69\x6d\ +\x61\x67\x65\x2f\x70\x6e\x67\x22\x20\x78\x6d\x70\x4d\x4d\x3a\x49\ +\x6e\x73\x74\x61\x6e\x63\x65\x49\x44\x3d\x22\x78\x6d\x70\x2e\x69\ +\x69\x64\x3a\x66\x31\x37\x65\x62\x62\x32\x33\x2d\x65\x36\x32\x61\ +\x2d\x33\x39\x34\x36\x2d\x61\x39\x37\x35\x2d\x64\x34\x66\x36\x61\ +\x62\x34\x34\x64\x34\x30\x39\x22\x20\x78\x6d\x70\x4d\x4d\x3a\x44\ +\x6f\x63\x75\x6d\x65\x6e\x74\x49\x44\x3d\x22\x78\x6d\x70\x2e\x64\ +\x69\x64\x3a\x66\x31\x37\x65\x62\x62\x32\x33\x2d\x65\x36\x32\x61\ +\x2d\x33\x39\x34\x36\x2d\x61\x39\x37\x35\x2d\x64\x34\x66\x36\x61\ +\x62\x34\x34\x64\x34\x30\x39\x22\x20\x78\x6d\x70\x4d\x4d\x3a\x4f\ +\x72\x69\x67\x69\x6e\x61\x6c\x44\x6f\x63\x75\x6d\x65\x6e\x74\x49\ +\x44\x3d\x22\x78\x6d\x70\x2e\x64\x69\x64\x3a\x66\x31\x37\x65\x62\ +\x62\x32\x33\x2d\x65\x36\x32\x61\x2d\x33\x39\x34\x36\x2d\x61\x39\ +\x37\x35\x2d\x64\x34\x66\x36\x61\x62\x34\x34\x64\x34\x30\x39\x22\ +\x20\x70\x68\x6f\x74\x6f\x73\x68\x6f\x70\x3a\x43\x6f\x6c\x6f\x72\ +\x4d\x6f\x64\x65\x3d\x22\x33\x22\x20\x70\x68\x6f\x74\x6f\x73\x68\ +\x6f\x70\x3a\x49\x43\x43\x50\x72\x6f\x66\x69\x6c\x65\x3d\x22\x73\ +\x52\x47\x42\x20\x49\x45\x43\x36\x31\x39\x36\x36\x2d\x32\x2e\x31\ +\x22\x3e\x20\x3c\x78\x6d\x70\x4d\x4d\x3a\x48\x69\x73\x74\x6f\x72\ +\x79\x3e\x20\x3c\x72\x64\x66\x3a\x53\x65\x71\x3e\x20\x3c\x72\x64\ +\x66\x3a\x6c\x69\x20\x73\x74\x45\x76\x74\x3a\x61\x63\x74\x69\x6f\ +\x6e\x3d\x22\x63\x72\x65\x61\x74\x65\x64\x22\x20\x73\x74\x45\x76\ +\x74\x3a\x69\x6e\x73\x74\x61\x6e\x63\x65\x49\x44\x3d\x22\x78\x6d\ +\x70\x2e\x69\x69\x64\x3a\x66\x31\x37\x65\x62\x62\x32\x33\x2d\x65\ +\x36\x32\x61\x2d\x33\x39\x34\x36\x2d\x61\x39\x37\x35\x2d\x64\x34\ +\x66\x36\x61\x62\x34\x34\x64\x34\x30\x39\x22\x20\x73\x74\x45\x76\ +\x74\x3a\x77\x68\x65\x6e\x3d\x22\x32\x30\x32\x31\x2d\x31\x31\x2d\ +\x31\x30\x54\x31\x37\x3a\x33\x39\x3a\x30\x32\x2b\x30\x31\x3a\x30\ +\x30\x22\x20\x73\x74\x45\x76\x74\x3a\x73\x6f\x66\x74\x77\x61\x72\ +\x65\x41\x67\x65\x6e\x74\x3d\x22\x41\x64\x6f\x62\x65\x20\x50\x68\ +\x6f\x74\x6f\x73\x68\x6f\x70\x20\x32\x32\x2e\x35\x20\x28\x57\x69\ +\x6e\x64\x6f\x77\x73\x29\x22\x2f\x3e\x20\x3c\x2f\x72\x64\x66\x3a\ +\x53\x65\x71\x3e\x20\x3c\x2f\x78\x6d\x70\x4d\x4d\x3a\x48\x69\x73\ +\x74\x6f\x72\x79\x3e\x20\x3c\x2f\x72\x64\x66\x3a\x44\x65\x73\x63\ +\x72\x69\x70\x74\x69\x6f\x6e\x3e\x20\x3c\x2f\x72\x64\x66\x3a\x52\ +\x44\x46\x3e\x20\x3c\x2f\x78\x3a\x78\x6d\x70\x6d\x65\x74\x61\x3e\ +\x20\x3c\x3f\x78\x70\x61\x63\x6b\x65\x74\x20\x65\x6e\x64\x3d\x22\ +\x72\x22\x3f\x3e\x07\x62\x0c\x81\x00\x00\x00\x0d\x49\x44\x41\x54\ +\x08\x1d\x63\xf8\xff\xff\x3f\x03\x00\x08\xfc\x02\xfe\xe6\x0c\xff\ +\xab\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x00\x9f\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x09\x00\x00\x00\x06\x08\x04\x00\x00\x00\xbb\xce\x7c\x4e\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x02\x62\x4b\x47\x44\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09\x70\ +\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ +\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdc\x08\x17\x08\x14\x1f\xf9\ +\x23\xd9\x0b\x00\x00\x00\x23\x49\x44\x41\x54\x08\xd7\x63\x60\xc0\ +\x0d\xe6\x7c\x80\xb1\x18\x91\x05\x52\x04\xe0\x42\x08\x15\x29\x02\ +\x0c\x0c\x8c\xc8\x02\x08\x95\x68\x00\x00\xac\xac\x07\x90\x4e\x65\ +\x34\xac\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x00\xa0\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x06\x00\x00\x00\x09\x08\x04\x00\x00\x00\xbb\x93\x95\x16\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x02\x62\x4b\x47\x44\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09\x70\ +\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ +\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdc\x08\x17\x14\x1c\x1f\x24\ +\xc6\x09\x17\x00\x00\x00\x24\x49\x44\x41\x54\x08\xd7\x63\x60\x40\ +\x05\xff\xcf\xc3\x58\x4c\xc8\x5c\x26\x64\x59\x26\x64\xc5\x70\x0e\ +\xa3\x21\x9c\xc3\x68\x88\x61\x1a\x0a\x00\x00\x6d\x84\x09\x75\x37\ +\x9e\xd9\x23\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x00\xa6\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x06\x00\x00\x00\x09\x08\x04\x00\x00\x00\xbb\x93\x95\x16\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x02\x62\x4b\x47\x44\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09\x70\ +\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ +\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdc\x08\x17\x14\x1d\x00\xb0\ +\xd5\x35\xa3\x00\x00\x00\x2a\x49\x44\x41\x54\x08\xd7\x63\x60\xc0\ +\x06\xfe\x9f\x67\x60\x60\x42\x30\xa1\x1c\x08\x93\x81\x81\x09\xc1\ +\x64\x60\x60\x62\x60\x60\x34\x44\xe2\x20\x73\x19\x90\x8d\x40\x02\ +\x00\x64\x40\x09\x75\x86\xb3\xad\x9c\x00\x00\x00\x00\x49\x45\x4e\ +\x44\xae\x42\x60\x82\ +\x00\x00\x00\xa6\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x09\x00\x00\x00\x06\x08\x04\x00\x00\x00\xbb\xce\x7c\x4e\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x02\x62\x4b\x47\x44\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09\x70\ +\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ +\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdc\x08\x17\x08\x15\x3b\xdc\ +\x3b\x0c\x9b\x00\x00\x00\x2a\x49\x44\x41\x54\x08\xd7\x63\x60\xc0\ +\x00\x8c\x0c\x0c\x73\x3e\x20\x0b\xa4\x08\x30\x32\x30\x20\x0b\xa6\ +\x08\x30\x30\x30\x42\x98\x10\xc1\x14\x01\x14\x13\x50\xb5\xa3\x01\ +\x00\xc6\xb9\x07\x90\x5d\x66\x1f\x83\x00\x00\x00\x00\x49\x45\x4e\ +\x44\xae\x42\x60\x82\ +\x00\x00\x03\xff\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x30\x00\x00\x00\x30\x08\x06\x00\x00\x00\x57\x02\xf9\x87\ +\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\ +\x01\x00\x9a\x9c\x18\x00\x00\x03\xb1\x49\x44\x41\x54\x68\x81\xed\ +\x9a\x4f\x68\x1e\x45\x18\xc6\x7f\x9b\x26\x85\x82\x15\x15\xab\x42\ +\xcb\x03\x06\x05\xa9\x0a\x8a\xb7\x52\x3c\xd4\x96\xaa\xb5\xe0\x41\ +\xad\xc5\x43\x25\xa0\x07\x51\xcc\x4d\x28\xb4\x07\x45\x3c\x78\xb0\ +\xa0\x52\x50\x0f\x8a\x50\xf5\x50\xa5\x28\xc6\xbf\xa8\x34\x20\x08\ +\x9e\x4c\x41\xc5\x83\x3c\x20\xd2\x90\x2a\x0a\xfe\x69\x92\x3a\x1e\ +\x66\x8d\x5f\xd6\xdd\x9d\xfd\xf6\x8b\xd9\x2f\xd0\xdf\xed\x9b\x79\ +\xe7\x9d\xe7\x9d\xd9\x99\x79\x67\xbf\xcd\xc8\x09\x21\x5c\x0a\x1c\ +\x06\xb6\x03\xd7\x01\x63\x0c\x17\x0b\xc0\x0c\x30\x0d\x3c\x9e\x65\ +\xd9\xdc\x52\x4d\x08\x61\x47\x08\xe1\x74\x58\x3b\x9c\x0e\x21\xec\ +\x00\xc8\x42\x1c\xf9\x53\xc0\x65\x5d\x0c\xeb\x00\xcc\x02\xd7\x8e\ +\x10\x1f\x9b\xb5\x26\x1e\xa2\xe6\x43\xa3\xc4\x67\xbe\xc8\xb9\x55\ +\x16\xd3\x94\x75\x85\xdf\xdb\xb3\x10\xc2\x3c\xcb\x17\xec\xb9\x2c\ +\xcb\x46\x57\x51\x54\x63\x42\x08\x8b\x2c\x0f\x62\x61\x84\xe1\xdb\ +\x6d\xfa\x61\x6c\xa4\x6b\x05\x83\x72\x3e\x80\xae\x39\x1f\xc0\xff\ +\x81\xed\xbb\x6d\xbf\x66\x7b\x57\xca\x36\x0b\x21\x84\x42\x59\xa7\ +\xdb\xa8\xed\x47\x81\x23\xf9\xcf\x00\x1c\x05\x26\x25\x2d\x94\x6c\ +\xa3\xc3\x35\x03\xb6\xef\x05\x9e\xe9\x29\xca\x80\x87\x80\x17\xab\ +\xda\x0c\xcd\x81\x65\x7b\x27\xf0\x0a\x51\x74\x91\x03\xb6\xbf\x2a\ +\x6b\x37\x14\x33\x60\xfb\x26\xe0\x4d\x60\x7d\x8d\xd9\x6d\x65\x85\ +\x9d\x07\x60\xfb\x2a\xe0\x5d\x60\x63\xc2\xf4\xa5\xb2\xc2\x4e\x03\ +\xb0\x7d\x39\xf0\x3e\xe9\x6c\xf8\x69\x49\xaf\x97\x55\x74\x16\x80\ +\xed\x8d\xc0\x14\x30\x9e\x30\x7d\x15\x78\xac\xaa\xb2\x93\x00\x6c\ +\xaf\x07\xde\x02\x6e\x4c\x98\xbe\x07\x4c\x48\x2a\x6e\xf5\x4b\xac\ +\x7a\x00\xb6\x47\x88\xa3\x7a\x4b\xc2\xf4\x0b\xe0\x2e\x49\x8b\x75\ +\x46\x5d\xcc\xc0\x11\xe0\x9e\x84\xcd\xb7\xc0\x1e\x49\xbf\xa5\x9c\ +\x35\x0a\xc0\x76\x96\x8f\xdc\x40\xd8\x3e\x08\x3c\x92\x30\xfb\x11\ +\xd8\x2d\x69\x2e\x61\x07\x24\x02\xc8\x85\x4f\x02\x7f\x00\x33\xb6\ +\xaf\x6f\xa4\xb4\xdc\xd7\x04\xf0\x64\xc2\xec\x17\xe0\x56\x49\xdf\ +\x37\xf5\x5b\x99\x0b\xd9\x1e\x03\x8e\x03\x7b\x7b\xea\x66\x81\x9b\ +\x25\x7d\xd3\xb4\x03\x00\xdb\x7b\x89\x8b\xb6\x78\xa7\xed\xe5\x2c\ +\x71\xe4\x3f\xab\x32\xe8\x37\x17\x7a\x8e\xe5\xe2\x21\xee\xd7\x1f\ +\xdb\xbe\xb2\x56\x71\x0f\xb6\xb7\x01\x6f\x14\x3b\x2e\xf0\x17\x70\ +\x5f\x9d\xf8\x2a\x4a\x03\xc8\x47\xec\xc1\x8a\x36\x9b\x81\x8f\x6c\ +\x6f\x4e\x39\xb7\xbd\x15\x78\x1b\xd8\x90\x30\x7d\x58\xd2\xf1\x94\ +\xbf\x32\xaa\x66\xe0\x92\x44\xbb\x71\x62\x10\x9b\xaa\x0c\x6c\x6f\ +\x21\x9e\xb2\x29\x5f\x4f\x48\x3a\x9a\xb0\xa9\xa4\x2a\x80\x63\xc4\ +\x7d\xb8\x8e\x6b\x80\x0f\x6c\x5f\x54\xac\xb0\x7d\x31\x51\xfc\x96\ +\x84\x8f\x17\x24\x1d\x4e\xaa\xac\xa1\x34\x00\x49\x0b\xc4\xec\xaf\ +\x34\x85\xed\xe1\x06\x60\xca\xf6\x05\xff\x14\xd8\xde\x40\x7c\x6c\ +\xb6\x26\xda\x9e\x20\xe6\xfa\x03\x51\x7b\x23\xcb\x93\xad\x93\xc0\ +\xd5\x09\x3f\x9f\x02\xb7\x03\xf3\xc4\xdd\xa6\xb8\xf8\x8b\x4c\x03\ +\xbb\x24\xfd\xd9\x8f\xd8\xb2\x5d\x28\x79\xa5\xb4\x2d\x62\x10\x4a\ +\xf8\x9f\x22\x1e\x42\x13\x09\xbb\x19\xe2\x56\xfc\x73\x23\xd5\x3d\ +\xb4\x0a\x00\x96\x72\xf6\x93\xc0\x15\xfd\x76\x5a\xc0\xc0\x36\x49\ +\x3f\xb4\x69\xdc\xfa\x4e\x2c\xe9\x3b\x60\x27\x70\xa6\x4d\xc7\x39\ +\x3f\x11\x0f\xaa\x56\xe2\xab\x68\x9c\xdf\x48\x3a\x05\xec\x06\x7e\ +\x6d\xd1\xcf\xef\xc0\x1d\x92\xbe\x6e\xd1\xb6\x96\xbe\x12\x34\x49\ +\x5f\x02\x7b\x72\x41\x4d\x59\x04\xf6\x49\xfa\xbc\x9f\xbe\x9a\xd2\ +\x77\x86\x29\x69\x1a\xb8\x93\x98\xbb\x34\xe1\x01\x49\xef\xf4\xdb\ +\x4f\x53\x5a\xa5\xc8\x92\x3e\x04\xf6\x11\x47\xb7\x8e\x83\x92\x5e\ +\x6e\xd3\x47\x53\x5a\xe7\xf8\x92\x4e\x00\x07\x88\x89\x58\x19\xcf\ +\x4a\x7a\xaa\xad\xff\xa6\x0c\x74\x49\x91\x74\x0c\xb8\x9f\xff\xae\ +\x89\xe7\x81\xc9\x41\x7c\x37\x65\x45\xde\x8d\xda\x1e\x27\x9e\xbe\ +\x17\x02\x9f\xe4\xeb\x64\xc5\x69\x7d\x90\x0d\x0b\x55\x07\xd9\x42\ +\x37\x72\x56\x84\xf9\x51\x62\x6e\xd2\xfb\x7e\x66\x5d\x1e\xe9\x30\ +\x52\xbc\xd5\xcd\x8c\x12\x33\xc3\xe2\x0b\xa6\xba\xeb\xdf\x30\x31\ +\xbd\xf6\x3f\x35\xc8\xbf\xfa\xd8\x9f\x17\xac\x15\x66\x81\xfd\x59\ +\x96\xcd\x2d\xfd\x99\x90\xcf\xc4\x21\xfe\xfd\xdc\xa6\xee\x5d\x7d\ +\x17\xcc\xb3\xfc\x73\x9b\x33\x00\x7f\x03\xd9\x1a\xfb\xdb\xbb\xa7\ +\x8f\x07\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ " qt_resource_name = b"\ @@ -1202,145 +1286,79 @@ qt_resource_name = b"\ \x07\x03\x7d\xc3\ \x00\x69\ \x00\x6d\x00\x61\x00\x67\x00\x65\x00\x73\ -\x00\x23\ -\x06\xf2\x1a\x47\ -\x00\x63\ -\x00\x68\x00\x65\x00\x63\x00\x6b\x00\x62\x00\x6f\x00\x78\x00\x5f\x00\x69\x00\x6e\x00\x64\x00\x65\x00\x74\x00\x65\x00\x72\x00\x6d\ -\x00\x69\x00\x6e\x00\x61\x00\x74\x00\x65\x00\x5f\x00\x64\x00\x69\x00\x73\x00\x61\x00\x62\x00\x6c\x00\x65\x00\x64\x00\x2e\x00\x70\ -\x00\x6e\x00\x67\ -\x00\x12\ -\x01\x2e\x03\x27\ -\x00\x63\ -\x00\x6f\x00\x6d\x00\x62\x00\x6f\x00\x62\x00\x6f\x00\x78\x00\x5f\x00\x61\x00\x72\x00\x72\x00\x6f\x00\x77\x00\x2e\x00\x70\x00\x6e\ -\x00\x67\ -\x00\x14\ -\x07\xec\xd1\xc7\ -\x00\x63\ -\x00\x68\x00\x65\x00\x63\x00\x6b\x00\x62\x00\x6f\x00\x78\x00\x5f\x00\x63\x00\x68\x00\x65\x00\x63\x00\x6b\x00\x65\x00\x64\x00\x2e\ -\x00\x70\x00\x6e\x00\x67\ -\x00\x15\ -\x0f\xf3\xc0\x07\ -\x00\x75\ -\x00\x70\x00\x5f\x00\x61\x00\x72\x00\x72\x00\x6f\x00\x77\x00\x5f\x00\x64\x00\x69\x00\x73\x00\x61\x00\x62\x00\x6c\x00\x65\x00\x64\ -\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x11\ \x0b\xda\x30\xa7\ \x00\x62\ \x00\x72\x00\x61\x00\x6e\x00\x63\x00\x68\x00\x5f\x00\x63\x00\x6c\x00\x6f\x00\x73\x00\x65\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\ \ -\x00\x20\ -\x0f\xd4\x1b\xc7\ -\x00\x63\ -\x00\x68\x00\x65\x00\x63\x00\x6b\x00\x62\x00\x6f\x00\x78\x00\x5f\x00\x69\x00\x6e\x00\x64\x00\x65\x00\x74\x00\x65\x00\x72\x00\x6d\ -\x00\x69\x00\x6e\x00\x61\x00\x74\x00\x65\x00\x5f\x00\x68\x00\x6f\x00\x76\x00\x65\x00\x72\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x1a\ -\x05\x11\xe0\xe7\ -\x00\x63\ -\x00\x68\x00\x65\x00\x63\x00\x6b\x00\x62\x00\x6f\x00\x78\x00\x5f\x00\x63\x00\x68\x00\x65\x00\x63\x00\x6b\x00\x65\x00\x64\x00\x5f\ -\x00\x66\x00\x6f\x00\x63\x00\x75\x00\x73\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x1a\ -\x03\x0e\xe4\x87\ -\x00\x63\ -\x00\x68\x00\x65\x00\x63\x00\x6b\x00\x62\x00\x6f\x00\x78\x00\x5f\x00\x63\x00\x68\x00\x65\x00\x63\x00\x6b\x00\x65\x00\x64\x00\x5f\ -\x00\x68\x00\x6f\x00\x76\x00\x65\x00\x72\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x1c\ -\x0e\x3c\xde\x07\ -\x00\x63\ -\x00\x68\x00\x65\x00\x63\x00\x6b\x00\x62\x00\x6f\x00\x78\x00\x5f\x00\x75\x00\x6e\x00\x63\x00\x68\x00\x65\x00\x63\x00\x6b\x00\x65\ -\x00\x64\x00\x5f\x00\x68\x00\x6f\x00\x76\x00\x65\x00\x72\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x0f\ +\x06\x53\x25\xa7\ +\x00\x62\ +\x00\x72\x00\x61\x00\x6e\x00\x63\x00\x68\x00\x5f\x00\x6f\x00\x70\x00\x65\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x1a\ \x01\x87\xae\x67\ \x00\x63\ \x00\x68\x00\x65\x00\x63\x00\x6b\x00\x62\x00\x6f\x00\x78\x00\x5f\x00\x69\x00\x6e\x00\x64\x00\x65\x00\x74\x00\x65\x00\x72\x00\x6d\ \x00\x69\x00\x6e\x00\x61\x00\x74\x00\x65\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x17\ -\x0c\xab\x51\x07\ -\x00\x64\ -\x00\x6f\x00\x77\x00\x6e\x00\x5f\x00\x61\x00\x72\x00\x72\x00\x6f\x00\x77\x00\x5f\x00\x64\x00\x69\x00\x73\x00\x61\x00\x62\x00\x6c\ -\x00\x65\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0f\ -\x06\x53\x25\xa7\ -\x00\x62\ -\x00\x72\x00\x61\x00\x6e\x00\x63\x00\x68\x00\x5f\x00\x6f\x00\x70\x00\x65\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x17\ -\x0c\x65\xce\x07\ -\x00\x6c\ -\x00\x65\x00\x66\x00\x74\x00\x5f\x00\x61\x00\x72\x00\x72\x00\x6f\x00\x77\x00\x5f\x00\x64\x00\x69\x00\x73\x00\x61\x00\x62\x00\x6c\ -\x00\x65\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0e\ -\x04\xa2\xfc\xa7\ -\x00\x64\ -\x00\x6f\x00\x77\x00\x6e\x00\x5f\x00\x61\x00\x72\x00\x72\x00\x6f\x00\x77\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x1d\ -\x09\x07\x81\x07\ -\x00\x63\ -\x00\x68\x00\x65\x00\x63\x00\x6b\x00\x62\x00\x6f\x00\x78\x00\x5f\x00\x63\x00\x68\x00\x65\x00\x63\x00\x6b\x00\x65\x00\x64\x00\x5f\ -\x00\x64\x00\x69\x00\x73\x00\x61\x00\x62\x00\x6c\x00\x65\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x20\ -\x09\xd7\x1f\xa7\ +\x0f\xd4\x1b\xc7\ \x00\x63\ \x00\x68\x00\x65\x00\x63\x00\x6b\x00\x62\x00\x6f\x00\x78\x00\x5f\x00\x69\x00\x6e\x00\x64\x00\x65\x00\x74\x00\x65\x00\x72\x00\x6d\ -\x00\x69\x00\x6e\x00\x61\x00\x74\x00\x65\x00\x5f\x00\x66\x00\x6f\x00\x63\x00\x75\x00\x73\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0f\ -\x0c\xe2\x68\x67\ -\x00\x74\ -\x00\x72\x00\x61\x00\x6e\x00\x73\x00\x70\x00\x61\x00\x72\x00\x65\x00\x6e\x00\x74\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0e\ -\x0e\xde\xfa\xc7\ -\x00\x6c\ -\x00\x65\x00\x66\x00\x74\x00\x5f\x00\x61\x00\x72\x00\x72\x00\x6f\x00\x77\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x11\ -\x00\xb8\x8c\x07\ -\x00\x6c\ -\x00\x65\x00\x66\x00\x74\x00\x5f\x00\x61\x00\x72\x00\x72\x00\x6f\x00\x77\x00\x5f\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\ -\x00\x1f\ -\x0a\xae\x27\x47\ -\x00\x63\ -\x00\x68\x00\x65\x00\x63\x00\x6b\x00\x62\x00\x6f\x00\x78\x00\x5f\x00\x75\x00\x6e\x00\x63\x00\x68\x00\x65\x00\x63\x00\x6b\x00\x65\ -\x00\x64\x00\x5f\x00\x64\x00\x69\x00\x73\x00\x61\x00\x62\x00\x6c\x00\x65\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x69\x00\x6e\x00\x61\x00\x74\x00\x65\x00\x5f\x00\x68\x00\x6f\x00\x76\x00\x65\x00\x72\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x1b\ \x03\x5a\x32\x27\ \x00\x63\ \x00\x6f\x00\x6d\x00\x62\x00\x6f\x00\x62\x00\x6f\x00\x78\x00\x5f\x00\x61\x00\x72\x00\x72\x00\x6f\x00\x77\x00\x5f\x00\x64\x00\x69\ \x00\x73\x00\x61\x00\x62\x00\x6c\x00\x65\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x0f\ +\x02\x9f\x05\x87\ +\x00\x72\ +\x00\x69\x00\x67\x00\x68\x00\x74\x00\x5f\x00\x61\x00\x72\x00\x72\x00\x6f\x00\x77\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x0e\ +\x04\xa2\xfc\xa7\ +\x00\x64\ +\x00\x6f\x00\x77\x00\x6e\x00\x5f\x00\x61\x00\x72\x00\x72\x00\x6f\x00\x77\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x1c\ +\x0e\x3c\xde\x07\ +\x00\x63\ +\x00\x68\x00\x65\x00\x63\x00\x6b\x00\x62\x00\x6f\x00\x78\x00\x5f\x00\x75\x00\x6e\x00\x63\x00\x68\x00\x65\x00\x63\x00\x6b\x00\x65\ +\x00\x64\x00\x5f\x00\x68\x00\x6f\x00\x76\x00\x65\x00\x72\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x12\ -\x05\x8f\x9d\x07\ -\x00\x62\ -\x00\x72\x00\x61\x00\x6e\x00\x63\x00\x68\x00\x5f\x00\x6f\x00\x70\x00\x65\x00\x6e\x00\x5f\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\ +\x01\x2e\x03\x27\ +\x00\x63\ +\x00\x6f\x00\x6d\x00\x62\x00\x6f\x00\x62\x00\x6f\x00\x78\x00\x5f\x00\x61\x00\x72\x00\x72\x00\x6f\x00\x77\x00\x2e\x00\x70\x00\x6e\ \x00\x67\ \x00\x15\ \x03\x27\x72\x67\ \x00\x63\ \x00\x6f\x00\x6d\x00\x62\x00\x6f\x00\x62\x00\x6f\x00\x78\x00\x5f\x00\x61\x00\x72\x00\x72\x00\x6f\x00\x77\x00\x5f\x00\x6f\x00\x6e\ \x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0f\ -\x01\x73\x8b\x07\ -\x00\x75\ -\x00\x70\x00\x5f\x00\x61\x00\x72\x00\x72\x00\x6f\x00\x77\x00\x5f\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x11\ -\x01\x1f\xc3\x87\ -\x00\x64\ -\x00\x6f\x00\x77\x00\x6e\x00\x5f\x00\x61\x00\x72\x00\x72\x00\x6f\x00\x77\x00\x5f\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\ +\x00\x1d\ +\x09\x07\x81\x07\ +\x00\x63\ +\x00\x68\x00\x65\x00\x63\x00\x6b\x00\x62\x00\x6f\x00\x78\x00\x5f\x00\x63\x00\x68\x00\x65\x00\x63\x00\x6b\x00\x65\x00\x64\x00\x5f\ +\x00\x64\x00\x69\x00\x73\x00\x61\x00\x62\x00\x6c\x00\x65\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x23\ +\x06\xf2\x1a\x47\ +\x00\x63\ +\x00\x68\x00\x65\x00\x63\x00\x6b\x00\x62\x00\x6f\x00\x78\x00\x5f\x00\x69\x00\x6e\x00\x64\x00\x65\x00\x74\x00\x65\x00\x72\x00\x6d\ +\x00\x69\x00\x6e\x00\x61\x00\x74\x00\x65\x00\x5f\x00\x64\x00\x69\x00\x73\x00\x61\x00\x62\x00\x6c\x00\x65\x00\x64\x00\x2e\x00\x70\ +\x00\x6e\x00\x67\ +\x00\x17\ +\x0c\x65\xce\x07\ +\x00\x6c\ +\x00\x65\x00\x66\x00\x74\x00\x5f\x00\x61\x00\x72\x00\x72\x00\x6f\x00\x77\x00\x5f\x00\x64\x00\x69\x00\x73\x00\x61\x00\x62\x00\x6c\ +\x00\x65\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x12\ -\x03\x8d\x04\x47\ -\x00\x72\ -\x00\x69\x00\x67\x00\x68\x00\x74\x00\x5f\x00\x61\x00\x72\x00\x72\x00\x6f\x00\x77\x00\x5f\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\ +\x05\x8f\x9d\x07\ +\x00\x62\ +\x00\x72\x00\x61\x00\x6e\x00\x63\x00\x68\x00\x5f\x00\x6f\x00\x70\x00\x65\x00\x6e\x00\x5f\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\ \x00\x67\ -\x00\x0f\ -\x02\x9f\x05\x87\ -\x00\x72\ -\x00\x69\x00\x67\x00\x68\x00\x74\x00\x5f\x00\x61\x00\x72\x00\x72\x00\x6f\x00\x77\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x18\ -\x03\x8e\xde\x67\ -\x00\x72\ -\x00\x69\x00\x67\x00\x68\x00\x74\x00\x5f\x00\x61\x00\x72\x00\x72\x00\x6f\x00\x77\x00\x5f\x00\x64\x00\x69\x00\x73\x00\x61\x00\x62\ -\x00\x6c\x00\x65\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0c\ -\x06\xe6\xe6\x67\ -\x00\x75\ -\x00\x70\x00\x5f\x00\x61\x00\x72\x00\x72\x00\x6f\x00\x77\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x14\ +\x07\xec\xd1\xc7\ +\x00\x63\ +\x00\x68\x00\x65\x00\x63\x00\x6b\x00\x62\x00\x6f\x00\x78\x00\x5f\x00\x63\x00\x68\x00\x65\x00\x63\x00\x6b\x00\x65\x00\x64\x00\x2e\ +\x00\x70\x00\x6e\x00\x67\ \x00\x16\ \x01\x75\xcc\x87\ \x00\x63\ @@ -1351,49 +1369,115 @@ qt_resource_name = b"\ \x00\x62\ \x00\x72\x00\x61\x00\x6e\x00\x63\x00\x68\x00\x5f\x00\x63\x00\x6c\x00\x6f\x00\x73\x00\x65\x00\x64\x00\x5f\x00\x6f\x00\x6e\x00\x2e\ \x00\x70\x00\x6e\x00\x67\ +\x00\x0f\ +\x01\x73\x8b\x07\ +\x00\x75\ +\x00\x70\x00\x5f\x00\x61\x00\x72\x00\x72\x00\x6f\x00\x77\x00\x5f\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x11\ +\x00\xb8\x8c\x07\ +\x00\x6c\ +\x00\x65\x00\x66\x00\x74\x00\x5f\x00\x61\x00\x72\x00\x72\x00\x6f\x00\x77\x00\x5f\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\ +\x00\x11\ +\x01\x1f\xc3\x87\ +\x00\x64\ +\x00\x6f\x00\x77\x00\x6e\x00\x5f\x00\x61\x00\x72\x00\x72\x00\x6f\x00\x77\x00\x5f\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\ +\x00\x0c\ +\x06\xe6\xe6\x67\ +\x00\x75\ +\x00\x70\x00\x5f\x00\x61\x00\x72\x00\x72\x00\x6f\x00\x77\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x1c\ \x08\x3f\xda\x67\ \x00\x63\ \x00\x68\x00\x65\x00\x63\x00\x6b\x00\x62\x00\x6f\x00\x78\x00\x5f\x00\x75\x00\x6e\x00\x63\x00\x68\x00\x65\x00\x63\x00\x6b\x00\x65\ \x00\x64\x00\x5f\x00\x66\x00\x6f\x00\x63\x00\x75\x00\x73\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x1a\ +\x03\x0e\xe4\x87\ +\x00\x63\ +\x00\x68\x00\x65\x00\x63\x00\x6b\x00\x62\x00\x6f\x00\x78\x00\x5f\x00\x63\x00\x68\x00\x65\x00\x63\x00\x6b\x00\x65\x00\x64\x00\x5f\ +\x00\x68\x00\x6f\x00\x76\x00\x65\x00\x72\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x20\ +\x09\xd7\x1f\xa7\ +\x00\x63\ +\x00\x68\x00\x65\x00\x63\x00\x6b\x00\x62\x00\x6f\x00\x78\x00\x5f\x00\x69\x00\x6e\x00\x64\x00\x65\x00\x74\x00\x65\x00\x72\x00\x6d\ +\x00\x69\x00\x6e\x00\x61\x00\x74\x00\x65\x00\x5f\x00\x66\x00\x6f\x00\x63\x00\x75\x00\x73\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x1f\ +\x0a\xae\x27\x47\ +\x00\x63\ +\x00\x68\x00\x65\x00\x63\x00\x6b\x00\x62\x00\x6f\x00\x78\x00\x5f\x00\x75\x00\x6e\x00\x63\x00\x68\x00\x65\x00\x63\x00\x6b\x00\x65\ +\x00\x64\x00\x5f\x00\x64\x00\x69\x00\x73\x00\x61\x00\x62\x00\x6c\x00\x65\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x18\ +\x03\x8e\xde\x67\ +\x00\x72\ +\x00\x69\x00\x67\x00\x68\x00\x74\x00\x5f\x00\x61\x00\x72\x00\x72\x00\x6f\x00\x77\x00\x5f\x00\x64\x00\x69\x00\x73\x00\x61\x00\x62\ +\x00\x6c\x00\x65\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x0f\ +\x0c\xe2\x68\x67\ +\x00\x74\ +\x00\x72\x00\x61\x00\x6e\x00\x73\x00\x70\x00\x61\x00\x72\x00\x65\x00\x6e\x00\x74\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x15\ +\x0f\xf3\xc0\x07\ +\x00\x75\ +\x00\x70\x00\x5f\x00\x61\x00\x72\x00\x72\x00\x6f\x00\x77\x00\x5f\x00\x64\x00\x69\x00\x73\x00\x61\x00\x62\x00\x6c\x00\x65\x00\x64\ +\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x12\ +\x03\x8d\x04\x47\ +\x00\x72\ +\x00\x69\x00\x67\x00\x68\x00\x74\x00\x5f\x00\x61\x00\x72\x00\x72\x00\x6f\x00\x77\x00\x5f\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\ +\x00\x67\ +\x00\x0e\ +\x0e\xde\xfa\xc7\ +\x00\x6c\ +\x00\x65\x00\x66\x00\x74\x00\x5f\x00\x61\x00\x72\x00\x72\x00\x6f\x00\x77\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x17\ +\x0c\xab\x51\x07\ +\x00\x64\ +\x00\x6f\x00\x77\x00\x6e\x00\x5f\x00\x61\x00\x72\x00\x72\x00\x6f\x00\x77\x00\x5f\x00\x64\x00\x69\x00\x73\x00\x61\x00\x62\x00\x6c\ +\x00\x65\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x1a\ +\x05\x11\xe0\xe7\ +\x00\x63\ +\x00\x68\x00\x65\x00\x63\x00\x6b\x00\x62\x00\x6f\x00\x78\x00\x5f\x00\x63\x00\x68\x00\x65\x00\x63\x00\x6b\x00\x65\x00\x64\x00\x5f\ +\x00\x66\x00\x6f\x00\x63\x00\x75\x00\x73\x00\x2e\x00\x70\x00\x6e\x00\x67\ " qt_resource_struct_v1 = b"\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ \x00\x00\x00\x16\x00\x02\x00\x00\x00\x20\x00\x00\x00\x03\ -\x00\x00\x03\xd0\x00\x00\x00\x00\x00\x01\x00\x00\x2c\x6c\ -\x00\x00\x04\xf6\x00\x00\x00\x00\x00\x01\x00\x00\x37\xb9\ -\x00\x00\x00\x74\x00\x00\x00\x00\x00\x01\x00\x00\x02\x00\ -\x00\x00\x04\xd2\x00\x00\x00\x00\x00\x01\x00\x00\x37\x17\ -\x00\x00\x05\xc0\x00\x00\x00\x00\x00\x01\x00\x00\x3a\xf0\ -\x00\x00\x02\x1c\x00\x00\x00\x00\x00\x01\x00\x00\x1a\x76\ -\x00\x00\x05\x48\x00\x00\x00\x00\x00\x01\x00\x00\x39\x06\ -\x00\x00\x01\xa4\x00\x00\x00\x00\x00\x01\x00\x00\x14\xf3\ -\x00\x00\x04\xa2\x00\x00\x00\x00\x00\x01\x00\x00\x36\x6d\ -\x00\x00\x04\x3c\x00\x00\x00\x00\x00\x01\x00\x00\x2e\x90\ -\x00\x00\x05\x1e\x00\x00\x00\x00\x00\x01\x00\x00\x38\x62\ -\x00\x00\x05\x6c\x00\x00\x00\x00\x00\x01\x00\x00\x39\xaa\ -\x00\x00\x05\xf2\x00\x00\x00\x00\x00\x01\x00\x00\x3c\x4f\ -\x00\x00\x02\xe2\x00\x00\x00\x00\x00\x01\x00\x00\x24\xb4\ -\x00\x00\x01\x6a\x00\x00\x00\x00\x00\x01\x00\x00\x10\xf0\ -\x00\x00\x04\x78\x00\x00\x00\x00\x00\x01\x00\x00\x2f\x39\ -\x00\x00\x02\x8a\x00\x00\x00\x00\x00\x01\x00\x00\x1d\x00\ -\x00\x00\x05\xa2\x00\x00\x00\x00\x00\x01\x00\x00\x3a\x4e\ +\x00\x00\x03\xaa\x00\x00\x00\x00\x00\x01\x00\x00\x33\x3b\ +\x00\x00\x03\xd2\x00\x00\x00\x00\x00\x01\x00\x00\x33\xe5\ +\x00\x00\x01\xb4\x00\x00\x00\x00\x00\x01\x00\x00\x15\xf1\ +\x00\x00\x03\x86\x00\x00\x00\x00\x00\x01\x00\x00\x32\x99\ +\x00\x00\x03\x26\x00\x00\x00\x00\x00\x01\x00\x00\x29\x59\ +\x00\x00\x00\x74\x00\x00\x00\x00\x00\x01\x00\x00\x0e\xbb\ +\x00\x00\x01\x30\x00\x00\x00\x00\x00\x01\x00\x00\x13\x37\ +\x00\x00\x04\x56\x00\x00\x00\x00\x00\x01\x00\x00\x36\x8b\ +\x00\x00\x01\xde\x00\x00\x00\x00\x00\x01\x00\x00\x16\x9b\ +\x00\x00\x00\xf4\x00\x00\x00\x00\x00\x01\x00\x00\x12\x8e\ +\x00\x00\x05\xa4\x00\x00\x00\x00\x00\x01\x00\x00\x44\xc9\ +\x00\x00\x05\x1a\x00\x00\x00\x00\x00\x01\x00\x00\x3e\x00\ +\x00\x00\x03\x58\x00\x00\x00\x00\x00\x01\x00\x00\x2a\xb8\ +\x00\x00\x01\x54\x00\x00\x00\x00\x00\x01\x00\x00\x13\xdb\ +\x00\x00\x06\x24\x00\x00\x00\x00\x00\x01\x00\x00\x46\xc1\ +\x00\x00\x02\xce\x00\x00\x00\x00\x00\x01\x00\x00\x1e\x26\ +\x00\x00\x00\x50\x00\x00\x00\x00\x00\x01\x00\x00\x07\xb1\ +\x00\x00\x03\xfa\x00\x00\x00\x00\x00\x01\x00\x00\x34\x8e\ +\x00\x00\x02\x4e\x00\x00\x00\x00\x00\x01\x00\x00\x1b\x7c\ +\x00\x00\x02\xf8\x00\x00\x00\x00\x00\x01\x00\x00\x25\x5a\ +\x00\x00\x04\x18\x00\x00\x00\x00\x00\x01\x00\x00\x35\x30\ +\x00\x00\x02\x0e\x00\x00\x00\x00\x00\x01\x00\x00\x17\x45\ +\x00\x00\x04\x90\x00\x00\x00\x00\x00\x01\x00\x00\x3a\xa1\ +\x00\x00\x04\xd6\x00\x00\x00\x00\x00\x01\x00\x00\x3c\x86\ \x00\x00\x00\x28\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ -\x00\x00\x00\x9e\x00\x00\x00\x00\x00\x01\x00\x00\x02\xaa\ -\x00\x00\x06\x20\x00\x00\x00\x00\x00\x01\x00\x00\x44\x30\ -\x00\x00\x03\x04\x00\x00\x00\x00\x00\x01\x00\x00\x25\x5d\ -\x00\x00\x03\x44\x00\x00\x00\x00\x00\x01\x00\x00\x29\x94\ -\x00\x00\x03\xf8\x00\x00\x00\x00\x00\x01\x00\x00\x2d\x16\ -\x00\x00\x00\xfc\x00\x00\x00\x00\x00\x01\x00\x00\x07\x4c\ -\x00\x00\x02\xae\x00\x00\x00\x00\x00\x01\x00\x00\x24\x0a\ -\x00\x00\x02\x56\x00\x00\x00\x00\x00\x01\x00\x00\x1c\x56\ -\x00\x00\x03\x8a\x00\x00\x00\x00\x00\x01\x00\x00\x2b\x79\ -\x00\x00\x01\xde\x00\x00\x00\x00\x00\x01\x00\x00\x19\x09\ -\x00\x00\x03\xae\x00\x00\x00\x00\x00\x01\x00\x00\x2b\xc2\ -\x00\x00\x01\x24\x00\x00\x00\x00\x00\x01\x00\x00\x0e\xfd\ -\x00\x00\x00\xcc\x00\x00\x00\x00\x00\x01\x00\x00\x06\xa9\ +\x00\x00\x02\x9a\x00\x00\x00\x00\x00\x01\x00\x00\x1d\x7c\ +\x00\x00\x05\xf0\x00\x00\x00\x00\x00\x01\x00\x00\x46\x17\ +\x00\x00\x05\x50\x00\x00\x00\x00\x00\x01\x00\x00\x3e\xa4\ +\x00\x00\x01\x76\x00\x00\x00\x00\x00\x01\x00\x00\x14\x84\ +\x00\x00\x05\xce\x00\x00\x00\x00\x00\x01\x00\x00\x45\x6d\ +\x00\x00\x00\xae\x00\x00\x00\x00\x00\x01\x00\x00\x10\x9b\ +\x00\x00\x05\x74\x00\x00\x00\x00\x00\x01\x00\x00\x44\x26\ " qt_resource_struct_v2 = b"\ @@ -1403,72 +1487,73 @@ qt_resource_struct_v2 = b"\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x00\x16\x00\x02\x00\x00\x00\x20\x00\x00\x00\x03\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x03\xd0\x00\x00\x00\x00\x00\x01\x00\x00\x2c\x6c\ -\x00\x00\x01\x79\xcb\xc2\x92\x54\ -\x00\x00\x04\xf6\x00\x00\x00\x00\x00\x01\x00\x00\x37\xb9\ -\x00\x00\x01\x79\xcb\xc2\x92\x49\ -\x00\x00\x00\x74\x00\x00\x00\x00\x00\x01\x00\x00\x02\x00\ -\x00\x00\x01\x79\xcb\xc2\x92\x46\ -\x00\x00\x04\xd2\x00\x00\x00\x00\x00\x01\x00\x00\x37\x17\ -\x00\x00\x01\x79\xcb\xc2\x92\x68\ -\x00\x00\x05\xc0\x00\x00\x00\x00\x00\x01\x00\x00\x3a\xf0\ -\x00\x00\x01\x7c\xf0\xa2\x65\xc4\ -\x00\x00\x02\x1c\x00\x00\x00\x00\x00\x01\x00\x00\x1a\x76\ -\x00\x00\x01\x7c\xf0\xa1\xd7\xf4\ -\x00\x00\x05\x48\x00\x00\x00\x00\x00\x01\x00\x00\x39\x06\ -\x00\x00\x01\x79\xcb\xc2\x92\x58\ -\x00\x00\x01\xa4\x00\x00\x00\x00\x00\x01\x00\x00\x14\xf3\ -\x00\x00\x01\x7c\xf0\xa3\x96\xef\ -\x00\x00\x04\xa2\x00\x00\x00\x00\x00\x01\x00\x00\x36\x6d\ -\x00\x00\x01\x79\xcb\xc2\x92\x47\ -\x00\x00\x04\x3c\x00\x00\x00\x00\x00\x01\x00\x00\x2e\x90\ -\x00\x00\x01\x79\xcb\xc2\x92\x47\ -\x00\x00\x05\x1e\x00\x00\x00\x00\x00\x01\x00\x00\x38\x62\ -\x00\x00\x01\x79\xcb\xc2\x92\x5e\ -\x00\x00\x05\x6c\x00\x00\x00\x00\x00\x01\x00\x00\x39\xaa\ -\x00\x00\x01\x79\xcb\xc2\x92\x5d\ -\x00\x00\x05\xf2\x00\x00\x00\x00\x00\x01\x00\x00\x3c\x4f\ -\x00\x00\x01\x79\xcb\xc2\x92\x3a\ -\x00\x00\x02\xe2\x00\x00\x00\x00\x00\x01\x00\x00\x24\xb4\ -\x00\x00\x01\x79\xcb\xc2\x92\x48\ -\x00\x00\x01\x6a\x00\x00\x00\x00\x00\x01\x00\x00\x10\xf0\ -\x00\x00\x01\x7c\xf0\xa3\xb9\x9b\ -\x00\x00\x04\x78\x00\x00\x00\x00\x00\x01\x00\x00\x2f\x39\ -\x00\x00\x01\x79\xcb\xc2\x92\x45\ -\x00\x00\x02\x8a\x00\x00\x00\x00\x00\x01\x00\x00\x1d\x00\ -\x00\x00\x01\x79\xcb\xc2\x92\x40\ -\x00\x00\x05\xa2\x00\x00\x00\x00\x00\x01\x00\x00\x3a\x4e\ -\x00\x00\x01\x79\xcb\xc2\x92\x63\ +\x00\x00\x03\xaa\x00\x00\x00\x00\x00\x01\x00\x00\x33\x3b\ +\x00\x00\x01\x7b\xe9\x78\x46\xdd\ +\x00\x00\x03\xd2\x00\x00\x00\x00\x00\x01\x00\x00\x33\xe5\ +\x00\x00\x01\x7b\xe9\x78\x46\xdb\ +\x00\x00\x01\xb4\x00\x00\x00\x00\x00\x01\x00\x00\x15\xf1\ +\x00\x00\x01\x7b\xe9\x78\x46\xd9\ +\x00\x00\x03\x86\x00\x00\x00\x00\x00\x01\x00\x00\x32\x99\ +\x00\x00\x01\x7b\xe9\x78\x46\xe0\ +\x00\x00\x03\x26\x00\x00\x00\x00\x00\x01\x00\x00\x29\x59\ +\x00\x00\x01\x7d\x0a\x8c\xfa\xc7\ +\x00\x00\x00\x74\x00\x00\x00\x00\x00\x01\x00\x00\x0e\xbb\ +\x00\x00\x01\x7d\x0a\x8c\xfa\xc5\ +\x00\x00\x01\x30\x00\x00\x00\x00\x00\x01\x00\x00\x13\x37\ +\x00\x00\x01\x7b\xe9\x78\x46\xdd\ +\x00\x00\x04\x56\x00\x00\x00\x00\x00\x01\x00\x00\x36\x8b\ +\x00\x00\x01\x7d\x0a\x8c\xfa\xc4\ +\x00\x00\x01\xde\x00\x00\x00\x00\x00\x01\x00\x00\x16\x9b\ +\x00\x00\x01\x7b\xe9\x78\x46\xda\ +\x00\x00\x00\xf4\x00\x00\x00\x00\x00\x01\x00\x00\x12\x8e\ +\x00\x00\x01\x7b\xe9\x78\x46\xd9\ +\x00\x00\x05\xa4\x00\x00\x00\x00\x00\x01\x00\x00\x44\xc9\ +\x00\x00\x01\x7b\xe9\x78\x46\xde\ +\x00\x00\x05\x1a\x00\x00\x00\x00\x00\x01\x00\x00\x3e\x00\ +\x00\x00\x01\x7b\xe9\x78\x46\xde\ +\x00\x00\x03\x58\x00\x00\x00\x00\x00\x01\x00\x00\x2a\xb8\ +\x00\x00\x01\x7b\xe9\x78\x46\xd7\ +\x00\x00\x01\x54\x00\x00\x00\x00\x00\x01\x00\x00\x13\xdb\ +\x00\x00\x01\x7b\xe9\x78\x46\xda\ +\x00\x00\x06\x24\x00\x00\x00\x00\x00\x01\x00\x00\x46\xc1\ +\x00\x00\x01\x7d\x0a\x8c\xfa\xc4\ +\x00\x00\x02\xce\x00\x00\x00\x00\x00\x01\x00\x00\x1e\x26\ +\x00\x00\x01\x7b\xe9\x78\x46\xd8\ +\x00\x00\x00\x50\x00\x00\x00\x00\x00\x01\x00\x00\x07\xb1\ +\x00\x00\x01\x7b\xe9\x78\x46\xd8\ +\x00\x00\x03\xfa\x00\x00\x00\x00\x00\x01\x00\x00\x34\x8e\ +\x00\x00\x01\x7b\xe9\x78\x46\xdf\ +\x00\x00\x02\x4e\x00\x00\x00\x00\x00\x01\x00\x00\x1b\x7c\ +\x00\x00\x01\x7d\x0a\x8c\xfa\xc5\ +\x00\x00\x02\xf8\x00\x00\x00\x00\x00\x01\x00\x00\x25\x5a\ +\x00\x00\x01\x7d\x0a\x8c\xfa\xc2\ +\x00\x00\x04\x18\x00\x00\x00\x00\x00\x01\x00\x00\x35\x30\ +\x00\x00\x01\x7d\x0a\x8c\xfa\xc8\ +\x00\x00\x02\x0e\x00\x00\x00\x00\x00\x01\x00\x00\x17\x45\ +\x00\x00\x01\x7d\x0a\x8c\xfa\xc3\ +\x00\x00\x04\x90\x00\x00\x00\x00\x00\x01\x00\x00\x3a\xa1\ +\x00\x00\x01\x7d\x0a\x8c\xfa\xc6\ +\x00\x00\x04\xd6\x00\x00\x00\x00\x00\x01\x00\x00\x3c\x86\ +\x00\x00\x01\x7d\x0a\x8c\xfa\xc7\ \x00\x00\x00\x28\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ -\x00\x00\x01\x7c\xf0\xa2\xf1\xbb\ -\x00\x00\x00\x9e\x00\x00\x00\x00\x00\x01\x00\x00\x02\xaa\ -\x00\x00\x01\x7c\xf0\xa3\xe3\x5c\ -\x00\x00\x06\x20\x00\x00\x00\x00\x00\x01\x00\x00\x44\x30\ -\x00\x00\x01\x7c\xf0\xa2\x42\xcc\ -\x00\x00\x03\x04\x00\x00\x00\x00\x00\x01\x00\x00\x25\x5d\ -\x00\x00\x01\x7c\xf0\xa3\x72\x55\ -\x00\x00\x03\x44\x00\x00\x00\x00\x00\x01\x00\x00\x29\x94\ -\x00\x00\x01\x7c\xf0\xa1\xba\x22\ -\x00\x00\x03\xf8\x00\x00\x00\x00\x00\x01\x00\x00\x2d\x16\ -\x00\x00\x01\x7c\xf0\xa3\x17\x26\ -\x00\x00\x00\xfc\x00\x00\x00\x00\x00\x01\x00\x00\x07\x4c\ -\x00\x00\x01\x79\xcb\xc2\x92\x34\ -\x00\x00\x02\xae\x00\x00\x00\x00\x00\x01\x00\x00\x24\x0a\ -\x00\x00\x01\x79\xcb\xc2\x92\x53\ -\x00\x00\x02\x56\x00\x00\x00\x00\x00\x01\x00\x00\x1c\x56\ -\x00\x00\x01\x79\xcb\xc2\x92\x48\ -\x00\x00\x03\x8a\x00\x00\x00\x00\x00\x01\x00\x00\x2b\x79\ -\x00\x00\x01\x7c\xe1\xd3\xcf\x43\ -\x00\x00\x01\xde\x00\x00\x00\x00\x00\x01\x00\x00\x19\x09\ -\x00\x00\x01\x7c\xf0\x9d\x46\x1e\ -\x00\x00\x03\xae\x00\x00\x00\x00\x00\x01\x00\x00\x2b\xc2\ -\x00\x00\x01\x79\xcb\xc2\x92\x4e\ -\x00\x00\x01\x24\x00\x00\x00\x00\x00\x01\x00\x00\x0e\xfd\ -\x00\x00\x01\x7c\xf0\xa1\x99\xa2\ -\x00\x00\x00\xcc\x00\x00\x00\x00\x00\x01\x00\x00\x06\xa9\ -\x00\x00\x01\x79\xcb\xc2\x92\x68\ +\x00\x00\x01\x7b\xe9\x78\x46\xd7\ +\x00\x00\x02\x9a\x00\x00\x00\x00\x00\x01\x00\x00\x1d\x7c\ +\x00\x00\x01\x7b\xe9\x78\x46\xdc\ +\x00\x00\x05\xf0\x00\x00\x00\x00\x00\x01\x00\x00\x46\x17\ +\x00\x00\x01\x7b\xe9\x78\x46\xdb\ +\x00\x00\x05\x50\x00\x00\x00\x00\x00\x01\x00\x00\x3e\xa4\ +\x00\x00\x01\x7d\x0a\xb7\x38\x27\ +\x00\x00\x01\x76\x00\x00\x00\x00\x00\x01\x00\x00\x14\x84\ +\x00\x00\x01\x7d\x0a\x8c\xfa\xc9\ +\x00\x00\x05\xce\x00\x00\x00\x00\x00\x01\x00\x00\x45\x6d\ +\x00\x00\x01\x7b\xe9\x78\x46\xdc\ +\x00\x00\x00\xae\x00\x00\x00\x00\x00\x01\x00\x00\x10\x9b\ +\x00\x00\x01\x7d\x0a\x8c\xfa\xc6\ +\x00\x00\x05\x74\x00\x00\x00\x00\x00\x01\x00\x00\x44\x26\ +\x00\x00\x01\x7b\xe9\x78\x46\xdf\ " + qt_version = [int(v) for v in QtCore.qVersion().split('.')] if qt_version < [5, 8, 0]: rcc_version = 1 diff --git a/openpype/style/pyside2_resources.py b/openpype/style/pyside2_resources.py index 6dd843a6ce..2aa84d04f1 100644 --- a/openpype/style/pyside2_resources.py +++ b/openpype/style/pyside2_resources.py @@ -2,309 +2,28 @@ # Resource object code # -# Created: pá lis 5 16:14:21 2021 +# Created: Wed Nov 10 17:40:15 2021 # by: The Resource Compiler for PySide2 (Qt v5.12.5) # # WARNING! All changes made in this file will be lost! from PySide2 import QtCore + qt_resource_data = b"\ -\x00\x00\x04\x12\ +\x00\x00\x00\xa5\ \x89\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ -\x00\x000\x00\x00\x000\x08\x06\x00\x00\x00W\x02\xf9\x87\ -\x00\x00\x00\x09pHYs\x00\x00\x0b\x13\x00\x00\x0b\x13\ -\x01\x00\x9a\x9c\x18\x00\x00\x03\xc4IDATh\x81\xed\ -\x9a_\x88\x94U\x18\xc6\x7f3;\x1a\x0b\x19\x15f\x17\ -\xca\x03IPM\x09J7Q^D)&f\x05[\ -\xb9\xd2\x82\xb1P\x17\x91$t\x11\x08z\xa1D\x17]\ -T\x94\x04\xd6E\xe1\xa2\x15L\xb1\x18\x99\xfd%\xd8\x08\ -\x82nj]\xa4\x22\xe2\x81\xa8\x96\xd5(\xe8\x9f\xae\xd5\ -\xc5\xf9\xb6\xc6\xd9\xf9\xbesf\xc6vf\xc0\xdf\xdd\x9c\ -\xef=\xefy\x9fs\xe6\x9c\xf3~\xdf9%2Fj\ -SK\x81\xdd\xc0Z\xe0:`\x11\xbd\xc5i`\x12\x98\ -\x00\xf6\x8c\x0dUg\x00J\x00#\xb5\xa9[\x80C\xc0\ -\xb2\xae\x85\xd7\x1a\xd3\xc0\xd6\xb1\xa1\xea\x07\xa5\xac\xe7\x8f\ -\xd1?\xc1\xcf1\x0d\x5c[&\xfcm\xfa-x\x081\ -\xef\xaa\x10\xfe\xf3\x8d\x9cY\xe0`R\x19h\xf8\xbd\xb6\ -B\x98\xb0\xf5\x9c\x19\x1b\xaaV\x16(\xa0\x96\x18\xa9M\ -\xcdr\xb6\x88Uezo\xb5i\x85E\xe5nG\xd0\ -)\xe7\x05t\x9b\xf3\x02\xfe\x0fl\xdfc\xfb\x90\xed\xf5\ -1\xdb\x9e\x13`\xfb\x11\xe05`\x188j{\x9f\xed\ -\xdc\x95\xb2\xa7\x04\xd8\x1e\x06\x9e\xaa+*\x01\x0f\x01/\ -\xe4\xd5\xe9\x19\x01\xb6\xd7\x01/\x93%\x98\x0dl\xb3\xfd\ -h\xb3z=!\xc0\xf6\xf5\xc0\xeb\xc0\xe2\x02\xb3\x8d\xcd\ -\x0a\xbb.\xc0\xf6\x95\xc0[\xc0\x92\x88\xe9\x8b\xcd\x0a\xbb\ -*\xc0\xf6\xe5\xc0Q\xe2\xd9\xf0\x93\x92^i\xf6\xa0k\ -\x02l/\x01\x8e\x00+#\xa6\x07\x80\xc7\xf2\x1evE\ -\x80\xed\xc5\xc0\x1b\xc0\x9a\x88\xe9\xdb\xc0\xa8\xa4\xbf\xf3\x0c\ -\x16\x5c\x80\xed2\xa1Wo\x8d\x98~\x0a\xdc-i\xb6\ -\xc8\xa8\x1b#\xf04po\xc4\xe6K`\x93\xa4_c\ -\xce\x92\x04\xd8.e=\xd7\x11\xb6w\x02\xdb#f\xdf\ -\x03\x1b$\xcd\xa4\xf8,\x0c*\x0b|\x07\xf0;0i\ -{UR\xa4\xcd}\x8d\x02\x8fG\xcc~\x06n\x93\xf4\ -m\xaa\xdf\x5c\x01Y\xfe1N\xd8\xda/\x00\xae\x01\xde\ -\xb3}U\xaa\xf3:_\x9b\x81\xfd\x11\xb3?\x81;%\ -}\xde\x8a\xef\xa2\x11x\x0e\xd8\xdcP\xb6\x0cx\xdf\xf6\ -\x15\xa9\x0d\xd8\xbe\x11x\x95\xf9/\xe4\xf5\xfc\x05\xdc'\ -\xe9\xa3T\xbfs4\x15\x90\xf5\xd8\x839u\x96\x13F\ -by\xcc\xb9\xed*p\x18\x18\x8c\x98>,\xa9\x16\xf3\ -\xd7\x8c\xbc\x11\xb84Ro%A\xc4ey\x06\xb6W\ -\x10v\xd9\x98\xaf\xbd\x92\x9e\x8f\xd8\xe4\x92'\xe0 a\ -\x1d.\xe2j\xe0\x1d\xdb\x177>\xb0}\x09!\xf8\x15\ -\x11\x1f\xfb%\xed\x8eFY@S\x01\x92N\x13\xb2\xbf\ -/\x22\xf5W\x03Gl_8W`{\x90\xf0\xb7\xa9\ -F\xea\x8e\x13r\xfd\x8e\xc8\x9d\xc4\x92N\x02\xeb\x81\xaf\ -\x22>n\x00\x0e\xdb\x1e\xb4=@\x98\xb07E\xeaL\ -\x00\xc3\x92:\xfe\x02X\xb8\x0fH\xfa\x11X\x078\xe2\ -\xe7f\xa0FX*\x1bW\xaeF&\x81;$\xfd\x91\ -\x18c!\xd1\xddU\x92\x09y\xcb\x0f\x11\xd3\x8d\xc0h\ -\xc4\xc6\x84\x8d\xea\xa7\xb4\xf0\xe2$\xa5\x07\x92\xbe&\x8c\ -\xc4\x89\x0e\xda:IH\x11\xbe\xeb\xc0\xc7<\x92\xf3\x1b\ -I\xc7\x80\x0d\xc0/m\xb4\xf3\x1bp\xbb\xa4\xe3m\xd4\ --\xa4\xa5\x04M\xd2g\xc0\xa6,\xa0Tf\x81-\x92\ ->i\xa5\xadTZ\xce0%M\x00w\x11r\x97\x14\ -\x1e\x90\xf4f\xab\xed\xa4\xd2V\x8a,\xe9]`\x0b\xa1\ -w\x8b\xd8)\xe9\xa5v\xdaH\xa5\xed\x1c_\xd28\xb0\ -\x8d\x90\x885\xe3YIO\xb4\xeb?\x95\x8e^R$\ -\x1d\x04\xeeg\xfe\x9c\xd8\x07\xec\xe8\xc4w*\x1d\x1f%\ -I:`\xfbc\xc2\x06v\x11\xf0a6O\x16\x84s\ -r\x16&\xe9\x1b\xe0\x99s\xe1\xabU\xca\x84\x13\xf0~\ -\xe5T\x85\x90\x9b\xd4\x7f\x9f\x19\xc8N\x03{\x91\xc6\xb7\ -\xba\xc9\x0a!3l\xfc\xc0T\xf4\xfa\xd7KL\x94\x81\ -=\x84c\xfb~c\x1a\xd8[\xcen}l\xa5\xbfD\ -\xcc]\xf6\x98\xf9\xf70!\xbb\xf4\xb1\x8b\xff\xae\xdb\x14\ -}\xab\xef\x06\xa78\xfb\xba\xcd\x09\x80\x7f\x00\xc4\x1e\x10\ -)3[\x85\xf7\x00\x00\x00\x00IEND\xaeB`\ -\x82\ -\x00\x00\x07\x06\ -\x89\ -PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ -\x00\x00\x0a\x00\x00\x00\x07\x08\x06\x00\x00\x001\xac\xdcc\ -\x00\x00\x04\xb0iTXtXML:com.\ -adobe.xmp\x00\x00\x00\x00\x00\x0a\x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a\x0a\x85\x9d\x9f\x08\x00\x00\x01\x83\ -iCCPsRGB IEC6196\ -6-2.1\x00\x00(\x91u\x91\xcf+DQ\x14\ -\xc7?fh\xfc\x18\x8dba1e\x12\x16B\x83\x12\ -\x1b\x8b\x99\x18\x0a\x8b\x99Q~mf\x9ey3j\xde\ -x\xbd7\xd2d\xabl\xa7(\xb1\xf1k\xc1_\xc0V\ -Y+E\xa4d\xa7\xac\x89\x0dz\xce\x9bQ#\x99s\ -;\xf7|\xee\xf7\xdes\xba\xf7\x5cpD\xd3\x8afV\ -\xfaA\xcbd\x8dp(\xe0\x9b\x99\x9d\xf3\xb9\x9e\xa8\xa2\ -\x85\x1a:\xf1\xc6\x14S\x9f\x8c\x8cF)k\xef\xb7T\ -\xd8\xf1\xba\xdb\xaeU\xfe\xdc\xbfV\xb7\x980\x15\xa8\xa8\ -\x16\x1eVt#+<&<\xb1\x9a\xd5m\xde\x12n\ -RR\xb1E\xe1\x13\xe1.C.(|c\xeb\xf1\x22\ -?\xdb\x9c,\xf2\xa7\xcdF4\x1c\x04G\x83\xb0/\xf9\ -\x8b\xe3\xbfXI\x19\x9a\xb0\xbc\x9c6-\xbd\xa2\xfc\xdc\ -\xc7~\x89;\x91\x99\x8eHl\x15\xf7b\x12&D\x00\ -\x1f\xe3\x8c\x10d\x80^\x86d\x1e\xa0\x9b>zdE\ -\x99|\x7f!\x7f\x8ae\xc9Ud\xd6\xc9a\xb0D\x92\ -\x14Y\xbaD]\x91\xea\x09\x89\xaa\xe8\x09\x19irv\ -\xff\xff\xf6\xd5T\xfb\xfb\x8a\xd5\xdd\x01\xa8z\xb4\xac\xd7\ -vpm\xc2W\xde\xb2>\x0e,\xeb\xeb\x10\x9c\x0fp\ -\x9e)\xe5/\xef\xc3\xe0\x9b\xe8\xf9\x92\xd6\xb6\x07\x9eu\ -8\xbd(i\xf1m8\xdb\x80\xe6{=f\xc4\x0a\x92\ -S\xdc\xa1\xaa\xf0r\x0c\xf5\xb3\xd0x\x05\xb5\xf3\xc5\x9e\ -\xfd\xecst\x07\xd15\xf9\xaaK\xd8\xd9\x85\x0e9\xef\ -Y\xf8\x06\x8e\xfdg\xf8\xfd\x8a\x18\x97\x00\x00\x00\x09p\ -HYs\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ -\x00\x00\x00mIDAT\x18\x95u\xcf\xc1\x09\xc2P\ -\x10\x84\xe1\xd7\x85\x07\x9b\xd0C@\xd2\x82x\x14{0\ -W!\x8d\x84`?bKzH\xcc\x97\x83\xfb0\x04\ -\xdf\x9c\x86\x7fg\x99\xdd\x84\x0d\xaaT\x10jl\x13\x1e\ -\xbe\xba\xfe\x0951{\xe6\x8d\x0f&\x1c\x17\xa1S\xb0\ -\x11\x87\x0c/\x01\x07\xec\xb0\x0f?\xe1\xbc\xaei\xa3\xe6\ -\x85w\xf8[\xe9\xf0\xbb\x9f\xfa\xd2\x839\xdc\xa3[\xf3\ -\x19.\xa8\x89\xb50\xf7C\xa0\x00\x00\x00\x00IEN\ -D\xaeB`\x82\ -\x00\x00\x00\xa6\ -\x89\ -PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ -\x00\x00\x06\x00\x00\x00\x09\x08\x04\x00\x00\x00\xbb\x93\x95\x16\ +\x00\x00\x09\x00\x00\x00\x06\x08\x04\x00\x00\x00\xbb\xce|N\ \x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x02bKGD\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09p\ +\x02bKGD\x00\x9cS4\xfc]\x00\x00\x00\x09p\ HYs\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ -\x00\x00\x00\x07tIME\x07\xdc\x08\x17\x14\x1d\x00\xb0\ -\xd55\xa3\x00\x00\x00*IDAT\x08\xd7c`\xc0\ -\x06\xfe\x9fg``B0\xa1\x1c\x08\x93\x81\x81\x09\xc1\ -d``b``4D\xe2 s\x19\x90\x8d@\x02\ -\x00d@\x09u\x86\xb3\xad\x9c\x00\x00\x00\x00IEN\ -D\xaeB`\x82\ -\x00\x00\x01\xe1\ -\x89\ -PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ -\x00\x000\x00\x00\x000\x08\x06\x00\x00\x00W\x02\xf9\x87\ -\x00\x00\x00\x09pHYs\x00\x00\x0b\x13\x00\x00\x0b\x13\ -\x01\x00\x9a\x9c\x18\x00\x00\x01\x93IDATh\x81\xed\ -\x9a;N\xc3@\x10\x86\xbfq\x1c*\xe8\x10\xe56\x94\ -\xd0\xa4\xa1\x8a(\x22\x0a\x0aD\x9f\x9e\x0bp\x80Pp\ -\x01.\xc0\x15h\x80\x13\xa0\x1c!P\x91f\xbbD\xa1\ -B4yh(l\x1e\xb1\xfcH\x08\xc9\xda\xd2~\x9d\ -w\x5c\xfc\x9f\xb3\x1e9\xda\x11bTu\x17\xb8\x02\x9a\ -\xc0!P\xa7\x5cL\x80\x1e\xd0\x05\xaeEd\xf4]Q\ -\xd5\x96\xaa\x0e\xb4:\x0cT\xb5\x05 \x1a=\xf9g`\ -\xcf\xc5c]\x81!p\x10\x10m\x9b\xaa\x85\x87(s\ -'$\xda\xf3If\x1b\x0e\xb3(\xb5\xc4uSTu\ -\xcc\xfc\x0b;\x13\x91p\x83\xa1\x16FU\xa7\xccKL\ -\x02\xca\xd7m\x96\xa1\x1e\xb8N\xb0*^\xc05^\xc0\ -5^\xc05^\xc05^\xc05^\xc05\x85\x9f\xcd\ -\xd6\xda\x13\xe0\x08\xd8^\x7f\x9c9\xa6\xc0\x0b\xf0`\x8c\ -\xf9\xc8\xbaITU\x13k3\x11\x09\xad\xb5!p\x07\ -\x9c\xaf1\xe4\x22\xf4\x81Sc\xcck\xca\xff\x81\xdc-\ -t\x89\xfb\xf0\x00\xfb\xc0mV1O\xe0\xec\xff\xb3\xfc\ -\x99ck\xedNZ\xa1\xf2/q\x9e\xc0\xe3\xc6R\x14\ -\xf3d\x8cyO+\xe4\x09\xdc\x00\xf7\xeb\xc9\xb3\x14}\ -\xe0\x22\xab\x98\xd9\x85\xbe.\xca\xd4F\xd3\xbaP\xa1@\ -\x99X\xb6\x8dV\x02/\xe0\x1a/\xe0\x1a/\xe0\x1a/\ -\xe0\x1a/\xe0\x1a/\xe0\x9a\x80\xe8\x04\xbc\xaa\x8cC\xa2\ -\xe3\xfb\xc6\xaf\xc5Z\xfc\xd9ZF\x92\xc7\xac\xbd\x90h\ -\xf6\xa0QpcY\xe9V\x7f\xd4 \x9e\xfah\xc7\x0b\ -Ua\x08\xb4Ed$_+\xf1/\xd1\xe1g\xdcf\ -\xcbQ\xb8,\xc6\xcc\x8f\xdb\xbc\x01|\x02mw#\xb3\ -\xd4\x95Sv\x00\x00\x00\x00IEND\xaeB`\x82\ -\ -\x00\x00\x03\xff\ -\x89\ -PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ -\x00\x000\x00\x00\x000\x08\x06\x00\x00\x00W\x02\xf9\x87\ -\x00\x00\x00\x09pHYs\x00\x00\x0b\x13\x00\x00\x0b\x13\ -\x01\x00\x9a\x9c\x18\x00\x00\x03\xb1IDATh\x81\xed\ -\x9aOh\x1eE\x18\xc6\x7f\x9b&\x85\x82\x15\x15\xabB\ -\xcb\x03\x06\x05\xa9\x0a\x8a\xb7R<\xd4\x96\xaa\xb5\xe0A\ -\xad\xc5C%\xa0\x07Q\xccM(\xb4\x07E\x80\xae9\x1f\xc0\xff\ -\x81\xed\xbbm\xbff{W\xca6\x0b!\x84BY\xa7\ -\xdb\xa8\xedG\x81#\xf9\xcf\x00\x1c\x05&%-\x94l\ -\xa3\xc35\x03\xb6\xef\x05\x9e\xe9)\xca\x80\x87\x80\x17\xab\ -\xda\x0c\xcd\x81e{'\xf0\x0aQt\x91\x03\xb6\xbf*\ -k7\x143`\xfb&\xe0M`}\x8d\xd9me\x85\ -\x9d\x07`\xfb*\xe0]`c\xc2\xf4\xa5\xb2\xc2N\x03\ -\xb0}9\xf0>\xe9l\xf8iI\xaf\x97Ut\x16\x80\ -\xed\x8d\xc0\x140\x9e0}\x15x\xac\xaa\xb2\x93\x00l\ -\xaf\x07\xde\x02nL\x98\xbe\x07LH*n\xf5K\xac\ -z\x00\xb6G\x88\xa3zK\xc2\xf4\x0b\xe0.I\x8bu\ -F]\xcc\xc0\x11\xe0\x9e\x84\xcd\xb7\xc0\x1eI\xbf\xa5\x9c\ -5\x0a\xc0v\x96\x8f\xdc@\xd8>\x08<\x920\xfb\x11\ -\xd8-i.a\x07$\x02\xc8\x85O\x02\x7f\x003\xb6\ -\xafo\xa4\xb4\xdc\xd7\x04\xf0d\xc2\xec\x17\xe0VI\xdf\ -7\xf5[\x99\x0b\xd9\x1e\x03\x8e\x03{{\xeaf\x81\x9b\ -%}\xd3\xb4\x03\x00\xdb{\x89\x8b\xb6x\xa7\xed\xe5,\ -q\xe4?\xab2\xe87\x17z\x8e\xe5\xe2!\xee\xd7\x1f\ -\xdb\xbe\xb2Vq\x0f\xb6\xb7\x01o\x14;.\xf0\x17p\ -_\x9d\xf8*J\x03\xc8G\xec\xc1\x8a6\x9b\x81\x8fl\ -oN9\xb7\xbd\x15x\x1b\xd8\x900}X\xd2\xf1\x94\ -\xbf2\xaaf\xe0\x92D\xbbqb\x10\x9b\xaa\x0clo\ -!\x9e\xb2)_OH:\x9a\xb0\xa9\xa4*\x80c\xc4\ -}\xb8\x8ek\x80\x0fl_T\xac\xb0}1Q\xfc\x96\ -\x84\x8f\x17$\x1dN\xaa\xac\xa14\x00I\x0b\xc4\xec\xaf\ -4\x85\xed\xe1\x06`\xca\xf6\x05\xff\x14\xd8\xde@|l\ -\xb6&\xda\x9e \xe6\xfa\x03Q{#\xcb\x93\xad\x93\xc0\ -\xd5\x09?\x9f\x02\xb7\x03\xf3\xc4\xdd\xa6\xb8\xf8\x8bL\x03\ -\xbb$\xfd\xd9\x8f\xd8\xb2](y\xa5\xb4-b\x10J\ -\xf8\x9f\x22\x1eB\x13\x09\xbb\x19\xe2V\xfcs#\xd5=\ -\xb4\x0a\x00\x96r\xf6\x93\xc0\x15\xfdvZ\xc0\xc06I\ -?\xb4i\xdc\xfaN,\xe9;`'p\xa6M\xc79\ -?\x11\x0f\xaaV\xe2\xabh\x9c\xdfH:\x05\xec\x06~\ -m\xd1\xcf\xef\xc0\x1d\x92\xben\xd1\xb6\x96\xbe\x124I\ -_\x02{rAMY\x04\xf6I\xfa\xbc\x9f\xbe\x9a\xd2\ -w\x86)i\x1a\xb8\x93\x98\xbb4\xe1\x01I\xef\xf4\xdb\ -OSZ\xa5\xc8\x92>\x04\xf6\x11G\xb7\x8e\x83\x92^\ -n\xd3GSZ\xe7\xf8\x92N\x00\x07\x88\x89X\x19\xcf\ -Jz\xaa\xad\xff\xa6\x0ctI\x91t\x0c\xb8\x9f\xff\xae\ -\x89\xe7\x81\xc9A|7eE\xde\x8d\xda\x1e'\x9e\xbe\ -\x17\x02\x9f\xe4\xebd\xc5i}\x90\x0d\x0bU\x07\xd9B\ -7rV\x84\xf9Qbn\xd2\xfb~f]\x1e\xe90\ -R\xbc\xd5\xcd\x8c\x123\xc3\xe2\x0b\xa6\xba\xeb\xdf01\ -\xbd\xf6?5\xc8\xbf\xfa\xd8\x9f\x17\xac\x15f\x81\xfdY\ -\x96\xcd-\xfd\x99\x90\xcf\xc4!\xfe\xfd\xdc\xa6\xee]}\ -\x17\xcc\xb3\xfcs\x9b3\x00\x7f\x03\xd9\x1a\xfb\xdb\xbb\xa7\ -\x8f\x07\x00\x00\x00\x00IEND\xaeB`\x82\ +\x00\x00\x00\x07tIME\x07\xdc\x08\x17\x0b\x02\x04m\ +\x98\x1bi\x00\x00\x00)IDAT\x08\xd7c`\xc0\ +\x00\x8c\x0c\x0c\xff\xcf\xa3\x08\x18220 \x0b2\x1a\ +200B\x98\x10AFC\x14\x13P\xb5\xa3\x01\x00\ +\xd6\x10\x07\xd2/H\xdfJ\x00\x00\x00\x00IEND\ +\xaeB`\x82\ \x00\x00\x07\xad\ \x89\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ @@ -430,87 +149,76 @@ v)`\x8b\x07>\xa8\xe6\xd1\xfe\x0b\x9d\x85\x8eW\x0d\ ^x\xa2\x9e\x0e\xa7 tG9\x1d\xf6\xe1\x95+\xd6\ \xb1D\x8e\x0e\xcbX\xf0\x0fR\x8ay\x18\xdc\xe2\x02p\ \x00\x00\x00\x00IEND\xaeB`\x82\ -\x00\x00\x00\x9e\ -\x89\ -PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ -\x00\x00\x09\x00\x00\x00\x06\x08\x04\x00\x00\x00\xbb\xce|N\ -\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x02bKGD\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09p\ -HYs\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ -\x00\x00\x00\x07tIME\x07\xdc\x08\x17\x08\x15\x0f\xfd\ -\x8f\xf8.\x00\x00\x00\x22IDAT\x08\xd7c`\xc0\ -\x0d\xfe\x9f\x87\xb1\x18\x91\x05\x18\x0d\xe1BH*\x0c\x19\ -\x18\x18\x91\x05\x10*\xd1\x00\x00\xca\xb5\x07\xd2v\xbb\xb2\ -\xc5\x00\x00\x00\x00IEND\xaeB`\x82\ -\x00\x00\x00\xa6\ -\x89\ -PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ -\x00\x00\x06\x00\x00\x00\x09\x08\x04\x00\x00\x00\xbb\x93\x95\x16\ -\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x02bKGD\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09p\ -HYs\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ -\x00\x00\x00\x07tIME\x07\xdc\x08\x17\x14\x1d\x00\xb0\ -\xd55\xa3\x00\x00\x00*IDAT\x08\xd7c`\xc0\ -\x06\xfe\x9fg``B0\xa1\x1c\x08\x93\x81\x81\x09\xc1\ -d``b``4D\xe2 s\x19\x90\x8d@\x02\ -\x00d@\x09u\x86\xb3\xad\x9c\x00\x00\x00\x00IEN\ -D\xaeB`\x82\ -\x00\x00\x01\xdc\ +\x00\x00\x043\ \x89\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ \x00\x000\x00\x00\x000\x08\x06\x00\x00\x00W\x02\xf9\x87\ \x00\x00\x00\x09pHYs\x00\x00\x0b\x13\x00\x00\x0b\x13\ -\x01\x00\x9a\x9c\x18\x00\x00\x01\x8eIDATh\x81\xed\ -\x9a\xafN\xc3P\x14\x87\xbfn\x1d\x0a\x1cA\x1e\x83\x04\ -\xc4\x0cjA\x10\x04\x82\x80\x9e\xe7\x05x\x80!x\x01\ -^\x00\x8f\xc2\x00rA\x90=\xc2@1s\xe42\x14\ -\xc1\xecO\x82h\x1b\xb6e\xed(\xebv\xda\xe5~\xae\ -\xf7\x5c\xf1\xfb\xda{o\x9a\xdc\xe3\x11\xa2\xaa\xdb\xc05\ -P\x03\xf6\x81\x0a\xf9b\x00\xb4\x81\x16p#\x22=\x00\ -\x0f@U\x8f\x81{`\xc7,^:\xba@]D^\ -\xbc\xf0\xcd\xbfQ\x9c\xf0\x11]`\xafD\xb0l\x8a\x16\ -\x1e\x82\xcc\x0d\x9f`\xcdO3Zq\x98\xbfR\x9ez\ -\xae\xf9\x04\x1bv\x9c\x91\x88\xf8+\x0a\x94\x0aU\x1d2\ -)qP\x22\x7f\xa7M\x1a*%\xeb\x04\x8b\xe2\x04\xac\ -q\x02\xd68\x01k\x9c\x805N\xc0\x1a'`\xcd\xdc\ -\xdffU=\x01\x0e\x81\xcd\xe5\xc7\x99`\x08\xbc\x03O\ -\x22\xf2\x1d7)V@U}\xe0\x018\xcf>[*\ -:\xaaz*\x22\x1f\xb3\x8aIK\xe8\x0a\xfb\xf0\x00\xbb\ -\xc0]\x5c1I\xe0,\xfb,\xff\xe6HU\xb7f\x15\ -\x0a\xbf\x89\x93\x04\x9eW\x96b>\xaf\x22\xf25\xab\x90\ -$p\x0b<.'O*:\xc0e\x5c1\xf6\x14\x12\ -\x91!pQ\xd8c4BD\x9a@3\xc3`\x99\xb2\ -\xd6\x9b\xb8\x108\x01k\x9c\x805N\xc0\x1a'`\x8d\ -\x13\xb0f-\x04\x06\xd6!\x16\xa0\xef\x13\x5c\xdfW\xc7\ -\x06\xcb\xe1m`\x1e\x99\xbefm\xfb\x04\xbd\x07\xd59\ -\x13\xf3J\xab\xf8\xad\x06a\xd7G=\x1c(\x0aQ\xb3\ -G\xcf\x8bF\xc2/\xd1\xe0\xb7\xddf\xc3(\x5c\x1c}\ -&\xdbm>\x01~\x00%\xf8ZCUN:\x7f\x00\ -\x00\x00\x00IEND\xaeB`\x82\ -\x00\x00\x00\x9f\ -\x89\ -PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ -\x00\x00\x09\x00\x00\x00\x06\x08\x04\x00\x00\x00\xbb\xce|N\ -\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x02bKGD\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09p\ -HYs\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ -\x00\x00\x00\x07tIME\x07\xdc\x08\x17\x08\x14\x1f\xf9\ -#\xd9\x0b\x00\x00\x00#IDAT\x08\xd7c`\xc0\ -\x0d\xe6|\x80\xb1\x18\x91\x05R\x04\xe0B\x08\x15)\x02\ -\x0c\x0c\x8c\xc8\x02\x08\x95h\x00\x00\xac\xac\x07\x90Ne\ -4\xac\x00\x00\x00\x00IEND\xaeB`\x82\ -\x00\x00\x00\x9e\ -\x89\ -PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ -\x00\x00\x09\x00\x00\x00\x06\x08\x04\x00\x00\x00\xbb\xce|N\ -\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x02bKGD\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09p\ -HYs\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ -\x00\x00\x00\x07tIME\x07\xdc\x08\x17\x08\x15\x0f\xfd\ -\x8f\xf8.\x00\x00\x00\x22IDAT\x08\xd7c`\xc0\ -\x0d\xfe\x9f\x87\xb1\x18\x91\x05\x18\x0d\xe1BH*\x0c\x19\ -\x18\x18\x91\x05\x10*\xd1\x00\x00\xca\xb5\x07\xd2v\xbb\xb2\ -\xc5\x00\x00\x00\x00IEND\xaeB`\x82\ +\x01\x00\x9a\x9c\x18\x00\x00\x03\xe5IDATh\x81\xed\ +\x9aM\x88\x1cE\x14\xc7\x7f3;\x89.\x18\xf13\x1e\ +\x92\x8b\x8b\xa2&\x06\x14/\xa29\x88\x15\x89\xa9\x18\x15\ +\xd1\xca\x06\x0f\x91\x05=\x88bnB\xc0\x1c\x12\xc4\x83\ +\x07\x15\x0dB\x14\x89\x04\xa2\x96DY\x22/F}\x8a\ +\xb0\x22\x08\x1e4\xbb\x22F\x8c,\x88\xba\xc4\x88\x82_\ +\xc9F=T\x0f\x8c\xbd\xdd]\xdd\xd3a\xa7\x07\xfc\xdd\ +\xa6\xfa\xd5\xab\xf7\xea\xf3\xdf\xd5\xd3\x22\xc1Xw\x11\xb0\ +\x03X\x0b\x5c\x0d,\xa1Y\x9c\x02\xa6\x81)`\xa7\x8a\ +?\x0e\xd0\x020\xd6\xdd\x0c\xbc\x02,\x1fXx\xd5\x98\ +\x03\xb6\xa8\xf8\xf7[I\xcf\xcf0<\xc1w\x99\x03V\ +\xb7\x09\xd3f\xd8\x82\x87\x10\xf3c\x1d\xc2\x9cOsz\ +\x91\x83)\xcbH\xea\xf7\xda\x0ea\xc1\xf6rZ\xc5w\ +\x16)\xa0J\x18\xeb\xe6\xf9o\x12k\xda4o\xb7\xa9\ +\xc2\x92\xf6\xa0#\xa8\xcb\xff\x09\x0c\x9a\xa1O\xa0\xa9\xbb\ +\xcd=\xc0]\xc0K*\xfe\xdd\x22\xdb\xc6\x8d\x80\xb1\xee\ +\x11\xc0\x03\xe3\xc0ac\xddnc]\xeeN\xd9\xa8\x04\ +\x8cu\xe3\xc0S=E-\xe0A\xe0\x85\xbc:\x8d\x99\ +B\xc6\xbau\xc0\xcb$\x023\xc5Vc\xdd\x91\xacz\ +\x8d\x18\x01c\xddu\xc0\x1b\xc0\xd2\x02\xb3\x0dY\x85\x03\ +O\xc0Xw\x19 \xc0\xb2\x88\xe9\x8bY\x85\x03M\xc0\ +Xw\x09p\x98\xb8\x1a~R\xc5\xbf\x9a\xf5``\x09\ +\x18\xeb\x96\x01\x87\x80\xb1\x88\xe9>\xe0\xd1\xbc\x87\x03I\ +\xc0X\xb7\x14x\x13\xb86b\xfa60\xa1\xe2\xff\xc9\ +3X\xf4\x04\x8cumB\xaf\x9a\x88\xe9'\xc0\xdd*\ +~\xbe\xc8h\x10#\xf04\xe0\x226_\x01\x1bU\xfc\ +o1g\xa5\x120\xd6\xb5\x92\x9e\xab\x85\xb1n;\xf0\ +p\xc4\xec{`}\xf7\xd6!FaPI\xe0\xdb\x80\ +?\x80ic\xdd\x9aR\x91f\xfb\x9a\x00\x1e\x8f\x98\xfd\ +\x02\xdc\xaa\xe2\xbf-\xeb77\x81D\x7fL\x12\x8e\xf6\ +\xb3\x80\xab\x80\xf7\x8cuW\x94u\xde\xe3k\x13\xb0'\ +b\xf6\x17p\x87\x8a\xff\xbc\x8a\xef\xa2\x11x\x0e\xd8\x94\ +*[\x0e\xa8\xb1\xee\xd2\xb2\x0d\x18\xebn\x00^c\xe1\ +\x0by/\x7f\x03\xf7\xaa\xf8\x0f\xcb\xfa\xed\x92\x99@\xd2\ +c\x0f\xe4\xd4YA\x18\x89\x151\xe7\xc6\xbaU\xc0A\ +`4b\xfa\x90\x8a?\x10\xf3\x97E\xde\x08\x5c\x10\xa9\ +7FH\xe2\xe2<\x03c\xddJ\xc2)\x1b\xf3\xb5K\ +\xc5?\x1f\xb1\xc9%/\x81\xfd\x84}\xb8\x88+\x81w\ +\x8cu\xe7\xa5\x1f\x18\xeb\xce'\x04\xbf2\xe2c\x8f\x8a\ +\xdf\x11\x8d\xb2\x80\xcc\x04T\xfc)\x82\xfa\xcb\x94\xb0=\ +\x5c\x03\x1c2\xd6\x9d\xd3-0\xd6\x8d\x12\xa6\xcd\xaaH\ +\xddI\x82\xd6\xafE\xee\x22V\xf1'\x80[\x80\xa3\x11\ +\x1f\xd7\x03\x07\x8du\xa3\xc6\xba\x11\xc2\x82\xbd1Rg\ +\x0a\x18W\xf1\xb5o\x00\x0b\xcf\x01\x15\xff#\xb0\x0e\x98\ +\x8d\xf8\xb9\x098@\xd8*\xd3;W\x9ai\xe0v\x15\ +\xffg\xc9\x18\x0b\x89\x9e\xae*~\x96\xa0[~\x88\x98\ +n\x00&\x226\xb3\x84\x83\xea\xe7r\xe1\xc5)%\x0f\ +T\xfc\xd7\x84\x91\xf8\xa9F['\x08\x12\xe1\xbb\x1a>\ +\x16PZ\xdf\xa8\xf8\x19`=\xf0k\x1f\xed\xfc\x0e\xdc\ +\xa6\xe2\xbf\xec\xa3n!\x95\x04\x9a\x8a\xff\x14\xd8\x98\x04\ +T\x96y`\xb3\x8a\xff\xb8J[e\xa9\xac0U\xfc\ +\x14p'A\xbb\x94\xe1~\x15\xffV\xd5v\xca\xd2\x97\ +DNn\xcb6\x13z\xb7\x88\xed*~o?m\x94\ +\xa5o\x8d\xaf\xe2'\x81\xad\x04!\x96\xc5\xb3*\xfe\x89\ +~\xfd\x97\xa5\xd6K\x8a\x8a\xdf\x0f\xdc\xc7\xc25\xb1\x1b\ +\xd8V\xc7wYj\xdf\xcc\xa9\xf8}\xc6\xba\x8f\x08\x07\ +\xd8\xb9\xc0\x07\xc9:Y\x14\xce\xc8\xd5\xa2\x8a\xff\x06x\ +\xe6L\xf8\xaaJ\x9b\xf0\x05|X9\xd9!h\x93\xde\ +\xfb\x99\x91\xe4k`\x13I\xbf\xd5Mw\x08\xca0}\ +\xc1T\xf4\xfa\xd7$\xa6\xda\xc0N\xc2g\xfbac\x0e\ +\xd8\xd5N\xee_\xb60\x5cIt\xff\xecq|\x04\xe0\ +\xd8\xd1\x99cc\x97\xaf\xde\x0b\x9cM\xf8\xf0}!\xcd\ +\x9bF'\x81\xcf\x80\xd7\x01\xa7\xe2\xbf\x00\xf8\x17]\x81\ +\x0b8\xb3\xfa \x9c\x00\x00\x00\x00IEND\xaeB\ +`\x82\ \x00\x00\x01W\ \x89\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ @@ -535,6 +243,40 @@ xl(\x8f\xcd\xd4\x86\x872\xf3\xa6\xa5<\xf3C\xe7\ \x97\xd8\xf0}\xdc\xe6\xce4\xdco:\xfa\xc7m\xde\x00\ >\x00G\xd7\xea\xb1\xadi\xe1\xd6\x00\x00\x00\x00IE\ ND\xaeB`\x82\ +\x00\x00\x01\xfc\ +\x89\ +PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ +\x00\x000\x00\x00\x000\x08\x06\x00\x00\x00W\x02\xf9\x87\ +\x00\x00\x00\x09pHYs\x00\x00\x0b\x13\x00\x00\x0b\x13\ +\x01\x00\x9a\x9c\x18\x00\x00\x01\xaeIDATh\x81\xed\ +\x9a\xbdJ\x03A\x14FO6\x1b\xb0\xd0J\xf1\x01\x14\ +\xabh\x91\xc6*X\xb8\x16\xb2\x88v\x0b\xe9}\x01\x1f\ +@\x8b\xf8\x00\xbe\x80\x85\x9d0b\xa32U\xc6B\xf2\ +\x02B\x92F\x83}H'6\xf9\x01\x8b\xdd@\x12\xb2\ +\x89k~f7\xcc\xe9v\xef\x14\xdfY\xee\x0c\x0bw\ +R\x048\xae\xb7\x01\x5c\x01y`\x17\xc8\x10/\xda@\ +\x05(\x03E%E\x13 \x05\xe0\xb8\xde!p\x0fl\ +j\x8b\x17\x8d\x06PPR\xbc\xa6\x82/_%9\xe1\ +{4\x80\xac\x85\xdf6I\x0b\x0f~\xe6K\x1b\xbf\xe7\ +\x87\xe9.8\xcc_I\x0f=\xe7m\xfc\x0d\xdbOW\ +Ia/(P$\x1c\xd7\xeb0(\xb1g\x11\xbf\xd3\ +&\x0a\x19Kw\x82i1\x02\xba1\x02\xba1\x02\xba\ +1\x02\xba1\x02\xba1\x02\xba\x99\xf8\xdb\xec\xb8\xde\x11\ +\xb0\x0f\xac\xce?\xce\x00\x1d\xa0\x06<+)~\xc2\x16\ +\x85\x0a8\xaeg\x03\x8f\xc0\xe9\xec\xb3E\xa2\xee\xb8\xde\ +\xb1\x92\xe2sTq\x5c\x0b]\xa0?<\xc06p\x1b\ +V\x1c'p2\xfb,\xff\xe6\xc0q\xbd\xb5Q\x85\xc4\ +o\xe2q\x02/\x0bK1\x997%\xc5\xf7\xa8\xc28\ +\x81\x1b\xe0i>y\x22Q\x07\xce\xc3\x8a\xa1\xa7\x90\x92\ +\xa2\x03\x9c%\xf6\x18\xed\xa1\xa4(\x01\xa5\x19\x06\x9b)\ +K\xbd\x89\x13\x81\x11\xd0\x8d\x11\xd0\x8d\x11\xd0\x8d\x11\xd0\ +\x8d\x11\xd0\xcdR\x08\xb4u\x87\x98\x82\x96\x8d?\xbe\xcf\ +\xf5\xbdL\x07\xd3\xc082\xaa_[\ +;\xd9;`\x05\x7f\xf0\xbdN\xfc\xda\xa8\x05\xbc\x03\x0f\ +\x80\xa7\xa4\xa8\x01\xfc\x02Q\xab\x5c\x8a?\xde\xe3Y\x00\ +\x00\x00\x00IEND\xaeB`\x82\ \x00\x00\x00\xa6\ \x89\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ @@ -548,136 +290,31 @@ HYs\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ \x08000B\x98\x10\xc1\x14\x01\x14\x13P\xb5\xa3\x01\ \x00\xc6\xb9\x07\x90]f\x1f\x83\x00\x00\x00\x00IEN\ D\xaeB`\x82\ -\x00\x00\x070\ +\x00\x00\x01i\ \x89\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ -\x00\x00\x0a\x00\x00\x00\x07\x08\x06\x00\x00\x001\xac\xdcc\ -\x00\x00\x04\xb0iTXtXML:com.\ -adobe.xmp\x00\x00\x00\x00\x00\x0a\x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a\x0aH\x8b[^\x00\x00\x01\x83\ -iCCPsRGB IEC6196\ -6-2.1\x00\x00(\x91u\x91\xcf+DQ\x14\ -\xc7?fh\xfc\x18\x8dba1e\x12\x16B\x83\x12\ -\x1b\x8b\x99\x18\x0a\x8b\x99Q~mf\x9ey3j\xde\ -x\xbd7\xd2d\xabl\xa7(\xb1\xf1k\xc1_\xc0V\ -Y+E\xa4d\xa7\xac\x89\x0dz\xce\x9bQ#\x99s\ -;\xf7|\xee\xf7\xdes\xba\xf7\x5cpD\xd3\x8afV\ -\xfaA\xcbd\x8dp(\xe0\x9b\x99\x9d\xf3\xb9\x9e\xa8\xa2\ -\x85\x1a:\xf1\xc6\x14S\x9f\x8c\x8cF)k\xef\xb7T\ -\xd8\xf1\xba\xdb\xaeU\xfe\xdc\xbfV\xb7\x980\x15\xa8\xa8\ -\x16\x1eVt#+<&<\xb1\x9a\xd5m\xde\x12n\ -RR\xb1E\xe1\x13\xe1.C.(|c\xeb\xf1\x22\ -?\xdb\x9c,\xf2\xa7\xcdF4\x1c\x04G\x83\xb0/\xf9\ -\x8b\xe3\xbfXI\x19\x9a\xb0\xbc\x9c6-\xbd\xa2\xfc\xdc\ -\xc7~\x89;\x91\x99\x8eHl\x15\xf7b\x12&D\x00\ -\x1f\xe3\x8c\x10d\x80^\x86d\x1e\xa0\x9b>zdE\ -\x99|\x7f!\x7f\x8ae\xc9Ud\xd6\xc9a\xb0D\x92\ -\x14Y\xbaD]\x91\xea\x09\x89\xaa\xe8\x09\x19irv\ -\xff\xff\xf6\xd5T\xfb\xfb\x8a\xd5\xdd\x01\xa8z\xb4\xac\xd7\ -vpm\xc2W\xde\xb2>\x0e,\xeb\xeb\x10\x9c\x0fp\ -\x9e)\xe5/\xef\xc3\xe0\x9b\xe8\xf9\x92\xd6\xb6\x07\x9eu\ -8\xbd(i\xf1m8\xdb\x80\xe6{=f\xc4\x0a\x92\ -S\xdc\xa1\xaa\xf0r\x0c\xf5\xb3\xd0x\x05\xb5\xf3\xc5\x9e\ -\xfd\xecst\x07\xd15\xf9\xaaK\xd8\xd9\x85\x0e9\xef\ -Y\xf8\x06\x8e\xfdg\xf8\xfd\x8a\x18\x97\x00\x00\x00\x09p\ -HYs\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ -\x00\x00\x00\x97IDAT\x18\x95m\xcf\xb1j\x02A\ -\x14\x85\xe1o\xb7\xb6\xd0'H=Vi\x03\xb1\xb4H\ -;l\xa5\xf19\xf6Y\x02VB\xbaa\x0a\x0b;\x1b\ -\x1bkA\x18\x02)m\xe3\xbe\x82\xcd\x06\x16\xd9\xdb\xdd\ -\x9f\xff\x5c\xee\xa9b*\x13Ls\x13nF&\xa6\xf2\ -\x82\xaeF\x8b\xdf\x98\xca\xfb\x88\xb4\xc0\x0f\xda\x1a[t\ -\xd8\xc7T\xc2@\x9ac\x8f?|U=|\xc5\x09w\ -\xbc\xa1\xc2\x193,r\x13.\xd5\xe0\xc2\x12\x07\x5cQ\ -#\xe0#7\xe1\xa8O\x0e\x7f\xda`\xd7\xaf\x9f\xb9\x09\ -\xdfc\x05\xff\xe5uLe\xf5\xcc\x1f\x0d3,\x83\xb6\ -\x06D\x83\x00\x00\x00\x00IEND\xaeB`\x82\ -\x00\x00\x00\xa5\ -\x89\ -PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ -\x00\x00\x09\x00\x00\x00\x06\x08\x04\x00\x00\x00\xbb\xce|N\ -\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x02bKGD\x00\x9cS4\xfc]\x00\x00\x00\x09p\ -HYs\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ -\x00\x00\x00\x07tIME\x07\xdc\x08\x17\x0b\x02\x04m\ -\x98\x1bi\x00\x00\x00)IDAT\x08\xd7c`\xc0\ -\x00\x8c\x0c\x0c\xff\xcf\xa3\x08\x18220 \x0b2\x1a\ -200B\x98\x10AFC\x14\x13P\xb5\xa3\x01\x00\ -\xd6\x10\x07\xd2/H\xdfJ\x00\x00\x00\x00IEND\ -\xaeB`\x82\ +\x00\x000\x00\x00\x000\x08\x06\x00\x00\x00W\x02\xf9\x87\ +\x00\x00\x00\x09pHYs\x00\x00\x0b\x13\x00\x00\x0b\x13\ +\x01\x00\x9a\x9c\x18\x00\x00\x01\x1bIDATh\x81\xed\ +\xda\xb1m\xc2@\x14\x87\xf1\xcf\xc7\x91\x09P\x86pB\ +AO\xc5\x0a\xae\x90\xbc\x0a)\xc8*\x96Ry\x05*\ +F \x1e\xc2\x82\x05H\x90R\xdcY\x01KQbE\ +\xe2\xef\x93\xde\xaf\xb3E\xf1>\xcb\xa6\xb9\x97\x11\x95u\ +3\x03^\x80%\xf0\x0cL\x19\x97\x0f\xe0\x00\xec\x81m\ +U\xe4G\x80\x0c\xa0\xac\x9b\x15\xf0\x06<\xca\xc6\x1b\xa6\ +\x05\xd6U\x91\xef\xb2\xf8\xe4\xdfIg\xf8N\x0b<9\ +\xc2k\x93\xda\xf0\x10f\xdex\xc2;\xdfw\xb9\xf30\ +\x7f5\xe9]/=\xe1\x83\xbdv\xa9\x8a\xdc\xdfi\xa0\ +A\xca\xba\xf9\xe46b\xee\x18\xdf\xbf\xcd\x10S\xa7\x9e\ +\xe0\xbf,@\xcd\x02\xd4,@\xcd\x02\xd4,@\xcd\x02\ +\xd4,@\xcd\x02\xd4,@\xcd\x02\xd4,@\xcd\x02\xd4\ +,@\xcd\x02\xd4,@\xcd\x02\xd4,@\xcd\x11N\xc0\ +Su\xf6\x84\xe3\xfb\xc5\xd5\xcdI<\x0d\x1c\xa3\xfe1\ +\xeb\xc1\x13v\x0f\x16\xbf\xfcp\xac\xf6\x0e\xd8\x12\x8e\xed\ +S\xd3\x02\xaf.n}\xacI+\xa2[\xf68f\xdd\ +\x9d\xb8\xf4\xb1\xe1{\xdd\xe6A4\xdcO\xce\xdc\xae\xdb\ +\x9c\x00\xbe\x00\x9f\xf64>6O7\x81\x00\x00\x00\x00\ +IEND\xaeB`\x82\ \x00\x00\x03\xfb\ \x89\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ @@ -744,53 +381,84 @@ pV\x8e\x16%}\x07<{6|\xd5\xa5M\xf8\x02\ \x8f\x09\xd9Hl\xe7\xbf\xdfm\xaa\xce\xea\x9b\xe0$g\ \xfens\x14\xe0\x1f\x0aC\x12kO\xfd?\x13\x00\x00\ \x00\x00IEND\xaeB`\x82\ -\x00\x00\x01\xfc\ +\x00\x00\x03\xff\ \x89\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ \x00\x000\x00\x00\x000\x08\x06\x00\x00\x00W\x02\xf9\x87\ \x00\x00\x00\x09pHYs\x00\x00\x0b\x13\x00\x00\x0b\x13\ -\x01\x00\x9a\x9c\x18\x00\x00\x01\xaeIDATh\x81\xed\ -\x9a\xbdJ\x03A\x14FO6\x1b\xb0\xd0J\xf1\x01\x14\ -\xabh\x91\xc6*X\xb8\x16\xb2\x88v\x0b\xe9}\x01\x1f\ -@\x8b\xf8\x00\xbe\x80\x85\x9d0b\xa32U\xc6B\xf2\ -\x02B\x92F\x83}H'6\xf9\x01\x8b\xdd@\x12\xb2\ -\x89k~f7\xcc\xe9v\xef\x14\xdfY\xee\x0c\x0bw\ -R\x048\xae\xb7\x01\x5c\x01y`\x17\xc8\x10/\xda@\ -\x05(\x03E%E\x13 \x05\xe0\xb8\xde!p\x0fl\ -j\x8b\x17\x8d\x06PPR\xbc\xa6\x82/_%9\xe1\ -{4\x80\xac\x85\xdf6I\x0b\x0f~\xe6K\x1b\xbf\xe7\ -\x87\xe9.8\xcc_I\x0f=\xe7m\xfc\x0d\xdbOW\ -Ia/(P$\x1c\xd7\xeb0(\xb1g\x11\xbf\xd3\ -&\x0a\x19Kw\x82i1\x02\xba1\x02\xba1\x02\xba\ -1\x02\xba1\x02\xba1\x02\xba\x99\xf8\xdb\xec\xb8\xde\x11\ -\xb0\x0f\xac\xce?\xce\x00\x1d\xa0\x06<+)~\xc2\x16\ -\x85\x0a8\xaeg\x03\x8f\xc0\xe9\xec\xb3E\xa2\xee\xb8\xde\ -\xb1\x92\xe2sTq\x5c\x0b]\xa0?<\xc06p\x1b\ -V\x1c'p2\xfb,\xff\xe6\xc0q\xbd\xb5Q\x85\xc4\ -o\xe2q\x02/\x0bK1\x997%\xc5\xf7\xa8\xc28\ -\x81\x1b\xe0i>y\x22Q\x07\xce\xc3\x8a\xa1\xa7\x90\x92\ -\xa2\x03\x9c%\xf6\x18\xed\xa1\xa4(\x01\xa5\x19\x06\x9b)\ -K\xbd\x89\x13\x81\x11\xd0\x8d\x11\xd0\x8d\x11\xd0\x8d\x11\xd0\ -\x8d\x11\xd0\xcdR\x08\xb4u\x87\x98\x82\x96\x8d?\xbe\xcf\ -\xf5\xbdL\x07\xd3\xc082\xaa_[\ -;\xd9;`\x05\x7f\xf0\xbdN\xfc\xda\xa8\x05\xbc\x03\x0f\ -\x80\xa7\xa4\xa8\x01\xfc\x02Q\xab\x5c\x8a?\xde\xe3Y\x00\ -\x00\x00\x00IEND\xaeB`\x82\ -\x00\x00\x00\xa6\ +\x01\x00\x9a\x9c\x18\x00\x00\x03\xb1IDATh\x81\xed\ +\x9aOh\x1eE\x18\xc6\x7f\x9b&\x85\x82\x15\x15\xabB\ +\xcb\x03\x06\x05\xa9\x0a\x8a\xb7R<\xd4\x96\xaa\xb5\xe0A\ +\xad\xc5C%\xa0\x07Q\xccM(\xb4\x07E\x80\xae9\x1f\xc0\xff\ +\x81\xed\xbbm\xbff{W\xca6\x0b!\x84BY\xa7\ +\xdb\xa8\xedG\x81#\xf9\xcf\x00\x1c\x05&%-\x94l\ +\xa3\xc35\x03\xb6\xef\x05\x9e\xe9)\xca\x80\x87\x80\x17\xab\ +\xda\x0c\xcd\x81e{'\xf0\x0aQt\x91\x03\xb6\xbf*\ +k7\x143`\xfb&\xe0M`}\x8d\xd9me\x85\ +\x9d\x07`\xfb*\xe0]`c\xc2\xf4\xa5\xb2\xc2N\x03\ +\xb0}9\xf0>\xe9l\xf8iI\xaf\x97Ut\x16\x80\ +\xed\x8d\xc0\x140\x9e0}\x15x\xac\xaa\xb2\x93\x00l\ +\xaf\x07\xde\x02nL\x98\xbe\x07LH*n\xf5K\xac\ +z\x00\xb6G\x88\xa3zK\xc2\xf4\x0b\xe0.I\x8bu\ +F]\xcc\xc0\x11\xe0\x9e\x84\xcd\xb7\xc0\x1eI\xbf\xa5\x9c\ +5\x0a\xc0v\x96\x8f\xdc@\xd8>\x08<\x920\xfb\x11\ +\xd8-i.a\x07$\x02\xc8\x85O\x02\x7f\x003\xb6\ +\xafo\xa4\xb4\xdc\xd7\x04\xf0d\xc2\xec\x17\xe0VI\xdf\ +7\xf5[\x99\x0b\xd9\x1e\x03\x8e\x03{{\xeaf\x81\x9b\ +%}\xd3\xb4\x03\x00\xdb{\x89\x8b\xb6x\xa7\xed\xe5,\ +q\xe4?\xab2\xe87\x17z\x8e\xe5\xe2!\xee\xd7\x1f\ +\xdb\xbe\xb2Vq\x0f\xb6\xb7\x01o\x14;.\xf0\x17p\ +_\x9d\xf8*J\x03\xc8G\xec\xc1\x8a6\x9b\x81\x8fl\ +oN9\xb7\xbd\x15x\x1b\xd8\x900}X\xd2\xf1\x94\ +\xbf2\xaaf\xe0\x92D\xbbqb\x10\x9b\xaa\x0clo\ +!\x9e\xb2)_OH:\x9a\xb0\xa9\xa4*\x80c\xc4\ +}\xb8\x8ek\x80\x0fl_T\xac\xb0}1Q\xfc\x96\ +\x84\x8f\x17$\x1dN\xaa\xac\xa14\x00I\x0b\xc4\xec\xaf\ +4\x85\xed\xe1\x06`\xca\xf6\x05\xff\x14\xd8\xde@|l\ +\xb6&\xda\x9e \xe6\xfa\x03Q{#\xcb\x93\xad\x93\xc0\ +\xd5\x09?\x9f\x02\xb7\x03\xf3\xc4\xdd\xa6\xb8\xf8\x8bL\x03\ +\xbb$\xfd\xd9\x8f\xd8\xb2](y\xa5\xb4-b\x10J\ +\xf8\x9f\x22\x1eB\x13\x09\xbb\x19\xe2V\xfcs#\xd5=\ +\xb4\x0a\x00\x96r\xf6\x93\xc0\x15\xfdvZ\xc0\xc06I\ +?\xb4i\xdc\xfaN,\xe9;`'p\xa6M\xc79\ +?\x11\x0f\xaaV\xe2\xabh\x9c\xdfH:\x05\xec\x06~\ +m\xd1\xcf\xef\xc0\x1d\x92\xben\xd1\xb6\x96\xbe\x124I\ +_\x02{rAMY\x04\xf6I\xfa\xbc\x9f\xbe\x9a\xd2\ +w\x86)i\x1a\xb8\x93\x98\xbb4\xe1\x01I\xef\xf4\xdb\ +OSZ\xa5\xc8\x92>\x04\xf6\x11G\xb7\x8e\x83\x92^\ +n\xd3GSZ\xe7\xf8\x92N\x00\x07\x88\x89X\x19\xcf\ +Jz\xaa\xad\xff\xa6\x0ctI\x91t\x0c\xb8\x9f\xff\xae\ +\x89\xe7\x81\xc9A|7eE\xde\x8d\xda\x1e'\x9e\xbe\ +\x17\x02\x9f\xe4\xebd\xc5i}\x90\x0d\x0bU\x07\xd9B\ +7rV\x84\xf9Qbn\xd2\xfb~f]\x1e\xe90\ +R\xbc\xd5\xcd\x8c\x123\xc3\xe2\x0b\xa6\xba\xeb\xdf01\ +\xbd\xf6?5\xc8\xbf\xfa\xd8\x9f\x17\xac\x15f\x81\xfdY\ +\x96\xcd-\xfd\x99\x90\xcf\xc4!\xfe\xfd\xdc\xa6\xee]}\ +\x17\xcc\xb3\xfcs\x9b3\x00\x7f\x03\xd9\x1a\xfb\xdb\xbb\xa7\ +\x8f\x07\x00\x00\x00\x00IEND\xaeB`\x82\ +\x00\x00\x00\xa0\ \x89\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ \x00\x00\x06\x00\x00\x00\x09\x08\x04\x00\x00\x00\xbb\x93\x95\x16\ \x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\ \x02bKGD\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09p\ HYs\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ -\x00\x00\x00\x07tIME\x07\xdc\x08\x17\x14\x1f \xb9\ -\x8dw\xe9\x00\x00\x00*IDAT\x08\xd7c`\xc0\ -\x06\xe6|```B0\xa1\x1c\x08\x93\x81\x81\x09\xc1\ -d``b`H\x11@\xe2 s\x19\x90\x8d@\x02\ -\x00#\xed\x08\xafd\x9f\x0f\x15\x00\x00\x00\x00IEN\ -D\xaeB`\x82\ +\x00\x00\x00\x07tIME\x07\xdc\x08\x17\x14\x1f\x0d\xfc\ +R+\x9c\x00\x00\x00$IDAT\x08\xd7c`@\ +\x05s>\xc0XL\xc8\x5c&dY&d\xc5pN\ +\x8a\x00\x9c\x93\x22\x80a\x1a\x0a\x00\x00)\x95\x08\xaf\x88\ +\xac\xba4\x00\x00\x00\x00IEND\xaeB`\x82\ \x00\x00\x00\xa6\ \x89\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ @@ -816,6 +484,55 @@ HYs\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ \x05\xff\xcf\xc3XL\xc8\x5c&dY&d\xc5p\x0e\ \xa3!\x9c\xc3h\x88a\x1a\x0a\x00\x00m\x84\x09u7\ \x9e\xd9#\x00\x00\x00\x00IEND\xaeB`\x82\ +\x00\x00\x01[\ +\x89\ +PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ +\x00\x000\x00\x00\x000\x08\x06\x00\x00\x00W\x02\xf9\x87\ +\x00\x00\x00\x09pHYs\x00\x00\x0b\x13\x00\x00\x0b\x13\ +\x01\x00\x9a\x9c\x18\x00\x00\x01\x0dIDATh\x81\xed\ +\xda\xb1m\x02A\x10F\xe1w\xc7\xe2\x0a,\x87\xd3\x00\ +8 'r\x17\x14\x83\x037\xe3.\x1cQ\x0240\ +!\xc2\x0d`\x90\x1c\xec\x9e\x0c'Y\xf6\x09\x89\xffV\ +\x9a/cE0\x0f\x0e\x92\x9d\x86\xc2\xdd\x1f\x81W`\ +\x09\xcc\x81)\xe3\xf2\x05l\x81\x0d\xf0ff\x07\x80\x06\ +\xc0\xdd_\x80w\xe0I6\xde0{`ef\x1fM\ +\xf9\xe4w\xd43|g\x0f\xccZ\xf2cS\xdb\xf0\x90\ +g^'\xf23\xdfw\xbe\xf30\xff5\xe9\xbd^&\ +\xf2\x0f\xf6\xd2\xd9\xcc\xd2\x9d\x06\x1a\xc4\xddO\x5cG<\ +\xb7\x8c\xef\xdff\x88i\xab\x9e\xe0V\x11\xa0\x16\x01j\ +\x11\xa0\x16\x01j\x11\xa0\x16\x01j\x11\xa0\x16\x01j\x11\ +\xa0\x16\x01j\x11\xa0\x16\x01j\x11\xa0\x16\x01j\x11\xa0\ +\x16\x01j\x11\xa0\xd6\x92o\xc0kuL\xe4\xeb\xfb\xc5\ +\xc5\xe1\xa4\xdc\x06\x8eQ\xff\x9au\x9b\xc8\xbb\x07\x8b?\ +\xde8V\x9b\xfaW\x0d\xca\xd6\xc7\xaa\x1c\xd4\xa2[\xf6\ +84\xddI\xf9&\xd6\xfc\xac\xdb<\x88\x86\xfb\xcd\x91\ +\xebu\x9bO\x80oV\x016\x1ew\x0d\xa5B\x00\x00\ +\x00\x00IEND\xaeB`\x82\ +\x00\x00\x00\xa6\ +\x89\ +PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ +\x00\x00\x09\x00\x00\x00\x06\x08\x04\x00\x00\x00\xbb\xce|N\ +\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x02bKGD\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09p\ +HYs\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ +\x00\x00\x00\x07tIME\x07\xdc\x08\x17\x08\x15;\xdc\ +;\x0c\x9b\x00\x00\x00*IDAT\x08\xd7c`\xc0\ +\x00\x8c\x0c\x0cs> \x0b\xa4\x08020 \x0b\xa6\ +\x08000B\x98\x10\xc1\x14\x01\x14\x13P\xb5\xa3\x01\ +\x00\xc6\xb9\x07\x90]f\x1f\x83\x00\x00\x00\x00IEN\ +D\xaeB`\x82\ +\x00\x00\x00\x9f\ +\x89\ +PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ +\x00\x00\x09\x00\x00\x00\x06\x08\x04\x00\x00\x00\xbb\xce|N\ +\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x02bKGD\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09p\ +HYs\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ +\x00\x00\x00\x07tIME\x07\xdc\x08\x17\x08\x14\x1f\xf9\ +#\xd9\x0b\x00\x00\x00#IDAT\x08\xd7c`\xc0\ +\x0d\xe6|\x80\xb1\x18\x91\x05R\x04\xe0B\x08\x15)\x02\ +\x0c\x0c\x8c\xc8\x02\x08\x95h\x00\x00\xac\xac\x07\x90Ne\ +4\xac\x00\x00\x00\x00IEND\xaeB`\x82\ \x00\x00\x07\xdd\ \x89\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ @@ -944,109 +661,147 @@ zpp\xf0\xe3\x0e.\xa4\xd2\xae\xf0\x8a\xf7\x9a\xe3V\ q[s\x5c@H\xa5\xdda\x81\x0d\x9ek\x8e\xff\xfd\ \xcf?\xcc1\xe9\x01\x1c\x00sR-q\xe4J\x1bi\ \x00\x00\x00\x00IEND\xaeB`\x82\ +\x00\x00\x07\x06\ +\x89\ +PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ +\x00\x00\x0a\x00\x00\x00\x07\x08\x06\x00\x00\x001\xac\xdcc\ +\x00\x00\x04\xb0iTXtXML:com.\ +adobe.xmp\x00\x00\x00\x00\x00\x0a\x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a\x0a\x85\x9d\x9f\x08\x00\x00\x01\x83\ +iCCPsRGB IEC6196\ +6-2.1\x00\x00(\x91u\x91\xcf+DQ\x14\ +\xc7?fh\xfc\x18\x8dba1e\x12\x16B\x83\x12\ +\x1b\x8b\x99\x18\x0a\x8b\x99Q~mf\x9ey3j\xde\ +x\xbd7\xd2d\xabl\xa7(\xb1\xf1k\xc1_\xc0V\ +Y+E\xa4d\xa7\xac\x89\x0dz\xce\x9bQ#\x99s\ +;\xf7|\xee\xf7\xdes\xba\xf7\x5cpD\xd3\x8afV\ +\xfaA\xcbd\x8dp(\xe0\x9b\x99\x9d\xf3\xb9\x9e\xa8\xa2\ +\x85\x1a:\xf1\xc6\x14S\x9f\x8c\x8cF)k\xef\xb7T\ +\xd8\xf1\xba\xdb\xaeU\xfe\xdc\xbfV\xb7\x980\x15\xa8\xa8\ +\x16\x1eVt#+<&<\xb1\x9a\xd5m\xde\x12n\ +RR\xb1E\xe1\x13\xe1.C.(|c\xeb\xf1\x22\ +?\xdb\x9c,\xf2\xa7\xcdF4\x1c\x04G\x83\xb0/\xf9\ +\x8b\xe3\xbfXI\x19\x9a\xb0\xbc\x9c6-\xbd\xa2\xfc\xdc\ +\xc7~\x89;\x91\x99\x8eHl\x15\xf7b\x12&D\x00\ +\x1f\xe3\x8c\x10d\x80^\x86d\x1e\xa0\x9b>zdE\ +\x99|\x7f!\x7f\x8ae\xc9Ud\xd6\xc9a\xb0D\x92\ +\x14Y\xbaD]\x91\xea\x09\x89\xaa\xe8\x09\x19irv\ +\xff\xff\xf6\xd5T\xfb\xfb\x8a\xd5\xdd\x01\xa8z\xb4\xac\xd7\ +vpm\xc2W\xde\xb2>\x0e,\xeb\xeb\x10\x9c\x0fp\ +\x9e)\xe5/\xef\xc3\xe0\x9b\xe8\xf9\x92\xd6\xb6\x07\x9eu\ +8\xbd(i\xf1m8\xdb\x80\xe6{=f\xc4\x0a\x92\ +S\xdc\xa1\xaa\xf0r\x0c\xf5\xb3\xd0x\x05\xb5\xf3\xc5\x9e\ +\xfd\xecst\x07\xd15\xf9\xaaK\xd8\xd9\x85\x0e9\xef\ +Y\xf8\x06\x8e\xfdg\xf8\xfd\x8a\x18\x97\x00\x00\x00\x09p\ +HYs\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ +\x00\x00\x00mIDAT\x18\x95u\xcf\xc1\x09\xc2P\ +\x10\x84\xe1\xd7\x85\x07\x9b\xd0C@\xd2\x82x\x14{0\ +W!\x8d\x84`?bKzH\xcc\x97\x83\xfb0\x04\ +\xdf\x9c\x86\x7fg\x99\xdd\x84\x0d\xaaT\x10jl\x13\x1e\ +\xbe\xba\xfe\x0951{\xe6\x8d\x0f&\x1c\x17\xa1S\xb0\ +\x11\x87\x0c/\x01\x07\xec\xb0\x0f?\xe1\xbc\xaei\xa3\xe6\ +\x85w\xf8[\xe9\xf0\xbb\x9f\xfa\xd2\x839\xdc\xa3[\xf3\ +\x19.\xa8\x89\xb50\xf7C\xa0\x00\x00\x00\x00IEN\ +D\xaeB`\x82\ \x00\x00\x00\xa6\ \x89\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ -\x00\x00\x09\x00\x00\x00\x06\x08\x04\x00\x00\x00\xbb\xce|N\ +\x00\x00\x06\x00\x00\x00\x09\x08\x04\x00\x00\x00\xbb\x93\x95\x16\ \x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\ \x02bKGD\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09p\ HYs\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ -\x00\x00\x00\x07tIME\x07\xdc\x08\x17\x08\x15;\xdc\ -;\x0c\x9b\x00\x00\x00*IDAT\x08\xd7c`\xc0\ -\x00\x8c\x0c\x0cs> \x0b\xa4\x08020 \x0b\xa6\ -\x08000B\x98\x10\xc1\x14\x01\x14\x13P\xb5\xa3\x01\ -\x00\xc6\xb9\x07\x90]f\x1f\x83\x00\x00\x00\x00IEN\ +\x00\x00\x00\x07tIME\x07\xdc\x08\x17\x14\x1f \xb9\ +\x8dw\xe9\x00\x00\x00*IDAT\x08\xd7c`\xc0\ +\x06\xe6|```B0\xa1\x1c\x08\x93\x81\x81\x09\xc1\ +d``b`H\x11@\xe2 s\x19\x90\x8d@\x02\ +\x00#\xed\x08\xafd\x9f\x0f\x15\x00\x00\x00\x00IEN\ D\xaeB`\x82\ -\x00\x00\x043\ +\x00\x00\x00\xa6\ \x89\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ -\x00\x000\x00\x00\x000\x08\x06\x00\x00\x00W\x02\xf9\x87\ -\x00\x00\x00\x09pHYs\x00\x00\x0b\x13\x00\x00\x0b\x13\ -\x01\x00\x9a\x9c\x18\x00\x00\x03\xe5IDATh\x81\xed\ -\x9aM\x88\x1cE\x14\xc7\x7f3;\x89.\x18\xf13\x1e\ -\x92\x8b\x8b\xa2&\x06\x14/\xa29\x88\x15\x89\xa9\x18\x15\ -\xd1\xca\x06\x0f\x91\x05=\x88bnB\xc0\x1c\x12\xc4\x83\ -\x07\x15\x0dB\x14\x89\x04\xa2\x96DY\x22/F}\x8a\ -\xb0\x22\x08\x1e4\xbb\x22F\x8c,\x88\xba\xc4\x88\x82_\ -\xc9F=T\x0f\x8c\xbd\xdd]\xdd\xd3a\xa7\x07\xfc\xdd\ -\xa6\xfa\xd5\xab\xf7\xea\xf3\xdf\xd5\xd3\x22\xc1Xw\x11\xb0\ -\x03X\x0b\x5c\x0d,\xa1Y\x9c\x02\xa6\x81)`\xa7\x8a\ -?\x0e\xd0\x020\xd6\xdd\x0c\xbc\x02,\x1fXx\xd5\x98\ -\x03\xb6\xa8\xf8\xf7[I\xcf\xcf0<\xc1w\x99\x03V\ -\xb7\x09\xd3f\xd8\x82\x87\x10\xf3c\x1d\xc2\x9cOsz\ -\x91\x83)\xcbH\xea\xf7\xda\x0ea\xc1\xf6rZ\xc5w\ -\x16)\xa0J\x18\xeb\xe6\xf9o\x12k\xda4o\xb7\xa9\ -\xc2\x92\xf6\xa0#\xa8\xcb\xff\x09\x0c\x9a\xa1O\xa0\xa9\xbb\ -\xcd=\xc0]\xc0K*\xfe\xdd\x22\xdb\xc6\x8d\x80\xb1\xee\ -\x11\xc0\x03\xe3\xc0ac\xddnc]\xeeN\xd9\xa8\x04\ -\x8cu\xe3\xc0S=E-\xe0A\xe0\x85\xbc:\x8d\x99\ -B\xc6\xbau\xc0\xcb$\x023\xc5Vc\xdd\x91\xacz\ -\x8d\x18\x01c\xddu\xc0\x1b\xc0\xd2\x02\xb3\x0dY\x85\x03\ -O\xc0Xw\x19 \xc0\xb2\x88\xe9\x8bY\x85\x03M\xc0\ -Xw\x09p\x98\xb8\x1a~R\xc5\xbf\x9a\xf5``\x09\ -\x18\xeb\x96\x01\x87\x80\xb1\x88\xe9>\xe0\xd1\xbc\x87\x03I\ -\xc0X\xb7\x14x\x13\xb86b\xfa60\xa1\xe2\xff\xc9\ -3X\xf4\x04\x8cumB\xaf\x9a\x88\xe9'\xc0\xdd*\ -~\xbe\xc8h\x10#\xf04\xe0\x226_\x01\x1bU\xfc\ -o1g\xa5\x120\xd6\xb5\x92\x9e\xab\x85\xb1n;\xf0\ -p\xc4\xec{`}\xf7\xd6!FaPI\xe0\xdb\x80\ -?\x80ic\xdd\x9aR\x91f\xfb\x9a\x00\x1e\x8f\x98\xfd\ -\x02\xdc\xaa\xe2\xbf-\xeb77\x81D\x7fL\x12\x8e\xf6\ -\xb3\x80\xab\x80\xf7\x8cuW\x94u\xde\xe3k\x13\xb0'\ -b\xf6\x17p\x87\x8a\xff\xbc\x8a\xef\xa2\x11x\x0e\xd8\x94\ -*[\x0e\xa8\xb1\xee\xd2\xb2\x0d\x18\xebn\x00^c\xe1\ -\x0by/\x7f\x03\xf7\xaa\xf8\x0f\xcb\xfa\xed\x92\x99@\xd2\ -c\x0f\xe4\xd4YA\x18\x89\x151\xe7\xc6\xbaU\xc0A\ -`4b\xfa\x90\x8a?\x10\xf3\x97E\xde\x08\x5c\x10\xa9\ -7FH\xe2\xe2<\x03c\xddJ\xc2)\x1b\xf3\xb5K\ -\xc5?\x1f\xb1\xc9%/\x81\xfd\x84}\xb8\x88+\x81w\ -\x8cu\xe7\xa5\x1f\x18\xeb\xce'\x04\xbf2\xe2c\x8f\x8a\ -\xdf\x11\x8d\xb2\x80\xcc\x04T\xfc)\x82\xfa\xcb\x94\xb0=\ -\x5c\x03\x1c2\xd6\x9d\xd3-0\xd6\x8d\x12\xa6\xcd\xaaH\ -\xddI\x82\xd6\xafE\xee\x22V\xf1'\x80[\x80\xa3\x11\ -\x1f\xd7\x03\x07\x8du\xa3\xc6\xba\x11\xc2\x82\xbd1Rg\ -\x0a\x18W\xf1\xb5o\x00\x0b\xcf\x01\x15\xff#\xb0\x0e\x98\ -\x8d\xf8\xb9\x098@\xd8*\xd3;W\x9ai\xe0v\x15\ -\xffg\xc9\x18\x0b\x89\x9e\xae*~\x96\xa0[~\x88\x98\ -n\x00&\x226\xb3\x84\x83\xea\xe7r\xe1\xc5)%\x0f\ -T\xfc\xd7\x84\x91\xf8\xa9F['\x08\x12\xe1\xbb\x1a>\ -\x16PZ\xdf\xa8\xf8\x19`=\xf0k\x1f\xed\xfc\x0e\xdc\ -\xa6\xe2\xbf\xec\xa3n!\x95\x04\x9a\x8a\xff\x14\xd8\x98\x04\ -T\x96y`\xb3\x8a\xff\xb8J[e\xa9\xac0U\xfc\ -\x14p'A\xbb\x94\xe1~\x15\xffV\xd5v\xca\xd2\x97\ -DNn\xcb6\x13z\xb7\x88\xed*~o?m\x94\ -\xa5o\x8d\xaf\xe2'\x81\xad\x04!\x96\xc5\xb3*\xfe\x89\ -~\xfd\x97\xa5\xd6K\x8a\x8a\xdf\x0f\xdc\xc7\xc25\xb1\x1b\ -\xd8V\xc7wYj\xdf\xcc\xa9\xf8}\xc6\xba\x8f\x08\x07\ -\xd8\xb9\xc0\x07\xc9:Y\x14\xce\xc8\xd5\xa2\x8a\xff\x06x\ -\xe6L\xf8\xaaJ\x9b\xf0\x05|X9\xd9!h\x93\xde\ -\xfb\x99\x91\xe4k`\x13I\xbf\xd5Mw\x08\xca0}\ -\xc1T\xf4\xfa\xd7$\xa6\xda\xc0N\xc2g\xfbac\x0e\ -\xd8\xd5N\xee_\xb60\x5cIt\xff\xecq|\x04\xe0\ -\xd8\xd1\x99cc\x97\xaf\xde\x0b\x9cM\xf8\xf0}!\xcd\ -\x9bF'\x81\xcf\x80\xd7\x01\xa7\xe2\xbf\x00\xf8\x17]\x81\ -\x0b8\xb3\xfa \x9c\x00\x00\x00\x00IEND\xaeB\ -`\x82\ -\x00\x00\x00\xa5\ -\x89\ -PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ -\x00\x00\x09\x00\x00\x00\x06\x08\x04\x00\x00\x00\xbb\xce|N\ +\x00\x00\x06\x00\x00\x00\x09\x08\x04\x00\x00\x00\xbb\x93\x95\x16\ \x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x02bKGD\x00\x9cS4\xfc]\x00\x00\x00\x09p\ +\x02bKGD\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09p\ HYs\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ -\x00\x00\x00\x07tIME\x07\xdc\x08\x17\x0b\x02\x04m\ -\x98\x1bi\x00\x00\x00)IDAT\x08\xd7c`\xc0\ -\x00\x8c\x0c\x0c\xff\xcf\xa3\x08\x18220 \x0b2\x1a\ -200B\x98\x10AFC\x14\x13P\xb5\xa3\x01\x00\ -\xd6\x10\x07\xd2/H\xdfJ\x00\x00\x00\x00IEND\ -\xaeB`\x82\ -\x00\x00\x00E\ -\x89\ -PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ -\x00\x00\x01\x00\x00\x00\x01\x08\x02\x00\x00\x00\x90wS\xde\ -\x00\x00\x00\x0cIDAT\x08\x99c```\x00\x00\ -\x00\x04\x00\x01\xa3\x0a\x15\xe3\x00\x00\x00\x00IEND\ -\xaeB`\x82\ +\x00\x00\x00\x07tIME\x07\xdc\x08\x17\x14\x1d\x00\xb0\ +\xd55\xa3\x00\x00\x00*IDAT\x08\xd7c`\xc0\ +\x06\xfe\x9fg``B0\xa1\x1c\x08\x93\x81\x81\x09\xc1\ +d``b``4D\xe2 s\x19\x90\x8d@\x02\ +\x00d@\x09u\x86\xb3\xad\x9c\x00\x00\x00\x00IEN\ +D\xaeB`\x82\ \x00\x00\x01v\ \x89\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ @@ -1073,6 +828,19 @@ m\xd3\xc7\xc8\xa9'\xf8/\x0bP\xb3\x005\x0bP\xb3\ \xc5\xf7%\xc3;F{\xe0\x03x\x03\x8a\xba*\xd7\x00\ \xdf\xa4\xb56\xa2\xca\x99tG\x00\x00\x00\x00IEN\ D\xaeB`\x82\ +\x00\x00\x00\xa6\ +\x89\ +PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ +\x00\x00\x06\x00\x00\x00\x09\x08\x04\x00\x00\x00\xbb\x93\x95\x16\ +\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x02bKGD\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09p\ +HYs\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ +\x00\x00\x00\x07tIME\x07\xdc\x08\x17\x14\x1d\x00\xb0\ +\xd55\xa3\x00\x00\x00*IDAT\x08\xd7c`\xc0\ +\x06\xfe\x9fg``B0\xa1\x1c\x08\x93\x81\x81\x09\xc1\ +d``b``4D\xe2 s\x19\x90\x8d@\x02\ +\x00d@\x09u\x86\xb3\xad\x9c\x00\x00\x00\x00IEN\ +D\xaeB`\x82\ \x00\x00\x00\xa0\ \x89\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ @@ -1080,11 +848,11 @@ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ \x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\ \x02bKGD\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09p\ HYs\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ -\x00\x00\x00\x07tIME\x07\xdc\x08\x17\x14\x1f\x0d\xfc\ -R+\x9c\x00\x00\x00$IDAT\x08\xd7c`@\ -\x05s>\xc0XL\xc8\x5c&dY&d\xc5pN\ -\x8a\x00\x9c\x93\x22\x80a\x1a\x0a\x00\x00)\x95\x08\xaf\x88\ -\xac\xba4\x00\x00\x00\x00IEND\xaeB`\x82\ +\x00\x00\x00\x07tIME\x07\xdc\x08\x17\x14\x1c\x1f$\ +\xc6\x09\x17\x00\x00\x00$IDAT\x08\xd7c`@\ +\x05\xff\xcf\xc3XL\xc8\x5c&dY&d\xc5p\x0e\ +\xa3!\x9c\xc3h\x88a\x1a\x0a\x00\x00m\x84\x09u7\ +\x9e\xd9#\x00\x00\x00\x00IEND\xaeB`\x82\ \x00\x00\x00\xa5\ \x89\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ @@ -1098,30 +866,18 @@ HYs\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ 200B\x98\x10AFC\x14\x13P\xb5\xa3\x01\x00\ \xd6\x10\x07\xd2/H\xdfJ\x00\x00\x00\x00IEND\ \xaeB`\x82\ -\x00\x00\x01[\ +\x00\x00\x00\x9e\ \x89\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ -\x00\x000\x00\x00\x000\x08\x06\x00\x00\x00W\x02\xf9\x87\ -\x00\x00\x00\x09pHYs\x00\x00\x0b\x13\x00\x00\x0b\x13\ -\x01\x00\x9a\x9c\x18\x00\x00\x01\x0dIDATh\x81\xed\ -\xda\xb1m\x02A\x10F\xe1w\xc7\xe2\x0a,\x87\xd3\x00\ -8 'r\x17\x14\x83\x037\xe3.\x1cQ\x0240\ -!\xc2\x0d`\x90\x1c\xec\x9e\x0c'Y\xf6\x09\x89\xffV\ -\x9a/cE0\x0f\x0e\x92\x9d\x86\xc2\xdd\x1f\x81W`\ -\x09\xcc\x81)\xe3\xf2\x05l\x81\x0d\xf0ff\x07\x80\x06\ -\xc0\xdd_\x80w\xe0I6\xde0{`ef\x1fM\ -\xf9\xe4w\xd43|g\x0f\xccZ\xf2cS\xdb\xf0\x90\ -g^'\xf23\xdfw\xbe\xf30\xff5\xe9\xbd^&\ -\xf2\x0f\xf6\xd2\xd9\xcc\xd2\x9d\x06\x1a\xc4\xddO\x5cG<\ -\xb7\x8c\xef\xdff\x88i\xab\x9e\xe0V\x11\xa0\x16\x01j\ -\x11\xa0\x16\x01j\x11\xa0\x16\x01j\x11\xa0\x16\x01j\x11\ -\xa0\x16\x01j\x11\xa0\x16\x01j\x11\xa0\x16\x01j\x11\xa0\ -\x16\x01j\x11\xa0\xd6\x92o\xc0kuL\xe4\xeb\xfb\xc5\ -\xc5\xe1\xa4\xdc\x06\x8eQ\xff\x9au\x9b\xc8\xbb\x07\x8b?\ -\xde8V\x9b\xfaW\x0d\xca\xd6\xc7\xaa\x1c\xd4\xa2[\xf6\ -84\xddI\xf9&\xd6\xfc\xac\xdb<\x88\x86\xfb\xcd\x91\ -\xebu\x9bO\x80oV\x016\x1ew\x0d\xa5B\x00\x00\ -\x00\x00IEND\xaeB`\x82\ +\x00\x00\x09\x00\x00\x00\x06\x08\x04\x00\x00\x00\xbb\xce|N\ +\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x02bKGD\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09p\ +HYs\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ +\x00\x00\x00\x07tIME\x07\xdc\x08\x17\x08\x15\x0f\xfd\ +\x8f\xf8.\x00\x00\x00\x22IDAT\x08\xd7c`\xc0\ +\x0d\xfe\x9f\x87\xb1\x18\x91\x05\x18\x0d\xe1BH*\x0c\x19\ +\x18\x18\x91\x05\x10*\xd1\x00\x00\xca\xb5\x07\xd2v\xbb\xb2\ +\xc5\x00\x00\x00\x00IEND\xaeB`\x82\ \x00\x00\x01\xef\ \x89\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ @@ -1155,43 +911,371 @@ f\xaf\xd1\x10\xa5T\x13h\xa6\x18,U6\xfa\x10g\ D8\xec\xd1\xb3\xc27\xc1\xd0G\x8d\xdfq\x9b-\xa1\ pQ\xf4\x99\x1c\xb7\xf9\x04\xf8\x01o\xedXc-\xfd\ \xb2Y\x00\x00\x00\x00IEND\xaeB`\x82\ -\x00\x00\x00\xa0\ +\x00\x00\x070\ \x89\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ -\x00\x00\x06\x00\x00\x00\x09\x08\x04\x00\x00\x00\xbb\x93\x95\x16\ -\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x02bKGD\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09p\ +\x00\x00\x0a\x00\x00\x00\x07\x08\x06\x00\x00\x001\xac\xdcc\ +\x00\x00\x04\xb0iTXtXML:com.\ +adobe.xmp\x00\x00\x00\x00\x00\x0a\x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a \x0a\x0aH\x8b[^\x00\x00\x01\x83\ +iCCPsRGB IEC6196\ +6-2.1\x00\x00(\x91u\x91\xcf+DQ\x14\ +\xc7?fh\xfc\x18\x8dba1e\x12\x16B\x83\x12\ +\x1b\x8b\x99\x18\x0a\x8b\x99Q~mf\x9ey3j\xde\ +x\xbd7\xd2d\xabl\xa7(\xb1\xf1k\xc1_\xc0V\ +Y+E\xa4d\xa7\xac\x89\x0dz\xce\x9bQ#\x99s\ +;\xf7|\xee\xf7\xdes\xba\xf7\x5cpD\xd3\x8afV\ +\xfaA\xcbd\x8dp(\xe0\x9b\x99\x9d\xf3\xb9\x9e\xa8\xa2\ +\x85\x1a:\xf1\xc6\x14S\x9f\x8c\x8cF)k\xef\xb7T\ +\xd8\xf1\xba\xdb\xaeU\xfe\xdc\xbfV\xb7\x980\x15\xa8\xa8\ +\x16\x1eVt#+<&<\xb1\x9a\xd5m\xde\x12n\ +RR\xb1E\xe1\x13\xe1.C.(|c\xeb\xf1\x22\ +?\xdb\x9c,\xf2\xa7\xcdF4\x1c\x04G\x83\xb0/\xf9\ +\x8b\xe3\xbfXI\x19\x9a\xb0\xbc\x9c6-\xbd\xa2\xfc\xdc\ +\xc7~\x89;\x91\x99\x8eHl\x15\xf7b\x12&D\x00\ +\x1f\xe3\x8c\x10d\x80^\x86d\x1e\xa0\x9b>zdE\ +\x99|\x7f!\x7f\x8ae\xc9Ud\xd6\xc9a\xb0D\x92\ +\x14Y\xbaD]\x91\xea\x09\x89\xaa\xe8\x09\x19irv\ +\xff\xff\xf6\xd5T\xfb\xfb\x8a\xd5\xdd\x01\xa8z\xb4\xac\xd7\ +vpm\xc2W\xde\xb2>\x0e,\xeb\xeb\x10\x9c\x0fp\ +\x9e)\xe5/\xef\xc3\xe0\x9b\xe8\xf9\x92\xd6\xb6\x07\x9eu\ +8\xbd(i\xf1m8\xdb\x80\xe6{=f\xc4\x0a\x92\ +S\xdc\xa1\xaa\xf0r\x0c\xf5\xb3\xd0x\x05\xb5\xf3\xc5\x9e\ +\xfd\xecst\x07\xd15\xf9\xaaK\xd8\xd9\x85\x0e9\xef\ +Y\xf8\x06\x8e\xfdg\xf8\xfd\x8a\x18\x97\x00\x00\x00\x09p\ HYs\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ -\x00\x00\x00\x07tIME\x07\xdc\x08\x17\x14\x1c\x1f$\ -\xc6\x09\x17\x00\x00\x00$IDAT\x08\xd7c`@\ -\x05\xff\xcf\xc3XL\xc8\x5c&dY&d\xc5p\x0e\ -\xa3!\x9c\xc3h\x88a\x1a\x0a\x00\x00m\x84\x09u7\ -\x9e\xd9#\x00\x00\x00\x00IEND\xaeB`\x82\ -\x00\x00\x01i\ +\x00\x00\x00\x97IDAT\x18\x95m\xcf\xb1j\x02A\ +\x14\x85\xe1o\xb7\xb6\xd0'H=Vi\x03\xb1\xb4H\ +;l\xa5\xf19\xf6Y\x02VB\xbaa\x0a\x0b;\x1b\ +\x1bkA\x18\x02)m\xe3\xbe\x82\xcd\x06\x16\xd9\xdb\xdd\ +\x9f\xff\x5c\xee\xa9b*\x13Ls\x13nF&\xa6\xf2\ +\x82\xaeF\x8b\xdf\x98\xca\xfb\x88\xb4\xc0\x0f\xda\x1a[t\ +\xd8\xc7T\xc2@\x9ac\x8f?|U=|\xc5\x09w\ +\xbc\xa1\xc2\x193,r\x13.\xd5\xe0\xc2\x12\x07\x5cQ\ +#\xe0#7\xe1\xa8O\x0e\x7f\xda`\xd7\xaf\x9f\xb9\x09\ +\xdfc\x05\xff\xe5uLe\xf5\xcc\x1f\x0d3,\x83\xb6\ +\x06D\x83\x00\x00\x00\x00IEND\xaeB`\x82\ +\x00\x00\x01\xdc\ \x89\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ \x00\x000\x00\x00\x000\x08\x06\x00\x00\x00W\x02\xf9\x87\ \x00\x00\x00\x09pHYs\x00\x00\x0b\x13\x00\x00\x0b\x13\ -\x01\x00\x9a\x9c\x18\x00\x00\x01\x1bIDATh\x81\xed\ -\xda\xb1m\xc2@\x14\x87\xf1\xcf\xc7\x91\x09P\x86pB\ -AO\xc5\x0a\xae\x90\xbc\x0a)\xc8*\x96Ry\x05*\ -F \x1e\xc2\x82\x05H\x90R\xdcY\x01KQbE\ -\xe2\xef\x93\xde\xaf\xb3E\xf1>\xcb\xa6\xb9\x97\x11\x95u\ -3\x03^\x80%\xf0\x0cL\x19\x97\x0f\xe0\x00\xec\x81m\ -U\xe4G\x80\x0c\xa0\xac\x9b\x15\xf0\x06<\xca\xc6\x1b\xa6\ -\x05\xd6U\x91\xef\xb2\xf8\xe4\xdfIg\xf8N\x0b<9\ -\xc2k\x93\xda\xf0\x10f\xdex\xc2;\xdfw\xb9\xf30\ -\x7f5\xe9]/=\xe1\x83\xbdv\xa9\x8a\xdc\xdfi\xa0\ -A\xca\xba\xf9\xe46b\xee\x18\xdf\xbf\xcd\x10S\xa7\x9e\ -\xe0\xbf,@\xcd\x02\xd4,@\xcd\x02\xd4,@\xcd\x02\ -\xd4,@\xcd\x02\xd4,@\xcd\x02\xd4,@\xcd\x02\xd4\ -,@\xcd\x02\xd4,@\xcd\x02\xd4,@\xcd\x11N\xc0\ -Su\xf6\x84\xe3\xfb\xc5\xd5\xcdI<\x0d\x1c\xa3\xfe1\ -\xeb\xc1\x13v\x0f\x16\xbf\xfcp\xac\xf6\x0e\xd8\x12\x8e\xed\ -S\xd3\x02\xaf.n}\xacI+\xa2[\xf68f\xdd\ -\x9d\xb8\xf4\xb1\xe1{\xdd\xe6A4\xdcO\xce\xdc\xae\xdb\ -\x9c\x00\xbe\x00\x9f\xf64>6O7\x81\x00\x00\x00\x00\ -IEND\xaeB`\x82\ +\x01\x00\x9a\x9c\x18\x00\x00\x01\x8eIDATh\x81\xed\ +\x9a\xafN\xc3P\x14\x87\xbfn\x1d\x0a\x1cA\x1e\x83\x04\ +\xc4\x0cjA\x10\x04\x82\x80\x9e\xe7\x05x\x80!x\x01\ +^\x00\x8f\xc2\x00rA\x90=\xc2@1s\xe42\x14\ +\xc1\xecO\x82h\x1b\xb6e\xed(\xebv\xda\xe5~\xae\ +\xf7\x5c\xf1\xfb\xda{o\x9a\xdc\xe3\x11\xa2\xaa\xdb\xc05\ +P\x03\xf6\x81\x0a\xf9b\x00\xb4\x81\x16p#\x22=\x00\ +\x0f@U\x8f\x81{`\xc7,^:\xba@]D^\ +\xbc\xf0\xcd\xbfQ\x9c\xf0\x11]`\xafD\xb0l\x8a\x16\ +\x1e\x82\xcc\x0d\x9f`\xcdO3Zq\x98\xbfR\x9ez\ +\xae\xf9\x04\x1bv\x9c\x91\x88\xf8+\x0a\x94\x0aU\x1d2\ +)qP\x22\x7f\xa7M\x1a*%\xeb\x04\x8b\xe2\x04\xac\ +q\x02\xd68\x01k\x9c\x805N\xc0\x1a'`\xcd\xdc\ +\xdffU=\x01\x0e\x81\xcd\xe5\xc7\x99`\x08\xbc\x03O\ +\x22\xf2\x1d7)V@U}\xe0\x018\xcf>[*\ +:\xaaz*\x22\x1f\xb3\x8aIK\xe8\x0a\xfb\xf0\x00\xbb\ +\xc0]\x5c1I\xe0,\xfb,\xff\xe6HU\xb7f\x15\ +\x0a\xbf\x89\x93\x04\x9eW\x96b>\xaf\x22\xf25\xab\x90\ +$p\x0b<.'O*:\xc0e\x5c1\xf6\x14\x12\ +\x91!pQ\xd8c4BD\x9a@3\xc3`\x99\xb2\ +\xd6\x9b\xb8\x108\x01k\x9c\x805N\xc0\x1a'`\x8d\ +\x13\xb0f-\x04\x06\xd6!\x16\xa0\xef\x13\x5c\xdfW\xc7\ +\x06\xcb\xe1m`\x1e\x99\xbefm\xfb\x04\xbd\x07\xd59\ +\x13\xf3J\xab\xf8\xad\x06a\xd7G=\x1c(\x0aQ\xb3\ +G\xcf\x8bF\xc2/\xd1\xe0\xb7\xddf\xc3(\x5c\x1c}\ +&\xdbm>\x01~\x00%\xf8ZCUN:\x7f\x00\ +\x00\x00\x00IEND\xaeB`\x82\ +\x00\x00\x05~\ +\x89\ +PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ +\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1f\x15\xc4\x89\ +\x00\x00\x00\x09pHYs\x00\x00\x0b\x13\x00\x00\x0b\x13\ +\x01\x00\x9a\x9c\x18\x00\x00\x05\x17iTXtXML\ +:com.adobe.xmp\x00\x00\ +\x00\x00\x00 \ + \x07b\x0c\x81\x00\x00\x00\x0dIDAT\ +\x08\x1dc\xf8\xff\xff?\x03\x00\x08\xfc\x02\xfe\xe6\x0c\xff\ +\xab\x00\x00\x00\x00IEND\xaeB`\x82\ +\x00\x00\x00\x9e\ +\x89\ +PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ +\x00\x00\x09\x00\x00\x00\x06\x08\x04\x00\x00\x00\xbb\xce|N\ +\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x02bKGD\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09p\ +HYs\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ +\x00\x00\x00\x07tIME\x07\xdc\x08\x17\x08\x15\x0f\xfd\ +\x8f\xf8.\x00\x00\x00\x22IDAT\x08\xd7c`\xc0\ +\x0d\xfe\x9f\x87\xb1\x18\x91\x05\x18\x0d\xe1BH*\x0c\x19\ +\x18\x18\x91\x05\x10*\xd1\x00\x00\xca\xb5\x07\xd2v\xbb\xb2\ +\xc5\x00\x00\x00\x00IEND\xaeB`\x82\ +\x00\x00\x01\xe1\ +\x89\ +PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ +\x00\x000\x00\x00\x000\x08\x06\x00\x00\x00W\x02\xf9\x87\ +\x00\x00\x00\x09pHYs\x00\x00\x0b\x13\x00\x00\x0b\x13\ +\x01\x00\x9a\x9c\x18\x00\x00\x01\x93IDATh\x81\xed\ +\x9a;N\xc3@\x10\x86\xbfq\x1c*\xe8\x10\xe56\x94\ +\xd0\xa4\xa1\x8a(\x22\x0a\x0aD\x9f\x9e\x0bp\x80Pp\ +\x01.\xc0\x15h\x80\x13\xa0\x1c!P\x91f\xbbD\xa1\ +B4yh(l\x1e\xb1\xfcH\x08\xc9\xda\xd2~\x9d\ +w\x5c\xfc\x9f\xb3\x1e9\xda\x11bTu\x17\xb8\x02\x9a\ +\xc0!P\xa7\x5cL\x80\x1e\xd0\x05\xaeEd\xf4]Q\ +\xd5\x96\xaa\x0e\xb4:\x0cT\xb5\x05 \x1a=\xf9g`\ +\xcf\xc5c]\x81!p\x10\x10m\x9b\xaa\x85\x87(s\ +'$\xda\xf3If\x1b\x0e\xb3(\xb5\xc4uSTu\ +\xcc\xfc\x0b;\x13\x91p\x83\xa1\x16FU\xa7\xccKL\ +\x02\xca\xd7m\x96\xa1\x1e\xb8N\xb0*^\xc05^\xc0\ +5^\xc05^\xc05^\xc05^\xc05\x85\x9f\xcd\ +\xd6\xda\x13\xe0\x08\xd8^\x7f\x9c9\xa6\xc0\x0b\xf0`\x8c\ +\xf9\xc8\xbaITU\x13k3\x11\x09\xad\xb5!p\x07\ +\x9c\xaf1\xe4\x22\xf4\x81Sc\xcck\xca\xff\x81\xdc-\ +t\x89\xfb\xf0\x00\xfb\xc0mV1O\xe0\xec\xff\xb3\xfc\ +\x99ck\xedNZ\xa1\xf2/q\x9e\xc0\xe3\xc6R\x14\ +\xf3d\x8cyO+\xe4\x09\xdc\x00\xf7\xeb\xc9\xb3\x14}\ +\xe0\x22\xab\x98\xd9\x85\xbe.\xca\xd4F\xd3\xbaP\xa1@\ +\x99X\xb6\x8dV\x02/\xe0\x1a/\xe0\x1a/\xe0\x1a/\ +\xe0\x1a/\xe0\x1a/\xe0\x9a\x80\xe8\x04\xbc\xaa\x8cC\xa2\ +\xe3\xfb\xc6\xaf\xc5Z\xfc\xd9ZF\x92\xc7\xac\xbd\x90h\ +\xf6\xa0QpcY\xe9V\x7f\xd4 \x9e\xfah\xc7\x0b\ +Ua\x08\xb4Ed$_+\xf1/\xd1\xe1g\xdcf\ +\xcbQ\xb8,\xc6\xcc\x8f\xdb\xbc\x01|\x02mw#\xb3\ +\xd4\x95Sv\x00\x00\x00\x00IEND\xaeB`\x82\ +\ +\x00\x00\x00\xa5\ +\x89\ +PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ +\x00\x00\x09\x00\x00\x00\x06\x08\x04\x00\x00\x00\xbb\xce|N\ +\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x02bKGD\x00\x9cS4\xfc]\x00\x00\x00\x09p\ +HYs\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ +\x00\x00\x00\x07tIME\x07\xdc\x08\x17\x0b\x02\x04m\ +\x98\x1bi\x00\x00\x00)IDAT\x08\xd7c`\xc0\ +\x00\x8c\x0c\x0c\xff\xcf\xa3\x08\x18220 \x0b2\x1a\ +200B\x98\x10AFC\x14\x13P\xb5\xa3\x01\x00\ +\xd6\x10\x07\xd2/H\xdfJ\x00\x00\x00\x00IEND\ +\xaeB`\x82\ +\x00\x00\x04\x12\ +\x89\ +PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ +\x00\x000\x00\x00\x000\x08\x06\x00\x00\x00W\x02\xf9\x87\ +\x00\x00\x00\x09pHYs\x00\x00\x0b\x13\x00\x00\x0b\x13\ +\x01\x00\x9a\x9c\x18\x00\x00\x03\xc4IDATh\x81\xed\ +\x9a_\x88\x94U\x18\xc6\x7f3;\x1a\x0b\x19\x15f\x17\ +\xca\x03IPM\x09J7Q^D)&f\x05[\ +\xb9\xd2\x82\xb1P\x17\x91$t\x11\x08z\xa1D\x17]\ +T\x94\x04\xd6E\xe1\xa2\x15L\xb1\x18\x99\xfd%\xd8\x08\ +\x82nj]\xa4\x22\xe2\x81\xa8\x96\xd5(\xe8\x9f\xae\xd5\ +\xc5\xf9\xb6\xc6\xd9\xf9\xbesf\xc6vf\xc0\xdf\xdd\x9c\ +\xef=\xefy\x9fs\xe6\x9c\xf3~\xdf9%2Fj\ +SK\x81\xdd\xc0Z\xe0:`\x11\xbd\xc5i`\x12\x98\ +\x00\xf6\x8c\x0dUg\x00J\x00#\xb5\xa9[\x80C\xc0\ +\xb2\xae\x85\xd7\x1a\xd3\xc0\xd6\xb1\xa1\xea\x07\xa5\xac\xe7\x8f\ +\xd1?\xc1\xcf1\x0d\x5c[&\xfcm\xfa-x\x081\ +\xef\xaa\x10\xfe\xf3\x8d\x9cY\xe0`R\x19h\xf8\xbd\xb6\ +B\x98\xb0\xf5\x9c\x19\x1b\xaaV\x16(\xa0\x96\x18\xa9M\ +\xcdr\xb6\x88Uezo\xb5i\x85E\xe5nG\xd0\ +)\xe7\x05t\x9b\xf3\x02\xfe\x0fl\xdfc\xfb\x90\xed\xf5\ +1\xdb\x9e\x13`\xfb\x11\xe05`\x188j{\x9f\xed\ +\xdc\x95\xb2\xa7\x04\xd8\x1e\x06\x9e\xaa+*\x01\x0f\x01/\ +\xe4\xd5\xe9\x19\x01\xb6\xd7\x01/\x93%\x98\x0dl\xb3\xfd\ +h\xb3z=!\xc0\xf6\xf5\xc0\xeb\xc0\xe2\x02\xb3\x8d\xcd\ +\x0a\xbb.\xc0\xf6\x95\xc0[\xc0\x92\x88\xe9\x8b\xcd\x0a\xbb\ +*\xc0\xf6\xe5\xc0Q\xe2\xd9\xf0\x93\x92^i\xf6\xa0k\ +\x02l/\x01\x8e\x00+#\xa6\x07\x80\xc7\xf2\x1evE\ +\x80\xed\xc5\xc0\x1b\xc0\x9a\x88\xe9\xdb\xc0\xa8\xa4\xbf\xf3\x0c\ +\x16\x5c\x80\xed2\xa1Wo\x8d\x98~\x0a\xdc-i\xb6\ +\xc8\xa8\x1b#\xf04po\xc4\xe6K`\x93\xa4_c\ +\xce\x92\x04\xd8.e=\xd7\x11\xb6w\x02\xdb#f\xdf\ +\x03\x1b$\xcd\xa4\xf8,\x0c*\x0b|\x07\xf0;0i\ +{UR\xa4\xcd}\x8d\x02\x8fG\xcc~\x06n\x93\xf4\ +m\xaa\xdf\x5c\x01Y\xfe1N\xd8\xda/\x00\xae\x01\xde\ +\xb3}U\xaa\xf3:_\x9b\x81\xfd\x11\xb3?\x81;%\ +}\xde\x8a\xef\xa2\x11x\x0e\xd8\xdcP\xb6\x0cx\xdf\xf6\ +\x15\xa9\x0d\xd8\xbe\x11x\x95\xf9/\xe4\xf5\xfc\x05\xdc'\ +\xe9\xa3T\xbfs4\x15\x90\xf5\xd8\x839u\x96\x13F\ +by\xcc\xb9\xed*p\x18\x18\x8c\x98>,\xa9\x16\xf3\ +\xd7\x8c\xbc\x11\xb84Ro%A\xc4ey\x06\xb6W\ +\x10v\xd9\x98\xaf\xbd\x92\x9e\x8f\xd8\xe4\x92'\xe0 a\ +\x1d.\xe2j\xe0\x1d\xdb\x177>\xb0}\x09!\xf8\x15\ +\x11\x1f\xfb%\xed\x8eFY@S\x01\x92N\x13\xb2\xbf\ +/\x22\xf5W\x03Gl_8W`{\x90\xf0\xb7\xa9\ +F\xea\x8e\x13r\xfd\x8e\xc8\x9d\xc4\x92N\x02\xeb\x81\xaf\ +\x22>n\x00\x0e\xdb\x1e\xb4=@\x98\xb07E\xeaL\ +\x00\xc3\x92:\xfe\x02X\xb8\x0fH\xfa\x11X\x078\xe2\ +\xe7f\xa0FX*\x1bW\xaeF&\x81;$\xfd\x91\ +\x18c!\xd1\xddU\x92\x09y\xcb\x0f\x11\xd3\x8d\xc0h\ +\xc4\xc6\x84\x8d\xea\xa7\xb4\xf0\xe2$\xa5\x07\x92\xbe&\x8c\ +\xc4\x89\x0e\xda:IH\x11\xbe\xeb\xc0\xc7<\x92\xf3\x1b\ +I\xc7\x80\x0d\xc0/m\xb4\xf3\x1bp\xbb\xa4\xe3m\xd4\ +-\xa4\xa5\x04M\xd2g\xc0\xa6,\xa0Tf\x81-\x92\ +>i\xa5\xadTZ\xce0%M\x00w\x11r\x97\x14\ +\x1e\x90\xf4f\xab\xed\xa4\xd2V\x8a,\xe9]`\x0b\xa1\ +w\x8b\xd8)\xe9\xa5v\xdaH\xa5\xed\x1c_\xd28\xb0\ +\x8d\x90\x885\xe3YIO\xb4\xeb?\x95\x8e^R$\ +\x1d\x04\xeeg\xfe\x9c\xd8\x07\xec\xe8\xc4w*\x1d\x1f%\ +I:`\xfbc\xc2\x06v\x11\xf0a6O\x16\x84s\ +r\x16&\xe9\x1b\xe0\x99s\xe1\xabU\xca\x84\x13\xf0~\ +\xe5T\x85\x90\x9b\xd4\x7f\x9f\x19\xc8N\x03{\x91\xc6\xb7\ +\xba\xc9\x0a!3l\xfc\xc0T\xf4\xfa\xd7KL\x94\x81\ +=\x84c\xfb~c\x1a\xd8[\xcen}l\xa5\xbfD\ +\xcc]\xf6\x98\xf9\xf70!\xbb\xf4\xb1\x8b\xff\xae\xdb\x14\ +}\xab\xef\x06\xa78\xfb\xba\xcd\x09\x80\x7f\x00\xc4\x1e\x10\ +)3[\x85\xf7\x00\x00\x00\x00IEND\xaeB`\ +\x82\ " qt_resource_name = b"\ @@ -1203,198 +1287,198 @@ qt_resource_name = b"\ \x07\x03}\xc3\ \x00i\ \x00m\x00a\x00g\x00e\x00s\ -\x00\x1a\ -\x03\x0e\xe4\x87\ -\x00c\ -\x00h\x00e\x00c\x00k\x00b\x00o\x00x\x00_\x00c\x00h\x00e\x00c\x00k\x00e\x00d\x00_\ -\x00h\x00o\x00v\x00e\x00r\x00.\x00p\x00n\x00g\ -\x00\x0f\ -\x06S%\xa7\ -\x00b\ -\x00r\x00a\x00n\x00c\x00h\x00_\x00o\x00p\x00e\x00n\x00.\x00p\x00n\x00g\ \x00\x0e\ -\x0e\xde\xfa\xc7\ -\x00l\ -\x00e\x00f\x00t\x00_\x00a\x00r\x00r\x00o\x00w\x00.\x00p\x00n\x00g\ -\x00 \ -\x09\xd7\x1f\xa7\ -\x00c\ -\x00h\x00e\x00c\x00k\x00b\x00o\x00x\x00_\x00i\x00n\x00d\x00e\x00t\x00e\x00r\x00m\ -\x00i\x00n\x00a\x00t\x00e\x00_\x00f\x00o\x00c\x00u\x00s\x00.\x00p\x00n\x00g\ -\x00\x1a\ -\x05\x11\xe0\xe7\ -\x00c\ -\x00h\x00e\x00c\x00k\x00b\x00o\x00x\x00_\x00c\x00h\x00e\x00c\x00k\x00e\x00d\x00_\ -\x00f\x00o\x00c\x00u\x00s\x00.\x00p\x00n\x00g\ +\x04\xa2\xfc\xa7\ +\x00d\ +\x00o\x00w\x00n\x00_\x00a\x00r\x00r\x00o\x00w\x00.\x00p\x00n\x00g\ \x00\x11\ \x0b\xda0\xa7\ \x00b\ \x00r\x00a\x00n\x00c\x00h\x00_\x00c\x00l\x00o\x00s\x00e\x00d\x00.\x00p\x00n\x00g\ \ -\x00\x0f\ -\x01s\x8b\x07\ -\x00u\ -\x00p\x00_\x00a\x00r\x00r\x00o\x00w\x00_\x00o\x00n\x00.\x00p\x00n\x00g\ -\x00\x11\ -\x00\xb8\x8c\x07\ -\x00l\ -\x00e\x00f\x00t\x00_\x00a\x00r\x00r\x00o\x00w\x00_\x00o\x00n\x00.\x00p\x00n\x00g\ -\ -\x00\x1a\ -\x01\x87\xaeg\ +\x00\x1d\ +\x09\x07\x81\x07\ \x00c\ -\x00h\x00e\x00c\x00k\x00b\x00o\x00x\x00_\x00i\x00n\x00d\x00e\x00t\x00e\x00r\x00m\ -\x00i\x00n\x00a\x00t\x00e\x00.\x00p\x00n\x00g\ -\x00\x15\ -\x0f\xf3\xc0\x07\ -\x00u\ -\x00p\x00_\x00a\x00r\x00r\x00o\x00w\x00_\x00d\x00i\x00s\x00a\x00b\x00l\x00e\x00d\ -\x00.\x00p\x00n\x00g\ -\x00\x0c\ -\x06\xe6\xe6g\ -\x00u\ -\x00p\x00_\x00a\x00r\x00r\x00o\x00w\x00.\x00p\x00n\x00g\ +\x00h\x00e\x00c\x00k\x00b\x00o\x00x\x00_\x00c\x00h\x00e\x00c\x00k\x00e\x00d\x00_\ +\x00d\x00i\x00s\x00a\x00b\x00l\x00e\x00d\x00.\x00p\x00n\x00g\ \x00\x1c\ \x08?\xdag\ \x00c\ \x00h\x00e\x00c\x00k\x00b\x00o\x00x\x00_\x00u\x00n\x00c\x00h\x00e\x00c\x00k\x00e\ \x00d\x00_\x00f\x00o\x00c\x00u\x00s\x00.\x00p\x00n\x00g\ -\x00\x12\ -\x01.\x03'\ -\x00c\ -\x00o\x00m\x00b\x00o\x00b\x00o\x00x\x00_\x00a\x00r\x00r\x00o\x00w\x00.\x00p\x00n\ -\x00g\ -\x00\x12\ -\x05\x8f\x9d\x07\ -\x00b\ -\x00r\x00a\x00n\x00c\x00h\x00_\x00o\x00p\x00e\x00n\x00_\x00o\x00n\x00.\x00p\x00n\ -\x00g\ -\x00\x1b\ -\x03Z2'\ -\x00c\ -\x00o\x00m\x00b\x00o\x00b\x00o\x00x\x00_\x00a\x00r\x00r\x00o\x00w\x00_\x00d\x00i\ -\x00s\x00a\x00b\x00l\x00e\x00d\x00.\x00p\x00n\x00g\ -\x00\x14\ -\x07\xec\xd1\xc7\ -\x00c\ -\x00h\x00e\x00c\x00k\x00b\x00o\x00x\x00_\x00c\x00h\x00e\x00c\x00k\x00e\x00d\x00.\ -\x00p\x00n\x00g\ \x00#\ \x06\xf2\x1aG\ \x00c\ \x00h\x00e\x00c\x00k\x00b\x00o\x00x\x00_\x00i\x00n\x00d\x00e\x00t\x00e\x00r\x00m\ \x00i\x00n\x00a\x00t\x00e\x00_\x00d\x00i\x00s\x00a\x00b\x00l\x00e\x00d\x00.\x00p\ \x00n\x00g\ -\x00\x17\ -\x0ce\xce\x07\ -\x00l\ -\x00e\x00f\x00t\x00_\x00a\x00r\x00r\x00o\x00w\x00_\x00d\x00i\x00s\x00a\x00b\x00l\ -\x00e\x00d\x00.\x00p\x00n\x00g\ -\x00\x17\ -\x0c\xabQ\x07\ -\x00d\ -\x00o\x00w\x00n\x00_\x00a\x00r\x00r\x00o\x00w\x00_\x00d\x00i\x00s\x00a\x00b\x00l\ -\x00e\x00d\x00.\x00p\x00n\x00g\ \x00\x12\ -\x03\x8d\x04G\ -\x00r\ -\x00i\x00g\x00h\x00t\x00_\x00a\x00r\x00r\x00o\x00w\x00_\x00o\x00n\x00.\x00p\x00n\ +\x01.\x03'\ +\x00c\ +\x00o\x00m\x00b\x00o\x00b\x00o\x00x\x00_\x00a\x00r\x00r\x00o\x00w\x00.\x00p\x00n\ \x00g\ -\x00\x14\ -\x04^-\xa7\ -\x00b\ -\x00r\x00a\x00n\x00c\x00h\x00_\x00c\x00l\x00o\x00s\x00e\x00d\x00_\x00o\x00n\x00.\ -\x00p\x00n\x00g\ -\x00\x15\ -\x03'rg\ -\x00c\ -\x00o\x00m\x00b\x00o\x00b\x00o\x00x\x00_\x00a\x00r\x00r\x00o\x00w\x00_\x00o\x00n\ -\x00.\x00p\x00n\x00g\ -\x00\x1d\ -\x09\x07\x81\x07\ -\x00c\ -\x00h\x00e\x00c\x00k\x00b\x00o\x00x\x00_\x00c\x00h\x00e\x00c\x00k\x00e\x00d\x00_\ -\x00d\x00i\x00s\x00a\x00b\x00l\x00e\x00d\x00.\x00p\x00n\x00g\ -\x00\x0e\ -\x04\xa2\xfc\xa7\ -\x00d\ -\x00o\x00w\x00n\x00_\x00a\x00r\x00r\x00o\x00w\x00.\x00p\x00n\x00g\ -\x00\x0f\ -\x0c\xe2hg\ -\x00t\ -\x00r\x00a\x00n\x00s\x00p\x00a\x00r\x00e\x00n\x00t\x00.\x00p\x00n\x00g\ -\x00\x1f\ -\x0a\xae'G\ -\x00c\ -\x00h\x00e\x00c\x00k\x00b\x00o\x00x\x00_\x00u\x00n\x00c\x00h\x00e\x00c\x00k\x00e\ -\x00d\x00_\x00d\x00i\x00s\x00a\x00b\x00l\x00e\x00d\x00.\x00p\x00n\x00g\ -\x00\x18\ -\x03\x8e\xdeg\ -\x00r\ -\x00i\x00g\x00h\x00t\x00_\x00a\x00r\x00r\x00o\x00w\x00_\x00d\x00i\x00s\x00a\x00b\ -\x00l\x00e\x00d\x00.\x00p\x00n\x00g\ -\x00\x11\ -\x01\x1f\xc3\x87\ -\x00d\ -\x00o\x00w\x00n\x00_\x00a\x00r\x00r\x00o\x00w\x00_\x00o\x00n\x00.\x00p\x00n\x00g\ -\ -\x00\x16\ -\x01u\xcc\x87\ -\x00c\ -\x00h\x00e\x00c\x00k\x00b\x00o\x00x\x00_\x00u\x00n\x00c\x00h\x00e\x00c\x00k\x00e\ -\x00d\x00.\x00p\x00n\x00g\ -\x00 \ -\x0f\xd4\x1b\xc7\ -\x00c\ -\x00h\x00e\x00c\x00k\x00b\x00o\x00x\x00_\x00i\x00n\x00d\x00e\x00t\x00e\x00r\x00m\ -\x00i\x00n\x00a\x00t\x00e\x00_\x00h\x00o\x00v\x00e\x00r\x00.\x00p\x00n\x00g\ -\x00\x0f\ -\x02\x9f\x05\x87\ -\x00r\ -\x00i\x00g\x00h\x00t\x00_\x00a\x00r\x00r\x00o\x00w\x00.\x00p\x00n\x00g\ \x00\x1c\ \x0e<\xde\x07\ \x00c\ \x00h\x00e\x00c\x00k\x00b\x00o\x00x\x00_\x00u\x00n\x00c\x00h\x00e\x00c\x00k\x00e\ \x00d\x00_\x00h\x00o\x00v\x00e\x00r\x00.\x00p\x00n\x00g\ +\x00\x14\ +\x07\xec\xd1\xc7\ +\x00c\ +\x00h\x00e\x00c\x00k\x00b\x00o\x00x\x00_\x00c\x00h\x00e\x00c\x00k\x00e\x00d\x00.\ +\x00p\x00n\x00g\ +\x00\x1a\ +\x05\x11\xe0\xe7\ +\x00c\ +\x00h\x00e\x00c\x00k\x00b\x00o\x00x\x00_\x00c\x00h\x00e\x00c\x00k\x00e\x00d\x00_\ +\x00f\x00o\x00c\x00u\x00s\x00.\x00p\x00n\x00g\ +\x00\x18\ +\x03\x8e\xdeg\ +\x00r\ +\x00i\x00g\x00h\x00t\x00_\x00a\x00r\x00r\x00o\x00w\x00_\x00d\x00i\x00s\x00a\x00b\ +\x00l\x00e\x00d\x00.\x00p\x00n\x00g\ +\x00\x15\ +\x03'rg\ +\x00c\ +\x00o\x00m\x00b\x00o\x00b\x00o\x00x\x00_\x00a\x00r\x00r\x00o\x00w\x00_\x00o\x00n\ +\x00.\x00p\x00n\x00g\ +\x00\x12\ +\x03\x8d\x04G\ +\x00r\ +\x00i\x00g\x00h\x00t\x00_\x00a\x00r\x00r\x00o\x00w\x00_\x00o\x00n\x00.\x00p\x00n\ +\x00g\ +\x00\x16\ +\x01u\xcc\x87\ +\x00c\ +\x00h\x00e\x00c\x00k\x00b\x00o\x00x\x00_\x00u\x00n\x00c\x00h\x00e\x00c\x00k\x00e\ +\x00d\x00.\x00p\x00n\x00g\ +\x00\x17\ +\x0c\xabQ\x07\ +\x00d\ +\x00o\x00w\x00n\x00_\x00a\x00r\x00r\x00o\x00w\x00_\x00d\x00i\x00s\x00a\x00b\x00l\ +\x00e\x00d\x00.\x00p\x00n\x00g\ +\x00\x15\ +\x0f\xf3\xc0\x07\ +\x00u\ +\x00p\x00_\x00a\x00r\x00r\x00o\x00w\x00_\x00d\x00i\x00s\x00a\x00b\x00l\x00e\x00d\ +\x00.\x00p\x00n\x00g\ +\x00\x14\ +\x04^-\xa7\ +\x00b\ +\x00r\x00a\x00n\x00c\x00h\x00_\x00c\x00l\x00o\x00s\x00e\x00d\x00_\x00o\x00n\x00.\ +\x00p\x00n\x00g\ +\x00\x0f\ +\x06S%\xa7\ +\x00b\ +\x00r\x00a\x00n\x00c\x00h\x00_\x00o\x00p\x00e\x00n\x00.\x00p\x00n\x00g\ +\x00\x17\ +\x0ce\xce\x07\ +\x00l\ +\x00e\x00f\x00t\x00_\x00a\x00r\x00r\x00o\x00w\x00_\x00d\x00i\x00s\x00a\x00b\x00l\ +\x00e\x00d\x00.\x00p\x00n\x00g\ +\x00\x0e\ +\x0e\xde\xfa\xc7\ +\x00l\ +\x00e\x00f\x00t\x00_\x00a\x00r\x00r\x00o\x00w\x00.\x00p\x00n\x00g\ +\x00\x1f\ +\x0a\xae'G\ +\x00c\ +\x00h\x00e\x00c\x00k\x00b\x00o\x00x\x00_\x00u\x00n\x00c\x00h\x00e\x00c\x00k\x00e\ +\x00d\x00_\x00d\x00i\x00s\x00a\x00b\x00l\x00e\x00d\x00.\x00p\x00n\x00g\ +\x00\x11\ +\x00\xb8\x8c\x07\ +\x00l\ +\x00e\x00f\x00t\x00_\x00a\x00r\x00r\x00o\x00w\x00_\x00o\x00n\x00.\x00p\x00n\x00g\ +\ +\x00\x0f\ +\x02\x9f\x05\x87\ +\x00r\ +\x00i\x00g\x00h\x00t\x00_\x00a\x00r\x00r\x00o\x00w\x00.\x00p\x00n\x00g\ +\x00\x1b\ +\x03Z2'\ +\x00c\ +\x00o\x00m\x00b\x00o\x00b\x00o\x00x\x00_\x00a\x00r\x00r\x00o\x00w\x00_\x00d\x00i\ +\x00s\x00a\x00b\x00l\x00e\x00d\x00.\x00p\x00n\x00g\ +\x00\x0f\ +\x01s\x8b\x07\ +\x00u\ +\x00p\x00_\x00a\x00r\x00r\x00o\x00w\x00_\x00o\x00n\x00.\x00p\x00n\x00g\ +\x00 \ +\x0f\xd4\x1b\xc7\ +\x00c\ +\x00h\x00e\x00c\x00k\x00b\x00o\x00x\x00_\x00i\x00n\x00d\x00e\x00t\x00e\x00r\x00m\ +\x00i\x00n\x00a\x00t\x00e\x00_\x00h\x00o\x00v\x00e\x00r\x00.\x00p\x00n\x00g\ +\x00\x12\ +\x05\x8f\x9d\x07\ +\x00b\ +\x00r\x00a\x00n\x00c\x00h\x00_\x00o\x00p\x00e\x00n\x00_\x00o\x00n\x00.\x00p\x00n\ +\x00g\ +\x00\x1a\ +\x01\x87\xaeg\ +\x00c\ +\x00h\x00e\x00c\x00k\x00b\x00o\x00x\x00_\x00i\x00n\x00d\x00e\x00t\x00e\x00r\x00m\ +\x00i\x00n\x00a\x00t\x00e\x00.\x00p\x00n\x00g\ +\x00\x0f\ +\x0c\xe2hg\ +\x00t\ +\x00r\x00a\x00n\x00s\x00p\x00a\x00r\x00e\x00n\x00t\x00.\x00p\x00n\x00g\ +\x00\x0c\ +\x06\xe6\xe6g\ +\x00u\ +\x00p\x00_\x00a\x00r\x00r\x00o\x00w\x00.\x00p\x00n\x00g\ +\x00 \ +\x09\xd7\x1f\xa7\ +\x00c\ +\x00h\x00e\x00c\x00k\x00b\x00o\x00x\x00_\x00i\x00n\x00d\x00e\x00t\x00e\x00r\x00m\ +\x00i\x00n\x00a\x00t\x00e\x00_\x00f\x00o\x00c\x00u\x00s\x00.\x00p\x00n\x00g\ +\x00\x11\ +\x01\x1f\xc3\x87\ +\x00d\ +\x00o\x00w\x00n\x00_\x00a\x00r\x00r\x00o\x00w\x00_\x00o\x00n\x00.\x00p\x00n\x00g\ +\ +\x00\x1a\ +\x03\x0e\xe4\x87\ +\x00c\ +\x00h\x00e\x00c\x00k\x00b\x00o\x00x\x00_\x00c\x00h\x00e\x00c\x00k\x00e\x00d\x00_\ +\x00h\x00o\x00v\x00e\x00r\x00.\x00p\x00n\x00g\ " qt_resource_struct = b"\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ \x00\x00\x00\x16\x00\x02\x00\x00\x00 \x00\x00\x00\x03\ -\x00\x00\x01t\x00\x00\x00\x00\x00\x01\x00\x00\x1a\x05\ -\x00\x00\x05\x5c\x00\x00\x00\x00\x00\x01\x00\x00?\x7f\ -\x00\x00\x02b\x00\x00\x00\x00\x00\x01\x00\x00\x1f/\ -\x00\x00\x01P\x00\x00\x00\x00\x00\x01\x00\x00\x19c\ -\x00\x00\x05\x84\x00\x00\x00\x00\x00\x01\x00\x00@(\ -\x00\x00\x01\x9c\x00\x00\x00\x00\x00\x01\x00\x00\x1a\xaf\ -\x00\x00\x05\xfc\x00\x00\x00\x00\x00\x01\x00\x00Cz\ +\x00\x00\x04\x1e\x00\x00\x00\x00\x00\x01\x00\x000\x5c\ +\x00\x00\x05\xfc\x00\x00\x00\x00\x00\x01\x00\x00F\x05\ +\x00\x00\x01<\x00\x00\x00\x00\x00\x01\x00\x00\x0f\xec\ +\x00\x00\x04\xa6\x00\x00\x00\x00\x00\x01\x00\x002S\ +\x00\x00\x02\x9c\x00\x00\x00\x00\x00\x01\x00\x00\x1b\xf7\ +\x00\x00\x05:\x00\x00\x00\x00\x00\x01\x00\x00<\x1c\ +\x00\x00\x04F\x00\x00\x00\x00\x00\x01\x00\x001\x06\ +\x00\x00\x06$\x00\x00\x00\x00\x00\x01\x00\x00F\xae\ +\x00\x00\x02B\x00\x00\x00\x00\x00\x01\x00\x00\x1a\xa9\ +\x00\x00\x04j\x00\x00\x00\x00\x00\x01\x00\x001\xaa\ +\x00\x00\x02r\x00\x00\x00\x00\x00\x01\x00\x00\x1bS\ +\x00\x00\x02\x0c\x00\x00\x00\x00\x00\x01\x00\x00\x1a\x05\ +\x00\x00\x032\x00\x00\x00\x00\x00\x01\x00\x00\x1e\xa3\ \x00\x00\x00(\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ -\x00\x00\x04,\x00\x00\x00\x00\x00\x01\x00\x007\x8e\ -\x00\x00\x02\xb6\x00\x00\x00\x00\x00\x01\x00\x00'\x0d\ -\x00\x00\x03\xd4\x00\x00\x00\x00\x00\x01\x00\x00/\x09\ -\x00\x00\x05&\x00\x00\x00\x00\x00\x01\x00\x00>\xdb\ -\x00\x00\x03\xfe\x00\x00\x00\x00\x00\x01\x00\x00/\xad\ -\x00\x00\x04\x9c\x00\x00\x00\x00\x00\x01\x00\x003}\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}>.{representation}" # noqa + "path": "C:\\projects\\test_performance\\Assets\\Cylinder\\publish\\workfile\\workfileLookdev\\{}\\{}".format(version_str, file_name), # noqa: E501 + "template": "{root[work]}\\{project[name]}\\{hierarchy}\\{asset}\\publish\\{family}\\{subset}\\v{version:0>3}\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}>.{representation}" # noqa: E501 }, "type": "representation", "schema": "openpype:representation-2.0" @@ -188,21 +188,21 @@ class TestPerformance(): create_files=False): ret = [ { - "path": "{root[work]}" + "{root[work]}/test_performance/Assets/Cylinder/publish/workfile/workfileLookdev/v{:03d}/test_Cylinder_A_workfileLookdev_v{:03d}.dat".format(i, i), #noqa + "path": "{root[work]}" + "{root[work]}/test_performance/Assets/Cylinder/publish/workfile/workfileLookdev/v{:03d}/test_Cylinder_A_workfileLookdev_v{:03d}.dat".format(i, i), # noqa: E501 "_id": '{}'.format(file_id), "hash": "temphash", "sites": self.get_sites(self.MAX_NUMBER_OF_SITES), "size": random.randint(0, self.MAX_FILE_SIZE_B) }, { - "path": "{root[work]}" + "/test_performance/Assets/Cylinder/publish/workfile/workfileLookdev/v{:03d}/test_Cylinder_B_workfileLookdev_v{:03d}.dat".format(i, i), #noqa + "path": "{root[work]}" + "/test_performance/Assets/Cylinder/publish/workfile/workfileLookdev/v{:03d}/test_Cylinder_B_workfileLookdev_v{:03d}.dat".format(i, i), # noqa: E501 "_id": '{}'.format(file_id2), "hash": "temphash", "sites": self.get_sites(self.MAX_NUMBER_OF_SITES), "size": random.randint(0, self.MAX_FILE_SIZE_B) }, { - "path": "{root[work]}" + "/test_performance/Assets/Cylinder/publish/workfile/workfileLookdev/v{:03d}/test_Cylinder_C_workfileLookdev_v{:03d}.dat".format(i, i), #noqa + "path": "{root[work]}" + "/test_performance/Assets/Cylinder/publish/workfile/workfileLookdev/v{:03d}/test_Cylinder_C_workfileLookdev_v{:03d}.dat".format(i, i), # noqa: E501 "_id": '{}'.format(file_id3), "hash": "temphash", "sites": self.get_sites(self.MAX_NUMBER_OF_SITES), @@ -223,8 +223,8 @@ class TestPerformance(): ret = {} ret['{}'.format(file_id)] = { "path": "{root[work]}" + - "/test_performance/Assets/Cylinder/publish/workfile/workfileLookdev/" #noqa - "v{:03d}/test_CylinderA_workfileLookdev_v{:03d}.mb".format(i, i), # noqa + "/test_performance/Assets/Cylinder/publish/workfile/workfileLookdev/" # noqa: E501 + "v{:03d}/test_CylinderA_workfileLookdev_v{:03d}.mb".format(i, i), # noqa: E501 "hash": "temphash", "sites": ["studio"], "size": 87236 @@ -232,16 +232,16 @@ class TestPerformance(): ret['{}'.format(file_id2)] = { "path": "{root[work]}" + - "/test_performance/Assets/Cylinder/publish/workfile/workfileLookdev/" #noqa - "v{:03d}/test_CylinderB_workfileLookdev_v{:03d}.mb".format(i, i), # noqa + "/test_performance/Assets/Cylinder/publish/workfile/workfileLookdev/" # noqa: E501 + "v{:03d}/test_CylinderB_workfileLookdev_v{:03d}.mb".format(i, i), # noqa: E501 "hash": "temphash", "sites": ["studio"], "size": 87236 } ret['{}'.format(file_id3)] = { "path": "{root[work]}" + - "/test_performance/Assets/Cylinder/publish/workfile/workfileLookdev/" #noqa - "v{:03d}/test_CylinderC_workfileLookdev_v{:03d}.mb".format(i, i), # noqa + "/test_performance/Assets/Cylinder/publish/workfile/workfileLookdev/" # noqa: E501 + "v{:03d}/test_CylinderC_workfileLookdev_v{:03d}.mb".format(i, i), # noqa: E501 "hash": "temphash", "sites": ["studio"], "size": 87236 diff --git a/openpype/tools/context_dialog/window.py b/openpype/tools/context_dialog/window.py index 124a1beda3..3e7c8c7065 100644 --- a/openpype/tools/context_dialog/window.py +++ b/openpype/tools/context_dialog/window.py @@ -8,14 +8,12 @@ from openpype import style from openpype.tools.utils.lib import center_window from openpype.tools.utils.widgets import AssetWidget from openpype.tools.utils.constants import ( - TASK_NAME_ROLE, PROJECT_NAME_ROLE ) +from openpype.tools.utils.tasks_widget import TasksWidget from openpype.tools.utils.models import ( ProjectModel, - ProjectSortFilterProxy, - TasksModel, - TasksProxyModel + ProjectSortFilterProxy ) @@ -77,15 +75,11 @@ class ContextDialog(QtWidgets.QDialog): left_side_layout.addWidget(assets_widget) # Right side of window contains only tasks - task_view = QtWidgets.QListView(main_splitter) - task_model = TasksModel(dbcon) - task_proxy = TasksProxyModel() - task_proxy.setSourceModel(task_model) - task_view.setModel(task_proxy) + tasks_widget = TasksWidget(dbcon, main_splitter) # Add widgets to main splitter main_splitter.addWidget(left_side_widget) - main_splitter.addWidget(task_view) + main_splitter.addWidget(tasks_widget) # Set stretch of both sides main_splitter.setStretchFactor(0, 7) @@ -119,7 +113,7 @@ class ContextDialog(QtWidgets.QDialog): assets_widget.selection_changed.connect(self._on_asset_change) assets_widget.refresh_triggered.connect(self._on_asset_refresh_trigger) assets_widget.refreshed.connect(self._on_asset_widget_refresh_finished) - task_view.selectionModel().selectionChanged.connect( + tasks_widget.task_changed.selectionChanged.connect( self._on_task_change ) ok_btn.clicked.connect(self._on_ok_click) @@ -133,9 +127,7 @@ class ContextDialog(QtWidgets.QDialog): self._assets_widget = assets_widget - self._task_view = task_view - self._task_model = task_model - self._task_proxy = task_proxy + self._tasks_widget = tasks_widget self._ok_btn = ok_btn @@ -279,15 +271,13 @@ class ContextDialog(QtWidgets.QDialog): self._dbcon.Session["AVALON_ASSET"] = self._set_context_asset self._assets_widget.setEnabled(False) self._assets_widget.select_assets(self._set_context_asset) - self._set_asset_to_task_model() + self._set_asset_to_tasks_widget() else: self._assets_widget.setEnabled(True) self._assets_widget.set_current_asset_btn_visibility(False) # Refresh tasks - self._task_model.refresh() - # Sort tasks - self._task_proxy.sort(0, QtCore.Qt.AscendingOrder) + self._tasks_widget.refresh() self._ignore_value_changes = False @@ -314,20 +304,19 @@ class ContextDialog(QtWidgets.QDialog): """Selected assets have changed""" if self._ignore_value_changes: return - self._set_asset_to_task_model() + self._set_asset_to_tasks_widget() def _on_task_change(self): self._validate_strict() - def _set_asset_to_task_model(self): + def _set_asset_to_tasks_widget(self): # filter None docs they are silo asset_docs = self._assets_widget.get_selected_assets() asset_ids = [asset_doc["_id"] for asset_doc in asset_docs] asset_id = None if asset_ids: asset_id = asset_ids[0] - self._task_model.set_asset_id(asset_id) - self._task_proxy.sort(0, QtCore.Qt.AscendingOrder) + self._tasks_widget.set_asset_id(asset_id) def _confirm_values(self): """Store values to output.""" @@ -355,11 +344,7 @@ class ContextDialog(QtWidgets.QDialog): def get_selected_task(self): """Currently selected task.""" - task_name = None - index = self._task_view.selectionModel().currentIndex() - if index.isValid(): - task_name = index.data(TASK_NAME_ROLE) - return task_name + return self._tasks_widget.get_selected_task_name() def _validate_strict(self): if not self._strict: diff --git a/openpype/tools/launcher/models.py b/openpype/tools/launcher/models.py index f87871409e..427475cb4b 100644 --- a/openpype/tools/launcher/models.py +++ b/openpype/tools/launcher/models.py @@ -19,102 +19,6 @@ from openpype.lib import ApplicationManager log = logging.getLogger(__name__) -class TaskModel(QtGui.QStandardItemModel): - """A model listing the tasks combined for a list of assets""" - - def __init__(self, dbcon, parent=None): - super(TaskModel, self).__init__(parent=parent) - self.dbcon = dbcon - - self._num_assets = 0 - - self.default_icon = qtawesome.icon( - "fa.male", color=style.colors.default - ) - self.no_task_icon = qtawesome.icon( - "fa.exclamation-circle", color=style.colors.mid - ) - - self._icons = {} - - self._get_task_icons() - - def _get_task_icons(self): - if not self.dbcon.Session.get("AVALON_PROJECT"): - return - - # Get the project configured icons from database - project = self.dbcon.find_one({"type": "project"}) - for task in project["config"].get("tasks") or []: - icon_name = task.get("icon") - if icon_name: - self._icons[task["name"]] = qtawesome.icon( - "fa.{}".format(icon_name), color=style.colors.default - ) - - def set_assets(self, asset_ids=None, asset_docs=None): - """Set assets to track by their database id - - Arguments: - asset_ids (list): List of asset ids. - asset_docs (list): List of asset entities from MongoDB. - - """ - - if asset_docs is None and asset_ids is not None: - # find assets in db by query - asset_docs = list(self.dbcon.find({ - "type": "asset", - "_id": {"$in": asset_ids} - })) - db_assets_ids = tuple(asset_doc["_id"] for asset_doc in asset_docs) - - # check if all assets were found - not_found = tuple( - str(asset_id) - for asset_id in asset_ids - if asset_id not in db_assets_ids - ) - - assert not not_found, "Assets not found by id: {0}".format( - ", ".join(not_found) - ) - - self.clear() - - if not asset_docs: - return - - task_names = set() - for asset_doc in asset_docs: - asset_tasks = asset_doc.get("data", {}).get("tasks") or set() - task_names.update(asset_tasks) - - self.beginResetModel() - - if not task_names: - item = QtGui.QStandardItem(self.no_task_icon, "No task") - item.setEnabled(False) - self.appendRow(item) - - else: - for task_name in sorted(task_names): - icon = self._icons.get(task_name, self.default_icon) - item = QtGui.QStandardItem(icon, task_name) - self.appendRow(item) - - self.endResetModel() - - def headerData(self, section, orientation, role): - if ( - role == QtCore.Qt.DisplayRole - and orientation == QtCore.Qt.Horizontal - and section == 0 - ): - return "Tasks" - return super(TaskModel, self).headerData(section, orientation, role) - - class ActionModel(QtGui.QStandardItemModel): def __init__(self, dbcon, parent=None): super(ActionModel, self).__init__(parent=parent) diff --git a/openpype/tools/launcher/widgets.py b/openpype/tools/launcher/widgets.py index 5e01488ae6..edda8d08b5 100644 --- a/openpype/tools/launcher/widgets.py +++ b/openpype/tools/launcher/widgets.py @@ -6,7 +6,7 @@ from avalon.vendor import qtawesome from .delegates import ActionDelegate from . import lib -from .models import TaskModel, ActionModel +from .models import ActionModel from openpype.tools.flickcharm import FlickCharm from .constants import ( ACTION_ROLE, @@ -90,9 +90,6 @@ class ActionBar(QtWidgets.QWidget): self.project_handler = project_handler self.dbcon = dbcon - layout = QtWidgets.QHBoxLayout(self) - layout.setContentsMargins(8, 0, 8, 0) - view = QtWidgets.QListView(self) view.setProperty("mode", "icon") view.setObjectName("IconView") @@ -116,6 +113,8 @@ class ActionBar(QtWidgets.QWidget): ) view.setItemDelegate(delegate) + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(view) self.model = model @@ -261,92 +260,6 @@ class ActionBar(QtWidgets.QWidget): self.action_clicked.emit(action) -class TasksWidget(QtWidgets.QWidget): - """Widget showing active Tasks""" - - task_changed = QtCore.Signal() - selection_mode = ( - QtCore.QItemSelectionModel.Select | QtCore.QItemSelectionModel.Rows - ) - - def __init__(self, dbcon, parent=None): - super(TasksWidget, self).__init__(parent) - - self.dbcon = dbcon - - view = QtWidgets.QTreeView(self) - view.setIndentation(0) - view.setEditTriggers(QtWidgets.QTreeView.NoEditTriggers) - model = TaskModel(self.dbcon) - view.setModel(model) - - layout = QtWidgets.QVBoxLayout(self) - layout.addWidget(view) - - view.selectionModel().selectionChanged.connect(self.task_changed) - - self.model = model - self.view = view - - self._last_selected_task = None - - def set_asset(self, asset_id): - if asset_id is None: - # Asset deselected - self.model.set_assets() - return - - # Try and preserve the last selected task and reselect it - # after switching assets. If there's no currently selected - # asset keep whatever the "last selected" was prior to it. - current = self.get_current_task() - if current: - self._last_selected_task = current - - self.model.set_assets([asset_id]) - - if self._last_selected_task: - self.select_task(self._last_selected_task) - - # Force a task changed emit. - self.task_changed.emit() - - def select_task(self, task_name): - """Select a task by name. - - If the task does not exist in the current model then selection is only - cleared. - - Args: - task (str): Name of the task to select. - - """ - - # Clear selection - self.view.selectionModel().clearSelection() - - # Select the task - for row in range(self.model.rowCount()): - index = self.model.index(row, 0) - _task_name = index.data(QtCore.Qt.DisplayRole) - if _task_name == task_name: - self.view.selectionModel().select(index, self.selection_mode) - # Set the currently active index - self.view.setCurrentIndex(index) - break - - def get_current_task(self): - """Return name of task at current index (selected) - - Returns: - str: Name of the current task. - - """ - index = self.view.currentIndex() - if self.view.selectionModel().isSelected(index): - return index.data(QtCore.Qt.DisplayRole) - - class ActionHistory(QtWidgets.QPushButton): trigger_history = QtCore.Signal(tuple) diff --git a/openpype/tools/launcher/window.py b/openpype/tools/launcher/window.py index 454445824e..8d6b609282 100644 --- a/openpype/tools/launcher/window.py +++ b/openpype/tools/launcher/window.py @@ -9,13 +9,14 @@ from openpype import style from openpype.api import resources from openpype.tools.utils.widgets import AssetWidget +from openpype.tools.utils.tasks_widget import TasksWidget + from avalon.vendor import qtawesome from .models import ProjectModel from .lib import get_action_label, ProjectHandler from .widgets import ( ProjectBar, ActionBar, - TasksWidget, ActionHistory, SlidePageWidget ) @@ -91,8 +92,6 @@ class ProjectsPanel(QtWidgets.QWidget): def __init__(self, project_handler, parent=None): super(ProjectsPanel, self).__init__(parent=parent) - layout = QtWidgets.QVBoxLayout(self) - view = ProjectIconView(parent=self) view.setSelectionMode(QtWidgets.QListView.NoSelection) flick = FlickCharm(parent=self) @@ -100,6 +99,8 @@ class ProjectsPanel(QtWidgets.QWidget): view.setModel(project_handler.model) + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(view) view.clicked.connect(self.on_clicked) @@ -123,28 +124,21 @@ class AssetsPanel(QtWidgets.QWidget): self.dbcon = dbcon - # project bar - project_bar_widget = QtWidgets.QWidget(self) - - layout = QtWidgets.QHBoxLayout(project_bar_widget) - layout.setSpacing(4) - + # Project bar btn_back_icon = qtawesome.icon("fa.angle-left", color="white") - btn_back = QtWidgets.QPushButton(project_bar_widget) + btn_back = QtWidgets.QPushButton(self) btn_back.setIcon(btn_back_icon) - project_bar = ProjectBar(project_handler, project_bar_widget) + project_bar = ProjectBar(project_handler, self) - layout.addWidget(btn_back) - layout.addWidget(project_bar) + project_bar_layout = QtWidgets.QHBoxLayout() + project_bar_layout.setContentsMargins(0, 0, 0, 0) + project_bar_layout.setSpacing(4) + project_bar_layout.addWidget(btn_back) + project_bar_layout.addWidget(project_bar) - # assets - assets_proxy_widgets = QtWidgets.QWidget(self) - assets_proxy_widgets.setContentsMargins(0, 0, 0, 0) - assets_layout = QtWidgets.QVBoxLayout(assets_proxy_widgets) - assets_widget = AssetWidget( - dbcon=self.dbcon, parent=assets_proxy_widgets - ) + # Assets widget + assets_widget = AssetWidget(dbcon=self.dbcon, parent=self) # Make assets view flickable flick = FlickCharm(parent=self) @@ -152,18 +146,19 @@ class AssetsPanel(QtWidgets.QWidget): assets_widget.view.setVerticalScrollMode( assets_widget.view.ScrollPerPixel ) - assets_layout.addWidget(assets_widget) - # tasks + # Tasks widget tasks_widget = TasksWidget(self.dbcon, self) - body = QtWidgets.QSplitter() + + # Body + body = QtWidgets.QSplitter(self) body.setContentsMargins(0, 0, 0, 0) body.setSizePolicy( QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding ) body.setOrientation(QtCore.Qt.Horizontal) - body.addWidget(assets_proxy_widgets) + body.addWidget(assets_widget) body.addWidget(tasks_widget) body.setStretchFactor(0, 100) body.setStretchFactor(1, 65) @@ -171,22 +166,21 @@ class AssetsPanel(QtWidgets.QWidget): # main layout layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(0) - layout.addWidget(project_bar_widget) + layout.addLayout(project_bar_layout) layout.addWidget(body) # signals - project_handler.project_changed.connect(self.on_project_changed) - assets_widget.selection_changed.connect(self.on_asset_changed) - assets_widget.refreshed.connect(self.on_asset_changed) - tasks_widget.task_changed.connect(self.on_task_change) + project_handler.project_changed.connect(self._on_project_changed) + assets_widget.selection_changed.connect(self._on_asset_changed) + assets_widget.refreshed.connect(self._on_asset_changed) + tasks_widget.task_changed.connect(self._on_task_change) btn_back.clicked.connect(self.back_clicked) self.project_handler = project_handler self.project_bar = project_bar self.assets_widget = assets_widget - self.tasks_widget = tasks_widget + self._tasks_widget = tasks_widget self._btn_back = btn_back def showEvent(self, event): @@ -197,12 +191,16 @@ class AssetsPanel(QtWidgets.QWidget): btn_size = self.project_bar.height() self._btn_back.setFixedSize(QtCore.QSize(btn_size, btn_size)) - def on_project_changed(self): + def select_task_name(self, task_name): + self._on_asset_changed() + self._tasks_widget.select_task_name(task_name) + + def _on_project_changed(self): self.session_changed.emit() self.assets_widget.refresh() - def on_asset_changed(self): + def _on_asset_changed(self): """Callback on asset selection changed This updates the task view. @@ -237,16 +235,17 @@ class AssetsPanel(QtWidgets.QWidget): asset_id = None if asset_doc: asset_id = asset_doc["_id"] - self.tasks_widget.set_asset(asset_id) + self._tasks_widget.set_asset_id(asset_id) - def on_task_change(self): - task_name = self.tasks_widget.get_current_task() + def _on_task_change(self): + task_name = self._tasks_widget.get_selected_task_name() self.dbcon.Session["AVALON_TASK"] = task_name self.session_changed.emit() class LauncherWindow(QtWidgets.QDialog): """Launcher interface""" + message_timeout = 5000 def __init__(self, parent=None): super(LauncherWindow, self).__init__(parent) @@ -283,20 +282,17 @@ class LauncherWindow(QtWidgets.QDialog): actions_bar = ActionBar(project_handler, self.dbcon, self) # statusbar - statusbar = QtWidgets.QWidget() - layout = QtWidgets.QHBoxLayout(statusbar) + message_label = QtWidgets.QLabel(self) - message_label = QtWidgets.QLabel() - message_label.setFixedHeight(15) - - action_history = ActionHistory() + action_history = ActionHistory(self) action_history.setStatusTip("Show Action History") - layout.addWidget(message_label) - layout.addWidget(action_history) + status_layout = QtWidgets.QHBoxLayout() + status_layout.addWidget(message_label, 1) + status_layout.addWidget(action_history, 0) # Vertically split Pages and Actions - body = QtWidgets.QSplitter() + body = QtWidgets.QSplitter(self) body.setContentsMargins(0, 0, 0, 0) body.setSizePolicy( QtWidgets.QSizePolicy.Expanding, @@ -314,19 +310,13 @@ class LauncherWindow(QtWidgets.QDialog): layout = QtWidgets.QVBoxLayout(self) layout.addWidget(body) - layout.addWidget(statusbar) - layout.setSpacing(0) - layout.setContentsMargins(0, 0, 0, 0) + layout.addLayout(status_layout) - self.project_handler = project_handler + message_timer = QtCore.QTimer() + message_timer.setInterval(self.message_timeout) + message_timer.setSingleShot(True) - self.message_label = message_label - self.project_panel = project_panel - self.asset_panel = asset_panel - self.actions_bar = actions_bar - self.action_history = action_history - self.page_slider = page_slider - self._page = 0 + message_timer.timeout.connect(self._on_message_timeout) # signals actions_bar.action_clicked.connect(self.on_action_clicked) @@ -338,6 +328,19 @@ class LauncherWindow(QtWidgets.QDialog): self.resize(520, 740) + self._page = 0 + + self._message_timer = message_timer + + self.project_handler = project_handler + + self._message_label = message_label + self.project_panel = project_panel + self.asset_panel = asset_panel + self.actions_bar = actions_bar + self.action_history = action_history + self.page_slider = page_slider + def showEvent(self, event): self.project_handler.set_active(True) self.project_handler.start_timer(True) @@ -363,9 +366,12 @@ class LauncherWindow(QtWidgets.QDialog): self._page = page self.page_slider.slide_view(page, direction=direction) + def _on_message_timeout(self): + self._message_label.setText("") + def echo(self, message): - self.message_label.setText(str(message)) - QtCore.QTimer.singleShot(5000, lambda: self.message_label.setText("")) + self._message_label.setText(str(message)) + self._message_timer.start() self.log.debug(message) def on_session_changed(self): @@ -448,5 +454,4 @@ class LauncherWindow(QtWidgets.QDialog): if task_name: # requires a forced refresh first - self.asset_panel.on_asset_changed() - self.asset_panel.tasks_widget.select_task(task_name) + self.asset_panel.select_task_name(task_name) diff --git a/openpype/tools/libraryloader/app.py b/openpype/tools/libraryloader/app.py index 1456720540..710e25bd76 100644 --- a/openpype/tools/libraryloader/app.py +++ b/openpype/tools/libraryloader/app.py @@ -407,7 +407,8 @@ class LibraryLoaderWindow(QtWidgets.QDialog): self.data["state"]["assetIds"] = asset_ids # reset repre list - self._repres_widget.set_version_ids([]) + if self._repres_widget: + self._repres_widget.set_version_ids([]) def _subsetschanged(self): asset_ids = self.data["state"]["assetIds"] @@ -497,7 +498,8 @@ class LibraryLoaderWindow(QtWidgets.QDialog): self._thumbnail_widget.set_thumbnail(thumbnail_docs) version_ids = [doc["_id"] for doc in version_docs or []] - self._repres_widget.set_version_ids(version_ids) + if self._repres_widget: + self._repres_widget.set_version_ids(version_ids) def _set_context(self, context, refresh=True): """Set the selection in the interface using a context. diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index 6e9c7bf220..96a52fce97 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -15,6 +15,12 @@ from openpype.tools.utils.models import TreeModel, Item from openpype.tools.utils import lib from openpype.modules import ModulesManager +from openpype.tools.utils.constants import ( + LOCAL_PROVIDER_ROLE, + REMOTE_PROVIDER_ROLE, + LOCAL_AVAILABILITY_ROLE, + REMOTE_AVAILABILITY_ROLE +) def is_filtering_recursible(): @@ -237,9 +243,9 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): # update availability on active site when version changes if self.sync_server.enabled and version: - site = self.active_site query = self._repre_per_version_pipeline([version["_id"]], - site) + self.active_site, + self.remote_site) docs = list(self.dbcon.aggregate(query)) if docs: repre = docs.pop() @@ -333,7 +339,6 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): repre_info = version_data.get("repre_info") if repre_info: item["repre_info"] = repre_info - item["repre_icon"] = version_data.get("repre_icon") def _fetch(self): asset_docs = self.dbcon.find( @@ -445,14 +450,16 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): for _subset_id, doc in last_versions_by_subset_id.items(): version_ids.add(doc["_id"]) - site = self.active_site - query = self._repre_per_version_pipeline(list(version_ids), site) + query = self._repre_per_version_pipeline(list(version_ids), + self.active_site, + self.remote_site) repre_info = {} for doc in self.dbcon.aggregate(query): if self._doc_fetching_stop: return - doc["provider"] = self.active_provider + doc["active_provider"] = self.active_provider + doc["remote_provider"] = self.remote_provider repre_info[doc["_id"]] = doc self._doc_payload["repre_info_by_version_id"] = repre_info @@ -666,8 +673,8 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): if not index.isValid(): return + item = index.internalPointer() if role == self.SortDescendingRole: - item = index.internalPointer() if item.get("isGroup"): # Ensure groups be on top when sorting by descending order prefix = "2" @@ -683,7 +690,6 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): return prefix + order if role == self.SortAscendingRole: - item = index.internalPointer() if item.get("isGroup"): # Ensure groups be on top when sorting by ascending order prefix = "0" @@ -701,14 +707,12 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): if role == QtCore.Qt.DisplayRole: if index.column() == self.columns_index["family"]: # Show familyLabel instead of family - item = index.internalPointer() return item.get("familyLabel", None) elif role == QtCore.Qt.DecorationRole: # Add icon to subset column if index.column() == self.columns_index["subset"]: - item = index.internalPointer() if item.get("isGroup") or item.get("isMerged"): return item["icon"] else: @@ -716,20 +720,32 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): # Add icon to family column if index.column() == self.columns_index["family"]: - item = index.internalPointer() return item.get("familyIcon", None) - if index.column() == self.columns_index.get("repre_info"): - item = index.internalPointer() - return item.get("repre_icon", None) - elif role == QtCore.Qt.ForegroundRole: - item = index.internalPointer() version_doc = item.get("version_document") if version_doc and version_doc.get("type") == "hero_version": if not version_doc["is_from_latest"]: return self.not_last_hero_brush + elif role == LOCAL_AVAILABILITY_ROLE: + if not item.get("isGroup"): + return item.get("repre_info_local") + else: + return None + + elif role == REMOTE_AVAILABILITY_ROLE: + if not item.get("isGroup"): + return item.get("repre_info_remote") + else: + return None + + elif role == LOCAL_PROVIDER_ROLE: + return self.active_provider + + elif role == REMOTE_PROVIDER_ROLE: + return self.remote_provider + return super(SubsetsModel, self).data(index, role) def flags(self, index): @@ -759,19 +775,25 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): return data def _get_repre_dict(self, repre_info): - """Returns icon and str representation of availability""" + """Returns str representation of availability""" data = {} if repre_info: repres_str = "{}/{}".format( - int(math.floor(float(repre_info['avail_repre']))), + int(math.floor(float(repre_info['avail_repre_local']))), int(math.floor(float(repre_info['repre_count'])))) - data["repre_info"] = repres_str - data["repre_icon"] = self.repre_icons.get(self.active_provider) + data["repre_info_local"] = repres_str + + repres_str = "{}/{}".format( + int(math.floor(float(repre_info['avail_repre_remote']))), + int(math.floor(float(repre_info['repre_count'])))) + + data["repre_info_remote"] = repres_str return data - def _repre_per_version_pipeline(self, version_ids, site): + def _repre_per_version_pipeline(self, version_ids, + active_site, remote_site): query = [ {"$match": {"parent": {"$in": version_ids}, "type": "representation", @@ -779,35 +801,70 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): {"$unwind": "$files"}, {'$addFields': { 'order_local': { - '$filter': {'input': '$files.sites', 'as': 'p', - 'cond': {'$eq': ['$$p.name', site]} - }} + '$filter': { + 'input': '$files.sites', 'as': 'p', + 'cond': {'$eq': ['$$p.name', active_site]} + } + } + }}, + {'$addFields': { + 'order_remote': { + '$filter': { + 'input': '$files.sites', 'as': 'p', + 'cond': {'$eq': ['$$p.name', remote_site]} + } + } }}, {'$addFields': { 'progress_local': {"$arrayElemAt": [{ - '$cond': [{'$size': "$order_local.progress"}, - "$order_local.progress", - # if exists created_dt count is as available - {'$cond': [ - {'$size': "$order_local.created_dt"}, - [1], - [0] - ]} - ]}, 0]} + '$cond': [ + {'$size': "$order_local.progress"}, + "$order_local.progress", + # if exists created_dt count is as available + {'$cond': [ + {'$size': "$order_local.created_dt"}, + [1], + [0] + ]} + ]}, + 0 + ]} + }}, + {'$addFields': { + 'progress_remote': {"$arrayElemAt": [{ + '$cond': [ + {'$size': "$order_remote.progress"}, + "$order_remote.progress", + # if exists created_dt count is as available + {'$cond': [ + {'$size': "$order_remote.created_dt"}, + [1], + [0] + ]} + ]}, + 0 + ]} }}, {'$group': { # first group by repre '_id': '$_id', 'parent': {'$first': '$parent'}, - 'files_count': {'$sum': 1}, - 'files_avail': {'$sum': "$progress_local"}, - 'avail_ratio': {'$first': { - '$divide': [{'$sum': "$progress_local"}, {'$sum': 1}]}} + 'avail_ratio_local': { + '$first': { + '$divide': [{'$sum': "$progress_local"}, {'$sum': 1}] + } + }, + 'avail_ratio_remote': { + '$first': { + '$divide': [{'$sum': "$progress_remote"}, {'$sum': 1}] + } + } }}, {'$group': { # second group by parent, eg version_id '_id': '$parent', 'repre_count': {'$sum': 1}, # total representations # fully available representation for site - 'avail_repre': {'$sum': "$avail_ratio"} + 'avail_repre_local': {'$sum': "$avail_ratio_local"}, + 'avail_repre_remote': {'$sum': "$avail_ratio_remote"}, }}, ] return query diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index 4c075382ac..08b58eebbe 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -31,6 +31,13 @@ from .model import ( ) from . import lib +from openpype.tools.utils.constants import ( + LOCAL_PROVIDER_ROLE, + REMOTE_PROVIDER_ROLE, + LOCAL_AVAILABILITY_ROLE, + REMOTE_AVAILABILITY_ROLE +) + class OverlayFrame(QtWidgets.QFrame): def __init__(self, label, parent): @@ -197,6 +204,10 @@ class SubsetWidget(QtWidgets.QWidget): column = model.Columns.index("time") view.setItemDelegateForColumn(column, time_delegate) + avail_delegate = AvailabilityDelegate(self.dbcon, view) + column = model.Columns.index("repre_info") + view.setItemDelegateForColumn(column, avail_delegate) + layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.addLayout(top_bar_layout) @@ -1578,3 +1589,54 @@ def _load_subsets_by_loader(loader, subset_contexts, options, )) return error_info + + +class AvailabilityDelegate(QtWidgets.QStyledItemDelegate): + """ + Prints icons and downloaded representation ration for both sides. + """ + + def __init__(self, dbcon, parent=None): + super(AvailabilityDelegate, self).__init__(parent) + self.icons = tools_lib.get_repre_icons() + + def paint(self, painter, option, index): + super(AvailabilityDelegate, self).paint(painter, option, index) + option = QtWidgets.QStyleOptionViewItem(option) + option.showDecorationSelected = True + + provider_active = index.data(LOCAL_PROVIDER_ROLE) + provider_remote = index.data(REMOTE_PROVIDER_ROLE) + + availability_active = index.data(LOCAL_AVAILABILITY_ROLE) + availability_remote = index.data(REMOTE_AVAILABILITY_ROLE) + + if not availability_active or not availability_remote: # group lines + return + + idx = 0 + height = width = 24 + for value, provider in [(availability_active, provider_active), + (availability_remote, provider_remote)]: + icon = self.icons.get(provider) + if not icon: + continue + + pixmap = icon.pixmap(icon.actualSize(QtCore.QSize(height, width))) + padding = 10 + (70 * idx) + point = QtCore.QPoint(option.rect.x() + padding, + option.rect.y() + + (option.rect.height() - pixmap.height()) / 2) + painter.drawPixmap(point, pixmap) + + text_rect = option.rect.translated(padding + width + 10, 0) + painter.drawText( + text_rect, + option.displayAlignment, + value + ) + + idx += 1 + + def displayText(self, value, locale): + pass diff --git a/openpype/tools/pyblish_pype/control.py b/openpype/tools/pyblish_pype/control.py index 1fa3ee657b..2a9e67097e 100644 --- a/openpype/tools/pyblish_pype/control.py +++ b/openpype/tools/pyblish_pype/control.py @@ -190,7 +190,9 @@ class Controller(QtCore.QObject): plugins = pyblish.api.discover() - targets = pyblish.logic.registered_targets() or ["default"] + targets = set(pyblish.logic.registered_targets()) + targets.add("default") + targets = list(targets) plugins_by_targets = pyblish.logic.plugins_by_targets(plugins, targets) _plugins = [] diff --git a/openpype/tools/sceneinventory/__init__.py b/openpype/tools/sceneinventory/__init__.py new file mode 100644 index 0000000000..410b52e5fe --- /dev/null +++ b/openpype/tools/sceneinventory/__init__.py @@ -0,0 +1,9 @@ +from .window import ( + show, + SceneInventoryWindow +) + +__all__ = ( + "show", + "SceneInventoryWindow" +) diff --git a/openpype/tools/sceneinventory/lib.py b/openpype/tools/sceneinventory/lib.py new file mode 100644 index 0000000000..7653e1da89 --- /dev/null +++ b/openpype/tools/sceneinventory/lib.py @@ -0,0 +1,82 @@ +import os +from openpype_modules import sync_server + +from Qt import QtGui + + +def walk_hierarchy(node): + """Recursively yield group node.""" + for child in node.children(): + if child.get("isGroupNode"): + yield child + + for _child in walk_hierarchy(child): + yield _child + + +def get_site_icons(): + resource_path = os.path.join( + os.path.dirname(sync_server.sync_server_module.__file__), + "providers", + "resources" + ) + icons = {} + # TODO get from sync module + for provider in ["studio", "local_drive", "gdrive"]: + pix_url = "{}/{}.png".format(resource_path, provider) + icons[provider] = QtGui.QIcon(pix_url) + + return icons + + +def get_progress_for_repre(repre_doc, active_site, remote_site): + """ + Calculates average progress for representation. + + If site has created_dt >> fully available >> progress == 1 + + Could be calculated in aggregate if it would be too slow + Args: + repre_doc(dict): representation dict + Returns: + (dict) with active and remote sites progress + {'studio': 1.0, 'gdrive': -1} - gdrive site is not present + -1 is used to highlight the site should be added + {'studio': 1.0, 'gdrive': 0.0} - gdrive site is present, not + uploaded yet + """ + progress = {active_site: -1, remote_site: -1} + if not repre_doc: + return progress + + files = {active_site: 0, remote_site: 0} + doc_files = repre_doc.get("files") or [] + for doc_file in doc_files: + if not isinstance(doc_file, dict): + continue + + sites = doc_file.get("sites") or [] + for site in sites: + if ( + # Pype 2 compatibility + not isinstance(site, dict) + # Check if site name is one of progress sites + or site["name"] not in progress + ): + continue + + files[site["name"]] += 1 + norm_progress = max(progress[site["name"]], 0) + if site.get("created_dt"): + progress[site["name"]] = norm_progress + 1 + elif site.get("progress"): + progress[site["name"]] = norm_progress + site["progress"] + else: # site exists, might be failed, do not add again + progress[site["name"]] = 0 + + # for example 13 fully avail. files out of 26 >> 13/26 = 0.5 + avg_progress = { + active_site: progress[active_site] / max(files[active_site], 1), + remote_site: progress[remote_site] / max(files[remote_site], 1) + } + return avg_progress diff --git a/openpype/tools/sceneinventory/model.py b/openpype/tools/sceneinventory/model.py new file mode 100644 index 0000000000..d2b7f8b70f --- /dev/null +++ b/openpype/tools/sceneinventory/model.py @@ -0,0 +1,576 @@ +import re +import logging + +from collections import defaultdict + +from Qt import QtCore, QtGui +from avalon import api, io, style, schema +from avalon.vendor import qtawesome + +from avalon.lib import HeroVersionType +from avalon.tools.models import TreeModel, Item + +from .lib import ( + get_site_icons, + walk_hierarchy, + get_progress_for_repre +) + +from openpype.modules import ModulesManager + + +class InventoryModel(TreeModel): + """The model for the inventory""" + + Columns = ["Name", "version", "count", "family", "loader", "objectName"] + + OUTDATED_COLOR = QtGui.QColor(235, 30, 30) + CHILD_OUTDATED_COLOR = QtGui.QColor(200, 160, 30) + GRAYOUT_COLOR = QtGui.QColor(160, 160, 160) + + UniqueRole = QtCore.Qt.UserRole + 2 # unique label role + + def __init__(self, family_config_cache, parent=None): + super(InventoryModel, self).__init__(parent) + self.log = logging.getLogger(self.__class__.__name__) + + self.family_config_cache = family_config_cache + + self._hierarchy_view = False + + manager = ModulesManager() + sync_server = manager.modules_by_name["sync_server"] + self.sync_enabled = sync_server.enabled + self._site_icons = {} + self.active_site = self.remote_site = None + self.active_provider = self.remote_provider = None + + if not self.sync_enabled: + return + + project_name = io.Session["AVALON_PROJECT"] + active_site = sync_server.get_active_site(project_name) + remote_site = sync_server.get_remote_site(project_name) + + active_provider = "studio" + remote_provider = "studio" + if active_site != "studio": + # sanitized for icon + active_provider = sync_server.get_provider_for_site( + project_name, active_site + ) + + if remote_site != "studio": + remote_provider = sync_server.get_provider_for_site( + project_name, remote_site + ) + + # self.sync_server = sync_server + self.active_site = active_site + self.active_provider = active_provider + self.remote_site = remote_site + self.remote_provider = remote_provider + self._site_icons = get_site_icons() + if "active_site" not in self.Columns: + self.Columns.append("active_site") + if "remote_site" not in self.Columns: + self.Columns.append("remote_site") + + def outdated(self, item): + value = item.get("version") + if isinstance(value, HeroVersionType): + return False + + if item.get("version") == item.get("highest_version"): + return False + return True + + def data(self, index, role): + if not index.isValid(): + return + + item = index.internalPointer() + + if role == QtCore.Qt.FontRole: + # Make top-level entries bold + if item.get("isGroupNode") or item.get("isNotSet"): # group-item + font = QtGui.QFont() + font.setBold(True) + return font + + if role == QtCore.Qt.ForegroundRole: + # Set the text color to the OUTDATED_COLOR when the + # collected version is not the same as the highest version + key = self.Columns[index.column()] + if key == "version": # version + if item.get("isGroupNode"): # group-item + if self.outdated(item): + return self.OUTDATED_COLOR + + if self._hierarchy_view: + # If current group is not outdated, check if any + # outdated children. + for _node in walk_hierarchy(item): + if self.outdated(_node): + return self.CHILD_OUTDATED_COLOR + else: + + if self._hierarchy_view: + # Although this is not a group item, we still need + # to distinguish which one contain outdated child. + for _node in walk_hierarchy(item): + if self.outdated(_node): + return self.CHILD_OUTDATED_COLOR.darker(150) + + return self.GRAYOUT_COLOR + + if key == "Name" and not item.get("isGroupNode"): + return self.GRAYOUT_COLOR + + # Add icons + if role == QtCore.Qt.DecorationRole: + if index.column() == 0: + # Override color + color = item.get("color", style.colors.default) + if item.get("isGroupNode"): # group-item + return qtawesome.icon("fa.folder", color=color) + if item.get("isNotSet"): + return qtawesome.icon("fa.exclamation-circle", color=color) + + return qtawesome.icon("fa.file-o", color=color) + + if index.column() == 3: + # Family icon + return item.get("familyIcon", None) + + if item.get("isGroupNode"): + column_name = self.Columns[index.column()] + if column_name == "active_site": + provider = item.get("active_site_provider") + return self._site_icons.get(provider) + + if column_name == "remote_site": + provider = item.get("remote_site_provider") + return self._site_icons.get(provider) + + if role == QtCore.Qt.DisplayRole and item.get("isGroupNode"): + column_name = self.Columns[index.column()] + progress = None + if column_name == 'active_site': + progress = item.get("active_site_progress", 0) + elif column_name == 'remote_site': + progress = item.get("remote_site_progress", 0) + if progress is not None: + return "{}%".format(max(progress, 0) * 100) + + if role == self.UniqueRole: + return item["representation"] + item.get("objectName", "") + + return super(InventoryModel, self).data(index, role) + + def set_hierarchy_view(self, state): + """Set whether to display subsets in hierarchy view.""" + state = bool(state) + + if state != self._hierarchy_view: + self._hierarchy_view = state + + def refresh(self, selected=None, items=None): + """Refresh the model""" + + host = api.registered_host() + if not items: # for debugging or testing, injecting items from outside + items = host.ls() + + self.clear() + + if self._hierarchy_view and selected: + + if not hasattr(host.pipeline, "update_hierarchy"): + # If host doesn't support hierarchical containers, then + # cherry-pick only. + self.add_items((item for item in items + if item["objectName"] in selected)) + + # Update hierarchy info for all containers + items_by_name = {item["objectName"]: item + for item in host.pipeline.update_hierarchy(items)} + + selected_items = set() + + def walk_children(names): + """Select containers and extend to chlid containers""" + for name in [n for n in names if n not in selected_items]: + selected_items.add(name) + item = items_by_name[name] + yield item + + for child in walk_children(item["children"]): + yield child + + items = list(walk_children(selected)) # Cherry-picked and extended + + # Cut unselected upstream containers + for item in items: + if not item.get("parent") in selected_items: + # Parent not in selection, this is root item. + item["parent"] = None + + parents = [self._root_item] + + # The length of `items` array is the maximum depth that a + # hierarchy could be. + # Take this as an easiest way to prevent looping forever. + maximum_loop = len(items) + count = 0 + while items: + if count > maximum_loop: + self.log.warning("Maximum loop count reached, possible " + "missing parent node.") + break + + _parents = list() + for parent in parents: + _unparented = list() + + def _children(): + """Child item provider""" + for item in items: + if item.get("parent") == parent.get("objectName"): + # (NOTE) + # Since `self._root_node` has no "objectName" + # entry, it will be paired with root item if + # the value of key "parent" is None, or not + # having the key. + yield item + else: + # Not current parent's child, try next + _unparented.append(item) + + self.add_items(_children(), parent) + + items[:] = _unparented + + # Parents of next level + for group_node in parent.children(): + _parents += group_node.children() + + parents[:] = _parents + count += 1 + + else: + self.add_items(items) + + def add_items(self, items, parent=None): + """Add the items to the model. + + The items should be formatted similar to `api.ls()` returns, an item + is then represented as: + {"filename_v001.ma": [full/filename/of/loaded/filename_v001.ma, + full/filename/of/loaded/filename_v001.ma], + "nodetype" : "reference", + "node": "referenceNode1"} + + Note: When performing an additional call to `add_items` it will *not* + group the new items with previously existing item groups of the + same type. + + Args: + items (generator): the items to be processed as returned by `ls()` + parent (Item, optional): Set this item as parent for the added + items when provided. Defaults to the root of the model. + + Returns: + node.Item: root node which has children added based on the data + """ + + self.beginResetModel() + + # Group by representation + grouped = defaultdict(lambda: {"items": list()}) + for item in items: + grouped[item["representation"]]["items"].append(item) + + # Add to model + not_found = defaultdict(list) + not_found_ids = [] + for repre_id, group_dict in sorted(grouped.items()): + group_items = group_dict["items"] + # Get parenthood per group + representation = io.find_one({"_id": io.ObjectId(repre_id)}) + if not representation: + not_found["representation"].append(group_items) + not_found_ids.append(repre_id) + continue + + version = io.find_one({"_id": representation["parent"]}) + if not version: + not_found["version"].append(group_items) + not_found_ids.append(repre_id) + continue + + elif version["type"] == "hero_version": + _version = io.find_one({ + "_id": version["version_id"] + }) + version["name"] = HeroVersionType(_version["name"]) + version["data"] = _version["data"] + + subset = io.find_one({"_id": version["parent"]}) + if not subset: + not_found["subset"].append(group_items) + not_found_ids.append(repre_id) + continue + + asset = io.find_one({"_id": subset["parent"]}) + if not asset: + not_found["asset"].append(group_items) + not_found_ids.append(repre_id) + continue + + grouped[repre_id].update({ + "representation": representation, + "version": version, + "subset": subset, + "asset": asset + }) + + for id in not_found_ids: + grouped.pop(id) + + for where, group_items in not_found.items(): + # create the group header + group_node = Item() + name = "< NOT FOUND - {} >".format(where) + group_node["Name"] = name + group_node["representation"] = name + group_node["count"] = len(group_items) + group_node["isGroupNode"] = False + group_node["isNotSet"] = True + + self.add_child(group_node, parent=parent) + + for _group_items in group_items: + item_node = Item() + item_node["Name"] = ", ".join( + [item["objectName"] for item in _group_items] + ) + self.add_child(item_node, parent=group_node) + + for repre_id, group_dict in sorted(grouped.items()): + group_items = group_dict["items"] + representation = grouped[repre_id]["representation"] + version = grouped[repre_id]["version"] + subset = grouped[repre_id]["subset"] + asset = grouped[repre_id]["asset"] + + # Get the primary family + no_family = "" + maj_version, _ = schema.get_schema_version(subset["schema"]) + if maj_version < 3: + prim_family = version["data"].get("family") + if not prim_family: + families = version["data"].get("families") + prim_family = families[0] if families else no_family + else: + families = subset["data"].get("families") or [] + prim_family = families[0] if families else no_family + + # Get the label and icon for the family if in configuration + family_config = self.family_config_cache.family_config(prim_family) + family = family_config.get("label", prim_family) + family_icon = family_config.get("icon", None) + + # Store the highest available version so the model can know + # whether current version is currently up-to-date. + highest_version = io.find_one({ + "type": "version", + "parent": version["parent"] + }, sort=[("name", -1)]) + + # create the group header + group_node = Item() + group_node["Name"] = "%s_%s: (%s)" % (asset["name"], + subset["name"], + representation["name"]) + group_node["representation"] = repre_id + group_node["version"] = version["name"] + group_node["highest_version"] = highest_version["name"] + group_node["family"] = family + group_node["familyIcon"] = family_icon + group_node["count"] = len(group_items) + group_node["isGroupNode"] = True + + if self.sync_enabled: + progress = get_progress_for_repre( + representation, self.active_site, self.remote_site + ) + group_node["active_site"] = self.active_site + group_node["active_site_provider"] = self.active_provider + group_node["remote_site"] = self.remote_site + group_node["remote_site_provider"] = self.remote_provider + group_node["active_site_progress"] = progress[self.active_site] + group_node["remote_site_progress"] = progress[self.remote_site] + + self.add_child(group_node, parent=parent) + + for item in group_items: + item_node = Item() + item_node.update(item) + + # store the current version on the item + item_node["version"] = version["name"] + + # Remapping namespace to item name. + # Noted that the name key is capital "N", by doing this, we + # can view namespace in GUI without changing container data. + item_node["Name"] = item["namespace"] + + self.add_child(item_node, parent=group_node) + + self.endResetModel() + + return self._root_item + + +class FilterProxyModel(QtCore.QSortFilterProxyModel): + """Filter model to where key column's value is in the filtered tags""" + + def __init__(self, *args, **kwargs): + super(FilterProxyModel, self).__init__(*args, **kwargs) + self._filter_outdated = False + self._hierarchy_view = False + + def filterAcceptsRow(self, row, parent): + model = self.sourceModel() + source_index = model.index(row, self.filterKeyColumn(), parent) + + # Always allow bottom entries (individual containers), since their + # parent group hidden if it wouldn't have been validated. + rows = model.rowCount(source_index) + if not rows: + return True + + # Filter by regex + if not self.filterRegExp().isEmpty(): + pattern = re.escape(self.filterRegExp().pattern()) + + if not self._matches(row, parent, pattern): + return False + + if self._filter_outdated: + # When filtering to outdated we filter the up to date entries + # thus we "allow" them when they are outdated + if not self._is_outdated(row, parent): + return False + + return True + + def set_filter_outdated(self, state): + """Set whether to show the outdated entries only.""" + state = bool(state) + + if state != self._filter_outdated: + self._filter_outdated = bool(state) + self.invalidateFilter() + + def set_hierarchy_view(self, state): + state = bool(state) + + if state != self._hierarchy_view: + self._hierarchy_view = state + + def _is_outdated(self, row, parent): + """Return whether row is outdated. + + A row is considered outdated if it has "version" and "highest_version" + data and in the internal data structure, and they are not of an + equal value. + + """ + def outdated(node): + version = node.get("version", None) + highest = node.get("highest_version", None) + + # Always allow indices that have no version data at all + if version is None and highest is None: + return True + + # If either a version or highest is present but not the other + # consider the item invalid. + if not self._hierarchy_view: + # Skip this check if in hierarchy view, or the child item + # node will be hidden even it's actually outdated. + if version is None or highest is None: + return False + return version != highest + + index = self.sourceModel().index(row, self.filterKeyColumn(), parent) + + # The scene contents are grouped by "representation", e.g. the same + # "representation" loaded twice is grouped under the same header. + # Since the version check filters these parent groups we skip that + # check for the individual children. + has_parent = index.parent().isValid() + if has_parent and not self._hierarchy_view: + return True + + # Filter to those that have the different version numbers + node = index.internalPointer() + if outdated(node): + return True + + if self._hierarchy_view: + for _node in walk_hierarchy(node): + if outdated(_node): + return True + + return False + + def _matches(self, row, parent, pattern): + """Return whether row matches regex pattern. + + Args: + row (int): row number in model + parent (QtCore.QModelIndex): parent index + pattern (regex.pattern): pattern to check for in key + + Returns: + bool + + """ + model = self.sourceModel() + column = self.filterKeyColumn() + role = self.filterRole() + + def matches(row, parent, pattern): + index = model.index(row, column, parent) + key = model.data(index, role) + if re.search(pattern, key, re.IGNORECASE): + return True + + if matches(row, parent, pattern): + return True + + # Also allow if any of the children matches + source_index = model.index(row, column, parent) + rows = model.rowCount(source_index) + + if any( + matches(idx, source_index, pattern) + for idx in range(rows) + ): + return True + + if not self._hierarchy_view: + return False + + for idx in range(rows): + child_index = model.index(idx, column, source_index) + child_rows = model.rowCount(child_index) + return any( + self._matches(child_idx, child_index, pattern) + for child_idx in range(child_rows) + ) + + return True diff --git a/openpype/tools/sceneinventory/switch_dialog.py b/openpype/tools/sceneinventory/switch_dialog.py new file mode 100644 index 0000000000..ecad8eac0a --- /dev/null +++ b/openpype/tools/sceneinventory/switch_dialog.py @@ -0,0 +1,993 @@ +import collections +import logging +from Qt import QtWidgets, QtCore + +from avalon import io, api +from avalon.vendor import qtawesome + +from .widgets import SearchComboBox + +log = logging.getLogger("SwitchAssetDialog") + + +class ValidationState: + def __init__(self): + self.asset_ok = True + self.subset_ok = True + self.repre_ok = True + + @property + def all_ok(self): + return ( + self.asset_ok + and self.subset_ok + and self.repre_ok + ) + + +class SwitchAssetDialog(QtWidgets.QDialog): + """Widget to support asset switching""" + + MIN_WIDTH = 550 + + switched = QtCore.Signal() + + def __init__(self, parent=None, items=None): + super(SwitchAssetDialog, self).__init__(parent) + + self.setWindowTitle("Switch selected items ...") + + # Force and keep focus dialog + self.setModal(True) + + assets_combox = SearchComboBox(self) + subsets_combox = SearchComboBox(self) + repres_combobox = SearchComboBox(self) + + assets_combox.set_placeholder("") + subsets_combox.set_placeholder("") + repres_combobox.set_placeholder("") + + asset_label = QtWidgets.QLabel(self) + subset_label = QtWidgets.QLabel(self) + repre_label = QtWidgets.QLabel(self) + + current_asset_btn = QtWidgets.QPushButton("Use current asset") + + accept_icon = qtawesome.icon("fa.check", color="white") + accept_btn = QtWidgets.QPushButton(self) + accept_btn.setIcon(accept_icon) + + main_layout = QtWidgets.QGridLayout(self) + # Asset column + main_layout.addWidget(current_asset_btn, 0, 0) + main_layout.addWidget(assets_combox, 1, 0) + main_layout.addWidget(asset_label, 2, 0) + # Subset column + main_layout.addWidget(subsets_combox, 1, 1) + main_layout.addWidget(subset_label, 2, 1) + # Representation column + main_layout.addWidget(repres_combobox, 1, 2) + main_layout.addWidget(repre_label, 2, 2) + # Btn column + main_layout.addWidget(accept_btn, 1, 3) + main_layout.setColumnStretch(0, 1) + main_layout.setColumnStretch(1, 1) + main_layout.setColumnStretch(2, 1) + main_layout.setColumnStretch(3, 0) + + assets_combox.currentIndexChanged.connect( + self._combobox_value_changed + ) + subsets_combox.currentIndexChanged.connect( + self._combobox_value_changed + ) + repres_combobox.currentIndexChanged.connect( + self._combobox_value_changed + ) + accept_btn.clicked.connect(self._on_accept) + current_asset_btn.clicked.connect(self._on_current_asset) + + self._current_asset_btn = current_asset_btn + + self._assets_box = assets_combox + self._subsets_box = subsets_combox + self._representations_box = repres_combobox + + self._asset_label = asset_label + self._subset_label = subset_label + self._repre_label = repre_label + + self._accept_btn = accept_btn + + self._init_asset_name = None + self._init_subset_name = None + self._init_repre_name = None + + self._fill_check = False + + self._items = items + self._prepare_content_data() + self.refresh(True) + + self.setMinimumWidth(self.MIN_WIDTH) + + # Set default focus to accept button so you don't directly type in + # first asset field, this also allows to see the placeholder value. + accept_btn.setFocus() + + def _prepare_content_data(self): + repre_ids = [ + io.ObjectId(item["representation"]) + for item in self._items + ] + repres = list(io.find({ + "type": {"$in": ["representation", "archived_representation"]}, + "_id": {"$in": repre_ids} + })) + repres_by_id = {repre["_id"]: repre for repre in repres} + + # stash context values, works only for single representation + if len(repres) == 1: + self._init_asset_name = repres[0]["context"]["asset"] + self._init_subset_name = repres[0]["context"]["subset"] + self._init_repre_name = repres[0]["context"]["representation"] + + content_repres = {} + archived_repres = [] + missing_repres = [] + version_ids = [] + for repre_id in repre_ids: + if repre_id not in repres_by_id: + missing_repres.append(repre_id) + elif repres_by_id[repre_id]["type"] == "archived_representation": + repre = repres_by_id[repre_id] + archived_repres.append(repre) + version_ids.append(repre["parent"]) + else: + repre = repres_by_id[repre_id] + content_repres[repre_id] = repres_by_id[repre_id] + version_ids.append(repre["parent"]) + + versions = io.find({ + "type": {"$in": ["version", "hero_version"]}, + "_id": {"$in": list(set(version_ids))} + }) + content_versions = {} + hero_version_ids = set() + for version in versions: + content_versions[version["_id"]] = version + if version["type"] == "hero_version": + hero_version_ids.add(version["_id"]) + + missing_versions = [] + subset_ids = [] + for version_id in version_ids: + if version_id not in content_versions: + missing_versions.append(version_id) + else: + subset_ids.append(content_versions[version_id]["parent"]) + + subsets = io.find({ + "type": {"$in": ["subset", "archived_subset"]}, + "_id": {"$in": subset_ids} + }) + subsets_by_id = {sub["_id"]: sub for sub in subsets} + + asset_ids = [] + archived_subsets = [] + missing_subsets = [] + content_subsets = {} + for subset_id in subset_ids: + if subset_id not in subsets_by_id: + missing_subsets.append(subset_id) + elif subsets_by_id[subset_id]["type"] == "archived_subset": + subset = subsets_by_id[subset_id] + asset_ids.append(subset["parent"]) + archived_subsets.append(subset) + else: + subset = subsets_by_id[subset_id] + asset_ids.append(subset["parent"]) + content_subsets[subset_id] = subset + + assets = io.find({ + "type": {"$in": ["asset", "archived_asset"]}, + "_id": {"$in": list(asset_ids)} + }) + assets_by_id = {asset["_id"]: asset for asset in assets} + + missing_assets = [] + archived_assets = [] + content_assets = {} + for asset_id in asset_ids: + if asset_id not in assets_by_id: + missing_assets.append(asset_id) + elif assets_by_id[asset_id]["type"] == "archived_asset": + archived_assets.append(assets_by_id[asset_id]) + else: + content_assets[asset_id] = assets_by_id[asset_id] + + self.content_assets = content_assets + self.content_subsets = content_subsets + self.content_versions = content_versions + self.content_repres = content_repres + + self.hero_version_ids = hero_version_ids + + self.missing_assets = missing_assets + self.missing_versions = missing_versions + self.missing_subsets = missing_subsets + self.missing_repres = missing_repres + self.missing_docs = ( + bool(missing_assets) + or bool(missing_versions) + or bool(missing_subsets) + or bool(missing_repres) + ) + + self.archived_assets = archived_assets + self.archived_subsets = archived_subsets + self.archived_repres = archived_repres + + def _combobox_value_changed(self, *args, **kwargs): + self.refresh() + + def refresh(self, init_refresh=False): + """Build the need comboboxes with content""" + if not self._fill_check and not init_refresh: + return + + self._fill_check = False + + if init_refresh: + asset_values = self._get_asset_box_values() + self._fill_combobox(asset_values, "asset") + + validation_state = ValidationState() + + # Set other comboboxes to empty if any document is missing or any asset + # of loaded representations is archived. + self._is_asset_ok(validation_state) + if validation_state.asset_ok: + subset_values = self._get_subset_box_values() + self._fill_combobox(subset_values, "subset") + self._is_subset_ok(validation_state) + + if validation_state.asset_ok and validation_state.subset_ok: + repre_values = sorted(self._representations_box_values()) + self._fill_combobox(repre_values, "repre") + self._is_repre_ok(validation_state) + + # Fill comboboxes with values + self.set_labels() + self.apply_validations(validation_state) + + if init_refresh: # pre select context if possible + self._assets_box.set_valid_value(self._init_asset_name) + self._subsets_box.set_valid_value(self._init_subset_name) + self._representations_box.set_valid_value(self._init_repre_name) + + self._fill_check = True + + def _get_loaders(self, representations): + if not representations: + return list() + + available_loaders = filter( + lambda l: not (hasattr(l, "is_utility") and l.is_utility), + api.discover(api.Loader) + ) + + loaders = set() + + for representation in representations: + for loader in api.loaders_from_representation( + available_loaders, + representation + ): + loaders.add(loader) + + return loaders + + def _fill_combobox(self, values, combobox_type): + if combobox_type == "asset": + combobox_widget = self._assets_box + elif combobox_type == "subset": + combobox_widget = self._subsets_box + elif combobox_type == "repre": + combobox_widget = self._representations_box + else: + return + selected_value = combobox_widget.get_valid_value() + + # Fill combobox + if values is not None: + combobox_widget.populate(list(sorted(values))) + if selected_value and selected_value in values: + index = None + for idx in range(combobox_widget.count()): + if selected_value == str(combobox_widget.itemText(idx)): + index = idx + break + if index is not None: + combobox_widget.setCurrentIndex(index) + + def set_labels(self): + asset_label = self._assets_box.get_valid_value() + subset_label = self._subsets_box.get_valid_value() + repre_label = self._representations_box.get_valid_value() + + default = "*No changes" + self._asset_label.setText(asset_label or default) + self._subset_label.setText(subset_label or default) + self._repre_label.setText(repre_label or default) + + def apply_validations(self, validation_state): + error_msg = "*Please select" + error_sheet = "border: 1px solid red;" + success_sheet = "border: 1px solid green;" + + asset_sheet = None + subset_sheet = None + repre_sheet = None + accept_sheet = None + if validation_state.asset_ok is False: + asset_sheet = error_sheet + self._asset_label.setText(error_msg) + elif validation_state.subset_ok is False: + subset_sheet = error_sheet + self._subset_label.setText(error_msg) + elif validation_state.repre_ok is False: + repre_sheet = error_sheet + self._repre_label.setText(error_msg) + + if validation_state.all_ok: + accept_sheet = success_sheet + + self._assets_box.setStyleSheet(asset_sheet or "") + self._subsets_box.setStyleSheet(subset_sheet or "") + self._representations_box.setStyleSheet(repre_sheet or "") + + self._accept_btn.setEnabled(validation_state.all_ok) + self._accept_btn.setStyleSheet(accept_sheet or "") + + def _get_asset_box_values(self): + asset_docs = io.find( + {"type": "asset"}, + {"_id": 1, "name": 1} + ) + asset_names_by_id = { + asset_doc["_id"]: asset_doc["name"] + for asset_doc in asset_docs + } + subsets = io.find( + { + "type": "subset", + "parent": {"$in": list(asset_names_by_id.keys())} + }, + { + "parent": 1 + } + ) + + filtered_assets = [] + for subset in subsets: + asset_name = asset_names_by_id[subset["parent"]] + if asset_name not in filtered_assets: + filtered_assets.append(asset_name) + return sorted(filtered_assets) + + def _get_subset_box_values(self): + selected_asset = self._assets_box.get_valid_value() + if selected_asset: + asset_doc = io.find_one({"type": "asset", "name": selected_asset}) + asset_ids = [asset_doc["_id"]] + else: + asset_ids = list(self.content_assets.keys()) + + subsets = io.find( + { + "type": "subset", + "parent": {"$in": asset_ids} + }, + { + "parent": 1, + "name": 1 + } + ) + + subset_names_by_parent_id = collections.defaultdict(set) + for subset in subsets: + subset_names_by_parent_id[subset["parent"]].add(subset["name"]) + + possible_subsets = None + for subset_names in subset_names_by_parent_id.values(): + if possible_subsets is None: + possible_subsets = subset_names + else: + possible_subsets = (possible_subsets & subset_names) + + if not possible_subsets: + break + + return list(possible_subsets or list()) + + def _representations_box_values(self): + # NOTE hero versions are not used because it is expected that + # hero version has same representations as latests + selected_asset = self._assets_box.currentText() + selected_subset = self._subsets_box.currentText() + + # If nothing is selected + # [ ] [ ] [?] + if not selected_asset and not selected_subset: + # Find all representations of selection's subsets + possible_repres = list(io.find( + { + "type": "representation", + "parent": {"$in": list(self.content_versions.keys())} + }, + { + "parent": 1, + "name": 1 + } + )) + + possible_repres_by_parent = collections.defaultdict(set) + for repre in possible_repres: + possible_repres_by_parent[repre["parent"]].add(repre["name"]) + + output_repres = None + for repre_names in possible_repres_by_parent.values(): + if output_repres is None: + output_repres = repre_names + else: + output_repres = (output_repres & repre_names) + + if not output_repres: + break + + return list(output_repres or list()) + + # [x] [x] [?] + if selected_asset and selected_subset: + asset_doc = io.find_one( + {"type": "asset", "name": selected_asset}, + {"_id": 1} + ) + subset_doc = io.find_one( + { + "type": "subset", + "name": selected_subset, + "parent": asset_doc["_id"] + }, + {"_id": 1} + ) + subset_id = subset_doc["_id"] + last_versions_by_subset_id = self.find_last_versions([subset_id]) + version_doc = last_versions_by_subset_id.get(subset_id) + repre_docs = io.find( + { + "type": "representation", + "parent": version_doc["_id"] + }, + { + "name": 1 + } + ) + return [ + repre_doc["name"] + for repre_doc in repre_docs + ] + + # [x] [ ] [?] + # If asset only is selected + if selected_asset: + asset_doc = io.find_one( + {"type": "asset", "name": selected_asset}, + {"_id": 1} + ) + if not asset_doc: + return list() + + # Filter subsets by subset names from content + subset_names = set() + for subset_doc in self.content_subsets.values(): + subset_names.add(subset_doc["name"]) + subset_docs = io.find( + { + "type": "subset", + "parent": asset_doc["_id"], + "name": {"$in": list(subset_names)} + }, + {"_id": 1} + ) + subset_ids = [ + subset_doc["_id"] + for subset_doc in subset_docs + ] + if not subset_ids: + return list() + + last_versions_by_subset_id = self.find_last_versions(subset_ids) + subset_id_by_version_id = {} + for subset_id, last_version in last_versions_by_subset_id.items(): + version_id = last_version["_id"] + subset_id_by_version_id[version_id] = subset_id + + if not subset_id_by_version_id: + return list() + + repre_docs = list(io.find( + { + "type": "representation", + "parent": {"$in": list(subset_id_by_version_id.keys())} + }, + { + "name": 1, + "parent": 1 + } + )) + if not repre_docs: + return list() + + repre_names_by_parent = collections.defaultdict(set) + for repre_doc in repre_docs: + repre_names_by_parent[repre_doc["parent"]].add( + repre_doc["name"] + ) + + available_repres = None + for repre_names in repre_names_by_parent.values(): + if available_repres is None: + available_repres = repre_names + continue + + available_repres = available_repres.intersection(repre_names) + + return list(available_repres) + + # [ ] [x] [?] + subset_docs = list(io.find( + { + "type": "subset", + "parent": {"$in": list(self.content_assets.keys())}, + "name": selected_subset + }, + {"_id": 1, "parent": 1} + )) + if not subset_docs: + return list() + + subset_docs_by_id = { + subset_doc["_id"]: subset_doc + for subset_doc in subset_docs + } + last_versions_by_subset_id = self.find_last_versions( + subset_docs_by_id.keys() + ) + + subset_id_by_version_id = {} + for subset_id, last_version in last_versions_by_subset_id.items(): + version_id = last_version["_id"] + subset_id_by_version_id[version_id] = subset_id + + if not subset_id_by_version_id: + return list() + + repre_docs = list(io.find( + { + "type": "representation", + "parent": {"$in": list(subset_id_by_version_id.keys())} + }, + { + "name": 1, + "parent": 1 + } + )) + if not repre_docs: + return list() + + repre_names_by_asset_id = {} + for repre_doc in repre_docs: + subset_id = subset_id_by_version_id[repre_doc["parent"]] + asset_id = subset_docs_by_id[subset_id]["parent"] + if asset_id not in repre_names_by_asset_id: + repre_names_by_asset_id[asset_id] = set() + repre_names_by_asset_id[asset_id].add(repre_doc["name"]) + + available_repres = None + for repre_names in repre_names_by_asset_id.values(): + if available_repres is None: + available_repres = repre_names + continue + + available_repres = available_repres.intersection(repre_names) + + return list(available_repres) + + def _is_asset_ok(self, validation_state): + selected_asset = self._assets_box.get_valid_value() + if ( + selected_asset is None + and (self.missing_docs or self.archived_assets) + ): + validation_state.asset_ok = False + + def _is_subset_ok(self, validation_state): + selected_asset = self._assets_box.get_valid_value() + selected_subset = self._subsets_box.get_valid_value() + + # [?] [x] [?] + # If subset is selected then must be ok + if selected_subset is not None: + return + + # [ ] [ ] [?] + if selected_asset is None: + # If there were archived subsets and asset is not selected + if self.archived_subsets: + validation_state.subset_ok = False + return + + # [x] [ ] [?] + asset_doc = io.find_one( + {"type": "asset", "name": selected_asset}, + {"_id": 1} + ) + subset_docs = io.find( + {"type": "subset", "parent": asset_doc["_id"]}, + {"name": 1} + ) + subset_names = set( + subset_doc["name"] + for subset_doc in subset_docs + ) + + for subset_doc in self.content_subsets.values(): + if subset_doc["name"] not in subset_names: + validation_state.subset_ok = False + break + + def find_last_versions(self, subset_ids): + _pipeline = [ + # Find all versions of those subsets + {"$match": { + "type": "version", + "parent": {"$in": list(subset_ids)} + }}, + # Sorting versions all together + {"$sort": {"name": 1}}, + # Group them by "parent", but only take the last + {"$group": { + "_id": "$parent", + "_version_id": {"$last": "$_id"}, + "type": {"$last": "$type"} + }} + ] + last_versions_by_subset_id = dict() + for doc in io.aggregate(_pipeline): + doc["parent"] = doc["_id"] + doc["_id"] = doc.pop("_version_id") + last_versions_by_subset_id[doc["parent"]] = doc + return last_versions_by_subset_id + + def _is_repre_ok(self, validation_state): + selected_asset = self._assets_box.get_valid_value() + selected_subset = self._subsets_box.get_valid_value() + selected_repre = self._representations_box.get_valid_value() + + # [?] [?] [x] + # If subset is selected then must be ok + if selected_repre is not None: + return + + # [ ] [ ] [ ] + if selected_asset is None and selected_subset is None: + if ( + self.archived_repres + or self.missing_versions + or self.missing_repres + ): + validation_state.repre_ok = False + return + + # [x] [x] [ ] + if selected_asset is not None and selected_subset is not None: + asset_doc = io.find_one( + {"type": "asset", "name": selected_asset}, + {"_id": 1} + ) + subset_doc = io.find_one( + { + "type": "subset", + "parent": asset_doc["_id"], + "name": selected_subset + }, + {"_id": 1} + ) + last_versions_by_subset_id = self.find_last_versions( + [subset_doc["_id"]] + ) + last_version = last_versions_by_subset_id.get(subset_doc["_id"]) + if not last_version: + validation_state.repre_ok = False + return + + repre_docs = io.find( + { + "type": "representation", + "parent": last_version["_id"] + }, + {"name": 1} + ) + + repre_names = set( + repre_doc["name"] + for repre_doc in repre_docs + ) + for repre_doc in self.content_repres.values(): + if repre_doc["name"] not in repre_names: + validation_state.repre_ok = False + break + return + + # [x] [ ] [ ] + if selected_asset is not None: + asset_doc = io.find_one( + {"type": "asset", "name": selected_asset}, + {"_id": 1} + ) + subset_docs = list(io.find( + { + "type": "subset", + "parent": asset_doc["_id"] + }, + {"_id": 1, "name": 1} + )) + + subset_name_by_id = {} + subset_ids = set() + for subset_doc in subset_docs: + subset_id = subset_doc["_id"] + subset_ids.add(subset_id) + subset_name_by_id[subset_id] = subset_doc["name"] + + last_versions_by_subset_id = self.find_last_versions(subset_ids) + + subset_id_by_version_id = {} + for subset_id, last_version in last_versions_by_subset_id.items(): + version_id = last_version["_id"] + subset_id_by_version_id[version_id] = subset_id + + repre_docs = io.find( + { + "type": "representation", + "parent": {"$in": list(subset_id_by_version_id.keys())} + }, + { + "name": 1, + "parent": 1 + } + ) + repres_by_subset_name = {} + for repre_doc in repre_docs: + subset_id = subset_id_by_version_id[repre_doc["parent"]] + subset_name = subset_name_by_id[subset_id] + if subset_name not in repres_by_subset_name: + repres_by_subset_name[subset_name] = set() + repres_by_subset_name[subset_name].add(repre_doc["name"]) + + for repre_doc in self.content_repres.values(): + version_doc = self.content_versions[repre_doc["parent"]] + subset_doc = self.content_subsets[version_doc["parent"]] + repre_names = ( + repres_by_subset_name.get(subset_doc["name"]) or [] + ) + if repre_doc["name"] not in repre_names: + validation_state.repre_ok = False + break + return + + # [ ] [x] [ ] + # Subset documents + subset_docs = io.find( + { + "type": "subset", + "parent": {"$in": list(self.content_assets.keys())}, + "name": selected_subset + }, + {"_id": 1, "name": 1, "parent": 1} + ) + + subset_docs_by_id = {} + for subset_doc in subset_docs: + subset_docs_by_id[subset_doc["_id"]] = subset_doc + + last_versions_by_subset_id = self.find_last_versions( + subset_docs_by_id.keys() + ) + subset_id_by_version_id = {} + for subset_id, last_version in last_versions_by_subset_id.items(): + version_id = last_version["_id"] + subset_id_by_version_id[version_id] = subset_id + + repre_docs = io.find( + { + "type": "representation", + "parent": {"$in": list(subset_id_by_version_id.keys())} + }, + { + "name": 1, + "parent": 1 + } + ) + repres_by_asset_id = {} + for repre_doc in repre_docs: + subset_id = subset_id_by_version_id[repre_doc["parent"]] + asset_id = subset_docs_by_id[subset_id]["parent"] + if asset_id not in repres_by_asset_id: + repres_by_asset_id[asset_id] = set() + repres_by_asset_id[asset_id].add(repre_doc["name"]) + + for repre_doc in self.content_repres.values(): + version_doc = self.content_versions[repre_doc["parent"]] + subset_doc = self.content_subsets[version_doc["parent"]] + asset_id = subset_doc["parent"] + repre_names = ( + repres_by_asset_id.get(asset_id) or [] + ) + if repre_doc["name"] not in repre_names: + validation_state.repre_ok = False + break + + def _on_current_asset(self): + # Set initial asset as current. + asset_name = io.Session["AVALON_ASSET"] + index = self._assets_box.findText( + asset_name, QtCore.Qt.MatchFixedString + ) + if index >= 0: + print("Setting asset to {}".format(asset_name)) + self._assets_box.setCurrentIndex(index) + + def _on_accept(self): + # Use None when not a valid value or when placeholder value + selected_asset = self._assets_box.get_valid_value() + selected_subset = self._subsets_box.get_valid_value() + selected_representation = self._representations_box.get_valid_value() + + if selected_asset: + asset_doc = io.find_one({"type": "asset", "name": selected_asset}) + asset_docs_by_id = {asset_doc["_id"]: asset_doc} + else: + asset_docs_by_id = self.content_assets + + asset_docs_by_name = { + asset_doc["name"]: asset_doc + for asset_doc in asset_docs_by_id.values() + } + + asset_ids = list(asset_docs_by_id.keys()) + + subset_query = { + "type": "subset", + "parent": {"$in": asset_ids} + } + if selected_subset: + subset_query["name"] = selected_subset + + subset_docs = list(io.find(subset_query)) + subset_ids = [] + subset_docs_by_parent_and_name = collections.defaultdict(dict) + for subset in subset_docs: + subset_ids.append(subset["_id"]) + parent_id = subset["parent"] + name = subset["name"] + subset_docs_by_parent_and_name[parent_id][name] = subset + + # versions + version_docs = list(io.find({ + "type": "version", + "parent": {"$in": subset_ids} + }, sort=[("name", -1)])) + + hero_version_docs = list(io.find({ + "type": "hero_version", + "parent": {"$in": subset_ids} + })) + + version_ids = list() + + version_docs_by_parent_id = {} + for version_doc in version_docs: + parent_id = version_doc["parent"] + if parent_id not in version_docs_by_parent_id: + version_ids.append(version_doc["_id"]) + version_docs_by_parent_id[parent_id] = version_doc + + hero_version_docs_by_parent_id = {} + for hero_version_doc in hero_version_docs: + version_ids.append(hero_version_doc["_id"]) + parent_id = hero_version_doc["parent"] + hero_version_docs_by_parent_id[parent_id] = hero_version_doc + + repre_docs = io.find({ + "type": "representation", + "parent": {"$in": version_ids} + }) + repre_docs_by_parent_id_by_name = collections.defaultdict(dict) + for repre_doc in repre_docs: + parent_id = repre_doc["parent"] + name = repre_doc["name"] + repre_docs_by_parent_id_by_name[parent_id][name] = repre_doc + + for container in self._items: + container_repre_id = io.ObjectId(container["representation"]) + container_repre = self.content_repres[container_repre_id] + container_repre_name = container_repre["name"] + + container_version_id = container_repre["parent"] + container_version = self.content_versions[container_version_id] + + container_subset_id = container_version["parent"] + container_subset = self.content_subsets[container_subset_id] + container_subset_name = container_subset["name"] + + container_asset_id = container_subset["parent"] + container_asset = self.content_assets[container_asset_id] + container_asset_name = container_asset["name"] + + if selected_asset: + asset_doc = asset_docs_by_name[selected_asset] + else: + asset_doc = asset_docs_by_name[container_asset_name] + + subsets_by_name = subset_docs_by_parent_and_name[asset_doc["_id"]] + if selected_subset: + subset_doc = subsets_by_name[selected_subset] + else: + subset_doc = subsets_by_name[container_subset_name] + + repre_doc = None + subset_id = subset_doc["_id"] + if container_version["type"] == "hero_version": + hero_version = hero_version_docs_by_parent_id.get( + subset_id + ) + if hero_version: + _repres = repre_docs_by_parent_id_by_name.get( + hero_version["_id"] + ) + if selected_representation: + repre_doc = _repres.get(selected_representation) + else: + repre_doc = _repres.get(container_repre_name) + + if not repre_doc: + version_doc = version_docs_by_parent_id[subset_id] + version_id = version_doc["_id"] + repres_by_name = repre_docs_by_parent_id_by_name[version_id] + if selected_representation: + repre_doc = repres_by_name[selected_representation] + else: + repre_doc = repres_by_name[container_repre_name] + + try: + api.switch(container, repre_doc) + except Exception: + msg = ( + "Couldn't switch asset." + "See traceback for more information." + ) + log.warning(msg, exc_info=True) + dialog = QtWidgets.QMessageBox(self) + dialog.setWindowTitle("Switch asset failed") + dialog.setText( + "Switch asset failed. Search console log for more details" + ) + dialog.exec_() + + self.switched.emit() + + self.close() diff --git a/openpype/tools/sceneinventory/view.py b/openpype/tools/sceneinventory/view.py new file mode 100644 index 0000000000..80f26a881d --- /dev/null +++ b/openpype/tools/sceneinventory/view.py @@ -0,0 +1,794 @@ +import collections +import logging +from functools import partial + +from Qt import QtWidgets, QtCore + +from avalon import io, api, style +from avalon.vendor import qtawesome +from avalon.lib import HeroVersionType +from avalon.tools import lib as tools_lib + +from openpype.modules import ModulesManager + +from .switch_dialog import SwitchAssetDialog +from .model import InventoryModel + + +DEFAULT_COLOR = "#fb9c15" + +log = logging.getLogger("SceneInventory") + + +class SceneInvetoryView(QtWidgets.QTreeView): + data_changed = QtCore.Signal() + hierarchy_view_changed = QtCore.Signal(bool) + + def __init__(self, parent=None): + super(SceneInvetoryView, self).__init__(parent=parent) + + # view settings + self.setIndentation(12) + self.setAlternatingRowColors(True) + self.setSortingEnabled(True) + self.setSelectionMode(self.ExtendedSelection) + self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) + self.customContextMenuRequested.connect(self._show_right_mouse_menu) + self._hierarchy_view = False + self._selected = None + + manager = ModulesManager() + self.sync_server = manager.modules_by_name["sync_server"] + self.sync_enabled = self.sync_server.enabled + + def _set_hierarchy_view(self, enabled): + if enabled == self._hierarchy_view: + return + self._hierarchy_view = enabled + self.hierarchy_view_changed.emit(enabled) + + def _enter_hierarchy(self, items): + self._selected = set(i["objectName"] for i in items) + self._set_hierarchy_view(True) + self.data_changed.emit() + self.expandToDepth(1) + self.setStyleSheet(""" + QTreeView { + border-color: #fb9c15; + } + """) + + def _leave_hierarchy(self): + self._set_hierarchy_view(False) + self.data_changed.emit() + self.setStyleSheet("QTreeView {}") + + def _build_item_menu_for_selection(self, items, menu): + if not items: + return + + repre_ids = [] + for item in items: + item_id = io.ObjectId(item["representation"]) + if item_id not in repre_ids: + repre_ids.append(item_id) + + repre_docs = io.find( + { + "type": "representation", + "_id": {"$in": repre_ids} + }, + {"parent": 1} + ) + + version_ids = [] + for repre_doc in repre_docs: + version_id = repre_doc["parent"] + if version_id not in version_ids: + version_ids.append(version_id) + + loaded_versions = io.find({ + "_id": {"$in": version_ids}, + "type": {"$in": ["version", "hero_version"]} + }) + + loaded_hero_versions = [] + versions_by_parent_id = collections.defaultdict(list) + version_parents = [] + for version in loaded_versions: + if version["type"] == "hero_version": + loaded_hero_versions.append(version) + else: + parent_id = version["parent"] + versions_by_parent_id[parent_id].append(version) + if parent_id not in version_parents: + version_parents.append(parent_id) + + all_versions = io.find({ + "type": {"$in": ["hero_version", "version"]}, + "parent": {"$in": version_parents} + }) + hero_versions = [] + versions = [] + for version in all_versions: + if version["type"] == "hero_version": + hero_versions.append(version) + else: + versions.append(version) + + has_loaded_hero_versions = len(loaded_hero_versions) > 0 + has_available_hero_version = len(hero_versions) > 0 + has_outdated = False + + for version in versions: + parent_id = version["parent"] + current_versions = versions_by_parent_id[parent_id] + for current_version in current_versions: + if current_version["name"] < version["name"]: + has_outdated = True + break + + if has_outdated: + break + + switch_to_versioned = None + if has_loaded_hero_versions: + def _on_switch_to_versioned(items): + repre_ids = [] + for item in items: + item_id = io.ObjectId(item["representation"]) + if item_id not in repre_ids: + repre_ids.append(item_id) + + repre_docs = io.find( + { + "type": "representation", + "_id": {"$in": repre_ids} + }, + {"parent": 1} + ) + + version_ids = [] + version_id_by_repre_id = {} + for repre_doc in repre_docs: + version_id = repre_doc["parent"] + version_id_by_repre_id[repre_doc["_id"]] = version_id + if version_id not in version_ids: + version_ids.append(version_id) + hero_versions = io.find( + { + "_id": {"$in": version_ids}, + "type": "hero_version" + }, + {"version_id": 1} + ) + version_ids = set() + for hero_version in hero_versions: + version_id = hero_version["version_id"] + version_ids.add(version_id) + hero_version_id = hero_version["_id"] + for _repre_id, current_version_id in ( + version_id_by_repre_id.items() + ): + if current_version_id == hero_version_id: + version_id_by_repre_id[_repre_id] = version_id + + version_docs = io.find( + { + "_id": {"$in": list(version_ids)}, + "type": "version" + }, + {"name": 1} + ) + version_name_by_id = {} + for version_doc in version_docs: + version_name_by_id[version_doc["_id"]] = \ + version_doc["name"] + + for item in items: + repre_id = io.ObjectId(item["representation"]) + version_id = version_id_by_repre_id.get(repre_id) + version_name = version_name_by_id.get(version_id) + if version_name is not None: + try: + api.update(item, version_name) + except AssertionError: + self._show_version_error_dialog( + version_name, [item] + ) + log.warning("Update failed", exc_info=True) + + self.data_changed.emit() + + update_icon = qtawesome.icon( + "fa.asterisk", + color=DEFAULT_COLOR + ) + switch_to_versioned = QtWidgets.QAction( + update_icon, + "Switch to versioned", + menu + ) + switch_to_versioned.triggered.connect( + lambda: _on_switch_to_versioned(items) + ) + + update_to_latest_action = None + if has_outdated or has_loaded_hero_versions: + # update to latest version + def _on_update_to_latest(items): + for item in items: + try: + api.update(item, -1) + except AssertionError: + self._show_version_error_dialog(None, [item]) + log.warning("Update failed", exc_info=True) + self.data_changed.emit() + + update_icon = qtawesome.icon( + "fa.angle-double-up", + color=DEFAULT_COLOR + ) + update_to_latest_action = QtWidgets.QAction( + update_icon, + "Update to latest", + menu + ) + update_to_latest_action.triggered.connect( + lambda: _on_update_to_latest(items) + ) + + change_to_hero = None + if has_available_hero_version: + # change to hero version + def _on_update_to_hero(items): + for item in items: + try: + api.update(item, HeroVersionType(-1)) + except AssertionError: + self._show_version_error_dialog('hero', [item]) + log.warning("Update failed", exc_info=True) + self.data_changed.emit() + + # TODO change icon + change_icon = qtawesome.icon( + "fa.asterisk", + color="#00b359" + ) + change_to_hero = QtWidgets.QAction( + change_icon, + "Change to hero", + menu + ) + change_to_hero.triggered.connect( + lambda: _on_update_to_hero(items) + ) + + # set version + set_version_icon = qtawesome.icon("fa.hashtag", color=DEFAULT_COLOR) + set_version_action = QtWidgets.QAction( + set_version_icon, + "Set version", + menu + ) + set_version_action.triggered.connect( + lambda: self._show_version_dialog(items)) + + # switch asset + switch_asset_icon = qtawesome.icon("fa.sitemap", color=DEFAULT_COLOR) + switch_asset_action = QtWidgets.QAction( + switch_asset_icon, + "Switch Asset", + menu + ) + switch_asset_action.triggered.connect( + lambda: self._show_switch_dialog(items)) + + # remove + remove_icon = qtawesome.icon("fa.remove", color=DEFAULT_COLOR) + remove_action = QtWidgets.QAction(remove_icon, "Remove items", menu) + remove_action.triggered.connect( + lambda: self._show_remove_warning_dialog(items)) + + # add the actions + if switch_to_versioned: + menu.addAction(switch_to_versioned) + + if update_to_latest_action: + menu.addAction(update_to_latest_action) + + if change_to_hero: + menu.addAction(change_to_hero) + + menu.addAction(set_version_action) + menu.addAction(switch_asset_action) + + menu.addSeparator() + + menu.addAction(remove_action) + + self._handle_sync_server(menu, repre_ids) + + def _handle_sync_server(self, menu, repre_ids): + """ + Adds actions for download/upload when SyncServer is enabled + + Args: + menu (OptionMenu) + repre_ids (list) of object_ids + Returns: + (OptionMenu) + """ + if not self.sync_enabled: + return + + menu.addSeparator() + + download_icon = qtawesome.icon("fa.download", color=DEFAULT_COLOR) + download_active_action = QtWidgets.QAction( + download_icon, + "Download", + menu + ) + download_active_action.triggered.connect( + lambda: self._add_sites(repre_ids, 'active_site')) + + upload_icon = qtawesome.icon("fa.upload", color=DEFAULT_COLOR) + upload_remote_action = QtWidgets.QAction( + upload_icon, + "Upload", + menu + ) + upload_remote_action.triggered.connect( + lambda: self._add_sites(repre_ids, 'remote_site')) + + menu.addAction(download_active_action) + menu.addAction(upload_remote_action) + + def _add_sites(self, repre_ids, side): + """ + (Re)sync all 'repre_ids' to specific site. + + It checks if opposite site has fully available content to limit + accidents. (ReSync active when no remote >> losing active content) + + Args: + repre_ids (list) + side (str): 'active_site'|'remote_site' + """ + project_name = io.Session["AVALON_PROJECT"] + active_site = self.sync_server.get_active_site(project_name) + remote_site = self.sync_server.get_remote_site(project_name) + + repre_docs = io.find({ + "type": "representation", + "_id": {"$in": repre_ids} + }) + repre_docs_by_id = { + repre_doc["_id"]: repre_doc + for repre_doc in repre_docs + } + for repre_id in repre_ids: + repre_doc = repre_docs_by_id.get(repre_id) + if not repre_doc: + continue + + progress = tools_lib.get_progress_for_repre( + repre_doc, + active_site, + remote_site + ) + if side == "active_site": + # check opposite from added site, must be 1 or unable to sync + check_progress = progress[remote_site] + site = active_site + else: + check_progress = progress[active_site] + site = remote_site + + if check_progress == 1: + self.sync_server.add_site( + project_name, repre_id, site, force=True + ) + + self.data_changed.emit() + + def _build_item_menu(self, items=None): + """Create menu for the selected items""" + + if not items: + items = [] + + menu = QtWidgets.QMenu(self) + + # add the actions + self._build_item_menu_for_selection(items, menu) + + # These two actions should be able to work without selection + # expand all items + expandall_action = QtWidgets.QAction(menu, text="Expand all items") + expandall_action.triggered.connect(self.expandAll) + + # collapse all items + collapse_action = QtWidgets.QAction(menu, text="Collapse all items") + collapse_action.triggered.connect(self.collapseAll) + + menu.addAction(expandall_action) + menu.addAction(collapse_action) + + custom_actions = self._get_custom_actions(containers=items) + if custom_actions: + submenu = QtWidgets.QMenu("Actions", self) + for action in custom_actions: + color = action.color or DEFAULT_COLOR + icon = qtawesome.icon("fa.%s" % action.icon, color=color) + action_item = QtWidgets.QAction(icon, action.label, submenu) + action_item.triggered.connect( + partial(self._process_custom_action, action, items)) + + submenu.addAction(action_item) + + menu.addMenu(submenu) + + # go back to flat view + if self._hierarchy_view: + back_to_flat_icon = qtawesome.icon("fa.list", color=DEFAULT_COLOR) + back_to_flat_action = QtWidgets.QAction( + back_to_flat_icon, + "Back to Full-View", + menu + ) + back_to_flat_action.triggered.connect(self._leave_hierarchy) + + # send items to hierarchy view + enter_hierarchy_icon = qtawesome.icon("fa.indent", color="#d8d8d8") + enter_hierarchy_action = QtWidgets.QAction( + enter_hierarchy_icon, + "Cherry-Pick (Hierarchy)", + menu + ) + enter_hierarchy_action.triggered.connect( + lambda: self._enter_hierarchy(items)) + + if items: + menu.addAction(enter_hierarchy_action) + + if self._hierarchy_view: + menu.addAction(back_to_flat_action) + + return menu + + def _get_custom_actions(self, containers): + """Get the registered Inventory Actions + + Args: + containers(list): collection of containers + + Returns: + list: collection of filter and initialized actions + """ + + def sorter(Plugin): + """Sort based on order attribute of the plugin""" + return Plugin.order + + # Fedd an empty dict if no selection, this will ensure the compat + # lookup always work, so plugin can interact with Scene Inventory + # reversely. + containers = containers or [dict()] + + # Check which action will be available in the menu + Plugins = api.discover(api.InventoryAction) + compatible = [p() for p in Plugins if + any(p.is_compatible(c) for c in containers)] + + return sorted(compatible, key=sorter) + + def _process_custom_action(self, action, containers): + """Run action and if results are returned positive update the view + + If the result is list or dict, will select view items by the result. + + Args: + action (InventoryAction): Inventory Action instance + containers (list): Data of currently selected items + + Returns: + None + """ + + result = action.process(containers) + if result: + self.data_changed.emit() + + if isinstance(result, (list, set)): + self._select_items_by_action(result) + + if isinstance(result, dict): + self._select_items_by_action( + result["objectNames"], result["options"] + ) + + def _select_items_by_action(self, object_names, options=None): + """Select view items by the result of action + + Args: + object_names (list or set): A list/set of container object name + options (dict): GUI operation options. + + Returns: + None + + """ + options = options or dict() + + if options.get("clear", True): + self.clearSelection() + + object_names = set(object_names) + if ( + self._hierarchy_view + and not self._selected.issuperset(object_names) + ): + # If any container not in current cherry-picked view, update + # view before selecting them. + self._selected.update(object_names) + self.data_changed.emit() + + model = self.model() + selection_model = self.selectionModel() + + select_mode = { + "select": selection_model.Select, + "deselect": selection_model.Deselect, + "toggle": selection_model.Toggle, + }[options.get("mode", "select")] + + for item in tools_lib.iter_model_rows(model, 0): + item = item.data(InventoryModel.ItemRole) + if item.get("isGroupNode"): + continue + + name = item.get("objectName") + if name in object_names: + self.scrollTo(item) # Ensure item is visible + flags = select_mode | selection_model.Rows + selection_model.select(item, flags) + + object_names.remove(name) + + if len(object_names) == 0: + break + + def _show_right_mouse_menu(self, pos): + """Display the menu when at the position of the item clicked""" + + globalpos = self.viewport().mapToGlobal(pos) + + if not self.selectionModel().hasSelection(): + print("No selection") + # Build menu without selection, feed an empty list + menu = self._build_item_menu() + menu.exec_(globalpos) + return + + active = self.currentIndex() # index under mouse + active = active.sibling(active.row(), 0) # get first column + + # move index under mouse + indices = self.get_indices() + if active in indices: + indices.remove(active) + + indices.append(active) + + # Extend to the sub-items + all_indices = self._extend_to_children(indices) + items = [dict(i.data(InventoryModel.ItemRole)) for i in all_indices + if i.parent().isValid()] + + if self._hierarchy_view: + # Ensure no group item + items = [n for n in items if not n.get("isGroupNode")] + + menu = self._build_item_menu(items) + menu.exec_(globalpos) + + def get_indices(self): + """Get the selected rows""" + selection_model = self.selectionModel() + return selection_model.selectedRows() + + def _extend_to_children(self, indices): + """Extend the indices to the children indices. + + Top-level indices are extended to its children indices. Sub-items + are kept as is. + + Args: + indices (list): The indices to extend. + + Returns: + list: The children indices + + """ + def get_children(i): + model = i.model() + rows = model.rowCount(parent=i) + for row in range(rows): + child = model.index(row, 0, parent=i) + yield child + + subitems = set() + for i in indices: + valid_parent = i.parent().isValid() + if valid_parent and i not in subitems: + subitems.add(i) + + if self._hierarchy_view: + # Assume this is a group item + for child in get_children(i): + subitems.add(child) + else: + # is top level item + for child in get_children(i): + subitems.add(child) + + return list(subitems) + + def _show_version_dialog(self, items): + """Create a dialog with the available versions for the selected file + + Args: + items (list): list of items to run the "set_version" for + + Returns: + None + """ + + active = items[-1] + + # Get available versions for active representation + representation_id = io.ObjectId(active["representation"]) + representation = io.find_one({"_id": representation_id}) + version = io.find_one({ + "_id": representation["parent"] + }) + + versions = list(io.find( + { + "parent": version["parent"], + "type": "version" + }, + sort=[("name", 1)] + )) + + hero_version = io.find_one({ + "parent": version["parent"], + "type": "hero_version" + }) + if hero_version: + _version_id = hero_version["version_id"] + for _version in versions: + if _version["_id"] != _version_id: + continue + + hero_version["name"] = HeroVersionType( + _version["name"] + ) + hero_version["data"] = _version["data"] + break + + # Get index among the listed versions + current_item = None + current_version = active["version"] + if isinstance(current_version, HeroVersionType): + current_item = hero_version + else: + for version in versions: + if version["name"] == current_version: + current_item = version + break + + all_versions = [] + if hero_version: + all_versions.append(hero_version) + all_versions.extend(reversed(versions)) + + if current_item: + index = all_versions.index(current_item) + else: + index = 0 + + versions_by_label = dict() + labels = [] + for version in all_versions: + is_hero = version["type"] == "hero_version" + label = tools_lib.format_version(version["name"], is_hero) + labels.append(label) + versions_by_label[label] = version["name"] + + label, state = QtWidgets.QInputDialog.getItem( + self, + "Set version..", + "Set version number to", + labels, + current=index, + editable=False + ) + if not state: + return + + if label: + version = versions_by_label[label] + for item in items: + try: + api.update(item, version) + except AssertionError: + self._show_version_error_dialog(version, [item]) + log.warning("Update failed", exc_info=True) + # refresh model when done + self.data_changed.emit() + + def _show_switch_dialog(self, items): + """Display Switch dialog""" + dialog = SwitchAssetDialog(self, items) + dialog.switched.connect(self.data_changed.emit) + dialog.show() + + def _show_remove_warning_dialog(self, items): + """Prompt a dialog to inform the user the action will remove items""" + + accept = QtWidgets.QMessageBox.Ok + buttons = accept | QtWidgets.QMessageBox.Cancel + + state = QtWidgets.QMessageBox.question( + self, + "Are you sure?", + "Are you sure you want to remove {} item(s)".format(len(items)), + buttons=buttons, + defaultButton=accept + ) + + if state != accept: + return + + for item in items: + api.remove(item) + self.data_changed.emit() + + def _show_version_error_dialog(self, version, items): + """Shows QMessageBox when version switch doesn't work + + Args: + version: str or int or None + """ + if not version: + version_str = "latest" + elif version == "hero": + version_str = "hero" + elif isinstance(version, int): + version_str = "v{:03d}".format(version) + else: + version_str = version + + dialog = QtWidgets.QMessageBox() + dialog.setIcon(QtWidgets.QMessageBox.Warning) + dialog.setStyleSheet(style.load_stylesheet()) + dialog.setWindowTitle("Update failed") + + switch_btn = dialog.addButton( + "Switch Asset", + QtWidgets.QMessageBox.ActionRole + ) + switch_btn.clicked.connect(lambda: self._show_switch_dialog(items)) + + dialog.addButton(QtWidgets.QMessageBox.Cancel) + + msg = ( + "Version update to '{}' failed as representation doesn't exist." + "\n\nPlease update to version with a valid representation" + " OR \n use 'Switch Asset' button to change asset." + ).format(version_str) + dialog.setText(msg) + dialog.exec_() diff --git a/openpype/tools/sceneinventory/widgets.py b/openpype/tools/sceneinventory/widgets.py new file mode 100644 index 0000000000..6bb74d2d1b --- /dev/null +++ b/openpype/tools/sceneinventory/widgets.py @@ -0,0 +1,51 @@ +from Qt import QtWidgets, QtCore + + +class SearchComboBox(QtWidgets.QComboBox): + """Searchable ComboBox with empty placeholder value as first value""" + + def __init__(self, parent=None): + super(SearchComboBox, self).__init__(parent) + + self.setEditable(True) + self.setInsertPolicy(self.NoInsert) + + # Apply completer settings + completer = self.completer() + completer.setCompletionMode(completer.PopupCompletion) + completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive) + + # Force style sheet on popup menu + # It won't take the parent stylesheet for some reason + # todo: better fix for completer popup stylesheet + # if module.window: + # popup = completer.popup() + # popup.setStyleSheet(module.window.styleSheet()) + + def set_placeholder(self, placeholder): + self.lineEdit().setPlaceholderText(placeholder) + + def populate(self, items): + self.clear() + self.addItems([""]) # ensure first item is placeholder + self.addItems(items) + + def get_valid_value(self): + """Return the current text if it's a valid value else None + + Note: The empty placeholder value is valid and returns as "" + + """ + + text = self.currentText() + lookup = set(self.itemText(i) for i in range(self.count())) + if text not in lookup: + return None + + return text or None + + def set_valid_value(self, value): + """Try to locate 'value' and pre-select it in dropdown.""" + index = self.findText(value) + if index > -1: + self.setCurrentIndex(index) diff --git a/openpype/tools/sceneinventory/window.py b/openpype/tools/sceneinventory/window.py new file mode 100644 index 0000000000..e71af6a93d --- /dev/null +++ b/openpype/tools/sceneinventory/window.py @@ -0,0 +1,203 @@ +import os +import sys + +from Qt import QtWidgets, QtCore +from avalon.vendor import qtawesome +from avalon import io, api + +from openpype import style +from openpype.tools.utils.delegates import VersionDelegate +from openpype.tools.utils.lib import ( + qt_app_context, + preserve_expanded_rows, + preserve_selection, + FamilyConfigCache +) + +from .model import ( + InventoryModel, + FilterProxyModel +) +from .view import SceneInvetoryView + + +module = sys.modules[__name__] +module.window = None + + +class SceneInventoryWindow(QtWidgets.QDialog): + """Scene Inventory window""" + + def __init__(self, parent=None): + super(SceneInventoryWindow, self).__init__(parent) + + if not parent: + self.setWindowFlags( + self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint + ) + + project_name = os.getenv("AVALON_PROJECT") or "" + self.setWindowTitle("Scene Inventory 1.0 - {}".format(project_name)) + self.setObjectName("SceneInventory") + # Maya only property + self.setProperty("saveWindowPref", True) + + self.resize(1100, 480) + + # region control + filter_label = QtWidgets.QLabel("Search", self) + text_filter = QtWidgets.QLineEdit(self) + + outdated_only_checkbox = QtWidgets.QCheckBox( + "Filter to outdated", self + ) + outdated_only_checkbox.setToolTip("Show outdated files only") + outdated_only_checkbox.setChecked(False) + + icon = qtawesome.icon("fa.refresh", color="white") + refresh_button = QtWidgets.QPushButton(self) + refresh_button.setIcon(icon) + + control_layout = QtWidgets.QHBoxLayout() + control_layout.addWidget(filter_label) + control_layout.addWidget(text_filter) + control_layout.addWidget(outdated_only_checkbox) + control_layout.addWidget(refresh_button) + + # endregion control + family_config_cache = FamilyConfigCache(io) + + model = InventoryModel(family_config_cache) + proxy = FilterProxyModel() + proxy.setSourceModel(model) + proxy.setDynamicSortFilter(True) + proxy.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive) + + view = SceneInvetoryView(self) + view.setModel(proxy) + + # set some nice default widths for the view + view.setColumnWidth(0, 250) # name + view.setColumnWidth(1, 55) # version + view.setColumnWidth(2, 55) # count + view.setColumnWidth(3, 150) # family + view.setColumnWidth(4, 100) # namespace + + # apply delegates + version_delegate = VersionDelegate(io, self) + column = model.Columns.index("version") + view.setItemDelegateForColumn(column, version_delegate) + + layout = QtWidgets.QVBoxLayout(self) + layout.addLayout(control_layout) + layout.addWidget(view) + + # signals + text_filter.textChanged.connect(self._on_text_filter_change) + outdated_only_checkbox.stateChanged.connect( + self._on_outdated_state_change + ) + view.hierarchy_view_changed.connect( + self._on_hiearchy_view_change + ) + view.data_changed.connect(self.refresh) + refresh_button.clicked.connect(self.refresh) + + self._outdated_only_checkbox = outdated_only_checkbox + self._view = view + self._model = model + self._proxy = proxy + self._version_delegate = version_delegate + self._family_config_cache = family_config_cache + + self._first_show = True + + family_config_cache.refresh() + + def showEvent(self, event): + super(SceneInventoryWindow, self).showEvent(event) + if self._first_show: + self._first_show = False + self.setStyleSheet(style.load_stylesheet()) + + def keyPressEvent(self, event): + """Custom keyPressEvent. + + Override keyPressEvent to do nothing so that Maya's panels won't + take focus when pressing "SHIFT" whilst mouse is over viewport or + outliner. This way users don't accidently perform Maya commands + whilst trying to name an instance. + + """ + + def refresh(self, items=None): + with preserve_expanded_rows( + tree_view=self._view, + role=self._model.UniqueRole + ): + with preserve_selection( + tree_view=self._view, + role=self._model.UniqueRole, + current_index=False + ): + kwargs = {"items": items} + # TODO do not touch view's inner attribute + if self._view._hierarchy_view: + kwargs["selected"] = self._view._selected + self._model.refresh(**kwargs) + + def _on_hiearchy_view_change(self, enabled): + self._proxy.set_hierarchy_view(enabled) + self._model.set_hierarchy_view(enabled) + + def _on_text_filter_change(self, text_filter): + self._proxy.setFilterRegExp(text_filter) + + def _on_outdated_state_change(self): + self._proxy.set_filter_outdated( + self._outdated_only_checkbox.isChecked() + ) + + +def show(root=None, debug=False, parent=None, items=None): + """Display Scene Inventory GUI + + Arguments: + debug (bool, optional): Run in debug-mode, + defaults to False + parent (QtCore.QObject, optional): When provided parent the interface + to this QObject. + items (list) of dictionaries - for injection of items for standalone + testing + + """ + + try: + module.window.close() + del module.window + except (RuntimeError, AttributeError): + pass + + if debug is True: + io.install() + + if not os.environ.get("AVALON_PROJECT"): + any_project = next( + project for project in io.projects() + if project.get("active", True) is not False + ) + + api.Session["AVALON_PROJECT"] = any_project["name"] + else: + api.Session["AVALON_PROJECT"] = os.environ.get("AVALON_PROJECT") + + with qt_app_context(): + window = SceneInventoryWindow(parent) + window.show() + window.refresh(items=items) + + module.window = window + + # Pull window to the front. + module.window.raise_() + module.window.activateWindow() diff --git a/openpype/tools/subsetmanager/README.md b/openpype/tools/subsetmanager/README.md new file mode 100644 index 0000000000..062214834a --- /dev/null +++ b/openpype/tools/subsetmanager/README.md @@ -0,0 +1,19 @@ +Subset manager +-------------- + +Simple UI showing list of created subset that will be published via Pyblish. +Useful for applications (Photoshop, AfterEffects, TVPaint, Harmony) which are +storing metadata about instance hidden from user. + +This UI allows listing all created subset and removal of them if needed ( +in case use doesn't want to publish anymore, its using workfile as a starting +file for different task and instances should be completely different etc. +) + +Host is expected to implemented: +- `list_instances` - returning list of dictionaries (instances), must contain + unique uuid field + example: + ```[{"uuid":"15","active":true,"subset":"imageBG","family":"image","id":"pyblish.avalon.instance","asset":"Town"}]``` +- `remove_instance(instance)` - removes instance from file's metadata + instance is a dictionary, with uuid field \ No newline at end of file diff --git a/openpype/tools/subsetmanager/__init__.py b/openpype/tools/subsetmanager/__init__.py new file mode 100644 index 0000000000..6cfca7db66 --- /dev/null +++ b/openpype/tools/subsetmanager/__init__.py @@ -0,0 +1,9 @@ +from .window import ( + show, + SubsetManagerWindow +) + +__all__ = ( + "show", + "SubsetManagerWindow" +) diff --git a/openpype/tools/subsetmanager/model.py b/openpype/tools/subsetmanager/model.py new file mode 100644 index 0000000000..b76c3c2343 --- /dev/null +++ b/openpype/tools/subsetmanager/model.py @@ -0,0 +1,52 @@ +import uuid + +from Qt import QtCore, QtGui + +from avalon import api + +ITEM_ID_ROLE = QtCore.Qt.UserRole + 1 + + +class InstanceModel(QtGui.QStandardItemModel): + def __init__(self, *args, **kwargs): + super(InstanceModel, self).__init__(*args, **kwargs) + self._instances_by_item_id = {} + + def get_instance_by_id(self, item_id): + return self._instances_by_item_id.get(item_id) + + def refresh(self): + self.clear() + + self._instances_by_item_id = {} + + instances = None + host = api.registered_host() + list_instances = getattr(host, "list_instances", None) + if list_instances: + instances = list_instances() + + if not instances: + return + + items = [] + for instance_data in instances: + item_id = str(uuid.uuid4()) + label = instance_data.get("label") or instance_data["subset"] + item = QtGui.QStandardItem(label) + item.setEnabled(True) + item.setEditable(False) + item.setData(item_id, ITEM_ID_ROLE) + items.append(item) + self._instances_by_item_id[item_id] = instance_data + + if items: + self.invisibleRootItem().appendRows(items) + + def headerData(self, section, orientation, role): + if role == QtCore.Qt.DisplayRole and section == 0: + return "Instance" + + return super(InstanceModel, self).headerData( + section, orientation, role + ) diff --git a/openpype/tools/subsetmanager/widgets.py b/openpype/tools/subsetmanager/widgets.py new file mode 100644 index 0000000000..7a8cb15cbf --- /dev/null +++ b/openpype/tools/subsetmanager/widgets.py @@ -0,0 +1,110 @@ +import json +from Qt import QtWidgets, QtCore + + +class InstanceDetail(QtWidgets.QWidget): + save_triggered = QtCore.Signal() + + def __init__(self, parent=None): + super(InstanceDetail, self).__init__(parent) + + details_widget = QtWidgets.QPlainTextEdit(self) + details_widget.setObjectName("SubsetManagerDetailsText") + + save_btn = QtWidgets.QPushButton("Save", self) + + self._block_changes = False + self._editable = False + self._item_id = None + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(details_widget, 1) + layout.addWidget(save_btn, 0, QtCore.Qt.AlignRight) + + save_btn.clicked.connect(self._on_save_clicked) + details_widget.textChanged.connect(self._on_text_change) + + self._details_widget = details_widget + self._save_btn = save_btn + + self.set_editable(False) + + def _on_save_clicked(self): + if self.is_valid(): + self.save_triggered.emit() + + def set_editable(self, enabled=True): + self._editable = enabled + self.update_state() + + def update_state(self, valid=None): + editable = self._editable + if not self._item_id: + editable = False + + self._save_btn.setVisible(editable) + self._details_widget.setReadOnly(not editable) + if valid is None: + valid = self.is_valid() + + self._save_btn.setEnabled(valid) + self._set_invalid_detail(valid) + + def _set_invalid_detail(self, valid): + state = "" + if not valid: + state = "invalid" + + current_state = self._details_widget.property("state") + if current_state != state: + self._details_widget.setProperty("state", state) + self._details_widget.style().polish(self._details_widget) + + def set_details(self, container, item_id): + self._item_id = item_id + + text = "Nothing selected" + if item_id: + try: + text = json.dumps(container, indent=4) + except Exception: + text = str(container) + + self._block_changes = True + self._details_widget.setPlainText(text) + self._block_changes = False + + self.update_state() + + def instance_data_from_text(self): + try: + jsoned = json.loads(self._details_widget.toPlainText()) + except Exception: + jsoned = None + return jsoned + + def item_id(self): + return self._item_id + + def is_valid(self): + if not self._item_id: + return True + + value = self._details_widget.toPlainText() + valid = False + try: + jsoned = json.loads(value) + if jsoned and isinstance(jsoned, dict): + valid = True + + except Exception: + pass + return valid + + def _on_text_change(self): + if self._block_changes or not self._item_id: + return + + valid = self.is_valid() + self.update_state(valid) diff --git a/openpype/tools/subsetmanager/window.py b/openpype/tools/subsetmanager/window.py new file mode 100644 index 0000000000..cb0e3c1c1e --- /dev/null +++ b/openpype/tools/subsetmanager/window.py @@ -0,0 +1,218 @@ +import os +import sys + +from Qt import QtWidgets, QtCore + +from avalon import api +from avalon.vendor import qtawesome + +from openpype import style +from openpype.tools.utils.lib import ( + iter_model_rows, + qt_app_context +) +from openpype.tools.utils.models import RecursiveSortFilterProxyModel +from .model import ( + InstanceModel, + ITEM_ID_ROLE +) +from .widgets import InstanceDetail + + +module = sys.modules[__name__] +module.window = None + + +class SubsetManagerWindow(QtWidgets.QDialog): + def __init__(self, parent=None): + super(SubsetManagerWindow, self).__init__(parent=parent) + self.setWindowTitle("Subset Manager 0.1") + self.setObjectName("SubsetManager") + if not parent: + self.setWindowFlags( + self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint + ) + + self.resize(780, 430) + + # Trigger refresh on first called show + self._first_show = True + + left_side_widget = QtWidgets.QWidget(self) + + # Header part + header_widget = QtWidgets.QWidget(left_side_widget) + + # Filter input + filter_input = QtWidgets.QLineEdit(header_widget) + filter_input.setPlaceholderText("Filter subsets..") + + # Refresh button + icon = qtawesome.icon("fa.refresh", color="white") + refresh_btn = QtWidgets.QPushButton(header_widget) + refresh_btn.setIcon(icon) + + header_layout = QtWidgets.QHBoxLayout(header_widget) + header_layout.setContentsMargins(0, 0, 0, 0) + header_layout.addWidget(filter_input) + header_layout.addWidget(refresh_btn) + + # Instances view + view = QtWidgets.QTreeView(left_side_widget) + view.setIndentation(0) + view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) + + model = InstanceModel(view) + proxy = RecursiveSortFilterProxyModel() + proxy.setSourceModel(model) + proxy.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive) + + view.setModel(proxy) + + left_side_layout = QtWidgets.QVBoxLayout(left_side_widget) + left_side_layout.setContentsMargins(0, 0, 0, 0) + left_side_layout.addWidget(header_widget) + left_side_layout.addWidget(view) + + details_widget = InstanceDetail(self) + + layout = QtWidgets.QHBoxLayout(self) + layout.addWidget(left_side_widget, 0) + layout.addWidget(details_widget, 1) + + filter_input.textChanged.connect(proxy.setFilterFixedString) + refresh_btn.clicked.connect(self._on_refresh_clicked) + view.clicked.connect(self._on_activated) + view.customContextMenuRequested.connect(self.on_context_menu) + details_widget.save_triggered.connect(self._on_save) + + self._model = model + self._proxy = proxy + self._view = view + self._details_widget = details_widget + self._refresh_btn = refresh_btn + + def _on_refresh_clicked(self): + self.refresh() + + def _on_activated(self, index): + container = None + item_id = None + if index.isValid(): + item_id = index.data(ITEM_ID_ROLE) + container = self._model.get_instance_by_id(item_id) + + self._details_widget.set_details(container, item_id) + + def _on_save(self): + host = api.registered_host() + if not hasattr(host, "save_instances"): + print("BUG: Host does not have \"save_instances\" method") + return + + current_index = self._view.selectionModel().currentIndex() + if not current_index.isValid(): + return + + item_id = current_index.data(ITEM_ID_ROLE) + if item_id != self._details_widget.item_id(): + return + + item_data = self._details_widget.instance_data_from_text() + new_instances = [] + for index in iter_model_rows(self._model, 0): + _item_id = index.data(ITEM_ID_ROLE) + if _item_id == item_id: + instance_data = item_data + else: + instance_data = self._model.get_instance_by_id(item_id) + new_instances.append(instance_data) + + host.save_instances(new_instances) + + def on_context_menu(self, point): + point_index = self._view.indexAt(point) + item_id = point_index.data(ITEM_ID_ROLE) + instance_data = self._model.get_instance_by_id(item_id) + if instance_data is None: + return + + # Prepare menu + menu = QtWidgets.QMenu(self) + actions = [] + host = api.registered_host() + if hasattr(host, "remove_instance"): + action = QtWidgets.QAction("Remove instance", menu) + action.setData(host.remove_instance) + actions.append(action) + + if hasattr(host, "select_instance"): + action = QtWidgets.QAction("Select instance", menu) + action.setData(host.select_instance) + actions.append(action) + + if not actions: + actions.append(QtWidgets.QAction("* Nothing to do", menu)) + + for action in actions: + menu.addAction(action) + + # Show menu under mouse + global_point = self._view.mapToGlobal(point) + action = menu.exec_(global_point) + if not action or not action.data(): + return + + # Process action + # TODO catch exceptions + function = action.data() + function(instance_data) + + # Reset modified data + self.refresh() + + def refresh(self): + self._details_widget.set_details(None, None) + self._model.refresh() + + host = api.registered_host() + dev_mode = os.environ.get("AVALON_DEVELOP_MODE") or "" + editable = False + if dev_mode.lower() in ("1", "yes", "true", "on"): + editable = hasattr(host, "save_instances") + self._details_widget.set_editable(editable) + + def showEvent(self, *args, **kwargs): + super(SubsetManagerWindow, self).showEvent(*args, **kwargs) + if self._first_show: + self._first_show = False + self.setStyleSheet(style.load_stylesheet()) + self.refresh() + + +def show(root=None, debug=False, parent=None): + """Display Scene Inventory GUI + + Arguments: + debug (bool, optional): Run in debug-mode, + defaults to False + parent (QtCore.QObject, optional): When provided parent the interface + to this QObject. + + """ + + try: + module.window.close() + del module.window + except (RuntimeError, AttributeError): + pass + + with qt_app_context(): + window = SubsetManagerWindow(parent) + window.show() + + module.window = window + + # Pull window to the front. + module.window.raise_() + module.window.activateWindow() diff --git a/openpype/tools/utils/constants.py b/openpype/tools/utils/constants.py index 0e940a5595..33bdf43c08 100644 --- a/openpype/tools/utils/constants.py +++ b/openpype/tools/utils/constants.py @@ -7,4 +7,23 @@ PROJECT_IS_ACTIVE_ROLE = QtCore.Qt.UserRole + 102 TASK_NAME_ROLE = QtCore.Qt.UserRole + 301 TASK_TYPE_ROLE = QtCore.Qt.UserRole + 302 -TASK_ORDER_ROLE = QtCore.Qt.UserRole + 403 +TASK_ORDER_ROLE = QtCore.Qt.UserRole + 303 + +LOCAL_PROVIDER_ROLE = QtCore.Qt.UserRole + 500 # provider of active site +REMOTE_PROVIDER_ROLE = QtCore.Qt.UserRole + 501 # provider of remote site +LOCAL_PROGRESS_ROLE = QtCore.Qt.UserRole + 502 # percentage downld on active +REMOTE_PROGRESS_ROLE = QtCore.Qt.UserRole + 503 # percentage upload on remote +LOCAL_AVAILABILITY_ROLE = QtCore.Qt.UserRole + 504 # ratio of presence active +REMOTE_AVAILABILITY_ROLE = QtCore.Qt.UserRole + 505 +LOCAL_DATE_ROLE = QtCore.Qt.UserRole + 506 # created_dt on active site +REMOTE_DATE_ROLE = QtCore.Qt.UserRole + 507 +LOCAL_FAILED_ROLE = QtCore.Qt.UserRole + 508 +REMOTE_FAILED_ROLE = QtCore.Qt.UserRole + 509 +HEADER_NAME_ROLE = QtCore.Qt.UserRole + 510 +EDIT_ICON_ROLE = QtCore.Qt.UserRole + 511 +STATUS_ROLE = QtCore.Qt.UserRole + 512 +PATH_ROLE = QtCore.Qt.UserRole + 513 +LOCAL_SITE_NAME_ROLE = QtCore.Qt.UserRole + 514 +REMOTE_SITE_NAME_ROLE = QtCore.Qt.UserRole + 515 +ERROR_ROLE = QtCore.Qt.UserRole + 516 +TRIES_ROLE = QtCore.Qt.UserRole + 517 diff --git a/openpype/tools/utils/host_tools.py b/openpype/tools/utils/host_tools.py index a15d12b386..e87da7f0b4 100644 --- a/openpype/tools/utils/host_tools.py +++ b/openpype/tools/utils/host_tools.py @@ -133,22 +133,20 @@ class HostToolsHelper: def get_subset_manager_tool(self, parent): """Create, cache and return subset manager tool window.""" if self._subset_manager_tool is None: - from avalon.tools.subsetmanager import Window + from openpype.tools.subsetmanager import SubsetManagerWindow - subset_manager_window = Window(parent=parent or self._parent) + subset_manager_window = SubsetManagerWindow( + parent=parent or self._parent + ) self._subset_manager_tool = subset_manager_window return self._subset_manager_tool def show_subset_manager(self, parent=None): """Show tool display/remove existing created instances.""" - from avalon import style - subset_manager_tool = self.get_subset_manager_tool(parent) subset_manager_tool.show() - subset_manager_tool.setStyleSheet(style.load_stylesheet()) - # Pull window to the front. subset_manager_tool.raise_() subset_manager_tool.activateWindow() @@ -156,21 +154,20 @@ class HostToolsHelper: def get_scene_inventory_tool(self, parent): """Create, cache and return scene inventory tool window.""" if self._scene_inventory_tool is None: - from avalon.tools.sceneinventory.app import Window + from openpype.tools.sceneinventory import SceneInventoryWindow - scene_inventory_window = Window(parent=parent or self._parent) + scene_inventory_window = SceneInventoryWindow( + parent=parent or self._parent + ) self._scene_inventory_tool = scene_inventory_window return self._scene_inventory_tool def show_scene_inventory(self, parent=None): """Show tool maintain loaded containers.""" - from avalon import style - scene_inventory_tool = self.get_scene_inventory_tool(parent) scene_inventory_tool.show() scene_inventory_tool.refresh() - scene_inventory_tool.setStyleSheet(style.load_stylesheet()) # Pull window to the front. scene_inventory_tool.raise_() diff --git a/openpype/tools/utils/tasks_widget.py b/openpype/tools/utils/tasks_widget.py new file mode 100644 index 0000000000..9ffcde2885 --- /dev/null +++ b/openpype/tools/utils/tasks_widget.py @@ -0,0 +1,299 @@ +from Qt import QtWidgets, QtCore, QtGui + +from avalon import style +from avalon.vendor import qtawesome + +from .views import DeselectableTreeView +from .constants import ( + TASK_ORDER_ROLE, + TASK_TYPE_ROLE, + TASK_NAME_ROLE +) + + +class TasksModel(QtGui.QStandardItemModel): + """A model listing the tasks combined for a list of assets""" + def __init__(self, dbcon, parent=None): + super(TasksModel, self).__init__(parent=parent) + self.dbcon = dbcon + self.setHeaderData( + 0, QtCore.Qt.Horizontal, "Tasks", QtCore.Qt.DisplayRole + ) + self._default_icon = qtawesome.icon( + "fa.male", + color=style.colors.default + ) + self._no_tasks_icon = qtawesome.icon( + "fa.exclamation-circle", + color=style.colors.mid + ) + self._cached_icons = {} + self._project_task_types = {} + + self._empty_tasks_item = None + self._last_asset_id = None + self._loaded_project_name = None + + def _context_is_valid(self): + if self.dbcon.Session.get("AVALON_PROJECT"): + return True + return False + + def refresh(self): + self._refresh_task_types() + self.set_asset_id(self._last_asset_id) + + def _refresh_task_types(self): + # Get the project configured icons from database + task_types = {} + if self._context_is_valid(): + project = self.dbcon.find_one( + {"type": "project"}, + {"config.tasks"} + ) + task_types = project["config"].get("tasks") or task_types + self._project_task_types = task_types + + def _try_get_awesome_icon(self, icon_name): + icon = None + if icon_name: + try: + icon = qtawesome.icon( + "fa.{}".format(icon_name), + color=style.colors.default + ) + + except Exception: + pass + return icon + + def headerData(self, section, orientation, role=None): + if role is None: + role = QtCore.Qt.EditRole + # Show nice labels in the header + if section == 0: + if ( + role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole) + and orientation == QtCore.Qt.Horizontal + ): + return "Tasks" + + return super(TasksModel, self).headerData(section, orientation, role) + + def _get_icon(self, task_icon, task_type_icon): + if task_icon in self._cached_icons: + return self._cached_icons[task_icon] + + icon = self._try_get_awesome_icon(task_icon) + if icon is not None: + self._cached_icons[task_icon] = icon + return icon + + if task_type_icon in self._cached_icons: + icon = self._cached_icons[task_type_icon] + self._cached_icons[task_icon] = icon + return icon + + icon = self._try_get_awesome_icon(task_type_icon) + if icon is None: + icon = self._default_icon + + self._cached_icons[task_icon] = icon + self._cached_icons[task_type_icon] = icon + + return icon + + def set_asset_id(self, asset_id): + asset_doc = None + if self._context_is_valid(): + asset_doc = self.dbcon.find_one( + {"_id": asset_id}, + {"data.tasks": True} + ) + self._set_asset(asset_doc) + + def _get_empty_task_item(self): + if self._empty_tasks_item is None: + item = QtGui.QStandardItem("No task") + item.setData(self._no_tasks_icon, QtCore.Qt.DecorationRole) + item.setFlags(QtCore.Qt.NoItemFlags) + self._empty_tasks_item = item + return self._empty_tasks_item + + def _set_asset(self, asset_doc): + """Set assets to track by their database id + + Arguments: + asset_doc (dict): Asset document from MongoDB. + """ + asset_tasks = {} + self._last_asset_id = None + if asset_doc: + asset_tasks = asset_doc.get("data", {}).get("tasks") or {} + self._last_asset_id = asset_doc["_id"] + + root_item = self.invisibleRootItem() + root_item.removeRows(0, root_item.rowCount()) + + items = [] + for task_name, task_info in asset_tasks.items(): + task_icon = task_info.get("icon") + task_type = task_info.get("type") + task_order = task_info.get("order") + task_type_info = self._project_task_types.get(task_type) or {} + task_type_icon = task_type_info.get("icon") + icon = self._get_icon(task_icon, task_type_icon) + + label = "{} ({})".format(task_name, task_type or "type N/A") + item = QtGui.QStandardItem(label) + item.setData(task_name, TASK_NAME_ROLE) + item.setData(task_type, TASK_TYPE_ROLE) + item.setData(task_order, TASK_ORDER_ROLE) + item.setData(icon, QtCore.Qt.DecorationRole) + item.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable) + items.append(item) + + if not items: + item = QtGui.QStandardItem("No task") + item.setData(self._no_tasks_icon, QtCore.Qt.DecorationRole) + item.setFlags(QtCore.Qt.NoItemFlags) + items.append(item) + + root_item.appendRows(items) + + +class TasksProxyModel(QtCore.QSortFilterProxyModel): + def lessThan(self, x_index, y_index): + x_order = x_index.data(TASK_ORDER_ROLE) + y_order = y_index.data(TASK_ORDER_ROLE) + if x_order is not None and y_order is not None: + if x_order < y_order: + return True + if x_order > y_order: + return False + + elif x_order is None and y_order is not None: + return True + + elif y_order is None and x_order is not None: + return False + + x_name = x_index.data(QtCore.Qt.DisplayRole) + y_name = y_index.data(QtCore.Qt.DisplayRole) + if x_name == y_name: + return True + + if x_name == tuple(sorted((x_name, y_name)))[0]: + return True + return False + + +class TasksWidget(QtWidgets.QWidget): + """Widget showing active Tasks""" + + task_changed = QtCore.Signal() + + def __init__(self, dbcon, parent=None): + super(TasksWidget, self).__init__(parent) + + tasks_view = DeselectableTreeView(self) + tasks_view.setIndentation(0) + tasks_view.setSortingEnabled(True) + tasks_view.setEditTriggers(QtWidgets.QTreeView.NoEditTriggers) + + header_view = tasks_view.header() + header_view.setSortIndicator(0, QtCore.Qt.AscendingOrder) + + tasks_model = TasksModel(dbcon) + tasks_proxy = TasksProxyModel() + tasks_proxy.setSourceModel(tasks_model) + tasks_view.setModel(tasks_proxy) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(tasks_view) + + selection_model = tasks_view.selectionModel() + selection_model.selectionChanged.connect(self._on_task_change) + + self._tasks_model = tasks_model + self._tasks_proxy = tasks_proxy + self._tasks_view = tasks_view + + self._last_selected_task_name = None + + def refresh(self): + self._tasks_model.refresh() + + def set_asset_id(self, asset_id): + # Asset deselected + if asset_id is None: + return + + # Try and preserve the last selected task and reselect it + # after switching assets. If there's no currently selected + # asset keep whatever the "last selected" was prior to it. + current = self.get_selected_task_name() + if current: + self._last_selected_task_name = current + + self._tasks_model.set_asset_id(asset_id) + + if self._last_selected_task_name: + self.select_task_name(self._last_selected_task_name) + + # Force a task changed emit. + self.task_changed.emit() + + def select_task_name(self, task_name): + """Select a task by name. + + If the task does not exist in the current model then selection is only + cleared. + + Args: + task (str): Name of the task to select. + + """ + task_view_model = self._tasks_view.model() + if not task_view_model: + return + + # Clear selection + selection_model = self._tasks_view.selectionModel() + selection_model.clearSelection() + + # Select the task + mode = selection_model.Select | selection_model.Rows + for row in range(task_view_model.rowCount()): + index = task_view_model.index(row, 0) + name = index.data(TASK_NAME_ROLE) + if name == task_name: + selection_model.select(index, mode) + + # Set the currently active index + self._tasks_view.setCurrentIndex(index) + break + + def get_selected_task_name(self): + """Return name of task at current index (selected) + + Returns: + str: Name of the current task. + + """ + index = self._tasks_view.currentIndex() + selection_model = self._tasks_view.selectionModel() + if index.isValid() and selection_model.isSelected(index): + return index.data(TASK_NAME_ROLE) + return None + + def get_selected_task_type(self): + index = self._tasks_view.currentIndex() + selection_model = self._tasks_view.selectionModel() + if index.isValid() and selection_model.isSelected(index): + return index.data(TASK_TYPE_ROLE) + return None + + def _on_task_change(self): + self.task_changed.emit() diff --git a/openpype/tools/workfiles/app.py b/openpype/tools/workfiles/app.py index 4135eeccc9..edea7bb1e0 100644 --- a/openpype/tools/workfiles/app.py +++ b/openpype/tools/workfiles/app.py @@ -15,16 +15,9 @@ from openpype.tools.utils.lib import ( schedule, qt_app_context ) from openpype.tools.utils.widgets import AssetWidget +from openpype.tools.utils.tasks_widget import TasksWidget from openpype.tools.utils.delegates import PrettyTimeDelegate -from openpype.tools.utils.constants import ( - TASK_NAME_ROLE, - TASK_TYPE_ROLE -) -from openpype.tools.utils.models import ( - TasksModel, - TasksProxyModel -) from .model import FilesModel from .view import FilesView @@ -323,110 +316,6 @@ class NameWindow(QtWidgets.QDialog): ) -class TasksWidget(QtWidgets.QWidget): - """Widget showing active Tasks""" - - task_changed = QtCore.Signal() - - def __init__(self, dbcon=None, parent=None): - super(TasksWidget, self).__init__(parent) - - tasks_view = QtWidgets.QTreeView(self) - tasks_view.setIndentation(0) - tasks_view.setSortingEnabled(True) - if dbcon is None: - dbcon = io - - tasks_model = TasksModel(dbcon) - tasks_proxy = TasksProxyModel() - tasks_proxy.setSourceModel(tasks_model) - tasks_view.setModel(tasks_proxy) - - layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.addWidget(tasks_view) - - selection_model = tasks_view.selectionModel() - selection_model.currentChanged.connect(self.task_changed) - - self._tasks_model = tasks_model - self._tasks_proxy = tasks_proxy - self._tasks_view = tasks_view - - self._last_selected_task = None - - def set_asset(self, asset_doc): - # Asset deselected - if asset_doc is None: - return - - # Try and preserve the last selected task and reselect it - # after switching assets. If there's no currently selected - # asset keep whatever the "last selected" was prior to it. - current = self.get_current_task_name() - if current: - self._last_selected_task = current - - self._tasks_model.set_asset(asset_doc) - self._tasks_proxy.sort(0, QtCore.Qt.AscendingOrder) - - if self._last_selected_task: - self.select_task(self._last_selected_task) - - # Force a task changed emit. - self.task_changed.emit() - - def select_task(self, task_name): - """Select a task by name. - - If the task does not exist in the current model then selection is only - cleared. - - Args: - task (str): Name of the task to select. - - """ - task_view_model = self._tasks_view.model() - if not task_view_model: - return - - # Clear selection - selection_model = self._tasks_view.selectionModel() - selection_model.clearSelection() - - # Select the task - mode = selection_model.Select | selection_model.Rows - for row in range(task_view_model.rowCount()): - index = task_view_model.index(row, 0) - name = index.data(TASK_NAME_ROLE) - if name == task_name: - selection_model.select(index, mode) - - # Set the currently active index - self._tasks_view.setCurrentIndex(index) - break - - def get_current_task_name(self): - """Return name of task at current index (selected) - - Returns: - str: Name of the current task. - - """ - index = self._tasks_view.currentIndex() - selection_model = self._tasks_view.selectionModel() - if index.isValid() and selection_model.isSelected(index): - return index.data(TASK_NAME_ROLE) - return None - - def get_current_task_type(self): - index = self._tasks_view.currentIndex() - selection_model = self._tasks_view.selectionModel() - if index.isValid() and selection_model.isSelected(index): - return index.data(TASK_TYPE_ROLE) - return None - - class FilesWidget(QtWidgets.QWidget): """A widget displaying files that allows to save and open files.""" file_selected = QtCore.Signal(str) @@ -1052,7 +941,7 @@ class Window(QtWidgets.QMainWindow): if asset_docs: asset_doc = asset_docs[0] - task_name = self.tasks_widget.get_current_task_name() + task_name = self.tasks_widget.get_selected_task_name() workfile_doc = None if asset_doc and task_name and filepath: @@ -1082,7 +971,7 @@ class Window(QtWidgets.QMainWindow): def _get_current_workfile_doc(self, filepath=None): if filepath is None: filepath = self.files_widget._get_selected_filepath() - task_name = self.tasks_widget.get_current_task_name() + task_name = self.tasks_widget.get_selected_task_name() asset_docs = self.assets_widget.get_selected_assets() if not task_name or not asset_docs or not filepath: return @@ -1113,18 +1002,16 @@ class Window(QtWidgets.QMainWindow): "name": asset, "type": "asset" }, - { - "data.tasks": 1 - } - ) + {"_id": 1} + ) or {} # Select the asset self.assets_widget.select_assets([asset], expand=True) - self.tasks_widget.set_asset(asset_document) + self.tasks_widget.set_asset_id(asset_document.get("_id")) if "task" in context: - self.tasks_widget.select_task(context["task"]) + self.tasks_widget.select_task_name(context["task"]) def refresh(self): # Refresh asset widget @@ -1134,7 +1021,7 @@ class Window(QtWidgets.QMainWindow): def _on_asset_changed(self): asset = self.assets_widget.get_selected_assets() or None - + asset_id = None if not asset: # Force disable the other widgets if no # active selection @@ -1142,16 +1029,17 @@ class Window(QtWidgets.QMainWindow): self.files_widget.setEnabled(False) else: asset = asset[0] + asset_id = asset.get("_id") self.tasks_widget.setEnabled(True) - self.tasks_widget.set_asset(asset) + self.tasks_widget.set_asset_id(asset_id) def _on_task_changed(self): asset = self.assets_widget.get_selected_assets() or None if asset is not None: asset = asset[0] - task_name = self.tasks_widget.get_current_task_name() - task_type = self.tasks_widget.get_current_task_type() + task_name = self.tasks_widget.get_selected_task_name() + task_type = self.tasks_widget.get_selected_task_type() self.tasks_widget.setEnabled(bool(asset)) diff --git a/openpype/vendor/python/common/capture.py b/openpype/vendor/python/common/capture.py index 02d86952df..6b4c40a6e8 100644 --- a/openpype/vendor/python/common/capture.py +++ b/openpype/vendor/python/common/capture.py @@ -116,6 +116,8 @@ def capture(camera=None, if not cmds.objExists(camera): raise RuntimeError("Camera does not exist: {0}".format(camera)) + if width and height : + maintain_aspect_ratio = False width = width or cmds.getAttr("defaultResolution.width") height = height or cmds.getAttr("defaultResolution.height") if maintain_aspect_ratio: diff --git a/openpype/version.py b/openpype/version.py index 2f8cb42dc3..ef4bbe505b 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.6.0-nightly.4" +__version__ = "3.6.2-nightly.1" diff --git a/poetry.lock b/poetry.lock index 36105f4213..c07a20253c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -80,7 +80,7 @@ python-dateutil = ">=2.7.0" [[package]] name = "astroid" -version = "2.5.6" +version = "2.8.4" description = "An abstract syntax tree for Python with inference support." category = "dev" optional = false @@ -89,7 +89,8 @@ python-versions = "~=3.6" [package.dependencies] lazy-object-proxy = ">=1.4.0" typed-ast = {version = ">=1.4.0,<1.5", markers = "implementation_name == \"cpython\" and python_version < \"3.8\""} -wrapt = ">=1.11,<1.13" +typing-extensions = {version = ">=3.10", markers = "python_version < \"3.10\""} +wrapt = ">=1.11,<1.14" [[package]] name = "async-timeout" @@ -162,20 +163,20 @@ typecheck = ["mypy"] [[package]] name = "blessed" -version = "1.18.0" +version = "1.19.0" description = "Easy, practical library for making terminal apps, by providing an elegant, well-documented interface to Colors, Keyboard input, and screen Positioning capabilities." category = "main" optional = false -python-versions = "*" +python-versions = ">=2.7" [package.dependencies] -jinxed = {version = ">=0.5.4", markers = "platform_system == \"Windows\""} +jinxed = {version = ">=1.1.0", markers = "platform_system == \"Windows\""} six = ">=1.9.0" wcwidth = ">=0.1.4" [[package]] name = "cachetools" -version = "4.2.2" +version = "4.2.4" description = "Extensible memoizing collections and decorators" category = "main" optional = false @@ -183,7 +184,7 @@ python-versions = "~=3.5" [[package]] name = "certifi" -version = "2021.5.30" +version = "2021.10.8" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false @@ -191,7 +192,7 @@ python-versions = "*" [[package]] name = "cffi" -version = "1.14.5" +version = "1.15.0" description = "Foreign Function Interface for Python calling C code." category = "main" optional = false @@ -258,18 +259,21 @@ python-versions = "*" [[package]] name = "coverage" -version = "5.5" +version = "6.0.2" description = "Code coverage measurement for Python" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +python-versions = ">=3.6" + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "extra == \"toml\""} [package.extras] -toml = ["toml"] +toml = ["tomli"] [[package]] name = "cryptography" -version = "3.4.7" +version = "35.0.0" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." category = "main" optional = false @@ -282,9 +286,9 @@ cffi = ">=1.12" docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] -sdist = ["setuptools-rust (>=0.11.4)"] +sdist = ["setuptools_rust (>=0.11.4)"] ssh = ["bcrypt (>=3.1.5)"] -test = ["pytest (>=6.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] +test = ["pytest (>=6.2.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] [[package]] name = "cx-freeze" @@ -328,7 +332,7 @@ trio = ["trio (>=0.14.0)", "sniffio (>=1.1)"] [[package]] name = "docutils" -version = "0.16" +version = "0.18" description = "Docutils -- Python Documentation Utilities" category = "dev" optional = false @@ -336,7 +340,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "dropbox" -version = "11.20.0" +version = "11.22.0" description = "Official Dropbox API Client" category = "main" optional = false @@ -409,30 +413,30 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "gitdb" -version = "4.0.7" +version = "4.0.9" description = "Git Object Database" category = "dev" optional = false -python-versions = ">=3.4" +python-versions = ">=3.6" [package.dependencies] -smmap = ">=3.0.1,<5" +smmap = ">=3.0.1,<6" [[package]] name = "gitpython" -version = "3.1.17" -description = "Python Git Library" +version = "3.1.24" +description = "GitPython is a python library used to interact with Git repositories" category = "dev" optional = false -python-versions = ">=3.5" +python-versions = ">=3.7" [package.dependencies] gitdb = ">=4.0.1,<5" -typing-extensions = {version = ">=3.7.4.0", markers = "python_version < \"3.8\""} +typing-extensions = {version = ">=3.7.4.3", markers = "python_version < \"3.10\""} [[package]] name = "google-api-core" -version = "1.30.0" +version = "1.31.3" description = "Google API client core library" category = "main" optional = false @@ -442,7 +446,7 @@ python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" google-auth = ">=1.25.0,<2.0dev" googleapis-common-protos = ">=1.6.0,<2.0dev" packaging = ">=14.3" -protobuf = ">=3.12.0" +protobuf = ">=3.12.0,<3.18.0" pytz = "*" requests = ">=2.18.0,<3.0.0dev" six = ">=1.13.0" @@ -470,7 +474,7 @@ uritemplate = ">=3.0.0,<4dev" [[package]] name = "google-auth" -version = "1.31.0" +version = "1.35.0" description = "Google Authentication Library" category = "main" optional = false @@ -516,11 +520,11 @@ grpc = ["grpcio (>=1.0.0)"] [[package]] name = "httplib2" -version = "0.19.1" +version = "0.20.1" description = "A comprehensive HTTP client library." category = "main" optional = false -python-versions = "*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] pyparsing = ">=2.4.2,<3" @@ -543,7 +547,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "importlib-metadata" -version = "4.5.0" +version = "4.8.1" description = "Read metadata from Python packages" category = "main" optional = false @@ -555,7 +559,8 @@ zipp = ">=0.5" [package.extras] docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -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)"] +perf = ["ipython"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] [[package]] name = "iniconfig" @@ -567,16 +572,17 @@ python-versions = "*" [[package]] name = "isort" -version = "5.8.0" +version = "5.9.3" description = "A Python utility / library to sort Python imports." category = "dev" optional = false -python-versions = ">=3.6,<4.0" +python-versions = ">=3.6.1,<4.0" [package.extras] pipfile_deprecated_finder = ["pipreqs", "requirementslib"] requirements_deprecated_finder = ["pipreqs", "pip-api"] colors = ["colorama (>=0.4.3,<0.5.0)"] +plugins = ["setuptools"] [[package]] name = "jedi" @@ -594,14 +600,15 @@ testing = ["colorama", "docopt", "pytest (>=3.1.0)"] [[package]] name = "jeepney" -version = "0.6.0" +version = "0.7.1" description = "Low-level, pure Python DBus protocol wrapper." category = "main" optional = false python-versions = ">=3.6" [package.extras] -test = ["pytest", "pytest-trio", "pytest-asyncio", "testpath", "trio"] +test = ["pytest", "pytest-trio", "pytest-asyncio", "testpath", "trio", "async-timeout"] +trio = ["trio", "async-generator"] [[package]] name = "jinja2" @@ -701,7 +708,7 @@ python-versions = "*" [[package]] name = "multidict" -version = "5.1.0" +version = "5.2.0" description = "multidict implementation" category = "main" optional = false @@ -729,18 +736,18 @@ reference = "openpype" [[package]] name = "packaging" -version = "20.9" +version = "21.2" description = "Core utilities for Python packages" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6" [package.dependencies] -pyparsing = ">=2.0.2" +pyparsing = ">=2.0.2,<3" [[package]] name = "paramiko" -version = "2.7.2" +version = "2.8.0" description = "SSH2 protocol library" category = "main" optional = false @@ -771,7 +778,7 @@ testing = ["docopt", "pytest (<6.0.0)"] [[package]] name = "pathlib2" -version = "2.3.5" +version = "2.3.6" description = "Object-oriented filesystem paths" category = "main" optional = false @@ -782,25 +789,38 @@ six = "*" [[package]] name = "pillow" -version = "8.3.2" +version = "8.4.0" description = "Python Imaging Library (Fork)" category = "main" optional = false python-versions = ">=3.6" +[[package]] +name = "platformdirs" +version = "2.4.0" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] +test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] + [[package]] name = "pluggy" -version = "0.13.1" +version = "1.0.0" description = "plugin and hook calling mechanisms for python" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6" [package.dependencies] importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} [package.extras] dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] [[package]] name = "ply" @@ -910,7 +930,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pygments" -version = "2.9.0" +version = "2.10.0" description = "Pygments is a syntax highlighting package written in Python." category = "dev" optional = false @@ -918,22 +938,24 @@ python-versions = ">=3.5" [[package]] name = "pylint" -version = "2.8.3" +version = "2.11.1" description = "python code static checker" category = "dev" optional = false python-versions = "~=3.6" [package.dependencies] -astroid = "2.5.6" +astroid = ">=2.8.0,<2.9" colorama = {version = "*", markers = "sys_platform == \"win32\""} isort = ">=4.2.5,<6" mccabe = ">=0.6,<0.7" +platformdirs = ">=2.2.0" toml = ">=0.7.1" +typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} [[package]] name = "pymongo" -version = "3.11.4" +version = "3.12.1" description = "Python driver for MongoDB " category = "main" optional = false @@ -941,9 +963,9 @@ python-versions = "*" [package.extras] aws = ["pymongo-auth-aws (<2.0.0)"] -encryption = ["pymongocrypt (<2.0.0)"] +encryption = ["pymongocrypt (>=1.1.0,<2.0.0)"] gssapi = ["pykerberos"] -ocsp = ["pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)"] +ocsp = ["pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)", "certifi"] snappy = ["python-snappy"] srv = ["dnspython (>=1.16.0,<1.17.0)"] tls = ["ipaddress"] @@ -967,7 +989,7 @@ tests = ["pytest (>=3.2.1,!=3.3.0)", "hypothesis (>=3.27.0)"] [[package]] name = "pynput" -version = "1.7.3" +version = "1.7.4" description = "Monitor and control user input devices" category = "main" optional = false @@ -975,7 +997,8 @@ python-versions = "*" [package.dependencies] evdev = {version = ">=1.3", markers = "sys_platform in \"linux\""} -pyobjc-framework-Quartz = {version = ">=7.0", markers = "sys_platform == \"darwin\""} +pyobjc-framework-ApplicationServices = {version = ">=7.3", markers = "sys_platform == \"darwin\""} +pyobjc-framework-Quartz = {version = ">=7.3", markers = "sys_platform == \"darwin\""} python-xlib = {version = ">=0.17", markers = "sys_platform in \"linux\""} six = "*" @@ -987,6 +1010,19 @@ category = "main" optional = false python-versions = ">=3.6" +[[package]] +name = "pyobjc-framework-applicationservices" +version = "7.3" +description = "Wrappers for the framework ApplicationServices on macOS" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pyobjc-core = ">=7.3" +pyobjc-framework-Cocoa = ">=7.3" +pyobjc-framework-Quartz = ">=7.3" + [[package]] name = "pyobjc-framework-cocoa" version = "7.3" @@ -1020,11 +1056,11 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "pyrsistent" -version = "0.17.3" +version = "0.18.0" description = "Persistent/Functional/Immutable data structures" category = "main" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" [[package]] name = "pysftp" @@ -1039,7 +1075,7 @@ paramiko = ">=1.17" [[package]] name = "pytest" -version = "6.2.4" +version = "6.2.5" description = "pytest: simple powerful testing with Python" category = "dev" optional = false @@ -1052,7 +1088,7 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""} importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} iniconfig = "*" packaging = "*" -pluggy = ">=0.12,<1.0.0a1" +pluggy = ">=0.12,<2.0" py = ">=1.8.2" toml = "*" @@ -1061,37 +1097,36 @@ testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xm [[package]] name = "pytest-cov" -version = "2.12.1" +version = "3.0.0" description = "Pytest plugin for measuring coverage." category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.6" [package.dependencies] -coverage = ">=5.2.1" +coverage = {version = ">=5.2.1", extras = ["toml"]} pytest = ">=4.6" -toml = "*" [package.extras] testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"] [[package]] name = "pytest-print" -version = "0.2.1" +version = "0.3.0" description = "pytest-print adds the printer fixture you can use to print messages to the user (directly to the pytest runner, not stdout)" category = "dev" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +python-versions = ">=3.6" [package.dependencies] -pytest = ">=3.0.0" +pytest = ">=6" [package.extras] -test = ["coverage (>=5)", "pytest (>=4)"] +test = ["coverage (>=5)"] [[package]] name = "python-dateutil" -version = "2.8.1" +version = "2.8.2" description = "Extensions to the standard Python datetime module" category = "main" optional = false @@ -1102,7 +1137,7 @@ six = ">=1.5" [[package]] name = "python-xlib" -version = "0.30" +version = "0.31" description = "Python X Library" category = "main" optional = false @@ -1121,7 +1156,7 @@ python-versions = "*" [[package]] name = "pytz" -version = "2021.1" +version = "2021.3" description = "World timezone definitions, modern and historical" category = "main" optional = false @@ -1145,7 +1180,7 @@ python-versions = "*" [[package]] name = "qt.py" -version = "1.3.3" +version = "1.3.6" description = "Python 2 & 3 compatibility wrapper around all Qt bindings - PySide, PySide2, PyQt4 and PyQt5." category = "main" optional = false @@ -1223,23 +1258,23 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "slack-sdk" -version = "3.6.0" +version = "3.11.2" description = "The Slack API Platform SDK for Python" category = "main" optional = false python-versions = ">=3.6.0" [package.extras] -optional = ["aiodns (>1.0)", "aiohttp (>=3.7.3,<4)", "boto3 (<=2)", "SQLAlchemy (>=1,<2)", "websockets (>=9.1,<10)", "websocket-client (>=0.57,<1)"] -testing = ["pytest (>=5.4,<6)", "pytest-asyncio (<1)", "Flask-Sockets (>=0.2,<1)", "pytest-cov (>=2,<3)", "codecov (>=2,<3)", "flake8 (>=3,<4)", "black (==21.5b1)", "psutil (>=5,<6)", "databases (>=0.3)"] +optional = ["aiodns (>1.0)", "aiohttp (>=3.7.3,<4)", "boto3 (<=2)", "SQLAlchemy (>=1,<2)", "websockets (>=9.1,<10)", "websocket-client (>=1,<2)"] +testing = ["pytest (>=5.4,<6)", "pytest-asyncio (<1)", "Flask-Sockets (>=0.2,<1)", "Flask (>=1,<2)", "Werkzeug (<2)", "pytest-cov (>=2,<3)", "codecov (>=2,<3)", "flake8 (>=3,<4)", "black (==21.9b0)", "psutil (>=5,<6)", "databases (>=0.3)", "boto3 (<=2)", "moto (<2)"] [[package]] name = "smmap" -version = "4.0.0" +version = "5.0.0" description = "A pure Python implementation of a sliding window memory map manager" category = "dev" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" [[package]] name = "snowballstemmer" @@ -1259,17 +1294,17 @@ python-versions = "*" [[package]] name = "sphinx" -version = "4.0.2" +version = "3.5.3" description = "Python documentation generator" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.5" [package.dependencies] alabaster = ">=0.7,<0.8" babel = ">=1.3" colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""} -docutils = ">=0.14,<0.18" +docutils = ">=0.12" imagesize = "*" Jinja2 = ">=2.3" packaging = "*" @@ -1302,14 +1337,13 @@ sphinx = "*" [[package]] name = "sphinx-rtd-theme" -version = "0.5.2" +version = "0.5.1" description = "Read the Docs theme for Sphinx" category = "dev" optional = false python-versions = "*" [package.dependencies] -docutils = "<0.17" sphinx = "*" [package.extras] @@ -1429,6 +1463,14 @@ category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +[[package]] +name = "tomli" +version = "1.2.2" +description = "A lil' TOML parser" +category = "dev" +optional = false +python-versions = ">=3.6" + [[package]] name = "typed-ast" version = "1.4.3" @@ -1439,7 +1481,7 @@ python-versions = "*" [[package]] name = "typing-extensions" -version = "3.10.0.0" +version = "3.10.0.2" description = "Backported and Experimental Type Hints for Python 3.5+" category = "main" optional = false @@ -1455,7 +1497,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "urllib3" -version = "1.26.5" +version = "1.26.7" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false @@ -1487,11 +1529,11 @@ six = "*" [[package]] name = "wrapt" -version = "1.12.1" +version = "1.13.2" description = "Module for decorators, wrappers and monkey patching." category = "dev" optional = false -python-versions = "*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [[package]] name = "wsrpc-aiohttp" @@ -1512,7 +1554,7 @@ ujson = ["ujson"] [[package]] name = "yarl" -version = "1.6.3" +version = "1.7.0" description = "Yet another URL library" category = "main" optional = false @@ -1525,7 +1567,7 @@ typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} [[package]] name = "zipp" -version = "3.4.1" +version = "3.6.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "main" optional = false @@ -1533,7 +1575,7 @@ python-versions = ">=3.6" [package.extras] docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] [metadata] lock-version = "1.1" @@ -1602,8 +1644,8 @@ arrow = [ {file = "arrow-0.17.0.tar.gz", hash = "sha256:ff08d10cda1d36c68657d6ad20d74fbea493d980f8b2d45344e00d6ed2bf6ed4"}, ] astroid = [ - {file = "astroid-2.5.6-py3-none-any.whl", hash = "sha256:4db03ab5fc3340cf619dbc25e42c2cc3755154ce6009469766d7143d1fc2ee4e"}, - {file = "astroid-2.5.6.tar.gz", hash = "sha256:8a398dfce302c13f14bab13e2b14fe385d32b73f4e4853b9bdfb64598baa1975"}, + {file = "astroid-2.8.4-py3-none-any.whl", hash = "sha256:0755c998e7117078dcb7d0bda621391dd2a85da48052d948c7411ab187325346"}, + {file = "astroid-2.8.4.tar.gz", hash = "sha256:1e83a69fd51b013ebf5912d26b9338d6643a55fec2f20c787792680610eed4a2"}, ] async-timeout = [ {file = "async-timeout-3.0.1.tar.gz", hash = "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f"}, @@ -1635,67 +1677,68 @@ bcrypt = [ {file = "bcrypt-3.2.0.tar.gz", hash = "sha256:5b93c1726e50a93a033c36e5ca7fdcd29a5c7395af50a6892f5d9e7c6cfbfb29"}, ] blessed = [ - {file = "blessed-1.18.0-py2.py3-none-any.whl", hash = "sha256:5b5e2f0563d5a668c282f3f5946f7b1abb70c85829461900e607e74d7725106e"}, - {file = "blessed-1.18.0.tar.gz", hash = "sha256:1312879f971330a1b7f2c6341f2ae7e2cbac244bfc9d0ecfbbecd4b0293bc755"}, + {file = "blessed-1.19.0-py2.py3-none-any.whl", hash = "sha256:1f2d462631b2b6d2d4c3c65b54ef79ad87a6ca2dd55255df2f8d739fcc8a1ddb"}, + {file = "blessed-1.19.0.tar.gz", hash = "sha256:4db0f94e5761aea330b528e84a250027ffe996b5a94bf03e502600c9a5ad7a61"}, ] cachetools = [ - {file = "cachetools-4.2.2-py3-none-any.whl", hash = "sha256:2cc0b89715337ab6dbba85b5b50effe2b0c74e035d83ee8ed637cf52f12ae001"}, - {file = "cachetools-4.2.2.tar.gz", hash = "sha256:61b5ed1e22a0924aed1d23b478f37e8d52549ff8a961de2909c69bf950020cff"}, + {file = "cachetools-4.2.4-py3-none-any.whl", hash = "sha256:92971d3cb7d2a97efff7c7bb1657f21a8f5fb309a37530537c71b1774189f2d1"}, + {file = "cachetools-4.2.4.tar.gz", hash = "sha256:89ea6f1b638d5a73a4f9226be57ac5e4f399d22770b92355f92dcb0f7f001693"}, ] certifi = [ - {file = "certifi-2021.5.30-py2.py3-none-any.whl", hash = "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"}, - {file = "certifi-2021.5.30.tar.gz", hash = "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee"}, + {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, + {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, ] cffi = [ - {file = "cffi-1.14.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991"}, - {file = "cffi-1.14.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1"}, - {file = "cffi-1.14.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa"}, - {file = "cffi-1.14.5-cp27-cp27m-win32.whl", hash = "sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3"}, - {file = "cffi-1.14.5-cp27-cp27m-win_amd64.whl", hash = "sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5"}, - {file = "cffi-1.14.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482"}, - {file = "cffi-1.14.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6"}, - {file = "cffi-1.14.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045"}, - {file = "cffi-1.14.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa"}, - {file = "cffi-1.14.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406"}, - {file = "cffi-1.14.5-cp35-cp35m-win32.whl", hash = "sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369"}, - {file = "cffi-1.14.5-cp35-cp35m-win_amd64.whl", hash = "sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315"}, - {file = "cffi-1.14.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892"}, - {file = "cffi-1.14.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058"}, - {file = "cffi-1.14.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5"}, - {file = "cffi-1.14.5-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132"}, - {file = "cffi-1.14.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24ec4ff2c5c0c8f9c6b87d5bb53555bf267e1e6f70e52e5a9740d32861d36b6f"}, - {file = "cffi-1.14.5-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c3f39fa737542161d8b0d680df2ec249334cd70a8f420f71c9304bd83c3cbed"}, - {file = "cffi-1.14.5-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:681d07b0d1e3c462dd15585ef5e33cb021321588bebd910124ef4f4fb71aef55"}, - {file = "cffi-1.14.5-cp36-cp36m-win32.whl", hash = "sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53"}, - {file = "cffi-1.14.5-cp36-cp36m-win_amd64.whl", hash = "sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813"}, - {file = "cffi-1.14.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73"}, - {file = "cffi-1.14.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06"}, - {file = "cffi-1.14.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1"}, - {file = "cffi-1.14.5-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49"}, - {file = "cffi-1.14.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06d7cd1abac2ffd92e65c0609661866709b4b2d82dd15f611e602b9b188b0b69"}, - {file = "cffi-1.14.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f861a89e0043afec2a51fd177a567005847973be86f709bbb044d7f42fc4e05"}, - {file = "cffi-1.14.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc5a8e069b9ebfa22e26d0e6b97d6f9781302fe7f4f2b8776c3e1daea35f1adc"}, - {file = "cffi-1.14.5-cp37-cp37m-win32.whl", hash = "sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62"}, - {file = "cffi-1.14.5-cp37-cp37m-win_amd64.whl", hash = "sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4"}, - {file = "cffi-1.14.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053"}, - {file = "cffi-1.14.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0"}, - {file = "cffi-1.14.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e"}, - {file = "cffi-1.14.5-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827"}, - {file = "cffi-1.14.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:04c468b622ed31d408fea2346bec5bbffba2cc44226302a0de1ade9f5ea3d373"}, - {file = "cffi-1.14.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:06db6321b7a68b2bd6df96d08a5adadc1fa0e8f419226e25b2a5fbf6ccc7350f"}, - {file = "cffi-1.14.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:293e7ea41280cb28c6fcaaa0b1aa1f533b8ce060b9e701d78511e1e6c4a1de76"}, - {file = "cffi-1.14.5-cp38-cp38-win32.whl", hash = "sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e"}, - {file = "cffi-1.14.5-cp38-cp38-win_amd64.whl", hash = "sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396"}, - {file = "cffi-1.14.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea"}, - {file = "cffi-1.14.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322"}, - {file = "cffi-1.14.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c"}, - {file = "cffi-1.14.5-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee"}, - {file = "cffi-1.14.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bf1ac1984eaa7675ca8d5745a8cb87ef7abecb5592178406e55858d411eadc0"}, - {file = "cffi-1.14.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:df5052c5d867c1ea0b311fb7c3cd28b19df469c056f7fdcfe88c7473aa63e333"}, - {file = "cffi-1.14.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:24a570cd11895b60829e941f2613a4f79df1a27344cbbb82164ef2e0116f09c7"}, - {file = "cffi-1.14.5-cp39-cp39-win32.whl", hash = "sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396"}, - {file = "cffi-1.14.5-cp39-cp39-win_amd64.whl", hash = "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d"}, - {file = "cffi-1.14.5.tar.gz", hash = "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c"}, + {file = "cffi-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962"}, + {file = "cffi-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0"}, + {file = "cffi-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14"}, + {file = "cffi-1.15.0-cp27-cp27m-win32.whl", hash = "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474"}, + {file = "cffi-1.15.0-cp27-cp27m-win_amd64.whl", hash = "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6"}, + {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27"}, + {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023"}, + {file = "cffi-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2"}, + {file = "cffi-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e"}, + {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7"}, + {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3"}, + {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c"}, + {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962"}, + {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382"}, + {file = "cffi-1.15.0-cp310-cp310-win32.whl", hash = "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55"}, + {file = "cffi-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0"}, + {file = "cffi-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e"}, + {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39"}, + {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc"}, + {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032"}, + {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8"}, + {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605"}, + {file = "cffi-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e"}, + {file = "cffi-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc"}, + {file = "cffi-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636"}, + {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4"}, + {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997"}, + {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b"}, + {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2"}, + {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7"}, + {file = "cffi-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66"}, + {file = "cffi-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029"}, + {file = "cffi-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880"}, + {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20"}, + {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024"}, + {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e"}, + {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728"}, + {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6"}, + {file = "cffi-1.15.0-cp38-cp38-win32.whl", hash = "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c"}, + {file = "cffi-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443"}, + {file = "cffi-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a"}, + {file = "cffi-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37"}, + {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a"}, + {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e"}, + {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796"}, + {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df"}, + {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8"}, + {file = "cffi-1.15.0-cp39-cp39-win32.whl", hash = "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a"}, + {file = "cffi-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139"}, + {file = "cffi-1.15.0.tar.gz", hash = "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954"}, ] chardet = [ {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, @@ -1722,74 +1765,61 @@ coolname = [ {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"}, - {file = "coverage-5.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669"}, - {file = "coverage-5.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90"}, - {file = "coverage-5.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c"}, - {file = "coverage-5.5-cp27-cp27m-win32.whl", hash = "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a"}, - {file = "coverage-5.5-cp27-cp27m-win_amd64.whl", hash = "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82"}, - {file = "coverage-5.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905"}, - {file = "coverage-5.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083"}, - {file = "coverage-5.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5"}, - {file = "coverage-5.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81"}, - {file = "coverage-5.5-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6"}, - {file = "coverage-5.5-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0"}, - {file = "coverage-5.5-cp310-cp310-win_amd64.whl", hash = "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae"}, - {file = "coverage-5.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb"}, - {file = "coverage-5.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160"}, - {file = "coverage-5.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6"}, - {file = "coverage-5.5-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701"}, - {file = "coverage-5.5-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793"}, - {file = "coverage-5.5-cp35-cp35m-win32.whl", hash = "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e"}, - {file = "coverage-5.5-cp35-cp35m-win_amd64.whl", hash = "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3"}, - {file = "coverage-5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066"}, - {file = "coverage-5.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a"}, - {file = "coverage-5.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465"}, - {file = "coverage-5.5-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb"}, - {file = "coverage-5.5-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821"}, - {file = "coverage-5.5-cp36-cp36m-win32.whl", hash = "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45"}, - {file = "coverage-5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184"}, - {file = "coverage-5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a"}, - {file = "coverage-5.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53"}, - {file = "coverage-5.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d"}, - {file = "coverage-5.5-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638"}, - {file = "coverage-5.5-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3"}, - {file = "coverage-5.5-cp37-cp37m-win32.whl", hash = "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a"}, - {file = "coverage-5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a"}, - {file = "coverage-5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6"}, - {file = "coverage-5.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2"}, - {file = "coverage-5.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759"}, - {file = "coverage-5.5-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873"}, - {file = "coverage-5.5-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a"}, - {file = "coverage-5.5-cp38-cp38-win32.whl", hash = "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6"}, - {file = "coverage-5.5-cp38-cp38-win_amd64.whl", hash = "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502"}, - {file = "coverage-5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b"}, - {file = "coverage-5.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529"}, - {file = "coverage-5.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b"}, - {file = "coverage-5.5-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff"}, - {file = "coverage-5.5-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b"}, - {file = "coverage-5.5-cp39-cp39-win32.whl", hash = "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6"}, - {file = "coverage-5.5-cp39-cp39-win_amd64.whl", hash = "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03"}, - {file = "coverage-5.5-pp36-none-any.whl", hash = "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079"}, - {file = "coverage-5.5-pp37-none-any.whl", hash = "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4"}, - {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"}, + {file = "coverage-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1549e1d08ce38259de2bc3e9a0d5f3642ff4a8f500ffc1b2df73fd621a6cdfc0"}, + {file = "coverage-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcae10fccb27ca2a5f456bf64d84110a5a74144be3136a5e598f9d9fb48c0caa"}, + {file = "coverage-6.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:53a294dc53cfb39c74758edaa6305193fb4258a30b1f6af24b360a6c8bd0ffa7"}, + {file = "coverage-6.0.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8251b37be1f2cd9c0e5ccd9ae0380909c24d2a5ed2162a41fcdbafaf59a85ebd"}, + {file = "coverage-6.0.2-cp310-cp310-win32.whl", hash = "sha256:db42baa892cba723326284490283a68d4de516bfb5aaba369b4e3b2787a778b7"}, + {file = "coverage-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:bbffde2a68398682623d9dd8c0ca3f46fda074709b26fcf08ae7a4c431a6ab2d"}, + {file = "coverage-6.0.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:60e51a3dd55540bec686d7fff61b05048ca31e804c1f32cbb44533e6372d9cc3"}, + {file = "coverage-6.0.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a6a9409223a27d5ef3cca57dd7cd4dfcb64aadf2fad5c3b787830ac9223e01a"}, + {file = "coverage-6.0.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4b34ae4f51bbfa5f96b758b55a163d502be3dcb24f505d0227858c2b3f94f5b9"}, + {file = "coverage-6.0.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3bbda1b550e70fa6ac40533d3f23acd4f4e9cb4e6e77251ce77fdf41b3309fb2"}, + {file = "coverage-6.0.2-cp36-cp36m-win32.whl", hash = "sha256:4e28d2a195c533b58fc94a12826f4431726d8eb029ac21d874345f943530c122"}, + {file = "coverage-6.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:a82d79586a0a4f5fd1cf153e647464ced402938fbccb3ffc358c7babd4da1dd9"}, + {file = "coverage-6.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3be1206dc09fb6298de3fce70593e27436862331a85daee36270b6d0e1c251c4"}, + {file = "coverage-6.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9cd3828bbe1a40070c11fe16a51df733fd2f0cb0d745fb83b7b5c1f05967df7"}, + {file = "coverage-6.0.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:d036dc1ed8e1388e995833c62325df3f996675779541f682677efc6af71e96cc"}, + {file = "coverage-6.0.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:04560539c19ec26995ecfb3d9307ff154fbb9a172cb57e3b3cfc4ced673103d1"}, + {file = "coverage-6.0.2-cp37-cp37m-win32.whl", hash = "sha256:e4fb7ced4d9dec77d6cf533acfbf8e1415fe799430366affb18d69ee8a3c6330"}, + {file = "coverage-6.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:77b1da5767ed2f44611bc9bc019bc93c03fa495728ec389759b6e9e5039ac6b1"}, + {file = "coverage-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:61b598cbdbaae22d9e34e3f675997194342f866bb1d781da5d0be54783dce1ff"}, + {file = "coverage-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36e9040a43d2017f2787b28d365a4bb33fcd792c7ff46a047a04094dc0e2a30d"}, + {file = "coverage-6.0.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9f1627e162e3864a596486774876415a7410021f4b67fd2d9efdf93ade681afc"}, + {file = "coverage-6.0.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e7a0b42db2a47ecb488cde14e0f6c7679a2c5a9f44814393b162ff6397fcdfbb"}, + {file = "coverage-6.0.2-cp38-cp38-win32.whl", hash = "sha256:a1b73c7c4d2a42b9d37dd43199c5711d91424ff3c6c22681bc132db4a4afec6f"}, + {file = "coverage-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:1db67c497688fd4ba85b373b37cc52c50d437fd7267520ecd77bddbd89ea22c9"}, + {file = "coverage-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f2f184bf38e74f152eed7f87e345b51f3ab0b703842f447c22efe35e59942c24"}, + {file = "coverage-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd1cf1deb3d5544bd942356364a2fdc8959bad2b6cf6eb17f47d301ea34ae822"}, + {file = "coverage-6.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ad9b8c1206ae41d46ec7380b78ba735ebb77758a650643e841dd3894966c31d0"}, + {file = "coverage-6.0.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:381d773d896cc7f8ba4ff3b92dee4ed740fb88dfe33b6e42efc5e8ab6dfa1cfe"}, + {file = "coverage-6.0.2-cp39-cp39-win32.whl", hash = "sha256:424c44f65e8be58b54e2b0bd1515e434b940679624b1b72726147cfc6a9fc7ce"}, + {file = "coverage-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:abbff240f77347d17306d3201e14431519bf64495648ca5a49571f988f88dee9"}, + {file = "coverage-6.0.2-pp36-none-any.whl", hash = "sha256:7092eab374346121805fb637572483270324407bf150c30a3b161fc0c4ca5164"}, + {file = "coverage-6.0.2-pp37-none-any.whl", hash = "sha256:30922626ce6f7a5a30bdba984ad21021529d3d05a68b4f71ea3b16bda35b8895"}, + {file = "coverage-6.0.2.tar.gz", hash = "sha256:6807947a09510dc31fa86f43595bf3a14017cd60bf633cc746d52141bfa6b149"}, ] cryptography = [ - {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-macosx_10_10_x86_64.whl", hash = "sha256:b01fd6f2737816cb1e08ed4807ae194404790eac7ad030b34f2ce72b332f5586"}, - {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-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:bf40af59ca2465b24e54f671b2de2c59257ddc4f7e5706dbd6930e26823668d3"}, - {file = "cryptography-3.4.7.tar.gz", hash = "sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713"}, + {file = "cryptography-35.0.0-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:d57e0cdc1b44b6cdf8af1d01807db06886f10177469312fbde8f44ccbb284bc9"}, + {file = "cryptography-35.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:ced40344e811d6abba00295ced98c01aecf0c2de39481792d87af4fa58b7b4d6"}, + {file = "cryptography-35.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:54b2605e5475944e2213258e0ab8696f4f357a31371e538ef21e8d61c843c28d"}, + {file = "cryptography-35.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:7b7ceeff114c31f285528ba8b390d3e9cfa2da17b56f11d366769a807f17cbaa"}, + {file = "cryptography-35.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d69645f535f4b2c722cfb07a8eab916265545b3475fdb34e0be2f4ee8b0b15e"}, + {file = "cryptography-35.0.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a2d0e0acc20ede0f06ef7aa58546eee96d2592c00f450c9acb89c5879b61992"}, + {file = "cryptography-35.0.0-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:07bb7fbfb5de0980590ddfc7f13081520def06dc9ed214000ad4372fb4e3c7f6"}, + {file = "cryptography-35.0.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7eba2cebca600a7806b893cb1d541a6e910afa87e97acf2021a22b32da1df52d"}, + {file = "cryptography-35.0.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:18d90f4711bf63e2fb21e8c8e51ed8189438e6b35a6d996201ebd98a26abbbe6"}, + {file = "cryptography-35.0.0-cp36-abi3-win32.whl", hash = "sha256:c10c797ac89c746e488d2ee92bd4abd593615694ee17b2500578b63cad6b93a8"}, + {file = "cryptography-35.0.0-cp36-abi3-win_amd64.whl", hash = "sha256:7075b304cd567694dc692ffc9747f3e9cb393cc4aa4fb7b9f3abd6f5c4e43588"}, + {file = "cryptography-35.0.0-pp36-pypy36_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a688ebcd08250eab5bb5bca318cc05a8c66de5e4171a65ca51db6bd753ff8953"}, + {file = "cryptography-35.0.0-pp36-pypy36_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d99915d6ab265c22873f1b4d6ea5ef462ef797b4140be4c9d8b179915e0985c6"}, + {file = "cryptography-35.0.0-pp36-pypy36_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:928185a6d1ccdb816e883f56ebe92e975a262d31cc536429041921f8cb5a62fd"}, + {file = "cryptography-35.0.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:ebeddd119f526bcf323a89f853afb12e225902a24d29b55fe18dd6fcb2838a76"}, + {file = "cryptography-35.0.0-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:22a38e96118a4ce3b97509443feace1d1011d0571fae81fc3ad35f25ba3ea999"}, + {file = "cryptography-35.0.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb80e8a1f91e4b7ef8b33041591e6d89b2b8e122d787e87eeb2b08da71bb16ad"}, + {file = "cryptography-35.0.0-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:abb5a361d2585bb95012a19ed9b2c8f412c5d723a9836418fab7aaa0243e67d2"}, + {file = "cryptography-35.0.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:1ed82abf16df40a60942a8c211251ae72858b25b7421ce2497c2eb7a1cee817c"}, + {file = "cryptography-35.0.0.tar.gz", hash = "sha256:9933f28f70d0517686bd7de36166dda42094eac49415459d9bdf5e7df3e0086d"}, ] cx-freeze = [] cx-logging = [ @@ -1812,13 +1842,13 @@ 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.18-py2.py3-none-any.whl", hash = "sha256:a31688b2ea858517fa54293e5d5df06fbb875fb1f7e4c64529271b77781ca8fc"}, + {file = "docutils-0.18.tar.gz", hash = "sha256:c1d5dab2b11d16397406a282e53953fe495a46d69ae329f55aa98a5c4e3c5fbb"}, ] dropbox = [ - {file = "dropbox-11.20.0-py2-none-any.whl", hash = "sha256:0926aab25445fe78b0284e0b86f4126ec4e5e2bf6cd2ac8562002008a21073b8"}, - {file = "dropbox-11.20.0-py3-none-any.whl", hash = "sha256:f2106aa566f9e3c175879c226c60b7089a39099b228061acbb7258670f6b859c"}, - {file = "dropbox-11.20.0.tar.gz", hash = "sha256:1aa351ec8bbb11cf3560e731b81d25f39c7edcb5fa92c06c5d68866cb9f90d54"}, + {file = "dropbox-11.22.0-py2-none-any.whl", hash = "sha256:f2efc924529be2e2e2a1d6f49246b25966c201b23dda231dfb148a6f5ae1a149"}, + {file = "dropbox-11.22.0-py3-none-any.whl", hash = "sha256:0a9cc253391cae7fccf1954da75edf8459d6567ba764e21b471019f0fa001ab4"}, + {file = "dropbox-11.22.0.tar.gz", hash = "sha256:ab84c9c78606faa0dc94cdb95c6b2bdb579beb5f34fff42091c98a1e0fbeb16c"}, ] enlighten = [ {file = "enlighten-1.10.1-py2.py3-none-any.whl", hash = "sha256:3d6c3eec8cf3eb626ee7b65eddc1b3e904d01f4547a2b9fe7f1da8892a0297e8"}, @@ -1839,24 +1869,24 @@ future = [ {file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"}, ] gitdb = [ - {file = "gitdb-4.0.7-py3-none-any.whl", hash = "sha256:6c4cc71933456991da20917998acbe6cf4fb41eeaab7d6d67fbc05ecd4c865b0"}, - {file = "gitdb-4.0.7.tar.gz", hash = "sha256:96bf5c08b157a666fec41129e6d327235284cca4c81e92109260f353ba138005"}, + {file = "gitdb-4.0.9-py3-none-any.whl", hash = "sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd"}, + {file = "gitdb-4.0.9.tar.gz", hash = "sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa"}, ] gitpython = [ - {file = "GitPython-3.1.17-py3-none-any.whl", hash = "sha256:29fe82050709760081f588dd50ce83504feddbebdc4da6956d02351552b1c135"}, - {file = "GitPython-3.1.17.tar.gz", hash = "sha256:ee24bdc93dce357630764db659edaf6b8d664d4ff5447ccfeedd2dc5c253f41e"}, + {file = "GitPython-3.1.24-py3-none-any.whl", hash = "sha256:dc0a7f2f697657acc8d7f89033e8b1ea94dd90356b2983bca89dc8d2ab3cc647"}, + {file = "GitPython-3.1.24.tar.gz", hash = "sha256:df83fdf5e684fef7c6ee2c02fc68a5ceb7e7e759d08b694088d0cacb4eba59e5"}, ] google-api-core = [ - {file = "google-api-core-1.30.0.tar.gz", hash = "sha256:0724d354d394b3d763bc10dfee05807813c5210f0bd9b8e2ddf6b6925603411c"}, - {file = "google_api_core-1.30.0-py2.py3-none-any.whl", hash = "sha256:92cd9e9f366e84bfcf2524e34d2dc244906c645e731962617ba620da1620a1e0"}, + {file = "google-api-core-1.31.3.tar.gz", hash = "sha256:4b7ad965865aef22afa4aded3318b8fa09b20bcc7e8dbb639a3753cf60af08ea"}, + {file = "google_api_core-1.31.3-py2.py3-none-any.whl", hash = "sha256:f52c708ab9fd958862dea9ac94d9db1a065608073fe583c3b9c18537b177f59a"}, ] 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.31.0.tar.gz", hash = "sha256:154f7889c5d679a6f626f36adb12afbd4dbb0a9a04ec575d989d6ba79c4fd65e"}, - {file = "google_auth-1.31.0-py2.py3-none-any.whl", hash = "sha256:6d47c79b5d09fbc7e8355fd9594cc4cf65fdde5d401c63951eaac4baa1ba2ae1"}, + {file = "google-auth-1.35.0.tar.gz", hash = "sha256:b7033be9028c188ee30200b204ea00ed82ea1162e8ac1df4aa6ded19a191d88e"}, + {file = "google_auth-1.35.0-py2.py3-none-any.whl", hash = "sha256:997516b42ecb5b63e8d80f5632c1a61dddf41d2a4c2748057837e06e00014258"}, ] google-auth-httplib2 = [ {file = "google-auth-httplib2-0.1.0.tar.gz", hash = "sha256:a07c39fd632becacd3f07718dfd6021bf396978f03ad3ce4321d060015cc30ac"}, @@ -1867,8 +1897,8 @@ googleapis-common-protos = [ {file = "googleapis_common_protos-1.53.0-py2.py3-none-any.whl", hash = "sha256:f6d561ab8fb16b30020b940e2dd01cd80082f4762fa9f3ee670f4419b4b8dbd0"}, ] httplib2 = [ - {file = "httplib2-0.19.1-py3-none-any.whl", hash = "sha256:2ad195faf9faf079723f6714926e9a9061f694d07724b846658ce08d40f522b4"}, - {file = "httplib2-0.19.1.tar.gz", hash = "sha256:0b12617eeca7433d4c396a100eaecfa4b08ee99aa881e6df6e257a7aad5d533d"}, + {file = "httplib2-0.20.1-py3-none-any.whl", hash = "sha256:8fa4dbf2fbf839b71f8c7837a831e00fcdc860feca99b8bda58ceae4bc53d185"}, + {file = "httplib2-0.20.1.tar.gz", hash = "sha256:0efbcb8bfbfbc11578130d87d8afcc65c2274c6eb446e59fc674e4d7c972d327"}, ] idna = [ {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, @@ -1879,24 +1909,24 @@ imagesize = [ {file = "imagesize-1.2.0.tar.gz", hash = "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"}, ] importlib-metadata = [ - {file = "importlib_metadata-4.5.0-py3-none-any.whl", hash = "sha256:833b26fb89d5de469b24a390e9df088d4e52e4ba33b01dc5e0e4f41b81a16c00"}, - {file = "importlib_metadata-4.5.0.tar.gz", hash = "sha256:b142cc1dd1342f31ff04bb7d022492b09920cb64fed867cd3ea6f80fe3ebd139"}, + {file = "importlib_metadata-4.8.1-py3-none-any.whl", hash = "sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15"}, + {file = "importlib_metadata-4.8.1.tar.gz", hash = "sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1"}, ] 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.8.0-py3-none-any.whl", hash = "sha256:2bb1680aad211e3c9944dbce1d4ba09a989f04e238296c87fe2139faa26d655d"}, - {file = "isort-5.8.0.tar.gz", hash = "sha256:0a943902919f65c5684ac4e0154b1ad4fac6dcaa5d9f3426b732f1c8b5419be6"}, + {file = "isort-5.9.3-py3-none-any.whl", hash = "sha256:e17d6e2b81095c9db0a03a8025a957f334d6ea30b26f9ec70805411e5c7c81f2"}, + {file = "isort-5.9.3.tar.gz", hash = "sha256:9c2ea1e62d871267b78307fe511c0838ba0da28698c5732d54e2790bf3ba9899"}, ] jedi = [ {file = "jedi-0.13.3-py2.py3-none-any.whl", hash = "sha256:2c6bcd9545c7d6440951b12b44d373479bf18123a401a52025cf98563fbd826c"}, {file = "jedi-0.13.3.tar.gz", hash = "sha256:2bb0603e3506f708e792c7f4ad8fc2a7a9d9c2d292a358fbbd58da531695595b"}, ] jeepney = [ - {file = "jeepney-0.6.0-py3-none-any.whl", hash = "sha256:aec56c0eb1691a841795111e184e13cad504f7703b9a64f63020816afa79a8ae"}, - {file = "jeepney-0.6.0.tar.gz", hash = "sha256:7d59b6622675ca9e993a6bd38de845051d315f8b0c72cca3aef733a20b648657"}, + {file = "jeepney-0.7.1-py3-none-any.whl", hash = "sha256:1b5a0ea5c0e7b166b2f5895b91a08c14de8915afda4407fb5022a195224958ac"}, + {file = "jeepney-0.7.1.tar.gz", hash = "sha256:fa9e232dfa0c498bd0b8a3a73b8d8a31978304dcef0515adc859d4e096f96f4f"}, ] jinja2 = [ {file = "Jinja2-2.11.3-py2.py3-none-any.whl", hash = "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419"}, @@ -1942,22 +1972,12 @@ log4mongo = [ {file = "log4mongo-1.7.0.tar.gz", hash = "sha256:dc374617206162a0b14167fbb5feac01dbef587539a235dadba6200362984a68"}, ] markupsafe = [ - {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"}, @@ -1966,21 +1986,14 @@ markupsafe = [ {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"}, {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"}, {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"}, {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"}, {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"}, @@ -1990,9 +2003,6 @@ markupsafe = [ {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"}, {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"}, {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"}, {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"}, {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, @@ -2002,119 +2012,146 @@ mccabe = [ {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, ] multidict = [ - {file = "multidict-5.1.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:b7993704f1a4b204e71debe6095150d43b2ee6150fa4f44d6d966ec356a8d61f"}, - {file = "multidict-5.1.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:9dd6e9b1a913d096ac95d0399bd737e00f2af1e1594a787e00f7975778c8b2bf"}, - {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:f21756997ad8ef815d8ef3d34edd98804ab5ea337feedcd62fb52d22bf531281"}, - {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:1ab820665e67373de5802acae069a6a05567ae234ddb129f31d290fc3d1aa56d"}, - {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:9436dc58c123f07b230383083855593550c4d301d2532045a17ccf6eca505f6d"}, - {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:830f57206cc96ed0ccf68304141fec9481a096c4d2e2831f311bde1c404401da"}, - {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:2e68965192c4ea61fff1b81c14ff712fc7dc15d2bd120602e4a3494ea6584224"}, - {file = "multidict-5.1.0-cp36-cp36m-win32.whl", hash = "sha256:2f1a132f1c88724674271d636e6b7351477c27722f2ed789f719f9e3545a3d26"}, - {file = "multidict-5.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:3a4f32116f8f72ecf2a29dabfb27b23ab7cdc0ba807e8459e59a93a9be9506f6"}, - {file = "multidict-5.1.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:46c73e09ad374a6d876c599f2328161bcd95e280f84d2060cf57991dec5cfe76"}, - {file = "multidict-5.1.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:018132dbd8688c7a69ad89c4a3f39ea2f9f33302ebe567a879da8f4ca73f0d0a"}, - {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:4b186eb7d6ae7c06eb4392411189469e6a820da81447f46c0072a41c748ab73f"}, - {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:3a041b76d13706b7fff23b9fc83117c7b8fe8d5fe9e6be45eee72b9baa75f348"}, - {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:051012ccee979b2b06be928a6150d237aec75dd6bf2d1eeeb190baf2b05abc93"}, - {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:6a4d5ce640e37b0efcc8441caeea8f43a06addace2335bd11151bc02d2ee31f9"}, - {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:5cf3443199b83ed9e955f511b5b241fd3ae004e3cb81c58ec10f4fe47c7dce37"}, - {file = "multidict-5.1.0-cp37-cp37m-win32.whl", hash = "sha256:f200755768dc19c6f4e2b672421e0ebb3dd54c38d5a4f262b872d8cfcc9e93b5"}, - {file = "multidict-5.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:05c20b68e512166fddba59a918773ba002fdd77800cad9f55b59790030bab632"}, - {file = "multidict-5.1.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:54fd1e83a184e19c598d5e70ba508196fd0bbdd676ce159feb412a4a6664f952"}, - {file = "multidict-5.1.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:0e3c84e6c67eba89c2dbcee08504ba8644ab4284863452450520dad8f1e89b79"}, - {file = "multidict-5.1.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:dc862056f76443a0db4509116c5cd480fe1b6a2d45512a653f9a855cc0517456"}, - {file = "multidict-5.1.0-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:0e929169f9c090dae0646a011c8b058e5e5fb391466016b39d21745b48817fd7"}, - {file = "multidict-5.1.0-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:d81eddcb12d608cc08081fa88d046c78afb1bf8107e6feab5d43503fea74a635"}, - {file = "multidict-5.1.0-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:585fd452dd7782130d112f7ddf3473ffdd521414674c33876187e101b588738a"}, - {file = "multidict-5.1.0-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:37e5438e1c78931df5d3c0c78ae049092877e5e9c02dd1ff5abb9cf27a5914ea"}, - {file = "multidict-5.1.0-cp38-cp38-win32.whl", hash = "sha256:07b42215124aedecc6083f1ce6b7e5ec5b50047afa701f3442054373a6deb656"}, - {file = "multidict-5.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:929006d3c2d923788ba153ad0de8ed2e5ed39fdbe8e7be21e2f22ed06c6783d3"}, - {file = "multidict-5.1.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:b797515be8743b771aa868f83563f789bbd4b236659ba52243b735d80b29ed93"}, - {file = "multidict-5.1.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d5c65bdf4484872c4af3150aeebe101ba560dcfb34488d9a8ff8dbcd21079647"}, - {file = "multidict-5.1.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b47a43177a5e65b771b80db71e7be76c0ba23cc8aa73eeeb089ed5219cdbe27d"}, - {file = "multidict-5.1.0-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:806068d4f86cb06af37cd65821554f98240a19ce646d3cd24e1c33587f313eb8"}, - {file = "multidict-5.1.0-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:46dd362c2f045095c920162e9307de5ffd0a1bfbba0a6e990b344366f55a30c1"}, - {file = "multidict-5.1.0-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:ace010325c787c378afd7f7c1ac66b26313b3344628652eacd149bdd23c68841"}, - {file = "multidict-5.1.0-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:ecc771ab628ea281517e24fd2c52e8f31c41e66652d07599ad8818abaad38cda"}, - {file = "multidict-5.1.0-cp39-cp39-win32.whl", hash = "sha256:fc13a9524bc18b6fb6e0dbec3533ba0496bbed167c56d0aabefd965584557d80"}, - {file = "multidict-5.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:7df80d07818b385f3129180369079bd6934cf70469f99daaebfac89dca288359"}, - {file = "multidict-5.1.0.tar.gz", hash = "sha256:25b4e5f22d3a37ddf3effc0710ba692cfc792c2b9edfb9c05aefe823256e84d5"}, + {file = "multidict-5.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3822c5894c72e3b35aae9909bef66ec83e44522faf767c0ad39e0e2de11d3b55"}, + {file = "multidict-5.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:28e6d883acd8674887d7edc896b91751dc2d8e87fbdca8359591a13872799e4e"}, + {file = "multidict-5.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b61f85101ef08cbbc37846ac0e43f027f7844f3fade9b7f6dd087178caedeee7"}, + {file = "multidict-5.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9b668c065968c5979fe6b6fa6760bb6ab9aeb94b75b73c0a9c1acf6393ac3bf"}, + {file = "multidict-5.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:517d75522b7b18a3385726b54a081afd425d4f41144a5399e5abd97ccafdf36b"}, + {file = "multidict-5.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1b4ac3ba7a97b35a5ccf34f41b5a8642a01d1e55454b699e5e8e7a99b5a3acf5"}, + {file = "multidict-5.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:df23c83398715b26ab09574217ca21e14694917a0c857e356fd39e1c64f8283f"}, + {file = "multidict-5.2.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e58a9b5cc96e014ddf93c2227cbdeca94b56a7eb77300205d6e4001805391747"}, + {file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f76440e480c3b2ca7f843ff8a48dc82446b86ed4930552d736c0bac507498a52"}, + {file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cfde464ca4af42a629648c0b0d79b8f295cf5b695412451716531d6916461628"}, + {file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0fed465af2e0eb6357ba95795d003ac0bdb546305cc2366b1fc8f0ad67cc3fda"}, + {file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:b70913cbf2e14275013be98a06ef4b412329fe7b4f83d64eb70dce8269ed1e1a"}, + {file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a5635bcf1b75f0f6ef3c8a1ad07b500104a971e38d3683167b9454cb6465ac86"}, + {file = "multidict-5.2.0-cp310-cp310-win32.whl", hash = "sha256:77f0fb7200cc7dedda7a60912f2059086e29ff67cefbc58d2506638c1a9132d7"}, + {file = "multidict-5.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:9416cf11bcd73c861267e88aea71e9fcc35302b3943e45e1dbb4317f91a4b34f"}, + {file = "multidict-5.2.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fd77c8f3cba815aa69cb97ee2b2ef385c7c12ada9c734b0f3b32e26bb88bbf1d"}, + {file = "multidict-5.2.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98ec9aea6223adf46999f22e2c0ab6cf33f5914be604a404f658386a8f1fba37"}, + {file = "multidict-5.2.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e5283c0a00f48e8cafcecadebfa0ed1dac8b39e295c7248c44c665c16dc1138b"}, + {file = "multidict-5.2.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5f79c19c6420962eb17c7e48878a03053b7ccd7b69f389d5831c0a4a7f1ac0a1"}, + {file = "multidict-5.2.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e4a67f1080123de76e4e97a18d10350df6a7182e243312426d508712e99988d4"}, + {file = "multidict-5.2.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:94b117e27efd8e08b4046c57461d5a114d26b40824995a2eb58372b94f9fca02"}, + {file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:2e77282fd1d677c313ffcaddfec236bf23f273c4fba7cdf198108f5940ae10f5"}, + {file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:116347c63ba049c1ea56e157fa8aa6edaf5e92925c9b64f3da7769bdfa012858"}, + {file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:dc3a866cf6c13d59a01878cd806f219340f3e82eed514485e094321f24900677"}, + {file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:ac42181292099d91217a82e3fa3ce0e0ddf3a74fd891b7c2b347a7f5aa0edded"}, + {file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:f0bb0973f42ffcb5e3537548e0767079420aefd94ba990b61cf7bb8d47f4916d"}, + {file = "multidict-5.2.0-cp36-cp36m-win32.whl", hash = "sha256:ea21d4d5104b4f840b91d9dc8cbc832aba9612121eaba503e54eaab1ad140eb9"}, + {file = "multidict-5.2.0-cp36-cp36m-win_amd64.whl", hash = "sha256:e6453f3cbeb78440747096f239d282cc57a2997a16b5197c9bc839099e1633d0"}, + {file = "multidict-5.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d3def943bfd5f1c47d51fd324df1e806d8da1f8e105cc7f1c76a1daf0f7e17b0"}, + {file = "multidict-5.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35591729668a303a02b06e8dba0eb8140c4a1bfd4c4b3209a436a02a5ac1de11"}, + {file = "multidict-5.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8cacda0b679ebc25624d5de66c705bc53dcc7c6f02a7fb0f3ca5e227d80422"}, + {file = "multidict-5.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:baf1856fab8212bf35230c019cde7c641887e3fc08cadd39d32a421a30151ea3"}, + {file = "multidict-5.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a43616aec0f0d53c411582c451f5d3e1123a68cc7b3475d6f7d97a626f8ff90d"}, + {file = "multidict-5.2.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:25cbd39a9029b409167aa0a20d8a17f502d43f2efebfe9e3ac019fe6796c59ac"}, + {file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a2cbcfbea6dc776782a444db819c8b78afe4db597211298dd8b2222f73e9cd0"}, + {file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3d2d7d1fff8e09d99354c04c3fd5b560fb04639fd45926b34e27cfdec678a704"}, + {file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:a37e9a68349f6abe24130846e2f1d2e38f7ddab30b81b754e5a1fde32f782b23"}, + {file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:637c1896497ff19e1ee27c1c2c2ddaa9f2d134bbb5e0c52254361ea20486418d"}, + {file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9815765f9dcda04921ba467957be543423e5ec6a1136135d84f2ae092c50d87b"}, + {file = "multidict-5.2.0-cp37-cp37m-win32.whl", hash = "sha256:8b911d74acdc1fe2941e59b4f1a278a330e9c34c6c8ca1ee21264c51ec9b67ef"}, + {file = "multidict-5.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:380b868f55f63d048a25931a1632818f90e4be71d2081c2338fcf656d299949a"}, + {file = "multidict-5.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e7d81ce5744757d2f05fc41896e3b2ae0458464b14b5a2c1e87a6a9d69aefaa8"}, + {file = "multidict-5.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d1d55cdf706ddc62822d394d1df53573d32a7a07d4f099470d3cb9323b721b6"}, + {file = "multidict-5.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a4771d0d0ac9d9fe9e24e33bed482a13dfc1256d008d101485fe460359476065"}, + {file = "multidict-5.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da7d57ea65744d249427793c042094c4016789eb2562576fb831870f9c878d9e"}, + {file = "multidict-5.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdd68778f96216596218b4e8882944d24a634d984ee1a5a049b300377878fa7c"}, + {file = "multidict-5.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ecc99bce8ee42dcad15848c7885197d26841cb24fa2ee6e89d23b8993c871c64"}, + {file = "multidict-5.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:067150fad08e6f2dd91a650c7a49ba65085303fcc3decbd64a57dc13a2733031"}, + {file = "multidict-5.2.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:78c106b2b506b4d895ddc801ff509f941119394b89c9115580014127414e6c2d"}, + {file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e6c4fa1ec16e01e292315ba76eb1d012c025b99d22896bd14a66628b245e3e01"}, + {file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b227345e4186809d31f22087d0265655114af7cda442ecaf72246275865bebe4"}, + {file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:06560fbdcf22c9387100979e65b26fba0816c162b888cb65b845d3def7a54c9b"}, + {file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:7878b61c867fb2df7a95e44b316f88d5a3742390c99dfba6c557a21b30180cac"}, + {file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:246145bff76cc4b19310f0ad28bd0769b940c2a49fc601b86bfd150cbd72bb22"}, + {file = "multidict-5.2.0-cp38-cp38-win32.whl", hash = "sha256:c30ac9f562106cd9e8071c23949a067b10211917fdcb75b4718cf5775356a940"}, + {file = "multidict-5.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:f19001e790013ed580abfde2a4465388950728861b52f0da73e8e8a9418533c0"}, + {file = "multidict-5.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c1ff762e2ee126e6f1258650ac641e2b8e1f3d927a925aafcfde943b77a36d24"}, + {file = "multidict-5.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bd6c9c50bf2ad3f0448edaa1a3b55b2e6866ef8feca5d8dbec10ec7c94371d21"}, + {file = "multidict-5.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fc66d4016f6e50ed36fb39cd287a3878ffcebfa90008535c62e0e90a7ab713ae"}, + {file = "multidict-5.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9acb76d5f3dd9421874923da2ed1e76041cb51b9337fd7f507edde1d86535d6"}, + {file = "multidict-5.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dfc924a7e946dd3c6360e50e8f750d51e3ef5395c95dc054bc9eab0f70df4f9c"}, + {file = "multidict-5.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32fdba7333eb2351fee2596b756d730d62b5827d5e1ab2f84e6cbb287cc67fe0"}, + {file = "multidict-5.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:b9aad49466b8d828b96b9e3630006234879c8d3e2b0a9d99219b3121bc5cdb17"}, + {file = "multidict-5.2.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:93de39267c4c676c9ebb2057e98a8138bade0d806aad4d864322eee0803140a0"}, + {file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f9bef5cff994ca3026fcc90680e326d1a19df9841c5e3d224076407cc21471a1"}, + {file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:5f841c4f14331fd1e36cbf3336ed7be2cb2a8f110ce40ea253e5573387db7621"}, + {file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:38ba256ee9b310da6a1a0f013ef4e422fca30a685bcbec86a969bd520504e341"}, + {file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3bc3b1621b979621cee9f7b09f024ec76ec03cc365e638126a056317470bde1b"}, + {file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6ee908c070020d682e9b42c8f621e8bb10c767d04416e2ebe44e37d0f44d9ad5"}, + {file = "multidict-5.2.0-cp39-cp39-win32.whl", hash = "sha256:1c7976cd1c157fa7ba5456ae5d31ccdf1479680dc9b8d8aa28afabc370df42b8"}, + {file = "multidict-5.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:c9631c642e08b9fff1c6255487e62971d8b8e821808ddd013d8ac058087591ac"}, + {file = "multidict-5.2.0.tar.gz", hash = "sha256:0dd1c93edb444b33ba2274b66f63def8a327d607c6c790772f448a53b6ea59ce"}, ] opentimelineio = [] packaging = [ - {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, - {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, + {file = "packaging-21.2-py3-none-any.whl", hash = "sha256:14317396d1e8cdb122989b916fa2c7e9ca8e2be9e8060a6eff75b6b7b4d8a7e0"}, + {file = "packaging-21.2.tar.gz", hash = "sha256:096d689d78ca690e4cd8a89568ba06d07ca097e3306a4381635073ca91479966"}, ] paramiko = [ - {file = "paramiko-2.7.2-py2.py3-none-any.whl", hash = "sha256:4f3e316fef2ac628b05097a637af35685183111d4bc1b5979bd397c2ab7b5898"}, - {file = "paramiko-2.7.2.tar.gz", hash = "sha256:7f36f4ba2c0d81d219f4595e35f70d56cc94f9ac40a6acdf51d6ca210ce65035"}, + {file = "paramiko-2.8.0-py2.py3-none-any.whl", hash = "sha256:def3ec612399bab4e9f5eb66b0ae5983980db9dd9120d9e9c6ea3ff673865d1c"}, + {file = "paramiko-2.8.0.tar.gz", hash = "sha256:e673b10ee0f1c80d46182d3af7751d033d9b573dd7054d2d0aa46be186c3c1d2"}, ] parso = [ {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"}, + {file = "pathlib2-2.3.6-py2.py3-none-any.whl", hash = "sha256:3a130b266b3a36134dcc79c17b3c7ac9634f083825ca6ea9d8f557ee6195c9c8"}, + {file = "pathlib2-2.3.6.tar.gz", hash = "sha256:7d8bcb5555003cdf4a8d2872c538faa3a0f5d20630cb360e518ca3b981795e5f"}, ] pillow = [ - {file = "Pillow-8.3.2-cp310-cp310-macosx_10_10_universal2.whl", hash = "sha256:c691b26283c3a31594683217d746f1dad59a7ae1d4cfc24626d7a064a11197d4"}, - {file = "Pillow-8.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f514c2717012859ccb349c97862568fdc0479aad85b0270d6b5a6509dbc142e2"}, - {file = "Pillow-8.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be25cb93442c6d2f8702c599b51184bd3ccd83adebd08886b682173e09ef0c3f"}, - {file = "Pillow-8.3.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d675a876b295afa114ca8bf42d7f86b5fb1298e1b6bb9a24405a3f6c8338811c"}, - {file = "Pillow-8.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59697568a0455764a094585b2551fd76bfd6b959c9f92d4bdec9d0e14616303a"}, - {file = "Pillow-8.3.2-cp310-cp310-win32.whl", hash = "sha256:2d5e9dc0bf1b5d9048a94c48d0813b6c96fccfa4ccf276d9c36308840f40c228"}, - {file = "Pillow-8.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:11c27e74bab423eb3c9232d97553111cc0be81b74b47165f07ebfdd29d825875"}, - {file = "Pillow-8.3.2-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:11eb7f98165d56042545c9e6db3ce394ed8b45089a67124298f0473b29cb60b2"}, - {file = "Pillow-8.3.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f23b2d3079522fdf3c09de6517f625f7a964f916c956527bed805ac043799b8"}, - {file = "Pillow-8.3.2-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19ec4cfe4b961edc249b0e04b5618666c23a83bc35842dea2bfd5dfa0157f81b"}, - {file = "Pillow-8.3.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5a31c07cea5edbaeb4bdba6f2b87db7d3dc0f446f379d907e51cc70ea375629"}, - {file = "Pillow-8.3.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:15ccb81a6ffc57ea0137f9f3ac2737ffa1d11f786244d719639df17476d399a7"}, - {file = "Pillow-8.3.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:8f284dc1695caf71a74f24993b7c7473d77bc760be45f776a2c2f4e04c170550"}, - {file = "Pillow-8.3.2-cp36-cp36m-win32.whl", hash = "sha256:4abc247b31a98f29e5224f2d31ef15f86a71f79c7f4d2ac345a5d551d6393073"}, - {file = "Pillow-8.3.2-cp36-cp36m-win_amd64.whl", hash = "sha256:a048dad5ed6ad1fad338c02c609b862dfaa921fcd065d747194a6805f91f2196"}, - {file = "Pillow-8.3.2-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:06d1adaa284696785375fa80a6a8eb309be722cf4ef8949518beb34487a3df71"}, - {file = "Pillow-8.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd24054aaf21e70a51e2a2a5ed1183560d3a69e6f9594a4bfe360a46f94eba83"}, - {file = "Pillow-8.3.2-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27a330bf7014ee034046db43ccbb05c766aa9e70b8d6c5260bfc38d73103b0ba"}, - {file = "Pillow-8.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13654b521fb98abdecec105ea3fb5ba863d1548c9b58831dd5105bb3873569f1"}, - {file = "Pillow-8.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a1bd983c565f92779be456ece2479840ec39d386007cd4ae83382646293d681b"}, - {file = "Pillow-8.3.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:4326ea1e2722f3dc00ed77c36d3b5354b8fb7399fb59230249ea6d59cbed90da"}, - {file = "Pillow-8.3.2-cp37-cp37m-win32.whl", hash = "sha256:085a90a99404b859a4b6c3daa42afde17cb3ad3115e44a75f0d7b4a32f06a6c9"}, - {file = "Pillow-8.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:18a07a683805d32826c09acfce44a90bf474e6a66ce482b1c7fcd3757d588df3"}, - {file = "Pillow-8.3.2-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:4e59e99fd680e2b8b11bbd463f3c9450ab799305d5f2bafb74fefba6ac058616"}, - {file = "Pillow-8.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4d89a2e9219a526401015153c0e9dd48319ea6ab9fe3b066a20aa9aee23d9fd3"}, - {file = "Pillow-8.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56fd98c8294f57636084f4b076b75f86c57b2a63a8410c0cd172bc93695ee979"}, - {file = "Pillow-8.3.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b11c9d310a3522b0fd3c35667914271f570576a0e387701f370eb39d45f08a4"}, - {file = "Pillow-8.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0412516dcc9de9b0a1e0ae25a280015809de8270f134cc2c1e32c4eeb397cf30"}, - {file = "Pillow-8.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bcb04ff12e79b28be6c9988f275e7ab69f01cc2ba319fb3114f87817bb7c74b6"}, - {file = "Pillow-8.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:0b9911ec70731711c3b6ebcde26caea620cbdd9dcb73c67b0730c8817f24711b"}, - {file = "Pillow-8.3.2-cp38-cp38-win32.whl", hash = "sha256:ce2e5e04bb86da6187f96d7bab3f93a7877830981b37f0287dd6479e27a10341"}, - {file = "Pillow-8.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:35d27687f027ad25a8d0ef45dd5208ef044c588003cdcedf05afb00dbc5c2deb"}, - {file = "Pillow-8.3.2-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:04835e68ef12904bc3e1fd002b33eea0779320d4346082bd5b24bec12ad9c3e9"}, - {file = "Pillow-8.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:10e00f7336780ca7d3653cf3ac26f068fa11b5a96894ea29a64d3dc4b810d630"}, - {file = "Pillow-8.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cde7a4d3687f21cffdf5bb171172070bb95e02af448c4c8b2f223d783214056"}, - {file = "Pillow-8.3.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c3ff00110835bdda2b1e2b07f4a2548a39744bb7de5946dc8e95517c4fb2ca6"}, - {file = "Pillow-8.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35d409030bf3bd05fa66fb5fdedc39c521b397f61ad04309c90444e893d05f7d"}, - {file = "Pillow-8.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bff50ba9891be0a004ef48828e012babaaf7da204d81ab9be37480b9020a82b"}, - {file = "Pillow-8.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7dbfbc0020aa1d9bc1b0b8bcf255a7d73f4ad0336f8fd2533fcc54a4ccfb9441"}, - {file = "Pillow-8.3.2-cp39-cp39-win32.whl", hash = "sha256:963ebdc5365d748185fdb06daf2ac758116deecb2277ec5ae98139f93844bc09"}, - {file = "Pillow-8.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:cc9d0dec711c914ed500f1d0d3822868760954dce98dfb0b7382a854aee55d19"}, - {file = "Pillow-8.3.2-pp36-pypy36_pp73-macosx_10_10_x86_64.whl", hash = "sha256:2c661542c6f71dfd9dc82d9d29a8386287e82813b0375b3a02983feac69ef864"}, - {file = "Pillow-8.3.2-pp36-pypy36_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:548794f99ff52a73a156771a0402f5e1c35285bd981046a502d7e4793e8facaa"}, - {file = "Pillow-8.3.2-pp36-pypy36_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8b68f565a4175e12e68ca900af8910e8fe48aaa48fd3ca853494f384e11c8bcd"}, - {file = "Pillow-8.3.2-pp36-pypy36_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:838eb85de6d9307c19c655c726f8d13b8b646f144ca6b3771fa62b711ebf7624"}, - {file = "Pillow-8.3.2-pp36-pypy36_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:feb5db446e96bfecfec078b943cc07744cc759893cef045aa8b8b6d6aaa8274e"}, - {file = "Pillow-8.3.2-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:fc0db32f7223b094964e71729c0361f93db43664dd1ec86d3df217853cedda87"}, - {file = "Pillow-8.3.2-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:fd4fd83aa912d7b89b4b4a1580d30e2a4242f3936882a3f433586e5ab97ed0d5"}, - {file = "Pillow-8.3.2-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d0c8ebbfd439c37624db98f3877d9ed12c137cadd99dde2d2eae0dab0bbfc355"}, - {file = "Pillow-8.3.2-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6cb3dd7f23b044b0737317f892d399f9e2f0b3a02b22b2c692851fb8120d82c6"}, - {file = "Pillow-8.3.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a66566f8a22561fc1a88dc87606c69b84fa9ce724f99522cf922c801ec68f5c1"}, - {file = "Pillow-8.3.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ce651ca46d0202c302a535d3047c55a0131a720cf554a578fc1b8a2aff0e7d96"}, - {file = "Pillow-8.3.2.tar.gz", hash = "sha256:dde3f3ed8d00c72631bc19cbfff8ad3b6215062a5eed402381ad365f82f0c18c"}, + {file = "Pillow-8.4.0-cp310-cp310-macosx_10_10_universal2.whl", hash = "sha256:81f8d5c81e483a9442d72d182e1fb6dcb9723f289a57e8030811bac9ea3fef8d"}, + {file = "Pillow-8.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3f97cfb1e5a392d75dd8b9fd274d205404729923840ca94ca45a0af57e13dbe6"}, + {file = "Pillow-8.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb9fc393f3c61f9054e1ed26e6fe912c7321af2f41ff49d3f83d05bacf22cc78"}, + {file = "Pillow-8.4.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d82cdb63100ef5eedb8391732375e6d05993b765f72cb34311fab92103314649"}, + {file = "Pillow-8.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62cc1afda735a8d109007164714e73771b499768b9bb5afcbbee9d0ff374b43f"}, + {file = "Pillow-8.4.0-cp310-cp310-win32.whl", hash = "sha256:e3dacecfbeec9a33e932f00c6cd7996e62f53ad46fbe677577394aaa90ee419a"}, + {file = "Pillow-8.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:620582db2a85b2df5f8a82ddeb52116560d7e5e6b055095f04ad828d1b0baa39"}, + {file = "Pillow-8.4.0-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:1bc723b434fbc4ab50bb68e11e93ce5fb69866ad621e3c2c9bdb0cd70e345f55"}, + {file = "Pillow-8.4.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:72cbcfd54df6caf85cc35264c77ede902452d6df41166010262374155947460c"}, + {file = "Pillow-8.4.0-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70ad9e5c6cb9b8487280a02c0ad8a51581dcbbe8484ce058477692a27c151c0a"}, + {file = "Pillow-8.4.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25a49dc2e2f74e65efaa32b153527fc5ac98508d502fa46e74fa4fd678ed6645"}, + {file = "Pillow-8.4.0-cp36-cp36m-win32.whl", hash = "sha256:93ce9e955cc95959df98505e4608ad98281fff037350d8c2671c9aa86bcf10a9"}, + {file = "Pillow-8.4.0-cp36-cp36m-win_amd64.whl", hash = "sha256:2e4440b8f00f504ee4b53fe30f4e381aae30b0568193be305256b1462216feff"}, + {file = "Pillow-8.4.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:8c803ac3c28bbc53763e6825746f05cc407b20e4a69d0122e526a582e3b5e153"}, + {file = "Pillow-8.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8a17b5d948f4ceeceb66384727dde11b240736fddeda54ca740b9b8b1556b29"}, + {file = "Pillow-8.4.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1394a6ad5abc838c5cd8a92c5a07535648cdf6d09e8e2d6df916dfa9ea86ead8"}, + {file = "Pillow-8.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:792e5c12376594bfcb986ebf3855aa4b7c225754e9a9521298e460e92fb4a488"}, + {file = "Pillow-8.4.0-cp37-cp37m-win32.whl", hash = "sha256:d99ec152570e4196772e7a8e4ba5320d2d27bf22fdf11743dd882936ed64305b"}, + {file = "Pillow-8.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:7b7017b61bbcdd7f6363aeceb881e23c46583739cb69a3ab39cb384f6ec82e5b"}, + {file = "Pillow-8.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:d89363f02658e253dbd171f7c3716a5d340a24ee82d38aab9183f7fdf0cdca49"}, + {file = "Pillow-8.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0a0956fdc5defc34462bb1c765ee88d933239f9a94bc37d132004775241a7585"}, + {file = "Pillow-8.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b7bb9de00197fb4261825c15551adf7605cf14a80badf1761d61e59da347779"}, + {file = "Pillow-8.4.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72b9e656e340447f827885b8d7a15fc8c4e68d410dc2297ef6787eec0f0ea409"}, + {file = "Pillow-8.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5a4532a12314149d8b4e4ad8ff09dde7427731fcfa5917ff16d0291f13609df"}, + {file = "Pillow-8.4.0-cp38-cp38-win32.whl", hash = "sha256:82aafa8d5eb68c8463b6e9baeb4f19043bb31fefc03eb7b216b51e6a9981ae09"}, + {file = "Pillow-8.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:066f3999cb3b070a95c3652712cffa1a748cd02d60ad7b4e485c3748a04d9d76"}, + {file = "Pillow-8.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:5503c86916d27c2e101b7f71c2ae2cddba01a2cf55b8395b0255fd33fa4d1f1a"}, + {file = "Pillow-8.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4acc0985ddf39d1bc969a9220b51d94ed51695d455c228d8ac29fcdb25810e6e"}, + {file = "Pillow-8.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b052a619a8bfcf26bd8b3f48f45283f9e977890263e4571f2393ed8898d331b"}, + {file = "Pillow-8.4.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:493cb4e415f44cd601fcec11c99836f707bb714ab03f5ed46ac25713baf0ff20"}, + {file = "Pillow-8.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8831cb7332eda5dc89b21a7bce7ef6ad305548820595033a4b03cf3091235ed"}, + {file = "Pillow-8.4.0-cp39-cp39-win32.whl", hash = "sha256:5e9ac5f66616b87d4da618a20ab0a38324dbe88d8a39b55be8964eb520021e02"}, + {file = "Pillow-8.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:3eb1ce5f65908556c2d8685a8f0a6e989d887ec4057326f6c22b24e8a172c66b"}, + {file = "Pillow-8.4.0-pp36-pypy36_pp73-macosx_10_10_x86_64.whl", hash = "sha256:ddc4d832a0f0b4c52fff973a0d44b6c99839a9d016fe4e6a1cb8f3eea96479c2"}, + {file = "Pillow-8.4.0-pp36-pypy36_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a3e5ddc44c14042f0844b8cf7d2cd455f6cc80fd7f5eefbe657292cf601d9ad"}, + {file = "Pillow-8.4.0-pp36-pypy36_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c70e94281588ef053ae8998039610dbd71bc509e4acbc77ab59d7d2937b10698"}, + {file = "Pillow-8.4.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:3862b7256046fcd950618ed22d1d60b842e3a40a48236a5498746f21189afbbc"}, + {file = "Pillow-8.4.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4901622493f88b1a29bd30ec1a2f683782e57c3c16a2dbc7f2595ba01f639df"}, + {file = "Pillow-8.4.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84c471a734240653a0ec91dec0996696eea227eafe72a33bd06c92697728046b"}, + {file = "Pillow-8.4.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:244cf3b97802c34c41905d22810846802a3329ddcb93ccc432870243211c79fc"}, + {file = "Pillow-8.4.0.tar.gz", hash = "sha256:b8e2f83c56e141920c39464b852de3719dfbfb6e3c99a2d8da0edf4fb33176ed"}, +] +platformdirs = [ + {file = "platformdirs-2.4.0-py3-none-any.whl", hash = "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"}, + {file = "platformdirs-2.4.0.tar.gz", hash = "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2"}, ] pluggy = [ - {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, - {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] ply = [ {file = "ply-3.11-py2.py3-none-any.whl", hash = "sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce"}, @@ -2143,13 +2180,9 @@ protobuf = [ {file = "protobuf-3.17.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2ae692bb6d1992afb6b74348e7bb648a75bb0d3565a3f5eea5bec8f62bd06d87"}, {file = "protobuf-3.17.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:99938f2a2d7ca6563c0ade0c5ca8982264c484fdecf418bd68e880a7ab5730b1"}, {file = "protobuf-3.17.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6902a1e4b7a319ec611a7345ff81b6b004b36b0d2196ce7a748b3493da3d226d"}, - {file = "protobuf-3.17.3-cp38-cp38-win32.whl", hash = "sha256:59e5cf6b737c3a376932fbfb869043415f7c16a0cf176ab30a5bbc419cd709c1"}, - {file = "protobuf-3.17.3-cp38-cp38-win_amd64.whl", hash = "sha256:ebcb546f10069b56dc2e3da35e003a02076aaa377caf8530fe9789570984a8d2"}, {file = "protobuf-3.17.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4ffbd23640bb7403574f7aff8368e2aeb2ec9a5c6306580be48ac59a6bac8bde"}, {file = "protobuf-3.17.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:26010f693b675ff5a1d0e1bdb17689b8b716a18709113288fead438703d45539"}, {file = "protobuf-3.17.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e76d9686e088fece2450dbc7ee905f9be904e427341d289acbe9ad00b78ebd47"}, - {file = "protobuf-3.17.3-cp39-cp39-win32.whl", hash = "sha256:a38bac25f51c93e4be4092c88b2568b9f407c27217d3dd23c7a57fa522a17554"}, - {file = "protobuf-3.17.3-cp39-cp39-win_amd64.whl", hash = "sha256:85d6303e4adade2827e43c2b54114d9a6ea547b671cb63fafd5011dc47d0e13d"}, {file = "protobuf-3.17.3-py2.py3-none-any.whl", hash = "sha256:2bfb815216a9cd9faec52b16fd2bfa68437a44b67c56bee59bc3926522ecb04e"}, {file = "protobuf-3.17.3.tar.gz", hash = "sha256:72804ea5eaa9c22a090d2803813e280fb273b62d5ae497aaf3553d141c4fdd7b"}, ] @@ -2212,78 +2245,121 @@ pyflakes = [ {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, ] pygments = [ - {file = "Pygments-2.9.0-py3-none-any.whl", hash = "sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e"}, - {file = "Pygments-2.9.0.tar.gz", hash = "sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f"}, + {file = "Pygments-2.10.0-py3-none-any.whl", hash = "sha256:b8e67fe6af78f492b3c4b3e2970c0624cbf08beb1e493b2c99b9fa1b67a20380"}, + {file = "Pygments-2.10.0.tar.gz", hash = "sha256:f398865f7eb6874156579fdf36bc840a03cab64d1cde9e93d68f46a425ec52c6"}, ] pylint = [ - {file = "pylint-2.8.3-py3-none-any.whl", hash = "sha256:792b38ff30903884e4a9eab814ee3523731abd3c463f3ba48d7b627e87013484"}, - {file = "pylint-2.8.3.tar.gz", hash = "sha256:0a049c5d47b629d9070c3932d13bff482b12119b6a241a93bc460b0be16953c8"}, + {file = "pylint-2.11.1-py3-none-any.whl", hash = "sha256:0f358e221c45cbd4dad2a1e4b883e75d28acdcccd29d40c76eb72b307269b126"}, + {file = "pylint-2.11.1.tar.gz", hash = "sha256:2c9843fff1a88ca0ad98a256806c82c5a8f86086e7ccbdb93297d86c3f90c436"}, ] pymongo = [ - {file = "pymongo-3.11.4-cp27-cp27m-macosx_10_14_intel.whl", hash = "sha256:b7efc7e7049ef366777cfd35437c18a4166bb50a5606a1c840ee3b9624b54fc9"}, - {file = "pymongo-3.11.4-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:517ba47ca04a55b1f50ee8df9fd97f6c37df5537d118fb2718952b8623860466"}, - {file = "pymongo-3.11.4-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:225c61e08fe517aede7912937939e09adf086c8e6f7e40d4c85ad678c2c2aea3"}, - {file = "pymongo-3.11.4-cp27-cp27m-win32.whl", hash = "sha256:e4e9db78b71db2b1684ee4ecc3e32c4600f18cdf76e6b9ae03e338e52ee4b168"}, - {file = "pymongo-3.11.4-cp27-cp27m-win_amd64.whl", hash = "sha256:8e0004b0393d72d76de94b4792a006cb960c1c65c7659930fbf9a81ce4341982"}, - {file = "pymongo-3.11.4-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:fedf0dee7a412ca6d1d6d92c158fe9cbaa8ea0cae90d268f9ccc0744de7a97d0"}, - {file = "pymongo-3.11.4-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:f947b359cc4769af8b49be7e37af01f05fcf15b401da2528021148e4a54426d1"}, - {file = "pymongo-3.11.4-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:3a3498a8326111221560e930f198b495ea6926937e249f475052ffc6893a6680"}, - {file = "pymongo-3.11.4-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:9a4f6e0b01df820ba9ed0b4e618ca83a1c089e48d4f268d0e00dcd49893d4549"}, - {file = "pymongo-3.11.4-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:d65bac5f6724d9ea6f0b5a0f0e4952fbbf209adcf6b5583b54c54bd2fcd74dc0"}, - {file = "pymongo-3.11.4-cp34-cp34m-win32.whl", hash = "sha256:15b083d1b789b230e5ac284442d9ecb113c93f3785a6824f748befaab803b812"}, - {file = "pymongo-3.11.4-cp34-cp34m-win_amd64.whl", hash = "sha256:f08665d3cc5abc2f770f472a9b5f720a9b3ab0b8b3bb97c7c1487515e5653d39"}, - {file = "pymongo-3.11.4-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:977b1d4f868986b4ba5d03c317fde4d3b66e687d74473130cd598e3103db34fa"}, - {file = "pymongo-3.11.4-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:510cd3bfabb63a07405b7b79fae63127e34c118b7531a2cbbafc7a24fd878594"}, - {file = "pymongo-3.11.4-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:071552b065e809d24c5653fcc14968cfd6fde4e279408640d5ac58e3353a3c5f"}, - {file = "pymongo-3.11.4-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:f4ba58157e8ae33ee86fadf9062c506e535afd904f07f9be32731f4410a23b7f"}, - {file = "pymongo-3.11.4-cp35-cp35m-manylinux2014_i686.whl", hash = "sha256:b413117210fa6d92664c3d860571e8e8727c3e8f2ff197276c5d0cb365abd3ad"}, - {file = "pymongo-3.11.4-cp35-cp35m-manylinux2014_ppc64le.whl", hash = "sha256:08b8723248730599c9803ae4c97b8f3f76c55219104303c88cb962a31e3bb5ee"}, - {file = "pymongo-3.11.4-cp35-cp35m-manylinux2014_s390x.whl", hash = "sha256:8a41fdc751dc4707a4fafb111c442411816a7c225ebb5cadb57599534b5d5372"}, - {file = "pymongo-3.11.4-cp35-cp35m-manylinux2014_x86_64.whl", hash = "sha256:f664ed7613b8b18f0ce5696b146776266a038c19c5cd6efffa08ecc189b01b73"}, - {file = "pymongo-3.11.4-cp35-cp35m-win32.whl", hash = "sha256:5c36428cc4f7fae56354db7f46677fd21222fc3cb1e8829549b851172033e043"}, - {file = "pymongo-3.11.4-cp35-cp35m-win_amd64.whl", hash = "sha256:d0a70151d7de8a3194cdc906bcc1a42e14594787c64b0c1c9c975e5a2af3e251"}, - {file = "pymongo-3.11.4-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:9b9298964389c180a063a9e8bac8a80ed42de11d04166b20249bfa0a489e0e0f"}, - {file = "pymongo-3.11.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:b2f41261b648cf5dee425f37ff14f4ad151c2f24b827052b402637158fd056ef"}, - {file = "pymongo-3.11.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:e02beaab433fd1104b2804f909e694cfbdb6578020740a9051597adc1cd4e19f"}, - {file = "pymongo-3.11.4-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:8898f6699f740ca93a0879ed07d8e6db02d68af889d0ebb3d13ab017e6b1af1e"}, - {file = "pymongo-3.11.4-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:62c29bc36a6d9be68fe7b5aaf1e120b4aa66a958d1e146601fcd583eb12cae7b"}, - {file = "pymongo-3.11.4-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:424799c71ff435094e5fb823c40eebb4500f0e048133311e9c026467e8ccebac"}, - {file = "pymongo-3.11.4-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:3551912f5c34d8dd7c32c6bb00ae04192af47f7b9f653608f107d19c1a21a194"}, - {file = "pymongo-3.11.4-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:5db59223ed1e634d842a053325f85f908359c6dac9c8ddce8ef145061fae7df8"}, - {file = "pymongo-3.11.4-cp36-cp36m-win32.whl", hash = "sha256:fea5cb1c63efe1399f0812532c7cf65458d38fd011be350bc5021dfcac39fba8"}, - {file = "pymongo-3.11.4-cp36-cp36m-win_amd64.whl", hash = "sha256:d4e62417e89b717a7bcd8576ac3108cd063225942cc91c5b37ff5465fdccd386"}, - {file = "pymongo-3.11.4-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:4c7e8c8e1e1918dcf6a652ac4b9d87164587c26fd2ce5dd81e73a5ab3b3d492f"}, - {file = "pymongo-3.11.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:38a7b5140a48fc91681cdb5cb95b7cd64640b43d19259fdd707fa9d5a715f2b2"}, - {file = "pymongo-3.11.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:aff3656af2add93f290731a6b8930b23b35c0c09569150130a58192b3ec6fc61"}, - {file = "pymongo-3.11.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:03be7ad107d252bb7325d4af6309fdd2c025d08854d35f0e7abc8bf048f4245e"}, - {file = "pymongo-3.11.4-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:6060794aac9f7b0644b299f46a9c6cbc0bc470bd01572f4134df140afd41ded6"}, - {file = "pymongo-3.11.4-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:73326b211e7410c8bd6a74500b1e3f392f39cf10862e243d00937e924f112c01"}, - {file = "pymongo-3.11.4-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:20d75ea11527331a2980ab04762a9d960bcfea9475c54bbeab777af880de61cd"}, - {file = "pymongo-3.11.4-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:3135dd574ef1286189f3f04a36c8b7a256376914f8cbbce66b94f13125ded858"}, - {file = "pymongo-3.11.4-cp37-cp37m-win32.whl", hash = "sha256:7c97554ea521f898753d9773891d0347ebfaddcc1dee2ad94850b163171bf1f1"}, - {file = "pymongo-3.11.4-cp37-cp37m-win_amd64.whl", hash = "sha256:a08c8b322b671857c81f4c30cd3c8df2895fd3c0e9358714f39e0ef8fb327702"}, - {file = "pymongo-3.11.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f3d851af3852f16ad4adc7ee054fd9c90a7a5063de94d815b7f6a88477b9f4c6"}, - {file = "pymongo-3.11.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:3bfc7689a1bacb9bcd2f2d5185d99507aa29f667a58dd8adaa43b5a348139e46"}, - {file = "pymongo-3.11.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:b8f94acd52e530a38f25e4d5bf7ddfdd4bea9193e718f58419def0d4406b58d3"}, - {file = "pymongo-3.11.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:e4b631688dfbdd61b5610e20b64b99d25771c6d52d9da73349342d2a0f11c46a"}, - {file = "pymongo-3.11.4-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:474e21d0e07cd09679e357d1dac76e570dab86665e79a9d3354b10a279ac6fb3"}, - {file = "pymongo-3.11.4-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:421d13523d11c57f57f257152bc4a6bb463aadf7a3918e9c96fefdd6be8dbfb8"}, - {file = "pymongo-3.11.4-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:0cabfc297f4cf921f15bc789a8fbfd7115eb9f813d3f47a74b609894bc66ab0d"}, - {file = "pymongo-3.11.4-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:fe4189846448df013cd9df11bba38ddf78043f8c290a9f06430732a7a8601cce"}, - {file = "pymongo-3.11.4-cp38-cp38-win32.whl", hash = "sha256:eb4d176394c37a76e8b0afe54b12d58614a67a60a7f8c0dd3a5afbb013c01092"}, - {file = "pymongo-3.11.4-cp38-cp38-win_amd64.whl", hash = "sha256:fffff7bfb6799a763d3742c59c6ee7ffadda21abed557637bc44ed1080876484"}, - {file = "pymongo-3.11.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:13acf6164ead81c9fc2afa0e1ea6d6134352973ce2bb35496834fee057063c04"}, - {file = "pymongo-3.11.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d360e5d5dd3d55bf5d1776964625018d85b937d1032bae1926dd52253decd0db"}, - {file = "pymongo-3.11.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:0aaf4d44f1f819360f9432df538d54bbf850f18152f34e20337c01b828479171"}, - {file = "pymongo-3.11.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:08bda7b2c522ff9f1e554570da16298271ebb0c56ab9699446aacba249008988"}, - {file = "pymongo-3.11.4-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:1a994a42f49dab5b6287e499be7d3d2751776486229980d8857ad53b8333d469"}, - {file = "pymongo-3.11.4-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:161fcd3281c42f644aa8dec7753cca2af03ce654e17d76da4f0dab34a12480ca"}, - {file = "pymongo-3.11.4-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:78f07961f4f214ea8e80be63cffd5cc158eb06cd922ffbf6c7155b11728f28f9"}, - {file = "pymongo-3.11.4-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:ad31f184dcd3271de26ab1f9c51574afb99e1b0e484ab1da3641256b723e4994"}, - {file = "pymongo-3.11.4-cp39-cp39-win32.whl", hash = "sha256:5e606846c049ed40940524057bfdf1105af6066688c0e6a1a3ce2038589bae70"}, - {file = "pymongo-3.11.4-cp39-cp39-win_amd64.whl", hash = "sha256:3491c7de09e44eded16824cb58cf9b5cc1dc6f066a0bb7aa69929d02aa53b828"}, - {file = "pymongo-3.11.4-py2.7-macosx-10.14-intel.egg", hash = "sha256:506a6dab4c7ffdcacdf0b8e70bd20eb2e77fa994519547c9d88d676400fcad58"}, - {file = "pymongo-3.11.4.tar.gz", hash = "sha256:539d4cb1b16b57026999c53e5aab857fe706e70ae5310cc8c232479923f932e6"}, + {file = "pymongo-3.12.1-cp27-cp27m-macosx_10_14_intel.whl", hash = "sha256:c4653830375ab019b86d218c749ad38908b74182b2863d09936aa8d7f990d30e"}, + {file = "pymongo-3.12.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:2462a68f6675da548e333fa299d8e9807e00f95a4d198cfe9194d7be69f40c9b"}, + {file = "pymongo-3.12.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:4168b6c425d783e81723fc3dc382d374a228ff29530436a472a36d9f27593e73"}, + {file = "pymongo-3.12.1-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:36806ee53a85c3ba73939652f2ced2961e6a77cfbae385cd83f2e24cd97964b7"}, + {file = "pymongo-3.12.1-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:bf2d9d62178bb5c05e77d40becf89c309b1966fbcfb5c306238f81bf1ec2d6a2"}, + {file = "pymongo-3.12.1-cp27-cp27m-win32.whl", hash = "sha256:75c7ef67b4b8ec070e7a4740764f6c03ec9246b59d95e2ae45c029d41cb9efa1"}, + {file = "pymongo-3.12.1-cp27-cp27m-win_amd64.whl", hash = "sha256:49b0d92724d3fce1174fd30b0b428595072d5c6b14d6203e46a9ea347ae7b439"}, + {file = "pymongo-3.12.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cef2675004d85d85a4ccc24730b73a99931547368d18ceeed1259a2d9fcddbc1"}, + {file = "pymongo-3.12.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:5e3833c001a04aa06a28c6fd9628256862a654c09b0f81c07734b5629bc014ab"}, + {file = "pymongo-3.12.1-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6a96c04ce39d66df60d9ce89f4c254c4967bc7d9e2e2c52adc58f47be826ee96"}, + {file = "pymongo-3.12.1-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c2a17752f97a942bdb4ff4a0516a67c5ade1658ebe1ab2edacdec0b42e39fa75"}, + {file = "pymongo-3.12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:02e0c088f189ca69fac094cb5f851b43bbbd7cec42114495777d4d8f297f7f8a"}, + {file = "pymongo-3.12.1-cp310-cp310-manylinux1_i686.whl", hash = "sha256:45d6b47d70ed44e3c40bef618ed61866c48176e7e5dff80d06d8b1a6192e8584"}, + {file = "pymongo-3.12.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:891f541c7ed29b95799da0cd249ae1db1842777b564e8205a197b038c5df6135"}, + {file = "pymongo-3.12.1-cp310-cp310-manylinux2014_i686.whl", hash = "sha256:dc4749c230a71b34db50ac2481d9008bb17b67c92671c443c3b40e192fbea78e"}, + {file = "pymongo-3.12.1-cp310-cp310-manylinux2014_ppc64le.whl", hash = "sha256:aa434534cc91f51a85e3099dc257ee8034b3d2be77f2ca58fb335a686e3a681f"}, + {file = "pymongo-3.12.1-cp310-cp310-manylinux2014_s390x.whl", hash = "sha256:180b405e17b90a877ea5dbc5efe7f4c171af4c89323148e100c0f12cedb86f12"}, + {file = "pymongo-3.12.1-cp310-cp310-manylinux2014_x86_64.whl", hash = "sha256:a472ca3d43d33e596ff5836c6cc71c3e61be33f44fe1cfdab4a1100f4af60333"}, + {file = "pymongo-3.12.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe16517b275031d61261a4e3941c411fb7c46a9cd012f02381b56e7907cc9e06"}, + {file = "pymongo-3.12.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c0947d7be30335cb4c3d5d0983d8ebc8294ae52503cf1d596c926f7e7183900b"}, + {file = "pymongo-3.12.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:adb37bf22d25a51b84d989a2a5c770d4514ac590201eea1cb50ce8c9c5257f1d"}, + {file = "pymongo-3.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f5fe59328838fa28958cc06ecf94be585726b97d637012f168bc3c7abe4fd81"}, + {file = "pymongo-3.12.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87114b995506e7584cf3daf891e419b5f6e7e383e7df6267494da3a76312aa22"}, + {file = "pymongo-3.12.1-cp310-cp310-win32.whl", hash = "sha256:4f4bc64fe9cbd70d46f519f1e88c9e4677f7af18ab9cd4942abce2bcfa7549c3"}, + {file = "pymongo-3.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:8f87f53c9cd89010ae45490ec2c963ff18b31f5f290dc08b04151709589fe8d9"}, + {file = "pymongo-3.12.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:37a63da5ee623acdf98e6d511171c8a5827a6106b0712c18af4441ef4f11e6be"}, + {file = "pymongo-3.12.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:b1b06038c9940a49c73db0aeb0f6809b308e198da1326171768cf68d843af521"}, + {file = "pymongo-3.12.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:ab27d6d7d41a66d9e54269a290d27cd5c74f08e9add0054a754b4821026c4f42"}, + {file = "pymongo-3.12.1-cp34-cp34m-win32.whl", hash = "sha256:63be03f7ae1e15e72a234637ec7941ef229c7ab252c9ff6af48bba1e5418961c"}, + {file = "pymongo-3.12.1-cp34-cp34m-win_amd64.whl", hash = "sha256:56feb80ea1f5334ccab9bd16a5161571ab70392e51fcc752fb8a1dc67125f663"}, + {file = "pymongo-3.12.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:a81e52dbf95f236a0c89a5abcd2b6e1331da0c0312f471c73fae76c79d2acf6b"}, + {file = "pymongo-3.12.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:712de1876608fd5d76abc3fc8ec55077278dd5044073fbe9492631c9a2c58351"}, + {file = "pymongo-3.12.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:47ed77f62c8417a86f9ad158b803f3459a636386cb9d3d4e9e7d6a82d051f907"}, + {file = "pymongo-3.12.1-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1fa6f08ddb6975371777f97592d35c771e713ee2250e55618148a5e57e260aff"}, + {file = "pymongo-3.12.1-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:3a2fcbd04273a509fa85285d9eccf17ab65ce440bd4f5e5a58c978e563cd9e9a"}, + {file = "pymongo-3.12.1-cp35-cp35m-win32.whl", hash = "sha256:d1b98539b0de822b6f717498e59ae3e5ae2e7f564370ab513e6d0c060753e447"}, + {file = "pymongo-3.12.1-cp35-cp35m-win_amd64.whl", hash = "sha256:c660fd1e4a4b52f79f7d134a3d31d452948477b7f46ff5061074a534c5805ba6"}, + {file = "pymongo-3.12.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:460bdaa3f65ddb5b7474ae08589a1763b5da1a78b8348351b9ba1c63b459d67d"}, + {file = "pymongo-3.12.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:1d55982e5335925c55e2b87467043866ce72bd30ea7e7e3eeed6ec3d95a806d4"}, + {file = "pymongo-3.12.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:67e0b2ad3692f6d0335ae231a40de55ec395b6c2e971ad6f55b162244d1ec542"}, + {file = "pymongo-3.12.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:515e4708d6567901ffc06476a38abe2c9093733f52638235d9f149579c1d3de0"}, + {file = "pymongo-3.12.1-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:ed20ec5a01c43254f6047c5d8124b70d28e39f128c8ad960b437644fe94e1827"}, + {file = "pymongo-3.12.1-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:e2bccadbe313b11704160aaba5eec95d2da1aa663f02f41d2d1520d02bbbdcd5"}, + {file = "pymongo-3.12.1-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:ef8b927813c27c3bdfc82c55682d7767403bcdadfd9f9c0fc49f4be4553a877b"}, + {file = "pymongo-3.12.1-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:2d3abe548a280b49269c7907d5b71199882510c484d680a5ea7860f30c4a695f"}, + {file = "pymongo-3.12.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e30cce3cc86d6082c8596b3fbee0d4f54bc4d337a4fa1bf536920e2e319e24f0"}, + {file = "pymongo-3.12.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fe3ae4294d593da54862f0140fdcc89d1aeeb94258ca97f094119ed7f0e5882d"}, + {file = "pymongo-3.12.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9641be893ccce7d192a0094efd0a0d9f1783a1ebf314b4128f8a27bfadb8a77c"}, + {file = "pymongo-3.12.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13a7c6d055af58a1e9c505e736da8b6a2e95ccc8cec10b008143f7a536e5de8a"}, + {file = "pymongo-3.12.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25fd76deabe9ea37c8360c362b32f702cc095a208dd1c5328189938ca7685847"}, + {file = "pymongo-3.12.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e841695b5dbea38909ab2dbf17e91e9a823412d8d88d1ef77f1b94a7bc551c0f"}, + {file = "pymongo-3.12.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6ead0126fb4424c6c6a4fdc603d699a9db7c03cdb8eac374c352a75fec8a820a"}, + {file = "pymongo-3.12.1-cp36-cp36m-win32.whl", hash = "sha256:a5dbeeea6a375fbd79448b48a54c46fc9351611a03ef8398d2a40b684ce46194"}, + {file = "pymongo-3.12.1-cp36-cp36m-win_amd64.whl", hash = "sha256:87db421c9eb915b8d9a9a13c5b2ee338350e36ee83e26ff0adfc48abc5db3ac3"}, + {file = "pymongo-3.12.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8851544168703fb519e95556e3b463fca4beeef7ed3f731d81a68c8268515d9d"}, + {file = "pymongo-3.12.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:7d8cdd2f070c71366e64990653522cce84b08dc26ab0d1fa19aa8d14ee0cf9ba"}, + {file = "pymongo-3.12.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:51437c77030bed72d57d8a61e22758e3c389b13fea7787c808030002bb05ca39"}, + {file = "pymongo-3.12.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:f43cacda46fc188f998e6d308afe1c61ff41dcb300949f4cbf731e9a0a5eb2d3"}, + {file = "pymongo-3.12.1-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:1a7b138a04fdd17849930dc8bf664002e17db38448850bfb96d200c9c5a8b3a1"}, + {file = "pymongo-3.12.1-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:444c00ebc20f2f9dc62e34f7dc9453dc2f5f5a72419c8dccad6e26d546c35712"}, + {file = "pymongo-3.12.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:81ce5f871f5d8e82615c8bd0b34b68a9650204c8b1a04ce7890d58c98eb66e39"}, + {file = "pymongo-3.12.1-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:837cdef094f39c6f4a2967abc646a412999c2540fbf5d3cce1dd3b671f4b876c"}, + {file = "pymongo-3.12.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2174d3279b8e2b6d7613b338f684cd78ff7adf1e7ec5b7b7bde5609a129c9898"}, + {file = "pymongo-3.12.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:303531649fa45f96b694054c1aa02f79bda32ef57affe42c5c339336717eed74"}, + {file = "pymongo-3.12.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1821ce4e5a293313947fd017bbd2d2535aa6309680fa29b33d0442d15da296ec"}, + {file = "pymongo-3.12.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15dae01341571d0af51526b7a21648ca575e9375e16ba045c9860848dfa8952f"}, + {file = "pymongo-3.12.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fcc021530b7c71069132fe4846d95a3cdd74d143adc2f7e398d5fabf610f111c"}, + {file = "pymongo-3.12.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f44bea60fd2178d7153deef9621c4b526a93939da30010bba24d3408a98b0f79"}, + {file = "pymongo-3.12.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6f0f0a10f128ea0898e607d351ebfabf70941494fc94e87f12c76e2894d8e6c4"}, + {file = "pymongo-3.12.1-cp37-cp37m-win32.whl", hash = "sha256:afb16330ab6efbbf995375ad94e970fa2f89bb46bd10d854b7047620fdb0d67d"}, + {file = "pymongo-3.12.1-cp37-cp37m-win_amd64.whl", hash = "sha256:dcf906c1f7a33e4222e4bff18da1554d69323bc4dd95fe867a6fa80709ee5f93"}, + {file = "pymongo-3.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9b62d84478f471fdb0dcea3876acff38f146bd23cbdbed15074fb4622064ec2e"}, + {file = "pymongo-3.12.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:48722e91981bb22a16b0431ea01da3e1cc5b96805634d3b8d3c2a5315c1ce7f1"}, + {file = "pymongo-3.12.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:d6c6989c10008ac70c2bb2ad2b940fcfe883712746c89f7e3308c14c213a70d7"}, + {file = "pymongo-3.12.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:573e2387d0686976642142c50740dfc4d3494cc627e2a7d22782b99f70879055"}, + {file = "pymongo-3.12.1-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:7117bfd8827cfe550f65a3c399dcd6e02226197a91c6d11a3540c3e8efc686d6"}, + {file = "pymongo-3.12.1-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:6eb6789f26c398c383225e1313c8e75a7d290d323b8eaf65f3f3ddd0eb8a5a3c"}, + {file = "pymongo-3.12.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:138248c542051eb462f88b50b0267bd5286d6661064bab06faa0ef6ac30cdb4b"}, + {file = "pymongo-3.12.1-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:7abc87e45b572eb6d17a50422e69a9e5d6f13e691e821fe2312df512500faa50"}, + {file = "pymongo-3.12.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7430f3987d232e782304c109be1d0e6fff46ca6405cb2479e4d8d08cd29541e"}, + {file = "pymongo-3.12.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cb48ff6cc6109190e1ccf8ea1fc71cc244c9185813ce7d1c415dce991cfb8709"}, + {file = "pymongo-3.12.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:68409171ab2aa7ccd6e8e839233e4b8ddeec246383c9a3698614e814739356f9"}, + {file = "pymongo-3.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13d74bf3435c1e58d8fafccc0d5e87f246ae2c6e9cbef4b35e32a1c3759e354f"}, + {file = "pymongo-3.12.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:849e641cfed05c75d772f9e9018f42c5fbd00655d43d52da1b9c56346fd3e4cc"}, + {file = "pymongo-3.12.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5183b698d6542219e4135de583b57bc6286bd37df7f645b688278eb919bfa785"}, + {file = "pymongo-3.12.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:65f159c445761cab04b665fc448b3fc008aebc98e54fdcbfd1aff195ef1b1408"}, + {file = "pymongo-3.12.1-cp38-cp38-win32.whl", hash = "sha256:3b40e36d3036bfe69ba63ec8e746a390721f75467085a0384b528e1dda532c69"}, + {file = "pymongo-3.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:58a67b3800476232f9989e533d0244060309451b436d46670a53e6d189f1a7e7"}, + {file = "pymongo-3.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db3efec9dcecd96555d752215797816da40315d61878f90ca39c8e269791bf17"}, + {file = "pymongo-3.12.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:bfd073fea04061019a103a288847846b5ef40dfa2f73b940ed61e399ca95314f"}, + {file = "pymongo-3.12.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:5067c04d3b19c820faac6342854d887ade58e8d38c3db79b68c2a102bbb100e7"}, + {file = "pymongo-3.12.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:1c4e51a3b69789b6f468a8e881a13f2d1e8f5e99e41f80fd44845e6ec0f701e1"}, + {file = "pymongo-3.12.1-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:2fa101bb23619120673899694a65b094364269e597f551a87c4bdae3a474d726"}, + {file = "pymongo-3.12.1-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:eb65ec0255a0fccc47c87d44e505ef5180bfd71690bd5f84161b1f23949fb209"}, + {file = "pymongo-3.12.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:ed751a20840a31242e7bea566fcf93ba75bc11b33afe2777bbf46069c1af5094"}, + {file = "pymongo-3.12.1-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:17238115e6d37f5423b046cb829f1ca02c4ea7edb163f5b8b88e0c975dc3fec9"}, + {file = "pymongo-3.12.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fda3b3fb5c0d159195ab834b322a23808f1b059bcc7e475765abeddee6a2529"}, + {file = "pymongo-3.12.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6235bf2157aa46e53568ed79b70603aa8874baa202d5d1de82fa0eb917696e73"}, + {file = "pymongo-3.12.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d6428b8b422ba5205140e8be11722fa7292a0bedaa8bc80fb34c92eb19ba45"}, + {file = "pymongo-3.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e6d1cf4bd6552b5f519432cce1530c09e6b0aab98d44803b991f7e880bd332"}, + {file = "pymongo-3.12.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:287c2a0063267c1458c4ddf528b44063ce7f376a6436eea5bccd7f625bbc3b5e"}, + {file = "pymongo-3.12.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4a2d73a9281faefb273a5448f6d25f44ebd311ada9eb79b6801ae890508fe231"}, + {file = "pymongo-3.12.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6f07888e3b73c0dfa46f12d098760494f5f23fd66923a6615edfe486e6a7649c"}, + {file = "pymongo-3.12.1-cp39-cp39-win32.whl", hash = "sha256:77dddf596fb065de29fb39992fbc81301f7fd0003be649b7fa7448c77ca53bed"}, + {file = "pymongo-3.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:979e34db4f3dc5710c18db437aaf282f691092b352e708cb2afd4df287698c76"}, + {file = "pymongo-3.12.1-py2.7-macosx-10.14-intel.egg", hash = "sha256:c04e84ccf590933a266180286d8b6a5fc844078a5d934432628301bd8b5f9ca7"}, + {file = "pymongo-3.12.1.tar.gz", hash = "sha256:704879b6a54c45ad76cea7c6789c1ae7185050acea7afd15b58318fa1932ed45"}, ] pynacl = [ {file = "PyNaCl-1.4.0-cp27-cp27m-macosx_10_10_x86_64.whl", hash = "sha256:ea6841bc3a76fa4942ce00f3bda7d436fda21e2d91602b9e21b7ca9ecab8f3ff"}, @@ -2306,24 +2382,30 @@ pynacl = [ {file = "PyNaCl-1.4.0.tar.gz", hash = "sha256:54e9a2c849c742006516ad56a88f5c74bf2ce92c9f67435187c3c5953b346505"}, ] pynput = [ - {file = "pynput-1.7.3-py2.py3-none-any.whl", hash = "sha256:fea5777454f896bd79d35393088cd29a089f3b2da166f0848a922b1d5a807d4f"}, - {file = "pynput-1.7.3-py3.8.egg", hash = "sha256:6626e8ea9ca482bb5628a7169e1193824e382c4ad3053e40f4f24f41ee7b41c9"}, - {file = "pynput-1.7.3.tar.gz", hash = "sha256:4e50b1a0ab86847e87e58f6d1993688b9a44f9f4c88d4712315ea8eb552ef828"}, + {file = "pynput-1.7.4-py2.py3-none-any.whl", hash = "sha256:f78502cb2abd101721d867451bf315a4e1334666372f8682651393f16e1d2d9b"}, + {file = "pynput-1.7.4-py3.9.egg", hash = "sha256:225926bf5e98d36738911112c72e19e0cba830aafee3882ef8661c8d9cfb3b63"}, + {file = "pynput-1.7.4.tar.gz", hash = "sha256:16fecc4d1e53a28fb7c669c79e189c3f2cde14a08d6b457c3da07075c82f3b4c"}, ] pyobjc-core = [ {file = "pyobjc-core-7.3.tar.gz", hash = "sha256:5081aedf8bb40aac1a8ad95adac9e44e148a882686ded614adf46bb67fd67574"}, - {file = "pyobjc_core-7.3-1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a1f1e6b457127cbf2b5bd2b94520a7c89fb590b739911eadb2b0499a3a5b0e6f"}, - {file = "pyobjc_core-7.3-1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:ed708cc47bae8b711f81f252af09898a5f986c7a38cec5ad5623d571d328bff8"}, {file = "pyobjc_core-7.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4e93ad769a20b908778fe950f62a843a6d8f0fa71996e5f3cc9fab5ae7d17771"}, {file = "pyobjc_core-7.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9f63fd37bbf3785af4ddb2f86cad5ca81c62cfc7d1c0099637ca18343c3656c1"}, {file = "pyobjc_core-7.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e9b1311f72f2e170742a7ee3a8149f52c35158dc024a21e88d6f1e52ba5d718b"}, {file = "pyobjc_core-7.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8d5e12a0729dfd1d998a861998b422d0a3e41923d75ea229bacf31372c831d7b"}, {file = "pyobjc_core-7.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:efdee8c4884405e0c0186c57f87d7bfaa0abc1f50b18e865db3caea3a1f329b9"}, ] +pyobjc-framework-applicationservices = [ + {file = "pyobjc-framework-ApplicationServices-7.3.tar.gz", hash = "sha256:1925ac30a817e557d1c08450005103bbf76ebd3ff473631fe9875070377b0b4d"}, + {file = "pyobjc_framework_ApplicationServices-7.3-1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:484e5b5e9f1757ad7e28799bb5d5d59ce861a3e5449f06fc3a0d05b998e9e6bb"}, + {file = "pyobjc_framework_ApplicationServices-7.3-1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:ec0c07775ff7034751306fa382117d12ae8e383b696cda1b2815dfd334c36ff7"}, + {file = "pyobjc_framework_ApplicationServices-7.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:daa4a9c51a927630fdd3d3f627e03ebc370aee3c397305db85a0a8ba4c28ae93"}, + {file = "pyobjc_framework_ApplicationServices-7.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:167aa21ee47b0ee6e4e399915371d183ae84880dc3813c27519e759acb9d20c9"}, + {file = "pyobjc_framework_ApplicationServices-7.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7a98f0f1e21465868f9dd32588ae71e5e6a4cb5c434d4158c9e12273fd7b8f27"}, + {file = "pyobjc_framework_ApplicationServices-7.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2d55796610e6293e83cc40183347e7f75a7c0682775cc19e5986945efa9cac1b"}, + {file = "pyobjc_framework_ApplicationServices-7.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:afd1ef147447fe7b06a271458eabb37ece6436705abf86265d7fb57310eca45f"}, +] pyobjc-framework-cocoa = [ {file = "pyobjc-framework-Cocoa-7.3.tar.gz", hash = "sha256:b18d05e7a795a3455ad191c3e43d6bfa673c2a4fd480bb1ccf57191051b80b7e"}, - {file = "pyobjc_framework_Cocoa-7.3-1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1e31376806e5de883a1d7c7c87d9ff2a8b09fc05d267e0dfce6e42409fb70c67"}, - {file = "pyobjc_framework_Cocoa-7.3-1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:d999387927284346035cb63ebb51f86331abc41f9376f9a6970e7f18207db392"}, {file = "pyobjc_framework_Cocoa-7.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9edffdfa6dd1f71f21b531c3e61fdd3e4d5d3bf6c5a528c98e88828cd60bac11"}, {file = "pyobjc_framework_Cocoa-7.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:35a6340437a4e0109a302150b7d1f6baf57004ccf74834f9e6062fcafe2fd8d7"}, {file = "pyobjc_framework_Cocoa-7.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7c3886f2608ab3ed02482f8b2ebf9f782b324c559e84b52cfd92dba8a1109872"}, @@ -2332,8 +2414,6 @@ pyobjc-framework-cocoa = [ ] pyobjc-framework-quartz = [ {file = "pyobjc-framework-Quartz-7.3.tar.gz", hash = "sha256:98812844c34262def980bdf60923a875cd43428a8375b6fd53bd2cd800eccf0b"}, - {file = "pyobjc_framework_Quartz-7.3-1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1139bc6874c0f8b58f0b8602015e0994198bc506a6bcec1071208de32b55ed26"}, - {file = "pyobjc_framework_Quartz-7.3-1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:d94a3ed7051266c52392ec07d3b5adbf28d4be83341a24df0d88639344dcd84f"}, {file = "pyobjc_framework_Quartz-7.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1ef18f5a16511ded65980bf4f5983ea5d35c88224dbad1b3112abd29c60413ea"}, {file = "pyobjc_framework_Quartz-7.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3b41eec8d4b10c7c7e011e2f9051367f5499ef315ba52dfbae573c3a2e05469c"}, {file = "pyobjc_framework_Quartz-7.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c65456ed045dfe1711d0298734e5a3ad670f8c770f7eb3b19979256c388bdd2"}, @@ -2345,37 +2425,57 @@ pyparsing = [ {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, ] pyrsistent = [ - {file = "pyrsistent-0.17.3.tar.gz", hash = "sha256:2e636185d9eb976a18a8a8e96efce62f2905fea90041958d8cc2a189756ebf3e"}, + {file = "pyrsistent-0.18.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f4c8cabb46ff8e5d61f56a037974228e978f26bfefce4f61a4b1ac0ba7a2ab72"}, + {file = "pyrsistent-0.18.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:da6e5e818d18459fa46fac0a4a4e543507fe1110e808101277c5a2b5bab0cd2d"}, + {file = "pyrsistent-0.18.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5e4395bbf841693eaebaa5bb5c8f5cdbb1d139e07c975c682ec4e4f8126e03d2"}, + {file = "pyrsistent-0.18.0-cp36-cp36m-win32.whl", hash = "sha256:527be2bfa8dc80f6f8ddd65242ba476a6c4fb4e3aedbf281dfbac1b1ed4165b1"}, + {file = "pyrsistent-0.18.0-cp36-cp36m-win_amd64.whl", hash = "sha256:2aaf19dc8ce517a8653746d98e962ef480ff34b6bc563fc067be6401ffb457c7"}, + {file = "pyrsistent-0.18.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58a70d93fb79dc585b21f9d72487b929a6fe58da0754fa4cb9f279bb92369396"}, + {file = "pyrsistent-0.18.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:4916c10896721e472ee12c95cdc2891ce5890898d2f9907b1b4ae0f53588b710"}, + {file = "pyrsistent-0.18.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:73ff61b1411e3fb0ba144b8f08d6749749775fe89688093e1efef9839d2dcc35"}, + {file = "pyrsistent-0.18.0-cp37-cp37m-win32.whl", hash = "sha256:b29b869cf58412ca5738d23691e96d8aff535e17390128a1a52717c9a109da4f"}, + {file = "pyrsistent-0.18.0-cp37-cp37m-win_amd64.whl", hash = "sha256:097b96f129dd36a8c9e33594e7ebb151b1515eb52cceb08474c10a5479e799f2"}, + {file = "pyrsistent-0.18.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:772e94c2c6864f2cd2ffbe58bb3bdefbe2a32afa0acb1a77e472aac831f83427"}, + {file = "pyrsistent-0.18.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:c1a9ff320fa699337e05edcaae79ef8c2880b52720bc031b219e5b5008ebbdef"}, + {file = "pyrsistent-0.18.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:cd3caef37a415fd0dae6148a1b6957a8c5f275a62cca02e18474608cb263640c"}, + {file = "pyrsistent-0.18.0-cp38-cp38-win32.whl", hash = "sha256:e79d94ca58fcafef6395f6352383fa1a76922268fa02caa2272fff501c2fdc78"}, + {file = "pyrsistent-0.18.0-cp38-cp38-win_amd64.whl", hash = "sha256:a0c772d791c38bbc77be659af29bb14c38ced151433592e326361610250c605b"}, + {file = "pyrsistent-0.18.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d5ec194c9c573aafaceebf05fc400656722793dac57f254cd4741f3c27ae57b4"}, + {file = "pyrsistent-0.18.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:6b5eed00e597b5b5773b4ca30bd48a5774ef1e96f2a45d105db5b4ebb4bca680"}, + {file = "pyrsistent-0.18.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:48578680353f41dca1ca3dc48629fb77dfc745128b56fc01096b2530c13fd426"}, + {file = "pyrsistent-0.18.0-cp39-cp39-win32.whl", hash = "sha256:f3ef98d7b76da5eb19c37fda834d50262ff9167c65658d1d8f974d2e4d90676b"}, + {file = "pyrsistent-0.18.0-cp39-cp39-win_amd64.whl", hash = "sha256:404e1f1d254d314d55adb8d87f4f465c8693d6f902f67eb6ef5b4526dc58e6ea"}, + {file = "pyrsistent-0.18.0.tar.gz", hash = "sha256:773c781216f8c2900b42a7b638d5b517bb134ae1acbebe4d1e8f1f41ea60eb4b"}, ] pysftp = [ {file = "pysftp-0.2.9.tar.gz", hash = "sha256:fbf55a802e74d663673400acd92d5373c1c7ee94d765b428d9f977567ac4854a"}, ] pytest = [ - {file = "pytest-6.2.4-py3-none-any.whl", hash = "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"}, - {file = "pytest-6.2.4.tar.gz", hash = "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b"}, + {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, + {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, ] pytest-cov = [ - {file = "pytest-cov-2.12.1.tar.gz", hash = "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7"}, - {file = "pytest_cov-2.12.1-py2.py3-none-any.whl", hash = "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a"}, + {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"}, + {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"}, ] pytest-print = [ - {file = "pytest_print-0.2.1-py2.py3-none-any.whl", hash = "sha256:2cfcdeee8b398457d3e3488f1fde5f8303b404c30187be5fcb4c7818df5f4529"}, - {file = "pytest_print-0.2.1.tar.gz", hash = "sha256:8f61e5bb2d031ee88d19a5a7695a0c863caee7b1478f1a82d080c2128b76ad83"}, + {file = "pytest_print-0.3.0-py2.py3-none-any.whl", hash = "sha256:53fb0f71d371f137ac2e7171d92f204eb45055580e8c7920df619d9b2ee45359"}, + {file = "pytest_print-0.3.0.tar.gz", hash = "sha256:769f1b1b0943b2941dbeeaac6985766e76b341130ed538f88c23ebcd7087b90d"}, ] python-dateutil = [ - {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"}, - {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"}, + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, ] python-xlib = [ - {file = "python-xlib-0.30.tar.gz", hash = "sha256:74131418faf9e7b83178c71d9d80297fbbd678abe99ae9258f5a20cd027acb5f"}, - {file = "python_xlib-0.30-py2.py3-none-any.whl", hash = "sha256:c4c92cd47e07588b2cbc7d52de18407b2902c3812d7cdec39cd2177b060828e2"}, + {file = "python-xlib-0.31.tar.gz", hash = "sha256:74d83a081f532bc07f6d7afcd6416ec38403d68f68b9b9dc9e1f28fbf2d799e9"}, + {file = "python_xlib-0.31-py2.py3-none-any.whl", hash = "sha256:1ec6ce0de73d9e6592ead666779a5732b384e5b8fb1f1886bd0a81cafa477759"}, ] python3-xlib = [ {file = "python3-xlib-0.15.tar.gz", hash = "sha256:dc4245f3ae4aa5949c1d112ee4723901ade37a96721ba9645f2bfa56e5b383f8"}, ] pytz = [ - {file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"}, - {file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"}, + {file = "pytz-2021.3-py2.py3-none-any.whl", hash = "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c"}, + {file = "pytz-2021.3.tar.gz", hash = "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326"}, ] pywin32 = [ {file = "pywin32-301-cp35-cp35m-win32.whl", hash = "sha256:93367c96e3a76dfe5003d8291ae16454ca7d84bb24d721e0b74a07610b7be4a7"}, @@ -2394,8 +2494,8 @@ pywin32-ctypes = [ {file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"}, ] "qt.py" = [ - {file = "Qt.py-1.3.3-py2.py3-none-any.whl", hash = "sha256:9e3f5417187c98d246918a9b27a9e1f8055e089bdb2b063a2739986bc19a3d2e"}, - {file = "Qt.py-1.3.3.tar.gz", hash = "sha256:601606127f70be9adc82c248d209d696cccbd1df242c24d3fb1a9e399f3ecaf1"}, + {file = "Qt.py-1.3.6-py2.py3-none-any.whl", hash = "sha256:7edf6048d07a6924707506b5ba34a6e05d66dde9a3f4e3a62f9996ccab0b91c7"}, + {file = "Qt.py-1.3.6.tar.gz", hash = "sha256:0d78656a2f814602eee304521c7bf5da0cec414818b3833712c77524294c404a"}, ] recommonmark = [ {file = "recommonmark-0.7.1-py2.py3-none-any.whl", hash = "sha256:1b1db69af0231efce3fa21b94ff627ea33dee7079a01dd0a7f8482c3da148b3f"}, @@ -2422,12 +2522,12 @@ six = [ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] slack-sdk = [ - {file = "slack_sdk-3.6.0-py2.py3-none-any.whl", hash = "sha256:e1b257923a1ef88b8620dd3abff94dc5b3eee16ef37975d101ba9e60123ac3af"}, - {file = "slack_sdk-3.6.0.tar.gz", hash = "sha256:195f044e02a2844579a7a26818ce323e85dde8de224730c859644918d793399e"}, + {file = "slack_sdk-3.11.2-py2.py3-none-any.whl", hash = "sha256:35245ec34c8549fbb5c43ccc17101afd725b3508bb784da46530b214f496bf93"}, + {file = "slack_sdk-3.11.2.tar.gz", hash = "sha256:131bf605894525c2d66da064677eabc19f53f02ce0f82a3f2fa130d4ec3bc1b0"}, ] smmap = [ - {file = "smmap-4.0.0-py2.py3-none-any.whl", hash = "sha256:a9a7479e4c572e2e775c404dcd3080c8dc49f39918c2cf74913d30c4c478e3c2"}, - {file = "smmap-4.0.0.tar.gz", hash = "sha256:7e65386bd122d45405ddf795637b7f7d2b532e7e401d46bbe3fb49b9986d5182"}, + {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, + {file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"}, ] snowballstemmer = [ {file = "snowballstemmer-2.1.0-py2.py3-none-any.whl", hash = "sha256:b51b447bea85f9968c13b650126a888aabd4cb4463fca868ec596826325dedc2"}, @@ -2438,16 +2538,16 @@ speedcopy = [ {file = "speedcopy-2.1.0.tar.gz", hash = "sha256:8bb1a6c735900b83901a7be84ba2175ed3887c13c6786f97dea48f2ea7d504c2"}, ] sphinx = [ - {file = "Sphinx-4.0.2-py3-none-any.whl", hash = "sha256:d1cb10bee9c4231f1700ec2e24a91be3f3a3aba066ea4ca9f3bbe47e59d5a1d4"}, - {file = "Sphinx-4.0.2.tar.gz", hash = "sha256:b5c2ae4120bf00c799ba9b3699bc895816d272d120080fbc967292f29b52b48c"}, + {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"}, {file = "sphinx_qt_documentation-0.3.tar.gz", hash = "sha256:f09a0c9d9e989172ba3e282b92bf55613bb23ad47315ec5b0d38536b343ac6c8"}, ] sphinx-rtd-theme = [ - {file = "sphinx_rtd_theme-0.5.2-py2.py3-none-any.whl", hash = "sha256:4a05bdbe8b1446d77a01e20a23ebc6777c74f43237035e76be89699308987d6f"}, - {file = "sphinx_rtd_theme-0.5.2.tar.gz", hash = "sha256:32bd3b5d13dc8186d7a42fc816a23d32e83a4827d7d9882948e7b837c232da5a"}, + {file = "sphinx_rtd_theme-0.5.1-py2.py3-none-any.whl", hash = "sha256:fa6bebd5ab9a73da8e102509a86f3fcc36dec04a0b52ea80e5a033b2aba00113"}, + {file = "sphinx_rtd_theme-0.5.1.tar.gz", hash = "sha256:eda689eda0c7301a80cf122dad28b1861e5605cbf455558f3775e1e8200e83a5"}, ] sphinxcontrib-applehelp = [ {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, @@ -2489,6 +2589,10 @@ toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] +tomli = [ + {file = "tomli-1.2.2-py3-none-any.whl", hash = "sha256:f04066f68f5554911363063a30b108d2b5a5b1a010aa8b6132af78489fe3aade"}, + {file = "tomli-1.2.2.tar.gz", hash = "sha256:c6ce0015eb38820eaf32b5db832dbc26deb3dd427bd5f6556cf0acac2c214fee"}, +] typed-ast = [ {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"}, {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"}, @@ -2522,17 +2626,17 @@ typed-ast = [ {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, ] typing-extensions = [ - {file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"}, - {file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"}, - {file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"}, + {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"}, + {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"}, + {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"}, ] uritemplate = [ {file = "uritemplate-3.0.1-py2.py3-none-any.whl", hash = "sha256:07620c3f3f8eed1f12600845892b0e036a2420acf513c53f7de0abd911a5894f"}, {file = "uritemplate-3.0.1.tar.gz", hash = "sha256:5af8ad10cec94f215e3f48112de2022e1d5a37ed427fbd88652fa908f2ab7cae"}, ] urllib3 = [ - {file = "urllib3-1.26.5-py2.py3-none-any.whl", hash = "sha256:753a0374df26658f99d826cfe40394a686d05985786d946fbe4165b5148f5a7c"}, - {file = "urllib3-1.26.5.tar.gz", hash = "sha256:a7acd0977125325f516bda9735fa7142b909a8d01e8b2e4c8108d0984e6e0098"}, + {file = "urllib3-1.26.7-py2.py3-none-any.whl", hash = "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"}, + {file = "urllib3-1.26.7.tar.gz", hash = "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece"}, ] wcwidth = [ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, @@ -2543,52 +2647,130 @@ websocket-client = [ {file = "websocket_client-0.59.0-py2.py3-none-any.whl", hash = "sha256:2e50d26ca593f70aba7b13a489435ef88b8fc3b5c5643c1ce8808ff9b40f0b32"}, ] wrapt = [ - {file = "wrapt-1.12.1.tar.gz", hash = "sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7"}, + {file = "wrapt-1.13.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3de7b4d3066cc610054e7aa2c005645e308df2f92be730aae3a47d42e910566a"}, + {file = "wrapt-1.13.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:8164069f775c698d15582bf6320a4f308c50d048c1c10cf7d7a341feaccf5df7"}, + {file = "wrapt-1.13.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9adee1891253670575028279de8365c3a02d3489a74a66d774c321472939a0b1"}, + {file = "wrapt-1.13.2-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:a70d876c9aba12d3bd7f8f1b05b419322c6789beb717044eea2c8690d35cb91b"}, + {file = "wrapt-1.13.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:3f87042623530bcffea038f824b63084180513c21e2e977291a9a7e65a66f13b"}, + {file = "wrapt-1.13.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:e634136f700a21e1fcead0c137f433dde928979538c14907640607d43537d468"}, + {file = "wrapt-1.13.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:3e33c138d1e3620b1e0cc6fd21e46c266393ed5dae0d595b7ed5a6b73ed57aa0"}, + {file = "wrapt-1.13.2-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:283e402e5357e104ac1e3fba5791220648e9af6fb14ad7d9cc059091af2b31d2"}, + {file = "wrapt-1.13.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:ccb34ce599cab7f36a4c90318697ead18312c67a9a76327b3f4f902af8f68ea1"}, + {file = "wrapt-1.13.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:fbad5ba74c46517e6488149514b2e2348d40df88cd6b52a83855b7a8bf04723f"}, + {file = "wrapt-1.13.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:724ed2bc9c91a2b9026e5adce310fa60c6e7c8760b03391445730b9789b9d108"}, + {file = "wrapt-1.13.2-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:83f2793ec6f3ef513ad8d5b9586f5ee6081cad132e6eae2ecb7eac1cc3decae0"}, + {file = "wrapt-1.13.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:0473d1558b93e314e84313cc611f6c86be779369f9d3734302bf185a4d2625b1"}, + {file = "wrapt-1.13.2-cp35-cp35m-win32.whl", hash = "sha256:15eee0e6fd07f48af2f66d0e6f2ff1916ffe9732d464d5e2390695296872cad9"}, + {file = "wrapt-1.13.2-cp35-cp35m-win_amd64.whl", hash = "sha256:bc85d17d90201afd88e3d25421da805e4e135012b5d1f149e4de2981394b2a52"}, + {file = "wrapt-1.13.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c6ee5f8734820c21b9b8bf705e99faba87f21566d20626568eeb0d62cbeaf23c"}, + {file = "wrapt-1.13.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:53c6706a1bcfb6436f1625511b95b812798a6d2ccc51359cd791e33722b5ea32"}, + {file = "wrapt-1.13.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fbe6aebc9559fed7ea27de51c2bf5c25ba2a4156cf0017556f72883f2496ee9a"}, + {file = "wrapt-1.13.2-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:0582180566e7a13030f896c2f1ac6a56134ab5f3c3f4c5538086f758b1caf3f2"}, + {file = "wrapt-1.13.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:bff0a59387a0a2951cb869251257b6553663329a1b5525b5226cab8c88dcbe7e"}, + {file = "wrapt-1.13.2-cp36-cp36m-win32.whl", hash = "sha256:df3eae297a5f1594d1feb790338120f717dac1fa7d6feed7b411f87e0f2401c7"}, + {file = "wrapt-1.13.2-cp36-cp36m-win_amd64.whl", hash = "sha256:1eb657ed84f4d3e6ad648483c8a80a0cf0a78922ef94caa87d327e2e1ad49b48"}, + {file = "wrapt-1.13.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a0cdedf681db878416c05e1831ec69691b0e6577ac7dca9d4f815632e3549580"}, + {file = "wrapt-1.13.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:87ee3c73bdfb4367b26c57259995935501829f00c7b3eed373e2ad19ec21e4e4"}, + {file = "wrapt-1.13.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:3e0d16eedc242d01a6f8cf0623e9cdc3b869329da3f97a15961d8864111d8cf0"}, + {file = "wrapt-1.13.2-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:8318088860968c07e741537030b1abdd8908ee2c71fbe4facdaade624a09e006"}, + {file = "wrapt-1.13.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:d90520616fce71c05dedeac3a0fe9991605f0acacd276e5f821842e454485a70"}, + {file = "wrapt-1.13.2-cp37-cp37m-win32.whl", hash = "sha256:22142afab65daffc95863d78effcbd31c19a8003eca73de59f321ee77f73cadb"}, + {file = "wrapt-1.13.2-cp37-cp37m-win_amd64.whl", hash = "sha256:d0d717e10f952df7ea41200c507cc7e24458f4c45b56c36ad418d2e79dacd1d4"}, + {file = "wrapt-1.13.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:593cb049ce1c391e0288523b30426c4430b26e74c7e6f6e2844bd99ac7ecc831"}, + {file = "wrapt-1.13.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:8860c8011a6961a651b1b9f46fdbc589ab63b0a50d645f7d92659618a3655867"}, + {file = "wrapt-1.13.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:ada5e29e59e2feb710589ca1c79fd989b1dd94d27079dc1d199ec954a6ecc724"}, + {file = "wrapt-1.13.2-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:fdede980273aeca591ad354608778365a3a310e0ecdd7a3587b38bc5be9b1808"}, + {file = "wrapt-1.13.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:af9480de8e63c5f959a092047aaf3d7077422ded84695b3398f5d49254af3e90"}, + {file = "wrapt-1.13.2-cp38-cp38-win32.whl", hash = "sha256:c65e623ea7556e39c4f0818200a046cbba7575a6b570ff36122c276fdd30ab0a"}, + {file = "wrapt-1.13.2-cp38-cp38-win_amd64.whl", hash = "sha256:b20703356cae1799080d0ad15085dc3213c1ac3f45e95afb9f12769b98231528"}, + {file = "wrapt-1.13.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1c5c4cf188b5643a97e87e2110bbd4f5bc491d54a5b90633837b34d5df6a03fe"}, + {file = "wrapt-1.13.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:82223f72eba6f63eafca87a0f614495ae5aa0126fe54947e2b8c023969e9f2d7"}, + {file = "wrapt-1.13.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:81a4cf257263b299263472d669692785f9c647e7dca01c18286b8f116dbf6b38"}, + {file = "wrapt-1.13.2-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:728e2d9b7a99dd955d3426f237b940fc74017c4a39b125fec913f575619ddfe9"}, + {file = "wrapt-1.13.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:7574de567dcd4858a2ffdf403088d6df8738b0e1eabea220553abf7c9048f59e"}, + {file = "wrapt-1.13.2-cp39-cp39-win32.whl", hash = "sha256:c7ac2c7a8e34bd06710605b21dd1f3576764443d68e069d2afba9b116014d072"}, + {file = "wrapt-1.13.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e6d1a8eeef415d7fb29fe017de0e48f45e45efd2d1bfda28fc50b7b330859ef"}, + {file = "wrapt-1.13.2.tar.gz", hash = "sha256:dca56cc5963a5fd7c2aa8607017753f534ee514e09103a6c55d2db70b50e7447"}, ] wsrpc-aiohttp = [ {file = "wsrpc-aiohttp-3.2.0.tar.gz", hash = "sha256:f467abc51bcdc760fc5aeb7041abdeef46eeca3928dc43dd6e7fa7a533563818"}, {file = "wsrpc_aiohttp-3.2.0-py3-none-any.whl", hash = "sha256:fa9b0bf5cb056898cb5c9f64cbc5eacb8a5dd18ab1b7f0cd4a2208b4a7fde282"}, ] yarl = [ - {file = "yarl-1.6.3-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:0355a701b3998dcd832d0dc47cc5dedf3874f966ac7f870e0f3a6788d802d434"}, - {file = "yarl-1.6.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:bafb450deef6861815ed579c7a6113a879a6ef58aed4c3a4be54400ae8871478"}, - {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:547f7665ad50fa8563150ed079f8e805e63dd85def6674c97efd78eed6c224a6"}, - {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:63f90b20ca654b3ecc7a8d62c03ffa46999595f0167d6450fa8383bab252987e"}, - {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:97b5bdc450d63c3ba30a127d018b866ea94e65655efaf889ebeabc20f7d12406"}, - {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:d8d07d102f17b68966e2de0e07bfd6e139c7c02ef06d3a0f8d2f0f055e13bb76"}, - {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:15263c3b0b47968c1d90daa89f21fcc889bb4b1aac5555580d74565de6836366"}, - {file = "yarl-1.6.3-cp36-cp36m-win32.whl", hash = "sha256:b5dfc9a40c198334f4f3f55880ecf910adebdcb2a0b9a9c23c9345faa9185721"}, - {file = "yarl-1.6.3-cp36-cp36m-win_amd64.whl", hash = "sha256:b2e9a456c121e26d13c29251f8267541bd75e6a1ccf9e859179701c36a078643"}, - {file = "yarl-1.6.3-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:ce3beb46a72d9f2190f9e1027886bfc513702d748047b548b05dab7dfb584d2e"}, - {file = "yarl-1.6.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2ce4c621d21326a4a5500c25031e102af589edb50c09b321049e388b3934eec3"}, - {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:d26608cf178efb8faa5ff0f2d2e77c208f471c5a3709e577a7b3fd0445703ac8"}, - {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:4c5bcfc3ed226bf6419f7a33982fb4b8ec2e45785a0561eb99274ebbf09fdd6a"}, - {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:4736eaee5626db8d9cda9eb5282028cc834e2aeb194e0d8b50217d707e98bb5c"}, - {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:68dc568889b1c13f1e4745c96b931cc94fdd0defe92a72c2b8ce01091b22e35f"}, - {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:7356644cbed76119d0b6bd32ffba704d30d747e0c217109d7979a7bc36c4d970"}, - {file = "yarl-1.6.3-cp37-cp37m-win32.whl", hash = "sha256:00d7ad91b6583602eb9c1d085a2cf281ada267e9a197e8b7cae487dadbfa293e"}, - {file = "yarl-1.6.3-cp37-cp37m-win_amd64.whl", hash = "sha256:69ee97c71fee1f63d04c945f56d5d726483c4762845400a6795a3b75d56b6c50"}, - {file = "yarl-1.6.3-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:e46fba844f4895b36f4c398c5af062a9808d1f26b2999c58909517384d5deda2"}, - {file = "yarl-1.6.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:31ede6e8c4329fb81c86706ba8f6bf661a924b53ba191b27aa5fcee5714d18ec"}, - {file = "yarl-1.6.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fcbb48a93e8699eae920f8d92f7160c03567b421bc17362a9ffbbd706a816f71"}, - {file = "yarl-1.6.3-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:72a660bdd24497e3e84f5519e57a9ee9220b6f3ac4d45056961bf22838ce20cc"}, - {file = "yarl-1.6.3-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:324ba3d3c6fee56e2e0b0d09bf5c73824b9f08234339d2b788af65e60040c959"}, - {file = "yarl-1.6.3-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:e6b5460dc5ad42ad2b36cca524491dfcaffbfd9c8df50508bddc354e787b8dc2"}, - {file = "yarl-1.6.3-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:6d6283d8e0631b617edf0fd726353cb76630b83a089a40933043894e7f6721e2"}, - {file = "yarl-1.6.3-cp38-cp38-win32.whl", hash = "sha256:9ede61b0854e267fd565e7527e2f2eb3ef8858b301319be0604177690e1a3896"}, - {file = "yarl-1.6.3-cp38-cp38-win_amd64.whl", hash = "sha256:f0b059678fd549c66b89bed03efcabb009075bd131c248ecdf087bdb6faba24a"}, - {file = "yarl-1.6.3-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:329412812ecfc94a57cd37c9d547579510a9e83c516bc069470db5f75684629e"}, - {file = "yarl-1.6.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:c49ff66d479d38ab863c50f7bb27dee97c6627c5fe60697de15529da9c3de724"}, - {file = "yarl-1.6.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f040bcc6725c821a4c0665f3aa96a4d0805a7aaf2caf266d256b8ed71b9f041c"}, - {file = "yarl-1.6.3-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:d5c32c82990e4ac4d8150fd7652b972216b204de4e83a122546dce571c1bdf25"}, - {file = "yarl-1.6.3-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:d597767fcd2c3dc49d6eea360c458b65643d1e4dbed91361cf5e36e53c1f8c96"}, - {file = "yarl-1.6.3-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:8aa3decd5e0e852dc68335abf5478a518b41bf2ab2f330fe44916399efedfae0"}, - {file = "yarl-1.6.3-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:73494d5b71099ae8cb8754f1df131c11d433b387efab7b51849e7e1e851f07a4"}, - {file = "yarl-1.6.3-cp39-cp39-win32.whl", hash = "sha256:5b883e458058f8d6099e4420f0cc2567989032b5f34b271c0827de9f1079a424"}, - {file = "yarl-1.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:4953fb0b4fdb7e08b2f3b3be80a00d28c5c8a2056bb066169de00e6501b986b6"}, - {file = "yarl-1.6.3.tar.gz", hash = "sha256:8a9066529240171b68893d60dca86a763eae2139dd42f42106b03cf4b426bf10"}, + {file = "yarl-1.7.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e35d8230e4b08d86ea65c32450533b906a8267a87b873f2954adeaecede85169"}, + {file = "yarl-1.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eb4b3f277880c314e47720b4b6bb2c85114ab3c04c5442c9bc7006b3787904d8"}, + {file = "yarl-1.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c7015dcedb91d90a138eebdc7e432aec8966e0147ab2a55f2df27b1904fa7291"}, + {file = "yarl-1.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb3e478175e15e00d659fb0354a6a8db71a7811a2a5052aed98048bc972e5d2b"}, + {file = "yarl-1.7.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8b8c409aa3a7966647e7c1c524846b362a6bcbbe120bf8a176431f940d2b9a2e"}, + {file = "yarl-1.7.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b22ea41c7e98170474a01e3eded1377d46b2dfaef45888a0005c683eaaa49285"}, + {file = "yarl-1.7.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a7dfc46add4cfe5578013dbc4127893edc69fe19132d2836ff2f6e49edc5ecd6"}, + {file = "yarl-1.7.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:82ff6f85f67500a4f74885d81659cd270eb24dfe692fe44e622b8a2fd57e7279"}, + {file = "yarl-1.7.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f3cd2158b2ed0fb25c6811adfdcc47224efe075f2d68a750071dacc03a7a66e4"}, + {file = "yarl-1.7.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:59c0f13f9592820c51280d1cf811294d753e4a18baf90f0139d1dc93d4b6fc5f"}, + {file = "yarl-1.7.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7f7655ad83d1a8afa48435a449bf2f3009293da1604f5dd95b5ddcf5f673bd69"}, + {file = "yarl-1.7.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:aa9f0d9b62d15182341b3e9816582f46182cab91c1a57b2d308b9a3c4e2c4f78"}, + {file = "yarl-1.7.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fdd1b90c225a653b1bd1c0cae8edf1957892b9a09c8bf7ee6321eeb8208eac0f"}, + {file = "yarl-1.7.0-cp310-cp310-win32.whl", hash = "sha256:7c8d0bb76eabc5299db203e952ec55f8f4c53f08e0df4285aac8c92bd9e12675"}, + {file = "yarl-1.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:622a36fa779efb4ff9eff5fe52730ff17521431379851a31e040958fc251670c"}, + {file = "yarl-1.7.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3d461b7a8e139b9e4b41f62eb417ffa0b98d1c46d4caf14c845e6a3b349c0bb1"}, + {file = "yarl-1.7.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81cfacdd1e40bc931b5519499342efa388d24d262c30a3d31187bfa04f4a7001"}, + {file = "yarl-1.7.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:821b978f2152be7695d4331ef0621d207aedf9bbd591ba23a63412a3efc29a01"}, + {file = "yarl-1.7.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b64bd24c8c9a487f4a12260dc26732bf41028816dbf0c458f17864fbebdb3131"}, + {file = "yarl-1.7.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:98c9ddb92b60a83c21be42c776d3d9d5ec632a762a094c41bda37b7dfbd2cd83"}, + {file = "yarl-1.7.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a532d75ca74431c053a88a802e161fb3d651b8bf5821a3440bc3616e38754583"}, + {file = "yarl-1.7.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:053e09817eafb892e94e172d05406c1b3a22a93bc68f6eff5198363a3d764459"}, + {file = "yarl-1.7.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:98c51f02d542945d306c8e934aa2c1e66ba5e9c1c86b5bf37f3a51c8a747067e"}, + {file = "yarl-1.7.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:15ec41a5a5fdb7bace6d7b16701f9440007a82734f69127c0fbf6d87e10f4a1e"}, + {file = "yarl-1.7.0-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:a7f08819dba1e1255d6991ed37448a1bf4b1352c004bcd899b9da0c47958513d"}, + {file = "yarl-1.7.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8e3ffab21db0542ffd1887f3b9575ddd58961f2cf61429cb6458afc00c4581e0"}, + {file = "yarl-1.7.0-cp36-cp36m-win32.whl", hash = "sha256:50127634f519b2956005891507e3aa4ac345f66a7ea7bbc2d7dcba7401f41898"}, + {file = "yarl-1.7.0-cp36-cp36m-win_amd64.whl", hash = "sha256:36ec44f15193f6d5288d42ebb8e751b967ebdfb72d6830983838d45ab18edb4f"}, + {file = "yarl-1.7.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ec1b5a25a25c880c976d0bb3d107def085bb08dbb3db7f4442e0a2b980359d24"}, + {file = "yarl-1.7.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b36f5a63c891f813c6f04ef19675b382efc190fd5ce7e10ab19386d2548bca06"}, + {file = "yarl-1.7.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38173b8c3a29945e7ecade9a3f6ff39581eee8201338ee6a2c8882db5df3e806"}, + {file = "yarl-1.7.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ba402f32184f0b405fb281b93bd0d8ab7e3257735b57b62a6ed2e94cdf4fe50"}, + {file = "yarl-1.7.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:be52bc5208d767cdd8308a9e93059b3b36d1e048fecbea0e0346d0d24a76adc0"}, + {file = "yarl-1.7.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:08c2044a956f4ef30405f2f433ce77f1f57c2c773bf81ae43201917831044d5a"}, + {file = "yarl-1.7.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:484d61c047c45670ef5967653a1d0783e232c54bf9dd786a7737036828fa8d54"}, + {file = "yarl-1.7.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b7de92a4af85cfcaf4081f8aa6165b1d63ee5de150af3ee85f954145f93105a7"}, + {file = "yarl-1.7.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:376e41775aab79c5575534924a386c8e0f1a5d91db69fc6133fd27a489bcaf10"}, + {file = "yarl-1.7.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:8a8b10d0e7bac154f959b709fcea593cda527b234119311eb950096653816a86"}, + {file = "yarl-1.7.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:f46cd4c43e6175030e2a56def8f1d83b64e6706eeb2bb9ab0ef4756f65eab23f"}, + {file = "yarl-1.7.0-cp37-cp37m-win32.whl", hash = "sha256:b28cfb46140efe1a6092b8c5c4994a1fe70dc83c38fbcea4992401e0c6fb9cce"}, + {file = "yarl-1.7.0-cp37-cp37m-win_amd64.whl", hash = "sha256:9624154ec9c02a776802da1086eed7f5034bd1971977f5146233869c2ac80297"}, + {file = "yarl-1.7.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:69945d13e1bbf81784a9bc48824feb9cd66491e6a503d4e83f6cd7c7cc861361"}, + {file = "yarl-1.7.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:46a742ed9e363bd01be64160ce7520e92e11989bd4cb224403cfd31c101cc83d"}, + {file = "yarl-1.7.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cb4ff1ac7cb4500f43581b3f4cbd627d702143aa6be1fdc1fa3ebffaf4dc1be5"}, + {file = "yarl-1.7.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ad51e17cd65ea3debb0e10f0120cf8dd987c741fe423ed2285087368090b33d"}, + {file = "yarl-1.7.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7e37786ea89a5d3ffbbf318ea9790926f8dfda83858544f128553c347ad143c6"}, + {file = "yarl-1.7.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c63c1e208f800daad71715786bfeb1cecdc595d87e2e9b1cd234fd6e597fd71d"}, + {file = "yarl-1.7.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:91cbe24300c11835ef186436363352b3257db7af165e0a767f4f17aa25761388"}, + {file = "yarl-1.7.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e510dbec7c59d32eaa61ffa48173d5e3d7170a67f4a03e8f5e2e9e3971aca622"}, + {file = "yarl-1.7.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3def6e681cc02397e5d8141ee97b41d02932b2bcf0fb34532ad62855eab7c60e"}, + {file = "yarl-1.7.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:263c81b94e6431942b27f6f671fa62f430a0a5c14bb255f2ab69eeb9b2b66ff7"}, + {file = "yarl-1.7.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:e78c91faefe88d601ddd16e3882918dbde20577a2438e2320f8239c8b7507b8f"}, + {file = "yarl-1.7.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:22b2430c49713bfb2f0a0dd4a8d7aab218b28476ba86fd1c78ad8899462cbcf2"}, + {file = "yarl-1.7.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2e7ad9db939082f5d0b9269cfd92c025cb8f2fbbb1f1b9dc5a393c639db5bd92"}, + {file = "yarl-1.7.0-cp38-cp38-win32.whl", hash = "sha256:3a31e4a8dcb1beaf167b7e7af61b88cb961b220db8d3ba1c839723630e57eef7"}, + {file = "yarl-1.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:d579957439933d752358c6a300c93110f84aae67b63dd0c19dde6ecbf4056f6b"}, + {file = "yarl-1.7.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:87721b549505a546eb003252185103b5ec8147de6d3ad3714d148a5a67b6fe53"}, + {file = "yarl-1.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1fa866fa24d9f4108f9e58ea8a2135655419885cdb443e36b39a346e1181532"}, + {file = "yarl-1.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1d3b8449dfedfe94eaff2b77954258b09b24949f6818dfa444b05dbb05ae1b7e"}, + {file = "yarl-1.7.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db2372e350794ce8b9f810feb094c606b7e0e4aa6807141ac4fadfe5ddd75bb0"}, + {file = "yarl-1.7.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a06d9d0b9a97fa99b84fee71d9dd11e69e21ac8a27229089f07b5e5e50e8d63c"}, + {file = "yarl-1.7.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a3455c2456d6307bcfa80bc1157b8603f7d93573291f5bdc7144489ca0df4628"}, + {file = "yarl-1.7.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:d30d67e3486aea61bb2cbf7cf81385364c2e4f7ce7469a76ed72af76a5cdfe6b"}, + {file = "yarl-1.7.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c18a4b286e8d780c3a40c31d7b79836aa93b720f71d5743f20c08b7e049ca073"}, + {file = "yarl-1.7.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d54c925396e7891666cabc0199366ca55b27d003393465acef63fd29b8b7aa92"}, + {file = "yarl-1.7.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:64773840952de17851a1c7346ad7f71688c77e74248d1f0bc230e96680f84028"}, + {file = "yarl-1.7.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:acbf1756d9dc7cd0ae943d883be72e84e04396f6c2ff93a6ddeca929d562039f"}, + {file = "yarl-1.7.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:2e48f27936aa838939c798f466c851ba4ae79e347e8dfce43b009c64b930df12"}, + {file = "yarl-1.7.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1beef4734ca1ad40a9d8c6b20a76ab46e3a2ed09f38561f01e4aa2ea82cafcef"}, + {file = "yarl-1.7.0-cp39-cp39-win32.whl", hash = "sha256:8ee78c9a5f3c642219d4607680a4693b59239c27a3aa608b64ef79ddc9698039"}, + {file = "yarl-1.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:d750503682605088a14d29a4701548c15c510da4f13c8b17409c4097d5b04c52"}, + {file = "yarl-1.7.0.tar.gz", hash = "sha256:8e7ebaf62e19c2feb097ffb7c94deb0f0c9fab52590784c8cd679d30ab009162"}, ] zipp = [ - {file = "zipp-3.4.1-py3-none-any.whl", hash = "sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098"}, - {file = "zipp-3.4.1.tar.gz", hash = "sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76"}, + {file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"}, + {file = "zipp-3.6.0.tar.gz", hash = "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832"}, ] diff --git a/pyproject.toml b/pyproject.toml index 8eafb92cf6..cfe7422d49 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.6.0-nightly.4" # OpenPype +version = "3.6.2-nightly.1" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" diff --git a/tests/openpype/modules/default_modules/royal_render/test_rr_job.py b/tests/openpype/modules/default_modules/royal_render/test_rr_job.py new file mode 100644 index 0000000000..ab8b1bfd50 --- /dev/null +++ b/tests/openpype/modules/default_modules/royal_render/test_rr_job.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +"""Test suite for User Settings.""" +# import pytest +# from openpype.modules import ModulesManager + + +def test_rr_job(): + # manager = ModulesManager() + # rr_module = manager.modules_by_name["royalrender"] + ... diff --git a/website/docs/assets/site_sync_always_on.png b/website/docs/assets/site_sync_always_on.png new file mode 100644 index 0000000000..712adf173b Binary files /dev/null and b/website/docs/assets/site_sync_always_on.png differ diff --git a/website/docs/assets/site_sync_system_sites.png b/website/docs/assets/site_sync_system_sites.png new file mode 100644 index 0000000000..e9f895c743 Binary files /dev/null and b/website/docs/assets/site_sync_system_sites.png differ diff --git a/website/docs/module_site_sync.md b/website/docs/module_site_sync.md index b0604ed3cf..571da60ceb 100644 --- a/website/docs/module_site_sync.md +++ b/website/docs/module_site_sync.md @@ -27,6 +27,38 @@ To use synchronization, *Site Sync* needs to be enabled globally in **OpenPype S ![Configure module](assets/site_sync_system.png) +### Sites + +By default there are two sites created for each OpenPype installation: +- **studio** - default site - usually a centralized mounted disk accessible to all artists. Studio site is used if Site Sync is disabled. +- **local** - each workstation or server running OpenPype Tray receives its own with unique site name. Workstation refers to itself as "local"however all other sites will see it under it's unique ID. + +Artists can explore their site ID by opening OpenPype Info tool by clicking on a version number in the tray app. + +Many different sites can be created and configured on the system level, and some or all can be assigned to each project. + +Each OpenPype Tray app works with two sites at one time. (Sites can be the same, and no synching is done in this setup). + +Sites could be configured differently per project basis. + +Each new site needs to be created first in `System Settings`. Most important feature of site is its Provider, select one from already prepared Providers. + +#### Alternative sites + +This attribute is meant for special use cases only. + +One of the use cases is sftp site vendoring (exposing) same data as regular site (studio). Each site is accessible for different audience. 'studio' for artists in a studio via shared disk, 'sftp' for externals via sftp server with mounted 'studio' drive. + +Change of file status on one site actually means same change on 'alternate' site occured too. (eg. artists publish to 'studio', 'sftp' is using +same location >> file is accessible on 'sftp' site right away, no need to sync it anyhow.) + +##### Example +![Configure module](assets/site_sync_system_sites.png) +Admin created new `sftp` site which is handled by `SFTP` provider. Somewhere in the studio SFTP server is deployed on a machine that has access to `studio` drive. + +Alternative sites work both way: +- everything published to `studio` is accessible on a `sftp` site too +- everything published to `sftp` (most probably via artist's local disk - artists publishes locally, representation is marked to be synced to `sftp`. Immediately after it is synced, it is marked to be available on `studio` too for artists in the studio to use.) ## Project Settings @@ -45,21 +77,6 @@ Artists can also override which site they use as active and remote if need be. ![Local overrides](assets/site_sync_local_setting.png) -## Sites - -By default there are two sites created for each OpenPype installation: -- **studio** - default site - usually a centralized mounted disk accessible to all artists. Studio site is used if Site Sync is disabled. -- **local** - each workstation or server running OpenPype Tray receives its own with unique site name. Workstation refers to itself as "local"however all other sites will see it under it's unique ID. - -Artists can explore their site ID by opening OpenPype Info tool by clicking on a version number in the tray app. - -Many different sites can be created and configured on the system level, and some or all can be assigned to each project. - -Each OpenPype Tray app works with two sites at one time. (Sites can be the same, and no synching is done in this setup). - -Sites could be configured differently per project basis. - - ## Providers Each site implements a so called `provider` which handles most common operations (list files, copy files etc.) and provides interface with a particular type of storage. (disk, gdrive, aws, etc.) @@ -140,3 +157,42 @@ Beware that ssh key expects OpenSSH format (`.pem`) not a Putty format (`.ppk`)! If a studio needs to use other services for cloud storage, or want to implement totally different storage providers, they can do so by writing their own provider plugin. We're working on a developer documentation, however, for now we recommend looking at `abstract_provider.py`and `gdrive.py` inside `openpype/modules/sync_server/providers` and using it as a template. +### Running Site Sync in background + +Site Sync server synchronizes new published files from artist machine into configured remote location by default. + +There might be a use case where you need to synchronize between "non-artist" sites, for example between studio site and cloud. In this case +you need to run Site Sync as a background process from a command line (via service etc) 24/7. + +To configure all sites where all published files should be synced eventually you need to configure `project_settings/global/sync_server/config/always_accessible_on` property in Settins (per project) first. + +![Set another non artist remote site](assets/site_sync_always_on.png) + +This is an example of: +- Site Sync is enabled for a project +- default active and remote sites are set to `studio` - eg. standard process: everyone is working in a studio, publishing to shared location etc. +- (but this also allows any of the artists to work remotely, they would change their active site in their own Local Settings to `local` and configure local root. + This would result in everything artist publishes is saved first onto his local folder AND synchronized to `studio` site eventually.) +- everything exported must also be eventually uploaded to `sftp` site + +This eventual synchronization between `studio` and `sftp` sites must be physically handled by background process. + +As current implementation relies heavily on Settings and Local Settings, background process for a specific site ('studio' for example) must be configured via Tray first to `syncserver` command to work. + +To do this: + +- run OP `Tray` with environment variable OPENPYPE_LOCAL_ID set to name of active (source) site. In most use cases it would be studio (for cases of backups of everything published to studio site to different cloud site etc.) +- start `Tray` +- check `Local ID` in information dialog after clicking on version number in the Tray +- open `Local Settings` in the `Tray` +- configure for each project necessary active site and remote site +- close `Tray` +- run OP from a command line with `syncserver` and `--active_site` arguments + + +This is an example how to trigger background synching process where active (source) site is `studio`. +(It is expected that OP is installed on a machine, `openpype_console` is on PATH. If not, add full path to executable. +) +```shell +openpype_console syncserver --active_site studio +``` \ No newline at end of file