From b12e4f42c89f784bdc02ecf70a00b131d941cdf6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 20 Apr 2021 13:48:07 +0200 Subject: [PATCH 001/279] make set concatenation python 2 and 3 compatible --- openpype/hosts/maya/api/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index ae2d329a97..8387bdeaac 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -581,7 +581,7 @@ def get_shader_assignments_from_shapes(shapes, components=True): # Build a mapping from parent to shapes to include in lookup. transforms = {shape.rsplit("|", 1)[0]: shape for shape in shapes} - lookup = set(shapes + transforms.keys()) + lookup = set(shapes) | set(transforms.keys()) component_assignments = defaultdict(list) for shading_group in assignments.keys(): From 3bef54dcf9fbeb082e0ea807ffc666eb0798c986 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 20 Apr 2021 13:49:35 +0200 Subject: [PATCH 002/279] use six.string_types instead of basestring --- openpype/hosts/maya/api/setdress.py | 3 ++- .../hosts/maya/plugins/publish/validate_instance_subset.py | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/api/setdress.py b/openpype/hosts/maya/api/setdress.py index be26572039..111d33da8c 100644 --- a/openpype/hosts/maya/api/setdress.py +++ b/openpype/hosts/maya/api/setdress.py @@ -5,6 +5,7 @@ import os import contextlib import copy +import six from maya import cmds from avalon import api, io @@ -94,7 +95,7 @@ def load_package(filepath, name, namespace=None): # Define a unique namespace for the package namespace = os.path.basename(filepath).split(".")[0] unique_namespace(namespace) - assert isinstance(namespace, basestring) + assert isinstance(namespace, six.string_types) # Load the setdress package data with open(filepath, "r") as fp: diff --git a/openpype/hosts/maya/plugins/publish/validate_instance_subset.py b/openpype/hosts/maya/plugins/publish/validate_instance_subset.py index a8c16425d6..539f3f9d3c 100644 --- a/openpype/hosts/maya/plugins/publish/validate_instance_subset.py +++ b/openpype/hosts/maya/plugins/publish/validate_instance_subset.py @@ -2,6 +2,8 @@ import pyblish.api import openpype.api import string +import six + # Allow only characters, numbers and underscore allowed = set(string.ascii_lowercase + string.ascii_uppercase + @@ -29,7 +31,7 @@ class ValidateSubsetName(pyblish.api.InstancePlugin): raise RuntimeError("Instance is missing subset " "name: {0}".format(subset)) - if not isinstance(subset, basestring): + if not isinstance(subset, six.string_types): raise TypeError("Instance subset name must be string, " "got: {0} ({1})".format(subset, type(subset))) From f6aa39af120054b4752087847fead6af0778f981 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 20 Apr 2021 13:54:48 +0200 Subject: [PATCH 003/279] first version of iteritems replacement --- openpype/hosts/maya/api/lib.py | 9 ++++-- openpype/hosts/maya/api/setdress.py | 3 +- .../hosts/maya/plugins/publish/extract_fbx.py | 3 +- .../plugins/publish/submit_maya_muster.py | 2 +- .../publish/validate_node_ids_unique.py | 3 +- .../publish/validate_node_no_ghosting.py | 5 +++- .../publish/validate_shape_render_stats.py | 6 ++-- openpype/vendor/python/common/capture.py | 29 +++++++++++++------ 8 files changed, 41 insertions(+), 19 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 8387bdeaac..2070e14683 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -437,7 +437,8 @@ def empty_sets(sets, force=False): cmds.connectAttr(src, dest) # Restore original members - for origin_set, members in original.iteritems(): + _iteritems = getattr(original, "iteritems", original.items) + for origin_set, members in _iteritems(): cmds.sets(members, forceElement=origin_set) @@ -669,7 +670,8 @@ def displaySmoothness(nodes, yield finally: # Revert state - for node, state in originals.iteritems(): + _iteritems = getattr(originals, "iteritems", originals.items) + for node, state in _iteritems(): if state: cmds.displaySmoothness(node, **state) @@ -712,7 +714,8 @@ def no_display_layers(nodes): yield finally: # Restore original members - for layer, members in original.iteritems(): + _iteritems = getattr(original, "iteritems", original.items) + for layer, members in _iteritems(): cmds.editDisplayLayerMembers(layer, members, noRecurse=True) diff --git a/openpype/hosts/maya/api/setdress.py b/openpype/hosts/maya/api/setdress.py index 111d33da8c..3537fa3837 100644 --- a/openpype/hosts/maya/api/setdress.py +++ b/openpype/hosts/maya/api/setdress.py @@ -70,7 +70,8 @@ def unlocked(nodes): yield finally: # Reapply original states - for uuid, state in states.iteritems(): + _iteritems = getattr(states, "iteritems", states.items) + for uuid, state in _iteritems(): nodes_from_id = cmds.ls(uuid, long=True) if nodes_from_id: node = nodes_from_id[0] diff --git a/openpype/hosts/maya/plugins/publish/extract_fbx.py b/openpype/hosts/maya/plugins/publish/extract_fbx.py index e5f3b0cda4..720a61b0a7 100644 --- a/openpype/hosts/maya/plugins/publish/extract_fbx.py +++ b/openpype/hosts/maya/plugins/publish/extract_fbx.py @@ -183,7 +183,8 @@ class ExtractFBX(openpype.api.Extractor): # Apply the FBX overrides through MEL since the commands # only work correctly in MEL according to online # available discussions on the topic - for option, value in options.iteritems(): + _iteritems = getattr(options, "iteritems", options.items) + for option, value in _iteritems(): key = option[0].upper() + option[1:] # uppercase first letter # Boolean must be passed as lower-case strings diff --git a/openpype/hosts/maya/plugins/publish/submit_maya_muster.py b/openpype/hosts/maya/plugins/publish/submit_maya_muster.py index 1c97f0faf7..207cf56cfe 100644 --- a/openpype/hosts/maya/plugins/publish/submit_maya_muster.py +++ b/openpype/hosts/maya/plugins/publish/submit_maya_muster.py @@ -383,7 +383,7 @@ class MayaSubmitMuster(pyblish.api.InstancePlugin): "attributes": { "environmental_variables": { "value": ", ".join("{!s}={!r}".format(k, v) - for (k, v) in env.iteritems()), + for (k, v) in env.items()), "state": True, "subst": False diff --git a/openpype/hosts/maya/plugins/publish/validate_node_ids_unique.py b/openpype/hosts/maya/plugins/publish/validate_node_ids_unique.py index 39bb148911..ed9ef526d6 100644 --- a/openpype/hosts/maya/plugins/publish/validate_node_ids_unique.py +++ b/openpype/hosts/maya/plugins/publish/validate_node_ids_unique.py @@ -52,7 +52,8 @@ class ValidateNodeIdsUnique(pyblish.api.InstancePlugin): # Take only the ids with more than one member invalid = list() - for _ids, members in ids.iteritems(): + _iteritems = getattr(ids, "iteritems", ids.items) + for _ids, members in _iteritems(): if len(members) > 1: cls.log.error("ID found on multiple nodes: '%s'" % members) invalid.extend(members) diff --git a/openpype/hosts/maya/plugins/publish/validate_node_no_ghosting.py b/openpype/hosts/maya/plugins/publish/validate_node_no_ghosting.py index 671c744a22..38f3ab1e68 100644 --- a/openpype/hosts/maya/plugins/publish/validate_node_no_ghosting.py +++ b/openpype/hosts/maya/plugins/publish/validate_node_no_ghosting.py @@ -32,7 +32,10 @@ class ValidateNodeNoGhosting(pyblish.api.InstancePlugin): nodes = cmds.ls(instance, long=True, type=['transform', 'shape']) invalid = [] for node in nodes: - for attr, required_value in cls._attributes.iteritems(): + _iteritems = getattr( + cls._attributes, "iteritems", cls._attributes.items + ) + for attr, required_value in _iteritems(): if cmds.attributeQuery(attr, node=node, exists=True): value = cmds.getAttr('{0}.{1}'.format(node, attr)) diff --git a/openpype/hosts/maya/plugins/publish/validate_shape_render_stats.py b/openpype/hosts/maya/plugins/publish/validate_shape_render_stats.py index 667a1f13be..714451bb98 100644 --- a/openpype/hosts/maya/plugins/publish/validate_shape_render_stats.py +++ b/openpype/hosts/maya/plugins/publish/validate_shape_render_stats.py @@ -33,7 +33,8 @@ class ValidateShapeRenderStats(pyblish.api.Validator): shapes = cmds.ls(instance, long=True, type='surfaceShape') invalid = [] for shape in shapes: - for attr, default_value in cls.defaults.iteritems(): + _iteritems = getattr(cls.defaults, "iteritems", cls.defaults.items) + for attr, default_value in _iteritems(): if cmds.attributeQuery(attr, node=shape, exists=True): value = cmds.getAttr('{}.{}'.format(shape, attr)) if value != default_value: @@ -52,7 +53,8 @@ class ValidateShapeRenderStats(pyblish.api.Validator): @classmethod def repair(cls, instance): for shape in cls.get_invalid(instance): - for attr, default_value in cls.defaults.iteritems(): + _iteritems = getattr(cls.defaults, "iteritems", cls.defaults.items) + for attr, default_value in _iteritems(): if cmds.attributeQuery(attr, node=shape, exists=True): plug = '{0}.{1}'.format(shape, attr) diff --git a/openpype/vendor/python/common/capture.py b/openpype/vendor/python/common/capture.py index 83816ec92a..59aafcb57a 100644 --- a/openpype/vendor/python/common/capture.py +++ b/openpype/vendor/python/common/capture.py @@ -364,7 +364,8 @@ def apply_view(panel, **options): # Display options display_options = options.get("display_options", {}) - for key, value in display_options.iteritems(): + _iteritems = getattr(display_options, "iteritems", display_options.items) + for key, value in _iteritems(): if key in _DisplayOptionsRGB: cmds.displayRGBColor(key, *value) else: @@ -372,16 +373,21 @@ def apply_view(panel, **options): # Camera options camera_options = options.get("camera_options", {}) - for key, value in camera_options.iteritems(): + _iteritems = getattr(camera_options, "iteritems", camera_options.items) + for key, value in _iteritems: cmds.setAttr("{0}.{1}".format(camera, key), value) # Viewport options viewport_options = options.get("viewport_options", {}) - for key, value in viewport_options.iteritems(): + _iteritems = getattr(viewport_options, "iteritems", viewport_options.items) + for key, value in _iteritems(): cmds.modelEditor(panel, edit=True, **{key: value}) viewport2_options = options.get("viewport2_options", {}) - for key, value in viewport2_options.iteritems(): + _iteritems = getattr( + viewport2_options, "iteritems", viewport2_options.items + ) + for key, value in _iteritems(): attr = "hardwareRenderingGlobals.{0}".format(key) cmds.setAttr(attr, value) @@ -629,14 +635,16 @@ def _applied_camera_options(options, panel): "for capture: %s" % opt) options.pop(opt) - for opt, value in options.iteritems(): + _iteritems = getattr(options, "iteritems", options.items) + for opt, value in _iteritems(): cmds.setAttr(camera + "." + opt, value) try: yield finally: if old_options: - for opt, value in old_options.iteritems(): + _iteritems = getattr(old_options, "iteritems", old_options.items) + for opt, value in _iteritems(): cmds.setAttr(camera + "." + opt, value) @@ -722,14 +730,16 @@ def _applied_viewport2_options(options): options.pop(opt) # Apply settings - for opt, value in options.iteritems(): + _iteritems = getattr(options, "iteritems", options.items) + for opt, value in _iteritems(): cmds.setAttr("hardwareRenderingGlobals." + opt, value) try: yield finally: # Restore previous settings - for opt, value in original.iteritems(): + _iteritems = getattr(original, "iteritems", original.items) + for opt, value in _iteritems(): cmds.setAttr("hardwareRenderingGlobals." + opt, value) @@ -769,7 +779,8 @@ def _maintain_camera(panel, camera): try: yield finally: - for camera, renderable in state.iteritems(): + _iteritems = getattr(state, "iteritems", state.items) + for camera, renderable in _iteritems(): cmds.setAttr(camera + ".rnd", renderable) From 66ec28ca964a653dab3db017a3392e6776873c9d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 20 Apr 2021 13:55:00 +0200 Subject: [PATCH 004/279] fix context manager in maya capture --- openpype/vendor/python/common/capture.py | 75 ++++++++++++++++-------- 1 file changed, 50 insertions(+), 25 deletions(-) diff --git a/openpype/vendor/python/common/capture.py b/openpype/vendor/python/common/capture.py index 59aafcb57a..02d86952df 100644 --- a/openpype/vendor/python/common/capture.py +++ b/openpype/vendor/python/common/capture.py @@ -161,37 +161,62 @@ def capture(camera=None, cmds.currentTime(cmds.currentTime(query=True)) padding = 10 # Extend panel to accommodate for OS window manager + with _independent_panel(width=width + padding, height=height + padding, off_screen=off_screen) as panel: cmds.setFocus(panel) - with contextlib.nested( - _disabled_inview_messages(), - _maintain_camera(panel, camera), - _applied_viewport_options(viewport_options, panel), - _applied_camera_options(camera_options, panel), - _applied_display_options(display_options), - _applied_viewport2_options(viewport2_options), - _isolated_nodes(isolate, panel), - _maintained_time()): + all_playblast_kwargs = { + "compression": compression, + "format": format, + "percent": 100, + "quality": quality, + "viewer": viewer, + "startTime": start_frame, + "endTime": end_frame, + "offScreen": off_screen, + "showOrnaments": show_ornaments, + "forceOverwrite": overwrite, + "filename": filename, + "widthHeight": [width, height], + "rawFrameNumbers": raw_frame_numbers, + "framePadding": frame_padding + } + all_playblast_kwargs.update(playblast_kwargs) - output = cmds.playblast( - compression=compression, - format=format, - percent=100, - quality=quality, - viewer=viewer, - startTime=start_frame, - endTime=end_frame, - offScreen=off_screen, - showOrnaments=show_ornaments, - forceOverwrite=overwrite, - filename=filename, - widthHeight=[width, height], - rawFrameNumbers=raw_frame_numbers, - framePadding=frame_padding, - **playblast_kwargs) + if getattr(contextlib, "nested", None): + with contextlib.nested( + _disabled_inview_messages(), + _maintain_camera(panel, camera), + _applied_viewport_options(viewport_options, panel), + _applied_camera_options(camera_options, panel), + _applied_display_options(display_options), + _applied_viewport2_options(viewport2_options), + _isolated_nodes(isolate, panel), + _maintained_time() + ): + output = cmds.playblast(**all_playblast_kwargs) + else: + with contextlib.ExitStack() as stack: + stack.enter_context(_disabled_inview_messages()) + stack.enter_context(_maintain_camera(panel, camera)) + stack.enter_context( + _applied_viewport_options(viewport_options, panel) + ) + stack.enter_context( + _applied_camera_options(camera_options, panel) + ) + stack.enter_context( + _applied_display_options(display_options) + ) + stack.enter_context( + _applied_viewport2_options(viewport2_options) + ) + stack.enter_context(_isolated_nodes(isolate, panel)) + stack.enter_context(_maintained_time()) + + output = cmds.playblast(**all_playblast_kwargs) return output From c30b917e6ca5c4e754bc60450de9495482051ebd Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 22 Jun 2021 17:53:09 +0200 Subject: [PATCH 005/279] remove PyQt5 and add PySide2 --- openpype/lib/import_utils.py | 15 +++++++ poetry.lock | 75 ++++++--------------------------- pyproject.toml | 1 - tools/fetch_thirdparty_libs.ps1 | 3 ++ 4 files changed, 31 insertions(+), 63 deletions(-) diff --git a/openpype/lib/import_utils.py b/openpype/lib/import_utils.py index 4e72618803..9f459bbb51 100644 --- a/openpype/lib/import_utils.py +++ b/openpype/lib/import_utils.py @@ -2,6 +2,7 @@ import os import sys import importlib from .log import PypeLogger as Logger +from pathlib import Path log = Logger().get_logger(__name__) @@ -23,3 +24,17 @@ def discover_host_vendor_module(module_name): sys.path.insert(1, module_path) return importlib.import_module(module_name) + + +def get_pyside2_location(): + """Get location of PySide2 and its dependencies. + + Returned path can be used with `site.addsitedir()` + + Returns: + str: path to PySide2 + + """ + path = Path(os.getenv("OPENPYPE_ROOT")) + path = path / "vendor/python/PySide2" + return str(path) diff --git a/poetry.lock b/poetry.lock index 48e6f95469..869e953060 100644 --- a/poetry.lock +++ b/poetry.lock @@ -941,34 +941,6 @@ category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -[[package]] -name = "pyqt5" -version = "5.15.4" -description = "Python bindings for the Qt cross platform application toolkit" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -PyQt5-Qt5 = ">=5.15" -PyQt5-sip = ">=12.8,<13" - -[[package]] -name = "pyqt5-qt5" -version = "5.15.2" -description = "The subset of a Qt installation needed by PyQt5." -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "pyqt5-sip" -version = "12.9.0" -description = "The sip module support for PyQt5" -category = "main" -optional = false -python-versions = ">=3.5" - [[package]] name = "pyrsistent" version = "0.17.3" @@ -1466,7 +1438,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pyt [metadata] lock-version = "1.1" python-versions = "3.7.*" -content-hash = "8875d530ae66f9763b5b0cb84d9d35edc184ef5c141b63d38bf1ff5a1226e556" +content-hash = "70c5951f20ded8f10757ea030f7a99a49c1ea6773ad944f922533b692a3c5166" [metadata.files] acre = [] @@ -1582,24 +1554,36 @@ cffi = [ {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"}, @@ -1976,7 +1960,6 @@ pillow = [ {file = "Pillow-8.2.0-pp37-pypy37_pp73-manylinux2010_i686.whl", hash = "sha256:aac00e4bc94d1b7813fe882c28990c1bc2f9d0e1aa765a5f2b516e8a6a16a9e4"}, {file = "Pillow-8.2.0-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:22fd0f42ad15dfdde6c581347eaa4adb9a6fc4b865f90b23378aa7914895e120"}, {file = "Pillow-8.2.0-pp37-pypy37_pp73-win32.whl", hash = "sha256:e98eca29a05913e82177b3ba3d198b1728e164869c613d76d0de4bde6768a50e"}, - {file = "Pillow-8.2.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:8b56553c0345ad6dcb2e9b433ae47d67f95fc23fe28a0bde15a120f25257e291"}, {file = "Pillow-8.2.0.tar.gz", hash = "sha256:a787ab10d7bb5494e5f76536ac460741788f1fbce851068d73a87ca7c35fc3e1"}, ] pluggy = [ @@ -2177,38 +2160,6 @@ pyparsing = [ {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, ] -pyqt5 = [ - {file = "PyQt5-5.15.4-cp36.cp37.cp38.cp39-abi3-macosx_10_13_intel.whl", hash = "sha256:8c0848ba790a895801d5bfd171da31cad3e551dbcc4e59677a3b622de2ceca98"}, - {file = "PyQt5-5.15.4-cp36.cp37.cp38.cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:883a549382fc22d29a0568f3ef20b38c8e7ab633a59498ac4eb63a3bf36d3fd3"}, - {file = "PyQt5-5.15.4-cp36.cp37.cp38.cp39-none-win32.whl", hash = "sha256:a88526a271e846e44779bb9ad7a738c6d3c4a9d01e15a128ecfc6dd4696393b7"}, - {file = "PyQt5-5.15.4-cp36.cp37.cp38.cp39-none-win_amd64.whl", hash = "sha256:213bebd51821ed89b4d5b35bb10dbe67564228b3568f463a351a08e8b1677025"}, - {file = "PyQt5-5.15.4.tar.gz", hash = "sha256:2a69597e0dd11caabe75fae133feca66387819fc9bc050f547e5551bce97e5be"}, -] -pyqt5-qt5 = [ - {file = "PyQt5_Qt5-5.15.2-py3-none-macosx_10_13_intel.whl", hash = "sha256:76980cd3d7ae87e3c7a33bfebfaee84448fd650bad6840471d6cae199b56e154"}, - {file = "PyQt5_Qt5-5.15.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:1988f364ec8caf87a6ee5d5a3a5210d57539988bf8e84714c7d60972692e2f4a"}, - {file = "PyQt5_Qt5-5.15.2-py3-none-win32.whl", hash = "sha256:9cc7a768b1921f4b982ebc00a318ccb38578e44e45316c7a4a850e953e1dd327"}, - {file = "PyQt5_Qt5-5.15.2-py3-none-win_amd64.whl", hash = "sha256:750b78e4dba6bdf1607febedc08738e318ea09e9b10aea9ff0d73073f11f6962"}, -] -pyqt5-sip = [ - {file = "PyQt5_sip-12.9.0-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:d85002238b5180bce4b245c13d6face848faa1a7a9e5c6e292025004f2fd619a"}, - {file = "PyQt5_sip-12.9.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:83c3220b1ca36eb8623ba2eb3766637b19eb0ce9f42336ad8253656d32750c0a"}, - {file = "PyQt5_sip-12.9.0-cp36-cp36m-win32.whl", hash = "sha256:d8b2bdff7bbf45bc975c113a03b14fd669dc0c73e1327f02706666a7dd51a197"}, - {file = "PyQt5_sip-12.9.0-cp36-cp36m-win_amd64.whl", hash = "sha256:69a3ad4259172e2b1aa9060de211efac39ddd734a517b1924d9c6c0cc4f55f96"}, - {file = "PyQt5_sip-12.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:42274a501ab4806d2c31659170db14c282b8313d2255458064666d9e70d96206"}, - {file = "PyQt5_sip-12.9.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:6a8701892a01a5a2a4720872361197cc80fdd5f49c8482d488ddf38c9c84f055"}, - {file = "PyQt5_sip-12.9.0-cp37-cp37m-win32.whl", hash = "sha256:ac57d796c78117eb39edd1d1d1aea90354651efac9d3590aac67fa4983f99f1f"}, - {file = "PyQt5_sip-12.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4347bd81d30c8e3181e553b3734f91658cfbdd8f1a19f254777f906870974e6d"}, - {file = "PyQt5_sip-12.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c446971c360a0a1030282a69375a08c78e8a61d568bfd6dab3dcc5cf8817f644"}, - {file = "PyQt5_sip-12.9.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:fc43f2d7c438517ee33e929e8ae77132749c15909afab6aeece5fcf4147ffdb5"}, - {file = "PyQt5_sip-12.9.0-cp38-cp38-win32.whl", hash = "sha256:055581c6fed44ba4302b70eeb82e979ff70400037358908f251cd85cbb3dbd93"}, - {file = "PyQt5_sip-12.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:c5216403d4d8d857ec4a61f631d3945e44fa248aa2415e9ee9369ab7c8a4d0c7"}, - {file = "PyQt5_sip-12.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a25b9843c7da6a1608f310879c38e6434331aab1dc2fe6cb65c14f1ecf33780e"}, - {file = "PyQt5_sip-12.9.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:dd05c768c2b55ffe56a9d49ce6cc77cdf3d53dbfad935258a9e347cbfd9a5850"}, - {file = "PyQt5_sip-12.9.0-cp39-cp39-win32.whl", hash = "sha256:4f8e05fe01d54275877c59018d8e82dcdd0bc5696053a8b830eecea3ce806121"}, - {file = "PyQt5_sip-12.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:b09f4cd36a4831229fb77c424d89635fa937d97765ec90685e2f257e56a2685a"}, - {file = "PyQt5_sip-12.9.0.tar.gz", hash = "sha256:d3e4489d7c2b0ece9d203ae66e573939f7f60d4d29e089c9f11daa17cfeaae32"}, -] pyrsistent = [ {file = "pyrsistent-0.17.3.tar.gz", hash = "sha256:2e636185d9eb976a18a8a8e96efce62f2905fea90041958d8cc2a189756ebf3e"}, ] diff --git a/pyproject.toml b/pyproject.toml index e376986606..c9580b1601 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,6 @@ Pillow = "^8.1" # only used for slates prototype pyblish-base = "^1.8.8" pynput = "^1.7.2" # idle manager in tray pymongo = "^3.11.2" -pyqt5 = "^5.12.2" # ideally should be replaced with PySide2 "Qt.py" = "^1.3.3" speedcopy = "^2.1" six = "^1.15" diff --git a/tools/fetch_thirdparty_libs.ps1 b/tools/fetch_thirdparty_libs.ps1 index 23f0b50c7a..7ece9ee10e 100644 --- a/tools/fetch_thirdparty_libs.ps1 +++ b/tools/fetch_thirdparty_libs.ps1 @@ -36,6 +36,9 @@ if (-not (Test-Path -PathType Container -Path "$openpype_root\.poetry\bin")) { } else { Write-Host "OK" -ForegroundColor Green } +Write-Host ">>> " -NoNewline -ForegroundColor Green +Write-Host "Installing PySide2 ... " +& "$($env:POETRY_HOME)\bin\poetry.bat" run python -m pip install PySide2 -t "$($openpype_root)\vendor\python\PySide2" & poetry run python "$($openpype_root)\tools\fetch_thirdparty_libs.py" Set-Location -Path $current_dir From fbc7ccab31613d8766d5ef5408a3b2f8718ca720 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 22 Jun 2021 18:08:05 +0200 Subject: [PATCH 006/279] linux version --- tools/fetch_thirdparty_libs.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tools/fetch_thirdparty_libs.sh b/tools/fetch_thirdparty_libs.sh index 31f109ba68..e5c79b81dc 100755 --- a/tools/fetch_thirdparty_libs.sh +++ b/tools/fetch_thirdparty_libs.sh @@ -99,6 +99,9 @@ main () { pushd "$openpype_root" > /dev/null || return > /dev/null + echo -e "${BIGreen}>>>${RST} Installing PySide2 ..." + "$POETRY_HOME/bin/poetry" run python -m pip install PySide2 -t "$openpype_root/vendor/python/PySide2" + echo -e "${BIGreen}>>>${RST} Running Pype tool ..." poetry run python "$openpype_root/tools/fetch_thirdparty_libs.py" } From 09486d7e66cd5d946164605f25442642dbdf5ac1 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 22 Jun 2021 18:09:57 +0200 Subject: [PATCH 007/279] add vendor/python to gitignore --- .dockerignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.dockerignore b/.dockerignore index 07c1c151ce..c4d0d4a9d0 100644 --- a/.dockerignore +++ b/.dockerignore @@ -142,5 +142,6 @@ cython_debug/ .poetry/ .github/ vendor/bin/ +vendor/python/ docs/ website/ From 6c06eefaefa86314b524724eeb2d1809477d23b8 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 23 Jun 2021 10:24:11 +0200 Subject: [PATCH 008/279] added python vendor to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 221a2f2241..fa3fae1ad2 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,7 @@ Temporary Items /dist/ /vendor/bin/* +/vendor/python/* /.venv /venv/ From 25a18e1bc1b74da8c05521411e50d405bd9a7fc6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 23 Jun 2021 10:24:35 +0200 Subject: [PATCH 009/279] do not install PySide2 into subfolder --- tools/fetch_thirdparty_libs.ps1 | 2 +- tools/fetch_thirdparty_libs.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/fetch_thirdparty_libs.ps1 b/tools/fetch_thirdparty_libs.ps1 index 7ece9ee10e..f87ce3e724 100644 --- a/tools/fetch_thirdparty_libs.ps1 +++ b/tools/fetch_thirdparty_libs.ps1 @@ -38,7 +38,7 @@ if (-not (Test-Path -PathType Container -Path "$openpype_root\.poetry\bin")) { } Write-Host ">>> " -NoNewline -ForegroundColor Green Write-Host "Installing PySide2 ... " -& "$($env:POETRY_HOME)\bin\poetry.bat" run python -m pip install PySide2 -t "$($openpype_root)\vendor\python\PySide2" +& "$($env:POETRY_HOME)\bin\poetry.bat" run python -m pip install PySide2 -t "$($openpype_root)\vendor\python" & poetry run python "$($openpype_root)\tools\fetch_thirdparty_libs.py" Set-Location -Path $current_dir diff --git a/tools/fetch_thirdparty_libs.sh b/tools/fetch_thirdparty_libs.sh index e5c79b81dc..f619bc9f01 100755 --- a/tools/fetch_thirdparty_libs.sh +++ b/tools/fetch_thirdparty_libs.sh @@ -100,7 +100,7 @@ main () { pushd "$openpype_root" > /dev/null || return > /dev/null echo -e "${BIGreen}>>>${RST} Installing PySide2 ..." - "$POETRY_HOME/bin/poetry" run python -m pip install PySide2 -t "$openpype_root/vendor/python/PySide2" + "$POETRY_HOME/bin/poetry" run python -m pip install PySide2 -t "$openpype_root/vendor/python" echo -e "${BIGreen}>>>${RST} Running Pype tool ..." poetry run python "$openpype_root/tools/fetch_thirdparty_libs.py" From 64a1839c9d0f1632db45ccb5038816acc10046aa Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 23 Jun 2021 10:24:59 +0200 Subject: [PATCH 010/279] add vendor directory to sys.path in start.py --- start.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/start.py b/start.py index 8e7c195e95..905526480f 100644 --- a/start.py +++ b/start.py @@ -124,6 +124,10 @@ else: paths.append(frozen_libs) os.environ["PYTHONPATH"] = os.pathsep.join(paths) +# Vendored python modules that must not be in PYTHONPATH environment but +# are required for OpenPype processes +vendor_python_path = os.path.join(OPENPYPE_ROOT, "vendor", "python") +sys.path.insert(0, vendor_python_path) import blessed # noqa: E402 import certifi # noqa: E402 From 39bf35429b7403735c3ff02025e78a7fc46aed5a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 23 Jun 2021 10:25:34 +0200 Subject: [PATCH 011/279] removed get_pyside2_location as is not needed inside openpype --- openpype/lib/import_utils.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/openpype/lib/import_utils.py b/openpype/lib/import_utils.py index 9f459bbb51..4e72618803 100644 --- a/openpype/lib/import_utils.py +++ b/openpype/lib/import_utils.py @@ -2,7 +2,6 @@ import os import sys import importlib from .log import PypeLogger as Logger -from pathlib import Path log = Logger().get_logger(__name__) @@ -24,17 +23,3 @@ def discover_host_vendor_module(module_name): sys.path.insert(1, module_path) return importlib.import_module(module_name) - - -def get_pyside2_location(): - """Get location of PySide2 and its dependencies. - - Returned path can be used with `site.addsitedir()` - - Returns: - str: path to PySide2 - - """ - path = Path(os.getenv("OPENPYPE_ROOT")) - path = path / "vendor/python/PySide2" - return str(path) From ea8a3e891d68806384a08857c9f55b1fb70c4c71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Wed, 23 Jun 2021 10:50:47 +0200 Subject: [PATCH 012/279] changes in ignore files --- .dockerignore | 2 +- .gitignore | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.dockerignore b/.dockerignore index c4d0d4a9d0..9c506b9964 100644 --- a/.dockerignore +++ b/.dockerignore @@ -87,7 +87,7 @@ ipython_config.py # pyenv # For a library or package, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: -# .python-version +.python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. diff --git a/.gitignore b/.gitignore index 221a2f2241..d2c682b1e5 100644 --- a/.gitignore +++ b/.gitignore @@ -99,3 +99,5 @@ website/.docusaurus .poetry/ .python-version + +vendor/python/PySide2 From 2e35e30ead8f06ee79dbe576d382b5a73a09678d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 23 Jun 2021 11:04:17 +0200 Subject: [PATCH 013/279] removed duplicated ignore directory --- .gitignore | 2 -- 1 file changed, 2 deletions(-) diff --git a/.gitignore b/.gitignore index 63d311f033..fa3fae1ad2 100644 --- a/.gitignore +++ b/.gitignore @@ -100,5 +100,3 @@ website/.docusaurus .poetry/ .python-version - -vendor/python/PySide2 From 5937461cf6c53f29d48153a14527bb771031846f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 23 Jun 2021 11:26:05 +0200 Subject: [PATCH 014/279] fix receivers discovery --- openpype/tools/settings/settings/window.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/tools/settings/settings/window.py b/openpype/tools/settings/settings/window.py index a60a2a1d88..54f8ec0a11 100644 --- a/openpype/tools/settings/settings/window.py +++ b/openpype/tools/settings/settings/window.py @@ -141,7 +141,10 @@ class MainWidget(QtWidgets.QWidget): # Don't show dialog if there are not registered slots for # `trigger_restart` signal. # - For example when settings are runnin as standalone tool - if self.receivers(self.trigger_restart) < 1: + # - PySide2 and PyQt5 compatible way how to find out + method_index = self.metaObject().indexOfMethod("trigger_restart()") + method = self.metaObject().method(method_index) + if not self.isSignalConnected(method): return dialog = RestartDialog(self) From 60445b705cf2dd9591aa12ec9c7969e441a9de90 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 23 Jun 2021 11:56:56 +0200 Subject: [PATCH 015/279] fix log viewer stylesheet of qtoolbutton --- openpype/modules/log_viewer/tray/app.py | 8 ++++---- openpype/modules/log_viewer/tray/widgets.py | 5 ++--- openpype/style/style.css | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/openpype/modules/log_viewer/tray/app.py b/openpype/modules/log_viewer/tray/app.py index 9aab37cd20..1e8d6483cd 100644 --- a/openpype/modules/log_viewer/tray/app.py +++ b/openpype/modules/log_viewer/tray/app.py @@ -7,12 +7,13 @@ class LogsWindow(QtWidgets.QWidget): def __init__(self, parent=None): super(LogsWindow, self).__init__(parent) - self.setStyleSheet(style.load_stylesheet()) + self.setWindowTitle("Logs viewer") + self.resize(1400, 800) log_detail = OutputWidget(parent=self) logs_widget = LogsWidget(log_detail, parent=self) - main_layout = QtWidgets.QHBoxLayout() + main_layout = QtWidgets.QHBoxLayout(self) log_splitter = QtWidgets.QSplitter(self) log_splitter.setOrientation(QtCore.Qt.Horizontal) @@ -24,5 +25,4 @@ class LogsWindow(QtWidgets.QWidget): self.logs_widget = logs_widget self.log_detail = log_detail - self.setLayout(main_layout) - self.setWindowTitle("Logs") + self.setStyleSheet(style.load_stylesheet()) diff --git a/openpype/modules/log_viewer/tray/widgets.py b/openpype/modules/log_viewer/tray/widgets.py index b9a8499a4c..d906a1b6ad 100644 --- a/openpype/modules/log_viewer/tray/widgets.py +++ b/openpype/modules/log_viewer/tray/widgets.py @@ -76,13 +76,12 @@ class CustomCombo(QtWidgets.QWidget): toolbutton.setMenu(toolmenu) toolbutton.setPopupMode(QtWidgets.QToolButton.MenuButtonPopup) + toolbutton.setProperty("popup_mode", "1") - layout = QtWidgets.QHBoxLayout() + layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(toolbutton) - self.setLayout(layout) - toolmenu.selection_changed.connect(self.selection_changed) self.toolbutton = toolbutton diff --git a/openpype/style/style.css b/openpype/style/style.css index c57b9a8da6..8391fcd0ae 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -97,7 +97,7 @@ QToolButton:disabled { background: {color:bg-buttons-disabled}; } -QToolButton[popupMode="1"] { +QToolButton[popupMode="1"], QToolButton[popup_mode="1"] { /* make way for the popup button */ padding-right: 20px; border: 1px solid {color:bg-buttons}; From 7b45e69f99dba951c9f9a13ee3cea73061aa187e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 23 Jun 2021 12:11:17 +0200 Subject: [PATCH 016/279] added comment --- openpype/modules/log_viewer/tray/widgets.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/modules/log_viewer/tray/widgets.py b/openpype/modules/log_viewer/tray/widgets.py index d906a1b6ad..669acf4b67 100644 --- a/openpype/modules/log_viewer/tray/widgets.py +++ b/openpype/modules/log_viewer/tray/widgets.py @@ -76,6 +76,9 @@ class CustomCombo(QtWidgets.QWidget): toolbutton.setMenu(toolmenu) toolbutton.setPopupMode(QtWidgets.QToolButton.MenuButtonPopup) + + # Fake popupMenu property as PySide2 does not store it's value as + # integer but as enum object toolbutton.setProperty("popup_mode", "1") layout = QtWidgets.QHBoxLayout(self) From 25cb349b7a0f1de801831c638e12933b0d71e672 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 23 Jun 2021 12:38:50 +0200 Subject: [PATCH 017/279] fixed popupMode property --- openpype/modules/log_viewer/tray/widgets.py | 4 ---- openpype/style/style.css | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/openpype/modules/log_viewer/tray/widgets.py b/openpype/modules/log_viewer/tray/widgets.py index 669acf4b67..0f77a7f111 100644 --- a/openpype/modules/log_viewer/tray/widgets.py +++ b/openpype/modules/log_viewer/tray/widgets.py @@ -77,10 +77,6 @@ class CustomCombo(QtWidgets.QWidget): toolbutton.setMenu(toolmenu) toolbutton.setPopupMode(QtWidgets.QToolButton.MenuButtonPopup) - # Fake popupMenu property as PySide2 does not store it's value as - # integer but as enum object - toolbutton.setProperty("popup_mode", "1") - layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(toolbutton) diff --git a/openpype/style/style.css b/openpype/style/style.css index 8391fcd0ae..8dffd98e43 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -97,7 +97,7 @@ QToolButton:disabled { background: {color:bg-buttons-disabled}; } -QToolButton[popupMode="1"], QToolButton[popup_mode="1"] { +QToolButton[popupMode="1"], QToolButton[popupMode="MenuButtonPopup"] { /* make way for the popup button */ padding-right: 20px; border: 1px solid {color:bg-buttons}; From bc30890b88ee23cce7c3908d47fab215b5d128c6 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Wed, 23 Jun 2021 14:58:31 +0200 Subject: [PATCH 018/279] get PySide2 version from pyproject.toml --- pyproject.toml | 6 ++++++ tools/fetch_thirdparty_libs.ps1 | 3 --- tools/fetch_thirdparty_libs.py | 21 +++++++++++++++++++-- tools/fetch_thirdparty_libs.sh | 3 --- 4 files changed, 25 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c9580b1601..1e797130db 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -101,6 +101,12 @@ build-backend = "poetry.core.masonry.api" [openpype] +[openpype.pyside2] +# note: in here we can use pip version specifiers as this is installed with pip until +# Poetry will support custom location (-t flag for pip) +# https://pip.pypa.io/en/stable/cli/pip_install/#requirement-specifiers +version = "==5.15.2" + [openpype.thirdparty.ffmpeg.windows] url = "https://distribute.openpype.io/thirdparty/ffmpeg-4.4-windows.zip" hash = "dd51ba29d64ee238e7c4c3c7301b19754c3f0ee2e2a729c20a0e2789e72db925" diff --git a/tools/fetch_thirdparty_libs.ps1 b/tools/fetch_thirdparty_libs.ps1 index f87ce3e724..23f0b50c7a 100644 --- a/tools/fetch_thirdparty_libs.ps1 +++ b/tools/fetch_thirdparty_libs.ps1 @@ -36,9 +36,6 @@ if (-not (Test-Path -PathType Container -Path "$openpype_root\.poetry\bin")) { } else { Write-Host "OK" -ForegroundColor Green } -Write-Host ">>> " -NoNewline -ForegroundColor Green -Write-Host "Installing PySide2 ... " -& "$($env:POETRY_HOME)\bin\poetry.bat" run python -m pip install PySide2 -t "$($openpype_root)\vendor\python" & poetry run python "$($openpype_root)\tools\fetch_thirdparty_libs.py" Set-Location -Path $current_dir diff --git a/tools/fetch_thirdparty_libs.py b/tools/fetch_thirdparty_libs.py index 75ee052950..60392d782c 100644 --- a/tools/fetch_thirdparty_libs.py +++ b/tools/fetch_thirdparty_libs.py @@ -20,6 +20,7 @@ import hashlib import tarfile import zipfile import time +import subprocess term = blessed.Terminal() @@ -65,11 +66,27 @@ def _print(msg: str, message_type: int = 0) -> None: print("{}{}".format(header, msg)) - -_print("Processing third-party dependencies ...") start_time = time.time_ns() openpype_root = Path(os.path.dirname(__file__)).parent pyproject = toml.load(openpype_root / "pyproject.toml") +_print("Handling PySide2 Qt framework ...") +pyside2_version = None +try: + pyside2_version = pyproject["openpype"]["pyside2"]["version"] +except AttributeError: + _print("No PySide2 version was specified, using latest available.", 2) + +pyside2_arg = "PySide2" if pyside2_version else "PySide2{}".format(pyside2_version) # noqa: E501 +try: + subprocess.run( + [sys.executable, "-m", "pip", "install", "--upgrade", + pyside2_arg, "-t", str(openpype_root / "vendor/python")], check=True) +except subprocess.CalledProcessError as e: + _print("Error during PySide2 installation.", 1) + _print(str(e), 1) + sys.exit(1) + +_print("Processing third-party dependencies ...") platform_name = platform.system().lower() try: diff --git a/tools/fetch_thirdparty_libs.sh b/tools/fetch_thirdparty_libs.sh index f619bc9f01..31f109ba68 100755 --- a/tools/fetch_thirdparty_libs.sh +++ b/tools/fetch_thirdparty_libs.sh @@ -99,9 +99,6 @@ main () { pushd "$openpype_root" > /dev/null || return > /dev/null - echo -e "${BIGreen}>>>${RST} Installing PySide2 ..." - "$POETRY_HOME/bin/poetry" run python -m pip install PySide2 -t "$openpype_root/vendor/python" - echo -e "${BIGreen}>>>${RST} Running Pype tool ..." poetry run python "$openpype_root/tools/fetch_thirdparty_libs.py" } From 4362668b66f22fec09b26a65ec8ae767353eed79 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 25 Jun 2021 10:15:49 +0200 Subject: [PATCH 019/279] use parenting to skip style set --- .../tools/standalonepublish/widgets/widget_component_item.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/openpype/tools/standalonepublish/widgets/widget_component_item.py b/openpype/tools/standalonepublish/widgets/widget_component_item.py index 186c8024db..de3cde50cd 100644 --- a/openpype/tools/standalonepublish/widgets/widget_component_item.py +++ b/openpype/tools/standalonepublish/widgets/widget_component_item.py @@ -1,7 +1,6 @@ import os from Qt import QtCore, QtGui, QtWidgets from .resources import get_resource -from avalon import style class ComponentItem(QtWidgets.QFrame): @@ -61,7 +60,7 @@ class ComponentItem(QtWidgets.QFrame): name="menu", size=QtCore.QSize(22, 22) ) - self.action_menu = QtWidgets.QMenu() + self.action_menu = QtWidgets.QMenu(self.btn_action_menu) expanding_sizePolicy = QtWidgets.QSizePolicy( QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding @@ -229,7 +228,6 @@ class ComponentItem(QtWidgets.QFrame): if not self.btn_action_menu.isVisible(): self.btn_action_menu.setVisible(True) self.btn_action_menu.clicked.connect(self.show_actions) - self.action_menu.setStyleSheet(style.load_stylesheet()) def set_repre_name_valid(self, valid): self.has_valid_repre = valid From d28f8cd1d433b002bff2ded970b7950bd21fc120 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Wed, 30 Jun 2021 18:13:11 +0200 Subject: [PATCH 020/279] fix linux scripts, add patchelf --- Dockerfile | 46 ++++++++++++++++------------------ tools/build.sh | 5 ++-- tools/create_env.sh | 4 +-- tools/docker_build.sh | 2 +- tools/fetch_thirdparty_libs.py | 5 ++-- tools/fetch_thirdparty_libs.sh | 4 +-- 6 files changed, 33 insertions(+), 33 deletions(-) diff --git a/Dockerfile b/Dockerfile index 2d8ed27b15..99b9743de0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Build Pype docker image -FROM centos:7 AS builder +FROM centos:7 AS system_builder ARG OPENPYPE_PYTHON_VERSION=3.7.10 LABEL org.opencontainers.image.name="pypeclub/openpype" @@ -22,6 +22,7 @@ RUN yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.n which \ git \ devtoolset-7-gcc* \ + gcc-c++ \ make \ cmake \ curl \ @@ -35,13 +36,19 @@ RUN yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.n openssl-devel \ tk-devel libffi-devel \ qt5-qtbase-devel \ - patchelf \ + autoconf \ + automake \ && yum clean all -RUN mkdir /opt/openpype -# RUN useradd -m pype -# RUN chown pype /opt/openpype -# USER pype +# we need to build our own patchelf +WORKDIR /temp-patchelf +RUN git clone https://github.com/NixOS/patchelf.git . \ + && source scl_source enable devtoolset-7 \ + && ./bootstrap.sh \ + && ./configure \ + && make \ + && make install + RUN curl https://pyenv.run | bash ENV PYTHON_CONFIGURE_OPTS --enable-shared @@ -49,27 +56,21 @@ ENV PYTHON_CONFIGURE_OPTS --enable-shared RUN echo 'export PATH="$HOME/.pyenv/bin:$PATH"'>> $HOME/.bashrc \ && echo 'eval "$(pyenv init -)"' >> $HOME/.bashrc \ && echo 'eval "$(pyenv virtualenv-init -)"' >> $HOME/.bashrc \ - && echo 'eval "$(pyenv init --path)"' >> $HOME/.bashrc -RUN source $HOME/.bashrc && pyenv install ${OPENPYPE_PYTHON_VERSION} - -COPY . /opt/openpype/ -RUN rm -rf /openpype/.poetry || echo "No Poetry installed yet." -# USER root -# RUN chown -R pype /opt/openpype -RUN chmod +x /opt/openpype/tools/create_env.sh && chmod +x /opt/openpype/tools/build.sh - -# USER pype + && echo 'eval "$(pyenv init --path)"' >> $HOME/.bashrc \ + && source $HOME/.bashrc \ + && pyenv install ${OPENPYPE_PYTHON_VERSION} WORKDIR /opt/openpype - -RUN cd /opt/openpype \ +COPY . /opt/openpype/ +RUN rm -rf /opt/openpype/.poetry || echo "No Poetry installed yet." \ + && chmod +x /opt/openpype/tools/create_env.sh \ + && chmod +x /opt/openpype/tools/build.sh \ && source $HOME/.bashrc \ && pyenv local ${OPENPYPE_PYTHON_VERSION} RUN source $HOME/.bashrc \ - && ./tools/create_env.sh - -RUN source $HOME/.bashrc \ + && ./tools/create_env.sh \ + && source $HOME/.bashrc \ && ./tools/fetch_thirdparty_libs.sh RUN source $HOME/.bashrc \ @@ -77,6 +78,3 @@ RUN source $HOME/.bashrc \ && cp /usr/lib64/libffi* ./build/exe.linux-x86_64-3.7/lib \ && cp /usr/lib64/libssl* ./build/exe.linux-x86_64-3.7/lib \ && cp /usr/lib64/libcrypto* ./build/exe.linux-x86_64-3.7/lib - -RUN cd /opt/openpype \ - rm -rf ./vendor/bin diff --git a/tools/build.sh b/tools/build.sh index aa8f0121ea..4343431c2b 100755 --- a/tools/build.sh +++ b/tools/build.sh @@ -58,7 +58,7 @@ BICyan='\033[1;96m' # Cyan BIWhite='\033[1;97m' # White args=$@ -disable_submodule_update = 0 +disable_submodule_update=0 while :; do case $1 in --no-submodule-update) @@ -122,7 +122,7 @@ clean_pyc () { local path path=$openpype_root echo -e "${BIGreen}>>>${RST} Cleaning pyc at [ ${BIWhite}$path${RST} ] ... \c" - find "$path" -path ./build -prune -o -regex '^.*\(__pycache__\|\.py[co]\)$' -delete + find "$path" -path ./build -o -regex '^.*\(__pycache__\|\.py[co]\)$' -delete echo -e "${BIGreen}DONE${RST}" } @@ -228,3 +228,4 @@ if [ "$disable_submodule_update" == 1 ]; then } main +exit $? diff --git a/tools/create_env.sh b/tools/create_env.sh index 226a26e199..f93b8e32e6 100755 --- a/tools/create_env.sh +++ b/tools/create_env.sh @@ -126,7 +126,7 @@ clean_pyc () { local path path=$openpype_root echo -e "${BIGreen}>>>${RST} Cleaning pyc at [ ${BIWhite}$path${RST} ] ... \c" - find "$path" -path ./build -prune -o -regex '^.*\(__pycache__\|\.py[co]\)$' -delete + find "$path" -path ./build -o -regex '^.*\(__pycache__\|\.py[co]\)$' -delete echo -e "${BIGreen}DONE${RST}" } @@ -177,7 +177,7 @@ main () { echo -e "${BIGreen}>>>${RST} Installing dependencies ..." fi - poetry install --no-root $poetry_verbosity || { echo -e "${BIRed}!!!${RST} Poetry environment installation failed"; return; } + poetry install --no-root --ansi $poetry_verbosity || { echo -e "${BIRed}!!!${RST} Poetry environment installation failed"; return; } echo -e "${BIGreen}>>>${RST} Cleaning cache files ..." clean_pyc diff --git a/tools/docker_build.sh b/tools/docker_build.sh index 7600fe044b..a6df2a099e 100755 --- a/tools/docker_build.sh +++ b/tools/docker_build.sh @@ -32,7 +32,7 @@ main () { openpype_version="$(python3 <<< ${version_command})" echo -e "${BIGreen}>>>${RST} Running docker build ..." - docker build --pull --no-cache -t pypeclub/openpype:$openpype_version . + docker build --pull -t pypeclub/openpype:$openpype_version . if [ $? -ne 0 ] ; then echo -e "${BIRed}!!!${RST} Docker build failed." return 1 diff --git a/tools/fetch_thirdparty_libs.py b/tools/fetch_thirdparty_libs.py index 60392d782c..1ded907576 100644 --- a/tools/fetch_thirdparty_libs.py +++ b/tools/fetch_thirdparty_libs.py @@ -73,14 +73,15 @@ _print("Handling PySide2 Qt framework ...") pyside2_version = None try: pyside2_version = pyproject["openpype"]["pyside2"]["version"] + _print("We'll install PySide2{}".format(pyside2_version)) except AttributeError: _print("No PySide2 version was specified, using latest available.", 2) -pyside2_arg = "PySide2" if pyside2_version else "PySide2{}".format(pyside2_version) # noqa: E501 +pyside2_arg = "PySide2" if not pyside2_version else "PySide2{}".format(pyside2_version) # noqa: E501 try: subprocess.run( [sys.executable, "-m", "pip", "install", "--upgrade", - pyside2_arg, "-t", str(openpype_root / "vendor/python")], check=True) + pyside2_arg, "-t", str(openpype_root / "vendor/python")], check=True, stdout=subprocess.DEVNULL) except subprocess.CalledProcessError as e: _print("Error during PySide2 installation.", 1) _print(str(e), 1) diff --git a/tools/fetch_thirdparty_libs.sh b/tools/fetch_thirdparty_libs.sh index 31f109ba68..12116d9e9e 100755 --- a/tools/fetch_thirdparty_libs.sh +++ b/tools/fetch_thirdparty_libs.sh @@ -99,8 +99,8 @@ main () { pushd "$openpype_root" > /dev/null || return > /dev/null - echo -e "${BIGreen}>>>${RST} Running Pype tool ..." + echo -e "${BIGreen}>>>${RST} Fetching third party dependencies ..." poetry run python "$openpype_root/tools/fetch_thirdparty_libs.py" } -main \ No newline at end of file +main From f460fad23f992d3ed34a5135c0eb286ef24ca2c5 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Wed, 30 Jun 2021 18:15:43 +0200 Subject: [PATCH 021/279] fix hound --- tools/fetch_thirdparty_libs.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/fetch_thirdparty_libs.py b/tools/fetch_thirdparty_libs.py index 1ded907576..803d092186 100644 --- a/tools/fetch_thirdparty_libs.py +++ b/tools/fetch_thirdparty_libs.py @@ -81,7 +81,8 @@ pyside2_arg = "PySide2" if not pyside2_version else "PySide2{}".format(pyside2_v try: subprocess.run( [sys.executable, "-m", "pip", "install", "--upgrade", - pyside2_arg, "-t", str(openpype_root / "vendor/python")], check=True, stdout=subprocess.DEVNULL) + pyside2_arg, "-t", str(openpype_root / "vendor/python")], + check=True, stdout=subprocess.DEVNULL) except subprocess.CalledProcessError as e: _print("Error during PySide2 installation.", 1) _print(str(e), 1) From fa78f9805b2135df3ea9bef8ab6adf282623156a Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 1 Jul 2021 10:41:23 +0200 Subject: [PATCH 022/279] add ncurses --- Dockerfile | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 99b9743de0..74ab06a114 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,7 +22,7 @@ RUN yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.n which \ git \ devtoolset-7-gcc* \ - gcc-c++ \ + gcc-c++ \ make \ cmake \ curl \ @@ -36,8 +36,9 @@ RUN yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.n openssl-devel \ tk-devel libffi-devel \ qt5-qtbase-devel \ - autoconf \ - automake \ + autoconf \ + automake \ + ncurses-libs \ && yum clean all # we need to build our own patchelf From 6d270bfeb5d951fe09ef580e5b7d625c3f2ec753 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 3 Sep 2021 19:44:10 +0200 Subject: [PATCH 023/279] script fixes --- Dockerfile | 9 ++++++--- pyproject.toml | 2 +- tools/build.sh | 15 +++++++++------ tools/create_env.sh | 22 ++++++++++++++-------- tools/docker_build.sh | 4 +++- 5 files changed, 33 insertions(+), 19 deletions(-) diff --git a/Dockerfile b/Dockerfile index 2d8ed27b15..78611860ea 100644 --- a/Dockerfile +++ b/Dockerfile @@ -33,6 +33,7 @@ RUN yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.n readline-devel \ sqlite sqlite-devel \ openssl-devel \ + openssl-libs \ tk-devel libffi-devel \ qt5-qtbase-devel \ patchelf \ @@ -73,10 +74,12 @@ RUN source $HOME/.bashrc \ && ./tools/fetch_thirdparty_libs.sh RUN source $HOME/.bashrc \ - && bash ./tools/build.sh \ - && cp /usr/lib64/libffi* ./build/exe.linux-x86_64-3.7/lib \ + && bash ./tools/build.sh + +RUN cp /usr/lib64/libffi* ./build/exe.linux-x86_64-3.7/lib \ && cp /usr/lib64/libssl* ./build/exe.linux-x86_64-3.7/lib \ - && cp /usr/lib64/libcrypto* ./build/exe.linux-x86_64-3.7/lib + && cp /usr/lib64/libcrypto* ./build/exe.linux-x86_64-3.7/lib \ + && cp /root/.pyenv/versions/${OPENPYPE_PYTHON_VERSION}/lib/libpython* ./build/exe.linux-x86_64-3.7/lib RUN cd /opt/openpype \ rm -rf ./vendor/bin diff --git a/pyproject.toml b/pyproject.toml index e376986606..a57ae19224 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -68,7 +68,7 @@ slack-sdk = "^3.6.0" flake8 = "^3.7" autopep8 = "^1.4" coverage = "*" -cx_freeze = "^6.6" +cx_freeze = "*" GitPython = "^3.1.17" jedi = "^0.13" Jinja2 = "^2.11" diff --git a/tools/build.sh b/tools/build.sh index c44e7157af..bc79f03db7 100755 --- a/tools/build.sh +++ b/tools/build.sh @@ -58,7 +58,7 @@ BICyan='\033[1;96m' # Cyan BIWhite='\033[1;97m' # White args=$@ -disable_submodule_update = 0 +disable_submodule_update=0 while :; do case $1 in --no-submodule-update) @@ -90,6 +90,7 @@ done ############################################################################### detect_python () { echo -e "${BIGreen}>>>${RST} Using python \c" + command -v python >/dev/null 2>&1 || { echo -e "${BIRed}- NOT FOUND${RST} ${BIYellow}You need Python 3.7 installed to continue.${RST}"; return 1; } local version_command version_command="import sys;print('{0}.{1}'.format(sys.version_info[0], sys.version_info[1]))" local python_version @@ -122,7 +123,7 @@ clean_pyc () { local path path=$openpype_root echo -e "${BIGreen}>>>${RST} Cleaning pyc at [ ${BIWhite}$path${RST} ] ... \c" - find "$path" -path ./build -prune -o -regex '^.*\(__pycache__\|\.py[co]\)$' -delete + find "$path" -path ./build -o -regex '^.*\(__pycache__\|\.py[co]\)$' -delete echo -e "${BIGreen}DONE${RST}" } @@ -173,7 +174,7 @@ main () { else echo -e "${BIYellow}NOT FOUND${RST}" echo -e "${BIYellow}***${RST} We need to install Poetry and virtual env ..." - . "$openpype_root/tools/create_env.sh" || { echo -e "${BIRed}!!!${RST} Poetry installation failed"; return; } + . "$openpype_root/tools/create_env.sh" || { echo -e "${BIRed}!!!${RST} Poetry installation failed"; return 1; } fi if [ "$disable_submodule_update" == 1 ]; then @@ -184,9 +185,9 @@ if [ "$disable_submodule_update" == 1 ]; then fi echo -e "${BIGreen}>>>${RST} Building ..." if [[ "$OSTYPE" == "linux-gnu"* ]]; then - "$POETRY_HOME/bin/poetry" run python "$openpype_root/setup.py" build > "$openpype_root/build/build.log" || { echo -e "${BIRed}!!!${RST} Build failed, see the build log."; return; } + "$POETRY_HOME/bin/poetry" run python "$openpype_root/setup.py" build &> "$openpype_root/build/build.log" || { echo -e "${BIRed}!!!${RST} Build failed, see the build log."; return 1; } elif [[ "$OSTYPE" == "darwin"* ]]; then - "$POETRY_HOME/bin/poetry" run python "$openpype_root/setup.py" bdist_mac > "$openpype_root/build/build.log" || { echo -e "${BIRed}!!!${RST} Build failed, see the build log."; return; } + "$POETRY_HOME/bin/poetry" run python "$openpype_root/setup.py" bdist_mac &> "$openpype_root/build/build.log" || { echo -e "${BIRed}!!!${RST} Build failed, see the build log."; return 1; } fi "$POETRY_HOME/bin/poetry" run python "$openpype_root/tools/build_dependencies.py" @@ -210,4 +211,6 @@ if [ "$disable_submodule_update" == 1 ]; then echo -e "${BIWhite}$openpype_root/build${RST} directory." } -main +return_code=0 +main || return_code=$? +exit $return_code diff --git a/tools/create_env.sh b/tools/create_env.sh index cc9eddc317..4ed6412c43 100755 --- a/tools/create_env.sh +++ b/tools/create_env.sh @@ -88,6 +88,7 @@ done ############################################################################### detect_python () { echo -e "${BIGreen}>>>${RST} Using python \c" + command -v python >/dev/null 2>&1 || { echo -e "${BIRed}- NOT FOUND${RST} ${BIYellow}You need Python 3.7 installed to continue.${RST}"; return 1; } local version_command="import sys;print('{0}.{1}'.format(sys.version_info[0], sys.version_info[1]))" local python_version="$(python <<< ${version_command})" oIFS="$IFS" @@ -125,7 +126,7 @@ clean_pyc () { local path path=$openpype_root echo -e "${BIGreen}>>>${RST} Cleaning pyc at [ ${BIWhite}$path${RST} ] ... \c" - find "$path" -path ./build -prune -o -regex '^.*\(__pycache__\|\.py[co]\)$' -delete + find "$path" -path ./build -o -regex '^.*\(__pycache__\|\.py[co]\)$' -delete echo -e "${BIGreen}DONE${RST}" } @@ -166,7 +167,7 @@ main () { echo -e "${BIGreen}OK${RST}" else echo -e "${BIYellow}NOT FOUND${RST}" - install_poetry || { echo -e "${BIRed}!!!${RST} Poetry installation failed"; return; } + install_poetry || { echo -e "${BIRed}!!!${RST} Poetry installation failed"; return 1; } fi if [ -f "$openpype_root/poetry.lock" ]; then @@ -175,7 +176,11 @@ main () { echo -e "${BIGreen}>>>${RST} Installing dependencies ..." fi - "$POETRY_HOME/bin/poetry" install --no-root $poetry_verbosity || { echo -e "${BIRed}!!!${RST} Poetry environment installation failed"; return; } + "$POETRY_HOME/bin/poetry" install --no-root $poetry_verbosity || { echo -e "${BIRed}!!!${RST} Poetry environment installation failed"; return 1; } + if [ $? -ne 0 ] ; then + echo -e "${BIRed}!!!${RST} Virtual environment creation failed." + return 1 + fi echo -e "${BIGreen}>>>${RST} Cleaning cache files ..." clean_pyc @@ -184,10 +189,11 @@ main () { # cx_freeze will crash on missing __pychache__ on these but # reinstalling them solves the problem. echo -e "${BIGreen}>>>${RST} Fixing pycache bug ..." - "$POETRY_HOME/bin/poetry" run python -m pip install --force-reinstall pip - "$POETRY_HOME/bin/poetry" run pip install --force-reinstall setuptools - "$POETRY_HOME/bin/poetry" run pip install --force-reinstall wheel - "$POETRY_HOME/bin/poetry" run python -m pip install --force-reinstall pip + "$POETRY_HOME/bin/poetry" run pip install --disable-pip-version-check --force-reinstall setuptools + "$POETRY_HOME/bin/poetry" run pip install --disable-pip-version-check --force-reinstall wheel + "$POETRY_HOME/bin/poetry" run python -m pip install --disable-pip-version-check --force-reinstall pip } -main -3 +return_code=0 +main || return_code=$? +exit $return_code diff --git a/tools/docker_build.sh b/tools/docker_build.sh index 7600fe044b..dca217d534 100755 --- a/tools/docker_build.sh +++ b/tools/docker_build.sh @@ -32,7 +32,8 @@ main () { openpype_version="$(python3 <<< ${version_command})" echo -e "${BIGreen}>>>${RST} Running docker build ..." - docker build --pull --no-cache -t pypeclub/openpype:$openpype_version . + # docker build --pull --no-cache -t pypeclub/openpype:$openpype_version . + docker build --pull -t pypeclub/openpype:$openpype_version . if [ $? -ne 0 ] ; then echo -e "${BIRed}!!!${RST} Docker build failed." return 1 @@ -47,6 +48,7 @@ main () { fi echo -e "${BIYellow}---${RST} Copying ..." docker cp "$id:/opt/openpype/build/exe.linux-x86_64-3.7" "$openpype_root/build" + docker cp "$id:/opt/openpype/build/build.log" "$openpype_root/build" if [ $? -ne 0 ] ; then echo -e "${BIRed}!!!${RST} Copying failed." return 1 From d26095883921ac187dfccde229f38ceee5eea745 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Mon, 6 Sep 2021 10:43:50 +0200 Subject: [PATCH 024/279] try to get error log from failed build --- tools/docker_build.sh | 44 +++++++++++++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/tools/docker_build.sh b/tools/docker_build.sh index dca217d534..c27041a1af 100755 --- a/tools/docker_build.sh +++ b/tools/docker_build.sh @@ -20,6 +20,28 @@ realpath () { echo $(cd $(dirname "$1"); pwd)/$(basename "$1") } +create_container () { + if [ ! -f "$openpype_root/build/docker-image.id" ]; then + echo -e "${BIRed}!!!${RST} Docker command failed, cannot find image id." + exit 1 + fi + local id=$(<"$openpype_root/build/docker-image.id") + echo -e "${BIYellow}---${RST} Creating container from $id ..." + local cid="$(docker create $id bash)" + if [ $? -ne 0 ] ; then + echo -e "${BIRed}!!!${RST} Cannot create container." + exit 1 + fi + return $cid +} + +retrieve_build_log () { + create_container + local cid=$? + echo -e "${BIYellow}***${RST} Copying build log to ${BIWhite}$openpype_root/build/build.log${RST}" + docker cp "$cid:/opt/openpype/build/build.log" "$openpype_root/build" +} + # Main main () { openpype_root=$(realpath $(dirname $(dirname "${BASH_SOURCE[0]}"))) @@ -28,34 +50,32 @@ main () { echo -e "${BIYellow}---${RST} Cleaning build directory ..." rm -rf "$openpype_root/build" && mkdir "$openpype_root/build" > /dev/null - version_command="import os;exec(open(os.path.join('$openpype_root', 'openpype', 'version.py')).read());print(__version__);" - openpype_version="$(python3 <<< ${version_command})" + local version_command="import os;exec(open(os.path.join('$openpype_root', 'openpype', 'version.py')).read());print(__version__);" + local openpype_version="$(python3 <<< ${version_command})" echo -e "${BIGreen}>>>${RST} Running docker build ..." # docker build --pull --no-cache -t pypeclub/openpype:$openpype_version . - docker build --pull -t pypeclub/openpype:$openpype_version . + docker build --pull --iidfile $openpype_root/build/docker-image.id -t pypeclub/openpype:$openpype_version . if [ $? -ne 0 ] ; then + echo $? echo -e "${BIRed}!!!${RST} Docker build failed." + retrieve_build_log return 1 fi echo -e "${BIGreen}>>>${RST} Copying build from container ..." - echo -e "${BIYellow}---${RST} Creating container from pypeclub/openpype:$openpype_version ..." - id="$(docker create -ti pypeclub/openpype:$openpype_version bash)" - if [ $? -ne 0 ] ; then - echo -e "${BIRed}!!!${RST} Cannot create just built container." - return 1 - fi + create_container + local cid=$? echo -e "${BIYellow}---${RST} Copying ..." - docker cp "$id:/opt/openpype/build/exe.linux-x86_64-3.7" "$openpype_root/build" - docker cp "$id:/opt/openpype/build/build.log" "$openpype_root/build" + docker cp "$cid:/opt/openpype/build/exe.linux-x86_64-3.7" "$openpype_root/build" + docker cp "$cid:/opt/openpype/build/build.log" "$openpype_root/build" if [ $? -ne 0 ] ; then echo -e "${BIRed}!!!${RST} Copying failed." return 1 fi echo -e "${BIGreen}>>>${RST} Fixing user ownership ..." - username="$(logname)" + local username="$(logname)" chown -R $username ./build echo -e "${BIGreen}>>>${RST} All done, you can delete container:" From 1d6b10bb1d98e48f3badb6f8da84f06607ca455c Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Tue, 7 Sep 2021 15:51:36 +0100 Subject: [PATCH 025/279] Camera handling between Blender and Unreal implemented --- .../blender/plugins/create/create_camera.py | 51 ++++- .../blender/plugins/load/load_layout_json.py | 18 ++ .../blender/plugins/publish/extract_camera.py | 72 +++++++ .../hosts/unreal/plugins/load/load_camera.py | 175 ++++++++++++++++++ 4 files changed, 306 insertions(+), 10 deletions(-) create mode 100644 openpype/hosts/blender/plugins/publish/extract_camera.py create mode 100644 openpype/hosts/unreal/plugins/load/load_camera.py diff --git a/openpype/hosts/blender/plugins/create/create_camera.py b/openpype/hosts/blender/plugins/create/create_camera.py index c7fea30787..cf95c6b6d1 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -3,11 +3,12 @@ import bpy from avalon import api -from avalon.blender import lib -import openpype.hosts.blender.api.plugin +from avalon.blender import lib, ops +from avalon.blender.pipeline import AVALON_INSTANCES +from openpype.hosts.blender.api import plugin -class CreateCamera(openpype.hosts.blender.api.plugin.Creator): +class CreateCamera(plugin.Creator): """Polygonal static geometry""" name = "cameraMain" @@ -16,17 +17,47 @@ class CreateCamera(openpype.hosts.blender.api.plugin.Creator): icon = "video-camera" def process(self): + """ Run the creator on Blender main thread""" + mti = ops.MainThreadItem(self._process) + ops.execute_in_main_thread(mti) + def process(self): + # Get Instance Containter or create it if it does not exist + instances = bpy.data.collections.get(AVALON_INSTANCES) + if not instances: + instances = bpy.data.collections.new(name=AVALON_INSTANCES) + bpy.context.scene.collection.children.link(instances) + + + # Create instance object asset = self.data["asset"] subset = self.data["subset"] - name = openpype.hosts.blender.api.plugin.asset_name(asset, subset) - collection = bpy.data.collections.new(name=name) - bpy.context.scene.collection.children.link(collection) + name = plugin.asset_name(asset, subset) + + camera = bpy.data.cameras.new(subset) + camera_obj = bpy.data.objects.new(subset, camera) + + instances.objects.link(camera_obj) + + asset_group = bpy.data.objects.new(name=name, object_data=None) + asset_group.empty_display_type = 'SINGLE_ARROW' + instances.objects.link(asset_group) self.data['task'] = api.Session.get('AVALON_TASK') - lib.imprint(collection, self.data) + print(f"self.data: {self.data}") + lib.imprint(asset_group, self.data) if (self.options or {}).get("useSelection"): - for obj in lib.get_selection(): - collection.objects.link(obj) + bpy.context.view_layer.objects.active = asset_group + selected = lib.get_selection() + for obj in selected: + obj.select_set(True) + selected.append(asset_group) + bpy.ops.object.parent_set(keep_transform=True) + else: + bpy.ops.object.select_all(action='DESELECT') + camera_obj.select_set(True) + asset_group.select_set(True) + bpy.context.view_layer.objects.active = asset_group + bpy.ops.object.parent_set(keep_transform=True) - return collection + return asset_group diff --git a/openpype/hosts/blender/plugins/load/load_layout_json.py b/openpype/hosts/blender/plugins/load/load_layout_json.py index 1a4dbbb5cb..0b3e69ada8 100644 --- a/openpype/hosts/blender/plugins/load/load_layout_json.py +++ b/openpype/hosts/blender/plugins/load/load_layout_json.py @@ -12,6 +12,7 @@ from avalon.blender.pipeline import AVALON_CONTAINERS from avalon.blender.pipeline import AVALON_CONTAINER_ID from avalon.blender.pipeline import AVALON_PROPERTY from avalon.blender.pipeline import AVALON_INSTANCES +from openpype import lib from openpype.hosts.blender.api import plugin @@ -59,6 +60,8 @@ class JsonLayoutLoader(plugin.AssetLoader): return None def _process(self, libpath, asset, asset_group, actions): + print(f"asset: {asset}") + bpy.ops.object.select_all(action='DESELECT') with open(libpath, "r") as fp: @@ -103,6 +106,21 @@ class JsonLayoutLoader(plugin.AssetLoader): options=options ) + # Create the camera asset and the camera instance + creator_plugin = lib.get_creator_by_name("CreateCamera") + if not creator_plugin: + raise ValueError("Creator plugin \"CreateCamera\" was " + "not found.") + + api.create( + creator_plugin, + name="camera", + # name=f"{unique_number}_{subset}_animation", + asset=asset, + options={"useSelection": False} + # data={"dependencies": str(context["representation"]["_id"])} + ) + def process_asset(self, context: dict, name: str, diff --git a/openpype/hosts/blender/plugins/publish/extract_camera.py b/openpype/hosts/blender/plugins/publish/extract_camera.py new file mode 100644 index 0000000000..3523498808 --- /dev/null +++ b/openpype/hosts/blender/plugins/publish/extract_camera.py @@ -0,0 +1,72 @@ +import os + +from openpype import api +from openpype.hosts.blender.api import plugin + +import bpy + + +class ExtractFBX(api.Extractor): + """Extract as FBX.""" + + label = "Extract FBX" + hosts = ["blender"] + families = ["camera"] + optional = True + + def process(self, instance): + # Define extract output file path + stagingdir = self.staging_dir(instance) + filename = f"{instance.name}.fbx" + filepath = os.path.join(stagingdir, filename) + + # Perform extraction + self.log.info("Performing extraction..") + + bpy.ops.object.select_all(action='DESELECT') + + selected = [] + + camera = None + + for obj in instance: + if obj.type == "CAMERA": + obj.select_set(True) + selected.append(obj) + camera = obj + break + + assert camera, "No camera found" + + context = plugin.create_blender_context( + active=camera, selected=selected) + + scale_length = bpy.context.scene.unit_settings.scale_length + bpy.context.scene.unit_settings.scale_length = 0.01 + + # We export the fbx + bpy.ops.export_scene.fbx( + context, + filepath=filepath, + use_active_collection=False, + use_selection=True, + object_types={'CAMERA'} + ) + + bpy.context.scene.unit_settings.scale_length = scale_length + + bpy.ops.object.select_all(action='DESELECT') + + if "representations" not in instance.data: + instance.data["representations"] = [] + + representation = { + 'name': 'fbx', + 'ext': 'fbx', + 'files': filename, + "stagingDir": stagingdir, + } + instance.data["representations"].append(representation) + + self.log.info("Extracted instance '%s' to: %s", + instance.name, representation) diff --git a/openpype/hosts/unreal/plugins/load/load_camera.py b/openpype/hosts/unreal/plugins/load/load_camera.py new file mode 100644 index 0000000000..3d6cde7daf --- /dev/null +++ b/openpype/hosts/unreal/plugins/load/load_camera.py @@ -0,0 +1,175 @@ +import os + +from avalon import api, pipeline +from avalon.unreal import lib +from avalon.unreal import pipeline as unreal_pipeline +import unreal + + +class CameraLoader(api.Loader): + """Load Unreal StaticMesh from FBX""" + + families = ["camera"] + label = "Load Camera" + representations = ["fbx"] + icon = "cube" + color = "orange" + + def load(self, context, name, namespace, data): + """ + Load and containerise representation into Content Browser. + + This is two step process. First, import FBX to temporary path and + then call `containerise()` on it - this moves all content to new + directory and then it will create AssetContainer there and imprint it + with metadata. This will mark this path as container. + + Args: + context (dict): application context + name (str): subset name + namespace (str): in Unreal this is basically path to container. + This is not passed here, so namespace is set + by `containerise()` because only then we know + real path. + data (dict): Those would be data to be imprinted. This is not used + now, data are imprinted by `containerise()`. + + Returns: + list(str): list of container content + """ + + # Create directory for asset and avalon container + root = "/Game/Avalon/Assets" + asset = context.get('asset').get('name') + suffix = "_CON" + if asset: + asset_name = "{}_{}".format(asset, name) + else: + asset_name = "{}".format(name) + + tools = unreal.AssetToolsHelpers().get_asset_tools() + + unique_number = 1 + + if unreal.EditorAssetLibrary.does_directory_exist(f"{root}/{asset}"): + asset_content = unreal.EditorAssetLibrary.list_assets( + f"{root}/{asset}", recursive=False, include_folder=True + ) + + # Get highest number to make a unique name + folders = [a for a in asset_content + if a[-1] == "/" and f"{name}_" in a] + f_numbers = [] + for f in folders: + # Get number from folder name. Splits the string by "_" and + # removes the last element (which is a "/"). + f_numbers.append(int(f.split("_")[-1][:-1])) + f_numbers.sort() + unique_number = f_numbers[-1] + 1 + + asset_dir, container_name = tools.create_unique_asset_name( + f"{root}/{asset}/{name}_{unique_number:02d}", suffix="") + + container_name += suffix + + unreal.EditorAssetLibrary.make_directory(asset_dir) + + sequence = tools.create_asset( + asset_name=asset_name, + package_path=asset_dir, + asset_class=unreal.LevelSequence, + factory=unreal.LevelSequenceFactoryNew() + ) + + settings = unreal.MovieSceneUserImportFBXSettings() + + unreal.SequencerTools.import_fbx( + unreal.EditorLevelLibrary.get_editor_world(), + sequence, + sequence.get_bindings(), + settings, + self.fname + ) + + # Create Asset Container + lib.create_avalon_container(container=container_name, path=asset_dir) + + data = { + "schema": "openpype:container-2.0", + "id": pipeline.AVALON_CONTAINER_ID, + "asset": asset, + "namespace": asset_dir, + "container_name": container_name, + "asset_name": asset_name, + "loader": str(self.__class__.__name__), + "representation": context["representation"]["_id"], + "parent": context["representation"]["parent"], + "family": context["representation"]["context"]["family"] + } + unreal_pipeline.imprint( + "{}/{}".format(asset_dir, container_name), data) + + asset_content = unreal.EditorAssetLibrary.list_assets( + asset_dir, recursive=True, include_folder=True + ) + + for a in asset_content: + unreal.EditorAssetLibrary.save_asset(a) + + return asset_content + + def update(self, container, representation): + path = container["namespace"] + + ar = unreal.AssetRegistryHelpers.get_asset_registry() + tools = unreal.AssetToolsHelpers().get_asset_tools() + + asset_content = unreal.EditorAssetLibrary.list_assets( + path, recursive=False, include_folder=False + ) + asset_name = "" + for a in asset_content: + asset = ar.get_asset_by_object_path(a) + if a.endswith("_CON"): + loaded_asset = unreal.EditorAssetLibrary.load_asset(a) + unreal.EditorAssetLibrary.set_metadata_tag( + loaded_asset, "representation", str(representation["_id"]) + ) + unreal.EditorAssetLibrary.set_metadata_tag( + loaded_asset, "parent", str(representation["parent"]) + ) + asset_name = unreal.EditorAssetLibrary.get_metadata_tag( + loaded_asset, "asset_name" + ) + elif asset.asset_class == "LevelSequence": + unreal.EditorAssetLibrary.delete_asset(a) + + sequence = tools.create_asset( + asset_name=asset_name, + package_path=path, + asset_class=unreal.LevelSequence, + factory=unreal.LevelSequenceFactoryNew() + ) + + settings = unreal.MovieSceneUserImportFBXSettings() + + unreal.SequencerTools.import_fbx( + unreal.EditorLevelLibrary.get_editor_world(), + sequence, + sequence.get_bindings(), + settings, + str(representation["data"]["path"]) + ) + + def remove(self, container): + path = container["namespace"] + parent_path = os.path.dirname(path) + + unreal.EditorAssetLibrary.delete_directory(path) + + asset_content = unreal.EditorAssetLibrary.list_assets( + parent_path, recursive=False, include_folder=True + ) + + if len(asset_content) == 0: + unreal.EditorAssetLibrary.delete_directory(parent_path) From eae7079b087f71e5d6e21fc85da9998eaf448368 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Tue, 7 Sep 2021 16:00:05 +0100 Subject: [PATCH 026/279] Hound fixes --- openpype/hosts/blender/plugins/create/create_camera.py | 3 +-- openpype/hosts/unreal/plugins/load/load_camera.py | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/blender/plugins/create/create_camera.py b/openpype/hosts/blender/plugins/create/create_camera.py index cf95c6b6d1..fad827d85a 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -21,14 +21,13 @@ class CreateCamera(plugin.Creator): mti = ops.MainThreadItem(self._process) ops.execute_in_main_thread(mti) - def process(self): + def _process(self): # Get Instance Containter or create it if it does not exist instances = bpy.data.collections.get(AVALON_INSTANCES) if not instances: instances = bpy.data.collections.new(name=AVALON_INSTANCES) bpy.context.scene.collection.children.link(instances) - # Create instance object asset = self.data["asset"] subset = self.data["subset"] diff --git a/openpype/hosts/unreal/plugins/load/load_camera.py b/openpype/hosts/unreal/plugins/load/load_camera.py index 3d6cde7daf..bf89ecd23e 100644 --- a/openpype/hosts/unreal/plugins/load/load_camera.py +++ b/openpype/hosts/unreal/plugins/load/load_camera.py @@ -57,8 +57,8 @@ class CameraLoader(api.Loader): ) # Get highest number to make a unique name - folders = [a for a in asset_content - if a[-1] == "/" and f"{name}_" in a] + folders = [a for a in asset_content + if a[-1] == "/" and f"{name}_" in a] f_numbers = [] for f in folders: # Get number from folder name. Splits the string by "_" and From 46697d8d816c3a603a4ec8f0ed8cd8f1c2ef17e8 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Wed, 8 Sep 2021 18:53:37 +0200 Subject: [PATCH 027/279] fix docker build, switch to cx_freeze 6.7 --- poetry.lock | 684 +++++++++++++++++++++++++----------------- pyproject.toml | 2 +- tools/docker_build.sh | 7 +- 3 files changed, 416 insertions(+), 277 deletions(-) diff --git a/poetry.lock b/poetry.lock index e011b781c9..6dae442c9d 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.7.3" description = "An abstract syntax tree for Python with inference support." category = "dev" optional = false @@ -89,6 +89,7 @@ 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\""} +typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} wrapt = ">=1.11,<1.13" [[package]] @@ -146,11 +147,11 @@ pytz = ">=2015.7" [[package]] name = "blessed" -version = "1.18.0" +version = "1.18.1" 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\""} @@ -175,7 +176,7 @@ python-versions = "*" [[package]] name = "cffi" -version = "1.14.5" +version = "1.14.6" description = "Foreign Function Interface for Python calling C code." category = "main" optional = false @@ -192,6 +193,17 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +[[package]] +name = "charset-normalizer" +version = "2.0.4" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" +optional = false +python-versions = ">=3.5.0" + +[package.extras] +unicode_backport = ["unicodedata2"] + [[package]] name = "click" version = "7.1.2" @@ -253,7 +265,7 @@ toml = ["toml"] [[package]] name = "cryptography" -version = "3.4.7" +version = "3.4.8" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." category = "main" optional = false @@ -272,15 +284,20 @@ test = ["pytest (>=6.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pret [[package]] name = "cx-freeze" -version = "6.6" +version = "6.7" description = "Create standalone executables from Python scripts" category = "dev" optional = false python-versions = ">=3.6" [package.dependencies] -cx-Logging = {version = ">=3.0", markers = "sys_platform == \"win32\""} -importlib-metadata = ">=3.1.1" +cx-logging = {version = ">=3.0", markers = "sys_platform == \"win32\""} +importlib-metadata = ">=4.3.1" + +[package.source] +type = "legacy" +url = "https://distribute.openpype.io/wheels" +reference = "openpype" [[package]] name = "cx-logging" @@ -386,19 +403,19 @@ smmap = ">=3.0.1,<5" [[package]] name = "gitpython" -version = "3.1.17" +version = "3.1.20" description = "Python Git Library" category = "dev" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" [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.2" description = "Google API client core library" category = "main" optional = false @@ -436,7 +453,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 @@ -493,11 +510,11 @@ pyparsing = ">=2.4.2,<3" [[package]] name = "idna" -version = "2.10" +version = "3.2" description = "Internationalized Domain Names in Applications (IDNA)" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.5" [[package]] name = "imagesize" @@ -509,7 +526,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 @@ -521,7 +538,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" @@ -533,16 +551,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" @@ -560,14 +579,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" @@ -695,11 +715,11 @@ reference = "openpype" [[package]] name = "packaging" -version = "20.9" +version = "21.0" 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" @@ -718,7 +738,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 @@ -729,25 +749,38 @@ six = "*" [[package]] name = "pillow" -version = "8.2.0" +version = "8.3.2" description = "Python Imaging Library (Fork)" category = "main" optional = false python-versions = ">=3.6" +[[package]] +name = "platformdirs" +version = "2.3.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 = "prefixed" @@ -849,7 +882,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 @@ -857,22 +890,23 @@ python-versions = ">=3.5" [[package]] name = "pylint" -version = "2.8.3" +version = "2.10.2" description = "python code static checker" category = "dev" optional = false python-versions = "~=3.6" [package.dependencies] -astroid = "2.5.6" +astroid = ">=2.7.2,<2.8" colorama = {version = "*", markers = "sys_platform == \"win32\""} isort = ">=4.2.5,<6" mccabe = ">=0.6,<0.7" +platformdirs = ">=2.2.0" toml = ">=0.7.1" [[package]] name = "pymongo" -version = "3.11.4" +version = "3.12.0" description = "Python driver for MongoDB " category = "main" optional = false @@ -880,9 +914,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"] @@ -971,15 +1005,15 @@ python-versions = ">=3.5" [[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 = "pytest" -version = "6.2.4" +version = "6.2.5" description = "pytest: simple powerful testing with Python" category = "dev" optional = false @@ -992,7 +1026,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 = "*" @@ -1017,21 +1051,21 @@ testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtuale [[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 @@ -1042,7 +1076,7 @@ six = ">=1.5" [[package]] name = "python-xlib" -version = "0.30" +version = "0.31" description = "Python X Library" category = "main" optional = false @@ -1085,7 +1119,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 @@ -1106,21 +1140,21 @@ sphinx = ">=1.3.1" [[package]] name = "requests" -version = "2.25.1" +version = "2.26.0" description = "Python HTTP for Humans." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [package.dependencies] certifi = ">=2017.4.17" -chardet = ">=3.0.2,<5" -idna = ">=2.5,<3" +charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} +idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} urllib3 = ">=1.21.1,<1.27" [package.extras] -security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] +use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] [[package]] name = "rsa" @@ -1163,15 +1197,15 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "slack-sdk" -version = "3.6.0" +version = "3.10.1" 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.7b0)", "psutil (>=5,<6)", "databases (>=0.3)", "boto3 (<=2)", "moto (<2)"] [[package]] name = "smmap" @@ -1199,7 +1233,7 @@ python-versions = "*" [[package]] name = "sphinx" -version = "4.0.2" +version = "4.1.2" description = "Python documentation generator" category = "dev" optional = false @@ -1218,14 +1252,14 @@ requests = ">=2.5.0" snowballstemmer = ">=1.1" sphinxcontrib-applehelp = "*" sphinxcontrib-devhelp = "*" -sphinxcontrib-htmlhelp = "*" +sphinxcontrib-htmlhelp = ">=2.0.0" sphinxcontrib-jsmath = "*" sphinxcontrib-qthelp = "*" -sphinxcontrib-serializinghtml = "*" +sphinxcontrib-serializinghtml = ">=1.1.5" [package.extras] docs = ["sphinxcontrib-websupport"] -lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.800)", "docutils-stubs"] +lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.900)", "docutils-stubs", "types-typed-ast", "types-pkg-resources", "types-requests"] test = ["pytest", "pytest-cov", "html5lib", "cython", "typed-ast"] [[package]] @@ -1367,7 +1401,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 @@ -1383,7 +1417,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "urllib3" -version = "1.26.5" +version = "1.26.6" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false @@ -1453,7 +1487,7 @@ typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} [[package]] name = "zipp" -version = "3.4.1" +version = "3.5.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "main" optional = false @@ -1461,12 +1495,12 @@ 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" python-versions = "3.7.*" -content-hash = "8875d530ae66f9763b5b0cb84d9d35edc184ef5c141b63d38bf1ff5a1226e556" +content-hash = "ca2a0258a784674ff489a07d0dc8dd2a22373ee39add02cb4676898b8a6993a1" [metadata.files] acre = [] @@ -1530,8 +1564,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.7.3-py3-none-any.whl", hash = "sha256:dc1e8b28427d6bbef6b8842b18765ab58f558c42bb80540bd7648c98412af25e"}, + {file = "astroid-2.7.3.tar.gz", hash = "sha256:3b680ce0419b8a771aba6190139a3998d14b413852506d99aff8dc2bf65ee67c"}, ] async-timeout = [ {file = "async-timeout-3.0.1.tar.gz", hash = "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f"}, @@ -1554,8 +1588,8 @@ babel = [ {file = "Babel-2.9.1.tar.gz", hash = "sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0"}, ] 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.18.1-py2.py3-none-any.whl", hash = "sha256:dd7c0d33db9a2e7f597b446996484d0ed46e1586239db064fb5025008937dcae"}, + {file = "blessed-1.18.1.tar.gz", hash = "sha256:8b09936def6bc06583db99b65636b980075733e13550cb6af262ce724a55da23"}, ] cachetools = [ {file = "cachetools-4.2.2-py3-none-any.whl", hash = "sha256:2cc0b89715337ab6dbba85b5b50effe2b0c74e035d83ee8ed637cf52f12ae001"}, @@ -1566,48 +1600,60 @@ certifi = [ {file = "certifi-2021.5.30.tar.gz", hash = "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee"}, ] 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-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-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-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-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.14.6-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:22b9c3c320171c108e903d61a3723b51e37aaa8c81255b5e7ce102775bd01e2c"}, + {file = "cffi-1.14.6-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:f0c5d1acbfca6ebdd6b1e3eded8d261affb6ddcf2186205518f1428b8569bb99"}, + {file = "cffi-1.14.6-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:99f27fefe34c37ba9875f224a8f36e31d744d8083e00f520f133cab79ad5e819"}, + {file = "cffi-1.14.6-cp27-cp27m-win32.whl", hash = "sha256:55af55e32ae468e9946f741a5d51f9896da6b9bf0bbdd326843fec05c730eb20"}, + {file = "cffi-1.14.6-cp27-cp27m-win_amd64.whl", hash = "sha256:7bcac9a2b4fdbed2c16fa5681356d7121ecabf041f18d97ed5b8e0dd38a80224"}, + {file = "cffi-1.14.6-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ed38b924ce794e505647f7c331b22a693bee1538fdf46b0222c4717b42f744e7"}, + {file = "cffi-1.14.6-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e22dcb48709fc51a7b58a927391b23ab37eb3737a98ac4338e2448bef8559b33"}, + {file = "cffi-1.14.6-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:aedb15f0a5a5949ecb129a82b72b19df97bbbca024081ed2ef88bd5c0a610534"}, + {file = "cffi-1.14.6-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:48916e459c54c4a70e52745639f1db524542140433599e13911b2f329834276a"}, + {file = "cffi-1.14.6-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f627688813d0a4140153ff532537fbe4afea5a3dffce1f9deb7f91f848a832b5"}, + {file = "cffi-1.14.6-cp35-cp35m-win32.whl", hash = "sha256:f0010c6f9d1a4011e429109fda55a225921e3206e7f62a0c22a35344bfd13cca"}, + {file = "cffi-1.14.6-cp35-cp35m-win_amd64.whl", hash = "sha256:57e555a9feb4a8460415f1aac331a2dc833b1115284f7ded7278b54afc5bd218"}, + {file = "cffi-1.14.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e8c6a99be100371dbb046880e7a282152aa5d6127ae01783e37662ef73850d8f"}, + {file = "cffi-1.14.6-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:19ca0dbdeda3b2615421d54bef8985f72af6e0c47082a8d26122adac81a95872"}, + {file = "cffi-1.14.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d950695ae4381ecd856bcaf2b1e866720e4ab9a1498cba61c602e56630ca7195"}, + {file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9dc245e3ac69c92ee4c167fbdd7428ec1956d4e754223124991ef29eb57a09d"}, + {file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a8661b2ce9694ca01c529bfa204dbb144b275a31685a075ce123f12331be790b"}, + {file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b315d709717a99f4b27b59b021e6207c64620790ca3e0bde636a6c7f14618abb"}, + {file = "cffi-1.14.6-cp36-cp36m-win32.whl", hash = "sha256:80b06212075346b5546b0417b9f2bf467fea3bfe7352f781ffc05a8ab24ba14a"}, + {file = "cffi-1.14.6-cp36-cp36m-win_amd64.whl", hash = "sha256:a9da7010cec5a12193d1af9872a00888f396aba3dc79186604a09ea3ee7c029e"}, + {file = "cffi-1.14.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4373612d59c404baeb7cbd788a18b2b2a8331abcc84c3ba40051fcd18b17a4d5"}, + {file = "cffi-1.14.6-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f10afb1004f102c7868ebfe91c28f4a712227fe4cb24974350ace1f90e1febbf"}, + {file = "cffi-1.14.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:fd4305f86f53dfd8cd3522269ed7fc34856a8ee3709a5e28b2836b2db9d4cd69"}, + {file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d6169cb3c6c2ad50db5b868db6491a790300ade1ed5d1da29289d73bbe40b56"}, + {file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d4b68e216fc65e9fe4f524c177b54964af043dde734807586cf5435af84045c"}, + {file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33791e8a2dc2953f28b8d8d300dde42dd929ac28f974c4b4c6272cb2955cb762"}, + {file = "cffi-1.14.6-cp37-cp37m-win32.whl", hash = "sha256:0c0591bee64e438883b0c92a7bed78f6290d40bf02e54c5bf0978eaf36061771"}, + {file = "cffi-1.14.6-cp37-cp37m-win_amd64.whl", hash = "sha256:8eb687582ed7cd8c4bdbff3df6c0da443eb89c3c72e6e5dcdd9c81729712791a"}, + {file = "cffi-1.14.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ba6f2b3f452e150945d58f4badd92310449876c4c954836cfb1803bdd7b422f0"}, + {file = "cffi-1.14.6-cp38-cp38-manylinux1_i686.whl", hash = "sha256:64fda793737bc4037521d4899be780534b9aea552eb673b9833b01f945904c2e"}, + {file = "cffi-1.14.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:9f3e33c28cd39d1b655ed1ba7247133b6f7fc16fa16887b120c0c670e35ce346"}, + {file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26bb2549b72708c833f5abe62b756176022a7b9a7f689b571e74c8478ead51dc"}, + {file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb687a11f0a7a1839719edd80f41e459cc5366857ecbed383ff376c4e3cc6afd"}, + {file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d2ad4d668a5c0645d281dcd17aff2be3212bc109b33814bbb15c4939f44181cc"}, + {file = "cffi-1.14.6-cp38-cp38-win32.whl", hash = "sha256:487d63e1454627c8e47dd230025780e91869cfba4c753a74fda196a1f6ad6548"}, + {file = "cffi-1.14.6-cp38-cp38-win_amd64.whl", hash = "sha256:c33d18eb6e6bc36f09d793c0dc58b0211fccc6ae5149b808da4a62660678b156"}, + {file = "cffi-1.14.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:06c54a68935738d206570b20da5ef2b6b6d92b38ef3ec45c5422c0ebaf338d4d"}, + {file = "cffi-1.14.6-cp39-cp39-manylinux1_i686.whl", hash = "sha256:f174135f5609428cc6e1b9090f9268f5c8935fddb1b25ccb8255a2d50de6789e"}, + {file = "cffi-1.14.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f3ebe6e73c319340830a9b2825d32eb6d8475c1dac020b4f0aa774ee3b898d1c"}, + {file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c8d896becff2fa653dc4438b54a5a25a971d1f4110b32bd3068db3722c80202"}, + {file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4922cd707b25e623b902c86188aca466d3620892db76c0bdd7b99a3d5e61d35f"}, + {file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c9e005e9bd57bc987764c32a1bee4364c44fdc11a3cc20a40b93b444984f2b87"}, + {file = "cffi-1.14.6-cp39-cp39-win32.whl", hash = "sha256:eb9e2a346c5238a30a746893f23a9535e700f8192a68c07c0258e7ece6ff3728"}, + {file = "cffi-1.14.6-cp39-cp39-win_amd64.whl", hash = "sha256:818014c754cd3dba7229c0f5884396264d51ffb87ec86e927ef0be140bfdb0d2"}, + {file = "cffi-1.14.6.tar.gz", hash = "sha256:c9a875ce9d7fe32887784274dd533c57909b7b1dcadcc128a2ac21331a9765dd"}, ] chardet = [ {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, ] +charset-normalizer = [ + {file = "charset-normalizer-2.0.4.tar.gz", hash = "sha256:f23667ebe1084be45f6ae0538e4a5a865206544097e4e8bbcacf42cd02a348f3"}, + {file = "charset_normalizer-2.0.4-py3-none-any.whl", hash = "sha256:0c8911edd15d19223366a194a513099a302055a962bca2cec0f54b8b63175d8b"}, +] click = [ {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, @@ -1683,30 +1729,25 @@ coverage = [ {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"}, ] 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-manylinux2010_x86_64.whl", hash = "sha256:7ec5d3b029f5fa2b179325908b9cd93db28ab7b85bb6c1db56b10e0b54235177"}, - {file = "cryptography-3.4.7-pp37-pypy37_pp73-manylinux2014_x86_64.whl", hash = "sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9"}, - {file = "cryptography-3.4.7.tar.gz", hash = "sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713"}, -] -cx-freeze = [ - {file = "cx_Freeze-6.6-cp36-cp36m-win32.whl", hash = "sha256:b3d3a6bcd1a07c50b4e1c907f14842642156110e63a99cd5c73b8a24751e9b97"}, - {file = "cx_Freeze-6.6-cp36-cp36m-win_amd64.whl", hash = "sha256:1935266ec644ea4f7e584985f44cefc0622a449a09980d990833a1a2afcadac8"}, - {file = "cx_Freeze-6.6-cp37-cp37m-win32.whl", hash = "sha256:1eac2b0f254319cc641ce25bd83337effd7936092562fde701f3ffb40e0274ec"}, - {file = "cx_Freeze-6.6-cp37-cp37m-win_amd64.whl", hash = "sha256:2bc46ef6d510811b6002f34a3ae4cbfdea44e18644febd2a404d3ee8e48a9fc4"}, - {file = "cx_Freeze-6.6-cp38-cp38-win32.whl", hash = "sha256:46eb50ebc46f7ae236d16c6a52671ab0f7bb479bea668da19f4b6de3cc413e9e"}, - {file = "cx_Freeze-6.6-cp38-cp38-win_amd64.whl", hash = "sha256:8c3b00476ce385bb58595bffce55aed031e5a6e16ab6e14d8bee9d1d569e46c3"}, - {file = "cx_Freeze-6.6-cp39-cp39-win32.whl", hash = "sha256:6e9340cbcf52d4836980ecc83ddba4f7704ff6654dd41168c146b74f512977ce"}, - {file = "cx_Freeze-6.6-cp39-cp39-win_amd64.whl", hash = "sha256:2fcf1c8b77ae5c06f45be3a9aff79e1dd808c0d624e97561f840dec5ea9b214a"}, - {file = "cx_Freeze-6.6.tar.gz", hash = "sha256:c4af8ad3f7e7d71e291c1dec5d0fb26bbe92df834b098ed35434c901fbd6762f"}, + {file = "cryptography-3.4.8-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:a00cf305f07b26c351d8d4e1af84ad7501eca8a342dedf24a7acb0e7b7406e14"}, + {file = "cryptography-3.4.8-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:f44d141b8c4ea5eb4dbc9b3ad992d45580c1d22bf5e24363f2fbf50c2d7ae8a7"}, + {file = "cryptography-3.4.8-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0a7dcbcd3f1913f664aca35d47c1331fce738d44ec34b7be8b9d332151b0b01e"}, + {file = "cryptography-3.4.8-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34dae04a0dce5730d8eb7894eab617d8a70d0c97da76b905de9efb7128ad7085"}, + {file = "cryptography-3.4.8-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1eb7bb0df6f6f583dd8e054689def236255161ebbcf62b226454ab9ec663746b"}, + {file = "cryptography-3.4.8-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:9965c46c674ba8cc572bc09a03f4c649292ee73e1b683adb1ce81e82e9a6a0fb"}, + {file = "cryptography-3.4.8-cp36-abi3-win32.whl", hash = "sha256:21ca464b3a4b8d8e86ba0ee5045e103a1fcfac3b39319727bc0fc58c09c6aff7"}, + {file = "cryptography-3.4.8-cp36-abi3-win_amd64.whl", hash = "sha256:3520667fda779eb788ea00080124875be18f2d8f0848ec00733c0ec3bb8219fc"}, + {file = "cryptography-3.4.8-pp36-pypy36_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d2a6e5ef66503da51d2110edf6c403dc6b494cc0082f85db12f54e9c5d4c3ec5"}, + {file = "cryptography-3.4.8-pp36-pypy36_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a305600e7a6b7b855cd798e00278161b681ad6e9b7eca94c721d5f588ab212af"}, + {file = "cryptography-3.4.8-pp36-pypy36_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:3fa3a7ccf96e826affdf1a0a9432be74dc73423125c8f96a909e3835a5ef194a"}, + {file = "cryptography-3.4.8-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:d9ec0e67a14f9d1d48dd87a2531009a9b251c02ea42851c060b25c782516ff06"}, + {file = "cryptography-3.4.8-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5b0fbfae7ff7febdb74b574055c7466da334a5371f253732d7e2e7525d570498"}, + {file = "cryptography-3.4.8-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94fff993ee9bc1b2440d3b7243d488c6a3d9724cc2b09cdb297f6a886d040ef7"}, + {file = "cryptography-3.4.8-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:8695456444f277af73a4877db9fc979849cd3ee74c198d04fc0776ebc3db52b9"}, + {file = "cryptography-3.4.8-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:cd65b60cfe004790c795cc35f272e41a3df4631e2fb6b35aa7ac6ef2859d554e"}, + {file = "cryptography-3.4.8.tar.gz", hash = "sha256:94cc5ed4ceaefcbe5bf38c8fba6a21fc1d365bb8fb826ea1688e3370b2e24a1c"}, ] +cx-freeze = [] cx-logging = [ {file = "cx_Logging-3.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:9fcd297e5c51470521c47eff0f86ba844aeca6be97e13c3e2114ebdf03fa3c96"}, {file = "cx_Logging-3.0-cp36-cp36m-win32.whl", hash = "sha256:0df4be47c5022cc54316949e283403214568ef599817ced0c0972183d6d4fabb"}, @@ -1753,20 +1794,20 @@ gitdb = [ {file = "gitdb-4.0.7.tar.gz", hash = "sha256:96bf5c08b157a666fec41129e6d327235284cca4c81e92109260f353ba138005"}, ] 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.20-py3-none-any.whl", hash = "sha256:b1e1c269deab1b08ce65403cf14e10d2ef1f6c89e33ea7c5e5bb0222ea593b8a"}, + {file = "GitPython-3.1.20.tar.gz", hash = "sha256:df0e072a200703a65387b0cfdf0466e3bab729c0458cf6b7349d0e9877636519"}, ] 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.2.tar.gz", hash = "sha256:8500aded318fdb235130bf183c726a05a9cb7c4b09c266bd5119b86cdb8a4d10"}, + {file = "google_api_core-1.31.2-py2.py3-none-any.whl", hash = "sha256:384459a0dc98c1c8cd90b28dc5800b8705e0275a673a7144a513ae80fc77950b"}, ] 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"}, @@ -1781,32 +1822,32 @@ httplib2 = [ {file = "httplib2-0.19.1.tar.gz", hash = "sha256:0b12617eeca7433d4c396a100eaecfa4b08ee99aa881e6df6e257a7aad5d533d"}, ] idna = [ - {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, - {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, + {file = "idna-3.2-py3-none-any.whl", hash = "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a"}, + {file = "idna-3.2.tar.gz", hash = "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"}, ] imagesize = [ {file = "imagesize-1.2.0-py2.py3-none-any.whl", hash = "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1"}, {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"}, @@ -1852,12 +1893,22 @@ 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"}, @@ -1866,14 +1917,21 @@ 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"}, @@ -1883,6 +1941,9 @@ 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"}, @@ -1932,56 +1993,79 @@ multidict = [ ] 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.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"}, + {file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"}, ] 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.2.0-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:dc38f57d8f20f06dd7c3161c59ca2c86893632623f33a42d592f097b00f720a9"}, - {file = "Pillow-8.2.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a013cbe25d20c2e0c4e85a9daf438f85121a4d0344ddc76e33fd7e3965d9af4b"}, - {file = "Pillow-8.2.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8bb1e155a74e1bfbacd84555ea62fa21c58e0b4e7e6b20e4447b8d07990ac78b"}, - {file = "Pillow-8.2.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c5236606e8570542ed424849f7852a0ff0bce2c4c8d0ba05cc202a5a9c97dee9"}, - {file = "Pillow-8.2.0-cp36-cp36m-win32.whl", hash = "sha256:12e5e7471f9b637762453da74e390e56cc43e486a88289995c1f4c1dc0bfe727"}, - {file = "Pillow-8.2.0-cp36-cp36m-win_amd64.whl", hash = "sha256:5afe6b237a0b81bd54b53f835a153770802f164c5570bab5e005aad693dab87f"}, - {file = "Pillow-8.2.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:cb7a09e173903541fa888ba010c345893cd9fc1b5891aaf060f6ca77b6a3722d"}, - {file = "Pillow-8.2.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0d19d70ee7c2ba97631bae1e7d4725cdb2ecf238178096e8c82ee481e189168a"}, - {file = "Pillow-8.2.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:083781abd261bdabf090ad07bb69f8f5599943ddb539d64497ed021b2a67e5a9"}, - {file = "Pillow-8.2.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:c6b39294464b03457f9064e98c124e09008b35a62e3189d3513e5148611c9388"}, - {file = "Pillow-8.2.0-cp37-cp37m-win32.whl", hash = "sha256:01425106e4e8cee195a411f729cff2a7d61813b0b11737c12bd5991f5f14bcd5"}, - {file = "Pillow-8.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3b570f84a6161cf8865c4e08adf629441f56e32f180f7aa4ccbd2e0a5a02cba2"}, - {file = "Pillow-8.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:031a6c88c77d08aab84fecc05c3cde8414cd6f8406f4d2b16fed1e97634cc8a4"}, - {file = "Pillow-8.2.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:66cc56579fd91f517290ab02c51e3a80f581aba45fd924fcdee01fa06e635812"}, - {file = "Pillow-8.2.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6c32cc3145928c4305d142ebec682419a6c0a8ce9e33db900027ddca1ec39178"}, - {file = "Pillow-8.2.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:624b977355cde8b065f6d51b98497d6cd5fbdd4f36405f7a8790e3376125e2bb"}, - {file = "Pillow-8.2.0-cp38-cp38-win32.whl", hash = "sha256:5cbf3e3b1014dddc45496e8cf38b9f099c95a326275885199f427825c6522232"}, - {file = "Pillow-8.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:463822e2f0d81459e113372a168f2ff59723e78528f91f0bd25680ac185cf797"}, - {file = "Pillow-8.2.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:95d5ef984eff897850f3a83883363da64aae1000e79cb3c321915468e8c6add5"}, - {file = "Pillow-8.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b91c36492a4bbb1ee855b7d16fe51379e5f96b85692dc8210831fbb24c43e484"}, - {file = "Pillow-8.2.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d68cb92c408261f806b15923834203f024110a2e2872ecb0bd2a110f89d3c602"}, - {file = "Pillow-8.2.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f217c3954ce5fd88303fc0c317af55d5e0204106d86dea17eb8205700d47dec2"}, - {file = "Pillow-8.2.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:5b70110acb39f3aff6b74cf09bb4169b167e2660dabc304c1e25b6555fa781ef"}, - {file = "Pillow-8.2.0-cp39-cp39-win32.whl", hash = "sha256:a7d5e9fad90eff8f6f6106d3b98b553a88b6f976e51fce287192a5d2d5363713"}, - {file = "Pillow-8.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:238c197fc275b475e87c1453b05b467d2d02c2915fdfdd4af126145ff2e4610c"}, - {file = "Pillow-8.2.0-pp36-pypy36_pp73-macosx_10_10_x86_64.whl", hash = "sha256:0e04d61f0064b545b989126197930807c86bcbd4534d39168f4aa5fda39bb8f9"}, - {file = "Pillow-8.2.0-pp36-pypy36_pp73-manylinux2010_i686.whl", hash = "sha256:63728564c1410d99e6d1ae8e3b810fe012bc440952168af0a2877e8ff5ab96b9"}, - {file = "Pillow-8.2.0-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:c03c07ed32c5324939b19e36ae5f75c660c81461e312a41aea30acdd46f93a7c"}, - {file = "Pillow-8.2.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:4d98abdd6b1e3bf1a1cbb14c3895226816e666749ac040c4e2554231068c639b"}, - {file = "Pillow-8.2.0-pp37-pypy37_pp73-manylinux2010_i686.whl", hash = "sha256:aac00e4bc94d1b7813fe882c28990c1bc2f9d0e1aa765a5f2b516e8a6a16a9e4"}, - {file = "Pillow-8.2.0-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:22fd0f42ad15dfdde6c581347eaa4adb9a6fc4b865f90b23378aa7914895e120"}, - {file = "Pillow-8.2.0-pp37-pypy37_pp73-win32.whl", hash = "sha256:e98eca29a05913e82177b3ba3d198b1728e164869c613d76d0de4bde6768a50e"}, - {file = "Pillow-8.2.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:8b56553c0345ad6dcb2e9b433ae47d67f95fc23fe28a0bde15a120f25257e291"}, - {file = "Pillow-8.2.0.tar.gz", hash = "sha256:a787ab10d7bb5494e5f76536ac460741788f1fbce851068d73a87ca7c35fc3e1"}, + {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"}, +] +platformdirs = [ + {file = "platformdirs-2.3.0-py3-none-any.whl", hash = "sha256:8003ac87717ae2c7ee1ea5a84a1a61e87f3fbd16eb5aadba194ea30a9019f648"}, + {file = "platformdirs-2.3.0.tar.gz", hash = "sha256:15b056538719b1c94bdaccb29e5f81879c7f7f0f4a153f46086d155dffcd4f0f"}, ] 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"}, ] prefixed = [ {file = "prefixed-0.3.2-py2.py3-none-any.whl", hash = "sha256:5e107306462d63f2f03c529dbf11b0026fdfec621a9a008ca639d71de22995c3"}, @@ -2006,9 +2090,13 @@ 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"}, ] @@ -2071,78 +2159,112 @@ 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.10.2-py3-none-any.whl", hash = "sha256:e178e96b6ba171f8ef51fbce9ca30931e6acbea4a155074d80cc081596c9e852"}, + {file = "pylint-2.10.2.tar.gz", hash = "sha256:6758cce3ddbab60c52b57dcc07f0c5d779e5daf0cf50f6faacbef1d3ea62d2a1"}, ] 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.0-cp27-cp27m-macosx_10_14_intel.whl", hash = "sha256:072ba7cb65c8aa4d5c5659bf6722ee85781c9d7816dc00679b8b6f3dff1ddafc"}, + {file = "pymongo-3.12.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:d6e11ffd43184d529d6752d6dcb62b994f903038a17ea2168ef1910c96324d26"}, + {file = "pymongo-3.12.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:7412a36798966624dc4c57d64aa43c2d1100b348abd98daaac8e99e57d87e1d7"}, + {file = "pymongo-3.12.0-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e8a82e35d52ad6f867e88096a1a2b9bdc7ec4d5e65c7b4976a248bf2d1a32a93"}, + {file = "pymongo-3.12.0-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:dcd3d0009fbb6e454d729f8b22d0063bd9171c31a55e0f0271119bd4f2700023"}, + {file = "pymongo-3.12.0-cp27-cp27m-win32.whl", hash = "sha256:1bc6fe7279ff40c6818db002bf5284aa03ec181ea1b1ceaeee33c289d412afa7"}, + {file = "pymongo-3.12.0-cp27-cp27m-win_amd64.whl", hash = "sha256:e2b7670c0c8c6b501464150dd49dd0d6be6cb7f049e064124911cec5514fa19e"}, + {file = "pymongo-3.12.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:316c1b8723afa9870567cd6dff35d440b2afeda53aa13da6c5ab85f98ed6f5ca"}, + {file = "pymongo-3.12.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:255a35bf29185f44b412e31a927d9dcedda7c2c380127ecc4fbf2f61b72fa978"}, + {file = "pymongo-3.12.0-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ffbae429ba9e42d0582d3ac63fdb410338892468a2107d8ff68228ec9a39a0ed"}, + {file = "pymongo-3.12.0-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c188db6cf9e14dbbb42f5254292be96f05374a35e7dfa087cc2140f0ff4f10f6"}, + {file = "pymongo-3.12.0-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:6fb3f85870ae26896bb44e67db94045f2ebf00c5d41e6b66cdcbb5afd644fc18"}, + {file = "pymongo-3.12.0-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:aaa038eafb7186a4abbb311fcf20724be9363645882bbce540bef4797e812a7a"}, + {file = "pymongo-3.12.0-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:7d98ce3c42921bb91566121b658e0d9d59a9082a9bd6f473190607ff25ab637f"}, + {file = "pymongo-3.12.0-cp34-cp34m-win32.whl", hash = "sha256:b0a0cf39f589e52d801fdef418305562bc030cdf8929217463c8433c65fd5c2f"}, + {file = "pymongo-3.12.0-cp34-cp34m-win_amd64.whl", hash = "sha256:ceae3ab9e11a27aaab42878f1d203600dfd24f0e43678b47298219a0f10c0d30"}, + {file = "pymongo-3.12.0-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:5e574664f1468872cd40f74e4811e22b1aa4de9399d6bcfdf1ee6ea94c017fcf"}, + {file = "pymongo-3.12.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73b400fdc22de84bae0dbf1a22613928a41612ec0a3d6ed47caf7ad4d3d0f2ff"}, + {file = "pymongo-3.12.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:cbf8672edeb7b7128c4a939274801f0e32bbf5159987815e3d1eace625264a46"}, + {file = "pymongo-3.12.0-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:a634a4730ce0b0934ed75e45beba730968e12b4dafbb22f69b3b2f616d9e644e"}, + {file = "pymongo-3.12.0-cp35-cp35m-manylinux2014_i686.whl", hash = "sha256:c55782a55f4a013a78ac5b6ee4b8731a192dea7ab09f1b6b3044c96d5128edd4"}, + {file = "pymongo-3.12.0-cp35-cp35m-manylinux2014_ppc64le.whl", hash = "sha256:11f9e0cfc84ade088a38df2708d0b958bb76360181df1b2e1e1a41beaa57952b"}, + {file = "pymongo-3.12.0-cp35-cp35m-manylinux2014_s390x.whl", hash = "sha256:186104a94d39b8412f8e3de385acd990a628346a4402d4f3a288a82b8660bd22"}, + {file = "pymongo-3.12.0-cp35-cp35m-manylinux2014_x86_64.whl", hash = "sha256:70761fd3c576b027eec882b43ee0a8e5b22ff9c20cdf4d0400e104bc29e53e34"}, + {file = "pymongo-3.12.0-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:333bfad77aa9cd11711febfb75eed0bb537a1d022e1c252714dad38993590240"}, + {file = "pymongo-3.12.0-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fa8957e9a1b202cb45e6b839c241cd986c897be1e722b81d2f32e9c6aeee80b0"}, + {file = "pymongo-3.12.0-cp35-cp35m-win32.whl", hash = "sha256:4ba0def4abef058c0e5101e05e3d5266e6fffb9795bbf8be0fe912a7361a0209"}, + {file = "pymongo-3.12.0-cp35-cp35m-win_amd64.whl", hash = "sha256:a0e5dff6701fa615f165306e642709e1c1550d5b237c5a7a6ea299886828bd50"}, + {file = "pymongo-3.12.0-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:b542d56ed1b8d5cf3bb36326f814bd2fbe8812dfd2582b80a15689ea433c0e35"}, + {file = "pymongo-3.12.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a325600c83e61e3c9cebc0c2b1c8c4140fa887f789085075e8f44c8ff2547eb9"}, + {file = "pymongo-3.12.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:48d5bc80ab0af6b60c4163c5617f5cd23f2f880d7600940870ea5055816af024"}, + {file = "pymongo-3.12.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c5cab230e7cabdae9ff23c12271231283efefb944c1b79bed79a91beb65ba547"}, + {file = "pymongo-3.12.0-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:d73e10772152605f6648ba4410318594f1043bbfe36d2fadee7c4b8912eff7c5"}, + {file = "pymongo-3.12.0-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:b1c4874331ab960429caca81acb9d2932170d66d6d6f87e65dc4507a85aca152"}, + {file = "pymongo-3.12.0-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:a3566acfbcde46911c52810374ecc0354fdb841284a3efef6ff7105bc007e9a8"}, + {file = "pymongo-3.12.0-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:b3b5b3cbc3fdf4fcfa292529df2a85b5d9c7053913a739d3069af1e12e12219f"}, + {file = "pymongo-3.12.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd3854148005c808c485c754a184c71116372263709958b42aefbef2e5dd373a"}, + {file = "pymongo-3.12.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f55c1ddcc1f6050b07d468ce594f55dbf6107b459e16f735d26818d7be1e9538"}, + {file = "pymongo-3.12.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ced944dcdd561476deef7cb7bfd4987c69fffbfeff6d02ca4d5d4fd592d559b7"}, + {file = "pymongo-3.12.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78ecb8d42f50d393af912bfb1fb1dcc9aabe9967973efb49ee577e8f1cea494c"}, + {file = "pymongo-3.12.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1970cfe2aec1bf74b40cf30c130ad10cd968941694630386db33e1d044c22a2e"}, + {file = "pymongo-3.12.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b8bf42d3b32f586f4c9e37541769993783a534ad35531ce8a4379f6fa664fba9"}, + {file = "pymongo-3.12.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:bc9ac81e73573516070d24ce15da91281922811f385645df32bd3c8a45ab4684"}, + {file = "pymongo-3.12.0-cp36-cp36m-win32.whl", hash = "sha256:d04ca462cb99077e6c059e97c072957caf2918e6e4191e3161c01c439e0193de"}, + {file = "pymongo-3.12.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f2acf9bbcd514e901f82c4ca6926bbd2ae61716728f110b4343eb0a69612d018"}, + {file = "pymongo-3.12.0-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:b754240daafecd9d5fce426b0fbaaed03f4ebb130745c8a4ae9231fffb8d75e5"}, + {file = "pymongo-3.12.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:af586e85144023686fb0af09c8cdf672484ea182f352e7ceead3d832de381e1b"}, + {file = "pymongo-3.12.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:fe5872ce6f9627deac8314bdffd3862624227c3de4c17ef0cc78bbf0402999eb"}, + {file = "pymongo-3.12.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:f6977a520bd96e097c8a37a8cbb9faa1ea99d21bf84190195056e25f688af73d"}, + {file = "pymongo-3.12.0-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:2dbfbbded947a83a3dffc2bd1ec4750c17e40904692186e2c55a3ad314ca0222"}, + {file = "pymongo-3.12.0-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:a752ecd1a26000a6d67be7c9a2e93801994a8b3f866ac95b672fbc00225ca91a"}, + {file = "pymongo-3.12.0-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:1bab889ae7640eba739f67fcbf8eff252dddc60d4495e6ddd3a87cd9a95fdb52"}, + {file = "pymongo-3.12.0-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:f94c7d22fb36b184734dded7345a04ec5f95130421c775b8b0c65044ef073f34"}, + {file = "pymongo-3.12.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec5ca7c0007ce268048bbe0ffc6846ed1616cf3d8628b136e81d5e64ff3f52a2"}, + {file = "pymongo-3.12.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7c72d08acdf573455b2b9d2b75b8237654841d63a48bc2327dc102c6ee89b75a"}, + {file = "pymongo-3.12.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b6ea08758b6673610b3c5bdf47189286cf9c58b1077558706a2f6f8744922527"}, + {file = "pymongo-3.12.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46d5ec90276f71af3a29917b30f2aec2315a2759b5f8d45b3b63a07ca8a070a3"}, + {file = "pymongo-3.12.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:625befa3bc9b40746a749115cc6a15bf20b9bd7597ca55d646205b479a2c99c7"}, + {file = "pymongo-3.12.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d1131562ddc2ea8a446f66c2648d7dabec2b3816fc818528eb978a75a6d23b2e"}, + {file = "pymongo-3.12.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:eee42a1cc06565f6b21caa1f504ec15e07de7ebfd520ab57f8cb3308bc118e22"}, + {file = "pymongo-3.12.0-cp37-cp37m-win32.whl", hash = "sha256:94d38eba4d1b5eb3e6bfece0651b855a35c44f32fd91f512ab4ba41b8c0d3e66"}, + {file = "pymongo-3.12.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e018a4921657c2d3f89c720b7b90b9182e277178a04a7e9542cc79d7d787ca51"}, + {file = "pymongo-3.12.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7c6a9948916a7bbcc6d3a9f6fb75db1acb5546078023bfb3db6efabcd5a67527"}, + {file = "pymongo-3.12.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e9faf8d4712d5ea301d74abfcf6dafe4b7f4af7936e91f283b0ad7bf69ed3e3a"}, + {file = "pymongo-3.12.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:cc2894fe91f31a513860238ede69fe47fada21f9e7ddfe73f7f9fef93a971e41"}, + {file = "pymongo-3.12.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:053b4ebf91c7395d1fcd2ce6a9edff0024575b7b2de6781554a4114448a8adc9"}, + {file = "pymongo-3.12.0-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:39dafa2eaf577d1969f289dc9a44501859a1897eb45bd589e93ce843fc610800"}, + {file = "pymongo-3.12.0-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:246ec420e4c8744fceb4e259f906211b9c198e1f345e6158dcd7cbad3737e11e"}, + {file = "pymongo-3.12.0-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:208debdcf76ed39ebf24f38509f50dc1c100e31e8653817fedb8e1f867850a13"}, + {file = "pymongo-3.12.0-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:18290649759f9db660972442aa606f845c368db9b08c4c73770f6da14113569b"}, + {file = "pymongo-3.12.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:657ad80de8ec9ed656f28844efc801a0802961e8c6a85038d97ff6f555ef4919"}, + {file = "pymongo-3.12.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b772bab31cbd9cb911e41e1a611ebc9497f9a32a7348e2747c38210f75c00f41"}, + {file = "pymongo-3.12.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2399a85b54f68008e483b2871f4a458b4c980469c7fe921595ede073e4844f1e"}, + {file = "pymongo-3.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e66780f14c2efaf989cd3ac613b03ee6a8e3a0ba7b96c0bb14adca71a427e55"}, + {file = "pymongo-3.12.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02dc0b0f48ed3cd06c13b7e31b066bf91e00dac5f8147b0a0a45f9009bfab857"}, + {file = "pymongo-3.12.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:070a4ef689c9438a999ec3830e69b208ff0d12251846e064d947f97d819d1d05"}, + {file = "pymongo-3.12.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:db93608a246da44d728842b8fa9e45aa9782db76955f634a707739a8d53ff544"}, + {file = "pymongo-3.12.0-cp38-cp38-win32.whl", hash = "sha256:5af390fa9faf56c93252dab09ea57cd020c9123aa921b63a0ed51832fdb492e7"}, + {file = "pymongo-3.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:a2239556ff7241584ce57be1facf25081669bb457a9e5cbe68cce4aae6567aa1"}, + {file = "pymongo-3.12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cda9e628b1315beec8341e8c04aac9a0b910650b05e0751e42e399d5694aeacb"}, + {file = "pymongo-3.12.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:845a8b83798b2fb11b09928413cb32692866bfbc28830a433d9fa4c8c3720dd0"}, + {file = "pymongo-3.12.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:da8288bc4a7807c6715416deed1c57d94d5e03e93537889e002bf985be503f1a"}, + {file = "pymongo-3.12.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:a9ba2a63777027b06b116e1ea8248e66fd1bedc2c644f93124b81a91ddbf6d88"}, + {file = "pymongo-3.12.0-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:9a13661681d17e43009bb3e85e837aa1ec5feeea1e3654682a01b8821940f8b3"}, + {file = "pymongo-3.12.0-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:6b89dc51206e4971c5568c797991eaaef5dc2a6118d67165858ad11752dba055"}, + {file = "pymongo-3.12.0-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:701e08457183da70ed96b35a6b43e6ba1df0b47c837b063cde39a1fbe1aeda81"}, + {file = "pymongo-3.12.0-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:e7a33322e08021c37e89cae8ff06327503e8a1719e97c69f32c31cbf6c30d72c"}, + {file = "pymongo-3.12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd1f49f949a658c4e8f81ed73f9aad25fcc7d4f62f767f591e749e30038c4e1d"}, + {file = "pymongo-3.12.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a6d055f01b83b1a4df8bb0c61983d3bdffa913764488910af3620e5c2450bf83"}, + {file = "pymongo-3.12.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd6ff2192f34bd622883c745a56f492b1c9ccd44e14953e8051c33024a2947d5"}, + {file = "pymongo-3.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19d4bd0fc29aa405bb1781456c9cfff9fceabb68543741eb17234952dbc2bbb0"}, + {file = "pymongo-3.12.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24f8aeec4d6b894a6128844e50ff423dd02462ee83addf503c598ee3a80ddf3d"}, + {file = "pymongo-3.12.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b6055e0ef451ff73c93d0348d122a0750dddf323b9361de5835dac2f6cf7fc1"}, + {file = "pymongo-3.12.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6261bee7c5abadeac7497f8f1c43e521da78dd13b0a2439f526a7b0fc3788824"}, + {file = "pymongo-3.12.0-cp39-cp39-win32.whl", hash = "sha256:2e92aa32300a0b5e4175caec7769f482b292769807024a86d674b3f19b8e3755"}, + {file = "pymongo-3.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:3ce83f17f641a62a4dfb0ba1b8a3c1ced7c842f511b5450d90c030c7828e3693"}, + {file = "pymongo-3.12.0-py2.7-macosx-10.14-intel.egg", hash = "sha256:d1740776b70367277323fafb76bcf09753a5cc9824f5d705bac22a34ff3668ea"}, + {file = "pymongo-3.12.0.tar.gz", hash = "sha256:b88d1742159bc93a078733f9789f563cef26f5e370eba810476a71aa98e5fbc2"}, ] pynput = [ {file = "pynput-1.7.3-py2.py3-none-any.whl", hash = "sha256:fea5777454f896bd79d35393088cd29a089f3b2da166f0848a922b1d5a807d4f"}, @@ -2210,27 +2332,47 @@ pyqt5-sip = [ {file = "PyQt5_sip-12.9.0.tar.gz", hash = "sha256:d3e4489d7c2b0ece9d203ae66e573939f7f60d4d29e089c9f11daa17cfeaae32"}, ] 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"}, ] 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"}, ] 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"}, @@ -2256,16 +2398,16 @@ 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"}, {file = "recommonmark-0.7.1.tar.gz", hash = "sha256:bdb4db649f2222dcd8d2d844f0006b958d627f732415d399791ee436a3686d67"}, ] requests = [ - {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, - {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, + {file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"}, + {file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"}, ] rsa = [ {file = "rsa-4.7.2-py3-none-any.whl", hash = "sha256:78f9a9bf4e7be0c5ded4583326e7461e3a3c5aae24073648b4bdfa797d78c9d2"}, @@ -2284,8 +2426,8 @@ 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.10.1-py2.py3-none-any.whl", hash = "sha256:f17b71a578e94204d9033bffded634475f4ca0a6274c6c7a4fd8a9cb0ac7cd8b"}, + {file = "slack_sdk-3.10.1.tar.gz", hash = "sha256:2b4dde7728eb4ff5a581025d204578ccff25a5d8f0fe11ae175e3ce6e074434f"}, ] smmap = [ {file = "smmap-4.0.0-py2.py3-none-any.whl", hash = "sha256:a9a7479e4c572e2e775c404dcd3080c8dc49f39918c2cf74913d30c4c478e3c2"}, @@ -2300,8 +2442,8 @@ 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-4.1.2-py3-none-any.whl", hash = "sha256:46d52c6cee13fec44744b8c01ed692c18a640f6910a725cbb938bc36e8d64544"}, + {file = "Sphinx-4.1.2.tar.gz", hash = "sha256:3092d929cd807926d846018f2ace47ba2f3b671b309c7a89cd3306e80c826b13"}, ] sphinx-qt-documentation = [ {file = "sphinx_qt_documentation-0.3-py3-none-any.whl", hash = "sha256:bee247cb9e4fc03fc496d07adfdb943100e1103320c3e5e820e0cfa7c790d9b6"}, @@ -2379,17 +2521,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.6-py2.py3-none-any.whl", hash = "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4"}, + {file = "urllib3-1.26.6.tar.gz", hash = "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"}, ] wcwidth = [ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, @@ -2446,6 +2588,6 @@ yarl = [ {file = "yarl-1.6.3.tar.gz", hash = "sha256:8a9066529240171b68893d60dca86a763eae2139dd42f42106b03cf4b426bf10"}, ] 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.5.0-py3-none-any.whl", hash = "sha256:957cfda87797e389580cb8b9e3870841ca991e2125350677b2ca83a0e99390a3"}, + {file = "zipp-3.5.0.tar.gz", hash = "sha256:f5812b1e007e48cff63449a5e9f4e7ebea716b4111f9c4f9a645f91d579bf0c4"}, ] diff --git a/pyproject.toml b/pyproject.toml index a57ae19224..24e51a17bf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -68,7 +68,7 @@ slack-sdk = "^3.6.0" flake8 = "^3.7" autopep8 = "^1.4" coverage = "*" -cx_freeze = "*" +cx_freeze = { version = "6.7", source = "openpype" } GitPython = "^3.1.17" jedi = "^0.13" Jinja2 = "^2.11" diff --git a/tools/docker_build.sh b/tools/docker_build.sh index c27041a1af..d2dbef2e48 100755 --- a/tools/docker_build.sh +++ b/tools/docker_build.sh @@ -27,17 +27,15 @@ create_container () { fi local id=$(<"$openpype_root/build/docker-image.id") echo -e "${BIYellow}---${RST} Creating container from $id ..." - local cid="$(docker create $id bash)" + cid="$(docker create $id bash)" if [ $? -ne 0 ] ; then echo -e "${BIRed}!!!${RST} Cannot create container." exit 1 fi - return $cid } retrieve_build_log () { create_container - local cid=$? echo -e "${BIYellow}***${RST} Copying build log to ${BIWhite}$openpype_root/build/build.log${RST}" docker cp "$cid:/opt/openpype/build/build.log" "$openpype_root/build" } @@ -65,7 +63,6 @@ main () { echo -e "${BIGreen}>>>${RST} Copying build from container ..." create_container - local cid=$? echo -e "${BIYellow}---${RST} Copying ..." docker cp "$cid:/opt/openpype/build/exe.linux-x86_64-3.7" "$openpype_root/build" docker cp "$cid:/opt/openpype/build/build.log" "$openpype_root/build" @@ -79,7 +76,7 @@ main () { chown -R $username ./build echo -e "${BIGreen}>>>${RST} All done, you can delete container:" - echo -e "${BIYellow}$id${RST}" + echo -e "${BIYellow}$cid${RST}" } return_code=0 From 6cced73ac9e07b36112311b2aa03653089571354 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 9 Sep 2021 16:36:50 +0200 Subject: [PATCH 028/279] change to debian, add platform selection --- Dockerfile | 84 +++++++++++++++++------------------------ Dockerfile.centos7 | 87 +++++++++++++++++++++++++++++++++++++++++++ README.md | 11 ++++++ tools/docker_build.sh | 17 ++++++++- 4 files changed, 148 insertions(+), 51 deletions(-) create mode 100644 Dockerfile.centos7 diff --git a/Dockerfile b/Dockerfile index 78611860ea..cef83b5811 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,9 @@ # Build Pype docker image -FROM centos:7 AS builder -ARG OPENPYPE_PYTHON_VERSION=3.7.10 +FROM debian:bookworm-slim AS builder +ARG OPENPYPE_PYTHON_VERSION=3.7.12 +LABEL maintainer="info@openpype.io" +LABEL description="Docker Image to build and run OpenPype" LABEL org.opencontainers.image.name="pypeclub/openpype" LABEL org.opencontainers.image.title="OpenPype Docker Image" LABEL org.opencontainers.image.url="https://openpype.io/" @@ -9,57 +11,49 @@ LABEL org.opencontainers.image.source="https://github.com/pypeclub/pype" USER root -# update base -RUN yum -y install deltarpm \ - && yum -y update \ - && yum clean all +ARG DEBIAN_FRONTEND=noninteractive -# add tools we need -RUN yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm \ - && yum -y install centos-release-scl \ - && yum -y install \ +# update base +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + ca-certificates \ bash \ - which \ git \ - devtoolset-7-gcc* \ - make \ cmake \ + make \ curl \ wget \ - gcc \ - zlib-devel \ - bzip2 \ - bzip2-devel \ - readline-devel \ - sqlite sqlite-devel \ - openssl-devel \ - openssl-libs \ - tk-devel libffi-devel \ - qt5-qtbase-devel \ - patchelf \ - && yum clean all + build-essential \ + checkinstall \ + libssl-dev \ + zlib1g-dev \ + libbz2-dev \ + libreadline-dev \ + libsqlite3-dev \ + llvm \ + libncursesw5-dev \ + xz-utils \ + tk-dev \ + libxml2-dev \ + libxmlsec1-dev \ + libffi-dev \ + liblzma-dev \ + patchelf + +SHELL ["/bin/bash", "-c"] RUN mkdir /opt/openpype -# RUN useradd -m pype -# RUN chown pype /opt/openpype -# USER pype -RUN curl https://pyenv.run | bash -ENV PYTHON_CONFIGURE_OPTS --enable-shared - -RUN echo 'export PATH="$HOME/.pyenv/bin:$PATH"'>> $HOME/.bashrc \ +RUN curl https://pyenv.run | bash \ + && echo 'export PATH="$HOME/.pyenv/bin:$PATH"'>> $HOME/.bashrc \ && echo 'eval "$(pyenv init -)"' >> $HOME/.bashrc \ && echo 'eval "$(pyenv virtualenv-init -)"' >> $HOME/.bashrc \ - && echo 'eval "$(pyenv init --path)"' >> $HOME/.bashrc -RUN source $HOME/.bashrc && pyenv install ${OPENPYPE_PYTHON_VERSION} + && echo 'eval "$(pyenv init --path)"' >> $HOME/.bashrc \ + && source $HOME/.bashrc && pyenv install ${OPENPYPE_PYTHON_VERSION} COPY . /opt/openpype/ -RUN rm -rf /openpype/.poetry || echo "No Poetry installed yet." -# USER root -# RUN chown -R pype /opt/openpype -RUN chmod +x /opt/openpype/tools/create_env.sh && chmod +x /opt/openpype/tools/build.sh -# USER pype +RUN chmod +x /opt/openpype/tools/create_env.sh && chmod +x /opt/openpype/tools/build.sh WORKDIR /opt/openpype @@ -68,18 +62,8 @@ RUN cd /opt/openpype \ && pyenv local ${OPENPYPE_PYTHON_VERSION} RUN source $HOME/.bashrc \ - && ./tools/create_env.sh - -RUN source $HOME/.bashrc \ + && ./tools/create_env.sh \ && ./tools/fetch_thirdparty_libs.sh RUN source $HOME/.bashrc \ && bash ./tools/build.sh - -RUN cp /usr/lib64/libffi* ./build/exe.linux-x86_64-3.7/lib \ - && cp /usr/lib64/libssl* ./build/exe.linux-x86_64-3.7/lib \ - && cp /usr/lib64/libcrypto* ./build/exe.linux-x86_64-3.7/lib \ - && cp /root/.pyenv/versions/${OPENPYPE_PYTHON_VERSION}/lib/libpython* ./build/exe.linux-x86_64-3.7/lib - -RUN cd /opt/openpype \ - rm -rf ./vendor/bin diff --git a/Dockerfile.centos7 b/Dockerfile.centos7 new file mode 100644 index 0000000000..0e2fdd4ba0 --- /dev/null +++ b/Dockerfile.centos7 @@ -0,0 +1,87 @@ +# Build Pype docker image +FROM centos:7 AS builder +ARG OPENPYPE_PYTHON_VERSION=3.7.10 + +LABEL org.opencontainers.image.name="pypeclub/openpype" +LABEL org.opencontainers.image.title="OpenPype Docker Image" +LABEL org.opencontainers.image.url="https://openpype.io/" +LABEL org.opencontainers.image.source="https://github.com/pypeclub/pype" + +USER root + +# update base +RUN yum -y install deltarpm \ + && yum -y update \ + && yum clean all + +# add tools we need +RUN yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm \ + && yum -y install centos-release-scl \ + && yum -y install \ + bash \ + which \ + git \ + devtoolset-7-gcc* \ + make \ + cmake \ + curl \ + wget \ + gcc \ + zlib-devel \ + bzip2 \ + bzip2-devel \ + readline-devel \ + sqlite sqlite-devel \ + openssl-devel \ + openssl-libs \ + tk-devel libffi-devel \ + qt5-qtbase-devel \ + patchelf \ + ncurses \ + ncurses-devel \ + && yum clean all + +RUN mkdir /opt/openpype +# RUN useradd -m pype +# RUN chown pype /opt/openpype +# USER pype + +RUN curl https://pyenv.run | bash +# ENV PYTHON_CONFIGURE_OPTS --enable-shared + +RUN echo 'export PATH="$HOME/.pyenv/bin:$PATH"'>> $HOME/.bashrc \ + && echo 'eval "$(pyenv init -)"' >> $HOME/.bashrc \ + && echo 'eval "$(pyenv virtualenv-init -)"' >> $HOME/.bashrc \ + && echo 'eval "$(pyenv init --path)"' >> $HOME/.bashrc +RUN source $HOME/.bashrc && pyenv install ${OPENPYPE_PYTHON_VERSION} + +COPY . /opt/openpype/ +RUN rm -rf /openpype/.poetry || echo "No Poetry installed yet." +# USER root +# RUN chown -R pype /opt/openpype +RUN chmod +x /opt/openpype/tools/create_env.sh && chmod +x /opt/openpype/tools/build.sh + +# USER pype + +WORKDIR /opt/openpype + +RUN cd /opt/openpype \ + && source $HOME/.bashrc \ + && pyenv local ${OPENPYPE_PYTHON_VERSION} + +RUN source $HOME/.bashrc \ + && ./tools/create_env.sh + +RUN source $HOME/.bashrc \ + && ./tools/fetch_thirdparty_libs.sh + +RUN source $HOME/.bashrc \ + && bash ./tools/build.sh + +RUN cp /usr/lib64/libffi* ./build/exe.linux-x86_64-3.7/lib \ + && cp /usr/lib64/libssl* ./build/exe.linux-x86_64-3.7/lib \ + && cp /usr/lib64/libcrypto* ./build/exe.linux-x86_64-3.7/lib \ + && cp /root/.pyenv/versions/${OPENPYPE_PYTHON_VERSION}/lib/libpython* ./build/exe.linux-x86_64-3.7/lib + +RUN cd /opt/openpype \ + rm -rf ./vendor/bin diff --git a/README.md b/README.md index 209af24c75..0e450fc48d 100644 --- a/README.md +++ b/README.md @@ -133,6 +133,12 @@ Easiest way to build OpenPype on Linux is using [Docker](https://www.docker.com/ sudo ./tools/docker_build.sh ``` +This will by default use Debian as base image. If you need to make Centos 7 compatible build, please run: + +```sh +sudo ./tools/docker_build.sh centos7 +``` + If all is successful, you'll find built OpenPype in `./build/` folder. #### Manual build @@ -158,6 +164,11 @@ you'll need also additional libraries for Qt5: ```sh sudo apt install qt5-default ``` +or if you are on Ubuntu > 20.04, there is no `qt5-default` packages so you need to install its content individually: + +```sh +sudo apt-get install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools +```
diff --git a/tools/docker_build.sh b/tools/docker_build.sh index d2dbef2e48..04c26424eb 100755 --- a/tools/docker_build.sh +++ b/tools/docker_build.sh @@ -40,6 +40,21 @@ retrieve_build_log () { docker cp "$cid:/opt/openpype/build/build.log" "$openpype_root/build" } +openpype_root=$(realpath $(dirname $(dirname "${BASH_SOURCE[0]}"))) + + +if [ -z $1 ]; then + dockerfile="Dockerfile" +else + dockerfile="Dockerfile.$1" + if [ ! -f "$openpype_root/$dockerfile" ]; then + echo -e "${BIRed}!!!${RST} Dockerfile for specifed platform ${BIWhite}$1${RST} doesn't exist." + exit 1 + else + echo -e "${BIGreen}>>>${RST} Using Dockerfile for ${BIWhite}$1${RST} ..." + fi +fi + # Main main () { openpype_root=$(realpath $(dirname $(dirname "${BASH_SOURCE[0]}"))) @@ -53,7 +68,7 @@ main () { echo -e "${BIGreen}>>>${RST} Running docker build ..." # docker build --pull --no-cache -t pypeclub/openpype:$openpype_version . - docker build --pull --iidfile $openpype_root/build/docker-image.id -t pypeclub/openpype:$openpype_version . + docker build --pull --iidfile $openpype_root/build/docker-image.id -t pypeclub/openpype:$openpype_version -f $dockerfile . if [ $? -ne 0 ] ; then echo $? echo -e "${BIRed}!!!${RST} Docker build failed." From d90a866b5bbcc55878068f20777c9db2ad6e68b1 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 9 Sep 2021 17:11:40 +0200 Subject: [PATCH 029/279] =?UTF-8?q?add=20changes=20to=20docs=20?= =?UTF-8?q?=F0=9F=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- website/docs/dev_build.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/website/docs/dev_build.md b/website/docs/dev_build.md index b3e0c24fc2..f71118eba6 100644 --- a/website/docs/dev_build.md +++ b/website/docs/dev_build.md @@ -84,6 +84,13 @@ You can use Docker to build OpenPype. Just run: ```shell $ sudo ./tools/docker_build.sh ``` + +This will by default use Debian as base image. If you need to make Centos 7 compatible build, please run: + +```sh +sudo ./tools/docker_build.sh centos7 +``` + and you should have built OpenPype in `build` directory. It is using **Centos 7** as a base image. @@ -323,14 +330,18 @@ Same as: poetry run python ./tools/create_zip.py ``` -### docker_build.sh +### docker_build.sh *[variant]* Script to build OpenPype on [Docker](https://www.docker.com/) enabled systems - usually Linux and Windows with [Docker Desktop](https://docs.docker.com/docker-for-windows/install/) and [Windows Subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/about) (WSL) installed. It must be run with administrative privileges - `sudo ./docker_build.sh`. -It will use **Centos 7** base image to build OpenPype. You'll see your build in `./build` folder. +It will use latest **Debian** base image to build OpenPype. If you need to build OpenPype for +older systems like Centos 7, use `centos7` as argument. This will use another Dockerfile to build +OpenPype with **Centos 7** as base image. + +You'll see your build in `./build` folder. ### fetch_thirdparty_libs This script will download necessary tools for OpenPype defined in `pyproject.toml` like FFMpeg, From a290d77a41b40ac89235e48bb9f1521fc041bc6c Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Mon, 13 Sep 2021 11:18:25 +0100 Subject: [PATCH 030/279] Fixed Unreal support for templates --- .../unreal/hooks/pre_workfile_preparation.py | 50 ++++++++++++++++--- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/unreal/hooks/pre_workfile_preparation.py b/openpype/hosts/unreal/hooks/pre_workfile_preparation.py index 01b8b6bc05..0c7146634f 100644 --- a/openpype/hosts/unreal/hooks/pre_workfile_preparation.py +++ b/openpype/hosts/unreal/hooks/pre_workfile_preparation.py @@ -6,7 +6,9 @@ from pathlib import Path from openpype.lib import ( PreLaunchHook, ApplicationLaunchFailed, - ApplicationNotFound + ApplicationNotFound, + get_workdir_data, + get_workfile_template_key ) from openpype.hosts.unreal.api import lib as unreal_lib @@ -25,13 +27,45 @@ class UnrealPrelaunchHook(PreLaunchHook): self.signature = "( {} )".format(self.__class__.__name__) + def _get_work_filename(self): + # Use last workfile if was found + last_workfile = self.data.get("last_workfile_path") + if last_workfile and os.path.exists(last_workfile): + return os.path.basename(last_workfile) + + # Prepare data for fill data and for getting workfile template key + task_name = self.data["task_name"] + anatomy = self.data["anatomy"] + asset_doc = self.data["asset_doc"] + project_doc = self.data["project_doc"] + + asset_tasks = asset_doc.get("data", {}).get("tasks") or {} + task_info = asset_tasks.get(task_name) or {} + task_type = task_info.get("type") + + workdir_data = get_workdir_data( + project_doc, asset_doc, task_name, self.host_name + ) + # QUESTION raise exception if version is part of filename template? + workdir_data["version"] = 1 + workdir_data["ext"] = "uproject" + + # Get workfile template key for current context + workfile_template_key = get_workfile_template_key( + task_type, + self.host_name, + project_name=project_doc["name"] + ) + # Fill templates + filled_anatomy = anatomy.format(workdir_data) + + # Return filename + return filled_anatomy[workfile_template_key]["file"] + def execute(self): """Hook entry method.""" - asset_name = self.data["asset_name"] - task_name = self.data["task_name"] workdir = self.launch_context.env["AVALON_WORKDIR"] engine_version = self.app_name.split("/")[-1].replace("-", ".") - unreal_project_name = f"{asset_name}_{task_name}" try: if int(engine_version.split(".")[0]) < 4 and \ int(engine_version.split(".")[1]) < 26: @@ -45,6 +79,8 @@ class UnrealPrelaunchHook(PreLaunchHook): # so lets keep it quite. ... + unreal_project_filename = self._get_work_filename() + unreal_project_name = os.path.splitext(unreal_project_filename)[0] # Unreal is sensitive about project names longer then 20 chars if len(unreal_project_name) > 20: self.log.warning(( @@ -55,7 +91,7 @@ class UnrealPrelaunchHook(PreLaunchHook): # of the project name. This is because project name is then used # in various places inside c++ code and there variable names cannot # start with non-alpha. We append 'P' before project name to solve it. - # 😱 + # 😱 if not unreal_project_name[:1].isalpha(): self.log.warning(( "Project name doesn't start with alphabet " @@ -89,10 +125,10 @@ class UnrealPrelaunchHook(PreLaunchHook): ue4_path = unreal_lib.get_editor_executable_path( Path(detected[engine_version])) - self.launch_context.launch_args.append(ue4_path.as_posix()) + self.launch_context.launch_args = [ue4_path.as_posix()] project_path.mkdir(parents=True, exist_ok=True) - project_file = project_path / f"{unreal_project_name}.uproject" + project_file = project_path / unreal_project_filename if not project_file.is_file(): engine_path = detected[engine_version] self.log.info(( From aa9a945b9ca537b91aef4722a18389e8ad6f3f7f Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Wed, 15 Sep 2021 14:02:02 +0200 Subject: [PATCH 031/279] remove devtoolset-7 from centos build --- Dockerfile.centos7 | 1 - 1 file changed, 1 deletion(-) diff --git a/Dockerfile.centos7 b/Dockerfile.centos7 index 0e2fdd4ba0..e39fc2dc8c 100644 --- a/Dockerfile.centos7 +++ b/Dockerfile.centos7 @@ -21,7 +21,6 @@ RUN yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.n bash \ which \ git \ - devtoolset-7-gcc* \ make \ cmake \ curl \ From 595f441947a6bb149a349dc9212fc8bcdaa0c00c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Mon, 20 Sep 2021 18:21:44 +0200 Subject: [PATCH 032/279] remove submodules --- openpype/modules/ftrack/python2_vendor/arrow | 1 - openpype/modules/ftrack/python2_vendor/ftrack-python-api | 1 - 2 files changed, 2 deletions(-) delete mode 160000 openpype/modules/ftrack/python2_vendor/arrow delete mode 160000 openpype/modules/ftrack/python2_vendor/ftrack-python-api diff --git a/openpype/modules/ftrack/python2_vendor/arrow b/openpype/modules/ftrack/python2_vendor/arrow deleted file mode 160000 index b746fedf72..0000000000 --- a/openpype/modules/ftrack/python2_vendor/arrow +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b746fedf7286c3755a46f07ab72f4c414cd41fc0 diff --git a/openpype/modules/ftrack/python2_vendor/ftrack-python-api b/openpype/modules/ftrack/python2_vendor/ftrack-python-api deleted file mode 160000 index d277f474ab..0000000000 --- a/openpype/modules/ftrack/python2_vendor/ftrack-python-api +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d277f474ab016e7b53479c36af87cb861d0cc53e From c2a51824d589175cbcb65f5150ddb6e7d95acc9c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Sep 2021 09:47:15 +0200 Subject: [PATCH 033/279] add more required libraries to centos docker --- Dockerfile.centos7 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Dockerfile.centos7 b/Dockerfile.centos7 index 0095ddff53..8b87654775 100644 --- a/Dockerfile.centos7 +++ b/Dockerfile.centos7 @@ -35,12 +35,15 @@ RUN yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.n openssl-devel \ openssl-libs \ tk-devel libffi-devel \ - qt5-qtbase-devel \ patchelf \ automake \ autoconf \ ncurses \ ncurses-devel \ + qt5-qtbase-devel \ + libxcb libxcb-devel \ + xcb-util xcb-util-devel \ + libxkbcommon-devel libxkbcommon-x11-devel && yum clean all # we need to build our own patchelf From 3d1f4fcdd42b8f87c0ebf728dc47e6d5815dee6a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Sep 2021 10:20:31 +0200 Subject: [PATCH 034/279] fixed endline --- Dockerfile.centos7 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.centos7 b/Dockerfile.centos7 index 8b87654775..67d45ce3b2 100644 --- a/Dockerfile.centos7 +++ b/Dockerfile.centos7 @@ -43,7 +43,7 @@ RUN yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.n qt5-qtbase-devel \ libxcb libxcb-devel \ xcb-util xcb-util-devel \ - libxkbcommon-devel libxkbcommon-x11-devel + libxkbcommon-devel libxkbcommon-x11-devel \ && yum clean all # we need to build our own patchelf From 39c9df52b818938bf02f38c35277cf4dec92ab34 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Sep 2021 12:28:52 +0000 Subject: [PATCH 035/279] Bump axios from 0.21.1 to 0.21.4 in /website Bumps [axios](https://github.com/axios/axios) from 0.21.1 to 0.21.4. - [Release notes](https://github.com/axios/axios/releases) - [Changelog](https://github.com/axios/axios/blob/master/CHANGELOG.md) - [Commits](https://github.com/axios/axios/compare/v0.21.1...v0.21.4) --- updated-dependencies: - dependency-name: axios dependency-type: indirect ... Signed-off-by: dependabot[bot] --- website/yarn.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/website/yarn.lock b/website/yarn.lock index 066d156d97..ae40005384 100644 --- a/website/yarn.lock +++ b/website/yarn.lock @@ -2175,11 +2175,11 @@ autoprefixer@^10.0.2, autoprefixer@^10.2.5: postcss-value-parser "^4.1.0" axios@^0.21.1: - version "0.21.1" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8" - integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA== + version "0.21.4" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" + integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== dependencies: - follow-redirects "^1.10.0" + follow-redirects "^1.14.0" babel-loader@^8.2.2: version "8.2.2" @@ -3982,10 +3982,10 @@ flux@^4.0.1: fbemitter "^3.0.0" fbjs "^3.0.0" -follow-redirects@^1.0.0, follow-redirects@^1.10.0: - version "1.13.3" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.3.tgz#e5598ad50174c1bc4e872301e82ac2cd97f90267" - integrity sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA== +follow-redirects@^1.0.0, follow-redirects@^1.14.0: + version "1.14.4" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.4.tgz#838fdf48a8bbdd79e52ee51fb1c94e3ed98b9379" + integrity sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g== for-in@^1.0.2: version "1.0.2" From a10f2379e5aa0ef9f9768631d71bc57923fd7201 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Sep 2021 17:16:50 +0200 Subject: [PATCH 036/279] define openpype_root in build_dependencies --- tools/build_dependencies.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/build_dependencies.py b/tools/build_dependencies.py index e5a430e220..dcdae3814d 100644 --- a/tools/build_dependencies.py +++ b/tools/build_dependencies.py @@ -93,18 +93,18 @@ _print("Getting venv site-packages ...") assert site_pkg, "No venv site-packages are found." _print(f"Working with: {site_pkg}", 2) - -build_dir = "exe.{}-{}".format(get_platform(), sys.version[0:3]) +openpype_root = Path(os.path.dirname(__file__)).parent # create full path if platform.system().lower() == "darwin": - build_dir = Path(os.path.dirname(__file__)).parent.joinpath( + build_dir = openpype_root.joinpath( "build", "OpenPype.app", "Contents", "MacOS") else: - build_dir = Path(os.path.dirname(__file__)).parent / "build" / build_dir + build_subdir = "exe.{}-{}".format(get_platform(), sys.version[0:3]) + build_dir = openpype_root / "build" / build_subdir _print(f"Using build at {build_dir}", 2) if not build_dir.exists(): From cd52b1e1805649c5f242a03e661dc51bbf9aabc0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Sep 2021 17:17:30 +0200 Subject: [PATCH 037/279] added rpath modifications using patchelf --- tools/build_dependencies.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tools/build_dependencies.py b/tools/build_dependencies.py index dcdae3814d..05ae07d406 100644 --- a/tools/build_dependencies.py +++ b/tools/build_dependencies.py @@ -23,6 +23,7 @@ import sys import site from distutils.util import get_platform import platform +import subprocess from pathlib import Path import shutil import blessed @@ -145,6 +146,29 @@ if platform.system().lower() == "windows": _print("Could not find {}".format(src), 1) sys.exit(1) +# On Linux use rpath from source libraries in destination libraries +if platform.system().lower() == "linux": + src_pyside_dir = openpype_root / "vendor" / "python" / "PySide2" + dst_pyside_dir = build_dir / "vendor" / "python" / "PySide2" + src_rpath_per_so_file = {} + for filepath in src_pyside_dir.glob("*.so"): + filename = filepath.name + rpath = ( + subprocess.check_output(["patchelf", "--print-rpath", filepath]) + .decode("utf-8") + .strip() + ) + src_rpath_per_so_file[filename] = rpath + + for filepath in dst_pyside_dir.glob("*.so"): + filename = filepath.name + if filename not in src_rpath_per_so_file: + continue + src_rpath = src_rpath_per_so_file[filename] + subprocess.check_call( + ["patchelf", "--set-rpath", src_rpath, filepath] + ) + to_delete = [] # _print("Finding duplicates ...") deps_items = list(deps_dir.iterdir()) From da57732b226a4ca37d96a0c0de1165babb6ba611 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Sep 2021 17:23:12 +0200 Subject: [PATCH 038/279] removed PyQt special code --- tools/build_dependencies.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/tools/build_dependencies.py b/tools/build_dependencies.py index 05ae07d406..1798b7ca8f 100644 --- a/tools/build_dependencies.py +++ b/tools/build_dependencies.py @@ -136,15 +136,6 @@ progress_bar.close() # iterate over frozen libs and create list to delete libs_dir = build_dir / "lib" -# On Windows "python3.dll" is needed for PyQt5 from the build. -if platform.system().lower() == "windows": - src = Path(libs_dir / "PyQt5" / "python3.dll") - dst = Path(deps_dir / "PyQt5" / "python3.dll") - if src.exists(): - shutil.copyfile(src, dst) - else: - _print("Could not find {}".format(src), 1) - sys.exit(1) # On Linux use rpath from source libraries in destination libraries if platform.system().lower() == "linux": From 39eb48abe31909222ccc996fedb6fe53158557d5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Sep 2021 18:52:27 +0200 Subject: [PATCH 039/279] removed pyqt5 from poetry.lock --- poetry.lock | 60 ----------------------------------------------------- 1 file changed, 60 deletions(-) diff --git a/poetry.lock b/poetry.lock index 6dae442c9d..968fda3d7b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -975,34 +975,6 @@ category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -[[package]] -name = "pyqt5" -version = "5.15.4" -description = "Python bindings for the Qt cross platform application toolkit" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -PyQt5-Qt5 = ">=5.15" -PyQt5-sip = ">=12.8,<13" - -[[package]] -name = "pyqt5-qt5" -version = "5.15.2" -description = "The subset of a Qt installation needed by PyQt5." -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "pyqt5-sip" -version = "12.9.0" -description = "The sip module support for PyQt5" -category = "main" -optional = false -python-versions = ">=3.5" - [[package]] name = "pyrsistent" version = "0.18.0" @@ -2299,38 +2271,6 @@ pyparsing = [ {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, ] -pyqt5 = [ - {file = "PyQt5-5.15.4-cp36.cp37.cp38.cp39-abi3-macosx_10_13_intel.whl", hash = "sha256:8c0848ba790a895801d5bfd171da31cad3e551dbcc4e59677a3b622de2ceca98"}, - {file = "PyQt5-5.15.4-cp36.cp37.cp38.cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:883a549382fc22d29a0568f3ef20b38c8e7ab633a59498ac4eb63a3bf36d3fd3"}, - {file = "PyQt5-5.15.4-cp36.cp37.cp38.cp39-none-win32.whl", hash = "sha256:a88526a271e846e44779bb9ad7a738c6d3c4a9d01e15a128ecfc6dd4696393b7"}, - {file = "PyQt5-5.15.4-cp36.cp37.cp38.cp39-none-win_amd64.whl", hash = "sha256:213bebd51821ed89b4d5b35bb10dbe67564228b3568f463a351a08e8b1677025"}, - {file = "PyQt5-5.15.4.tar.gz", hash = "sha256:2a69597e0dd11caabe75fae133feca66387819fc9bc050f547e5551bce97e5be"}, -] -pyqt5-qt5 = [ - {file = "PyQt5_Qt5-5.15.2-py3-none-macosx_10_13_intel.whl", hash = "sha256:76980cd3d7ae87e3c7a33bfebfaee84448fd650bad6840471d6cae199b56e154"}, - {file = "PyQt5_Qt5-5.15.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:1988f364ec8caf87a6ee5d5a3a5210d57539988bf8e84714c7d60972692e2f4a"}, - {file = "PyQt5_Qt5-5.15.2-py3-none-win32.whl", hash = "sha256:9cc7a768b1921f4b982ebc00a318ccb38578e44e45316c7a4a850e953e1dd327"}, - {file = "PyQt5_Qt5-5.15.2-py3-none-win_amd64.whl", hash = "sha256:750b78e4dba6bdf1607febedc08738e318ea09e9b10aea9ff0d73073f11f6962"}, -] -pyqt5-sip = [ - {file = "PyQt5_sip-12.9.0-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:d85002238b5180bce4b245c13d6face848faa1a7a9e5c6e292025004f2fd619a"}, - {file = "PyQt5_sip-12.9.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:83c3220b1ca36eb8623ba2eb3766637b19eb0ce9f42336ad8253656d32750c0a"}, - {file = "PyQt5_sip-12.9.0-cp36-cp36m-win32.whl", hash = "sha256:d8b2bdff7bbf45bc975c113a03b14fd669dc0c73e1327f02706666a7dd51a197"}, - {file = "PyQt5_sip-12.9.0-cp36-cp36m-win_amd64.whl", hash = "sha256:69a3ad4259172e2b1aa9060de211efac39ddd734a517b1924d9c6c0cc4f55f96"}, - {file = "PyQt5_sip-12.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:42274a501ab4806d2c31659170db14c282b8313d2255458064666d9e70d96206"}, - {file = "PyQt5_sip-12.9.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:6a8701892a01a5a2a4720872361197cc80fdd5f49c8482d488ddf38c9c84f055"}, - {file = "PyQt5_sip-12.9.0-cp37-cp37m-win32.whl", hash = "sha256:ac57d796c78117eb39edd1d1d1aea90354651efac9d3590aac67fa4983f99f1f"}, - {file = "PyQt5_sip-12.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4347bd81d30c8e3181e553b3734f91658cfbdd8f1a19f254777f906870974e6d"}, - {file = "PyQt5_sip-12.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c446971c360a0a1030282a69375a08c78e8a61d568bfd6dab3dcc5cf8817f644"}, - {file = "PyQt5_sip-12.9.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:fc43f2d7c438517ee33e929e8ae77132749c15909afab6aeece5fcf4147ffdb5"}, - {file = "PyQt5_sip-12.9.0-cp38-cp38-win32.whl", hash = "sha256:055581c6fed44ba4302b70eeb82e979ff70400037358908f251cd85cbb3dbd93"}, - {file = "PyQt5_sip-12.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:c5216403d4d8d857ec4a61f631d3945e44fa248aa2415e9ee9369ab7c8a4d0c7"}, - {file = "PyQt5_sip-12.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a25b9843c7da6a1608f310879c38e6434331aab1dc2fe6cb65c14f1ecf33780e"}, - {file = "PyQt5_sip-12.9.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:dd05c768c2b55ffe56a9d49ce6cc77cdf3d53dbfad935258a9e347cbfd9a5850"}, - {file = "PyQt5_sip-12.9.0-cp39-cp39-win32.whl", hash = "sha256:4f8e05fe01d54275877c59018d8e82dcdd0bc5696053a8b830eecea3ce806121"}, - {file = "PyQt5_sip-12.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:b09f4cd36a4831229fb77c424d89635fa937d97765ec90685e2f257e56a2685a"}, - {file = "PyQt5_sip-12.9.0.tar.gz", hash = "sha256:d3e4489d7c2b0ece9d203ae66e573939f7f60d4d29e089c9f11daa17cfeaae32"}, -] pyrsistent = [ {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"}, From 0bbea713fb6357708798aa530a07d7ef40b0cda0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Sep 2021 18:58:14 +0200 Subject: [PATCH 040/279] added current develop lock file --- poetry.lock | 684 +++++++++++++++++++++------------------------------- 1 file changed, 271 insertions(+), 413 deletions(-) diff --git a/poetry.lock b/poetry.lock index 968fda3d7b..e43c788d74 100644 --- a/poetry.lock +++ b/poetry.lock @@ -80,7 +80,7 @@ python-dateutil = ">=2.7.0" [[package]] name = "astroid" -version = "2.7.3" +version = "2.5.6" description = "An abstract syntax tree for Python with inference support." category = "dev" optional = false @@ -89,7 +89,6 @@ 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\""} -typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} wrapt = ">=1.11,<1.13" [[package]] @@ -147,11 +146,11 @@ pytz = ">=2015.7" [[package]] name = "blessed" -version = "1.18.1" +version = "1.18.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 = ">=2.7" +python-versions = "*" [package.dependencies] jinxed = {version = ">=0.5.4", markers = "platform_system == \"Windows\""} @@ -176,7 +175,7 @@ python-versions = "*" [[package]] name = "cffi" -version = "1.14.6" +version = "1.14.5" description = "Foreign Function Interface for Python calling C code." category = "main" optional = false @@ -193,17 +192,6 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -[[package]] -name = "charset-normalizer" -version = "2.0.4" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" -optional = false -python-versions = ">=3.5.0" - -[package.extras] -unicode_backport = ["unicodedata2"] - [[package]] name = "click" version = "7.1.2" @@ -265,7 +253,7 @@ toml = ["toml"] [[package]] name = "cryptography" -version = "3.4.8" +version = "3.4.7" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." category = "main" optional = false @@ -284,20 +272,15 @@ test = ["pytest (>=6.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pret [[package]] name = "cx-freeze" -version = "6.7" +version = "6.6" description = "Create standalone executables from Python scripts" category = "dev" optional = false python-versions = ">=3.6" [package.dependencies] -cx-logging = {version = ">=3.0", markers = "sys_platform == \"win32\""} -importlib-metadata = ">=4.3.1" - -[package.source] -type = "legacy" -url = "https://distribute.openpype.io/wheels" -reference = "openpype" +cx-Logging = {version = ">=3.0", markers = "sys_platform == \"win32\""} +importlib-metadata = ">=3.1.1" [[package]] name = "cx-logging" @@ -403,19 +386,19 @@ smmap = ">=3.0.1,<5" [[package]] name = "gitpython" -version = "3.1.20" +version = "3.1.17" description = "Python Git Library" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.5" [package.dependencies] gitdb = ">=4.0.1,<5" -typing-extensions = {version = ">=3.7.4.3", markers = "python_version < \"3.10\""} +typing-extensions = {version = ">=3.7.4.0", markers = "python_version < \"3.8\""} [[package]] name = "google-api-core" -version = "1.31.2" +version = "1.30.0" description = "Google API client core library" category = "main" optional = false @@ -453,7 +436,7 @@ uritemplate = ">=3.0.0,<4dev" [[package]] name = "google-auth" -version = "1.35.0" +version = "1.31.0" description = "Google Authentication Library" category = "main" optional = false @@ -510,11 +493,11 @@ pyparsing = ">=2.4.2,<3" [[package]] name = "idna" -version = "3.2" +version = "2.10" description = "Internationalized Domain Names in Applications (IDNA)" category = "main" optional = false -python-versions = ">=3.5" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "imagesize" @@ -526,7 +509,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "importlib-metadata" -version = "4.8.1" +version = "4.5.0" description = "Read metadata from Python packages" category = "main" optional = false @@ -538,8 +521,7 @@ zipp = ">=0.5" [package.extras] docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -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)"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] [[package]] name = "iniconfig" @@ -551,17 +533,16 @@ python-versions = "*" [[package]] name = "isort" -version = "5.9.3" +version = "5.8.0" description = "A Python utility / library to sort Python imports." category = "dev" optional = false -python-versions = ">=3.6.1,<4.0" +python-versions = ">=3.6,<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" @@ -579,15 +560,14 @@ testing = ["colorama", "docopt", "pytest (>=3.1.0)"] [[package]] name = "jeepney" -version = "0.7.1" +version = "0.6.0" 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", "async-timeout"] -trio = ["trio", "async-generator"] +test = ["pytest", "pytest-trio", "pytest-asyncio", "testpath", "trio"] [[package]] name = "jinja2" @@ -715,11 +695,11 @@ reference = "openpype" [[package]] name = "packaging" -version = "21.0" +version = "20.9" description = "Core utilities for Python packages" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] pyparsing = ">=2.0.2" @@ -738,7 +718,7 @@ testing = ["docopt", "pytest (<6.0.0)"] [[package]] name = "pathlib2" -version = "2.3.6" +version = "2.3.5" description = "Object-oriented filesystem paths" category = "main" optional = false @@ -749,38 +729,25 @@ six = "*" [[package]] name = "pillow" -version = "8.3.2" +version = "8.2.0" description = "Python Imaging Library (Fork)" category = "main" optional = false python-versions = ">=3.6" -[[package]] -name = "platformdirs" -version = "2.3.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 = "1.0.0" +version = "0.13.1" description = "plugin and hook calling mechanisms for python" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} [package.extras] dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] [[package]] name = "prefixed" @@ -882,7 +849,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pygments" -version = "2.10.0" +version = "2.9.0" description = "Pygments is a syntax highlighting package written in Python." category = "dev" optional = false @@ -890,23 +857,22 @@ python-versions = ">=3.5" [[package]] name = "pylint" -version = "2.10.2" +version = "2.8.3" description = "python code static checker" category = "dev" optional = false python-versions = "~=3.6" [package.dependencies] -astroid = ">=2.7.2,<2.8" +astroid = "2.5.6" colorama = {version = "*", markers = "sys_platform == \"win32\""} isort = ">=4.2.5,<6" mccabe = ">=0.6,<0.7" -platformdirs = ">=2.2.0" toml = ">=0.7.1" [[package]] name = "pymongo" -version = "3.12.0" +version = "3.11.4" description = "Python driver for MongoDB " category = "main" optional = false @@ -914,9 +880,9 @@ python-versions = "*" [package.extras] aws = ["pymongo-auth-aws (<2.0.0)"] -encryption = ["pymongocrypt (>=1.1.0,<2.0.0)"] +encryption = ["pymongocrypt (<2.0.0)"] gssapi = ["pykerberos"] -ocsp = ["pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)", "certifi"] +ocsp = ["pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)"] snappy = ["python-snappy"] srv = ["dnspython (>=1.16.0,<1.17.0)"] tls = ["ipaddress"] @@ -977,15 +943,15 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "pyrsistent" -version = "0.18.0" +version = "0.17.3" description = "Persistent/Functional/Immutable data structures" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.5" [[package]] name = "pytest" -version = "6.2.5" +version = "6.2.4" description = "pytest: simple powerful testing with Python" category = "dev" optional = false @@ -998,7 +964,7 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""} importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} iniconfig = "*" packaging = "*" -pluggy = ">=0.12,<2.0" +pluggy = ">=0.12,<1.0.0a1" py = ">=1.8.2" toml = "*" @@ -1023,21 +989,21 @@ testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtuale [[package]] name = "pytest-print" -version = "0.3.0" +version = "0.2.1" 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.6" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [package.dependencies] -pytest = ">=6" +pytest = ">=3.0.0" [package.extras] -test = ["coverage (>=5)"] +test = ["coverage (>=5)", "pytest (>=4)"] [[package]] name = "python-dateutil" -version = "2.8.2" +version = "2.8.1" description = "Extensions to the standard Python datetime module" category = "main" optional = false @@ -1048,7 +1014,7 @@ six = ">=1.5" [[package]] name = "python-xlib" -version = "0.31" +version = "0.30" description = "Python X Library" category = "main" optional = false @@ -1091,7 +1057,7 @@ python-versions = "*" [[package]] name = "qt.py" -version = "1.3.6" +version = "1.3.3" description = "Python 2 & 3 compatibility wrapper around all Qt bindings - PySide, PySide2, PyQt4 and PyQt5." category = "main" optional = false @@ -1112,21 +1078,21 @@ sphinx = ">=1.3.1" [[package]] name = "requests" -version = "2.26.0" +version = "2.25.1" description = "Python HTTP for Humans." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.dependencies] certifi = ">=2017.4.17" -charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} -idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} +chardet = ">=3.0.2,<5" +idna = ">=2.5,<3" urllib3 = ">=1.21.1,<1.27" [package.extras] +security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] -use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] [[package]] name = "rsa" @@ -1169,15 +1135,15 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "slack-sdk" -version = "3.10.1" +version = "3.6.0" 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 (>=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.7b0)", "psutil (>=5,<6)", "databases (>=0.3)", "boto3 (<=2)", "moto (<2)"] +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)"] [[package]] name = "smmap" @@ -1205,7 +1171,7 @@ python-versions = "*" [[package]] name = "sphinx" -version = "4.1.2" +version = "4.0.2" description = "Python documentation generator" category = "dev" optional = false @@ -1224,14 +1190,14 @@ requests = ">=2.5.0" snowballstemmer = ">=1.1" sphinxcontrib-applehelp = "*" sphinxcontrib-devhelp = "*" -sphinxcontrib-htmlhelp = ">=2.0.0" +sphinxcontrib-htmlhelp = "*" sphinxcontrib-jsmath = "*" sphinxcontrib-qthelp = "*" -sphinxcontrib-serializinghtml = ">=1.1.5" +sphinxcontrib-serializinghtml = "*" [package.extras] docs = ["sphinxcontrib-websupport"] -lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.900)", "docutils-stubs", "types-typed-ast", "types-pkg-resources", "types-requests"] +lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.800)", "docutils-stubs"] test = ["pytest", "pytest-cov", "html5lib", "cython", "typed-ast"] [[package]] @@ -1373,7 +1339,7 @@ python-versions = "*" [[package]] name = "typing-extensions" -version = "3.10.0.2" +version = "3.10.0.0" description = "Backported and Experimental Type Hints for Python 3.5+" category = "main" optional = false @@ -1389,7 +1355,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "urllib3" -version = "1.26.6" +version = "1.26.5" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false @@ -1459,7 +1425,7 @@ typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} [[package]] name = "zipp" -version = "3.5.0" +version = "3.4.1" description = "Backport of pathlib-compatible object wrapper for zip files" category = "main" optional = false @@ -1467,12 +1433,12 @@ python-versions = ">=3.6" [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)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] +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"] [metadata] lock-version = "1.1" python-versions = "3.7.*" -content-hash = "ca2a0258a784674ff489a07d0dc8dd2a22373ee39add02cb4676898b8a6993a1" +content-hash = "8875d530ae66f9763b5b0cb84d9d35edc184ef5c141b63d38bf1ff5a1226e556" [metadata.files] acre = [] @@ -1536,8 +1502,8 @@ arrow = [ {file = "arrow-0.17.0.tar.gz", hash = "sha256:ff08d10cda1d36c68657d6ad20d74fbea493d980f8b2d45344e00d6ed2bf6ed4"}, ] astroid = [ - {file = "astroid-2.7.3-py3-none-any.whl", hash = "sha256:dc1e8b28427d6bbef6b8842b18765ab58f558c42bb80540bd7648c98412af25e"}, - {file = "astroid-2.7.3.tar.gz", hash = "sha256:3b680ce0419b8a771aba6190139a3998d14b413852506d99aff8dc2bf65ee67c"}, + {file = "astroid-2.5.6-py3-none-any.whl", hash = "sha256:4db03ab5fc3340cf619dbc25e42c2cc3755154ce6009469766d7143d1fc2ee4e"}, + {file = "astroid-2.5.6.tar.gz", hash = "sha256:8a398dfce302c13f14bab13e2b14fe385d32b73f4e4853b9bdfb64598baa1975"}, ] async-timeout = [ {file = "async-timeout-3.0.1.tar.gz", hash = "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f"}, @@ -1560,8 +1526,8 @@ babel = [ {file = "Babel-2.9.1.tar.gz", hash = "sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0"}, ] blessed = [ - {file = "blessed-1.18.1-py2.py3-none-any.whl", hash = "sha256:dd7c0d33db9a2e7f597b446996484d0ed46e1586239db064fb5025008937dcae"}, - {file = "blessed-1.18.1.tar.gz", hash = "sha256:8b09936def6bc06583db99b65636b980075733e13550cb6af262ce724a55da23"}, + {file = "blessed-1.18.0-py2.py3-none-any.whl", hash = "sha256:5b5e2f0563d5a668c282f3f5946f7b1abb70c85829461900e607e74d7725106e"}, + {file = "blessed-1.18.0.tar.gz", hash = "sha256:1312879f971330a1b7f2c6341f2ae7e2cbac244bfc9d0ecfbbecd4b0293bc755"}, ] cachetools = [ {file = "cachetools-4.2.2-py3-none-any.whl", hash = "sha256:2cc0b89715337ab6dbba85b5b50effe2b0c74e035d83ee8ed637cf52f12ae001"}, @@ -1572,60 +1538,48 @@ certifi = [ {file = "certifi-2021.5.30.tar.gz", hash = "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee"}, ] cffi = [ - {file = "cffi-1.14.6-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:22b9c3c320171c108e903d61a3723b51e37aaa8c81255b5e7ce102775bd01e2c"}, - {file = "cffi-1.14.6-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:f0c5d1acbfca6ebdd6b1e3eded8d261affb6ddcf2186205518f1428b8569bb99"}, - {file = "cffi-1.14.6-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:99f27fefe34c37ba9875f224a8f36e31d744d8083e00f520f133cab79ad5e819"}, - {file = "cffi-1.14.6-cp27-cp27m-win32.whl", hash = "sha256:55af55e32ae468e9946f741a5d51f9896da6b9bf0bbdd326843fec05c730eb20"}, - {file = "cffi-1.14.6-cp27-cp27m-win_amd64.whl", hash = "sha256:7bcac9a2b4fdbed2c16fa5681356d7121ecabf041f18d97ed5b8e0dd38a80224"}, - {file = "cffi-1.14.6-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ed38b924ce794e505647f7c331b22a693bee1538fdf46b0222c4717b42f744e7"}, - {file = "cffi-1.14.6-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e22dcb48709fc51a7b58a927391b23ab37eb3737a98ac4338e2448bef8559b33"}, - {file = "cffi-1.14.6-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:aedb15f0a5a5949ecb129a82b72b19df97bbbca024081ed2ef88bd5c0a610534"}, - {file = "cffi-1.14.6-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:48916e459c54c4a70e52745639f1db524542140433599e13911b2f329834276a"}, - {file = "cffi-1.14.6-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f627688813d0a4140153ff532537fbe4afea5a3dffce1f9deb7f91f848a832b5"}, - {file = "cffi-1.14.6-cp35-cp35m-win32.whl", hash = "sha256:f0010c6f9d1a4011e429109fda55a225921e3206e7f62a0c22a35344bfd13cca"}, - {file = "cffi-1.14.6-cp35-cp35m-win_amd64.whl", hash = "sha256:57e555a9feb4a8460415f1aac331a2dc833b1115284f7ded7278b54afc5bd218"}, - {file = "cffi-1.14.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e8c6a99be100371dbb046880e7a282152aa5d6127ae01783e37662ef73850d8f"}, - {file = "cffi-1.14.6-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:19ca0dbdeda3b2615421d54bef8985f72af6e0c47082a8d26122adac81a95872"}, - {file = "cffi-1.14.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d950695ae4381ecd856bcaf2b1e866720e4ab9a1498cba61c602e56630ca7195"}, - {file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9dc245e3ac69c92ee4c167fbdd7428ec1956d4e754223124991ef29eb57a09d"}, - {file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a8661b2ce9694ca01c529bfa204dbb144b275a31685a075ce123f12331be790b"}, - {file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b315d709717a99f4b27b59b021e6207c64620790ca3e0bde636a6c7f14618abb"}, - {file = "cffi-1.14.6-cp36-cp36m-win32.whl", hash = "sha256:80b06212075346b5546b0417b9f2bf467fea3bfe7352f781ffc05a8ab24ba14a"}, - {file = "cffi-1.14.6-cp36-cp36m-win_amd64.whl", hash = "sha256:a9da7010cec5a12193d1af9872a00888f396aba3dc79186604a09ea3ee7c029e"}, - {file = "cffi-1.14.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4373612d59c404baeb7cbd788a18b2b2a8331abcc84c3ba40051fcd18b17a4d5"}, - {file = "cffi-1.14.6-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f10afb1004f102c7868ebfe91c28f4a712227fe4cb24974350ace1f90e1febbf"}, - {file = "cffi-1.14.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:fd4305f86f53dfd8cd3522269ed7fc34856a8ee3709a5e28b2836b2db9d4cd69"}, - {file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d6169cb3c6c2ad50db5b868db6491a790300ade1ed5d1da29289d73bbe40b56"}, - {file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d4b68e216fc65e9fe4f524c177b54964af043dde734807586cf5435af84045c"}, - {file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33791e8a2dc2953f28b8d8d300dde42dd929ac28f974c4b4c6272cb2955cb762"}, - {file = "cffi-1.14.6-cp37-cp37m-win32.whl", hash = "sha256:0c0591bee64e438883b0c92a7bed78f6290d40bf02e54c5bf0978eaf36061771"}, - {file = "cffi-1.14.6-cp37-cp37m-win_amd64.whl", hash = "sha256:8eb687582ed7cd8c4bdbff3df6c0da443eb89c3c72e6e5dcdd9c81729712791a"}, - {file = "cffi-1.14.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ba6f2b3f452e150945d58f4badd92310449876c4c954836cfb1803bdd7b422f0"}, - {file = "cffi-1.14.6-cp38-cp38-manylinux1_i686.whl", hash = "sha256:64fda793737bc4037521d4899be780534b9aea552eb673b9833b01f945904c2e"}, - {file = "cffi-1.14.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:9f3e33c28cd39d1b655ed1ba7247133b6f7fc16fa16887b120c0c670e35ce346"}, - {file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26bb2549b72708c833f5abe62b756176022a7b9a7f689b571e74c8478ead51dc"}, - {file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb687a11f0a7a1839719edd80f41e459cc5366857ecbed383ff376c4e3cc6afd"}, - {file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d2ad4d668a5c0645d281dcd17aff2be3212bc109b33814bbb15c4939f44181cc"}, - {file = "cffi-1.14.6-cp38-cp38-win32.whl", hash = "sha256:487d63e1454627c8e47dd230025780e91869cfba4c753a74fda196a1f6ad6548"}, - {file = "cffi-1.14.6-cp38-cp38-win_amd64.whl", hash = "sha256:c33d18eb6e6bc36f09d793c0dc58b0211fccc6ae5149b808da4a62660678b156"}, - {file = "cffi-1.14.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:06c54a68935738d206570b20da5ef2b6b6d92b38ef3ec45c5422c0ebaf338d4d"}, - {file = "cffi-1.14.6-cp39-cp39-manylinux1_i686.whl", hash = "sha256:f174135f5609428cc6e1b9090f9268f5c8935fddb1b25ccb8255a2d50de6789e"}, - {file = "cffi-1.14.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f3ebe6e73c319340830a9b2825d32eb6d8475c1dac020b4f0aa774ee3b898d1c"}, - {file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c8d896becff2fa653dc4438b54a5a25a971d1f4110b32bd3068db3722c80202"}, - {file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4922cd707b25e623b902c86188aca466d3620892db76c0bdd7b99a3d5e61d35f"}, - {file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c9e005e9bd57bc987764c32a1bee4364c44fdc11a3cc20a40b93b444984f2b87"}, - {file = "cffi-1.14.6-cp39-cp39-win32.whl", hash = "sha256:eb9e2a346c5238a30a746893f23a9535e700f8192a68c07c0258e7ece6ff3728"}, - {file = "cffi-1.14.6-cp39-cp39-win_amd64.whl", hash = "sha256:818014c754cd3dba7229c0f5884396264d51ffb87ec86e927ef0be140bfdb0d2"}, - {file = "cffi-1.14.6.tar.gz", hash = "sha256:c9a875ce9d7fe32887784274dd533c57909b7b1dcadcc128a2ac21331a9765dd"}, + {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-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-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-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-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"}, ] chardet = [ {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, ] -charset-normalizer = [ - {file = "charset-normalizer-2.0.4.tar.gz", hash = "sha256:f23667ebe1084be45f6ae0538e4a5a865206544097e4e8bbcacf42cd02a348f3"}, - {file = "charset_normalizer-2.0.4-py3-none-any.whl", hash = "sha256:0c8911edd15d19223366a194a513099a302055a962bca2cec0f54b8b63175d8b"}, -] click = [ {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, @@ -1701,25 +1655,30 @@ coverage = [ {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"}, ] cryptography = [ - {file = "cryptography-3.4.8-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:a00cf305f07b26c351d8d4e1af84ad7501eca8a342dedf24a7acb0e7b7406e14"}, - {file = "cryptography-3.4.8-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:f44d141b8c4ea5eb4dbc9b3ad992d45580c1d22bf5e24363f2fbf50c2d7ae8a7"}, - {file = "cryptography-3.4.8-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0a7dcbcd3f1913f664aca35d47c1331fce738d44ec34b7be8b9d332151b0b01e"}, - {file = "cryptography-3.4.8-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34dae04a0dce5730d8eb7894eab617d8a70d0c97da76b905de9efb7128ad7085"}, - {file = "cryptography-3.4.8-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1eb7bb0df6f6f583dd8e054689def236255161ebbcf62b226454ab9ec663746b"}, - {file = "cryptography-3.4.8-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:9965c46c674ba8cc572bc09a03f4c649292ee73e1b683adb1ce81e82e9a6a0fb"}, - {file = "cryptography-3.4.8-cp36-abi3-win32.whl", hash = "sha256:21ca464b3a4b8d8e86ba0ee5045e103a1fcfac3b39319727bc0fc58c09c6aff7"}, - {file = "cryptography-3.4.8-cp36-abi3-win_amd64.whl", hash = "sha256:3520667fda779eb788ea00080124875be18f2d8f0848ec00733c0ec3bb8219fc"}, - {file = "cryptography-3.4.8-pp36-pypy36_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d2a6e5ef66503da51d2110edf6c403dc6b494cc0082f85db12f54e9c5d4c3ec5"}, - {file = "cryptography-3.4.8-pp36-pypy36_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a305600e7a6b7b855cd798e00278161b681ad6e9b7eca94c721d5f588ab212af"}, - {file = "cryptography-3.4.8-pp36-pypy36_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:3fa3a7ccf96e826affdf1a0a9432be74dc73423125c8f96a909e3835a5ef194a"}, - {file = "cryptography-3.4.8-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:d9ec0e67a14f9d1d48dd87a2531009a9b251c02ea42851c060b25c782516ff06"}, - {file = "cryptography-3.4.8-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5b0fbfae7ff7febdb74b574055c7466da334a5371f253732d7e2e7525d570498"}, - {file = "cryptography-3.4.8-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94fff993ee9bc1b2440d3b7243d488c6a3d9724cc2b09cdb297f6a886d040ef7"}, - {file = "cryptography-3.4.8-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:8695456444f277af73a4877db9fc979849cd3ee74c198d04fc0776ebc3db52b9"}, - {file = "cryptography-3.4.8-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:cd65b60cfe004790c795cc35f272e41a3df4631e2fb6b35aa7ac6ef2859d554e"}, - {file = "cryptography-3.4.8.tar.gz", hash = "sha256:94cc5ed4ceaefcbe5bf38c8fba6a21fc1d365bb8fb826ea1688e3370b2e24a1c"}, + {file = "cryptography-3.4.7-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:3d8427734c781ea5f1b41d6589c293089704d4759e34597dce91014ac125aad1"}, + {file = "cryptography-3.4.7-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:8e56e16617872b0957d1c9742a3f94b43533447fd78321514abbe7db216aa250"}, + {file = "cryptography-3.4.7-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:37340614f8a5d2fb9aeea67fd159bfe4f5f4ed535b1090ce8ec428b2f15a11f2"}, + {file = "cryptography-3.4.7-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:240f5c21aef0b73f40bb9f78d2caff73186700bf1bc6b94285699aff98cc16c6"}, + {file = "cryptography-3.4.7-cp36-abi3-manylinux2014_x86_64.whl", hash = "sha256:1e056c28420c072c5e3cb36e2b23ee55e260cb04eee08f702e0edfec3fb51959"}, + {file = "cryptography-3.4.7-cp36-abi3-win32.whl", hash = "sha256:0f1212a66329c80d68aeeb39b8a16d54ef57071bf22ff4e521657b27372e327d"}, + {file = "cryptography-3.4.7-cp36-abi3-win_amd64.whl", hash = "sha256:de4e5f7f68220d92b7637fc99847475b59154b7a1b3868fb7385337af54ac9ca"}, + {file = "cryptography-3.4.7-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:26965837447f9c82f1855e0bc8bc4fb910240b6e0d16a664bb722df3b5b06873"}, + {file = "cryptography-3.4.7-pp36-pypy36_pp73-manylinux2014_x86_64.whl", hash = "sha256:eb8cc2afe8b05acbd84a43905832ec78e7b3873fb124ca190f574dca7389a87d"}, + {file = "cryptography-3.4.7-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:7ec5d3b029f5fa2b179325908b9cd93db28ab7b85bb6c1db56b10e0b54235177"}, + {file = "cryptography-3.4.7-pp37-pypy37_pp73-manylinux2014_x86_64.whl", hash = "sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9"}, + {file = "cryptography-3.4.7.tar.gz", hash = "sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713"}, +] +cx-freeze = [ + {file = "cx_Freeze-6.6-cp36-cp36m-win32.whl", hash = "sha256:b3d3a6bcd1a07c50b4e1c907f14842642156110e63a99cd5c73b8a24751e9b97"}, + {file = "cx_Freeze-6.6-cp36-cp36m-win_amd64.whl", hash = "sha256:1935266ec644ea4f7e584985f44cefc0622a449a09980d990833a1a2afcadac8"}, + {file = "cx_Freeze-6.6-cp37-cp37m-win32.whl", hash = "sha256:1eac2b0f254319cc641ce25bd83337effd7936092562fde701f3ffb40e0274ec"}, + {file = "cx_Freeze-6.6-cp37-cp37m-win_amd64.whl", hash = "sha256:2bc46ef6d510811b6002f34a3ae4cbfdea44e18644febd2a404d3ee8e48a9fc4"}, + {file = "cx_Freeze-6.6-cp38-cp38-win32.whl", hash = "sha256:46eb50ebc46f7ae236d16c6a52671ab0f7bb479bea668da19f4b6de3cc413e9e"}, + {file = "cx_Freeze-6.6-cp38-cp38-win_amd64.whl", hash = "sha256:8c3b00476ce385bb58595bffce55aed031e5a6e16ab6e14d8bee9d1d569e46c3"}, + {file = "cx_Freeze-6.6-cp39-cp39-win32.whl", hash = "sha256:6e9340cbcf52d4836980ecc83ddba4f7704ff6654dd41168c146b74f512977ce"}, + {file = "cx_Freeze-6.6-cp39-cp39-win_amd64.whl", hash = "sha256:2fcf1c8b77ae5c06f45be3a9aff79e1dd808c0d624e97561f840dec5ea9b214a"}, + {file = "cx_Freeze-6.6.tar.gz", hash = "sha256:c4af8ad3f7e7d71e291c1dec5d0fb26bbe92df834b098ed35434c901fbd6762f"}, ] -cx-freeze = [] cx-logging = [ {file = "cx_Logging-3.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:9fcd297e5c51470521c47eff0f86ba844aeca6be97e13c3e2114ebdf03fa3c96"}, {file = "cx_Logging-3.0-cp36-cp36m-win32.whl", hash = "sha256:0df4be47c5022cc54316949e283403214568ef599817ced0c0972183d6d4fabb"}, @@ -1766,20 +1725,20 @@ gitdb = [ {file = "gitdb-4.0.7.tar.gz", hash = "sha256:96bf5c08b157a666fec41129e6d327235284cca4c81e92109260f353ba138005"}, ] gitpython = [ - {file = "GitPython-3.1.20-py3-none-any.whl", hash = "sha256:b1e1c269deab1b08ce65403cf14e10d2ef1f6c89e33ea7c5e5bb0222ea593b8a"}, - {file = "GitPython-3.1.20.tar.gz", hash = "sha256:df0e072a200703a65387b0cfdf0466e3bab729c0458cf6b7349d0e9877636519"}, + {file = "GitPython-3.1.17-py3-none-any.whl", hash = "sha256:29fe82050709760081f588dd50ce83504feddbebdc4da6956d02351552b1c135"}, + {file = "GitPython-3.1.17.tar.gz", hash = "sha256:ee24bdc93dce357630764db659edaf6b8d664d4ff5447ccfeedd2dc5c253f41e"}, ] google-api-core = [ - {file = "google-api-core-1.31.2.tar.gz", hash = "sha256:8500aded318fdb235130bf183c726a05a9cb7c4b09c266bd5119b86cdb8a4d10"}, - {file = "google_api_core-1.31.2-py2.py3-none-any.whl", hash = "sha256:384459a0dc98c1c8cd90b28dc5800b8705e0275a673a7144a513ae80fc77950b"}, + {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"}, ] 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.35.0.tar.gz", hash = "sha256:b7033be9028c188ee30200b204ea00ed82ea1162e8ac1df4aa6ded19a191d88e"}, - {file = "google_auth-1.35.0-py2.py3-none-any.whl", hash = "sha256:997516b42ecb5b63e8d80f5632c1a61dddf41d2a4c2748057837e06e00014258"}, + {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"}, ] google-auth-httplib2 = [ {file = "google-auth-httplib2-0.1.0.tar.gz", hash = "sha256:a07c39fd632becacd3f07718dfd6021bf396978f03ad3ce4321d060015cc30ac"}, @@ -1794,32 +1753,32 @@ httplib2 = [ {file = "httplib2-0.19.1.tar.gz", hash = "sha256:0b12617eeca7433d4c396a100eaecfa4b08ee99aa881e6df6e257a7aad5d533d"}, ] idna = [ - {file = "idna-3.2-py3-none-any.whl", hash = "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a"}, - {file = "idna-3.2.tar.gz", hash = "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"}, + {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, + {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, ] imagesize = [ {file = "imagesize-1.2.0-py2.py3-none-any.whl", hash = "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1"}, {file = "imagesize-1.2.0.tar.gz", hash = "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"}, ] importlib-metadata = [ - {file = "importlib_metadata-4.8.1-py3-none-any.whl", hash = "sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15"}, - {file = "importlib_metadata-4.8.1.tar.gz", hash = "sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1"}, + {file = "importlib_metadata-4.5.0-py3-none-any.whl", hash = "sha256:833b26fb89d5de469b24a390e9df088d4e52e4ba33b01dc5e0e4f41b81a16c00"}, + {file = "importlib_metadata-4.5.0.tar.gz", hash = "sha256:b142cc1dd1342f31ff04bb7d022492b09920cb64fed867cd3ea6f80fe3ebd139"}, ] 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.9.3-py3-none-any.whl", hash = "sha256:e17d6e2b81095c9db0a03a8025a957f334d6ea30b26f9ec70805411e5c7c81f2"}, - {file = "isort-5.9.3.tar.gz", hash = "sha256:9c2ea1e62d871267b78307fe511c0838ba0da28698c5732d54e2790bf3ba9899"}, + {file = "isort-5.8.0-py3-none-any.whl", hash = "sha256:2bb1680aad211e3c9944dbce1d4ba09a989f04e238296c87fe2139faa26d655d"}, + {file = "isort-5.8.0.tar.gz", hash = "sha256:0a943902919f65c5684ac4e0154b1ad4fac6dcaa5d9f3426b732f1c8b5419be6"}, ] jedi = [ {file = "jedi-0.13.3-py2.py3-none-any.whl", hash = "sha256:2c6bcd9545c7d6440951b12b44d373479bf18123a401a52025cf98563fbd826c"}, {file = "jedi-0.13.3.tar.gz", hash = "sha256:2bb0603e3506f708e792c7f4ad8fc2a7a9d9c2d292a358fbbd58da531695595b"}, ] jeepney = [ - {file = "jeepney-0.7.1-py3-none-any.whl", hash = "sha256:1b5a0ea5c0e7b166b2f5895b91a08c14de8915afda4407fb5022a195224958ac"}, - {file = "jeepney-0.7.1.tar.gz", hash = "sha256:fa9e232dfa0c498bd0b8a3a73b8d8a31978304dcef0515adc859d4e096f96f4f"}, + {file = "jeepney-0.6.0-py3-none-any.whl", hash = "sha256:aec56c0eb1691a841795111e184e13cad504f7703b9a64f63020816afa79a8ae"}, + {file = "jeepney-0.6.0.tar.gz", hash = "sha256:7d59b6622675ca9e993a6bd38de845051d315f8b0c72cca3aef733a20b648657"}, ] jinja2 = [ {file = "Jinja2-2.11.3-py2.py3-none-any.whl", hash = "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419"}, @@ -1865,22 +1824,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"}, @@ -1889,21 +1838,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"}, @@ -1913,9 +1855,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"}, @@ -1965,79 +1904,56 @@ multidict = [ ] opentimelineio = [] packaging = [ - {file = "packaging-21.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"}, - {file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"}, + {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, + {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, ] 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.6-py2.py3-none-any.whl", hash = "sha256:3a130b266b3a36134dcc79c17b3c7ac9634f083825ca6ea9d8f557ee6195c9c8"}, - {file = "pathlib2-2.3.6.tar.gz", hash = "sha256:7d8bcb5555003cdf4a8d2872c538faa3a0f5d20630cb360e518ca3b981795e5f"}, + {file = "pathlib2-2.3.5-py2.py3-none-any.whl", hash = "sha256:0ec8205a157c80d7acc301c0b18fbd5d44fe655968f5d947b6ecef5290fc35db"}, + {file = "pathlib2-2.3.5.tar.gz", hash = "sha256:6cd9a47b597b37cc57de1c05e56fb1a1c9cc9fab04fe78c29acd090418529868"}, ] pillow = [ - {file = "Pillow-8.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"}, -] -platformdirs = [ - {file = "platformdirs-2.3.0-py3-none-any.whl", hash = "sha256:8003ac87717ae2c7ee1ea5a84a1a61e87f3fbd16eb5aadba194ea30a9019f648"}, - {file = "platformdirs-2.3.0.tar.gz", hash = "sha256:15b056538719b1c94bdaccb29e5f81879c7f7f0f4a153f46086d155dffcd4f0f"}, + {file = "Pillow-8.2.0-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:dc38f57d8f20f06dd7c3161c59ca2c86893632623f33a42d592f097b00f720a9"}, + {file = "Pillow-8.2.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a013cbe25d20c2e0c4e85a9daf438f85121a4d0344ddc76e33fd7e3965d9af4b"}, + {file = "Pillow-8.2.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8bb1e155a74e1bfbacd84555ea62fa21c58e0b4e7e6b20e4447b8d07990ac78b"}, + {file = "Pillow-8.2.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c5236606e8570542ed424849f7852a0ff0bce2c4c8d0ba05cc202a5a9c97dee9"}, + {file = "Pillow-8.2.0-cp36-cp36m-win32.whl", hash = "sha256:12e5e7471f9b637762453da74e390e56cc43e486a88289995c1f4c1dc0bfe727"}, + {file = "Pillow-8.2.0-cp36-cp36m-win_amd64.whl", hash = "sha256:5afe6b237a0b81bd54b53f835a153770802f164c5570bab5e005aad693dab87f"}, + {file = "Pillow-8.2.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:cb7a09e173903541fa888ba010c345893cd9fc1b5891aaf060f6ca77b6a3722d"}, + {file = "Pillow-8.2.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0d19d70ee7c2ba97631bae1e7d4725cdb2ecf238178096e8c82ee481e189168a"}, + {file = "Pillow-8.2.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:083781abd261bdabf090ad07bb69f8f5599943ddb539d64497ed021b2a67e5a9"}, + {file = "Pillow-8.2.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:c6b39294464b03457f9064e98c124e09008b35a62e3189d3513e5148611c9388"}, + {file = "Pillow-8.2.0-cp37-cp37m-win32.whl", hash = "sha256:01425106e4e8cee195a411f729cff2a7d61813b0b11737c12bd5991f5f14bcd5"}, + {file = "Pillow-8.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3b570f84a6161cf8865c4e08adf629441f56e32f180f7aa4ccbd2e0a5a02cba2"}, + {file = "Pillow-8.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:031a6c88c77d08aab84fecc05c3cde8414cd6f8406f4d2b16fed1e97634cc8a4"}, + {file = "Pillow-8.2.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:66cc56579fd91f517290ab02c51e3a80f581aba45fd924fcdee01fa06e635812"}, + {file = "Pillow-8.2.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6c32cc3145928c4305d142ebec682419a6c0a8ce9e33db900027ddca1ec39178"}, + {file = "Pillow-8.2.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:624b977355cde8b065f6d51b98497d6cd5fbdd4f36405f7a8790e3376125e2bb"}, + {file = "Pillow-8.2.0-cp38-cp38-win32.whl", hash = "sha256:5cbf3e3b1014dddc45496e8cf38b9f099c95a326275885199f427825c6522232"}, + {file = "Pillow-8.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:463822e2f0d81459e113372a168f2ff59723e78528f91f0bd25680ac185cf797"}, + {file = "Pillow-8.2.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:95d5ef984eff897850f3a83883363da64aae1000e79cb3c321915468e8c6add5"}, + {file = "Pillow-8.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b91c36492a4bbb1ee855b7d16fe51379e5f96b85692dc8210831fbb24c43e484"}, + {file = "Pillow-8.2.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d68cb92c408261f806b15923834203f024110a2e2872ecb0bd2a110f89d3c602"}, + {file = "Pillow-8.2.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f217c3954ce5fd88303fc0c317af55d5e0204106d86dea17eb8205700d47dec2"}, + {file = "Pillow-8.2.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:5b70110acb39f3aff6b74cf09bb4169b167e2660dabc304c1e25b6555fa781ef"}, + {file = "Pillow-8.2.0-cp39-cp39-win32.whl", hash = "sha256:a7d5e9fad90eff8f6f6106d3b98b553a88b6f976e51fce287192a5d2d5363713"}, + {file = "Pillow-8.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:238c197fc275b475e87c1453b05b467d2d02c2915fdfdd4af126145ff2e4610c"}, + {file = "Pillow-8.2.0-pp36-pypy36_pp73-macosx_10_10_x86_64.whl", hash = "sha256:0e04d61f0064b545b989126197930807c86bcbd4534d39168f4aa5fda39bb8f9"}, + {file = "Pillow-8.2.0-pp36-pypy36_pp73-manylinux2010_i686.whl", hash = "sha256:63728564c1410d99e6d1ae8e3b810fe012bc440952168af0a2877e8ff5ab96b9"}, + {file = "Pillow-8.2.0-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:c03c07ed32c5324939b19e36ae5f75c660c81461e312a41aea30acdd46f93a7c"}, + {file = "Pillow-8.2.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:4d98abdd6b1e3bf1a1cbb14c3895226816e666749ac040c4e2554231068c639b"}, + {file = "Pillow-8.2.0-pp37-pypy37_pp73-manylinux2010_i686.whl", hash = "sha256:aac00e4bc94d1b7813fe882c28990c1bc2f9d0e1aa765a5f2b516e8a6a16a9e4"}, + {file = "Pillow-8.2.0-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:22fd0f42ad15dfdde6c581347eaa4adb9a6fc4b865f90b23378aa7914895e120"}, + {file = "Pillow-8.2.0-pp37-pypy37_pp73-win32.whl", hash = "sha256:e98eca29a05913e82177b3ba3d198b1728e164869c613d76d0de4bde6768a50e"}, + {file = "Pillow-8.2.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:8b56553c0345ad6dcb2e9b433ae47d67f95fc23fe28a0bde15a120f25257e291"}, + {file = "Pillow-8.2.0.tar.gz", hash = "sha256:a787ab10d7bb5494e5f76536ac460741788f1fbce851068d73a87ca7c35fc3e1"}, ] pluggy = [ - {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, - {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, + {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, + {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, ] prefixed = [ {file = "prefixed-0.3.2-py2.py3-none-any.whl", hash = "sha256:5e107306462d63f2f03c529dbf11b0026fdfec621a9a008ca639d71de22995c3"}, @@ -2062,13 +1978,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"}, ] @@ -2131,112 +2043,78 @@ pyflakes = [ {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, ] pygments = [ - {file = "Pygments-2.10.0-py3-none-any.whl", hash = "sha256:b8e67fe6af78f492b3c4b3e2970c0624cbf08beb1e493b2c99b9fa1b67a20380"}, - {file = "Pygments-2.10.0.tar.gz", hash = "sha256:f398865f7eb6874156579fdf36bc840a03cab64d1cde9e93d68f46a425ec52c6"}, + {file = "Pygments-2.9.0-py3-none-any.whl", hash = "sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e"}, + {file = "Pygments-2.9.0.tar.gz", hash = "sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f"}, ] pylint = [ - {file = "pylint-2.10.2-py3-none-any.whl", hash = "sha256:e178e96b6ba171f8ef51fbce9ca30931e6acbea4a155074d80cc081596c9e852"}, - {file = "pylint-2.10.2.tar.gz", hash = "sha256:6758cce3ddbab60c52b57dcc07f0c5d779e5daf0cf50f6faacbef1d3ea62d2a1"}, + {file = "pylint-2.8.3-py3-none-any.whl", hash = "sha256:792b38ff30903884e4a9eab814ee3523731abd3c463f3ba48d7b627e87013484"}, + {file = "pylint-2.8.3.tar.gz", hash = "sha256:0a049c5d47b629d9070c3932d13bff482b12119b6a241a93bc460b0be16953c8"}, ] pymongo = [ - {file = "pymongo-3.12.0-cp27-cp27m-macosx_10_14_intel.whl", hash = "sha256:072ba7cb65c8aa4d5c5659bf6722ee85781c9d7816dc00679b8b6f3dff1ddafc"}, - {file = "pymongo-3.12.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:d6e11ffd43184d529d6752d6dcb62b994f903038a17ea2168ef1910c96324d26"}, - {file = "pymongo-3.12.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:7412a36798966624dc4c57d64aa43c2d1100b348abd98daaac8e99e57d87e1d7"}, - {file = "pymongo-3.12.0-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e8a82e35d52ad6f867e88096a1a2b9bdc7ec4d5e65c7b4976a248bf2d1a32a93"}, - {file = "pymongo-3.12.0-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:dcd3d0009fbb6e454d729f8b22d0063bd9171c31a55e0f0271119bd4f2700023"}, - {file = "pymongo-3.12.0-cp27-cp27m-win32.whl", hash = "sha256:1bc6fe7279ff40c6818db002bf5284aa03ec181ea1b1ceaeee33c289d412afa7"}, - {file = "pymongo-3.12.0-cp27-cp27m-win_amd64.whl", hash = "sha256:e2b7670c0c8c6b501464150dd49dd0d6be6cb7f049e064124911cec5514fa19e"}, - {file = "pymongo-3.12.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:316c1b8723afa9870567cd6dff35d440b2afeda53aa13da6c5ab85f98ed6f5ca"}, - {file = "pymongo-3.12.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:255a35bf29185f44b412e31a927d9dcedda7c2c380127ecc4fbf2f61b72fa978"}, - {file = "pymongo-3.12.0-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ffbae429ba9e42d0582d3ac63fdb410338892468a2107d8ff68228ec9a39a0ed"}, - {file = "pymongo-3.12.0-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c188db6cf9e14dbbb42f5254292be96f05374a35e7dfa087cc2140f0ff4f10f6"}, - {file = "pymongo-3.12.0-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:6fb3f85870ae26896bb44e67db94045f2ebf00c5d41e6b66cdcbb5afd644fc18"}, - {file = "pymongo-3.12.0-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:aaa038eafb7186a4abbb311fcf20724be9363645882bbce540bef4797e812a7a"}, - {file = "pymongo-3.12.0-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:7d98ce3c42921bb91566121b658e0d9d59a9082a9bd6f473190607ff25ab637f"}, - {file = "pymongo-3.12.0-cp34-cp34m-win32.whl", hash = "sha256:b0a0cf39f589e52d801fdef418305562bc030cdf8929217463c8433c65fd5c2f"}, - {file = "pymongo-3.12.0-cp34-cp34m-win_amd64.whl", hash = "sha256:ceae3ab9e11a27aaab42878f1d203600dfd24f0e43678b47298219a0f10c0d30"}, - {file = "pymongo-3.12.0-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:5e574664f1468872cd40f74e4811e22b1aa4de9399d6bcfdf1ee6ea94c017fcf"}, - {file = "pymongo-3.12.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73b400fdc22de84bae0dbf1a22613928a41612ec0a3d6ed47caf7ad4d3d0f2ff"}, - {file = "pymongo-3.12.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:cbf8672edeb7b7128c4a939274801f0e32bbf5159987815e3d1eace625264a46"}, - {file = "pymongo-3.12.0-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:a634a4730ce0b0934ed75e45beba730968e12b4dafbb22f69b3b2f616d9e644e"}, - {file = "pymongo-3.12.0-cp35-cp35m-manylinux2014_i686.whl", hash = "sha256:c55782a55f4a013a78ac5b6ee4b8731a192dea7ab09f1b6b3044c96d5128edd4"}, - {file = "pymongo-3.12.0-cp35-cp35m-manylinux2014_ppc64le.whl", hash = "sha256:11f9e0cfc84ade088a38df2708d0b958bb76360181df1b2e1e1a41beaa57952b"}, - {file = "pymongo-3.12.0-cp35-cp35m-manylinux2014_s390x.whl", hash = "sha256:186104a94d39b8412f8e3de385acd990a628346a4402d4f3a288a82b8660bd22"}, - {file = "pymongo-3.12.0-cp35-cp35m-manylinux2014_x86_64.whl", hash = "sha256:70761fd3c576b027eec882b43ee0a8e5b22ff9c20cdf4d0400e104bc29e53e34"}, - {file = "pymongo-3.12.0-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:333bfad77aa9cd11711febfb75eed0bb537a1d022e1c252714dad38993590240"}, - {file = "pymongo-3.12.0-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fa8957e9a1b202cb45e6b839c241cd986c897be1e722b81d2f32e9c6aeee80b0"}, - {file = "pymongo-3.12.0-cp35-cp35m-win32.whl", hash = "sha256:4ba0def4abef058c0e5101e05e3d5266e6fffb9795bbf8be0fe912a7361a0209"}, - {file = "pymongo-3.12.0-cp35-cp35m-win_amd64.whl", hash = "sha256:a0e5dff6701fa615f165306e642709e1c1550d5b237c5a7a6ea299886828bd50"}, - {file = "pymongo-3.12.0-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:b542d56ed1b8d5cf3bb36326f814bd2fbe8812dfd2582b80a15689ea433c0e35"}, - {file = "pymongo-3.12.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a325600c83e61e3c9cebc0c2b1c8c4140fa887f789085075e8f44c8ff2547eb9"}, - {file = "pymongo-3.12.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:48d5bc80ab0af6b60c4163c5617f5cd23f2f880d7600940870ea5055816af024"}, - {file = "pymongo-3.12.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c5cab230e7cabdae9ff23c12271231283efefb944c1b79bed79a91beb65ba547"}, - {file = "pymongo-3.12.0-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:d73e10772152605f6648ba4410318594f1043bbfe36d2fadee7c4b8912eff7c5"}, - {file = "pymongo-3.12.0-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:b1c4874331ab960429caca81acb9d2932170d66d6d6f87e65dc4507a85aca152"}, - {file = "pymongo-3.12.0-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:a3566acfbcde46911c52810374ecc0354fdb841284a3efef6ff7105bc007e9a8"}, - {file = "pymongo-3.12.0-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:b3b5b3cbc3fdf4fcfa292529df2a85b5d9c7053913a739d3069af1e12e12219f"}, - {file = "pymongo-3.12.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd3854148005c808c485c754a184c71116372263709958b42aefbef2e5dd373a"}, - {file = "pymongo-3.12.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f55c1ddcc1f6050b07d468ce594f55dbf6107b459e16f735d26818d7be1e9538"}, - {file = "pymongo-3.12.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ced944dcdd561476deef7cb7bfd4987c69fffbfeff6d02ca4d5d4fd592d559b7"}, - {file = "pymongo-3.12.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78ecb8d42f50d393af912bfb1fb1dcc9aabe9967973efb49ee577e8f1cea494c"}, - {file = "pymongo-3.12.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1970cfe2aec1bf74b40cf30c130ad10cd968941694630386db33e1d044c22a2e"}, - {file = "pymongo-3.12.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b8bf42d3b32f586f4c9e37541769993783a534ad35531ce8a4379f6fa664fba9"}, - {file = "pymongo-3.12.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:bc9ac81e73573516070d24ce15da91281922811f385645df32bd3c8a45ab4684"}, - {file = "pymongo-3.12.0-cp36-cp36m-win32.whl", hash = "sha256:d04ca462cb99077e6c059e97c072957caf2918e6e4191e3161c01c439e0193de"}, - {file = "pymongo-3.12.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f2acf9bbcd514e901f82c4ca6926bbd2ae61716728f110b4343eb0a69612d018"}, - {file = "pymongo-3.12.0-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:b754240daafecd9d5fce426b0fbaaed03f4ebb130745c8a4ae9231fffb8d75e5"}, - {file = "pymongo-3.12.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:af586e85144023686fb0af09c8cdf672484ea182f352e7ceead3d832de381e1b"}, - {file = "pymongo-3.12.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:fe5872ce6f9627deac8314bdffd3862624227c3de4c17ef0cc78bbf0402999eb"}, - {file = "pymongo-3.12.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:f6977a520bd96e097c8a37a8cbb9faa1ea99d21bf84190195056e25f688af73d"}, - {file = "pymongo-3.12.0-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:2dbfbbded947a83a3dffc2bd1ec4750c17e40904692186e2c55a3ad314ca0222"}, - {file = "pymongo-3.12.0-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:a752ecd1a26000a6d67be7c9a2e93801994a8b3f866ac95b672fbc00225ca91a"}, - {file = "pymongo-3.12.0-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:1bab889ae7640eba739f67fcbf8eff252dddc60d4495e6ddd3a87cd9a95fdb52"}, - {file = "pymongo-3.12.0-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:f94c7d22fb36b184734dded7345a04ec5f95130421c775b8b0c65044ef073f34"}, - {file = "pymongo-3.12.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec5ca7c0007ce268048bbe0ffc6846ed1616cf3d8628b136e81d5e64ff3f52a2"}, - {file = "pymongo-3.12.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7c72d08acdf573455b2b9d2b75b8237654841d63a48bc2327dc102c6ee89b75a"}, - {file = "pymongo-3.12.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b6ea08758b6673610b3c5bdf47189286cf9c58b1077558706a2f6f8744922527"}, - {file = "pymongo-3.12.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46d5ec90276f71af3a29917b30f2aec2315a2759b5f8d45b3b63a07ca8a070a3"}, - {file = "pymongo-3.12.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:625befa3bc9b40746a749115cc6a15bf20b9bd7597ca55d646205b479a2c99c7"}, - {file = "pymongo-3.12.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d1131562ddc2ea8a446f66c2648d7dabec2b3816fc818528eb978a75a6d23b2e"}, - {file = "pymongo-3.12.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:eee42a1cc06565f6b21caa1f504ec15e07de7ebfd520ab57f8cb3308bc118e22"}, - {file = "pymongo-3.12.0-cp37-cp37m-win32.whl", hash = "sha256:94d38eba4d1b5eb3e6bfece0651b855a35c44f32fd91f512ab4ba41b8c0d3e66"}, - {file = "pymongo-3.12.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e018a4921657c2d3f89c720b7b90b9182e277178a04a7e9542cc79d7d787ca51"}, - {file = "pymongo-3.12.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7c6a9948916a7bbcc6d3a9f6fb75db1acb5546078023bfb3db6efabcd5a67527"}, - {file = "pymongo-3.12.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e9faf8d4712d5ea301d74abfcf6dafe4b7f4af7936e91f283b0ad7bf69ed3e3a"}, - {file = "pymongo-3.12.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:cc2894fe91f31a513860238ede69fe47fada21f9e7ddfe73f7f9fef93a971e41"}, - {file = "pymongo-3.12.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:053b4ebf91c7395d1fcd2ce6a9edff0024575b7b2de6781554a4114448a8adc9"}, - {file = "pymongo-3.12.0-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:39dafa2eaf577d1969f289dc9a44501859a1897eb45bd589e93ce843fc610800"}, - {file = "pymongo-3.12.0-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:246ec420e4c8744fceb4e259f906211b9c198e1f345e6158dcd7cbad3737e11e"}, - {file = "pymongo-3.12.0-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:208debdcf76ed39ebf24f38509f50dc1c100e31e8653817fedb8e1f867850a13"}, - {file = "pymongo-3.12.0-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:18290649759f9db660972442aa606f845c368db9b08c4c73770f6da14113569b"}, - {file = "pymongo-3.12.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:657ad80de8ec9ed656f28844efc801a0802961e8c6a85038d97ff6f555ef4919"}, - {file = "pymongo-3.12.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b772bab31cbd9cb911e41e1a611ebc9497f9a32a7348e2747c38210f75c00f41"}, - {file = "pymongo-3.12.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2399a85b54f68008e483b2871f4a458b4c980469c7fe921595ede073e4844f1e"}, - {file = "pymongo-3.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e66780f14c2efaf989cd3ac613b03ee6a8e3a0ba7b96c0bb14adca71a427e55"}, - {file = "pymongo-3.12.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02dc0b0f48ed3cd06c13b7e31b066bf91e00dac5f8147b0a0a45f9009bfab857"}, - {file = "pymongo-3.12.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:070a4ef689c9438a999ec3830e69b208ff0d12251846e064d947f97d819d1d05"}, - {file = "pymongo-3.12.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:db93608a246da44d728842b8fa9e45aa9782db76955f634a707739a8d53ff544"}, - {file = "pymongo-3.12.0-cp38-cp38-win32.whl", hash = "sha256:5af390fa9faf56c93252dab09ea57cd020c9123aa921b63a0ed51832fdb492e7"}, - {file = "pymongo-3.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:a2239556ff7241584ce57be1facf25081669bb457a9e5cbe68cce4aae6567aa1"}, - {file = "pymongo-3.12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cda9e628b1315beec8341e8c04aac9a0b910650b05e0751e42e399d5694aeacb"}, - {file = "pymongo-3.12.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:845a8b83798b2fb11b09928413cb32692866bfbc28830a433d9fa4c8c3720dd0"}, - {file = "pymongo-3.12.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:da8288bc4a7807c6715416deed1c57d94d5e03e93537889e002bf985be503f1a"}, - {file = "pymongo-3.12.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:a9ba2a63777027b06b116e1ea8248e66fd1bedc2c644f93124b81a91ddbf6d88"}, - {file = "pymongo-3.12.0-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:9a13661681d17e43009bb3e85e837aa1ec5feeea1e3654682a01b8821940f8b3"}, - {file = "pymongo-3.12.0-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:6b89dc51206e4971c5568c797991eaaef5dc2a6118d67165858ad11752dba055"}, - {file = "pymongo-3.12.0-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:701e08457183da70ed96b35a6b43e6ba1df0b47c837b063cde39a1fbe1aeda81"}, - {file = "pymongo-3.12.0-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:e7a33322e08021c37e89cae8ff06327503e8a1719e97c69f32c31cbf6c30d72c"}, - {file = "pymongo-3.12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd1f49f949a658c4e8f81ed73f9aad25fcc7d4f62f767f591e749e30038c4e1d"}, - {file = "pymongo-3.12.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a6d055f01b83b1a4df8bb0c61983d3bdffa913764488910af3620e5c2450bf83"}, - {file = "pymongo-3.12.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd6ff2192f34bd622883c745a56f492b1c9ccd44e14953e8051c33024a2947d5"}, - {file = "pymongo-3.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19d4bd0fc29aa405bb1781456c9cfff9fceabb68543741eb17234952dbc2bbb0"}, - {file = "pymongo-3.12.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24f8aeec4d6b894a6128844e50ff423dd02462ee83addf503c598ee3a80ddf3d"}, - {file = "pymongo-3.12.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b6055e0ef451ff73c93d0348d122a0750dddf323b9361de5835dac2f6cf7fc1"}, - {file = "pymongo-3.12.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6261bee7c5abadeac7497f8f1c43e521da78dd13b0a2439f526a7b0fc3788824"}, - {file = "pymongo-3.12.0-cp39-cp39-win32.whl", hash = "sha256:2e92aa32300a0b5e4175caec7769f482b292769807024a86d674b3f19b8e3755"}, - {file = "pymongo-3.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:3ce83f17f641a62a4dfb0ba1b8a3c1ced7c842f511b5450d90c030c7828e3693"}, - {file = "pymongo-3.12.0-py2.7-macosx-10.14-intel.egg", hash = "sha256:d1740776b70367277323fafb76bcf09753a5cc9824f5d705bac22a34ff3668ea"}, - {file = "pymongo-3.12.0.tar.gz", hash = "sha256:b88d1742159bc93a078733f9789f563cef26f5e370eba810476a71aa98e5fbc2"}, + {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"}, ] pynput = [ {file = "pynput-1.7.3-py2.py3-none-any.whl", hash = "sha256:fea5777454f896bd79d35393088cd29a089f3b2da166f0848a922b1d5a807d4f"}, @@ -2272,47 +2150,27 @@ pyparsing = [ {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, ] pyrsistent = [ - {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"}, + {file = "pyrsistent-0.17.3.tar.gz", hash = "sha256:2e636185d9eb976a18a8a8e96efce62f2905fea90041958d8cc2a189756ebf3e"}, ] pytest = [ - {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, - {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, + {file = "pytest-6.2.4-py3-none-any.whl", hash = "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"}, + {file = "pytest-6.2.4.tar.gz", hash = "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b"}, ] 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"}, ] pytest-print = [ - {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"}, + {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"}, ] python-dateutil = [ - {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"}, + {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"}, ] python-xlib = [ - {file = "python-xlib-0.31.tar.gz", hash = "sha256:74d83a081f532bc07f6d7afcd6416ec38403d68f68b9b9dc9e1f28fbf2d799e9"}, - {file = "python_xlib-0.31-py2.py3-none-any.whl", hash = "sha256:1ec6ce0de73d9e6592ead666779a5732b384e5b8fb1f1886bd0a81cafa477759"}, + {file = "python-xlib-0.30.tar.gz", hash = "sha256:74131418faf9e7b83178c71d9d80297fbbd678abe99ae9258f5a20cd027acb5f"}, + {file = "python_xlib-0.30-py2.py3-none-any.whl", hash = "sha256:c4c92cd47e07588b2cbc7d52de18407b2902c3812d7cdec39cd2177b060828e2"}, ] python3-xlib = [ {file = "python3-xlib-0.15.tar.gz", hash = "sha256:dc4245f3ae4aa5949c1d112ee4723901ade37a96721ba9645f2bfa56e5b383f8"}, @@ -2338,16 +2196,16 @@ pywin32-ctypes = [ {file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"}, ] "qt.py" = [ - {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"}, + {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"}, ] recommonmark = [ {file = "recommonmark-0.7.1-py2.py3-none-any.whl", hash = "sha256:1b1db69af0231efce3fa21b94ff627ea33dee7079a01dd0a7f8482c3da148b3f"}, {file = "recommonmark-0.7.1.tar.gz", hash = "sha256:bdb4db649f2222dcd8d2d844f0006b958d627f732415d399791ee436a3686d67"}, ] requests = [ - {file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"}, - {file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"}, + {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, + {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, ] rsa = [ {file = "rsa-4.7.2-py3-none-any.whl", hash = "sha256:78f9a9bf4e7be0c5ded4583326e7461e3a3c5aae24073648b4bdfa797d78c9d2"}, @@ -2366,8 +2224,8 @@ six = [ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] slack-sdk = [ - {file = "slack_sdk-3.10.1-py2.py3-none-any.whl", hash = "sha256:f17b71a578e94204d9033bffded634475f4ca0a6274c6c7a4fd8a9cb0ac7cd8b"}, - {file = "slack_sdk-3.10.1.tar.gz", hash = "sha256:2b4dde7728eb4ff5a581025d204578ccff25a5d8f0fe11ae175e3ce6e074434f"}, + {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"}, ] smmap = [ {file = "smmap-4.0.0-py2.py3-none-any.whl", hash = "sha256:a9a7479e4c572e2e775c404dcd3080c8dc49f39918c2cf74913d30c4c478e3c2"}, @@ -2382,8 +2240,8 @@ speedcopy = [ {file = "speedcopy-2.1.0.tar.gz", hash = "sha256:8bb1a6c735900b83901a7be84ba2175ed3887c13c6786f97dea48f2ea7d504c2"}, ] sphinx = [ - {file = "Sphinx-4.1.2-py3-none-any.whl", hash = "sha256:46d52c6cee13fec44744b8c01ed692c18a640f6910a725cbb938bc36e8d64544"}, - {file = "Sphinx-4.1.2.tar.gz", hash = "sha256:3092d929cd807926d846018f2ace47ba2f3b671b309c7a89cd3306e80c826b13"}, + {file = "Sphinx-4.0.2-py3-none-any.whl", hash = "sha256:d1cb10bee9c4231f1700ec2e24a91be3f3a3aba066ea4ca9f3bbe47e59d5a1d4"}, + {file = "Sphinx-4.0.2.tar.gz", hash = "sha256:b5c2ae4120bf00c799ba9b3699bc895816d272d120080fbc967292f29b52b48c"}, ] sphinx-qt-documentation = [ {file = "sphinx_qt_documentation-0.3-py3-none-any.whl", hash = "sha256:bee247cb9e4fc03fc496d07adfdb943100e1103320c3e5e820e0cfa7c790d9b6"}, @@ -2461,17 +2319,17 @@ typed-ast = [ {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, ] typing-extensions = [ - {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"}, + {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"}, ] 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.6-py2.py3-none-any.whl", hash = "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4"}, - {file = "urllib3-1.26.6.tar.gz", hash = "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"}, + {file = "urllib3-1.26.5-py2.py3-none-any.whl", hash = "sha256:753a0374df26658f99d826cfe40394a686d05985786d946fbe4165b5148f5a7c"}, + {file = "urllib3-1.26.5.tar.gz", hash = "sha256:a7acd0977125325f516bda9735fa7142b909a8d01e8b2e4c8108d0984e6e0098"}, ] wcwidth = [ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, @@ -2528,6 +2386,6 @@ yarl = [ {file = "yarl-1.6.3.tar.gz", hash = "sha256:8a9066529240171b68893d60dca86a763eae2139dd42f42106b03cf4b426bf10"}, ] zipp = [ - {file = "zipp-3.5.0-py3-none-any.whl", hash = "sha256:957cfda87797e389580cb8b9e3870841ca991e2125350677b2ca83a0e99390a3"}, - {file = "zipp-3.5.0.tar.gz", hash = "sha256:f5812b1e007e48cff63449a5e9f4e7ebea716b4111f9c4f9a645f91d579bf0c4"}, + {file = "zipp-3.4.1-py3-none-any.whl", hash = "sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098"}, + {file = "zipp-3.4.1.tar.gz", hash = "sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76"}, ] From 7a34b728e33ba709faee0e2d3767aeac2b419109 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Sep 2021 19:18:34 +0200 Subject: [PATCH 041/279] change cx freeze --- poetry.lock | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/poetry.lock b/poetry.lock index e43c788d74..357623b74d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -272,15 +272,20 @@ test = ["pytest (>=6.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pret [[package]] name = "cx-freeze" -version = "6.6" +version = "6.7" description = "Create standalone executables from Python scripts" category = "dev" optional = false python-versions = ">=3.6" [package.dependencies] -cx-Logging = {version = ">=3.0", markers = "sys_platform == \"win32\""} -importlib-metadata = ">=3.1.1" +cx-logging = {version = ">=3.0", markers = "sys_platform == \"win32\""} +importlib-metadata = ">=4.3.1" + +[package.source] +type = "legacy" +url = "https://distribute.openpype.io/wheels" +reference = "openpype" [[package]] name = "cx-logging" @@ -1668,17 +1673,7 @@ cryptography = [ {file = "cryptography-3.4.7-pp37-pypy37_pp73-manylinux2014_x86_64.whl", hash = "sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9"}, {file = "cryptography-3.4.7.tar.gz", hash = "sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713"}, ] -cx-freeze = [ - {file = "cx_Freeze-6.6-cp36-cp36m-win32.whl", hash = "sha256:b3d3a6bcd1a07c50b4e1c907f14842642156110e63a99cd5c73b8a24751e9b97"}, - {file = "cx_Freeze-6.6-cp36-cp36m-win_amd64.whl", hash = "sha256:1935266ec644ea4f7e584985f44cefc0622a449a09980d990833a1a2afcadac8"}, - {file = "cx_Freeze-6.6-cp37-cp37m-win32.whl", hash = "sha256:1eac2b0f254319cc641ce25bd83337effd7936092562fde701f3ffb40e0274ec"}, - {file = "cx_Freeze-6.6-cp37-cp37m-win_amd64.whl", hash = "sha256:2bc46ef6d510811b6002f34a3ae4cbfdea44e18644febd2a404d3ee8e48a9fc4"}, - {file = "cx_Freeze-6.6-cp38-cp38-win32.whl", hash = "sha256:46eb50ebc46f7ae236d16c6a52671ab0f7bb479bea668da19f4b6de3cc413e9e"}, - {file = "cx_Freeze-6.6-cp38-cp38-win_amd64.whl", hash = "sha256:8c3b00476ce385bb58595bffce55aed031e5a6e16ab6e14d8bee9d1d569e46c3"}, - {file = "cx_Freeze-6.6-cp39-cp39-win32.whl", hash = "sha256:6e9340cbcf52d4836980ecc83ddba4f7704ff6654dd41168c146b74f512977ce"}, - {file = "cx_Freeze-6.6-cp39-cp39-win_amd64.whl", hash = "sha256:2fcf1c8b77ae5c06f45be3a9aff79e1dd808c0d624e97561f840dec5ea9b214a"}, - {file = "cx_Freeze-6.6.tar.gz", hash = "sha256:c4af8ad3f7e7d71e291c1dec5d0fb26bbe92df834b098ed35434c901fbd6762f"}, -] +cx-freeze = [] cx-logging = [ {file = "cx_Logging-3.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:9fcd297e5c51470521c47eff0f86ba844aeca6be97e13c3e2114ebdf03fa3c96"}, {file = "cx_Logging-3.0-cp36-cp36m-win32.whl", hash = "sha256:0df4be47c5022cc54316949e283403214568ef599817ced0c0972183d6d4fabb"}, From 0e9ece13e97fc9abec2ad82276db423fd0cdd476 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Sep 2021 19:18:50 +0200 Subject: [PATCH 042/279] define requests version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9cbf4e5383..8d329cc2b2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,6 +62,7 @@ jinxed = [ python3-xlib = { version="*", markers = "sys_platform == 'linux'"} enlighten = "^1.9.0" slack-sdk = "^3.6.0" +requests = "2.25.1" [tool.poetry.dev-dependencies] flake8 = "^3.7" @@ -86,7 +87,6 @@ wheel = "*" enlighten = "*" # cool terminal progress bars toml = "^0.10.2" # for parsing pyproject.toml - [tool.poetry.urls] "Bug Tracker" = "https://github.com/pypeclub/openpype/issues" "Discussions" = "https://github.com/pypeclub/openpype/discussions" From 3cff1e38a870cc13bc343c39437c9b92e08c704a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 23 Sep 2021 15:59:07 +0200 Subject: [PATCH 043/279] added info about thirdparty python modules and about PySide2 --- website/docs/dev_build.md | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/website/docs/dev_build.md b/website/docs/dev_build.md index f71118eba6..bf606ae7c0 100644 --- a/website/docs/dev_build.md +++ b/website/docs/dev_build.md @@ -19,6 +19,7 @@ We use [CX_Freeze](https://cx-freeze.readthedocs.io/en/latest) to freeze the cod This is outline of build steps. Most of them are done automatically via scripts: - Virtual environment is created using **Poetry** in `.venv` +- Necessary python modules outside of `.venv` are stored to `./vendor/python` (like `PySide2`) - Necessary third-party tools (like [ffmpeg](https://www.ffmpeg.org/), [OpenImageIO](https://github.com/OpenImageIO/oiio) and [usd libraries](https://developer.nvidia.com/usd)) are downloaded to `./vendor/bin` - OpenPype code is frozen with **cx_freeze** to `./build` @@ -55,14 +56,14 @@ For development purposes it is possible to run OpenPype directly from the source To start OpenPype from source you need to 1. Run `.\tools\create_env.ps1` to create virtual environment in `.venv` -2. Run `.\tools\fetch_thirdparty_libs.ps1` to get **ffmpeg**, **oiio** and other tools needed. +2. Run `.\tools\fetch_thirdparty_libs.ps1` to get **PySide2**, **ffmpeg**, **oiio** and other tools needed. 3. Run `.\tools\run_tray.ps1` if you have all required dependencies on your machine you should be greeted with OpenPype igniter window and once you give it your Mongo URL, with OpenPype icon in the system tray. Step 1 and 2 needs to be run only once (or when something was changed). #### To build OpenPype: 1. Run `.\tools\create_env.ps1` to create virtual environment in `.venv` -2. Run `.\tools\fetch_thirdparty_libs.ps1` to get **ffmpeg**, **oiio** and other tools needed. +2. Run `.\tools\fetch_thirdparty_libs.ps1` to get **PySide2**, **ffmpeg**, **oiio** and other tools needed. 3. `.\tools\build.ps1` to build OpenPype to `.\build` @@ -185,7 +186,7 @@ For more information about setting your build environment please refer to [pyenv #### To build Pype: 1. Run `./tools/create_env.sh` to create virtual environment in `./venv` -2. Run `./tools/fetch_thirdparty_libs.sh` to get **ffmpeg**, **oiio** and other tools needed. +2. Run `./tools/fetch_thirdparty_libs.sh` to get **PySide2**, **ffmpeg**, **oiio** and other tools needed. 3. Run `./tools/build.sh` to build pype executables in `.\build\` @@ -273,6 +274,19 @@ pywin32 = { version = "300", markers = "sys_platform == 'win32'" } For more information see [Poetry documentation](https://python-poetry.org/docs/dependency-specification/). +### Python modules as thirdparty +There are some python modules that can be available only in OpenPype and should not be propagated to any subprocess. +Best example is **PySide2** which is required to run OpenPype but can be used only in OpenPype and should not be in PYTHONPATH for most of host applications. +We've decided to separate these breaking dependencies to be able run OpenPype from code and from build the same way. + +:::warning +**PySide2** has handled special cases related to it's build process. +### Linux +- We're fixing rpath of shared objects on linux which is modified during cx freeze processing. +### MacOS +- **QtSql** libraries are removed on MacOS because their dependencies are not available and would require to modify rpath of Postgre library. +::: + ### Binary dependencies To add some binary tool or something that doesn't fit standard Python distribution methods, you can use [fetch_thirdparty_libs](#fetch_thirdparty_libs) script. It will take things defined in From b7fee7f332a76d9d1249b5e1853828b2947e5220 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 23 Sep 2021 16:03:25 +0200 Subject: [PATCH 044/279] remove sql drivers on mac --- tools/fetch_thirdparty_libs.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/tools/fetch_thirdparty_libs.py b/tools/fetch_thirdparty_libs.py index 803d092186..b616beab27 100644 --- a/tools/fetch_thirdparty_libs.py +++ b/tools/fetch_thirdparty_libs.py @@ -78,18 +78,28 @@ except AttributeError: _print("No PySide2 version was specified, using latest available.", 2) pyside2_arg = "PySide2" if not pyside2_version else "PySide2{}".format(pyside2_version) # noqa: E501 +python_vendor_dir = openpype_root / "vendor" / "python" try: subprocess.run( [sys.executable, "-m", "pip", "install", "--upgrade", - pyside2_arg, "-t", str(openpype_root / "vendor/python")], + pyside2_arg, "-t", str(python_vendor_dir)], check=True, stdout=subprocess.DEVNULL) except subprocess.CalledProcessError as e: _print("Error during PySide2 installation.", 1) _print(str(e), 1) sys.exit(1) -_print("Processing third-party dependencies ...") +# Remove libraries for QtSql which don't have available libraries +# by default and Postgre library would require to modify rpath of dependency platform_name = platform.system().lower() +if platform_name == "darwin": + pyside2_sqldrivers_dir = ( + python_vendor_dir / "PySide2" / "Qt" / "plugins" / "sqldrivers" + ) + for filepath in pyside2_sqldrivers_dir.iterdir(): + os.remove(str(filepath)) + +_print("Processing third-party dependencies ...") try: thirdparty = pyproject["openpype"]["thirdparty"] From 8c27675b4382ee8277b6c325f7d3b912b684752c Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 23 Sep 2021 16:26:59 +0100 Subject: [PATCH 045/279] Implemented camera publishing in Unreal and loading in Blender --- .../{load_camera.py => load_camera_blend.py} | 2 +- .../blender/plugins/load/load_camera_fbx.py | 218 ++++++++++++++++++ .../blender/plugins/publish/extract_camera.py | 6 +- .../unreal/plugins/create/create_camera.py | 56 +++++ .../unreal/plugins/publish/extract_camera.py | 55 +++++ 5 files changed, 333 insertions(+), 4 deletions(-) rename openpype/hosts/blender/plugins/load/{load_camera.py => load_camera_blend.py} (99%) create mode 100644 openpype/hosts/blender/plugins/load/load_camera_fbx.py create mode 100644 openpype/hosts/unreal/plugins/create/create_camera.py create mode 100644 openpype/hosts/unreal/plugins/publish/extract_camera.py diff --git a/openpype/hosts/blender/plugins/load/load_camera.py b/openpype/hosts/blender/plugins/load/load_camera_blend.py similarity index 99% rename from openpype/hosts/blender/plugins/load/load_camera.py rename to openpype/hosts/blender/plugins/load/load_camera_blend.py index 30300100e0..6c3348a1a6 100644 --- a/openpype/hosts/blender/plugins/load/load_camera.py +++ b/openpype/hosts/blender/plugins/load/load_camera_blend.py @@ -23,7 +23,7 @@ class BlendCameraLoader(openpype.hosts.blender.api.plugin.AssetLoader): families = ["camera"] representations = ["blend"] - label = "Link Camera" + label = "Link Camera (Blend)" icon = "code-fork" color = "orange" diff --git a/openpype/hosts/blender/plugins/load/load_camera_fbx.py b/openpype/hosts/blender/plugins/load/load_camera_fbx.py new file mode 100644 index 0000000000..88a8931784 --- /dev/null +++ b/openpype/hosts/blender/plugins/load/load_camera_fbx.py @@ -0,0 +1,218 @@ +"""Load an asset in Blender from an Alembic file.""" + +from pathlib import Path +from pprint import pformat +from typing import Dict, List, Optional + +import bpy + +from avalon import api +from avalon.blender import lib +from avalon.blender.pipeline import AVALON_CONTAINERS +from avalon.blender.pipeline import AVALON_CONTAINER_ID +from avalon.blender.pipeline import AVALON_PROPERTY +from openpype.hosts.blender.api import plugin + + +class FbxCameraLoader(plugin.AssetLoader): + """Load a camera from FBX. + + Stores the imported asset in an empty named after the asset. + """ + + families = ["camera"] + representations = ["fbx"] + + label = "Load Camera (FBX)" + icon = "code-fork" + color = "orange" + + def _remove(self, asset_group): + objects = list(asset_group.children) + + for obj in objects: + if obj.type == 'CAMERA': + bpy.data.cameras.remove(obj.data) + elif obj.type == 'EMPTY': + objects.extend(obj.children) + bpy.data.objects.remove(obj) + + def _process(self, libpath, asset_group, group_name): + bpy.ops.object.select_all(action='DESELECT') + + collection = bpy.context.view_layer.active_layer_collection.collection + + bpy.ops.import_scene.fbx(filepath=libpath) + + parent = bpy.context.scene.collection + + objects = lib.get_selection() + + for obj in objects: + obj.parent = asset_group + + for obj in objects: + parent.objects.link(obj) + collection.objects.unlink(obj) + + for obj in objects: + name = obj.name + obj.name = f"{group_name}:{name}" + if obj.type != 'EMPTY': + name_data = obj.data.name + obj.data.name = f"{group_name}:{name_data}" + + if not obj.get(AVALON_PROPERTY): + obj[AVALON_PROPERTY] = dict() + + avalon_info = obj[AVALON_PROPERTY] + avalon_info.update({"container_name": group_name}) + + bpy.ops.object.select_all(action='DESELECT') + + return objects + + def process_asset( + self, context: dict, name: str, namespace: Optional[str] = None, + options: Optional[Dict] = None + ) -> Optional[List]: + """ + Arguments: + name: Use pre-defined name + namespace: Use pre-defined namespace + context: Full parenthood of representation to load + options: Additional settings dictionary + """ + libpath = self.fname + asset = context["asset"]["name"] + subset = context["subset"]["name"] + + asset_name = plugin.asset_name(asset, subset) + unique_number = plugin.get_unique_number(asset, subset) + group_name = plugin.asset_name(asset, subset, unique_number) + namespace = namespace or f"{asset}_{unique_number}" + + avalon_container = bpy.data.collections.get(AVALON_CONTAINERS) + if not avalon_container: + avalon_container = bpy.data.collections.new(name=AVALON_CONTAINERS) + bpy.context.scene.collection.children.link(avalon_container) + + asset_group = bpy.data.objects.new(group_name, object_data=None) + avalon_container.objects.link(asset_group) + + objects = self._process(libpath, asset_group, group_name) + + objects = [] + nodes = list(asset_group.children) + + for obj in nodes: + objects.append(obj) + nodes.extend(list(obj.children)) + + bpy.context.scene.collection.objects.link(asset_group) + + asset_group[AVALON_PROPERTY] = { + "schema": "openpype:container-2.0", + "id": AVALON_CONTAINER_ID, + "name": name, + "namespace": namespace or '', + "loader": str(self.__class__.__name__), + "representation": str(context["representation"]["_id"]), + "libpath": libpath, + "asset_name": asset_name, + "parent": str(context["representation"]["parent"]), + "family": context["representation"]["context"]["family"], + "objectName": group_name + } + + self[:] = objects + return objects + + def exec_update(self, container: Dict, representation: Dict): + """Update the loaded asset. + + This will remove all objects of the current collection, load the new + ones and add them to the collection. + If the objects of the collection are used in another collection they + will not be removed, only unlinked. Normally this should not be the + case though. + + Warning: + No nested collections are supported at the moment! + """ + object_name = container["objectName"] + asset_group = bpy.data.objects.get(object_name) + libpath = Path(api.get_representation_path(representation)) + extension = libpath.suffix.lower() + + self.log.info( + "Container: %s\nRepresentation: %s", + pformat(container, indent=2), + pformat(representation, indent=2), + ) + + assert asset_group, ( + f"The asset is not loaded: {container['objectName']}" + ) + assert libpath, ( + "No existing library file found for {container['objectName']}" + ) + assert libpath.is_file(), ( + f"The file doesn't exist: {libpath}" + ) + assert extension in plugin.VALID_EXTENSIONS, ( + f"Unsupported file: {libpath}" + ) + + metadata = asset_group.get(AVALON_PROPERTY) + group_libpath = metadata["libpath"] + + normalized_group_libpath = ( + str(Path(bpy.path.abspath(group_libpath)).resolve()) + ) + normalized_libpath = ( + str(Path(bpy.path.abspath(str(libpath))).resolve()) + ) + self.log.debug( + "normalized_group_libpath:\n %s\nnormalized_libpath:\n %s", + normalized_group_libpath, + normalized_libpath, + ) + if normalized_group_libpath == normalized_libpath: + self.log.info("Library already loaded, not updating...") + return + + mat = asset_group.matrix_basis.copy() + + self._remove(asset_group) + self._process(str(libpath), asset_group, object_name, action) + + asset_group.matrix_basis = mat + + metadata["libpath"] = str(libpath) + metadata["representation"] = str(representation["_id"]) + + def exec_remove(self, container: Dict) -> bool: + """Remove an existing container from a Blender scene. + + Arguments: + container (openpype:container-1.0): Container to remove, + from `host.ls()`. + + Returns: + bool: Whether the container was deleted. + + Warning: + No nested collections are supported at the moment! + """ + object_name = container["objectName"] + asset_group = bpy.data.objects.get(object_name) + + if not asset_group: + return False + + self._remove(asset_group) + + bpy.data.objects.remove(asset_group) + + return True diff --git a/openpype/hosts/blender/plugins/publish/extract_camera.py b/openpype/hosts/blender/plugins/publish/extract_camera.py index 3523498808..f7e128f227 100644 --- a/openpype/hosts/blender/plugins/publish/extract_camera.py +++ b/openpype/hosts/blender/plugins/publish/extract_camera.py @@ -6,10 +6,10 @@ from openpype.hosts.blender.api import plugin import bpy -class ExtractFBX(api.Extractor): - """Extract as FBX.""" +class ExtractCamera(api.Extractor): + """Extract as the camera as FBX.""" - label = "Extract FBX" + label = "Extract Camera" hosts = ["blender"] families = ["camera"] optional = True diff --git a/openpype/hosts/unreal/plugins/create/create_camera.py b/openpype/hosts/unreal/plugins/create/create_camera.py new file mode 100644 index 0000000000..74a3c2876b --- /dev/null +++ b/openpype/hosts/unreal/plugins/create/create_camera.py @@ -0,0 +1,56 @@ +import unreal +from unreal import EditorAssetLibrary as eal +from unreal import EditorLevelLibrary as ell + +from openpype.hosts.unreal.api.plugin import Creator +from avalon.unreal import ( + instantiate, +) + + +class CreateCamera(Creator): + """Layout output for character rigs""" + + name = "layoutMain" + label = "Camera" + family = "camera" + icon = "cubes" + + root = "/Game/Avalon/Instances" + suffix = "_INS" + + def __init__(self, *args, **kwargs): + super(CreateCamera, self).__init__(*args, **kwargs) + + def process(self): + data = self.data + + name = data["subset"] + + # selection = [] + # # if (self.options or {}).get("useSelection"): + # # sel_objects = unreal.EditorUtilityLibrary.get_selected_assets() + # # selection = [a.get_path_name() for a in sel_objects] + + # data["level"] = ell.get_editor_world().get_path_name() + + data["level"] = ell.get_editor_world().get_path_name() + + # if (self.options or {}).get("useSelection"): + # # Set as members the selected actors + # for actor in ell.get_selected_level_actors(): + # data["members"].append("{}.{}".format( + # actor.get_outer().get_name(), actor.get_name())) + + if not eal.does_directory_exist(self.root): + eal.make_directory(self.root) + + factory = unreal.LevelSequenceFactoryNew() + tools = unreal.AssetToolsHelpers().get_asset_tools() + asset = tools.create_asset(name, f"{self.root}/{name}", None, factory) + + asset_name = f"{self.root}/{name}/{name}.{name}" + + data["members"] = [asset_name] + + instantiate(f"{self.root}", name, data, None, self.suffix) diff --git a/openpype/hosts/unreal/plugins/publish/extract_camera.py b/openpype/hosts/unreal/plugins/publish/extract_camera.py new file mode 100644 index 0000000000..fe5326cf02 --- /dev/null +++ b/openpype/hosts/unreal/plugins/publish/extract_camera.py @@ -0,0 +1,55 @@ +import os + +import unreal +from unreal import EditorAssetLibrary as eal +from unreal import EditorLevelLibrary as ell + +import openpype.api +from avalon import io + + +class ExtractCamera(openpype.api.Extractor): + """Extract a camera.""" + + label = "Extract Camera" + hosts = ["unreal"] + families = ["camera"] + optional = True + + def process(self, instance): + # Define extract output file path + stagingdir = self.staging_dir(instance) + fbx_filename = "{}.fbx".format(instance.name) + + # Perform extraction + self.log.info("Performing extraction..") + + # Check if the loaded level is the same of the instance + current_level = ell.get_editor_world().get_path_name() + assert current_level == instance.data.get("level"), \ + "Wrong level loaded" + + for member in instance[:]: + data = eal.find_asset_data(member) + if data.asset_class == "LevelSequence": + ar = unreal.AssetRegistryHelpers.get_asset_registry() + sequence = ar.get_asset_by_object_path(member).get_asset() + unreal.SequencerTools.export_fbx( + ell.get_editor_world(), + sequence, + sequence.get_bindings(), + unreal.FbxExportOption(), + os.path.join(stagingdir, fbx_filename) + ) + break + + if "representations" not in instance.data: + instance.data["representations"] = [] + + fbx_representation = { + 'name': 'fbx', + 'ext': 'fbx', + 'files': fbx_filename, + "stagingDir": stagingdir, + } + instance.data["representations"].append(fbx_representation) From abc03a402a498b35c43e2ab2940171bafe5a04c7 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 23 Sep 2021 16:30:54 +0100 Subject: [PATCH 046/279] Fix: camera was created once per asset when layout was loaded --- .../blender/plugins/load/load_layout_json.py | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/openpype/hosts/blender/plugins/load/load_layout_json.py b/openpype/hosts/blender/plugins/load/load_layout_json.py index 0b3e69ada8..dac74ec5f0 100644 --- a/openpype/hosts/blender/plugins/load/load_layout_json.py +++ b/openpype/hosts/blender/plugins/load/load_layout_json.py @@ -106,20 +106,20 @@ class JsonLayoutLoader(plugin.AssetLoader): options=options ) - # Create the camera asset and the camera instance - creator_plugin = lib.get_creator_by_name("CreateCamera") - if not creator_plugin: - raise ValueError("Creator plugin \"CreateCamera\" was " - "not found.") + # Create the camera asset and the camera instance + creator_plugin = lib.get_creator_by_name("CreateCamera") + if not creator_plugin: + raise ValueError("Creator plugin \"CreateCamera\" was " + "not found.") - api.create( - creator_plugin, - name="camera", - # name=f"{unique_number}_{subset}_animation", - asset=asset, - options={"useSelection": False} - # data={"dependencies": str(context["representation"]["_id"])} - ) + api.create( + creator_plugin, + name="camera", + # name=f"{unique_number}_{subset}_animation", + asset=asset, + options={"useSelection": False} + # data={"dependencies": str(context["representation"]["_id"])} + ) def process_asset(self, context: dict, From c1eeee9fdc81a255f84dd98338d90ea818e2ec77 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 23 Sep 2021 16:49:51 +0100 Subject: [PATCH 047/279] Apply suggestions from code review --- openpype/hosts/unreal/hooks/pre_workfile_preparation.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/unreal/hooks/pre_workfile_preparation.py b/openpype/hosts/unreal/hooks/pre_workfile_preparation.py index 0c7146634f..880dba5cfb 100644 --- a/openpype/hosts/unreal/hooks/pre_workfile_preparation.py +++ b/openpype/hosts/unreal/hooks/pre_workfile_preparation.py @@ -29,9 +29,10 @@ class UnrealPrelaunchHook(PreLaunchHook): def _get_work_filename(self): # Use last workfile if was found - last_workfile = self.data.get("last_workfile_path") - if last_workfile and os.path.exists(last_workfile): - return os.path.basename(last_workfile) + if self.data.get("last_workfile_path"): + last_workfile = Path(self.data.get("last_workfile_path")) + if last_workfile and last_workfile.exists(): + return last_workfile.name # Prepare data for fill data and for getting workfile template key task_name = self.data["task_name"] @@ -91,7 +92,7 @@ class UnrealPrelaunchHook(PreLaunchHook): # of the project name. This is because project name is then used # in various places inside c++ code and there variable names cannot # start with non-alpha. We append 'P' before project name to solve it. - # 😱 + # 😱 if not unreal_project_name[:1].isalpha(): self.log.warning(( "Project name doesn't start with alphabet " From fe0d4e8347a2c7a32ee700ca8de3fae9a2dd84be Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 23 Sep 2021 16:56:33 +0100 Subject: [PATCH 048/279] Hound fixes --- .../hosts/blender/plugins/load/load_camera_fbx.py | 2 +- .../blender/plugins/load/load_layout_json.py | 2 +- .../hosts/unreal/plugins/create/create_camera.py | 15 +-------------- .../unreal/plugins/publish/extract_camera.py | 1 - 4 files changed, 3 insertions(+), 17 deletions(-) diff --git a/openpype/hosts/blender/plugins/load/load_camera_fbx.py b/openpype/hosts/blender/plugins/load/load_camera_fbx.py index 88a8931784..c6d491870d 100644 --- a/openpype/hosts/blender/plugins/load/load_camera_fbx.py +++ b/openpype/hosts/blender/plugins/load/load_camera_fbx.py @@ -185,7 +185,7 @@ class FbxCameraLoader(plugin.AssetLoader): mat = asset_group.matrix_basis.copy() self._remove(asset_group) - self._process(str(libpath), asset_group, object_name, action) + self._process(str(libpath), asset_group, object_name) asset_group.matrix_basis = mat diff --git a/openpype/hosts/blender/plugins/load/load_layout_json.py b/openpype/hosts/blender/plugins/load/load_layout_json.py index dac74ec5f0..9c7a75ad7f 100644 --- a/openpype/hosts/blender/plugins/load/load_layout_json.py +++ b/openpype/hosts/blender/plugins/load/load_layout_json.py @@ -110,7 +110,7 @@ class JsonLayoutLoader(plugin.AssetLoader): creator_plugin = lib.get_creator_by_name("CreateCamera") if not creator_plugin: raise ValueError("Creator plugin \"CreateCamera\" was " - "not found.") + "not found.") api.create( creator_plugin, diff --git a/openpype/hosts/unreal/plugins/create/create_camera.py b/openpype/hosts/unreal/plugins/create/create_camera.py index 74a3c2876b..eda2b52be3 100644 --- a/openpype/hosts/unreal/plugins/create/create_camera.py +++ b/openpype/hosts/unreal/plugins/create/create_camera.py @@ -27,27 +27,14 @@ class CreateCamera(Creator): name = data["subset"] - # selection = [] - # # if (self.options or {}).get("useSelection"): - # # sel_objects = unreal.EditorUtilityLibrary.get_selected_assets() - # # selection = [a.get_path_name() for a in sel_objects] - - # data["level"] = ell.get_editor_world().get_path_name() - data["level"] = ell.get_editor_world().get_path_name() - # if (self.options or {}).get("useSelection"): - # # Set as members the selected actors - # for actor in ell.get_selected_level_actors(): - # data["members"].append("{}.{}".format( - # actor.get_outer().get_name(), actor.get_name())) - if not eal.does_directory_exist(self.root): eal.make_directory(self.root) factory = unreal.LevelSequenceFactoryNew() tools = unreal.AssetToolsHelpers().get_asset_tools() - asset = tools.create_asset(name, f"{self.root}/{name}", None, factory) + tools.create_asset(name, f"{self.root}/{name}", None, factory) asset_name = f"{self.root}/{name}/{name}.{name}" diff --git a/openpype/hosts/unreal/plugins/publish/extract_camera.py b/openpype/hosts/unreal/plugins/publish/extract_camera.py index fe5326cf02..10862fc0ef 100644 --- a/openpype/hosts/unreal/plugins/publish/extract_camera.py +++ b/openpype/hosts/unreal/plugins/publish/extract_camera.py @@ -5,7 +5,6 @@ from unreal import EditorAssetLibrary as eal from unreal import EditorLevelLibrary as ell import openpype.api -from avalon import io class ExtractCamera(openpype.api.Extractor): From 22664f777d1dece5db6279092549b09303b6d4bf Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 23 Sep 2021 18:04:41 +0200 Subject: [PATCH 049/279] removed not needed packages --- Dockerfile.centos7 | 3 --- 1 file changed, 3 deletions(-) diff --git a/Dockerfile.centos7 b/Dockerfile.centos7 index 67d45ce3b2..f3b257e66b 100644 --- a/Dockerfile.centos7 +++ b/Dockerfile.centos7 @@ -41,9 +41,6 @@ RUN yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.n ncurses \ ncurses-devel \ qt5-qtbase-devel \ - libxcb libxcb-devel \ - xcb-util xcb-util-devel \ - libxkbcommon-devel libxkbcommon-x11-devel \ && yum clean all # we need to build our own patchelf From 5d1915aff068977ed9ccbe9a2779038be8568a18 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Fri, 24 Sep 2021 11:04:14 +0100 Subject: [PATCH 050/279] Fix: problem when loading camera and no existing folders --- openpype/hosts/unreal/plugins/load/load_camera.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/unreal/plugins/load/load_camera.py b/openpype/hosts/unreal/plugins/load/load_camera.py index bf89ecd23e..3b7377f848 100644 --- a/openpype/hosts/unreal/plugins/load/load_camera.py +++ b/openpype/hosts/unreal/plugins/load/load_camera.py @@ -65,7 +65,10 @@ class CameraLoader(api.Loader): # removes the last element (which is a "/"). f_numbers.append(int(f.split("_")[-1][:-1])) f_numbers.sort() - unique_number = f_numbers[-1] + 1 + if not f_numbers: + unique_number = 1 + else: + unique_number = f_numbers[-1] + 1 asset_dir, container_name = tools.create_unique_asset_name( f"{root}/{asset}/{name}_{unique_number:02d}", suffix="") From e382b17622c9ffa7ef0db656272b2be0b270f739 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 6 Oct 2021 11:12:02 +0100 Subject: [PATCH 051/279] SequencerScripting plugin is automatically enabled when creating project This plugin is essential to handle camera assets in Unreal --- openpype/hosts/unreal/api/lib.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/unreal/api/lib.py b/openpype/hosts/unreal/api/lib.py index 7e34c3ff15..c0fafbb667 100644 --- a/openpype/hosts/unreal/api/lib.py +++ b/openpype/hosts/unreal/api/lib.py @@ -253,6 +253,7 @@ def create_unreal_project(project_name: str, "Plugins": [ {"Name": "PythonScriptPlugin", "Enabled": True}, {"Name": "EditorScriptingUtilities", "Enabled": True}, + {"Name": "SequencerScripting", "Enabled": True}, {"Name": "Avalon", "Enabled": True} ] } From 1428963e5f42810b8bf33a3d3fba0c105e0e42ca Mon Sep 17 00:00:00 2001 From: karimmozilla Date: Wed, 6 Oct 2021 13:31:33 +0200 Subject: [PATCH 052/279] crop to reformat --- openpype/hosts/nuke/api/lib.py | 4 ++-- openpype/hosts/nuke/plugins/create/create_write_render.py | 6 +++--- .../nuke/plugins/publish/validate_output_resolution.py | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index ab4c992719..87bf137d93 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -1031,7 +1031,7 @@ class WorkfileSettings(object): log.error(msg) nuke.message(msg) - bbox = self._asset_entity.get('data', {}).get('crop') + bbox = self._asset_entity.get('data', {}).get('reformat') if bbox: try: @@ -1046,7 +1046,7 @@ class WorkfileSettings(object): ) except Exception as e: bbox = None - msg = ("{}:{} \nFormat:Crop need to be set with dots, " + msg = ("{}:{} \nFormat:Reformat need to be set with dots, " "example: 0.0.1920.1080, " "/nSetting to default").format(__name__, e) log.error(msg) diff --git a/openpype/hosts/nuke/plugins/create/create_write_render.py b/openpype/hosts/nuke/plugins/create/create_write_render.py index a1381122ee..3a68ce4035 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_render.py +++ b/openpype/hosts/nuke/plugins/create/create_write_render.py @@ -99,7 +99,7 @@ class CreateWriteRender(plugin.PypeCreator): "fpath_template": ("{work}/renders/nuke/{subset}" "/{subset}.{frame}.{ext}")}) - # add crop node to cut off all outside of format bounding box + # add reformat node to cut off all outside of format bounding box # get width and height try: width, height = (selected_node.width(), selected_node.height()) @@ -109,8 +109,8 @@ class CreateWriteRender(plugin.PypeCreator): _prenodes = [ { - "name": "Crop01", - "class": "Crop", + "name": "Reformat01", + "class": "Reformat", "knobs": [ ("box", [ 0.0, diff --git a/openpype/hosts/nuke/plugins/publish/validate_output_resolution.py b/openpype/hosts/nuke/plugins/publish/validate_output_resolution.py index 2563ee929f..27094b8d74 100644 --- a/openpype/hosts/nuke/plugins/publish/validate_output_resolution.py +++ b/openpype/hosts/nuke/plugins/publish/validate_output_resolution.py @@ -56,8 +56,8 @@ class ValidateOutputResolution(pyblish.api.InstancePlugin): def process(self, instance): - # Skip bounding box check if a crop node exists. - if instance[0].dependencies()[0].Class() == "Crop": + # Skip bounding box check if a reformat node exists. + if instance[0].dependencies()[0].Class() == "Reformat": return msg = "Bounding box is outside the format." From cb4dc83fd84aebe87a3dd863556d0a790d7d8bdf Mon Sep 17 00:00:00 2001 From: karimmozilla Date: Wed, 6 Oct 2021 13:49:59 +0200 Subject: [PATCH 053/279] reformat knobs --- .../hosts/nuke/plugins/create/create_write_render.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/nuke/plugins/create/create_write_render.py b/openpype/hosts/nuke/plugins/create/create_write_render.py index 3a68ce4035..acc1ee53ff 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_render.py +++ b/openpype/hosts/nuke/plugins/create/create_write_render.py @@ -112,12 +112,10 @@ class CreateWriteRender(plugin.PypeCreator): "name": "Reformat01", "class": "Reformat", "knobs": [ - ("box", [ - 0.0, - 0.0, - width, - height - ]) + ("type", 1), + ("box_fixed", 1), + ("box_width", width), + ("box_height", height) ], "dependent": None } From efe8915842d5c07a5c049b220612b2d3171857fb Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 6 Oct 2021 16:20:49 +0100 Subject: [PATCH 054/279] Changed fbx settings for extraction of the camera --- openpype/hosts/blender/plugins/publish/extract_camera.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/blender/plugins/publish/extract_camera.py b/openpype/hosts/blender/plugins/publish/extract_camera.py index f7e128f227..7888ddad6a 100644 --- a/openpype/hosts/blender/plugins/publish/extract_camera.py +++ b/openpype/hosts/blender/plugins/publish/extract_camera.py @@ -50,7 +50,8 @@ class ExtractCamera(api.Extractor): filepath=filepath, use_active_collection=False, use_selection=True, - object_types={'CAMERA'} + object_types={'CAMERA'}, + bake_anim_simplify_factor=0.0 ) bpy.context.scene.unit_settings.scale_length = scale_length From 31c46152899d895fbc6293779fe0c691a189bf14 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 6 Oct 2021 16:22:05 +0100 Subject: [PATCH 055/279] Use info from database for fps and frame range when loading Camera --- openpype/hosts/unreal/plugins/load/load_camera.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/unreal/plugins/load/load_camera.py b/openpype/hosts/unreal/plugins/load/load_camera.py index 3b7377f848..ea55c491e2 100644 --- a/openpype/hosts/unreal/plugins/load/load_camera.py +++ b/openpype/hosts/unreal/plugins/load/load_camera.py @@ -1,6 +1,6 @@ import os -from avalon import api, pipeline +from avalon import api, io, pipeline from avalon.unreal import lib from avalon.unreal import pipeline as unreal_pipeline import unreal @@ -84,6 +84,19 @@ class CameraLoader(api.Loader): factory=unreal.LevelSequenceFactoryNew() ) + asset_name = io.Session["AVALON_ASSET"] + asset_doc = io.find_one({ + "type": "asset", + "name": asset_name + }) + + data = asset_doc.get("data") + + if data: + sequence.set_display_rate(unreal.FrameRate(data.get("fps"), 1.0)) + sequence.set_playback_start(data.get("frameStart")) + sequence.set_playback_end(data.get("frameEnd")) + settings = unreal.MovieSceneUserImportFBXSettings() unreal.SequencerTools.import_fbx( From 947139f425218c51618e09d7d1de748521b03039 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Wed, 6 Oct 2021 18:14:37 +0200 Subject: [PATCH 056/279] downgrade setuptools --- tools/create_env.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tools/create_env.sh b/tools/create_env.sh index 4ed6412c43..6fe6cdafb9 100755 --- a/tools/create_env.sh +++ b/tools/create_env.sh @@ -188,10 +188,11 @@ main () { # reinstall these because of bug in poetry? or cx_freeze? # cx_freeze will crash on missing __pychache__ on these but # reinstalling them solves the problem. - echo -e "${BIGreen}>>>${RST} Fixing pycache bug ..." - "$POETRY_HOME/bin/poetry" run pip install --disable-pip-version-check --force-reinstall setuptools + echo -e "${BIGreen}>>>${RST} Post-venv creation fixes ..." + "$POETRY_HOME/bin/poetry" run pip install setuptools==49.6.0 "$POETRY_HOME/bin/poetry" run pip install --disable-pip-version-check --force-reinstall wheel "$POETRY_HOME/bin/poetry" run python -m pip install --disable-pip-version-check --force-reinstall pip + "$POETRY_HOME/bin/poetry" run pip install --disable-pip-version-check --force-reinstall cx_freeze } return_code=0 From 4d612931ef6295ef848ba11f3894c452115e4f6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 7 Oct 2021 09:46:16 +0200 Subject: [PATCH 057/279] pyproject parser for shell scripts --- tools/parse_pyproject.py | 52 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 tools/parse_pyproject.py diff --git a/tools/parse_pyproject.py b/tools/parse_pyproject.py new file mode 100644 index 0000000000..0aed321fcf --- /dev/null +++ b/tools/parse_pyproject.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +"""Parse pyproject.toml and return its values. + +Useful for shell scripts to know more about OpenPype build. +""" +import os +import blessed +import toml +from pathlib import Path +import click + +term = blessed.Terminal() + + +def _print(msg: str, message_type: int = 0) -> None: + """Print message to console. + + Args: + msg (str): message to print + message_type (int): type of message (0 info, 1 error, 2 note) + + """ + if message_type == 0: + header = term.aquamarine3(">>> ") + elif message_type == 1: + header = term.orangered2("!!! ") + elif message_type == 2: + header = term.tan1("... ") + else: + header = term.darkolivegreen3("--- ") + + print("{}{}".format(header, msg)) + + +@click.command() +@click.argument("key", nargs=-1, type=click.STRING) +def main(key): + _print("Reading build metadata ...") + openpype_root = Path(os.path.dirname(__file__)).parent + py_project = toml.load(openpype_root / "pyproject.toml") + query = key.split(".") + data = py_project + for k in query: + if isinstance(data, dict): + data = data.get(k) + else: + break + print(data) + + +if __name__ == "__main__": + main() From ff8b69f09413c0cc11090689d6a67f7aac8904d5 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 7 Oct 2021 11:10:38 +0100 Subject: [PATCH 058/279] Get frame range and fps from database for update as well --- .../hosts/unreal/plugins/load/load_camera.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/unreal/plugins/load/load_camera.py b/openpype/hosts/unreal/plugins/load/load_camera.py index ea55c491e2..afacb92a05 100644 --- a/openpype/hosts/unreal/plugins/load/load_camera.py +++ b/openpype/hosts/unreal/plugins/load/load_camera.py @@ -84,10 +84,10 @@ class CameraLoader(api.Loader): factory=unreal.LevelSequenceFactoryNew() ) - asset_name = io.Session["AVALON_ASSET"] + io_asset = io.Session["AVALON_ASSET"] asset_doc = io.find_one({ "type": "asset", - "name": asset_name + "name": io_asset }) data = asset_doc.get("data") @@ -167,6 +167,19 @@ class CameraLoader(api.Loader): factory=unreal.LevelSequenceFactoryNew() ) + io_asset = io.Session["AVALON_ASSET"] + asset_doc = io.find_one({ + "type": "asset", + "name": io_asset + }) + + data = asset_doc.get("data") + + if data: + sequence.set_display_rate(unreal.FrameRate(data.get("fps"), 1.0)) + sequence.set_playback_start(data.get("frameStart")) + sequence.set_playback_end(data.get("frameEnd")) + settings = unreal.MovieSceneUserImportFBXSettings() unreal.SequencerTools.import_fbx( From 0689783f607c55d9ed237be1941e1d3f3a392aa5 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 7 Oct 2021 12:08:12 +0100 Subject: [PATCH 059/279] Updated FBX import settings for the camera in Unreal --- openpype/hosts/unreal/plugins/load/load_camera.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/hosts/unreal/plugins/load/load_camera.py b/openpype/hosts/unreal/plugins/load/load_camera.py index afacb92a05..b2b25eec73 100644 --- a/openpype/hosts/unreal/plugins/load/load_camera.py +++ b/openpype/hosts/unreal/plugins/load/load_camera.py @@ -98,6 +98,7 @@ class CameraLoader(api.Loader): sequence.set_playback_end(data.get("frameEnd")) settings = unreal.MovieSceneUserImportFBXSettings() + settings.set_editor_property('reduce_keys', False) unreal.SequencerTools.import_fbx( unreal.EditorLevelLibrary.get_editor_world(), @@ -181,6 +182,7 @@ class CameraLoader(api.Loader): sequence.set_playback_end(data.get("frameEnd")) settings = unreal.MovieSceneUserImportFBXSettings() + settings.set_editor_property('reduce_keys', False) unreal.SequencerTools.import_fbx( unreal.EditorLevelLibrary.get_editor_world(), From 1f2225742304f213528182e8a34bc5e09a0b3383 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 7 Oct 2021 16:25:11 +0100 Subject: [PATCH 060/279] Added validator for camera animations to have a keyframe at frame 0 Unreal shifts the first keyframe to frame 0. Forcing the camera to have a keyframe at frame 0 will ensure that the animation will be the same in Unreal and Blender. --- .../publish/validate_camera_zero_keyframe.py | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py diff --git a/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py b/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py new file mode 100644 index 0000000000..39b9b67511 --- /dev/null +++ b/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py @@ -0,0 +1,48 @@ +from typing import List + +import mathutils + +import pyblish.api +import openpype.hosts.blender.api.action + + +class ValidateCameraZeroKeyframe(pyblish.api.InstancePlugin): + """Camera must have a keyframe at frame 0. + + Unreal shifts the first keyframe to frame 0. Forcing the camera to have + a keyframe at frame 0 will ensure that the animation will be the same + in Unreal and Blender. + """ + + order = openpype.api.ValidateContentsOrder + hosts = ["blender"] + families = ["camera"] + category = "geometry" + version = (0, 1, 0) + label = "Zero Keyframe" + actions = [openpype.hosts.blender.api.action.SelectInvalidAction] + + _identity = mathutils.Matrix() + + @classmethod + def get_invalid(cls, instance) -> List: + invalid = [] + for obj in [obj for obj in instance]: + if obj.type == "CAMERA": + if obj.animation_data and obj.animation_data.action: + action = obj.animation_data.action + frames_set = set() + for fcu in action.fcurves: + for kp in fcu.keyframe_points: + frames_set.add(kp.co[0]) + frames = list(frames_set) + frames.sort() + if frames[0] != 0.0: + invalid.append(obj) + return invalid + + def process(self, instance): + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError( + f"Object found in instance is not in Object Mode: {invalid}") From 9f9caafb0d3b80e9bf0d2a4e2fae288db7d30056 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 7 Oct 2021 18:18:33 +0200 Subject: [PATCH 061/279] reinstall cx_freeze with downgraded setuptools --- setup.py | 5 ++-- tools/build_dependencies.py | 25 ++++++++++++---- tools/create_env.sh | 4 ++- tools/parse_pyproject.py | 60 +++++++++++++++++-------------------- 4 files changed, 52 insertions(+), 42 deletions(-) diff --git a/setup.py b/setup.py index 55098cb0b4..cd3ed4f82c 100644 --- a/setup.py +++ b/setup.py @@ -59,13 +59,14 @@ includes = [] excludes = [ "openpype" ] -bin_includes = [] +bin_includes = [ + "vendor" +] include_files = [ "igniter", "openpype", "repos", "schema", - "vendor", "LICENSE", "README.md" ] diff --git a/tools/build_dependencies.py b/tools/build_dependencies.py index 1798b7ca8f..75d6e0c315 100644 --- a/tools/build_dependencies.py +++ b/tools/build_dependencies.py @@ -113,7 +113,26 @@ if not build_dir.exists(): _print("Probably freezing of code failed. Check ./build/build.log", 3) sys.exit(1) +def _progress(_base, _names): + progress_bar.update() + return [] + deps_dir = build_dir / "dependencies" +vendor_dir = build_dir / "vendor" +vendor_src = openpype_root / "vendor" + +# copy vendor files +_print("Copying vendor files ...") + +total_files = count_folders(vendor_src) +progress_bar = enlighten.Counter( + total=total_files, desc="Copying vendor files ...", + units="%", color=(64, 128, 222)) + +shutil.copytree(vendor_src.as_posix(), + vendor_dir.as_posix(), + ignore=_progress) +progress_bar.close() # copy all files _print("Copying dependencies ...") @@ -123,12 +142,6 @@ progress_bar = enlighten.Counter( total=total_files, desc="Processing Dependencies", units="%", color=(53, 178, 202)) - -def _progress(_base, _names): - progress_bar.update() - return [] - - shutil.copytree(site_pkg.as_posix(), deps_dir.as_posix(), ignore=_progress) diff --git a/tools/create_env.sh b/tools/create_env.sh index 6fe6cdafb9..917ddc36ba 100755 --- a/tools/create_env.sh +++ b/tools/create_env.sh @@ -189,10 +189,12 @@ main () { # cx_freeze will crash on missing __pychache__ on these but # reinstalling them solves the problem. echo -e "${BIGreen}>>>${RST} Post-venv creation fixes ..." + local openpype_index=$("$POETRY_HOME/bin/poetry" run python "$openpype_root/tools/parse_pyproject.py" tool.poetry.source.0.url) + echo -e "${BIGreen}- ${RST} Using index: ${BIWhite}$openpype_index${RST}" "$POETRY_HOME/bin/poetry" run pip install setuptools==49.6.0 "$POETRY_HOME/bin/poetry" run pip install --disable-pip-version-check --force-reinstall wheel "$POETRY_HOME/bin/poetry" run python -m pip install --disable-pip-version-check --force-reinstall pip - "$POETRY_HOME/bin/poetry" run pip install --disable-pip-version-check --force-reinstall cx_freeze + "$POETRY_HOME/bin/poetry" run pip install --disable-pip-version-check --force-reinstall cx_freeze -i $openpype_index --extra-index-url https://pypi.org/simple } return_code=0 diff --git a/tools/parse_pyproject.py b/tools/parse_pyproject.py index 0aed321fcf..296d73654d 100644 --- a/tools/parse_pyproject.py +++ b/tools/parse_pyproject.py @@ -3,49 +3,43 @@ Useful for shell scripts to know more about OpenPype build. """ +import sys import os -import blessed import toml from pathlib import Path import click -term = blessed.Terminal() - - -def _print(msg: str, message_type: int = 0) -> None: - """Print message to console. - - Args: - msg (str): message to print - message_type (int): type of message (0 info, 1 error, 2 note) - - """ - if message_type == 0: - header = term.aquamarine3(">>> ") - elif message_type == 1: - header = term.orangered2("!!! ") - elif message_type == 2: - header = term.tan1("... ") - else: - header = term.darkolivegreen3("--- ") - - print("{}{}".format(header, msg)) @click.command() -@click.argument("key", nargs=-1, type=click.STRING) -def main(key): - _print("Reading build metadata ...") +@click.argument("keys", nargs=-1, type=click.STRING) +def main(keys): + """Get values from `pyproject.toml`. + + You can specify dot separated keys from `pyproject.toml` + as arguments and this script will return them on separate + lines. If key doesn't exists, None is returned. + + + """ openpype_root = Path(os.path.dirname(__file__)).parent py_project = toml.load(openpype_root / "pyproject.toml") - query = key.split(".") - data = py_project - for k in query: - if isinstance(data, dict): - data = data.get(k) - else: - break - print(data) + for q in keys: + query = q.split(".") + data = py_project + for i, k in enumerate(query): + + if isinstance(data, list): + try: + data = data[int(k)] + except IndexError: + print("None") + sys.exit() + continue + + if isinstance(data, dict): + data = data.get(k) + print(data) if __name__ == "__main__": From 4276f34a301fe65ed1960e9a87935121907a0016 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 7 Oct 2021 23:04:48 +0200 Subject: [PATCH 062/279] fix hound --- tools/build_dependencies.py | 2 ++ tools/parse_pyproject.py | 4 +--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/build_dependencies.py b/tools/build_dependencies.py index 75d6e0c315..8eaf9d87e3 100644 --- a/tools/build_dependencies.py +++ b/tools/build_dependencies.py @@ -113,10 +113,12 @@ if not build_dir.exists(): _print("Probably freezing of code failed. Check ./build/build.log", 3) sys.exit(1) + def _progress(_base, _names): progress_bar.update() return [] + deps_dir = build_dir / "dependencies" vendor_dir = build_dir / "vendor" vendor_src = openpype_root / "vendor" diff --git a/tools/parse_pyproject.py b/tools/parse_pyproject.py index 296d73654d..dacecd88d0 100644 --- a/tools/parse_pyproject.py +++ b/tools/parse_pyproject.py @@ -10,7 +10,6 @@ from pathlib import Path import click - @click.command() @click.argument("keys", nargs=-1, type=click.STRING) def main(keys): @@ -19,7 +18,6 @@ def main(keys): You can specify dot separated keys from `pyproject.toml` as arguments and this script will return them on separate lines. If key doesn't exists, None is returned. - """ openpype_root = Path(os.path.dirname(__file__)).parent @@ -27,8 +25,8 @@ def main(keys): for q in keys: query = q.split(".") data = py_project - for i, k in enumerate(query): + for k in query: if isinstance(data, list): try: data = data[int(k)] From f92ee311b8fdb05ffae23194a64fa9e93edd4bfc Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 8 Oct 2021 17:23:10 +0200 Subject: [PATCH 063/279] PYPE-1901 - Fix ConsoleTrayApp sending lines --- openpype/tools/tray_app/app.py | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/openpype/tools/tray_app/app.py b/openpype/tools/tray_app/app.py index 03f8321464..476f061e26 100644 --- a/openpype/tools/tray_app/app.py +++ b/openpype/tools/tray_app/app.py @@ -142,18 +142,23 @@ class ConsoleTrayApp: self.tray_reconnect = False ConsoleTrayApp.webserver_client.close() - def _send_text(self, new_text): + def _send_text_queue(self): + """Sends lines and purges queue""" + lines = tuple(self.new_text) + self.new_text.clear() + + if lines: + self._send_lines(lines) + + def _send_lines(self, lines): """ Send console content. """ if not ConsoleTrayApp.webserver_client: return - if isinstance(new_text, str): - new_text = collections.deque(new_text.split("\n")) - payload = { "host": self.host_id, "action": host_console_listener.MsgAction.ADD, - "text": "\n".join(new_text) + "text": "\n".join(lines) } self._send(payload) @@ -174,14 +179,7 @@ class ConsoleTrayApp: if self.tray_reconnect: self._connect() # reconnect - if ConsoleTrayApp.webserver_client and self.new_text: - self._send_text(self.new_text) - self.new_text = collections.deque() - - if self.new_text: # no webserver_client, text keeps stashing - start = max(len(self.new_text) - self.MAX_LINES, 0) - self.new_text = itertools.islice(self.new_text, - start, self.MAX_LINES) + self._send_text_queue() if not self.initialized: if self.initializing: @@ -191,7 +189,7 @@ class ConsoleTrayApp: elif not host_connected: text = "{} process is not alive. Exiting".format(self.host) print(text) - self._send_text([text]) + self._send_lines([text]) ConsoleTrayApp.websocket_server.stop() sys.exit(1) elif host_connected: From b90bcd9877e56cfd91040d110a1cc13fc7aea2c5 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 8 Oct 2021 17:25:42 +0200 Subject: [PATCH 064/279] PYPE-1901 - New pluging for remote publishing (webpublish) Extracted _json_load method --- .../publish/collect_remote_instances.py | 88 +++++++++++++++++++ .../publish/collect_published_files.py | 23 +---- openpype/lib/plugin_tools.py | 17 ++++ 3 files changed, 109 insertions(+), 19 deletions(-) create mode 100644 openpype/hosts/photoshop/plugins/publish/collect_remote_instances.py diff --git a/openpype/hosts/photoshop/plugins/publish/collect_remote_instances.py b/openpype/hosts/photoshop/plugins/publish/collect_remote_instances.py new file mode 100644 index 0000000000..a9d6d6fec6 --- /dev/null +++ b/openpype/hosts/photoshop/plugins/publish/collect_remote_instances.py @@ -0,0 +1,88 @@ +import pyblish.api +import os + +from avalon import photoshop +from openpype.lib import prepare_template_data + + +class CollectRemoteInstances(pyblish.api.ContextPlugin): + """Gather instances configured color code of a layer. + + Used in remote publishing when artists marks publishable layers by color- + coding. + + Identifier: + id (str): "pyblish.avalon.instance" + """ + order = pyblish.api.CollectorOrder + 0.100 + + label = "Instances" + order = pyblish.api.CollectorOrder + hosts = ["photoshop"] + + # configurable by Settings + families = ["background"] + color_code = ["red"] + subset_template_name = "" + + def process(self, context): + self.log.info("CollectRemoteInstances") + 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") + variant = "Main" + if batch_dir and os.path.exists(batch_dir): + # TODO check if batch manifest is same as tasks manifests + task_data = self.parse_json(os.path.join(batch_dir, + "manifest.json")) + variant = task_data["variant"] + + stub = photoshop.stub() + layers = stub.get_layers() + + instance_names = [] + for layer in layers: + self.log.info("!!!Layer:: {}".format(layer)) + if layer.color_code not in self.color_code: + self.log.debug("Not marked, skip") + continue + + if layer.parents: + self.log.debug("Not a top layer, skip") + continue + + instance = context.create_instance(layer.name) + instance.append(layer) + instance.data["family"] = self.families[0] + instance.data["publish"] = layer.visible + + # populate data from context, coming from outside?? TODO + # TEMP + self.log.info("asset {}".format(context.data["assetEntity"])) + self.log.info("taskType {}".format(context.data["taskType"])) + instance.data["asset"] = context.data["assetEntity"]["name"] + instance.data["task"] = context.data["taskType"] + + fill_pairs = { + "variant": variant, + "family": instance.data["family"], + "task": instance.data["task"], + "layer": layer.name + } + subset = self.subset_template.format( + **prepare_template_data(fill_pairs)) + instance.data["subset"] = subset + + instance_names.append(layer.name) + + # Produce diagnostic message for any graphical + # user interface interested in visualising it. + self.log.info("Found: \"%s\" " % instance.data["name"]) + self.log.info("instance: {} ".format(instance.data)) + + if len(instance_names) != len(set(instance_names)): + self.log.warning("Duplicate instances found. " + + "Remove unwanted via SubsetManager") diff --git a/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py b/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py index 7e9b98956a..2b4a1273b8 100644 --- a/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py +++ b/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py @@ -15,6 +15,7 @@ import tempfile import pyblish.api from avalon import io from openpype.lib import prepare_template_data +from openpype.lib.plugin_tools import parse_json class CollectPublishedFiles(pyblish.api.ContextPlugin): @@ -33,22 +34,6 @@ class CollectPublishedFiles(pyblish.api.ContextPlugin): # from Settings task_type_to_family = {} - def _load_json(self, path): - path = path.strip('\"') - assert os.path.isfile(path), ( - "Path to json file doesn't exist. \"{}\"".format(path) - ) - data = None - with open(path, "r") as json_file: - try: - data = json.load(json_file) - except Exception as exc: - self.log.error( - "Error loading json: " - "{} - Exception: {}".format(path, exc) - ) - return data - def _process_batch(self, dir_url): task_subfolders = [ os.path.join(dir_url, o) @@ -56,8 +41,8 @@ class CollectPublishedFiles(pyblish.api.ContextPlugin): if os.path.isdir(os.path.join(dir_url, o))] self.log.info("task_sub:: {}".format(task_subfolders)) for task_dir in task_subfolders: - task_data = self._load_json(os.path.join(task_dir, - "manifest.json")) + task_data = parse_json(os.path.join(task_dir, + "manifest.json")) self.log.info("task_data:: {}".format(task_data)) ctx = task_data["context"] task_type = "default_task_type" @@ -261,7 +246,7 @@ class CollectPublishedFiles(pyblish.api.ContextPlugin): assert batch_dir, ( "Missing `OPENPYPE_PUBLISH_DATA`") - assert batch_dir, \ + assert os.path.exists(batch_dir), \ "Folder {} doesn't exist".format(batch_dir) project_name = os.environ.get("AVALON_PROJECT") diff --git a/openpype/lib/plugin_tools.py b/openpype/lib/plugin_tools.py index 9dccadc44e..2158a3e28d 100644 --- a/openpype/lib/plugin_tools.py +++ b/openpype/lib/plugin_tools.py @@ -483,3 +483,20 @@ def should_decompress(file_url): "compression: \"dwab\"" in output return False + + +def parse_json(path): + path = path.strip('\"') + assert os.path.isfile(path), ( + "Path to json file doesn't exist. \"{}\"".format(path) + ) + data = None + with open(path, "r") as json_file: + try: + data = json.load(json_file) + except Exception as exc: + log.error( + "Error loading json: " + "{} - Exception: {}".format(path, exc) + ) + return data From 93766c1d0b9515764b1e57f80406948d843f62eb Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 8 Oct 2021 17:27:15 +0200 Subject: [PATCH 065/279] PYPE-1901 - Added plugin to close PS after remote publishing --- .../photoshop/plugins/publish/closePS.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 openpype/hosts/photoshop/plugins/publish/closePS.py diff --git a/openpype/hosts/photoshop/plugins/publish/closePS.py b/openpype/hosts/photoshop/plugins/publish/closePS.py new file mode 100644 index 0000000000..ce229c86bb --- /dev/null +++ b/openpype/hosts/photoshop/plugins/publish/closePS.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +"""Close PS after publish. For Webpublishing only.""" +import os + +import pyblish.api + +from avalon import photoshop + +class ClosePS(pyblish.api.InstancePlugin): + """Close PS after publish. For Webpublishing only. + """ + + order = pyblish.api.IntegratorOrder + 14 + label = "Close PS" + optional = True + active = True + + hosts = ["photoshop"] + + def process(self, instance): + self.log.info("ClosePS") + if not os.environ.get("IS_HEADLESS"): + return + + stub = photoshop.stub() + self.log.info("Shutting down PS") + stub.save() + stub.close() From 185d3ef399ebe25211b038dd0a8cfca7055ac81e Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 8 Oct 2021 17:35:11 +0200 Subject: [PATCH 066/279] PYPE-1901 - Fix wrong variable used --- .../hosts/photoshop/plugins/publish/collect_remote_instances.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/photoshop/plugins/publish/collect_remote_instances.py b/openpype/hosts/photoshop/plugins/publish/collect_remote_instances.py index a9d6d6fec6..fa4364b700 100644 --- a/openpype/hosts/photoshop/plugins/publish/collect_remote_instances.py +++ b/openpype/hosts/photoshop/plugins/publish/collect_remote_instances.py @@ -72,7 +72,7 @@ class CollectRemoteInstances(pyblish.api.ContextPlugin): "task": instance.data["task"], "layer": layer.name } - subset = self.subset_template.format( + subset = self.subset_template_name.format( **prepare_template_data(fill_pairs)) instance.data["subset"] = subset From d2c4678fd45b8fb5d38d3a3e0382ff99ab9a0d06 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 8 Oct 2021 17:35:38 +0200 Subject: [PATCH 067/279] PYPE-1901 - Added background family to ExtractImage --- openpype/hosts/photoshop/plugins/publish/extract_image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/photoshop/plugins/publish/extract_image.py b/openpype/hosts/photoshop/plugins/publish/extract_image.py index 87574d1269..ae9892e290 100644 --- a/openpype/hosts/photoshop/plugins/publish/extract_image.py +++ b/openpype/hosts/photoshop/plugins/publish/extract_image.py @@ -12,7 +12,7 @@ class ExtractImage(openpype.api.Extractor): label = "Extract Image" hosts = ["photoshop"] - families = ["image"] + families = ["image", "background"] formats = ["png", "jpg"] def process(self, instance): From 57de11638c91c6518dd599db39c26347f00ad459 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 8 Oct 2021 17:36:13 +0200 Subject: [PATCH 068/279] PYPE-1901 - Added settings for CollectRemoteInstances --- .../defaults/project_settings/photoshop.json | 5 ++++ .../schema_project_photoshop.json | 29 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/openpype/settings/defaults/project_settings/photoshop.json b/openpype/settings/defaults/project_settings/photoshop.json index 4c36e4bd49..14c294c0c5 100644 --- a/openpype/settings/defaults/project_settings/photoshop.json +++ b/openpype/settings/defaults/project_settings/photoshop.json @@ -12,6 +12,11 @@ "optional": true, "active": true }, + "CollectRemoteInstances": { + "color_code": [], + "families": [], + "subset_template_name": "" + }, "ExtractImage": { "formats": [ "png", 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 3b65f08ac4..008f1a265d 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json @@ -43,6 +43,35 @@ } ] }, + { + "type": "dict", + "collapsible": true, + "key": "CollectRemoteInstances", + "label": "Collect Instances for Webpublish", + "children": [ + { + "type": "label", + "label": "Set color for publishable layers, set publishable families." + }, + { + "type": "list", + "key": "color_code", + "label": "Color codes for layers", + "object_type": "text" + }, + { + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" + }, + { + "type": "text", + "key": "subset_template_name", + "label": "Subset template name" + } + ] + }, { "type": "dict", "collapsible": true, From d6b85f1c1a91901581a410aba9688f2e92410aea Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 8 Oct 2021 18:46:06 +0200 Subject: [PATCH 069/279] PYPE-1901 - Added testing class for PS publishing --- .../photoshop/test_publish_in_photoshop.py | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 tests/integration/hosts/photoshop/test_publish_in_photoshop.py diff --git a/tests/integration/hosts/photoshop/test_publish_in_photoshop.py b/tests/integration/hosts/photoshop/test_publish_in_photoshop.py new file mode 100644 index 0000000000..396468a966 --- /dev/null +++ b/tests/integration/hosts/photoshop/test_publish_in_photoshop.py @@ -0,0 +1,94 @@ +import pytest +import os +import shutil + +from tests.lib.testing_classes import PublishTest + + +class TestPublishInPhotoshop(PublishTest): + """Basic test case for publishing in Photoshop + + Uses generic TestCase to prepare fixtures for test data, testing DBs, + env vars. + + Opens Maya, run publish on prepared workile. + + Then checks content of DB (if subset, version, representations were + created. + Checks tmp folder if all expected files were published. + + """ + PERSIST = True + + TEST_FILES = [ + ("1Bciy2pCwMKl1UIpxuPnlX_LHMo_Xkq0K", "test_photoshop_publish.zip", "") + ] + + APP = "photoshop" + APP_VARIANT = "2020" + + APP_NAME = "{}/{}".format(APP, APP_VARIANT) + + TIMEOUT = 120 # publish timeout + + @pytest.fixture(scope="module") + def last_workfile_path(self, download_test_data): + """Get last_workfile_path from source data. + + Maya expects workfile in proper folder, so copy is done first. + """ + src_path = os.path.join(download_test_data, + "input", + "workfile", + "test_project_test_asset_TestTask_v001.psd") + dest_folder = os.path.join(download_test_data, + self.PROJECT, + self.ASSET, + "work", + self.TASK) + os.makedirs(dest_folder) + dest_path = os.path.join(dest_folder, + "test_project_test_asset_TestTask_v001.psd") + shutil.copy(src_path, dest_path) + + yield dest_path + + @pytest.fixture(scope="module") + def startup_scripts(self, monkeypatch_session, download_test_data): + """Points Maya to userSetup file from input data""" + os.environ["IS_HEADLESS"] = "true" + + def test_db_asserts(self, dbcon, publish_finished): + """Host and input data dependent expected results in DB.""" + print("test_db_asserts") + assert 5 == dbcon.count_documents({"type": "version"}), \ + "Not expected no of versions" + + assert 0 == dbcon.count_documents({"type": "version", + "name": {"$ne": 1}}), \ + "Only versions with 1 expected" + + assert 1 == dbcon.count_documents({"type": "subset", + "name": "modelMain"}), \ + "modelMain subset must be present" + + assert 1 == dbcon.count_documents({"type": "subset", + "name": "workfileTest_task"}), \ + "workfileTest_task subset must be present" + + assert 11 == dbcon.count_documents({"type": "representation"}), \ + "Not expected no of representations" + + assert 2 == dbcon.count_documents({"type": "representation", + "context.subset": "modelMain", + "context.ext": "abc"}), \ + "Not expected no of representations with ext 'abc'" + + assert 2 == dbcon.count_documents({"type": "representation", + "context.subset": "modelMain", + "context.ext": "ma"}), \ + "Not expected no of representations with ext 'abc'" + + +if __name__ == "__main__": + test_case = TestPublishInPhotoshop() From cff9c793464abb3211ef49780f7a0c94656c3831 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 8 Oct 2021 18:46:29 +0200 Subject: [PATCH 070/279] PYPE-1901 - Added WIP for PS webpublishing --- openpype/cli.py | 19 +++++++++++++++++++ openpype/pype_commands.py | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/openpype/cli.py b/openpype/cli.py index c69407e295..8438703bd3 100644 --- a/openpype/cli.py +++ b/openpype/cli.py @@ -158,6 +158,25 @@ def publish(debug, paths, targets): PypeCommands.publish(list(paths), 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 remotepublishfromapp(debug, project, path, host, targets=None, user=None): + """Start CLI publishing. + + Publish collects json from paths provided as an argument. + More than one path is allowed. + """ + if debug: + os.environ['OPENPYPE_DEBUG'] = '3' + PypeCommands.remotepublishfromapp(project, path, host, user, + targets=targets) + @main.command() @click.argument("path") @click.option("-d", "--debug", is_flag=True, help="Print debug messages") diff --git a/openpype/pype_commands.py b/openpype/pype_commands.py index 5288749e8b..318f2476ed 100644 --- a/openpype/pype_commands.py +++ b/openpype/pype_commands.py @@ -4,6 +4,7 @@ import os import sys import json from datetime import datetime +import time from openpype.lib import PypeLogger from openpype.api import get_app_environments_for_context @@ -110,6 +111,42 @@ class PypeCommands: log.info("Publish finished.") uninstall() + @staticmethod + def remotepublishfromapp(project, batch_path, host, user, targets=None): + from openpype import install, uninstall + from openpype.api import Logger + + log = Logger.get_logger() + + log.info("remotepublishphotoshop command") + + install() + + os.environ["OPENPYPE_PUBLISH_DATA"] = batch_path + os.environ["AVALON_PROJECT"] = project + os.environ["AVALON_APP"] = host + + os.environ["OPENPYPE_EXECUTABLE"] = sys.executable + os.environ["IS_HEADLESS"] = "true" + from openpype.lib import ApplicationManager + + application_manager = ApplicationManager() + data = { + "last_workfile_path": "c:/projects/test_project_test_asset_TestTask_v001.psd", + "start_last_workfile": True, + "project_name": project, + "asset_name": "test_asset", + "task_name": "test_task" + } + + launched_app = application_manager.launch( + os.environ["AVALON_APP"] + "/2020", **data) + + while launched_app.poll() is None: + time.sleep(0.5) + + print(launched_app) + @staticmethod def remotepublish(project, batch_path, host, user, targets=None): """Start headless publishing. From c8b1037e99e829be086ab092e94103e5eb115ef6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 11 Oct 2021 16:35:32 +0200 Subject: [PATCH 071/279] added few magic attributes to getattr --- openpype/modules/base.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/modules/base.py b/openpype/modules/base.py index 748c7857a9..7779fff6ec 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -48,7 +48,7 @@ class _ModuleClass(object): def __getattr__(self, attr_name): if attr_name not in self.__attributes__: - if attr_name in ("__path__"): + if attr_name in ("__path__", "__file__"): return None raise ImportError("No module named {}.{}".format( self.name, attr_name @@ -104,6 +104,9 @@ class _InterfacesClass(_ModuleClass): """ def __getattr__(self, attr_name): if attr_name not in self.__attributes__: + if attr_name in ("__path__", "__file__"): + return None + # Fake Interface if is not missing self.__attributes__[attr_name] = type( attr_name, From 45b39792e3d18c409e076a0d9788e981cd630001 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 11 Oct 2021 16:35:48 +0200 Subject: [PATCH 072/279] removed boolean from setFocus --- openpype/tools/settings/settings/categories.py | 2 +- openpype/tools/settings/settings/dict_mutable_widget.py | 6 +++--- openpype/tools/settings/settings/list_item_widget.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/openpype/tools/settings/settings/categories.py b/openpype/tools/settings/settings/categories.py index be2264340b..5f9051344d 100644 --- a/openpype/tools/settings/settings/categories.py +++ b/openpype/tools/settings/settings/categories.py @@ -508,7 +508,7 @@ class SettingsCategoryWidget(QtWidgets.QWidget): first_invalid_item = invalid_items[0] self.scroll_widget.ensureWidgetVisible(first_invalid_item) if first_invalid_item.isVisible(): - first_invalid_item.setFocus(True) + first_invalid_item.setFocus() return False def on_saved(self, saved_tab_widget): diff --git a/openpype/tools/settings/settings/dict_mutable_widget.py b/openpype/tools/settings/settings/dict_mutable_widget.py index cfb9d4a4b1..9afce7259e 100644 --- a/openpype/tools/settings/settings/dict_mutable_widget.py +++ b/openpype/tools/settings/settings/dict_mutable_widget.py @@ -128,9 +128,9 @@ class ModifiableDictEmptyItem(QtWidgets.QWidget): def add_new_item(self, key=None, label=None): input_field = self.entity_widget.add_new_key(key, label) if self.collapsible_key: - self.key_input.setFocus(True) + self.key_input.setFocus() else: - input_field.key_input.setFocus(True) + input_field.key_input.setFocus() return input_field def _on_add_clicked(self): @@ -563,7 +563,7 @@ class ModifiableDictItem(QtWidgets.QWidget): def on_add_clicked(self): widget = self.entity_widget.add_new_key(None, None) - widget.key_input.setFocus(True) + widget.key_input.setFocus() def on_edit_pressed(self): if not self.key_input.isVisible(): diff --git a/openpype/tools/settings/settings/list_item_widget.py b/openpype/tools/settings/settings/list_item_widget.py index 17412a30b9..128af92631 100644 --- a/openpype/tools/settings/settings/list_item_widget.py +++ b/openpype/tools/settings/settings/list_item_widget.py @@ -357,7 +357,7 @@ class ListWidget(InputWidget): new_entity = self.entity.add_new_item(row) input_field = self._input_fields_by_entity_id.get(new_entity.id) if input_field is not None: - input_field.input_field.setFocus(True) + input_field.input_field.setFocus() return new_entity def add_row(self, child_entity, row=None): From e3303f88322290143beffbc3b4b2bf116291bb63 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 11 Oct 2021 16:40:16 +0200 Subject: [PATCH 073/279] avoid garbage collection of clipboard --- openpype/tools/settings/settings/base.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/tools/settings/settings/base.py b/openpype/tools/settings/settings/base.py index ab6b27bdaf..92fffe6f9c 100644 --- a/openpype/tools/settings/settings/base.py +++ b/openpype/tools/settings/settings/base.py @@ -214,7 +214,8 @@ class BaseWidget(QtWidgets.QWidget): def _paste_value_actions(self, menu): output = [] # Allow paste of value only if were copied from this UI - mime_data = QtWidgets.QApplication.clipboard().mimeData() + clipboard = QtWidgets.QApplication.clipboard() + mime_data = clipboard.mimeData() mime_value = mime_data.data("application/copy_settings_value") # Skip if there is nothing to do if not mime_value: From 6f3f0e6732f725d192f3f3d30032c04ab81e105d Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Mon, 11 Oct 2021 16:55:24 +0100 Subject: [PATCH 074/279] Improved loading of camera assets in Blender from blend files --- .../blender/plugins/load/load_camera_blend.py | 263 +++++++++--------- 1 file changed, 134 insertions(+), 129 deletions(-) diff --git a/openpype/hosts/blender/plugins/load/load_camera_blend.py b/openpype/hosts/blender/plugins/load/load_camera_blend.py index 6c3348a1a6..1173e26d7b 100644 --- a/openpype/hosts/blender/plugins/load/load_camera_blend.py +++ b/openpype/hosts/blender/plugins/load/load_camera_blend.py @@ -5,14 +5,19 @@ from pathlib import Path from pprint import pformat from typing import Dict, List, Optional -from avalon import api, blender import bpy -import openpype.hosts.blender.api.plugin -logger = logging.getLogger("openpype").getChild("blender").getChild("load_camera") +from avalon import api +from avalon.blender.pipeline import AVALON_CONTAINERS +from avalon.blender.pipeline import AVALON_CONTAINER_ID +from avalon.blender.pipeline import AVALON_PROPERTY +from openpype.hosts.blender.api import plugin + +logger = logging.getLogger("openpype").getChild( + "blender").getChild("load_camera") -class BlendCameraLoader(openpype.hosts.blender.api.plugin.AssetLoader): +class BlendCameraLoader(plugin.AssetLoader): """Load a camera from a .blend file. Warning: @@ -27,55 +32,68 @@ class BlendCameraLoader(openpype.hosts.blender.api.plugin.AssetLoader): icon = "code-fork" color = "orange" - def _remove(self, objects, lib_container): - for obj in list(objects): - bpy.data.cameras.remove(obj.data) + def _remove(self, asset_group): + objects = list(asset_group.children) - bpy.data.collections.remove(bpy.data.collections[lib_container]) + for obj in objects: + if obj.type == 'CAMERA': + bpy.data.cameras.remove(obj.data) - def _process(self, libpath, lib_container, container_name, actions): - - relative = bpy.context.preferences.filepaths.use_relative_paths + def _process(self, libpath, asset_group, group_name): with bpy.data.libraries.load( - libpath, link=True, relative=relative - ) as (_, data_to): - data_to.collections = [lib_container] + libpath, link=True, relative=False + ) as (data_from, data_to): + data_to.objects = data_from.objects - scene = bpy.context.scene + parent = bpy.context.scene.collection - scene.collection.children.link(bpy.data.collections[lib_container]) + empties = [obj for obj in data_to.objects if obj.type == 'EMPTY'] - camera_container = scene.collection.children[lib_container].make_local() + container = None - objects_list = [] + for empty in empties: + if empty.get(AVALON_PROPERTY): + container = empty + break - for obj in camera_container.objects: - local_obj = obj.make_local() - local_obj.data.make_local() + assert container, "No asset group found" - if not local_obj.get(blender.pipeline.AVALON_PROPERTY): - local_obj[blender.pipeline.AVALON_PROPERTY] = dict() + # Children must be linked before parents, + # otherwise the hierarchy will break + objects = [] + nodes = list(container.children) - avalon_info = local_obj[blender.pipeline.AVALON_PROPERTY] - avalon_info.update({"container_name": container_name}) + for obj in nodes: + obj.parent = asset_group - if actions[0] is not None: - if local_obj.animation_data is None: - local_obj.animation_data_create() - local_obj.animation_data.action = actions[0] + for obj in nodes: + objects.append(obj) + nodes.extend(list(obj.children)) - if actions[1] is not None: - if local_obj.data.animation_data is None: - local_obj.data.animation_data_create() - local_obj.data.animation_data.action = actions[1] + objects.reverse() - objects_list.append(local_obj) + for obj in objects: + parent.objects.link(obj) - camera_container.pop(blender.pipeline.AVALON_PROPERTY) + for obj in objects: + local_obj = plugin.prepare_data(obj, group_name) + + if local_obj.type != 'EMPTY': + plugin.prepare_data(local_obj.data, group_name) + + if not local_obj.get(AVALON_PROPERTY): + local_obj[AVALON_PROPERTY] = dict() + + avalon_info = local_obj[AVALON_PROPERTY] + avalon_info.update({"container_name": group_name}) + + objects.reverse() + + bpy.data.orphans_purge(do_local_ids=False) bpy.ops.object.select_all(action='DESELECT') - return objects_list + return objects def process_asset( self, context: dict, name: str, namespace: Optional[str] = None, @@ -88,131 +106,118 @@ class BlendCameraLoader(openpype.hosts.blender.api.plugin.AssetLoader): context: Full parenthood of representation to load options: Additional settings dictionary """ - libpath = self.fname asset = context["asset"]["name"] subset = context["subset"]["name"] - lib_container = openpype.hosts.blender.api.plugin.asset_name(asset, subset) - container_name = openpype.hosts.blender.api.plugin.asset_name( - asset, subset, namespace - ) - container = bpy.data.collections.new(lib_container) - container.name = container_name - blender.pipeline.containerise_existing( - container, - name, - namespace, - context, - self.__class__.__name__, - ) + asset_name = plugin.asset_name(asset, subset) + unique_number = plugin.get_unique_number(asset, subset) + group_name = plugin.asset_name(asset, subset, unique_number) + namespace = namespace or f"{asset}_{unique_number}" - container_metadata = container.get( - blender.pipeline.AVALON_PROPERTY) + avalon_container = bpy.data.collections.get(AVALON_CONTAINERS) + if not avalon_container: + avalon_container = bpy.data.collections.new(name=AVALON_CONTAINERS) + bpy.context.scene.collection.children.link(avalon_container) - container_metadata["libpath"] = libpath - container_metadata["lib_container"] = lib_container + asset_group = bpy.data.objects.new(group_name, object_data=None) + asset_group.empty_display_type = 'SINGLE_ARROW' + avalon_container.objects.link(asset_group) - objects_list = self._process( - libpath, lib_container, container_name, (None, None)) + objects = self._process(libpath, asset_group, group_name) - # Save the list of objects in the metadata container - container_metadata["objects"] = objects_list + bpy.context.scene.collection.objects.link(asset_group) - nodes = list(container.objects) - nodes.append(container) - self[:] = nodes - return nodes + asset_group[AVALON_PROPERTY] = { + "schema": "openpype:container-2.0", + "id": AVALON_CONTAINER_ID, + "name": name, + "namespace": namespace or '', + "loader": str(self.__class__.__name__), + "representation": str(context["representation"]["_id"]), + "libpath": libpath, + "asset_name": asset_name, + "parent": str(context["representation"]["parent"]), + "family": context["representation"]["context"]["family"], + "objectName": group_name + } - def update(self, container: Dict, representation: Dict): + self[:] = objects + return objects + + def exec_update(self, container: Dict, representation: Dict): """Update the loaded asset. - This will remove all objects of the current collection, load the new - ones and add them to the collection. - If the objects of the collection are used in another collection they - will not be removed, only unlinked. Normally this should not be the - case though. - - Warning: - No nested collections are supported at the moment! + This will remove all children of the asset group, load the new ones + and add them as children of the group. """ - - collection = bpy.data.collections.get( - container["objectName"] - ) - + object_name = container["objectName"] + asset_group = bpy.data.objects.get(object_name) libpath = Path(api.get_representation_path(representation)) extension = libpath.suffix.lower() - logger.info( + self.log.info( "Container: %s\nRepresentation: %s", pformat(container, indent=2), pformat(representation, indent=2), ) - assert collection, ( + assert asset_group, ( f"The asset is not loaded: {container['objectName']}" ) - assert not (collection.children), ( - "Nested collections are not supported." - ) assert libpath, ( "No existing library file found for {container['objectName']}" ) assert libpath.is_file(), ( f"The file doesn't exist: {libpath}" ) - assert extension in openpype.hosts.blender.api.plugin.VALID_EXTENSIONS, ( + assert extension in plugin.VALID_EXTENSIONS, ( f"Unsupported file: {libpath}" ) - collection_metadata = collection.get( - blender.pipeline.AVALON_PROPERTY) - collection_libpath = collection_metadata["libpath"] - objects = collection_metadata["objects"] - lib_container = collection_metadata["lib_container"] + metadata = asset_group.get(AVALON_PROPERTY) + group_libpath = metadata["libpath"] - normalized_collection_libpath = ( - str(Path(bpy.path.abspath(collection_libpath)).resolve()) + normalized_group_libpath = ( + str(Path(bpy.path.abspath(group_libpath)).resolve()) ) normalized_libpath = ( str(Path(bpy.path.abspath(str(libpath))).resolve()) ) - logger.debug( - "normalized_collection_libpath:\n %s\nnormalized_libpath:\n %s", - normalized_collection_libpath, + self.log.debug( + "normalized_group_libpath:\n %s\nnormalized_libpath:\n %s", + normalized_group_libpath, normalized_libpath, ) - if normalized_collection_libpath == normalized_libpath: - logger.info("Library already loaded, not updating...") + if normalized_group_libpath == normalized_libpath: + self.log.info("Library already loaded, not updating...") return - camera = objects[0] + # Check how many assets use the same library + count = 0 + for obj in bpy.data.collections.get(AVALON_CONTAINERS).objects: + if obj.get(AVALON_PROPERTY).get('libpath') == group_libpath: + count += 1 - camera_action = None - camera_data_action = None + mat = asset_group.matrix_basis.copy() - if camera.animation_data and camera.animation_data.action: - camera_action = camera.animation_data.action + self._remove(asset_group) - if camera.data.animation_data and camera.data.animation_data.action: - camera_data_action = camera.data.animation_data.action + # If it is the last object to use that library, remove it + if count == 1: + library = bpy.data.libraries.get(bpy.path.basename(group_libpath)) + if library: + bpy.data.libraries.remove(library) - actions = (camera_action, camera_data_action) + self._process(str(libpath), asset_group, object_name) - self._remove(objects, lib_container) + asset_group.matrix_basis = mat - objects_list = self._process( - str(libpath), lib_container, collection.name, actions) + metadata["libpath"] = str(libpath) + metadata["representation"] = str(representation["_id"]) + metadata["parent"] = str(representation["parent"]) - # Save the list of objects in the metadata container - collection_metadata["objects"] = objects_list - collection_metadata["libpath"] = str(libpath) - collection_metadata["representation"] = str(representation["_id"]) - - bpy.ops.object.select_all(action='DESELECT') - - def remove(self, container: Dict) -> bool: + def exec_remove(self, container: Dict) -> bool: """Remove an existing container from a Blender scene. Arguments: @@ -221,27 +226,27 @@ class BlendCameraLoader(openpype.hosts.blender.api.plugin.AssetLoader): Returns: bool: Whether the container was deleted. - - Warning: - No nested collections are supported at the moment! """ + object_name = container["objectName"] + asset_group = bpy.data.objects.get(object_name) + libpath = asset_group.get(AVALON_PROPERTY).get('libpath') - collection = bpy.data.collections.get( - container["objectName"] - ) - if not collection: + # Check how many assets use the same library + count = 0 + for obj in bpy.data.collections.get(AVALON_CONTAINERS).objects: + if obj.get(AVALON_PROPERTY).get('libpath') == libpath: + count += 1 + + if not asset_group: return False - assert not (collection.children), ( - "Nested collections are not supported." - ) - collection_metadata = collection.get( - blender.pipeline.AVALON_PROPERTY) - objects = collection_metadata["objects"] - lib_container = collection_metadata["lib_container"] + self._remove(asset_group) - self._remove(objects, lib_container) + bpy.data.objects.remove(asset_group) - bpy.data.collections.remove(collection) + # If it is the last object to use that library, remove it + if count == 1: + library = bpy.data.libraries.get(bpy.path.basename(libpath)) + bpy.data.libraries.remove(library) return True From 280c7d96ea19ae3cc7076b0bde694eb6eff39509 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 12 Oct 2021 11:53:46 +0200 Subject: [PATCH 075/279] PYPE-1901 - wip of remotepublishfromapp --- openpype/pype_commands.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/openpype/pype_commands.py b/openpype/pype_commands.py index 318f2476ed..4f3e173f3e 100644 --- a/openpype/pype_commands.py +++ b/openpype/pype_commands.py @@ -125,6 +125,17 @@ class PypeCommands: os.environ["OPENPYPE_PUBLISH_DATA"] = batch_path os.environ["AVALON_PROJECT"] = project os.environ["AVALON_APP"] = host + os.environ["AVALON_APP_NAME"] = os.environ["AVALON_APP"] + "/2020" + os.environ["AVALON_ASSET"] = "test_asset" + os.environ["AVALON_TASK"] = "test_task" + + env = get_app_environments_for_context( + os.environ["AVALON_PROJECT"], + os.environ["AVALON_ASSET"], + os.environ["AVALON_TASK"], + os.environ["AVALON_APP_NAME"] + ) + os.environ.update(env) os.environ["OPENPYPE_EXECUTABLE"] = sys.executable os.environ["IS_HEADLESS"] = "true" From 9607026daec25e2e0803dd19093b528b03cea9fd Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 12 Oct 2021 11:54:05 +0200 Subject: [PATCH 076/279] PYPE-1901 - terminate process after timeout --- tests/lib/testing_classes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/lib/testing_classes.py b/tests/lib/testing_classes.py index 1832efb7ed..59d4abb3aa 100644 --- a/tests/lib/testing_classes.py +++ b/tests/lib/testing_classes.py @@ -228,6 +228,7 @@ class PublishTest(ModuleUnitTest): while launched_app.poll() is None: time.sleep(0.5) if time.time() - time_start > self.TIMEOUT: + launched_app.terminate() raise ValueError("Timeout reached") # some clean exit test possible? From e969cd188395b34b447978ff44989fb0d210ff01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20LORRAIN?= Date: Tue, 12 Oct 2021 16:41:02 +0200 Subject: [PATCH 077/279] Add photoshop extractReview plugin options for representations tags --- .../plugins/publish/extract_review.py | 9 +++-- openpype/plugins/publish/extract_burnin.py | 3 +- .../defaults/project_settings/photoshop.json | 12 +++++++ .../schema_project_photoshop.json | 34 ++++++++++++++++++- 4 files changed, 54 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/photoshop/plugins/publish/extract_review.py b/openpype/hosts/photoshop/plugins/publish/extract_review.py index 1c53c3a2ef..8c4d05b282 100644 --- a/openpype/hosts/photoshop/plugins/publish/extract_review.py +++ b/openpype/hosts/photoshop/plugins/publish/extract_review.py @@ -17,6 +17,10 @@ class ExtractReview(openpype.api.Extractor): hosts = ["photoshop"] families = ["review"] + # Extract Options + jpg_options = None + mov_options = None + def process(self, instance): staging_dir = self.staging_dir(instance) self.log.info("Outputting image to {}".format(staging_dir)) @@ -53,7 +57,8 @@ class ExtractReview(openpype.api.Extractor): "name": "jpg", "ext": "jpg", "files": output_image, - "stagingDir": staging_dir + "stagingDir": staging_dir, + "tags": self.jpg_options['tags'] }) instance.data["stagingDir"] = staging_dir @@ -97,7 +102,7 @@ class ExtractReview(openpype.api.Extractor): "frameEnd": 1, "fps": 25, "preview": True, - "tags": ["review", "ftrackreview"] + "tags": self.mov_options['tags'] }) # Required for extract_review plugin (L222 onwards). diff --git a/openpype/plugins/publish/extract_burnin.py b/openpype/plugins/publish/extract_burnin.py index 207e696fb1..06eb85c593 100644 --- a/openpype/plugins/publish/extract_burnin.py +++ b/openpype/plugins/publish/extract_burnin.py @@ -45,7 +45,8 @@ class ExtractBurnin(openpype.api.Extractor): "aftereffects", "tvpaint", "webpublisher", - "aftereffects" + "aftereffects", + "photoshop" # "resolve" ] optional = True diff --git a/openpype/settings/defaults/project_settings/photoshop.json b/openpype/settings/defaults/project_settings/photoshop.json index 4c36e4bd49..36c30bad6c 100644 --- a/openpype/settings/defaults/project_settings/photoshop.json +++ b/openpype/settings/defaults/project_settings/photoshop.json @@ -17,6 +17,18 @@ "png", "jpg" ] + }, + "ExtractReview": { + "jpg_options": { + "tags": [ + ] + }, + "mov_options": { + "tags": [ + "review", + "ftrackreview" + ] + } } }, "workfile_builder": { 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 3b65f08ac4..6f5577650c 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json @@ -60,7 +60,39 @@ "object_type": "text" } ] - } + }, + { + "type": "dict", + "collapsible": true, + "key": "ExtractReview", + "label": "Extract Review", + "children": [ + { + "type": "dict", + "collapsible": false, + "key": "jpg_options", + "label": "Extracted jpg Options", + "children": [ + { + "type": "schema", + "name": "schema_representation_tags" + } + ] + }, + { + "type": "dict", + "collapsible": false, + "key": "mov_options", + "label": "Extracted mov Options", + "children": [ + { + "type": "schema", + "name": "schema_representation_tags" + } + ] + } + ] + } ] }, { From 50742bba5c6b3d620bf13042ecd61dff352fc544 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 12 Oct 2021 16:45:29 +0200 Subject: [PATCH 078/279] nuke: adding supporting plugin function --- openpype/hosts/nuke/api/plugin.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/openpype/hosts/nuke/api/plugin.py b/openpype/hosts/nuke/api/plugin.py index 0ad98146b1..71329c0d46 100644 --- a/openpype/hosts/nuke/api/plugin.py +++ b/openpype/hosts/nuke/api/plugin.py @@ -23,3 +23,19 @@ class PypeCreator(PypeCreatorMixin, avalon.nuke.pipeline.Creator): self.log.error(msg + '\n\nPlease use other subset name!') raise NameError("`{0}: {1}".format(__name__, msg)) return + + +def get_review_presets_config(): + settings = get_current_project_settings() + review_profiles = ( + settings["global"] + ["publish"] + ["ExtractReview"] + ["profiles"] + ) + + outputs = {} + for profile in review_profiles: + outputs.update(profile.get("outputs", {})) + + return [str(name) for name, _prop in outputs.items()] From ad2be29831e483bdbf1c4c61622e40884e23b0a6 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 12 Oct 2021 16:45:53 +0200 Subject: [PATCH 079/279] nuke: adding repair inventory action --- .../plugins/inventory/repair_old_loaders.py | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 openpype/hosts/nuke/plugins/inventory/repair_old_loaders.py diff --git a/openpype/hosts/nuke/plugins/inventory/repair_old_loaders.py b/openpype/hosts/nuke/plugins/inventory/repair_old_loaders.py new file mode 100644 index 0000000000..e7ae51fa86 --- /dev/null +++ b/openpype/hosts/nuke/plugins/inventory/repair_old_loaders.py @@ -0,0 +1,37 @@ +from avalon import api, style +from avalon.nuke import lib as anlib +from openpype.api import ( + Logger) + + +class RepairOldLoaders(api.InventoryAction): + + label = "Repair Old Loaders" + icon = "gears" + color = style.colors.alert + + log = Logger().get_logger(__name__) + + def process(self, containers): + import nuke + new_loader = "LoadClip" + + for cdata in containers: + orig_loader = cdata["loader"] + orig_name = cdata["objectName"] + if orig_loader not in ["LoadSequence", "LoadMov"]: + self.log.warning( + "This repair action is only working on " + "`LoadSequence` and `LoadMov` Loaders") + continue + + new_name = orig_name.replace(orig_loader, new_loader) + node = nuke.toNode(cdata["objectName"]) + + cdata.update({ + "loader": new_loader, + "objectName": new_name + }) + node["name"].setValue(new_name) + # get data from avalon knob + anlib.set_avalon_knob_data(node, cdata) From fcd9ebda39459eccf407875217f920745ef8d5d7 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 12 Oct 2021 16:46:17 +0200 Subject: [PATCH 080/279] nuke: removing unused code --- .../nuke/plugins/inventory/set_tool_color.py | 68 ------------------- 1 file changed, 68 deletions(-) delete mode 100644 openpype/hosts/nuke/plugins/inventory/set_tool_color.py diff --git a/openpype/hosts/nuke/plugins/inventory/set_tool_color.py b/openpype/hosts/nuke/plugins/inventory/set_tool_color.py deleted file mode 100644 index 7a81444c90..0000000000 --- a/openpype/hosts/nuke/plugins/inventory/set_tool_color.py +++ /dev/null @@ -1,68 +0,0 @@ -# from avalon import api, style -# from avalon.vendor.Qt import QtGui, QtWidgets -# -# import avalon.fusion -# -# -# class FusionSetToolColor(api.InventoryAction): -# """Update the color of the selected tools""" -# -# label = "Set Tool Color" -# icon = "plus" -# color = "#d8d8d8" -# _fallback_color = QtGui.QColor(1.0, 1.0, 1.0) -# -# def process(self, containers): -# """Color all selected tools the selected colors""" -# -# result = [] -# comp = avalon.fusion.get_current_comp() -# -# # Get tool color -# first = containers[0] -# tool = first["_node"] -# color = tool.TileColor -# -# if color is not None: -# qcolor = QtGui.QColor().fromRgbF(color["R"], color["G"], color["B"]) -# else: -# qcolor = self._fallback_color -# -# # Launch pick color -# picked_color = self.get_color_picker(qcolor) -# if not picked_color: -# return -# -# with avalon.fusion.comp_lock_and_undo_chunk(comp): -# for container in containers: -# # Convert color to RGB 0-1 floats -# rgb_f = picked_color.getRgbF() -# rgb_f_table = {"R": rgb_f[0], "G": rgb_f[1], "B": rgb_f[2]} -# -# # Update tool -# tool = container["_node"] -# tool.TileColor = rgb_f_table -# -# result.append(container) -# -# return result -# -# def get_color_picker(self, color): -# """Launch color picker and return chosen color -# -# Args: -# color(QtGui.QColor): Start color to display -# -# Returns: -# QtGui.QColor -# -# """ -# -# color_dialog = QtWidgets.QColorDialog(color) -# color_dialog.setStyleSheet(style.load_stylesheet()) -# -# accepted = color_dialog.exec_() -# if not accepted: -# return -# -# return color_dialog.selectedColor() From 3ed84b3bc932d41c1ae2a49dfb67b81120740cee Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 12 Oct 2021 16:46:54 +0200 Subject: [PATCH 081/279] nuke: fixing inventory action obsolete way of accessing nodes --- openpype/hosts/nuke/plugins/inventory/select_containers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/nuke/plugins/inventory/select_containers.py b/openpype/hosts/nuke/plugins/inventory/select_containers.py index b420f53431..bd00983172 100644 --- a/openpype/hosts/nuke/plugins/inventory/select_containers.py +++ b/openpype/hosts/nuke/plugins/inventory/select_containers.py @@ -8,10 +8,10 @@ class SelectContainers(api.InventoryAction): color = "#d8d8d8" def process(self, containers): - + import nuke import avalon.nuke - nodes = [i["_node"] for i in containers] + nodes = [nuke.toNode(i["objectName"]) for i in containers] with avalon.nuke.viewer_update_and_undo_stop(): # clear previous_selection From d1029a30426706ef5f4ad169ab49ee116e19cb94 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 12 Oct 2021 16:54:36 +0200 Subject: [PATCH 082/279] Nuke: new way of adding representation from settings to plugins --- .../hosts/nuke/plugins/load/load_image.py | 14 +++++- .../defaults/project_settings/nuke.json | 45 ++----------------- .../schemas/schema_nuke_load.json | 8 +--- .../schemas/template_loader_plugin_nuke.json | 8 +--- 4 files changed, 19 insertions(+), 56 deletions(-) diff --git a/openpype/hosts/nuke/plugins/load/load_image.py b/openpype/hosts/nuke/plugins/load/load_image.py index 9b8bc43d12..2af44d6eba 100644 --- a/openpype/hosts/nuke/plugins/load/load_image.py +++ b/openpype/hosts/nuke/plugins/load/load_image.py @@ -12,7 +12,15 @@ from openpype.hosts.nuke.api.lib import ( class LoadImage(api.Loader): """Load still image into Nuke""" - families = ["render", "source", "plate", "review", "image"] + families = [ + "render2d", + "source", + "plate", + "render", + "prerender", + "review", + "image" + ] representations = ["exr", "dpx", "jpg", "jpeg", "png", "psd", "tiff"] label = "Load Image" @@ -33,6 +41,10 @@ class LoadImage(api.Loader): ) ] + @classmethod + def get_representations(cls): + return cls.representations + cls._representations + def load(self, context, name, namespace, options): from avalon.nuke import ( containerise, diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index dd65df02e5..e3c7834e4a 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -117,16 +117,7 @@ "load": { "LoadImage": { "enabled": true, - "families": [ - "render2d", - "source", - "plate", - "render", - "prerender", - "review", - "image" - ], - "representations": [ + "_representations": [ "exr", "dpx", "jpg", @@ -137,39 +128,9 @@ ], "node_name_template": "{class_name}_{ext}" }, - "LoadMov": { + "LoadClip": { "enabled": true, - "families": [ - "source", - "plate", - "render", - "prerender", - "review" - ], - "representations": [ - "mov", - "review", - "mp4", - "h264" - ], - "node_name_template": "{class_name}_{ext}" - }, - "LoadSequence": { - "enabled": true, - "families": [ - "render2d", - "source", - "plate", - "render", - "prerender", - "review" - ], - "representations": [ - "exr", - "dpx", - "jpg", - "jpeg", - "png" + "_representations": [ ], "node_name_template": "{class_name}_{ext}" } diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_load.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_load.json index 737843ad98..5bd8337e4c 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_load.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_load.json @@ -13,12 +13,8 @@ "label": "Image Loader" }, { - "key": "LoadMov", - "label": "Movie Loader" - }, - { - "key": "LoadSequence", - "label": "Image Sequence Loader" + "key": "LoadClip", + "label": "Clip Loader" } ] } diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/template_loader_plugin_nuke.json b/openpype/settings/entities/schemas/projects_schema/schemas/template_loader_plugin_nuke.json index d01691ed5f..7ee8d0bda0 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/template_loader_plugin_nuke.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/template_loader_plugin_nuke.json @@ -13,13 +13,7 @@ }, { "type": "list", - "key": "families", - "label": "Families", - "object_type": "text" - }, - { - "type": "list", - "key": "representations", + "key": "_representations", "label": "Representations", "object_type": "text" }, From 4d1eef14f16518b6bac1966021b4700660f2f333 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 12 Oct 2021 16:55:19 +0200 Subject: [PATCH 083/279] Nuke: new unified plugin for all clip data (sequence, video) --- openpype/hosts/nuke/plugins/load/load_clip.py | 340 ++++++++++++++++++ 1 file changed, 340 insertions(+) create mode 100644 openpype/hosts/nuke/plugins/load/load_clip.py diff --git a/openpype/hosts/nuke/plugins/load/load_clip.py b/openpype/hosts/nuke/plugins/load/load_clip.py new file mode 100644 index 0000000000..532d3dba2a --- /dev/null +++ b/openpype/hosts/nuke/plugins/load/load_clip.py @@ -0,0 +1,340 @@ +import nuke +from avalon.vendor import qargparse +from avalon import api, io + +from openpype.hosts.nuke.api.lib import ( + get_imageio_input_colorspace +) + +from openpype.hosts.nuke.api.plugin import ( + get_review_presets_config) + + +class LoadClip(api.Loader): + """Load clip into Nuke + + Either it is image sequence or video file. + """ + + families = [ + "source", + "plate", + "render", + "prerender", + "review" + ] + representations = ([ + "exr", "dpx", "mov", + "review", "mp4"] + + get_review_presets_config()) + + label = "Load Clip" + order = -20 + icon = "file-video-o" + color = "white" + + script_start = nuke.root()["first_frame"].value() + + # option gui + defaults = { + "start_at_workfile": True + } + + options = [ + qargparse.Boolean( + "start_at_workfile", + help="Load at workfile start frame", + default=True + ) + ] + + node_name_template = "" + + @classmethod + def get_representations(cls): + return cls.representations + cls._representations + + def load(self, context, name, namespace, options): + from avalon.nuke import ( + containerise, + viewer_update_and_undo_stop + ) + + start_at_workfile = options.get( + "start_at_workfile", self.defaults["start_at_workfile"]) + + version = context['version'] + version_data = version.get("data", {}) + repr_id = context["representation"]["_id"] + + self.log.info("version_data: {}\n".format(version_data)) + self.log.debug( + "Representation id `{}` ".format(repr_id)) + + self.first_frame = int(nuke.root()["first_frame"].getValue()) + self.handle_start = version_data.get("handleStart", 0) + self.handle_end = version_data.get("handleEnd", 0) + + first = version_data.get("frameStart", None) + last = version_data.get("frameEnd", None) + + # Fallback to asset name when namespace is None + if namespace is None: + namespace = context['asset']['name'] + + first -= self.handle_start + last += self.handle_end + + file = self.fname + + if not file: + repr_id = context["representation"]["_id"] + self.log.warning( + "Representation id `{}` is failing to load".format(repr_id)) + return + + file = file.replace("\\", "/") + + repr_cont = context["representation"]["context"] + assert repr_cont.get("frame"), "Representation is not sequence" + + if "#" not in file: + frame = repr_cont.get("frame") + if frame: + padding = len(frame) + file = file.replace(frame, "#" * padding) + + name_data = { + "asset": repr_cont["asset"], + "subset": repr_cont["subset"], + "representation": context["representation"]["name"], + "ext": repr_cont["representation"], + "id": context["representation"]["_id"], + "class_name": self.__class__.__name__ + } + + read_name = self.node_name_template.format(**name_data) + + # Create the Loader with the filename path set + read_node = nuke.createNode( + "Read", + "name {}".format(read_name)) + + # to avoid multiple undo steps for rest of process + # we will switch off undo-ing + with viewer_update_and_undo_stop(): + read_node["file"].setValue(file) + + # Set colorspace defined in version data + colorspace = context["version"]["data"].get("colorspace") + if colorspace: + read_node["colorspace"].setValue(str(colorspace)) + + preset_clrsp = get_imageio_input_colorspace(file) + + if preset_clrsp is not None: + read_node["colorspace"].setValue(preset_clrsp) + + # set start frame depending on workfile or version + self.loader_shift(read_node, start_at_workfile) + read_node["origfirst"].setValue(int(first)) + read_node["first"].setValue(int(first)) + read_node["origlast"].setValue(int(last)) + read_node["last"].setValue(int(last)) + + # add additional metadata from the version to imprint Avalon knob + add_keys = ["frameStart", "frameEnd", + "source", "colorspace", "author", "fps", "version", + "handleStart", "handleEnd"] + + data_imprint = {} + for k in add_keys: + if k == 'version': + data_imprint.update({k: context["version"]['name']}) + else: + data_imprint.update( + {k: context["version"]['data'].get(k, str(None))}) + + data_imprint.update({"objectName": read_name}) + + read_node["tile_color"].setValue(int("0x4ecd25ff", 16)) + + if version_data.get("retime", None): + speed = version_data.get("speed", 1) + time_warp_nodes = version_data.get("timewarps", []) + self.make_retimes(speed, time_warp_nodes) + + return containerise(read_node, + name=name, + namespace=namespace, + context=context, + loader=self.__class__.__name__, + data=data_imprint) + + def switch(self, container, representation): + self.update(container, representation) + + def update(self, container, representation): + """Update the Loader's path + + Nuke automatically tries to reset some variables when changing + the loader's path to a new file. These automatic changes are to its + inputs: + + """ + + from avalon.nuke import ( + update_container + ) + + read_node = nuke.toNode(container['objectName']) + + assert read_node.Class() == "Read", "Must be Read" + + repr_cont = representation["context"] + assert repr_cont.get("frame"), "Representation is not sequence" + + file = api.get_representation_path(representation) + + if not file: + repr_id = representation["_id"] + self.log.warning( + "Representation id `{}` is failing to load".format(repr_id)) + return + + file = file.replace("\\", "/") + + if "#" not in file: + frame = repr_cont.get("frame") + if frame: + padding = len(frame) + file = file.replace(frame, "#" * padding) + + # Get start frame from version data + version = io.find_one({ + "type": "version", + "_id": representation["parent"] + }) + + # get all versions in list + versions = io.find({ + "type": "version", + "parent": version["parent"] + }).distinct('name') + + max_version = max(versions) + + version_data = version.get("data", {}) + + self.first_frame = int(nuke.root()["first_frame"].getValue()) + self.handle_start = version_data.get("handleStart", 0) + self.handle_end = version_data.get("handleEnd", 0) + + first = version_data.get("frameStart") + last = version_data.get("frameEnd") + + if first is None: + self.log.warning( + "Missing start frame for updated version" + "assuming starts at frame 0 for: " + "{} ({})".format(read_node['name'].value(), representation)) + first = 0 + + first -= self.handle_start + last += self.handle_end + + read_node["file"].setValue(file) + + # set start frame depending on workfile or version + self.loader_shift( + read_node, + bool("start at" in read_node['frame_mode'].value())) + + read_node["origfirst"].setValue(int(first)) + read_node["first"].setValue(int(first)) + read_node["origlast"].setValue(int(last)) + read_node["last"].setValue(int(last)) + + updated_dict = {} + updated_dict.update({ + "representation": str(representation["_id"]), + "frameStart": str(first), + "frameEnd": str(last), + "version": str(version.get("name")), + "colorspace": version_data.get("colorspace"), + "source": version_data.get("source"), + "handleStart": str(self.handle_start), + "handleEnd": str(self.handle_end), + "fps": str(version_data.get("fps")), + "author": version_data.get("author"), + "outputDir": version_data.get("outputDir"), + }) + + # change color of read_node + if version.get("name") not in [max_version]: + read_node["tile_color"].setValue(int("0xd84f20ff", 16)) + else: + read_node["tile_color"].setValue(int("0x4ecd25ff", 16)) + + if version_data.get("retime", None): + speed = version_data.get("speed", 1) + time_warp_nodes = version_data.get("timewarps", []) + self.make_retimes(speed, time_warp_nodes) + + # Update the imprinted representation + update_container( + read_node, + updated_dict + ) + self.log.info("udated to version: {}".format(version.get("name"))) + + def remove(self, container): + + from avalon.nuke import viewer_update_and_undo_stop + + read_node = nuke.toNode(container['objectName']) + assert read_node.Class() == "Read", "Must be Read" + + with viewer_update_and_undo_stop(): + nuke.delete(read_node) + + def make_retimes(self, speed, time_warp_nodes): + ''' Create all retime and timewarping nodes with coppied animation ''' + if speed != 1: + rtn = nuke.createNode( + "Retime", + "speed {}".format(speed)) + rtn["before"].setValue("continue") + rtn["after"].setValue("continue") + rtn["input.first_lock"].setValue(True) + rtn["input.first"].setValue( + self.first_frame + ) + + if time_warp_nodes != []: + start_anim = self.first_frame + (self.handle_start / speed) + for timewarp in time_warp_nodes: + twn = nuke.createNode(timewarp["Class"], + "name {}".format(timewarp["name"])) + if isinstance(timewarp["lookup"], list): + # if array for animation + twn["lookup"].setAnimated() + for i, value in enumerate(timewarp["lookup"]): + twn["lookup"].setValueAt( + (start_anim + i) + value, + (start_anim + i)) + else: + # if static value `int` + twn["lookup"].setValue(timewarp["lookup"]) + + def loader_shift(self, read_node, workfile_start=False): + """ Set start frame of read node to a workfile start + + Args: + read_node (nuke.Node): The nuke's read node + workfile_start (bool): set workfile start frame if true + + """ + if workfile_start: + read_node['frame_mode'].setValue("start at") + read_node['frame'].setValue(str(self.script_start)) From 6177281a7fca6f1eba796a03078ac60b444ca5f9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 12 Oct 2021 17:01:19 +0200 Subject: [PATCH 084/279] Nuke: improving code of get_representation on loadClip --- openpype/hosts/nuke/plugins/load/load_clip.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/nuke/plugins/load/load_clip.py b/openpype/hosts/nuke/plugins/load/load_clip.py index 532d3dba2a..97b91b53a3 100644 --- a/openpype/hosts/nuke/plugins/load/load_clip.py +++ b/openpype/hosts/nuke/plugins/load/load_clip.py @@ -23,10 +23,13 @@ class LoadClip(api.Loader): "prerender", "review" ] - representations = ([ - "exr", "dpx", "mov", - "review", "mp4"] - + get_review_presets_config()) + representations = [ + "exr", + "dpx", + "mov", + "review", + "mp4" + ] label = "Load Clip" order = -20 @@ -52,7 +55,11 @@ class LoadClip(api.Loader): @classmethod def get_representations(cls): - return cls.representations + cls._representations + return ( + cls.representations + + cls._representations + + get_review_presets_config() + ) def load(self, context, name, namespace, options): from avalon.nuke import ( From 9a662f1a5d7b6b4b115ec5fecf5adcb0437a433f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 12 Oct 2021 17:03:33 +0200 Subject: [PATCH 085/279] Nuke: returning back node name template --- openpype/hosts/nuke/plugins/load/load_clip.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/plugins/load/load_clip.py b/openpype/hosts/nuke/plugins/load/load_clip.py index 97b91b53a3..f4fb765a43 100644 --- a/openpype/hosts/nuke/plugins/load/load_clip.py +++ b/openpype/hosts/nuke/plugins/load/load_clip.py @@ -51,7 +51,7 @@ class LoadClip(api.Loader): ) ] - node_name_template = "" + node_name_template = "{class_name}_{ext}" @classmethod def get_representations(cls): From 7991c806d645b91921557cf1bd0a62590982d7b9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 12 Oct 2021 17:35:39 +0200 Subject: [PATCH 086/279] Nuke: LoadClip wip integrating video file loading --- openpype/hosts/nuke/plugins/load/load_clip.py | 43 +++++++++---------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/openpype/hosts/nuke/plugins/load/load_clip.py b/openpype/hosts/nuke/plugins/load/load_clip.py index f4fb765a43..0bb030564a 100644 --- a/openpype/hosts/nuke/plugins/load/load_clip.py +++ b/openpype/hosts/nuke/plugins/load/load_clip.py @@ -36,7 +36,7 @@ class LoadClip(api.Loader): icon = "file-video-o" color = "white" - script_start = nuke.root()["first_frame"].value() + script_start = int(nuke.root()["first_frame"].value()) # option gui defaults = { @@ -66,6 +66,9 @@ class LoadClip(api.Loader): containerise, viewer_update_and_undo_stop ) + is_sequence = len(context["representation"]["files"]) <= 1 + + file = self.fname.replace("\\", "/") start_at_workfile = options.get( "start_at_workfile", self.defaults["start_at_workfile"]) @@ -73,44 +76,42 @@ class LoadClip(api.Loader): version = context['version'] version_data = version.get("data", {}) repr_id = context["representation"]["_id"] + colorspace = version_data.get("colorspace") + repr_cont = context["representation"]["context"] self.log.info("version_data: {}\n".format(version_data)) self.log.debug( "Representation id `{}` ".format(repr_id)) - self.first_frame = int(nuke.root()["first_frame"].getValue()) self.handle_start = version_data.get("handleStart", 0) self.handle_end = version_data.get("handleEnd", 0) first = version_data.get("frameStart", None) last = version_data.get("frameEnd", None) + first -= self.handle_start + last += self.handle_end + + if not is_sequence: + duration = last - first + 1 + first = 1 + last = first + duration + elif "#" not in file: + frame = repr_cont.get("frame") + assert frame, "Representation is not sequence" + + padding = len(frame) + file = file.replace(frame, "#" * padding) # Fallback to asset name when namespace is None if namespace is None: namespace = context['asset']['name'] - first -= self.handle_start - last += self.handle_end - - file = self.fname - if not file: repr_id = context["representation"]["_id"] self.log.warning( "Representation id `{}` is failing to load".format(repr_id)) return - file = file.replace("\\", "/") - - repr_cont = context["representation"]["context"] - assert repr_cont.get("frame"), "Representation is not sequence" - - if "#" not in file: - frame = repr_cont.get("frame") - if frame: - padding = len(frame) - file = file.replace(frame, "#" * padding) - name_data = { "asset": repr_cont["asset"], "subset": repr_cont["subset"], @@ -133,7 +134,6 @@ class LoadClip(api.Loader): read_node["file"].setValue(file) # Set colorspace defined in version data - colorspace = context["version"]["data"].get("colorspace") if colorspace: read_node["colorspace"].setValue(str(colorspace)) @@ -233,7 +233,6 @@ class LoadClip(api.Loader): version_data = version.get("data", {}) - self.first_frame = int(nuke.root()["first_frame"].getValue()) self.handle_start = version_data.get("handleStart", 0) self.handle_end = version_data.get("handleEnd", 0) @@ -315,11 +314,11 @@ class LoadClip(api.Loader): rtn["after"].setValue("continue") rtn["input.first_lock"].setValue(True) rtn["input.first"].setValue( - self.first_frame + self.script_start ) if time_warp_nodes != []: - start_anim = self.first_frame + (self.handle_start / speed) + start_anim = self.script_start + (self.handle_start / speed) for timewarp in time_warp_nodes: twn = nuke.createNode(timewarp["Class"], "name {}".format(timewarp["name"])) From eb4cd6d7c622175e1204a0a0c9da027b05e20b07 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 12 Oct 2021 18:27:32 +0200 Subject: [PATCH 087/279] PYPE-1901 - extracted method for task parsing --- .../publish/collect_remote_instances.py | 8 ++++-- .../publish/collect_published_files.py | 17 ++++------- openpype/lib/plugin_tools.py | 28 +++++++++++++++++++ 3 files changed, 39 insertions(+), 14 deletions(-) diff --git a/openpype/hosts/photoshop/plugins/publish/collect_remote_instances.py b/openpype/hosts/photoshop/plugins/publish/collect_remote_instances.py index fa4364b700..62d94483e5 100644 --- a/openpype/hosts/photoshop/plugins/publish/collect_remote_instances.py +++ b/openpype/hosts/photoshop/plugins/publish/collect_remote_instances.py @@ -3,6 +3,7 @@ import os from avalon import photoshop from openpype.lib import prepare_template_data +from openpype.lib.plugin_tools import parse_json class CollectRemoteInstances(pyblish.api.ContextPlugin): @@ -36,8 +37,11 @@ class CollectRemoteInstances(pyblish.api.ContextPlugin): variant = "Main" if batch_dir and os.path.exists(batch_dir): # TODO check if batch manifest is same as tasks manifests - task_data = self.parse_json(os.path.join(batch_dir, - "manifest.json")) + task_data = parse_json(os.path.join(batch_dir, + "manifest.json")) + if not task_data: + raise ValueError( + "Cannot parse batch meta in {} folder".format(batch_dir)) variant = task_data["variant"] stub = photoshop.stub() diff --git a/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py b/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py index 2b4a1273b8..ecd65ebae4 100644 --- a/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py +++ b/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py @@ -15,7 +15,7 @@ import tempfile import pyblish.api from avalon import io from openpype.lib import prepare_template_data -from openpype.lib.plugin_tools import parse_json +from openpype.lib.plugin_tools import parse_json, get_batch_asset_task_info class CollectPublishedFiles(pyblish.api.ContextPlugin): @@ -45,18 +45,11 @@ class CollectPublishedFiles(pyblish.api.ContextPlugin): "manifest.json")) self.log.info("task_data:: {}".format(task_data)) ctx = task_data["context"] - task_type = "default_task_type" - task_name = None - if ctx["type"] == "task": - items = ctx["path"].split('/') - asset = items[-2] - os.environ["AVALON_TASK"] = ctx["name"] - task_name = ctx["name"] - task_type = ctx["attributes"]["type"] - else: - asset = ctx["name"] - os.environ["AVALON_TASK"] = "" + 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 diff --git a/openpype/lib/plugin_tools.py b/openpype/lib/plugin_tools.py index 2158a3e28d..62a9d7c51e 100644 --- a/openpype/lib/plugin_tools.py +++ b/openpype/lib/plugin_tools.py @@ -486,6 +486,13 @@ def should_decompress(file_url): def parse_json(path): + """Parses json file at 'path' location + + Returns: + (dict) or None if unparsable + Raises: + AsssertionError if 'path' doesn't exist + """ path = path.strip('\"') assert os.path.isfile(path), ( "Path to json file doesn't exist. \"{}\"".format(path) @@ -500,3 +507,24 @@ def parse_json(path): "{} - Exception: {}".format(path, exc) ) return data + + +def get_batch_asset_task_info(ctx): + """Parses context data from webpublisher's batch metadata + + Returns: + (tuple): asset, task_name (Optional), task_type + """ + task_type = "default_task_type" + task_name = None + asset = None + + if ctx["type"] == "task": + items = ctx["path"].split('/') + asset = items[-2] + task_name = ctx["name"] + task_type = ctx["attributes"]["type"] + else: + asset = ctx["name"] + + return asset, task_name, task_type From 5d1a83a28ab886b624866920aa17158b3a56dc95 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 12 Oct 2021 18:42:57 +0200 Subject: [PATCH 088/279] PYPE-1901 - add resolving of ftrack username to remote publish of Photoshop too --- .../ftrack/plugins/publish/collect_username.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) 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 39b7433e11..438ef2f31b 100644 --- a/openpype/modules/default_modules/ftrack/plugins/publish/collect_username.py +++ b/openpype/modules/default_modules/ftrack/plugins/publish/collect_username.py @@ -26,14 +26,20 @@ class CollectUsername(pyblish.api.ContextPlugin): """ order = pyblish.api.CollectorOrder - 0.488 label = "Collect ftrack username" - hosts = ["webpublisher"] + hosts = ["webpublisher", "photoshop"] _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") + os.environ["FTRACK_API_USER"] = os.environ["FTRACK_BOT_API_USER"] os.environ["FTRACK_API_KEY"] = os.environ["FTRACK_BOT_API_KEY"] - self.log.info("CollectUsername") + for instance in context: email = instance.data["user_email"] self.log.info("email:: {}".format(email)) From 7d3c1863c278bbbae27aac9c5a34f0eb0736bfca Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 12 Oct 2021 19:25:55 +0200 Subject: [PATCH 089/279] PYPE-1901 - working remotepublishfromapp command --- openpype/pype_commands.py | 83 ++++++++++++++++++++++++++++----------- 1 file changed, 59 insertions(+), 24 deletions(-) diff --git a/openpype/pype_commands.py b/openpype/pype_commands.py index 4f3e173f3e..e06ab5b493 100644 --- a/openpype/pype_commands.py +++ b/openpype/pype_commands.py @@ -8,6 +8,7 @@ import time from openpype.lib import PypeLogger from openpype.api import get_app_environments_for_context +from openpype.lib.plugin_tools import parse_json, get_batch_asset_task_info class PypeCommands: @@ -112,7 +113,17 @@ class PypeCommands: uninstall() @staticmethod - def remotepublishfromapp(project, batch_path, host, user, targets=None): + def remotepublishfromapp(project, batch_dir, host, user, targets=None): + """Opens installed variant of 'host' and run remote publish there. + + Currently implemented and tested for Photoshop where customer + wants to process uploaded .psd file and publish collected layers + from there. + + Requires installed host application on the machine. + + Runs publish process as user would, in automatic fashion. + """ from openpype import install, uninstall from openpype.api import Logger @@ -122,36 +133,60 @@ class PypeCommands: install() - os.environ["OPENPYPE_PUBLISH_DATA"] = batch_path - os.environ["AVALON_PROJECT"] = project - os.environ["AVALON_APP"] = host - os.environ["AVALON_APP_NAME"] = os.environ["AVALON_APP"] + "/2020" - os.environ["AVALON_ASSET"] = "test_asset" - os.environ["AVALON_TASK"] = "test_task" + from openpype.lib import ApplicationManager + application_manager = ApplicationManager() + app_group = application_manager.app_groups.get(host) + if not app_group or not app_group.enabled: + raise ValueError("No application {} configured".format(host)) + + found_variant_key = None + # finds most up-to-date variant if any installed + for variant_key, variant in app_group.variants.items(): + for executable in variant.executables: + if executable.exists(): + found_variant_key = variant_key + + if not found_variant_key: + raise ValueError("No executable for {} found".format(host)) + + app_name = "{}/{}".format(host, found_variant_key) + + os.environ["OPENPYPE_PUBLISH_DATA"] = batch_dir + + batch_data = None + if batch_dir and os.path.exists(batch_dir): + # TODO check if batch manifest is same as tasks manifests + 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"]) + + workfile_path = os.path.join(batch_dir, + batch_data["task"], + batch_data["files"][0]) + print("workfile_path {}".format(workfile_path)) + data = { + "last_workfile_path": workfile_path, + "start_last_workfile": True + } + + # must have for proper launch of app env = get_app_environments_for_context( - os.environ["AVALON_PROJECT"], - os.environ["AVALON_ASSET"], - os.environ["AVALON_TASK"], - os.environ["AVALON_APP_NAME"] + project, + asset, + task_name, + app_name ) os.environ.update(env) - os.environ["OPENPYPE_EXECUTABLE"] = sys.executable os.environ["IS_HEADLESS"] = "true" - from openpype.lib import ApplicationManager - application_manager = ApplicationManager() - data = { - "last_workfile_path": "c:/projects/test_project_test_asset_TestTask_v001.psd", - "start_last_workfile": True, - "project_name": project, - "asset_name": "test_asset", - "task_name": "test_task" - } - - launched_app = application_manager.launch( - os.environ["AVALON_APP"] + "/2020", **data) + launched_app = application_manager.launch(app_name, **data) while launched_app.poll() is None: time.sleep(0.5) From 26790f65f0c51b8ac61a01dbc8cf8bed839242f7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 13 Oct 2021 10:08:37 +0200 Subject: [PATCH 090/279] openpype tray does not require to use it's qapplication --- openpype/tools/tray/pype_tray.py | 77 +++++++++++++++++++++----------- 1 file changed, 51 insertions(+), 26 deletions(-) diff --git a/openpype/tools/tray/pype_tray.py b/openpype/tools/tray/pype_tray.py index 3050e206ce..0f817d7130 100644 --- a/openpype/tools/tray/pype_tray.py +++ b/openpype/tools/tray/pype_tray.py @@ -268,7 +268,6 @@ class SystemTrayIcon(QtWidgets.QSystemTrayIcon): # Set modules self.tray_man = TrayManager(self, self.parent) - self.tray_man.initialize_modules() # Add menu to Context of SystemTrayIcon self.setContextMenu(self.menu) @@ -291,6 +290,17 @@ class SystemTrayIcon(QtWidgets.QSystemTrayIcon): self._doubleclick = False self._click_pos = None + self._initializing_modules = False + + @property + def initializing_modules(self): + return self._initializing_modules + + def initialize_modules(self): + self._initializing_modules = True + self.tray_man.initialize_modules() + self._initializing_modules = False + def _click_timer_timeout(self): self._click_timer.stop() doubleclick = self._doubleclick @@ -334,38 +344,48 @@ class SystemTrayIcon(QtWidgets.QSystemTrayIcon): QtCore.QCoreApplication.exit() -class TrayMainWindow(QtWidgets.QMainWindow): - """ TrayMainWindow is base of Pype application. - - Every widget should have set this window as parent because - QSystemTrayIcon widget is not allowed to be a parent of any widget. - """ - +class PypeTrayStarter(QtCore.QObject): def __init__(self, app): - super(TrayMainWindow, self).__init__() - self.app = app + app.setQuitOnLastWindowClosed(False) + self._app = app + self._splash = None - self.tray_widget = SystemTrayIcon(self) - self.tray_widget.show() + main_window = QtWidgets.QMainWindow() + tray_widget = SystemTrayIcon(main_window) + start_timer = QtCore.QTimer() + start_timer.setInterval(100) + start_timer.start() -class PypeTrayApplication(QtWidgets.QApplication): - """Qt application manages application's control flow.""" + start_timer.timeout.connect(self._on_start_timer) - def __init__(self): - super(PypeTrayApplication, self).__init__(sys.argv) - # Allows to close widgets without exiting app - self.setQuitOnLastWindowClosed(False) + self._main_window = main_window + self._tray_widget = tray_widget + self._timer_counter = 0 + self._start_timer = start_timer - # Sets up splash - splash_widget = self.set_splash() + def _on_start_timer(self): + if self._timer_counter == 0: + self._timer_counter += 1 + splash = self._get_splash() + splash.show() + self._tray_widget.show() - splash_widget.show() - self.processEvents() - self.main_window = TrayMainWindow(self) - splash_widget.hide() + elif self._timer_counter == 1: + self._timer_counter += 1 + self._tray_widget.initialize_modules() - def set_splash(self): + elif not self._tray_widget.initializing_modules: + splash = self._get_splash() + splash.hide() + self._start_timer.stop() + + def _get_splash(self): + if self._splash is None: + self._splash = self._create_splash() + return self._splash + + def _create_splash(self): splash_pix = QtGui.QPixmap(resources.get_openpype_splash_filepath()) splash = QtWidgets.QSplashScreen(splash_pix) splash.setMask(splash_pix.mask()) @@ -377,7 +397,12 @@ class PypeTrayApplication(QtWidgets.QApplication): def main(): - app = PypeTrayApplication() + app = QtWidgets.QApplication.instance() + if not app: + app = QtWidgets.QApplication([]) + + starter = PypeTrayStarter(app) + # TODO remove when pype.exe will have an icon if os.name == "nt": import ctypes From c9b760fcdee596771434c1ce8141198ff0aaa4ed Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 13 Oct 2021 10:55:21 +0200 Subject: [PATCH 091/279] stop all threads in ftrack --- openpype/modules/default_modules/ftrack/ftrack_module.py | 2 +- openpype/modules/default_modules/ftrack/tray/ftrack_tray.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/modules/default_modules/ftrack/ftrack_module.py b/openpype/modules/default_modules/ftrack/ftrack_module.py index 5a1fdbc276..73a4dfee82 100644 --- a/openpype/modules/default_modules/ftrack/ftrack_module.py +++ b/openpype/modules/default_modules/ftrack/ftrack_module.py @@ -371,7 +371,7 @@ class FtrackModule( return self.tray_module.validate() def tray_exit(self): - return self.tray_module.stop_action_server() + self.tray_module.tray_exit() def set_credentials_to_env(self, username, api_key): os.environ["FTRACK_API_USER"] = username or "" diff --git a/openpype/modules/default_modules/ftrack/tray/ftrack_tray.py b/openpype/modules/default_modules/ftrack/tray/ftrack_tray.py index 34e4646767..c6201a94f6 100644 --- a/openpype/modules/default_modules/ftrack/tray/ftrack_tray.py +++ b/openpype/modules/default_modules/ftrack/tray/ftrack_tray.py @@ -289,6 +289,10 @@ class FtrackTrayWrapper: parent_menu.addMenu(tray_menu) + def tray_exit(self): + self.stop_action_server() + self.stop_timer_thread() + # Definition of visibility of each menu actions def set_menu_visibility(self): self.tray_server_menu.menuAction().setVisible(self.bool_logged) From 339df3a586b3fc7c61e8e9755940d2f775f1e14f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 13 Oct 2021 10:58:20 +0200 Subject: [PATCH 092/279] wait for idle manager stop --- .../modules/default_modules/timers_manager/timers_manager.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/modules/default_modules/timers_manager/timers_manager.py b/openpype/modules/default_modules/timers_manager/timers_manager.py index 1199db0611..7687d056f8 100644 --- a/openpype/modules/default_modules/timers_manager/timers_manager.py +++ b/openpype/modules/default_modules/timers_manager/timers_manager.py @@ -150,6 +150,7 @@ class TimersManager(OpenPypeModule, ITrayService): def tray_exit(self): if self._idle_manager: self._idle_manager.stop() + self._idle_manager.wait() def start_timer(self, project_name, asset_name, task_name, hierarchy): """ From d085e4ff7df89cfe4e770fd9ae0894e8506265c0 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 13 Oct 2021 13:03:47 +0200 Subject: [PATCH 093/279] PYPE-1901 - switch to context plugin to limit double closing --- openpype/hosts/photoshop/plugins/publish/closePS.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/photoshop/plugins/publish/closePS.py b/openpype/hosts/photoshop/plugins/publish/closePS.py index ce229c86bb..fa9d27688b 100644 --- a/openpype/hosts/photoshop/plugins/publish/closePS.py +++ b/openpype/hosts/photoshop/plugins/publish/closePS.py @@ -6,7 +6,7 @@ import pyblish.api from avalon import photoshop -class ClosePS(pyblish.api.InstancePlugin): +class ClosePS(pyblish.api.ContextPlugin): """Close PS after publish. For Webpublishing only. """ @@ -17,7 +17,7 @@ class ClosePS(pyblish.api.InstancePlugin): hosts = ["photoshop"] - def process(self, instance): + def process(self, context): self.log.info("ClosePS") if not os.environ.get("IS_HEADLESS"): return @@ -26,3 +26,4 @@ class ClosePS(pyblish.api.InstancePlugin): self.log.info("Shutting down PS") stub.save() stub.close() + self.log.info("PS closed") From 15b67e239ba7a6619b13083ff461bf376aa34485 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 13 Oct 2021 14:44:51 +0200 Subject: [PATCH 094/279] PYPE-1901 - fix missing return for standard publishing --- .../default_modules/ftrack/plugins/publish/collect_username.py | 1 + 1 file changed, 1 insertion(+) 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 438ef2f31b..844a397066 100644 --- a/openpype/modules/default_modules/ftrack/plugins/publish/collect_username.py +++ b/openpype/modules/default_modules/ftrack/plugins/publish/collect_username.py @@ -36,6 +36,7 @@ class CollectUsername(pyblish.api.ContextPlugin): 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"] From 620909d0c7a6c328037cd88cfa312710032ccd68 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 13 Oct 2021 16:25:57 +0200 Subject: [PATCH 095/279] nuke: loadClip with update and retime creating parent nukeLoader --- openpype/hosts/nuke/api/plugin.py | 55 ++++ openpype/hosts/nuke/plugins/load/load_clip.py | 295 ++++++++++-------- 2 files changed, 215 insertions(+), 135 deletions(-) diff --git a/openpype/hosts/nuke/api/plugin.py b/openpype/hosts/nuke/api/plugin.py index 71329c0d46..62eadecaf4 100644 --- a/openpype/hosts/nuke/api/plugin.py +++ b/openpype/hosts/nuke/api/plugin.py @@ -1,4 +1,10 @@ +import random +import string + import avalon.nuke +from avalon.nuke import lib as anlib +from avalon import api + from openpype.api import ( get_current_project_settings, PypeCreatorMixin @@ -39,3 +45,52 @@ def get_review_presets_config(): outputs.update(profile.get("outputs", {})) return [str(name) for name, _prop in outputs.items()] + + +class NukeLoader(api.Loader): + container_id_knob = "containerId" + container_id = ''.join(random.choice( + string.ascii_uppercase + string.digits) for _ in range(10)) + + def get_container_id(self, node): + id_knob = node.knobs().get(self.container_id_knob) + return id_knob.value() if id_knob else None + + def get_members(self, source): + """Return nodes that has same 'containerId' as `source`""" + source_id = self.get_container_id(source) + return [node for node in nuke.allNodes(recurseGroups=True) + if self.get_container_id(node) == source_id + and node is not source] if source_id else [] + + def set_as_member(self, node): + source_id = self.get_container_id(node) + + if source_id: + node[self.container_id_knob].setValue(self.container_id) + else: + HIDEN_FLAG = 0x00040000 + _knob = anlib.Knobby( + "String_Knob", + self.container_id, + flags=[nuke.READ_ONLY, HIDEN_FLAG]) + knob = _knob.create(self.container_id_knob) + node.addKnob(knob) + + def clear_members(self, parent_node): + members = self.get_members(parent_node) + + dependent_nodes = None + for node in members: + _depndc = [n for n in node.dependent() if n not in members] + if not _depndc: + continue + + dependent_nodes = _depndc + break + + for member in members: + self.log.info("removing node: `{}".format(member.name())) + nuke.delete(member) + + return dependent_nodes diff --git a/openpype/hosts/nuke/plugins/load/load_clip.py b/openpype/hosts/nuke/plugins/load/load_clip.py index 0bb030564a..f56120ae0a 100644 --- a/openpype/hosts/nuke/plugins/load/load_clip.py +++ b/openpype/hosts/nuke/plugins/load/load_clip.py @@ -5,12 +5,18 @@ from avalon import api, io from openpype.hosts.nuke.api.lib import ( get_imageio_input_colorspace ) +from avalon.nuke import ( + containerise, + update_container, + viewer_update_and_undo_stop, + maintained_selection +) +from openpype.hosts.nuke.api import plugin -from openpype.hosts.nuke.api.plugin import ( - get_review_presets_config) +reload(plugin) -class LoadClip(api.Loader): +class LoadClip(plugin.NukeLoader): """Load clip into Nuke Either it is image sequence or video file. @@ -58,15 +64,12 @@ class LoadClip(api.Loader): return ( cls.representations + cls._representations - + get_review_presets_config() + + plugin.get_review_presets_config() ) def load(self, context, name, namespace, options): - from avalon.nuke import ( - containerise, - viewer_update_and_undo_stop - ) - is_sequence = len(context["representation"]["files"]) <= 1 + + is_sequence = len(context["representation"]["files"]) > 1 file = self.fname.replace("\\", "/") @@ -77,6 +80,7 @@ class LoadClip(api.Loader): version_data = version.get("data", {}) repr_id = context["representation"]["_id"] colorspace = version_data.get("colorspace") + iio_colorspace = get_imageio_input_colorspace(file) repr_cont = context["representation"]["context"] self.log.info("version_data: {}\n".format(version_data)) @@ -107,7 +111,6 @@ class LoadClip(api.Loader): namespace = context['asset']['name'] if not file: - repr_id = context["representation"]["_id"] self.log.warning( "Representation id `{}` is failing to load".format(repr_id)) return @@ -127,6 +130,7 @@ class LoadClip(api.Loader): read_node = nuke.createNode( "Read", "name {}".format(read_name)) + self.set_as_member(read_node) # to avoid multiple undo steps for rest of process # we will switch off undo-ing @@ -136,18 +140,11 @@ class LoadClip(api.Loader): # Set colorspace defined in version data if colorspace: read_node["colorspace"].setValue(str(colorspace)) + elif iio_colorspace is not None: + read_node["colorspace"].setValue(iio_colorspace) - preset_clrsp = get_imageio_input_colorspace(file) + self.set_range_to_node(read_node, first, last, start_at_workfile) - if preset_clrsp is not None: - read_node["colorspace"].setValue(preset_clrsp) - - # set start frame depending on workfile or version - self.loader_shift(read_node, start_at_workfile) - read_node["origfirst"].setValue(int(first)) - read_node["first"].setValue(int(first)) - read_node["origlast"].setValue(int(last)) - read_node["last"].setValue(int(last)) # add additional metadata from the version to imprint Avalon knob add_keys = ["frameStart", "frameEnd", @@ -166,17 +163,18 @@ class LoadClip(api.Loader): read_node["tile_color"].setValue(int("0x4ecd25ff", 16)) - if version_data.get("retime", None): - speed = version_data.get("speed", 1) - time_warp_nodes = version_data.get("timewarps", []) - self.make_retimes(speed, time_warp_nodes) + container = containerise( + read_node, + name=name, + namespace=namespace, + context=context, + loader=self.__class__.__name__, + data=data_imprint) - return containerise(read_node, - name=name, - namespace=namespace, - context=context, - loader=self.__class__.__name__, - data=data_imprint) + if version_data.get("retime", None): + self.make_retimes(read_node, version_data) + + return container def switch(self, container, representation): self.update(container, representation) @@ -190,109 +188,111 @@ class LoadClip(api.Loader): """ - from avalon.nuke import ( - update_container - ) + is_sequence = len(representation["files"]) > 1 read_node = nuke.toNode(container['objectName']) + file = api.get_representation_path(representation).replace("\\", "/") - assert read_node.Class() == "Read", "Must be Read" + start_at_workfile = bool("start at" in read_node['frame_mode'].value()) - repr_cont = representation["context"] - assert repr_cont.get("frame"), "Representation is not sequence" - - file = api.get_representation_path(representation) - - if not file: - repr_id = representation["_id"] - self.log.warning( - "Representation id `{}` is failing to load".format(repr_id)) - return - - file = file.replace("\\", "/") - - if "#" not in file: - frame = repr_cont.get("frame") - if frame: - padding = len(frame) - file = file.replace(frame, "#" * padding) - - # Get start frame from version data version = io.find_one({ "type": "version", "_id": representation["parent"] }) - - # get all versions in list - versions = io.find({ - "type": "version", - "parent": version["parent"] - }).distinct('name') - - max_version = max(versions) - version_data = version.get("data", {}) + repr_id = representation["_id"] + colorspace = version_data.get("colorspace") + iio_colorspace = get_imageio_input_colorspace(file) + repr_cont = representation["context"] self.handle_start = version_data.get("handleStart", 0) self.handle_end = version_data.get("handleEnd", 0) - first = version_data.get("frameStart") - last = version_data.get("frameEnd") - - if first is None: - self.log.warning( - "Missing start frame for updated version" - "assuming starts at frame 0 for: " - "{} ({})".format(read_node['name'].value(), representation)) - first = 0 - + first = version_data.get("frameStart", None) + last = version_data.get("frameEnd", None) first -= self.handle_start last += self.handle_end + if not is_sequence: + duration = last - first + 1 + first = 1 + last = first + duration + elif "#" not in file: + frame = repr_cont.get("frame") + assert frame, "Representation is not sequence" + + padding = len(frame) + file = file.replace(frame, "#" * padding) + + if not file: + self.log.warning( + "Representation id `{}` is failing to load".format(repr_id)) + return + read_node["file"].setValue(file) - # set start frame depending on workfile or version - self.loader_shift( - read_node, - bool("start at" in read_node['frame_mode'].value())) + # to avoid multiple undo steps for rest of process + # we will switch off undo-ing + with viewer_update_and_undo_stop(): - read_node["origfirst"].setValue(int(first)) - read_node["first"].setValue(int(first)) - read_node["origlast"].setValue(int(last)) - read_node["last"].setValue(int(last)) + # Set colorspace defined in version data + if colorspace: + read_node["colorspace"].setValue(str(colorspace)) + elif iio_colorspace is not None: + read_node["colorspace"].setValue(iio_colorspace) - updated_dict = {} - updated_dict.update({ - "representation": str(representation["_id"]), - "frameStart": str(first), - "frameEnd": str(last), - "version": str(version.get("name")), - "colorspace": version_data.get("colorspace"), - "source": version_data.get("source"), - "handleStart": str(self.handle_start), - "handleEnd": str(self.handle_end), - "fps": str(version_data.get("fps")), - "author": version_data.get("author"), - "outputDir": version_data.get("outputDir"), - }) + self.set_range_to_node(read_node, first, last, start_at_workfile) - # change color of read_node - if version.get("name") not in [max_version]: - read_node["tile_color"].setValue(int("0xd84f20ff", 16)) - else: - read_node["tile_color"].setValue(int("0x4ecd25ff", 16)) + updated_dict = { + "representation": str(representation["_id"]), + "frameStart": str(first), + "frameEnd": str(last), + "version": str(version.get("name")), + "colorspace": colorspace, + "source": version_data.get("source"), + "handleStart": str(self.handle_start), + "handleEnd": str(self.handle_end), + "fps": str(version_data.get("fps")), + "author": version_data.get("author"), + "outputDir": version_data.get("outputDir"), + } + + # change color of read_node + # get all versions in list + versions = io.find({ + "type": "version", + "parent": version["parent"] + }).distinct('name') + + max_version = max(versions) + + if version.get("name") not in [max_version]: + read_node["tile_color"].setValue(int("0xd84f20ff", 16)) + else: + read_node["tile_color"].setValue(int("0x4ecd25ff", 16)) + + # Update the imprinted representation + update_container( + read_node, + updated_dict + ) + self.log.info("udated to version: {}".format(version.get("name"))) if version_data.get("retime", None): - speed = version_data.get("speed", 1) - time_warp_nodes = version_data.get("timewarps", []) - self.make_retimes(speed, time_warp_nodes) + self.make_retimes(read_node, version_data) + else: + self.clear_members(read_node) - # Update the imprinted representation - update_container( - read_node, - updated_dict - ) - self.log.info("udated to version: {}".format(version.get("name"))) + self.set_as_member(read_node) + + def set_range_to_node(self, read_node, first, last, start_at_workfile): + read_node['origfirst'].setValue(int(first)) + read_node['first'].setValue(int(first)) + read_node['origlast'].setValue(int(last)) + read_node['last'].setValue(int(last)) + + # set start frame depending on workfile or version + self.loader_shift(read_node, start_at_workfile) def remove(self, container): @@ -302,36 +302,61 @@ class LoadClip(api.Loader): assert read_node.Class() == "Read", "Must be Read" with viewer_update_and_undo_stop(): + members = self.get_members(read_node) nuke.delete(read_node) + for member in members: + nuke.delete(member) - def make_retimes(self, speed, time_warp_nodes): + def make_retimes(self, parent_node, version_data): ''' Create all retime and timewarping nodes with coppied animation ''' - if speed != 1: - rtn = nuke.createNode( - "Retime", - "speed {}".format(speed)) - rtn["before"].setValue("continue") - rtn["after"].setValue("continue") - rtn["input.first_lock"].setValue(True) - rtn["input.first"].setValue( - self.script_start - ) + speed = version_data.get('speed', 1) + time_warp_nodes = version_data.get('timewarps', []) + last_node = None + source_id = self.get_container_id(parent_node) + self.log.info("__ source_id: {}".format(source_id)) + self.log.info("__ members: {}".format(self.get_members(parent_node))) + dependent_nodes = self.clear_members(parent_node) - if time_warp_nodes != []: - start_anim = self.script_start + (self.handle_start / speed) - for timewarp in time_warp_nodes: - twn = nuke.createNode(timewarp["Class"], - "name {}".format(timewarp["name"])) - if isinstance(timewarp["lookup"], list): - # if array for animation - twn["lookup"].setAnimated() - for i, value in enumerate(timewarp["lookup"]): - twn["lookup"].setValueAt( - (start_anim + i) + value, - (start_anim + i)) - else: - # if static value `int` - twn["lookup"].setValue(timewarp["lookup"]) + with maintained_selection(): + parent_node['selected'].setValue(True) + + if speed != 1: + rtn = nuke.createNode( + "Retime", + "speed {}".format(speed)) + + rtn["before"].setValue("continue") + rtn["after"].setValue("continue") + rtn["input.first_lock"].setValue(True) + rtn["input.first"].setValue( + self.script_start + ) + self.set_as_member(rtn) + last_node = rtn + + if time_warp_nodes != []: + start_anim = self.script_start + (self.handle_start / speed) + for timewarp in time_warp_nodes: + twn = nuke.createNode(timewarp["Class"], + "name {}".format(timewarp["name"])) + if isinstance(timewarp["lookup"], list): + # if array for animation + twn["lookup"].setAnimated() + for i, value in enumerate(timewarp["lookup"]): + twn["lookup"].setValueAt( + (start_anim + i) + value, + (start_anim + i)) + else: + # if static value `int` + twn["lookup"].setValue(timewarp["lookup"]) + + self.set_as_member(twn) + last_node = twn + + if dependent_nodes: + # connect to original inputs + for i, n in enumerate(dependent_nodes): + last_node.setInput(i, n) def loader_shift(self, read_node, workfile_start=False): """ Set start frame of read node to a workfile start From e4dc590242975ae6bca97e037db9d66697de1e74 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 13 Oct 2021 16:26:41 +0200 Subject: [PATCH 096/279] nuke: removing obsolete loader plugins --- openpype/hosts/nuke/plugins/load/load_mov.py | 347 ------------------ .../hosts/nuke/plugins/load/load_sequence.py | 320 ---------------- 2 files changed, 667 deletions(-) delete mode 100644 openpype/hosts/nuke/plugins/load/load_mov.py delete mode 100644 openpype/hosts/nuke/plugins/load/load_sequence.py diff --git a/openpype/hosts/nuke/plugins/load/load_mov.py b/openpype/hosts/nuke/plugins/load/load_mov.py deleted file mode 100644 index f7523d0a6e..0000000000 --- a/openpype/hosts/nuke/plugins/load/load_mov.py +++ /dev/null @@ -1,347 +0,0 @@ -import nuke -from avalon.vendor import qargparse -from avalon import api, io -from openpype.api import get_current_project_settings -from openpype.hosts.nuke.api.lib import ( - get_imageio_input_colorspace -) - - -def add_review_presets_config(): - returning = { - "families": list(), - "representations": list() - } - settings = get_current_project_settings() - review_profiles = ( - settings["global"] - ["publish"] - ["ExtractReview"] - ["profiles"] - ) - - outputs = {} - for profile in review_profiles: - outputs.update(profile.get("outputs", {})) - - for output, properities in outputs.items(): - returning["representations"].append(output) - returning["families"] += properities.get("families", []) - - return returning - - -class LoadMov(api.Loader): - """Load mov file into Nuke""" - families = ["render", "source", "plate", "review"] - representations = ["mov", "review", "mp4"] - - label = "Load mov" - order = -10 - icon = "code-fork" - color = "orange" - - first_frame = nuke.root()["first_frame"].value() - - # options gui - defaults = { - "start_at_workfile": True - } - - options = [ - qargparse.Boolean( - "start_at_workfile", - help="Load at workfile start frame", - default=True - ) - ] - - node_name_template = "{class_name}_{ext}" - - def load(self, context, name, namespace, options): - from avalon.nuke import ( - containerise, - viewer_update_and_undo_stop - ) - - start_at_workfile = options.get( - "start_at_workfile", self.defaults["start_at_workfile"]) - - version = context['version'] - version_data = version.get("data", {}) - repr_id = context["representation"]["_id"] - - self.handle_start = version_data.get("handleStart", 0) - self.handle_end = version_data.get("handleEnd", 0) - - orig_first = version_data.get("frameStart") - orig_last = version_data.get("frameEnd") - diff = orig_first - 1 - - first = orig_first - diff - last = orig_last - diff - - colorspace = version_data.get("colorspace") - repr_cont = context["representation"]["context"] - - self.log.debug( - "Representation id `{}` ".format(repr_id)) - - context["representation"]["_id"] - # create handles offset (only to last, because of mov) - last += self.handle_start + self.handle_end - - # Fallback to asset name when namespace is None - if namespace is None: - namespace = context['asset']['name'] - - file = self.fname - - if not file: - self.log.warning( - "Representation id `{}` is failing to load".format(repr_id)) - return - - file = file.replace("\\", "/") - - name_data = { - "asset": repr_cont["asset"], - "subset": repr_cont["subset"], - "representation": context["representation"]["name"], - "ext": repr_cont["representation"], - "id": context["representation"]["_id"], - "class_name": self.__class__.__name__ - } - - read_name = self.node_name_template.format(**name_data) - - read_node = nuke.createNode( - "Read", - "name {}".format(read_name) - ) - - # to avoid multiple undo steps for rest of process - # we will switch off undo-ing - with viewer_update_and_undo_stop(): - read_node["file"].setValue(file) - - read_node["origfirst"].setValue(first) - read_node["first"].setValue(first) - read_node["origlast"].setValue(last) - read_node["last"].setValue(last) - read_node['frame_mode'].setValue("start at") - - if start_at_workfile: - # start at workfile start - read_node['frame'].setValue(str(self.first_frame)) - else: - # start at version frame start - read_node['frame'].setValue( - str(orig_first - self.handle_start)) - - if colorspace: - read_node["colorspace"].setValue(str(colorspace)) - - preset_clrsp = get_imageio_input_colorspace(file) - - if preset_clrsp is not None: - read_node["colorspace"].setValue(preset_clrsp) - - # add additional metadata from the version to imprint Avalon knob - add_keys = [ - "frameStart", "frameEnd", "handles", "source", "author", - "fps", "version", "handleStart", "handleEnd" - ] - - data_imprint = {} - for key in add_keys: - if key == 'version': - data_imprint.update({ - key: context["version"]['name'] - }) - else: - data_imprint.update({ - key: context["version"]['data'].get(key, str(None)) - }) - - data_imprint.update({"objectName": read_name}) - - read_node["tile_color"].setValue(int("0x4ecd25ff", 16)) - - if version_data.get("retime", None): - speed = version_data.get("speed", 1) - time_warp_nodes = version_data.get("timewarps", []) - self.make_retimes(speed, time_warp_nodes) - - return containerise( - read_node, - name=name, - namespace=namespace, - context=context, - loader=self.__class__.__name__, - data=data_imprint - ) - - def switch(self, container, representation): - self.update(container, representation) - - def update(self, container, representation): - """Update the Loader's path - - Nuke automatically tries to reset some variables when changing - the loader's path to a new file. These automatic changes are to its - inputs: - - """ - - from avalon.nuke import ( - update_container - ) - - read_node = nuke.toNode(container['objectName']) - - assert read_node.Class() == "Read", "Must be Read" - - file = self.fname - - if not file: - repr_id = representation["_id"] - self.log.warning( - "Representation id `{}` is failing to load".format(repr_id)) - return - - file = file.replace("\\", "/") - - # Get start frame from version data - version = io.find_one({ - "type": "version", - "_id": representation["parent"] - }) - - # get all versions in list - versions = io.find({ - "type": "version", - "parent": version["parent"] - }).distinct('name') - - max_version = max(versions) - - version_data = version.get("data", {}) - - orig_first = version_data.get("frameStart") - orig_last = version_data.get("frameEnd") - diff = orig_first - 1 - - # set first to 1 - first = orig_first - diff - last = orig_last - diff - self.handle_start = version_data.get("handleStart", 0) - self.handle_end = version_data.get("handleEnd", 0) - colorspace = version_data.get("colorspace") - - if first is None: - self.log.warning(( - "Missing start frame for updated version" - "assuming starts at frame 0 for: " - "{} ({})").format( - read_node['name'].value(), representation)) - first = 0 - - # create handles offset (only to last, because of mov) - last += self.handle_start + self.handle_end - - read_node["file"].setValue(file) - - # Set the global in to the start frame of the sequence - read_node["origfirst"].setValue(first) - read_node["first"].setValue(first) - read_node["origlast"].setValue(last) - read_node["last"].setValue(last) - read_node['frame_mode'].setValue("start at") - - if int(float(self.first_frame)) == int( - float(read_node['frame'].value())): - # start at workfile start - read_node['frame'].setValue(str(self.first_frame)) - else: - # start at version frame start - read_node['frame'].setValue(str(orig_first - self.handle_start)) - - if colorspace: - read_node["colorspace"].setValue(str(colorspace)) - - preset_clrsp = get_imageio_input_colorspace(file) - - if preset_clrsp is not None: - read_node["colorspace"].setValue(preset_clrsp) - - updated_dict = {} - updated_dict.update({ - "representation": str(representation["_id"]), - "frameStart": str(first), - "frameEnd": str(last), - "version": str(version.get("name")), - "colorspace": version_data.get("colorspace"), - "source": version_data.get("source"), - "handleStart": str(self.handle_start), - "handleEnd": str(self.handle_end), - "fps": str(version_data.get("fps")), - "author": version_data.get("author"), - "outputDir": version_data.get("outputDir") - }) - - # change color of node - if version.get("name") not in [max_version]: - read_node["tile_color"].setValue(int("0xd84f20ff", 16)) - else: - read_node["tile_color"].setValue(int("0x4ecd25ff", 16)) - - if version_data.get("retime", None): - speed = version_data.get("speed", 1) - time_warp_nodes = version_data.get("timewarps", []) - self.make_retimes(speed, time_warp_nodes) - - # Update the imprinted representation - update_container( - read_node, updated_dict - ) - self.log.info("udated to version: {}".format(version.get("name"))) - - def remove(self, container): - - from avalon.nuke import viewer_update_and_undo_stop - - read_node = nuke.toNode(container['objectName']) - assert read_node.Class() == "Read", "Must be Read" - - with viewer_update_and_undo_stop(): - nuke.delete(read_node) - - def make_retimes(self, speed, time_warp_nodes): - ''' Create all retime and timewarping nodes with coppied animation ''' - if speed != 1: - rtn = nuke.createNode( - "Retime", - "speed {}".format(speed)) - rtn["before"].setValue("continue") - rtn["after"].setValue("continue") - rtn["input.first_lock"].setValue(True) - rtn["input.first"].setValue( - self.first_frame - ) - - if time_warp_nodes != []: - start_anim = self.first_frame + (self.handle_start / speed) - for timewarp in time_warp_nodes: - twn = nuke.createNode(timewarp["Class"], - "name {}".format(timewarp["name"])) - if isinstance(timewarp["lookup"], list): - # if array for animation - twn["lookup"].setAnimated() - for i, value in enumerate(timewarp["lookup"]): - twn["lookup"].setValueAt( - (start_anim + i) + value, - (start_anim + i)) - else: - # if static value `int` - twn["lookup"].setValue(timewarp["lookup"]) diff --git a/openpype/hosts/nuke/plugins/load/load_sequence.py b/openpype/hosts/nuke/plugins/load/load_sequence.py deleted file mode 100644 index 003b406ee7..0000000000 --- a/openpype/hosts/nuke/plugins/load/load_sequence.py +++ /dev/null @@ -1,320 +0,0 @@ -import nuke -from avalon.vendor import qargparse -from avalon import api, io -from openpype.hosts.nuke.api.lib import ( - get_imageio_input_colorspace -) - - -class LoadSequence(api.Loader): - """Load image sequence into Nuke""" - - families = ["render", "source", "plate", "review"] - representations = ["exr", "dpx"] - - label = "Load Image Sequence" - order = -20 - icon = "file-video-o" - color = "white" - - script_start = nuke.root()["first_frame"].value() - - # option gui - defaults = { - "start_at_workfile": True - } - - options = [ - qargparse.Boolean( - "start_at_workfile", - help="Load at workfile start frame", - default=True - ) - ] - - node_name_template = "{class_name}_{ext}" - - def load(self, context, name, namespace, options): - from avalon.nuke import ( - containerise, - viewer_update_and_undo_stop - ) - - start_at_workfile = options.get( - "start_at_workfile", self.defaults["start_at_workfile"]) - - version = context['version'] - version_data = version.get("data", {}) - repr_id = context["representation"]["_id"] - - self.log.info("version_data: {}\n".format(version_data)) - self.log.debug( - "Representation id `{}` ".format(repr_id)) - - self.first_frame = int(nuke.root()["first_frame"].getValue()) - self.handle_start = version_data.get("handleStart", 0) - self.handle_end = version_data.get("handleEnd", 0) - - first = version_data.get("frameStart", None) - last = version_data.get("frameEnd", None) - - # Fallback to asset name when namespace is None - if namespace is None: - namespace = context['asset']['name'] - - first -= self.handle_start - last += self.handle_end - - file = self.fname - - if not file: - repr_id = context["representation"]["_id"] - self.log.warning( - "Representation id `{}` is failing to load".format(repr_id)) - return - - file = file.replace("\\", "/") - - repr_cont = context["representation"]["context"] - assert repr_cont.get("frame"), "Representation is not sequence" - - if "#" not in file: - frame = repr_cont.get("frame") - if frame: - padding = len(frame) - file = file.replace(frame, "#" * padding) - - name_data = { - "asset": repr_cont["asset"], - "subset": repr_cont["subset"], - "representation": context["representation"]["name"], - "ext": repr_cont["representation"], - "id": context["representation"]["_id"], - "class_name": self.__class__.__name__ - } - - read_name = self.node_name_template.format(**name_data) - - # Create the Loader with the filename path set - read_node = nuke.createNode( - "Read", - "name {}".format(read_name)) - - # to avoid multiple undo steps for rest of process - # we will switch off undo-ing - with viewer_update_and_undo_stop(): - read_node["file"].setValue(file) - - # Set colorspace defined in version data - colorspace = context["version"]["data"].get("colorspace") - if colorspace: - read_node["colorspace"].setValue(str(colorspace)) - - preset_clrsp = get_imageio_input_colorspace(file) - - if preset_clrsp is not None: - read_node["colorspace"].setValue(preset_clrsp) - - # set start frame depending on workfile or version - self.loader_shift(read_node, start_at_workfile) - read_node["origfirst"].setValue(int(first)) - read_node["first"].setValue(int(first)) - read_node["origlast"].setValue(int(last)) - read_node["last"].setValue(int(last)) - - # add additional metadata from the version to imprint Avalon knob - add_keys = ["frameStart", "frameEnd", - "source", "colorspace", "author", "fps", "version", - "handleStart", "handleEnd"] - - data_imprint = {} - for k in add_keys: - if k == 'version': - data_imprint.update({k: context["version"]['name']}) - else: - data_imprint.update( - {k: context["version"]['data'].get(k, str(None))}) - - data_imprint.update({"objectName": read_name}) - - read_node["tile_color"].setValue(int("0x4ecd25ff", 16)) - - if version_data.get("retime", None): - speed = version_data.get("speed", 1) - time_warp_nodes = version_data.get("timewarps", []) - self.make_retimes(speed, time_warp_nodes) - - return containerise(read_node, - name=name, - namespace=namespace, - context=context, - loader=self.__class__.__name__, - data=data_imprint) - - def switch(self, container, representation): - self.update(container, representation) - - def update(self, container, representation): - """Update the Loader's path - - Nuke automatically tries to reset some variables when changing - the loader's path to a new file. These automatic changes are to its - inputs: - - """ - - from avalon.nuke import ( - update_container - ) - - read_node = nuke.toNode(container['objectName']) - - assert read_node.Class() == "Read", "Must be Read" - - repr_cont = representation["context"] - assert repr_cont.get("frame"), "Representation is not sequence" - - file = api.get_representation_path(representation) - - if not file: - repr_id = representation["_id"] - self.log.warning( - "Representation id `{}` is failing to load".format(repr_id)) - return - - file = file.replace("\\", "/") - - if "#" not in file: - frame = repr_cont.get("frame") - if frame: - padding = len(frame) - file = file.replace(frame, "#" * padding) - - # Get start frame from version data - version = io.find_one({ - "type": "version", - "_id": representation["parent"] - }) - - # get all versions in list - versions = io.find({ - "type": "version", - "parent": version["parent"] - }).distinct('name') - - max_version = max(versions) - - version_data = version.get("data", {}) - - self.first_frame = int(nuke.root()["first_frame"].getValue()) - self.handle_start = version_data.get("handleStart", 0) - self.handle_end = version_data.get("handleEnd", 0) - - first = version_data.get("frameStart") - last = version_data.get("frameEnd") - - if first is None: - self.log.warning( - "Missing start frame for updated version" - "assuming starts at frame 0 for: " - "{} ({})".format(read_node['name'].value(), representation)) - first = 0 - - first -= self.handle_start - last += self.handle_end - - read_node["file"].setValue(file) - - # set start frame depending on workfile or version - self.loader_shift( - read_node, - bool("start at" in read_node['frame_mode'].value())) - - read_node["origfirst"].setValue(int(first)) - read_node["first"].setValue(int(first)) - read_node["origlast"].setValue(int(last)) - read_node["last"].setValue(int(last)) - - updated_dict = {} - updated_dict.update({ - "representation": str(representation["_id"]), - "frameStart": str(first), - "frameEnd": str(last), - "version": str(version.get("name")), - "colorspace": version_data.get("colorspace"), - "source": version_data.get("source"), - "handleStart": str(self.handle_start), - "handleEnd": str(self.handle_end), - "fps": str(version_data.get("fps")), - "author": version_data.get("author"), - "outputDir": version_data.get("outputDir"), - }) - - # change color of read_node - if version.get("name") not in [max_version]: - read_node["tile_color"].setValue(int("0xd84f20ff", 16)) - else: - read_node["tile_color"].setValue(int("0x4ecd25ff", 16)) - - if version_data.get("retime", None): - speed = version_data.get("speed", 1) - time_warp_nodes = version_data.get("timewarps", []) - self.make_retimes(speed, time_warp_nodes) - - # Update the imprinted representation - update_container( - read_node, - updated_dict - ) - self.log.info("udated to version: {}".format(version.get("name"))) - - def remove(self, container): - - from avalon.nuke import viewer_update_and_undo_stop - - read_node = nuke.toNode(container['objectName']) - assert read_node.Class() == "Read", "Must be Read" - - with viewer_update_and_undo_stop(): - nuke.delete(read_node) - - def make_retimes(self, speed, time_warp_nodes): - ''' Create all retime and timewarping nodes with coppied animation ''' - if speed != 1: - rtn = nuke.createNode( - "Retime", - "speed {}".format(speed)) - rtn["before"].setValue("continue") - rtn["after"].setValue("continue") - rtn["input.first_lock"].setValue(True) - rtn["input.first"].setValue( - self.first_frame - ) - - if time_warp_nodes != []: - start_anim = self.first_frame + (self.handle_start / speed) - for timewarp in time_warp_nodes: - twn = nuke.createNode(timewarp["Class"], - "name {}".format(timewarp["name"])) - if isinstance(timewarp["lookup"], list): - # if array for animation - twn["lookup"].setAnimated() - for i, value in enumerate(timewarp["lookup"]): - twn["lookup"].setValueAt( - (start_anim + i) + value, - (start_anim + i)) - else: - # if static value `int` - twn["lookup"].setValue(timewarp["lookup"]) - - def loader_shift(self, read_node, workfile_start=False): - """ Set start frame of read node to a workfile start - - Args: - read_node (nuke.Node): The nuke's read node - workfile_start (bool): set workfile start frame if true - - """ - if workfile_start: - read_node['frame_mode'].setValue("start at") - read_node['frame'].setValue(str(self.script_start)) From a08b3da447f24be48c3448070ef5f493e8e51928 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 13 Oct 2021 16:52:33 +0200 Subject: [PATCH 097/279] PYPE-1901 - fix order of closing and processing from queue --- openpype/tools/tray_app/app.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/openpype/tools/tray_app/app.py b/openpype/tools/tray_app/app.py index 476f061e26..f1363d0cab 100644 --- a/openpype/tools/tray_app/app.py +++ b/openpype/tools/tray_app/app.py @@ -203,14 +203,15 @@ class ConsoleTrayApp: self.initializing = True self.launch_method(*self.subprocess_args) - elif ConsoleTrayApp.process.poll() is not None: - self.exit() - elif ConsoleTrayApp.callback_queue: + elif ConsoleTrayApp.callback_queue and \ + not ConsoleTrayApp.callback_queue.empty(): try: callback = ConsoleTrayApp.callback_queue.get(block=False) callback() except queue.Empty: pass + elif ConsoleTrayApp.process.poll() is not None: + self.exit() @classmethod def execute_in_main_thread(cls, func_to_call_from_main_thread): @@ -230,8 +231,9 @@ class ConsoleTrayApp: self._close() if ConsoleTrayApp.websocket_server: ConsoleTrayApp.websocket_server.stop() - ConsoleTrayApp.process.kill() - ConsoleTrayApp.process.wait() + if ConsoleTrayApp.process: + ConsoleTrayApp.process.kill() + ConsoleTrayApp.process.wait() if self.timer: self.timer.stop() QtCore.QCoreApplication.exit() From 818ffa1ac0442966989fc085bb0c1a280488e959 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 13 Oct 2021 16:54:33 +0200 Subject: [PATCH 098/279] global: patch discovery on pipeline too --- openpype/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/__init__.py b/openpype/__init__.py index 9d55006a67..11b563ebfe 100644 --- a/openpype/__init__.py +++ b/openpype/__init__.py @@ -69,6 +69,7 @@ def install(): """Install Pype to Avalon.""" from pyblish.lib import MessageHandler from openpype.modules import load_modules + from avalon import pipeline # Make sure modules are loaded load_modules() @@ -117,7 +118,9 @@ def install(): # apply monkey patched discover to original one log.info("Patching discovery") + avalon.discover = patched_discover + pipeline.discover = patched_discover avalon.on("taskChanged", _on_task_change) From 1b6da8f6981e2e794c6313198ac45ae76d98ae07 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 13 Oct 2021 16:55:07 +0200 Subject: [PATCH 099/279] nuke: replacing position of add to member --- openpype/hosts/nuke/plugins/load/load_clip.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/plugins/load/load_clip.py b/openpype/hosts/nuke/plugins/load/load_clip.py index f56120ae0a..265ab39b07 100644 --- a/openpype/hosts/nuke/plugins/load/load_clip.py +++ b/openpype/hosts/nuke/plugins/load/load_clip.py @@ -130,7 +130,6 @@ class LoadClip(plugin.NukeLoader): read_node = nuke.createNode( "Read", "name {}".format(read_name)) - self.set_as_member(read_node) # to avoid multiple undo steps for rest of process # we will switch off undo-ing @@ -174,6 +173,8 @@ class LoadClip(plugin.NukeLoader): if version_data.get("retime", None): self.make_retimes(read_node, version_data) + self.set_as_member(read_node) + return container def switch(self, container, representation): From 2150ac836edccf74420ee12d71c39bf496c9a26a Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 13 Oct 2021 16:55:42 +0200 Subject: [PATCH 100/279] PYPE-1901 - extracted methods into remote_publish library Methods are used in both remote* approaches for logging and reusability. --- openpype/lib/remote_publish.py | 92 ++++++++++++++++++++++++++++++++++ openpype/pype_commands.py | 86 +++++++++---------------------- 2 files changed, 116 insertions(+), 62 deletions(-) create mode 100644 openpype/lib/remote_publish.py diff --git a/openpype/lib/remote_publish.py b/openpype/lib/remote_publish.py new file mode 100644 index 0000000000..aa8d8821a8 --- /dev/null +++ b/openpype/lib/remote_publish.py @@ -0,0 +1,92 @@ +import os +from datetime import datetime +import sys +from bson.objectid import ObjectId + +import pyblish.util + +from openpype import uninstall +from openpype.lib.mongo import OpenPypeMongoConnection + + +def get_webpublish_conn(): + """Get connection to OP 'webpublishes' collection.""" + mongo_client = OpenPypeMongoConnection.get_mongo_client() + database_name = os.environ["OPENPYPE_DATABASE_NAME"] + return mongo_client[database_name]["webpublishes"] + + +def start_webpublish_log(dbcon, batch_id, user): + """Start new log record for 'batch_id' + + Args: + dbcon (OpenPypeMongoConnection) + batch_id (str) + user (str) + Returns + (ObjectId) from DB + """ + return dbcon.insert_one({ + "batch_id": batch_id, + "start_date": datetime.now(), + "user": user, + "status": "in_progress" + }).inserted_id + + +def publish_and_log(dbcon, _id, log): + """Loops through all plugins, logs ok and fails into OP DB. + + Args: + dbcon (OpenPypeMongoConnection) + _id (str) + log (OpenPypeLogger) + """ + # Error exit as soon as any error occurs. + error_format = "Failed {plugin.__name__}: {error} -- {error.traceback}" + + if isinstance(_id, str): + _id = ObjectId(_id) + + log_lines = [] + for result in pyblish.util.publish_iter(): + for record in result["records"]: + log_lines.append("{}: {}".format( + result["plugin"].label, record.msg)) + + if result["error"]: + log.error(error_format.format(**result)) + uninstall() + log_lines.append(error_format.format(**result)) + dbcon.update_one( + {"_id": _id}, + {"$set": + { + "finish_date": datetime.now(), + "status": "error", + "log": os.linesep.join(log_lines) + + }} + ) + sys.exit(1) + else: + dbcon.update_one( + {"_id": _id}, + {"$set": + { + "progress": max(result["progress"], 0.95), + "log": os.linesep.join(log_lines) + }} + ) + + # final update + dbcon.update_one( + {"_id": _id}, + {"$set": + { + "finish_date": datetime.now(), + "status": "finished_ok", + "progress": 1, + "log": os.linesep.join(log_lines) + }} + ) \ No newline at end of file diff --git a/openpype/pype_commands.py b/openpype/pype_commands.py index e06ab5b493..e2869a956d 100644 --- a/openpype/pype_commands.py +++ b/openpype/pype_commands.py @@ -9,6 +9,11 @@ import time from openpype.lib import PypeLogger from openpype.api import get_app_environments_for_context from openpype.lib.plugin_tools import parse_json, get_batch_asset_task_info +from openpype.lib.remote_publish import ( + get_webpublish_conn, + start_webpublish_log, + publish_and_log +) class PypeCommands: @@ -152,8 +157,6 @@ class PypeCommands: app_name = "{}/{}".format(host, found_variant_key) - os.environ["OPENPYPE_PUBLISH_DATA"] = batch_dir - batch_data = None if batch_dir and os.path.exists(batch_dir): # TODO check if batch manifest is same as tasks manifests @@ -170,10 +173,6 @@ class PypeCommands: batch_data["task"], batch_data["files"][0]) print("workfile_path {}".format(workfile_path)) - data = { - "last_workfile_path": workfile_path, - "start_last_workfile": True - } # must have for proper launch of app env = get_app_environments_for_context( @@ -184,19 +183,34 @@ class PypeCommands: ) os.environ.update(env) + _, batch_id = os.path.split(batch_dir) + dbcon = get_webpublish_conn() + # safer to start logging here, launch might be broken altogether + _id = start_webpublish_log(dbcon, batch_id, user) + + 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) + + data = { + "last_workfile_path": workfile_path, + "start_last_workfile": True + } launched_app = application_manager.launch(app_name, **data) while launched_app.poll() is None: time.sleep(0.5) - print(launched_app) + uninstall() @staticmethod def remotepublish(project, batch_path, host, user, targets=None): """Start headless publishing. + Used to publish rendered assets, workfiles etc. + Publish use json from passed paths argument. Args: @@ -217,7 +231,6 @@ class PypeCommands: from openpype import install, uninstall from openpype.api import Logger - from openpype.lib import OpenPypeMongoConnection # Register target and host import pyblish.api @@ -249,62 +262,11 @@ class PypeCommands: log.info("Running publish ...") - # Error exit as soon as any error occurs. - error_format = "Failed {plugin.__name__}: {error} -- {error.traceback}" - - mongo_client = OpenPypeMongoConnection.get_mongo_client() - database_name = os.environ["OPENPYPE_DATABASE_NAME"] - dbcon = mongo_client[database_name]["webpublishes"] - _, batch_id = os.path.split(batch_path) - _id = dbcon.insert_one({ - "batch_id": batch_id, - "start_date": datetime.now(), - "user": user, - "status": "in_progress" - }).inserted_id + dbcon = get_webpublish_conn() + _id = start_webpublish_log(dbcon, batch_id, user) - log_lines = [] - for result in pyblish.util.publish_iter(): - for record in result["records"]: - log_lines.append("{}: {}".format( - result["plugin"].label, record.msg)) - - if result["error"]: - log.error(error_format.format(**result)) - uninstall() - log_lines.append(error_format.format(**result)) - dbcon.update_one( - {"_id": _id}, - {"$set": - { - "finish_date": datetime.now(), - "status": "error", - "log": os.linesep.join(log_lines) - - }} - ) - sys.exit(1) - else: - dbcon.update_one( - {"_id": _id}, - {"$set": - { - "progress": max(result["progress"], 0.95), - "log": os.linesep.join(log_lines) - }} - ) - - dbcon.update_one( - {"_id": _id}, - {"$set": - { - "finish_date": datetime.now(), - "status": "finished_ok", - "progress": 1, - "log": os.linesep.join(log_lines) - }} - ) + publish_and_log(dbcon, _id, log) log.info("Publish finished.") uninstall() From b0705417ab1236bce923142c92cb3d2b0a0c463a Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 13 Oct 2021 17:38:37 +0200 Subject: [PATCH 101/279] PYPE-1901 - close host if any plugin fails --- .../photoshop/plugins/publish/closePS.py | 1 + openpype/lib/remote_publish.py | 22 +++++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/photoshop/plugins/publish/closePS.py b/openpype/hosts/photoshop/plugins/publish/closePS.py index fa9d27688b..19994a0db8 100644 --- a/openpype/hosts/photoshop/plugins/publish/closePS.py +++ b/openpype/hosts/photoshop/plugins/publish/closePS.py @@ -6,6 +6,7 @@ import pyblish.api from avalon import photoshop + class ClosePS(pyblish.api.ContextPlugin): """Close PS after publish. For Webpublishing only. """ diff --git a/openpype/lib/remote_publish.py b/openpype/lib/remote_publish.py index aa8d8821a8..6cca9e4217 100644 --- a/openpype/lib/remote_publish.py +++ b/openpype/lib/remote_publish.py @@ -4,6 +4,7 @@ import sys from bson.objectid import ObjectId import pyblish.util +import pyblish.api from openpype import uninstall from openpype.lib.mongo import OpenPypeMongoConnection @@ -34,17 +35,21 @@ def start_webpublish_log(dbcon, batch_id, user): }).inserted_id -def publish_and_log(dbcon, _id, log): +def publish_and_log(dbcon, _id, log, close_plugin_name=None): """Loops through all plugins, logs ok and fails into OP DB. Args: dbcon (OpenPypeMongoConnection) _id (str) log (OpenPypeLogger) + close_plugin_name (str): name of plugin with responsibility to + close host app """ # Error exit as soon as any error occurs. error_format = "Failed {plugin.__name__}: {error} -- {error.traceback}" + close_plugin = _get_close_plugin(close_plugin_name, log) + if isinstance(_id, str): _id = ObjectId(_id) @@ -68,6 +73,9 @@ def publish_and_log(dbcon, _id, log): }} ) + if close_plugin: # close host app explicitly after error + context = pyblish.api.Context() + close_plugin(context).process() sys.exit(1) else: dbcon.update_one( @@ -89,4 +97,14 @@ def publish_and_log(dbcon, _id, log): "progress": 1, "log": os.linesep.join(log_lines) }} - ) \ No newline at end of file + ) + + +def _get_close_plugin(close_plugin_name, log): + if close_plugin_name: + plugins = pyblish.api.discover() + for plugin in plugins: + if plugin.__name__ == close_plugin_name: + return plugin + + log.warning("Close plugin not found, app might not close.") From 2ae4b12f218cbf82ef15753bcf3b7a388b420ff1 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 14 Oct 2021 12:07:13 +0200 Subject: [PATCH 102/279] Fix - oiiotool wasn't recognized even if present This caused to DWAA support not working even if it could --- openpype/lib/plugin_tools.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/openpype/lib/plugin_tools.py b/openpype/lib/plugin_tools.py index 9dccadc44e..a982983805 100644 --- a/openpype/lib/plugin_tools.py +++ b/openpype/lib/plugin_tools.py @@ -377,7 +377,7 @@ def oiio_supported(): """ Checks if oiiotool is configured for this platform. - Expects full path to executable. + Triggers simple subprocess, handles exception if fails. 'should_decompress' will throw exception if configured, but not present or not working. @@ -385,7 +385,13 @@ def oiio_supported(): (bool) """ oiio_path = get_oiio_tools_path() - if not oiio_path or not os.path.exists(oiio_path): + if oiio_path: + try: + _ = run_subprocess([oiio_path, "-v"]) + except FileNotFoundError: + oiio_path = None + + if not oiio_path: log.debug("OIIOTool is not configured or not present at {}". format(oiio_path)) return False From d757793587b6f61fb6d8fd8b5a9a9f5332d9fb3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Thu, 14 Oct 2021 13:10:06 +0200 Subject: [PATCH 103/279] return when not sets --- openpype/hosts/maya/plugins/publish/extract_look.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/hosts/maya/plugins/publish/extract_look.py b/openpype/hosts/maya/plugins/publish/extract_look.py index bbf25ebdc7..e0b85907e9 100644 --- a/openpype/hosts/maya/plugins/publish/extract_look.py +++ b/openpype/hosts/maya/plugins/publish/extract_look.py @@ -205,6 +205,9 @@ class ExtractLook(openpype.api.Extractor): lookdata = instance.data["lookData"] relationships = lookdata["relationships"] sets = relationships.keys() + if not sets: + self.log.info("No sets found") + return results = self.process_resources(instance, staging_dir=dir_path) transfers = results["fileTransfers"] From 6fe7517303a359fdf91ab54353769e69cca1677d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 14 Oct 2021 13:21:34 +0200 Subject: [PATCH 104/279] PYPE-1901 - fix close plugin --- openpype/lib/remote_publish.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/lib/remote_publish.py b/openpype/lib/remote_publish.py index 6cca9e4217..4946e1bd53 100644 --- a/openpype/lib/remote_publish.py +++ b/openpype/lib/remote_publish.py @@ -75,7 +75,7 @@ def publish_and_log(dbcon, _id, log, close_plugin_name=None): ) if close_plugin: # close host app explicitly after error context = pyblish.api.Context() - close_plugin(context).process() + close_plugin().process(context) sys.exit(1) else: dbcon.update_one( From 0c410bb2b1d05e9d253bd0f867fdb5ed670b74ed Mon Sep 17 00:00:00 2001 From: karimmozlia Date: Thu, 14 Oct 2021 13:47:00 +0200 Subject: [PATCH 105/279] add UI booleans --- .../defaults/project_settings/maya.json | 15 +++++++++++ .../schemas/schema_maya_publish.json | 25 ++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index c592d74350..72a0ab362d 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -315,6 +315,21 @@ "optional": true, "active": true }, + "ValidateRigContents": { + "enabled": false, + "optional": true, + "active": true + }, + "ValidateJointsHidden": { + "enabled": false, + "optional": true, + "active": true + }, + "ValidateRigControllers": { + "enabled": false, + "optional": true, + "active": true + }, "ValidateCameraAttributes": { "enabled": false, "optional": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json index 26ebfb2bd7..f676094e90 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json @@ -166,7 +166,6 @@ } ] }, - { "type": "collapsible-wrap", "label": "Model", @@ -329,6 +328,30 @@ } ] }, + { + "type": "collapsible-wrap", + "label": "Rig", + "children": [ + { + "type": "schema_template", + "name": "template_publish_plugin", + "template_data": [ + { + "key": "ValidateRigContents", + "label": "Validate Rig Contents" + }, + { + "key": "ValidateJointsHidden", + "label": "Validate Joints Hidden" + }, + { + "key": "ValidateRigControllers", + "label": "Validate Rig Controllers" + } + ] + } + ] + }, { "type": "schema_template", "name": "template_publish_plugin", From 184ae8c2685af4ca87f846c36d9a7f738dce15af Mon Sep 17 00:00:00 2001 From: karimmozlia Date: Thu, 14 Oct 2021 13:58:12 +0200 Subject: [PATCH 106/279] ValidateRigJointsHidden typo --- openpype/settings/defaults/project_settings/maya.json | 2 +- .../schemas/projects_schema/schemas/schema_maya_publish.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 72a0ab362d..f8f3432d0f 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -320,7 +320,7 @@ "optional": true, "active": true }, - "ValidateJointsHidden": { + "ValidateRigJointsHidden": { "enabled": false, "optional": true, "active": true diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json index f676094e90..bde8c15958 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json @@ -341,8 +341,8 @@ "label": "Validate Rig Contents" }, { - "key": "ValidateJointsHidden", - "label": "Validate Joints Hidden" + "key": "ValidateRigJointsHidden", + "label": "Validate Rig JointsHidden" }, { "key": "ValidateRigControllers", From 8d69283b8a592ebf969bf6e61ce8735d14eec4a5 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 14 Oct 2021 16:27:43 +0200 Subject: [PATCH 107/279] PYPE-1343 - added project and task into context window --- openpype/hosts/maya/api/__init__.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/api/__init__.py b/openpype/hosts/maya/api/__init__.py index 725a075417..13f8a4cb78 100644 --- a/openpype/hosts/maya/api/__init__.py +++ b/openpype/hosts/maya/api/__init__.py @@ -313,9 +313,15 @@ def on_task_changed(*args): lib.set_context_settings() lib.update_content_on_context_change() + msg = " project: {}\n asset: {}\n task:{}".format( + avalon.Session["AVALON_PROJECT"], + avalon.Session["AVALON_ASSET"], + avalon.Session["AVALON_TASK"] + ) + lib.show_message( "Context was changed", - ("Context was changed to {}".format(avalon.Session["AVALON_ASSET"])), + ("Context was changed to:\n{}".format(msg)), ) From a10963188d1aaabdb170540e6d1635409d5829ba Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 14 Oct 2021 17:32:00 +0200 Subject: [PATCH 108/279] Fix - better approach for oiio_supported --- openpype/lib/plugin_tools.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/openpype/lib/plugin_tools.py b/openpype/lib/plugin_tools.py index a982983805..4eabb4d1ca 100644 --- a/openpype/lib/plugin_tools.py +++ b/openpype/lib/plugin_tools.py @@ -6,6 +6,7 @@ import logging import re import json import tempfile +import distutils from .execute import run_subprocess from .profiles_filtering import filter_profiles @@ -386,10 +387,7 @@ def oiio_supported(): """ oiio_path = get_oiio_tools_path() if oiio_path: - try: - _ = run_subprocess([oiio_path, "-v"]) - except FileNotFoundError: - oiio_path = None + oiio_path = distutils.spawn.find_executable(oiio_path) if not oiio_path: log.debug("OIIOTool is not configured or not present at {}". From df482e51b4e451ebd9ef38412b78a9c8b0a20077 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 14 Oct 2021 17:43:02 +0200 Subject: [PATCH 109/279] updated readme for entity types in settings --- openpype/settings/entities/schemas/README.md | 151 ++++++++++++------ .../schemas/system_schema/example_schema.json | 6 +- 2 files changed, 103 insertions(+), 54 deletions(-) diff --git a/openpype/settings/entities/schemas/README.md b/openpype/settings/entities/schemas/README.md index c8432f0f2e..5258fef9ec 100644 --- a/openpype/settings/entities/schemas/README.md +++ b/openpype/settings/entities/schemas/README.md @@ -2,7 +2,7 @@ ## Basic rules - configurations does not define GUI, but GUI defines configurations! -- output is always json (yaml is not needed for anatomy templates anymore) +- output is always json serializable - GUI schema has multiple input types, all inputs are represented by a dictionary - each input may have "input modifiers" (keys in dictionary) that are required or optional - only required modifier for all input items is key `"type"` which says what type of item it is @@ -13,16 +13,16 @@ - `"is_group"` - define that all values under key in hierarchy will be overriden if any value is modified, this information is also stored to overrides - this keys is not allowed for all inputs as they may have not reason for that - key is validated, can be only once in hierarchy but is not required -- currently there are `system configurations` and `project configurations` +- currently there are `system settings` and `project settings` ## Inner schema - GUI schemas are huge json files, to be able to split whole configuration into multiple schema there's type `schema` -- system configuration schemas are stored in `~/tools/settings/settings/gui_schemas/system_schema/` and project configurations in `~/tools/settings/settings/gui_schemas/projects_schema/` +- system configuration schemas are stored in `~/openpype/settings/entities/schemas/system_schema/` and project configurations in `~/openpype/settings/entities/schemas/projects_schema/` - each schema name is filename of json file except extension (without ".json") - if content is dictionary content will be used as `schema` else will be used as `schema_template` ### schema -- can have only key `"children"` which is list of strings, each string should represent another schema (order matters) string represebts name of the schema +- can have only key `"children"` which is list of strings, each string should represent another schema (order matters) string represents name of the schema - will just paste schemas from other schema file in order of "children" list ``` @@ -32,8 +32,9 @@ } ``` -### schema_template +### template - allows to define schema "templates" to not duplicate same content multiple times +- legacy name is `schema_template` (still usable) ```javascript // EXAMPLE json file content (filename: example_template.json) [ @@ -59,11 +60,11 @@ // EXAMPLE usage of the template in schema { "type": "dict", - "key": "schema_template_examples", + "key": "template_examples", "label": "Schema template examples", "children": [ { - "type": "schema_template", + "type": "template", // filename of template (example_template.json) "name": "example_template", "template_data": { @@ -72,7 +73,7 @@ "multipath_executables": false } }, { - "type": "schema_template", + "type": "template", "name": "example_template", "template_data": { "host_label": "Maya 2020", @@ -98,8 +99,16 @@ ... } ``` -- Unfilled fields can be also used for non string values, in that case value must contain only one key and value for fill must contain right type. +- Unfilled fields can be also used for non string values(e.g. dictionary), in that case value must contain only one key and value for fill must contain right type. ```javascript +// Passed data +{ + "executable_multiplatform": { + "type": "schema", + "name": "my_multiplatform_schema" + } +} +// Template content { ... // Allowed @@ -121,32 +130,34 @@ "name": "project_settings/global" } ``` -- all valid `ModuleSettingsDef` classes where calling of `get_settings_schemas` +- all valid `BaseModuleSettingsDef` classes where calling of `get_settings_schemas` will return dictionary where is key "project_settings/global" with schemas will extend and replace this item -- works almost the same way as templates +- dynamic schemas work almost the same way as templates - one item can be replaced by multiple items (or by 0 items) - goal is to dynamically loaded settings of OpenPype addons without having their schemas or default values in main repository + - values of these schemas are saved using the `BaseModuleSettingsDef` methods +- easiest is to use `JsonFilesSettingsDef` which has full implementation of storing default values to json files all you have to implement is method `get_settings_root_path` which should return path to root directory where settings schema can be found and will be saved ## Basic Dictionary inputs - these inputs wraps another inputs into {key: value} relation ## dict -- this is another dictionary input wrapping more inputs but visually makes them different -- item may be used as widget (in `list` or `dict-modifiable`) +- this is dictionary type wrapping more inputs with keys defined in schema +- may be used as dynamic children (e.g. in `list` or `dict-modifiable`) - in that case the only key modifier is `children` which is list of it's keys - USAGE: e.g. List of dictionaries where each dictionary have same structure. -- item may be with or without `"label"` if is not used as widget - - required keys are `"key"` under which will be stored - - without label it is just wrap item holding `"key"` - - can't have `"is_group"` key set to True as it breaks visual override showing - - if `"label"` is entetered there which will be shown in GUI - - item with label can be collapsible - - that can be set with key `"collapsible"` as `True`/`False` (Default: `True`) - - with key `"collapsed"` as `True`/`False` can be set that is collapsed when GUI is opened (Default: `False`) - - it is possible to add darker background with `"highlight_content"` (Default: `False`) - - darker background has limits of maximum applies after 3-4 nested highlighted items there is not difference in the color +- if is not used as dynamic children then must have defined `"key"` under which are it's values stored +- may be with or without `"label"` (only for GUI) + - `"label"` must be set to be able mark item as group with `"is_group"` key set to True +- item with label can visually wrap it's children + - this option is enabled by default to turn off set `"use_label_wrap"` to `False` + - label wrap is by default collapsible + - that can be set with key `"collapsible"` to `True`/`False` + - with key `"collapsed"` as `True`/`False` can be set that is collapsed when GUI is opened (Default: `False`) + - it is possible to add lighter background with `"highlight_content"` (Default: `False`) + - lighter background has limits of maximum applies after 3-4 nested highlighted items there is not much difference in the color - output is dictionary `{the "key": children values}` ``` # Example @@ -198,8 +209,8 @@ ``` ## dict-conditional -- is similar to `dict` but has only one child entity that will be always available -- the one entity is enumerator of possible values and based on value of the entity are defined and used other children entities +- is similar to `dict` but has always available one enum entity + - the enum entity has single selection and it's value define other children entities - each value of enumerator have defined children that will be used - there is no way how to have shared entities across multiple enum items - value from enumerator is also stored next to other values @@ -207,22 +218,27 @@ - `enum_key` must match key regex and any enum item can't have children with same key - `enum_label` is label of the entity for UI purposes - enum items are define with `enum_children` - - it's a list where each item represents enum item + - it's a list where each item represents single item for the enum - all items in `enum_children` must have at least `key` key which represents value stored under `enum_key` - - items can define `label` for UI purposes + - enum items can define `label` for UI purposes - most important part is that item can define `children` key where are definitions of it's children (`children` value works the same way as in `dict`) - to set default value for `enum_key` set it with `enum_default` - entity must have defined `"label"` if is not used as widget -- is set as group if any parent is not group -- if `"label"` is entetered there which will be shown in GUI - - item with label can be collapsible - - that can be set with key `"collapsible"` as `True`/`False` (Default: `True`) - - with key `"collapsed"` as `True`/`False` can be set that is collapsed when GUI is opened (Default: `False`) - - it is possible to add darker background with `"highlight_content"` (Default: `False`) - - darker background has limits of maximum applies after 3-4 nested highlighted items there is not difference in the color - - output is dictionary `{the "key": children values}` +- is set as group if any parent is not group (can't have children as group) +- may be with or without `"label"` (only for GUI) + - `"label"` must be set to be able mark item as group with `"is_group"` key set to True +- item with label can visually wrap it's children + - this option is enabled by default to turn off set `"use_label_wrap"` to `False` + - label wrap is by default collapsible + - that can be set with key `"collapsible"` to `True`/`False` + - with key `"collapsed"` as `True`/`False` can be set that is collapsed when GUI is opened (Default: `False`) + - it is possible to add lighter background with `"highlight_content"` (Default: `False`) + - lighter background has limits of maximum applies after 3-4 nested highlighted items there is not much difference in the color - for UI porposes was added `enum_is_horizontal` which will make combobox appear next to children inputs instead of on top of them (Default: `False`) - this has extended ability of `enum_on_right` which will move combobox to right side next to children widgets (Default: `False`) +- output is dictionary `{the "key": children values}` +- using this type as template item for list type can be used to create infinite hierarchies + ``` # Example { @@ -298,8 +314,8 @@ How output of the schema could look like on save: ``` ## Inputs for setting any kind of value (`Pure` inputs) -- all these input must have defined `"key"` under which will be stored and `"label"` which will be shown next to input - - unless they are used in different types of inputs (later) "as widgets" in that case `"key"` and `"label"` are not required as there is not place where to set them +- all inputs must have defined `"key"` if are not used as dynamic item + - they can also have defined `"label"` ### boolean - simple checkbox, nothing more to set @@ -355,21 +371,15 @@ How output of the schema could look like on save: ``` ### path-input -- enhanced text input - - does not allow to enter backslash, is auto-converted to forward slash - - may be added another validations, like do not allow end path with slash - this input is implemented to add additional features to text input -- this is meant to be used in proxy input `path-widget` +- this is meant to be used in proxy input `path` - DO NOT USE this input in schema please ### raw-json - a little bit enhanced text input for raw json +- can store dictionary (`{}`) or list (`[]`) but not both + - by default stores dictionary to change it to list set `is_list` to `True` - has validations of json format - - empty value is invalid value, always must be json serializable - - valid value types are list `[]` and dictionary `{}` -- schema also defines valid value type - - by default it is dictionary - - to be able use list it is required to define `is_list` to `true` - output can be stored as string - this is to allow any keys in dictionary - set key `store_as_string` to `true` @@ -385,7 +395,7 @@ How output of the schema could look like on save: ``` ### enum -- returns value of single on multiple items from predefined values +- enumeration of values that are predefined in schema - multiselection can be allowed with setting key `"multiselection"` to `True` (Default: `False`) - values are defined under value of key `"enum_items"` as list - each item in list is simple dictionary where value is label and key is value which will be stored @@ -415,6 +425,8 @@ How output of the schema could look like on save: - have only single selection mode - it is possible to define default value `default` - `"work"` is used if default value is not specified +- enum values are not updated on the fly it is required to save templates and + reset settings to recache values ``` { "key": "host", @@ -449,6 +461,42 @@ How output of the schema could look like on save: } ``` +### apps-enum +- enumeration of available application and their variants from system settings + - applications without host name are excluded +- can be used only in project settings +- has only `multiselection` +- used only in project anatomy +``` +{ + "type": "apps-enum", + "key": "applications", + "label": "Applications" +} +``` + +### tools-enum +- enumeration of available tools and their variants from system settings +- can be used only in project settings +- has only `multiselection` +- used only in project anatomy +``` +{ + "type": "tools-enum", + "key": "tools_env", + "label": "Tools" +} +``` + +### task-types-enum +- enumeration of task types from current project +- enum values are not updated on the fly and modifications of task types on project require save and reset to be propagated to this enum +- has set `multiselection` to `True` but can be changed to `False` in schema + +### deadline_url-enum +- deadline module specific enumerator using deadline system settings to fill it's values +- TODO: move this type to deadline module + ## Inputs for setting value using Pure inputs - these inputs also have required `"key"` - attribute `"label"` is required in few conditions @@ -594,7 +642,7 @@ How output of the schema could look like on save: } ``` -### path-widget +### path - input for paths, use `path-input` internally - has 2 input modifiers `"multiplatform"` and `"multipath"` - `"multiplatform"` - adds `"windows"`, `"linux"` and `"darwin"` path inputs result is dictionary @@ -685,12 +733,13 @@ How output of the schema could look like on save: } ``` -### splitter -- visual splitter of items (more divider than splitter) +### separator +- legacy name is `splitter` (still usable) +- visual separator of items (more divider than separator) ``` { - "type": "splitter" + "type": "separator" } ``` diff --git a/openpype/settings/entities/schemas/system_schema/example_schema.json b/openpype/settings/entities/schemas/system_schema/example_schema.json index af6a2d49f4..c30e1f6848 100644 --- a/openpype/settings/entities/schemas/system_schema/example_schema.json +++ b/openpype/settings/entities/schemas/system_schema/example_schema.json @@ -95,11 +95,11 @@ }, { "type": "dict", - "key": "schema_template_exaples", + "key": "template_exaples", "label": "Schema template examples", "children": [ { - "type": "schema_template", + "type": "template", "name": "example_template", "template_data": { "host_label": "Application 1", @@ -108,7 +108,7 @@ } }, { - "type": "schema_template", + "type": "template", "name": "example_template", "template_data": { "host_label": "Application 2", From 3ae3ec1185e3fd0c5c1020adedf94348f0bc8f78 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 14 Oct 2021 19:00:32 +0200 Subject: [PATCH 110/279] tools are always on top if don't have set parent --- openpype/tools/libraryloader/app.py | 5 ++++- openpype/tools/loader/app.py | 5 ++++- openpype/tools/workfiles/app.py | 5 ++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/openpype/tools/libraryloader/app.py b/openpype/tools/libraryloader/app.py index 8080c547c9..3f11157418 100644 --- a/openpype/tools/libraryloader/app.py +++ b/openpype/tools/libraryloader/app.py @@ -38,7 +38,10 @@ class LibraryLoaderWindow(QtWidgets.QDialog): # Enable minimize and maximize for app self.setWindowTitle(self.tool_title) - self.setWindowFlags(QtCore.Qt.Window) + window_flags = QtCore.Qt.Window + if not parent: + window_flags |= QtCore.Qt.WindowStaysOnTopHint + self.setWindowFlags(window_flags) self.setFocusPolicy(QtCore.Qt.StrongFocus) if icon is not None: self.setWindowIcon(icon) diff --git a/openpype/tools/loader/app.py b/openpype/tools/loader/app.py index c18b6e798a..bc0eef3bca 100644 --- a/openpype/tools/loader/app.py +++ b/openpype/tools/loader/app.py @@ -51,7 +51,10 @@ class LoaderWindow(QtWidgets.QDialog): self.family_config_cache = lib.FamilyConfigCache(io) # Enable minimize and maximize for app - self.setWindowFlags(QtCore.Qt.Window) + window_flags = QtCore.Qt.Window + if not parent: + window_flags |= QtCore.Qt.WindowStaysOnTopHint + self.setWindowFlags(window_flags) self.setFocusPolicy(QtCore.Qt.StrongFocus) body = QtWidgets.QWidget() diff --git a/openpype/tools/workfiles/app.py b/openpype/tools/workfiles/app.py index 6fff0d0278..18e8cfc6d3 100644 --- a/openpype/tools/workfiles/app.py +++ b/openpype/tools/workfiles/app.py @@ -944,7 +944,10 @@ class Window(QtWidgets.QMainWindow): def __init__(self, parent=None): super(Window, self).__init__(parent=parent) self.setWindowTitle(self.title) - self.setWindowFlags(QtCore.Qt.Window | QtCore.Qt.WindowCloseButtonHint) + window_flags = QtCore.Qt.Window | QtCore.Qt.WindowCloseButtonHint + if not parent: + window_flags |= QtCore.Qt.WindowStaysOnTopHint + self.setWindowFlags(window_flags) # Create pages widget and set it as central widget pages_widget = QtWidgets.QStackedWidget(self) From 5d426741d5553b93840912a86a991e70f3513658 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 14 Oct 2021 19:06:43 +0200 Subject: [PATCH 111/279] initial idea of caching host tools at single place --- openpype/tools/utils/host_tools.py | 205 +++++++++++++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100644 openpype/tools/utils/host_tools.py diff --git a/openpype/tools/utils/host_tools.py b/openpype/tools/utils/host_tools.py new file mode 100644 index 0000000000..203db36949 --- /dev/null +++ b/openpype/tools/utils/host_tools.py @@ -0,0 +1,205 @@ +"""Single access point to all tools usable in hosts. + +It is possible to create `HostToolsHelper` in host implementaion or +use singleton approach with global functions (using helper anyway). +""" + +from Qt import QtCore +import avalon.api + + +class HostToolsHelper: + """Create and cache tool windows in memory. + + Almost all methods expect parent widget but the parent is used only on + first tool creation. + + Class may also contain tools that are available only for one or few hosts. + """ + def __init__(self, parent=None): + self._parent = parent + self._workfiles_tool = None + self._loader_tool = None + self._creator_tool = None + self._subset_manager_tool = None + self._scene_inventory_tool = None + self._library_loader_tool = None + + def _get_workfiles_tool(self, parent): + if self._workfiles_tool is None: + from openpype.tools.workfiles.app import ( + Window, validate_host_requirements + ) + # Host validation + host = avalon.api.registered_host() + validate_host_requirements(host) + + window = Window(parent=parent) + + context = { + "asset": avalon.api.Session["AVALON_ASSET"], + "silo": avalon.api.Session["AVALON_SILO"], + "task": avalon.api.Session["AVALON_TASK"] + } + window.set_context(context) + + self._workfiles_tool = window + + return self._workfiles_tool + + def show_workfiles_tool(self, parent=None): + workfiles_tool = self._get_workfiles_tool(parent) + + workfiles_tool.refresh() + workfiles_tool.show() + # Pull window to the front. + workfiles_tool.raise_() + workfiles_tool.activateWindow() + + def _get_loader_tool(self, parent): + if self._loader_tool is None: + from openpype.tools.loader import LoaderWindow + + self._loader_tool = LoaderWindow(parent=parent or self._parent) + + return self._loader_tool + + def show_loader_tool(self, parent=None): + loader_tool = self._get_loader_tool(parent) + + context = {"asset": avalon.api.Session["AVALON_ASSET"]} + loader_tool.set_context(context, refresh=True) + + loader_tool.show() + loader_tool.raise_() + loader_tool.activateWindow() + loader_tool.refresh() + + def _get_creator_tool(self, parent): + if self._creator_tool is None: + from avalon.tools.creator.app import Window + + self._creator_tool = Window(parent=parent or self._parent) + + return self._creator_tool + + def show_creator_tool(self, parent=None): + creator_tool = self._get_creator_tool(parent) + creator_tool.refresh() + creator_tool.show() + + # Pull window to the front. + creator_tool.raise_() + creator_tool.activateWindow() + + def _get_subset_manager_tool(self, parent): + if self._subset_manager_tool is None: + from avalon.tools.subsetmanager import Window + + self._subset_manager_tool = Window(parent=parent or self._parent) + + return self._subset_manager_tool + + def show_subset_manager_tool(self, parent=None): + subset_manager_tool = self._get_subset_manager_tool(parent) + subset_manager_tool.show() + + # Pull window to the front. + subset_manager_tool.raise_() + subset_manager_tool.activateWindow() + + def _get_scene_inventory_tool(self, parent): + if self._scene_inventory_tool is None: + from avalon.tools.sceneinventory.app import Window + + self._scene_inventory_tool = Window(parent=parent or self._parent) + + return self._scene_inventory_tool + + def show_scene_inventory_tool(self, parent=None): + scene_inventory_tool = self._get_scene_inventory_tool(parent) + scene_inventory_tool.show() + scene_inventory_tool.refresh() + + # Pull window to the front. + scene_inventory_tool.raise_() + scene_inventory_tool.activateWindow() + + def _get_library_loader_tool(self, parent): + if self._library_loader_tool is None: + from openpype.tools.libraryloader import LibraryLoaderWindow + + self._library_loader_tool = LibraryLoaderWindow( + parent=parent or self._parent + ) + + return self._library_loader_tool + + def show_library_loader_tool(self, parent=None): + library_loader_tool = self._get_library_loader_tool(parent) + library_loader_tool.show() + library_loader_tool.raise_() + library_loader_tool.activateWindow() + library_loader_tool.refresh() + + def show_publish_tool(self, parent=None): + from avalon.tools import publish + + publish.show(parent) + + def show_tool_by_name(self, tool_name, parent=None): + if tool_name == "workfiles": + self.show_workfiles_tool(parent) + + elif tool_name == "loader": + self.show_loader_tool(parent) + + elif tool_name == "libraryloader": + self.show_library_loader_tool(parent) + + elif tool_name == "creator": + self.show_creator_tool(parent) + + elif tool_name == "subset_manager": + self.show_subset_manager_tool(parent) + + elif tool_name == "scene_inventory": + self.show_scene_inventory_tool(parent) + + +class _SingletonPoint: + helper = None + + @classmethod + def _create_helper(cls): + if cls.helper is None: + cls.helper = HostToolsHelper() + + @classmethod + def show_tool_by_name(cls, tool_name, parent=None): + cls._create_helper() + cls.helper.show_tool_by_name(tool_name, parent) + + +def show_workfiles_tool(parent=None): + _SingletonPoint.show_tool_by_name("workfiles", parent) + + +def show_loader_tool(parent=None): + _SingletonPoint.show_tool_by_name("loader", parent) + + +def show_library_loader_tool(parent=None): + _SingletonPoint.show_tool_by_name("libraryloader", parent) + + +def show_creator_tool(parent=None): + _SingletonPoint.show_tool_by_name("creator", parent) + + +def show_subset_manager_tool(parent=None): + _SingletonPoint.show_tool_by_name("subset_manager", parent) + + +def show_scene_inventory_tool(parent=None): + _SingletonPoint.show_tool_by_name("scene_inventory", parent) From 54c8988ce916c70634def2903fff665126b10eeb Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 14 Oct 2021 19:16:55 +0200 Subject: [PATCH 112/279] enhanced passing args and kwargs --- openpype/tools/utils/host_tools.py | 42 ++++++++++++++++-------------- openpype/tools/workfiles/app.py | 5 +++- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/openpype/tools/utils/host_tools.py b/openpype/tools/utils/host_tools.py index 203db36949..aa3d1ccc94 100644 --- a/openpype/tools/utils/host_tools.py +++ b/openpype/tools/utils/host_tools.py @@ -34,21 +34,23 @@ class HostToolsHelper: host = avalon.api.registered_host() validate_host_requirements(host) - window = Window(parent=parent) + self._workfiles_tool = Window(parent=parent) + return self._workfiles_tool + + def show_workfiles_tool(self, parent=None, use_context=True, save=True): + workfiles_tool = self._get_workfiles_tool(parent) + + if use_context: context = { "asset": avalon.api.Session["AVALON_ASSET"], "silo": avalon.api.Session["AVALON_SILO"], "task": avalon.api.Session["AVALON_TASK"] } - window.set_context(context) + workfiles_tool.set_context(context) - self._workfiles_tool = window - - return self._workfiles_tool - - def show_workfiles_tool(self, parent=None): - workfiles_tool = self._get_workfiles_tool(parent) + if save: + workfiles_tool.set_save_enabled(save) workfiles_tool.refresh() workfiles_tool.show() @@ -147,24 +149,24 @@ class HostToolsHelper: publish.show(parent) - def show_tool_by_name(self, tool_name, parent=None): + def show_tool_by_name(self, tool_name, parent=None, *args, **kwargs): if tool_name == "workfiles": - self.show_workfiles_tool(parent) + self.show_workfiles_tool(parent, *args, **kwargs) elif tool_name == "loader": - self.show_loader_tool(parent) + self.show_loader_tool(parent, *args, **kwargs) elif tool_name == "libraryloader": - self.show_library_loader_tool(parent) + self.show_library_loader_tool(parent, *args, **kwargs) elif tool_name == "creator": - self.show_creator_tool(parent) + self.show_creator_tool(parent, *args, **kwargs) elif tool_name == "subset_manager": - self.show_subset_manager_tool(parent) + self.show_subset_manager_tool(parent, *args, **kwargs) elif tool_name == "scene_inventory": - self.show_scene_inventory_tool(parent) + self.show_scene_inventory_tool(parent, *args, **kwargs) class _SingletonPoint: @@ -176,13 +178,15 @@ class _SingletonPoint: cls.helper = HostToolsHelper() @classmethod - def show_tool_by_name(cls, tool_name, parent=None): + def show_tool_by_name(cls, tool_name, parent=None, *args, **kwargs): cls._create_helper() - cls.helper.show_tool_by_name(tool_name, parent) + cls.helper.show_tool_by_name(tool_name, parent, *args, **kwargs) -def show_workfiles_tool(parent=None): - _SingletonPoint.show_tool_by_name("workfiles", parent) +def show_workfiles_tool(parent=None, use_context=True, save=True): + _SingletonPoint.show_tool_by_name( + "workfiles", parent, use_context=use_context, save=save + ) def show_loader_tool(parent=None): diff --git a/openpype/tools/workfiles/app.py b/openpype/tools/workfiles/app.py index 18e8cfc6d3..1679a18241 100644 --- a/openpype/tools/workfiles/app.py +++ b/openpype/tools/workfiles/app.py @@ -1018,6 +1018,9 @@ class Window(QtWidgets.QMainWindow): """ + def set_save_enabled(self, enabled): + self.files_widget.btn_save.setEnabled(enabled) + def on_task_changed(self): # Since we query the disk give it slightly more delay tools_lib.schedule(self._on_task_changed, 100, channel="mongo") @@ -1190,7 +1193,7 @@ def show(root=None, debug=False, parent=None, use_context=True, save=True): } window.set_context(context) - window.files_widget.btn_save.setEnabled(save) + window.set_save_enabled(save) window.show() window.setStyleSheet(style.load_stylesheet()) From b0628953d8039a65ffcc366a28f5cbd5c564e10a Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 14 Oct 2021 19:18:59 +0200 Subject: [PATCH 113/279] PYPE-1901 - reworked Settings Layer could be resolved to final family according to color_code OR name (or both). --- openpype/hooks/pre_foundry_apps.py | 2 +- .../publish/collect_remote_instances.py | 72 +++++++++++++++---- .../defaults/project_settings/photoshop.json | 11 ++- .../schema_project_photoshop.json | 49 +++++++++---- 4 files changed, 102 insertions(+), 32 deletions(-) diff --git a/openpype/hooks/pre_foundry_apps.py b/openpype/hooks/pre_foundry_apps.py index 85f68c6b60..7df1a6a833 100644 --- a/openpype/hooks/pre_foundry_apps.py +++ b/openpype/hooks/pre_foundry_apps.py @@ -13,7 +13,7 @@ class LaunchFoundryAppsWindows(PreLaunchHook): # Should be as last hook because must change launch arguments to string order = 1000 - app_groups = ["nuke", "nukex", "hiero", "nukestudio"] + app_groups = ["nuke", "nukex", "hiero", "nukestudio", "photoshop"] platforms = ["windows"] def execute(self): diff --git a/openpype/hosts/photoshop/plugins/publish/collect_remote_instances.py b/openpype/hosts/photoshop/plugins/publish/collect_remote_instances.py index 62d94483e5..9bb8e90350 100644 --- a/openpype/hosts/photoshop/plugins/publish/collect_remote_instances.py +++ b/openpype/hosts/photoshop/plugins/publish/collect_remote_instances.py @@ -1,5 +1,6 @@ import pyblish.api import os +import re from avalon import photoshop from openpype.lib import prepare_template_data @@ -22,12 +23,11 @@ class CollectRemoteInstances(pyblish.api.ContextPlugin): hosts = ["photoshop"] # configurable by Settings - families = ["background"] - color_code = ["red"] - subset_template_name = "" + color_code_mapping = [] 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 @@ -49,24 +49,26 @@ class CollectRemoteInstances(pyblish.api.ContextPlugin): instance_names = [] for layer in layers: - self.log.info("!!!Layer:: {}".format(layer)) - if layer.color_code not in self.color_code: - self.log.debug("Not marked, skip") + self.log.info("Layer:: {}".format(layer)) + resolved_family, resolved_subset_template = self._resolve_mapping( + layer + ) + self.log.info("resolved_family {}".format(resolved_family)) + self.log.info("resolved_subset_template {}".format( + resolved_subset_template)) + + if not resolved_subset_template or not resolved_family: + self.log.debug("!!! Not marked, skip") continue if layer.parents: - self.log.debug("Not a top layer, skip") + self.log.debug("!!! Not a top layer, skip") continue instance = context.create_instance(layer.name) instance.append(layer) - instance.data["family"] = self.families[0] + instance.data["family"] = resolved_family instance.data["publish"] = layer.visible - - # populate data from context, coming from outside?? TODO - # TEMP - self.log.info("asset {}".format(context.data["assetEntity"])) - self.log.info("taskType {}".format(context.data["taskType"])) instance.data["asset"] = context.data["assetEntity"]["name"] instance.data["task"] = context.data["taskType"] @@ -76,7 +78,7 @@ class CollectRemoteInstances(pyblish.api.ContextPlugin): "task": instance.data["task"], "layer": layer.name } - subset = self.subset_template_name.format( + subset = resolved_subset_template.format( **prepare_template_data(fill_pairs)) instance.data["subset"] = subset @@ -90,3 +92,45 @@ class CollectRemoteInstances(pyblish.api.ContextPlugin): if len(instance_names) != len(set(instance_names)): self.log.warning("Duplicate instances found. " + "Remove unwanted via SubsetManager") + + def _resolve_mapping(self, layer): + """Matches 'layer' color code and name to mapping. + + If both color code AND name regex is configured, BOTH must be valid + If layer matches to multiple mappings, only first is used! + """ + family_list = [] + family = None + subset_name_list = [] + resolved_subset_template = None + for mapping in self.color_code_mapping: + if mapping["color_code"] and \ + layer.color_code not in mapping["color_code"]: + break + + if mapping["layer_name_regex"] and \ + not any(re.search(pattern, layer.name) + for pattern in mapping["layer_name_regex"]): + break + + family_list.append(mapping["family"]) + subset_name_list.append(mapping["subset_template_name"]) + + if len(subset_name_list) > 1: + self.log.warning("Multiple mappings found for '{}'". + format(layer.name)) + self.log.warning("Only first subset name template used!") + subset_name_list[:] = subset_name_list[0] + + if len(family_list) > 1: + self.log.warning("Multiple mappings found for '{}'". + format(layer.name)) + self.log.warning("Only first family used!") + family_list[:] = family_list[0] + + if subset_name_list: + resolved_subset_template = subset_name_list.pop() + if family_list: + family = family_list.pop() + + return family, resolved_subset_template diff --git a/openpype/settings/defaults/project_settings/photoshop.json b/openpype/settings/defaults/project_settings/photoshop.json index 14c294c0c5..03fcbc162c 100644 --- a/openpype/settings/defaults/project_settings/photoshop.json +++ b/openpype/settings/defaults/project_settings/photoshop.json @@ -13,9 +13,14 @@ "active": true }, "CollectRemoteInstances": { - "color_code": [], - "families": [], - "subset_template_name": "" + "color_code_mapping": [ + { + "color_code": [], + "layer_name_regex": [], + "family": "", + "subset_template_name": "" + } + ] }, "ExtractImage": { "formats": [ 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 008f1a265d..cd457ee21d 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json @@ -46,6 +46,7 @@ { "type": "dict", "collapsible": true, + "is_group": true, "key": "CollectRemoteInstances", "label": "Collect Instances for Webpublish", "children": [ @@ -55,20 +56,40 @@ }, { "type": "list", - "key": "color_code", - "label": "Color codes for layers", - "object_type": "text" - }, - { - "key": "families", - "label": "Families", - "type": "list", - "object_type": "text" - }, - { - "type": "text", - "key": "subset_template_name", - "label": "Subset template name" + "key": "color_code_mapping", + "label": "Color code mappings", + "use_label_wrap": false, + "collapsible": false, + "object_type": { + "type": "dict", + "children": [ + { + "type": "list", + "key": "color_code", + "label": "Color codes for layers", + "object_type": "text" + }, + { + "type": "list", + "key": "layer_name_regex", + "label": "Layer name regex", + "object_type": "text" + }, + { + "type": "splitter" + }, + { + "key": "family", + "label": "Resulting family", + "type": "text" + }, + { + "type": "text", + "key": "subset_template_name", + "label": "Subset template name" + } + ] + } } ] }, From 0bac8ff6de8b04ef9a8eb35d65db8a3811148dee Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 15 Oct 2021 10:07:20 +0200 Subject: [PATCH 114/279] removed underscores from tool names --- openpype/tools/utils/host_tools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/utils/host_tools.py b/openpype/tools/utils/host_tools.py index aa3d1ccc94..979341375e 100644 --- a/openpype/tools/utils/host_tools.py +++ b/openpype/tools/utils/host_tools.py @@ -162,10 +162,10 @@ class HostToolsHelper: elif tool_name == "creator": self.show_creator_tool(parent, *args, **kwargs) - elif tool_name == "subset_manager": + elif tool_name == "subsetmanager": self.show_subset_manager_tool(parent, *args, **kwargs) - elif tool_name == "scene_inventory": + elif tool_name == "sceneinventory": self.show_scene_inventory_tool(parent, *args, **kwargs) From 08ad338cb5d3c14fbbd781ec12873bbffda89e92 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 15 Oct 2021 10:07:43 +0200 Subject: [PATCH 115/279] use_context and save don't have defaults in args definition --- openpype/tools/utils/host_tools.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/openpype/tools/utils/host_tools.py b/openpype/tools/utils/host_tools.py index 979341375e..8c9da2b4f9 100644 --- a/openpype/tools/utils/host_tools.py +++ b/openpype/tools/utils/host_tools.py @@ -38,9 +38,14 @@ class HostToolsHelper: return self._workfiles_tool - def show_workfiles_tool(self, parent=None, use_context=True, save=True): - workfiles_tool = self._get_workfiles_tool(parent) + def show_workfiles_tool(self, parent=None, use_context=None, save=None): + if use_context is None: + use_context = True + if save is None: + save = True + + workfiles_tool = self._get_workfiles_tool(parent) if use_context: context = { "asset": avalon.api.Session["AVALON_ASSET"], @@ -183,7 +188,7 @@ class _SingletonPoint: cls.helper.show_tool_by_name(tool_name, parent, *args, **kwargs) -def show_workfiles_tool(parent=None, use_context=True, save=True): +def show_workfiles_tool(parent=None, use_context=None, save=None): _SingletonPoint.show_tool_by_name( "workfiles", parent, use_context=use_context, save=save ) From a8e1d5164c8ca979e58f7dd8bc1f570905833539 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 15 Oct 2021 10:17:20 +0200 Subject: [PATCH 116/279] loader has use_context argument --- openpype/tools/utils/host_tools.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/openpype/tools/utils/host_tools.py b/openpype/tools/utils/host_tools.py index 8c9da2b4f9..daf9356bae 100644 --- a/openpype/tools/utils/host_tools.py +++ b/openpype/tools/utils/host_tools.py @@ -71,11 +71,16 @@ class HostToolsHelper: return self._loader_tool - def show_loader_tool(self, parent=None): + def show_loader_tool(self, parent=None, use_context=None): + if use_context is None: + use_context = False loader_tool = self._get_loader_tool(parent) - context = {"asset": avalon.api.Session["AVALON_ASSET"]} - loader_tool.set_context(context, refresh=True) + if use_context: + context = {"asset": avalon.api.Session["AVALON_ASSET"]} + loader_tool.set_context(context, refresh=True) + else: + loader_tool.refresh() loader_tool.show() loader_tool.raise_() @@ -194,8 +199,10 @@ def show_workfiles_tool(parent=None, use_context=None, save=None): ) -def show_loader_tool(parent=None): - _SingletonPoint.show_tool_by_name("loader", parent) +def show_loader_tool(parent=None, use_context=None): + _SingletonPoint.show_tool_by_name( + "loader", parent, use_context=use_context + ) def show_library_loader_tool(parent=None): From b1cf928c3b51de5ba1b14f67a2fa92ea33e1d440 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 15 Oct 2021 10:17:41 +0200 Subject: [PATCH 117/279] fixed tool names in global functions --- openpype/tools/utils/host_tools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/utils/host_tools.py b/openpype/tools/utils/host_tools.py index daf9356bae..492f21486d 100644 --- a/openpype/tools/utils/host_tools.py +++ b/openpype/tools/utils/host_tools.py @@ -214,8 +214,8 @@ def show_creator_tool(parent=None): def show_subset_manager_tool(parent=None): - _SingletonPoint.show_tool_by_name("subset_manager", parent) + _SingletonPoint.show_tool_by_name("subsetmanager", parent) def show_scene_inventory_tool(parent=None): - _SingletonPoint.show_tool_by_name("scene_inventory", parent) + _SingletonPoint.show_tool_by_name("sceneinventory", parent) From ca168580ba68c94103113a799248ddea4f1a4329 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 15 Oct 2021 10:36:06 +0200 Subject: [PATCH 118/279] added few docstrings --- openpype/tools/utils/host_tools.py | 34 ++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/openpype/tools/utils/host_tools.py b/openpype/tools/utils/host_tools.py index 492f21486d..7c7a566aae 100644 --- a/openpype/tools/utils/host_tools.py +++ b/openpype/tools/utils/host_tools.py @@ -17,7 +17,11 @@ class HostToolsHelper: Class may also contain tools that are available only for one or few hosts. """ def __init__(self, parent=None): + self._log = None + # Global parent for all tools (may and may not be set) self._parent = parent + + # Prepare attributes for all tools self._workfiles_tool = None self._loader_tool = None self._creator_tool = None @@ -25,6 +29,14 @@ class HostToolsHelper: self._scene_inventory_tool = None self._library_loader_tool = None + @property + def log(self): + if self._log is None: + from openpype.api import Logger + + self._log = Logger.get_logger(self.__class__.__name__) + return self._log + def _get_workfiles_tool(self, parent): if self._workfiles_tool is None: from openpype.tools.workfiles.app import ( @@ -39,6 +51,7 @@ class HostToolsHelper: return self._workfiles_tool def show_workfiles_tool(self, parent=None, use_context=None, save=None): + """Workfiles tool for changing context and saving workfiles.""" if use_context is None: use_context = True @@ -72,6 +85,7 @@ class HostToolsHelper: return self._loader_tool def show_loader_tool(self, parent=None, use_context=None): + """Loader tool for loading representations.""" if use_context is None: use_context = False loader_tool = self._get_loader_tool(parent) @@ -96,6 +110,7 @@ class HostToolsHelper: return self._creator_tool def show_creator_tool(self, parent=None): + """Show tool to create new instantes for publishing.""" creator_tool = self._get_creator_tool(parent) creator_tool.refresh() creator_tool.show() @@ -113,6 +128,7 @@ class HostToolsHelper: return self._subset_manager_tool def show_subset_manager_tool(self, parent=None): + """Show tool display/remove existing created instances.""" subset_manager_tool = self._get_subset_manager_tool(parent) subset_manager_tool.show() @@ -129,6 +145,7 @@ class HostToolsHelper: return self._scene_inventory_tool def show_scene_inventory_tool(self, parent=None): + """Show tool maintain loaded containers.""" scene_inventory_tool = self._get_scene_inventory_tool(parent) scene_inventory_tool.show() scene_inventory_tool.refresh() @@ -148,6 +165,7 @@ class HostToolsHelper: return self._library_loader_tool def show_library_loader_tool(self, parent=None): + """Loader tool for loading representations from library project.""" library_loader_tool = self._get_library_loader_tool(parent) library_loader_tool.show() library_loader_tool.raise_() @@ -155,11 +173,16 @@ class HostToolsHelper: library_loader_tool.refresh() def show_publish_tool(self, parent=None): + """Publish UI.""" from avalon.tools import publish publish.show(parent) def show_tool_by_name(self, tool_name, parent=None, *args, **kwargs): + """Show tool by it's name. + + This is helper for + """ if tool_name == "workfiles": self.show_workfiles_tool(parent, *args, **kwargs) @@ -178,8 +201,18 @@ class HostToolsHelper: elif tool_name == "sceneinventory": self.show_scene_inventory_tool(parent, *args, **kwargs) + self.log.warning( + "Can't show unknown tool name: \"{}\"".format(tool_name) + ) + class _SingletonPoint: + """Singleton access to host tools. + + Some hosts don't have ability to create 'HostToolsHelper' object anc can + only register function callbacks. For those cases is created this singleton + point where 'HostToolsHelper' is created "in shared memory". + """ helper = None @classmethod @@ -193,6 +226,7 @@ class _SingletonPoint: cls.helper.show_tool_by_name(tool_name, parent, *args, **kwargs) +# Function callbacks using singleton acces point def show_workfiles_tool(parent=None, use_context=None, save=None): _SingletonPoint.show_tool_by_name( "workfiles", parent, use_context=use_context, save=save From e1281231bab2ba3db102e3d3d3aea659bef0cf0c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 15 Oct 2021 10:54:06 +0200 Subject: [PATCH 119/279] added look manager --- openpype/tools/utils/host_tools.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/openpype/tools/utils/host_tools.py b/openpype/tools/utils/host_tools.py index 7c7a566aae..0cd9578f0f 100644 --- a/openpype/tools/utils/host_tools.py +++ b/openpype/tools/utils/host_tools.py @@ -28,6 +28,7 @@ class HostToolsHelper: self._subset_manager_tool = None self._scene_inventory_tool = None self._library_loader_tool = None + self._look_manager_tool = None @property def log(self): @@ -178,6 +179,18 @@ class HostToolsHelper: publish.show(parent) + def _get_look_manager_tool(self, parent): + if self._look_manager_tool is None: + import mayalookassigner + + self._look_manager_tool = mayalookassigner.App(parent) + return self._look_manager_tool + + def show_look_manager(self, parent=None): + """Look manager is Maya specific tool for look management.""" + look_manager_tool = self._get_look_manager_tool(parent) + look_manager_tool.show() + def show_tool_by_name(self, tool_name, parent=None, *args, **kwargs): """Show tool by it's name. @@ -201,6 +214,9 @@ class HostToolsHelper: elif tool_name == "sceneinventory": self.show_scene_inventory_tool(parent, *args, **kwargs) + elif tool_name == "lookmanager": + self.show_look_manager(parent, *args, **kwargs) + self.log.warning( "Can't show unknown tool name: \"{}\"".format(tool_name) ) @@ -253,3 +269,6 @@ def show_subset_manager_tool(parent=None): def show_scene_inventory_tool(parent=None): _SingletonPoint.show_tool_by_name("sceneinventory", parent) + +def show_look_manager(self, parent=None): + _SingletonPoint.show_tool_by_name("lookmanager", parent) From 5446ac65406e4dcb7d859b6e32facfbaefb937e5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 15 Oct 2021 11:03:01 +0200 Subject: [PATCH 120/279] removed _tool suffix from show methods --- openpype/tools/utils/host_tools.py | 43 +++++++++++++++++------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/openpype/tools/utils/host_tools.py b/openpype/tools/utils/host_tools.py index 0cd9578f0f..ce2a66fd4b 100644 --- a/openpype/tools/utils/host_tools.py +++ b/openpype/tools/utils/host_tools.py @@ -51,7 +51,7 @@ class HostToolsHelper: return self._workfiles_tool - def show_workfiles_tool(self, parent=None, use_context=None, save=None): + def show_workfiles(self, parent=None, use_context=None, save=None): """Workfiles tool for changing context and saving workfiles.""" if use_context is None: use_context = True @@ -85,7 +85,7 @@ class HostToolsHelper: return self._loader_tool - def show_loader_tool(self, parent=None, use_context=None): + def show_loader(self, parent=None, use_context=None): """Loader tool for loading representations.""" if use_context is None: use_context = False @@ -110,7 +110,7 @@ class HostToolsHelper: return self._creator_tool - def show_creator_tool(self, parent=None): + def show_creator(self, parent=None): """Show tool to create new instantes for publishing.""" creator_tool = self._get_creator_tool(parent) creator_tool.refresh() @@ -128,7 +128,7 @@ class HostToolsHelper: return self._subset_manager_tool - def show_subset_manager_tool(self, parent=None): + def show_subset_manager(self, parent=None): """Show tool display/remove existing created instances.""" subset_manager_tool = self._get_subset_manager_tool(parent) subset_manager_tool.show() @@ -145,7 +145,7 @@ class HostToolsHelper: return self._scene_inventory_tool - def show_scene_inventory_tool(self, parent=None): + def show_scene_inventory(self, parent=None): """Show tool maintain loaded containers.""" scene_inventory_tool = self._get_scene_inventory_tool(parent) scene_inventory_tool.show() @@ -165,7 +165,7 @@ class HostToolsHelper: return self._library_loader_tool - def show_library_loader_tool(self, parent=None): + def show_library_loader(self, parent=None): """Loader tool for loading representations from library project.""" library_loader_tool = self._get_library_loader_tool(parent) library_loader_tool.show() @@ -173,7 +173,7 @@ class HostToolsHelper: library_loader_tool.activateWindow() library_loader_tool.refresh() - def show_publish_tool(self, parent=None): + def show_publish(self, parent=None): """Publish UI.""" from avalon.tools import publish @@ -197,22 +197,22 @@ class HostToolsHelper: This is helper for """ if tool_name == "workfiles": - self.show_workfiles_tool(parent, *args, **kwargs) + self.show_workfiles(parent, *args, **kwargs) elif tool_name == "loader": - self.show_loader_tool(parent, *args, **kwargs) + self.show_loader(parent, *args, **kwargs) elif tool_name == "libraryloader": - self.show_library_loader_tool(parent, *args, **kwargs) + self.show_library_loader(parent, *args, **kwargs) elif tool_name == "creator": - self.show_creator_tool(parent, *args, **kwargs) + self.show_creator(parent, *args, **kwargs) elif tool_name == "subsetmanager": - self.show_subset_manager_tool(parent, *args, **kwargs) + self.show_subset_manager(parent, *args, **kwargs) elif tool_name == "sceneinventory": - self.show_scene_inventory_tool(parent, *args, **kwargs) + self.show_scene_inventory(parent, *args, **kwargs) elif tool_name == "lookmanager": self.show_look_manager(parent, *args, **kwargs) @@ -243,32 +243,37 @@ class _SingletonPoint: # Function callbacks using singleton acces point -def show_workfiles_tool(parent=None, use_context=None, save=None): +def show_tool_by_name(tool_name, parent=None, *args, **kwargs): + _SingletonPoint.show_tool_by_name(tool_name, parent, *args, **kwargs) + + +def show_workfiles(parent=None, use_context=None, save=None): _SingletonPoint.show_tool_by_name( "workfiles", parent, use_context=use_context, save=save ) -def show_loader_tool(parent=None, use_context=None): +def show_loader(parent=None, use_context=None): _SingletonPoint.show_tool_by_name( "loader", parent, use_context=use_context ) -def show_library_loader_tool(parent=None): +def show_library_loader(parent=None): _SingletonPoint.show_tool_by_name("libraryloader", parent) -def show_creator_tool(parent=None): +def show_creator(parent=None): _SingletonPoint.show_tool_by_name("creator", parent) -def show_subset_manager_tool(parent=None): +def show_subset_manager(parent=None): _SingletonPoint.show_tool_by_name("subsetmanager", parent) -def show_scene_inventory_tool(parent=None): +def show_scene_inventory(parent=None): _SingletonPoint.show_tool_by_name("sceneinventory", parent) + def show_look_manager(self, parent=None): _SingletonPoint.show_tool_by_name("lookmanager", parent) From d8064571a24e5eeb6610adfdbf964e3c5ac88cec Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 15 Oct 2021 11:09:10 +0200 Subject: [PATCH 121/279] fixed look manager to look assigner --- openpype/tools/utils/host_tools.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/openpype/tools/utils/host_tools.py b/openpype/tools/utils/host_tools.py index ce2a66fd4b..96c05e4135 100644 --- a/openpype/tools/utils/host_tools.py +++ b/openpype/tools/utils/host_tools.py @@ -28,7 +28,7 @@ class HostToolsHelper: self._subset_manager_tool = None self._scene_inventory_tool = None self._library_loader_tool = None - self._look_manager_tool = None + self._look_assigner_tool = None @property def log(self): @@ -179,17 +179,17 @@ class HostToolsHelper: publish.show(parent) - def _get_look_manager_tool(self, parent): - if self._look_manager_tool is None: + def _get_look_assigner_tool(self, parent): + if self._look_assigner_tool is None: import mayalookassigner - self._look_manager_tool = mayalookassigner.App(parent) - return self._look_manager_tool + self._look_assigner_tool = mayalookassigner.App(parent) + return self._look_assigner_tool - def show_look_manager(self, parent=None): + def show_look_assigner(self, parent=None): """Look manager is Maya specific tool for look management.""" - look_manager_tool = self._get_look_manager_tool(parent) - look_manager_tool.show() + look_assigner_tool = self._get_look_assigner_tool(parent) + look_assigner_tool.show() def show_tool_by_name(self, tool_name, parent=None, *args, **kwargs): """Show tool by it's name. @@ -214,8 +214,8 @@ class HostToolsHelper: elif tool_name == "sceneinventory": self.show_scene_inventory(parent, *args, **kwargs) - elif tool_name == "lookmanager": - self.show_look_manager(parent, *args, **kwargs) + elif tool_name == "lookassigner": + self.show_look_assigner(parent, *args, **kwargs) self.log.warning( "Can't show unknown tool name: \"{}\"".format(tool_name) @@ -275,5 +275,5 @@ def show_scene_inventory(parent=None): _SingletonPoint.show_tool_by_name("sceneinventory", parent) -def show_look_manager(self, parent=None): - _SingletonPoint.show_tool_by_name("lookmanager", parent) +def show_look_assigner(parent=None): + _SingletonPoint.show_tool_by_name("lookassigner", parent) From 06212357a633fa575abb51be4abbf78d84b6b73c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 15 Oct 2021 11:14:19 +0200 Subject: [PATCH 122/279] added publish to known tools --- openpype/tools/utils/host_tools.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/openpype/tools/utils/host_tools.py b/openpype/tools/utils/host_tools.py index 96c05e4135..09d3c808c2 100644 --- a/openpype/tools/utils/host_tools.py +++ b/openpype/tools/utils/host_tools.py @@ -217,9 +217,13 @@ class HostToolsHelper: elif tool_name == "lookassigner": self.show_look_assigner(parent, *args, **kwargs) - self.log.warning( - "Can't show unknown tool name: \"{}\"".format(tool_name) - ) + elif tool_name == "publish": + self.show_publish(parent, *args, **kwargs) + + else: + self.log.warning( + "Can't show unknown tool name: \"{}\"".format(tool_name) + ) class _SingletonPoint: @@ -277,3 +281,7 @@ def show_scene_inventory(parent=None): def show_look_assigner(parent=None): _SingletonPoint.show_tool_by_name("lookassigner", parent) + + +def show_publish(parent=None): + _SingletonPoint.show_tool_by_name("publish", parent) From 235bc68573417654399a5efbb9137de35daf29be Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 15 Oct 2021 11:14:38 +0200 Subject: [PATCH 123/279] use host_tools in maya --- openpype/hosts/maya/api/menu.py | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/openpype/hosts/maya/api/menu.py b/openpype/hosts/maya/api/menu.py index ad225dcd28..42cb76dca6 100644 --- a/openpype/hosts/maya/api/menu.py +++ b/openpype/hosts/maya/api/menu.py @@ -2,13 +2,15 @@ import sys import os import logging -from avalon.vendor.Qt import QtWidgets, QtGui -from avalon.maya import pipeline -from openpype.api import BuildWorkfile -import maya.cmds as cmds -from openpype.settings import get_project_settings +from Qt import QtWidgets, QtGui -self = sys.modules[__name__] +import maya.cmds as cmds + +from avalon.maya import pipeline + +from openpype.api import BuildWorkfile +from openpype.settings import get_project_settings +from openpype.tools.utils import host_tools log = logging.getLogger(__name__) @@ -26,6 +28,7 @@ def _get_menu(menu_name=None): def deferred(): + def add_build_workfiles_item(): # Add build first workfile cmds.menuItem(divider=True, parent=pipeline._menu) @@ -36,24 +39,17 @@ def deferred(): ) def add_look_assigner_item(): - import mayalookassigner cmds.menuItem( "Look assigner", parent=pipeline._menu, - command=lambda *args: mayalookassigner.show() + command=lambda *args: host_tools.show_look_assigner( + pipeline._parent + ) ) def modify_workfiles(): - from openpype.tools import workfiles - def launch_workfiles_app(*_args, **_kwargs): - workfiles.show( - os.path.join( - cmds.workspace(query=True, rootDirectory=True), - cmds.workspace(fileRuleEntry="scene") - ), - parent=pipeline._parent - ) + host_tools.show_workfiles(pipeline._parent) # Find the pipeline menu top_menu = _get_menu() From 73bb38696895bcdac33d5a85e2baac8808d665b0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 15 Oct 2021 11:19:43 +0200 Subject: [PATCH 124/279] added style loading --- openpype/tools/utils/host_tools.py | 35 ++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/openpype/tools/utils/host_tools.py b/openpype/tools/utils/host_tools.py index 09d3c808c2..f8a30cf8f6 100644 --- a/openpype/tools/utils/host_tools.py +++ b/openpype/tools/utils/host_tools.py @@ -40,6 +40,7 @@ class HostToolsHelper: def _get_workfiles_tool(self, parent): if self._workfiles_tool is None: + from avalon import style from openpype.tools.workfiles.app import ( Window, validate_host_requirements ) @@ -47,7 +48,9 @@ class HostToolsHelper: host = avalon.api.registered_host() validate_host_requirements(host) - self._workfiles_tool = Window(parent=parent) + workfiles_window = Window(parent=parent) + workfiles_window.setStyleSheet(style.load_stylesheet()) + self._workfiles_tool = workfiles_window return self._workfiles_tool @@ -79,9 +82,12 @@ class HostToolsHelper: def _get_loader_tool(self, parent): if self._loader_tool is None: + from avalon import style from openpype.tools.loader import LoaderWindow - self._loader_tool = LoaderWindow(parent=parent or self._parent) + loader_window = LoaderWindow(parent=parent or self._parent) + loader_window.setStyleSheet(style.load_stylesheet()) + self._loader_tool = loader_window return self._loader_tool @@ -104,9 +110,12 @@ class HostToolsHelper: def _get_creator_tool(self, parent): if self._creator_tool is None: + from avalon import style from avalon.tools.creator.app import Window - self._creator_tool = Window(parent=parent or self._parent) + creator_window = Window(parent=parent or self._parent) + creator_window.setStyleSheet(style.load_stylesheet()) + self._creator_tool = creator_window return self._creator_tool @@ -122,9 +131,12 @@ class HostToolsHelper: def _get_subset_manager_tool(self, parent): if self._subset_manager_tool is None: + from avalon import style from avalon.tools.subsetmanager import Window - self._subset_manager_tool = Window(parent=parent or self._parent) + subset_manager_window = Window(parent=parent or self._parent) + subset_manager_window.setStyleSheet(style.load_stylesheet()) + self._subset_manager_tool = subset_manager_window return self._subset_manager_tool @@ -139,9 +151,12 @@ class HostToolsHelper: def _get_scene_inventory_tool(self, parent): if self._scene_inventory_tool is None: + from avalon import style from avalon.tools.sceneinventory.app import Window - self._scene_inventory_tool = Window(parent=parent or self._parent) + scene_inventory_window = Window(parent=parent or self._parent) + scene_inventory_window.setStyleSheet(style.load_stylesheet()) + self._scene_inventory_tool = scene_inventory_window return self._scene_inventory_tool @@ -157,11 +172,14 @@ class HostToolsHelper: def _get_library_loader_tool(self, parent): if self._library_loader_tool is None: + from avalon import style from openpype.tools.libraryloader import LibraryLoaderWindow - self._library_loader_tool = LibraryLoaderWindow( + library_window = LibraryLoaderWindow( parent=parent or self._parent ) + library_window.setStyleSheet(style.load_stylesheet()) + self._library_loader_tool = library_window return self._library_loader_tool @@ -181,9 +199,12 @@ class HostToolsHelper: def _get_look_assigner_tool(self, parent): if self._look_assigner_tool is None: + from avalon import style import mayalookassigner - self._look_assigner_tool = mayalookassigner.App(parent) + mayalookassigner_window = mayalookassigner.App(parent) + mayalookassigner_window.setStyleSheet(style.load_stylesheet()) + self._look_assigner_tool = mayalookassigner_window return self._look_assigner_tool def show_look_assigner(self, parent=None): From ad0d1b03feca27b88e0a436f1f543207a5adab07 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 15 Oct 2021 11:23:01 +0200 Subject: [PATCH 125/279] changed getters to public methods --- openpype/tools/utils/host_tools.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/openpype/tools/utils/host_tools.py b/openpype/tools/utils/host_tools.py index f8a30cf8f6..31e305c70e 100644 --- a/openpype/tools/utils/host_tools.py +++ b/openpype/tools/utils/host_tools.py @@ -38,7 +38,7 @@ class HostToolsHelper: self._log = Logger.get_logger(self.__class__.__name__) return self._log - def _get_workfiles_tool(self, parent): + def get_workfiles_tool(self, parent): if self._workfiles_tool is None: from avalon import style from openpype.tools.workfiles.app import ( @@ -62,7 +62,7 @@ class HostToolsHelper: if save is None: save = True - workfiles_tool = self._get_workfiles_tool(parent) + workfiles_tool = self.get_workfiles_tool(parent) if use_context: context = { "asset": avalon.api.Session["AVALON_ASSET"], @@ -80,7 +80,7 @@ class HostToolsHelper: workfiles_tool.raise_() workfiles_tool.activateWindow() - def _get_loader_tool(self, parent): + def get_loader_tool(self, parent): if self._loader_tool is None: from avalon import style from openpype.tools.loader import LoaderWindow @@ -95,7 +95,7 @@ class HostToolsHelper: """Loader tool for loading representations.""" if use_context is None: use_context = False - loader_tool = self._get_loader_tool(parent) + loader_tool = self.get_loader_tool(parent) if use_context: context = {"asset": avalon.api.Session["AVALON_ASSET"]} @@ -108,7 +108,7 @@ class HostToolsHelper: loader_tool.activateWindow() loader_tool.refresh() - def _get_creator_tool(self, parent): + def get_creator_tool(self, parent): if self._creator_tool is None: from avalon import style from avalon.tools.creator.app import Window @@ -121,7 +121,7 @@ class HostToolsHelper: def show_creator(self, parent=None): """Show tool to create new instantes for publishing.""" - creator_tool = self._get_creator_tool(parent) + creator_tool = self.get_creator_tool(parent) creator_tool.refresh() creator_tool.show() @@ -129,7 +129,7 @@ class HostToolsHelper: creator_tool.raise_() creator_tool.activateWindow() - def _get_subset_manager_tool(self, parent): + def get_subset_manager_tool(self, parent): if self._subset_manager_tool is None: from avalon import style from avalon.tools.subsetmanager import Window @@ -142,14 +142,14 @@ class HostToolsHelper: def show_subset_manager(self, parent=None): """Show tool display/remove existing created instances.""" - subset_manager_tool = self._get_subset_manager_tool(parent) + subset_manager_tool = self.get_subset_manager_tool(parent) subset_manager_tool.show() # Pull window to the front. subset_manager_tool.raise_() subset_manager_tool.activateWindow() - def _get_scene_inventory_tool(self, parent): + def get_scene_inventory_tool(self, parent): if self._scene_inventory_tool is None: from avalon import style from avalon.tools.sceneinventory.app import Window @@ -162,7 +162,7 @@ class HostToolsHelper: def show_scene_inventory(self, parent=None): """Show tool maintain loaded containers.""" - scene_inventory_tool = self._get_scene_inventory_tool(parent) + scene_inventory_tool = self.get_scene_inventory_tool(parent) scene_inventory_tool.show() scene_inventory_tool.refresh() @@ -170,7 +170,7 @@ class HostToolsHelper: scene_inventory_tool.raise_() scene_inventory_tool.activateWindow() - def _get_library_loader_tool(self, parent): + def get_library_loader_tool(self, parent): if self._library_loader_tool is None: from avalon import style from openpype.tools.libraryloader import LibraryLoaderWindow @@ -185,7 +185,7 @@ class HostToolsHelper: def show_library_loader(self, parent=None): """Loader tool for loading representations from library project.""" - library_loader_tool = self._get_library_loader_tool(parent) + library_loader_tool = self.get_library_loader_tool(parent) library_loader_tool.show() library_loader_tool.raise_() library_loader_tool.activateWindow() @@ -197,7 +197,7 @@ class HostToolsHelper: publish.show(parent) - def _get_look_assigner_tool(self, parent): + def get_look_assigner_tool(self, parent): if self._look_assigner_tool is None: from avalon import style import mayalookassigner @@ -209,7 +209,7 @@ class HostToolsHelper: def show_look_assigner(self, parent=None): """Look manager is Maya specific tool for look management.""" - look_assigner_tool = self._get_look_assigner_tool(parent) + look_assigner_tool = self.get_look_assigner_tool(parent) look_assigner_tool.show() def show_tool_by_name(self, tool_name, parent=None, *args, **kwargs): From b2cdb17ed5efe28218b233a46df0df1d491964b7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 15 Oct 2021 11:26:31 +0200 Subject: [PATCH 126/279] adde simplified docstrings to get tool methods --- openpype/tools/utils/host_tools.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openpype/tools/utils/host_tools.py b/openpype/tools/utils/host_tools.py index 31e305c70e..6b137213f8 100644 --- a/openpype/tools/utils/host_tools.py +++ b/openpype/tools/utils/host_tools.py @@ -39,6 +39,7 @@ class HostToolsHelper: return self._log def get_workfiles_tool(self, parent): + """Create, cache and return workfiles tool window.""" if self._workfiles_tool is None: from avalon import style from openpype.tools.workfiles.app import ( @@ -81,6 +82,7 @@ class HostToolsHelper: workfiles_tool.activateWindow() def get_loader_tool(self, parent): + """Create, cache and return loader tool window.""" if self._loader_tool is None: from avalon import style from openpype.tools.loader import LoaderWindow @@ -109,6 +111,7 @@ class HostToolsHelper: loader_tool.refresh() def get_creator_tool(self, parent): + """Create, cache and return creator tool window.""" if self._creator_tool is None: from avalon import style from avalon.tools.creator.app import Window @@ -130,6 +133,7 @@ class HostToolsHelper: creator_tool.activateWindow() def get_subset_manager_tool(self, parent): + """Create, cache and return subset manager tool window.""" if self._subset_manager_tool is None: from avalon import style from avalon.tools.subsetmanager import Window @@ -150,6 +154,7 @@ class HostToolsHelper: subset_manager_tool.activateWindow() def get_scene_inventory_tool(self, parent): + """Create, cache and return scene inventory tool window.""" if self._scene_inventory_tool is None: from avalon import style from avalon.tools.sceneinventory.app import Window @@ -171,6 +176,7 @@ class HostToolsHelper: scene_inventory_tool.activateWindow() def get_library_loader_tool(self, parent): + """Create, cache and return library loader tool window.""" if self._library_loader_tool is None: from avalon import style from openpype.tools.libraryloader import LibraryLoaderWindow @@ -198,6 +204,7 @@ class HostToolsHelper: publish.show(parent) def get_look_assigner_tool(self, parent): + """Create, cache and return look assigner tool window.""" if self._look_assigner_tool is None: from avalon import style import mayalookassigner From 1517966a5a987803dcaf59d0ad6f8960d5c96063 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 15 Oct 2021 11:42:44 +0200 Subject: [PATCH 127/279] added ability to get window by name --- openpype/tools/utils/host_tools.py | 43 ++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/openpype/tools/utils/host_tools.py b/openpype/tools/utils/host_tools.py index 6b137213f8..431009ac27 100644 --- a/openpype/tools/utils/host_tools.py +++ b/openpype/tools/utils/host_tools.py @@ -219,6 +219,40 @@ class HostToolsHelper: look_assigner_tool = self.get_look_assigner_tool(parent) look_assigner_tool.show() + def get_tool_by_name(self, tool_name, parent=None, *args, **kwargs): + """Show tool by it's name. + + This is helper for + """ + if tool_name == "workfiles": + return self.get_workfiles_tool(parent, *args, **kwargs) + + elif tool_name == "loader": + return self.get_loader_tool(parent, *args, **kwargs) + + elif tool_name == "libraryloader": + return self.get_library_loader_tool(parent, *args, **kwargs) + + elif tool_name == "creator": + return self.get_creator_tool(parent, *args, **kwargs) + + elif tool_name == "subsetmanager": + return self.get_subset_manager_tool(parent, *args, **kwargs) + + elif tool_name == "sceneinventory": + return self.get_scene_inventory_tool(parent, *args, **kwargs) + + elif tool_name == "lookassigner": + return self.get_look_assigner_tool(parent, *args, **kwargs) + + elif tool_name == "publish": + self.log.info("Can't return publish tool window.") + + else: + self.log.warning( + "Can't show unknown tool name: \"{}\"".format(tool_name) + ) + def show_tool_by_name(self, tool_name, parent=None, *args, **kwargs): """Show tool by it's name. @@ -273,8 +307,17 @@ class _SingletonPoint: cls._create_helper() cls.helper.show_tool_by_name(tool_name, parent, *args, **kwargs) + @classmethod + def get_tool_by_name(cls, tool_name, parent=None, *args, **kwargs): + cls._create_helper() + return cls.helper.get_tool_by_name(tool_name, parent, *args, **kwargs) + # Function callbacks using singleton acces point +def get_tool_by_name(tool_name, parent=None, *args, **kwargs): + return _SingletonPoint.get_tool_by_name(tool_name, parent, *args, **kwargs) + + def show_tool_by_name(tool_name, parent=None, *args, **kwargs): _SingletonPoint.show_tool_by_name(tool_name, parent, *args, **kwargs) From 7f22b4fc5c75cd5a237bf7aaa66b1ad4bd315b5d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 15 Oct 2021 12:09:39 +0200 Subject: [PATCH 128/279] houdini use host_tools --- .../hosts/houdini/startup/MainMenuCommon.xml | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/houdini/startup/MainMenuCommon.xml b/openpype/hosts/houdini/startup/MainMenuCommon.xml index 76585085e2..2923cb9ef5 100644 --- a/openpype/hosts/houdini/startup/MainMenuCommon.xml +++ b/openpype/hosts/houdini/startup/MainMenuCommon.xml @@ -7,24 +7,30 @@ @@ -32,7 +38,7 @@ cbsceneinventory.show() @@ -43,9 +49,10 @@ publish.show(parent) From dd74ab89488a9369695bb528b906b0ffb68758f0 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 15 Oct 2021 14:16:22 +0200 Subject: [PATCH 129/279] hound: suggestions --- openpype/hosts/nuke/plugins/load/load_clip.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/nuke/plugins/load/load_clip.py b/openpype/hosts/nuke/plugins/load/load_clip.py index 265ab39b07..f8fc5e3928 100644 --- a/openpype/hosts/nuke/plugins/load/load_clip.py +++ b/openpype/hosts/nuke/plugins/load/load_clip.py @@ -13,8 +13,6 @@ from avalon.nuke import ( ) from openpype.hosts.nuke.api import plugin -reload(plugin) - class LoadClip(plugin.NukeLoader): """Load clip into Nuke @@ -144,7 +142,6 @@ class LoadClip(plugin.NukeLoader): self.set_range_to_node(read_node, first, last, start_at_workfile) - # add additional metadata from the version to imprint Avalon knob add_keys = ["frameStart", "frameEnd", "source", "colorspace", "author", "fps", "version", @@ -338,8 +335,10 @@ class LoadClip(plugin.NukeLoader): if time_warp_nodes != []: start_anim = self.script_start + (self.handle_start / speed) for timewarp in time_warp_nodes: - twn = nuke.createNode(timewarp["Class"], - "name {}".format(timewarp["name"])) + twn = nuke.createNode( + timewarp["Class"], + "name {}".format(timewarp["name"]) + ) if isinstance(timewarp["lookup"], list): # if array for animation twn["lookup"].setAnimated() From f91760cf1267e7b054f42feeb804c7e6b89b4765 Mon Sep 17 00:00:00 2001 From: karimmozlia Date: Fri, 15 Oct 2021 14:21:26 +0200 Subject: [PATCH 130/279] ValidateRigJointsHidden typo --- .../schemas/projects_schema/schemas/schema_maya_publish.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json index bde8c15958..cbacd12efa 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json @@ -342,7 +342,7 @@ }, { "key": "ValidateRigJointsHidden", - "label": "Validate Rig JointsHidden" + "label": "Validate Rig Joints Hidden" }, { "key": "ValidateRigControllers", From e39ffb35c55f974d4fad5dc231f1ae716e41e0ff Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 15 Oct 2021 14:23:00 +0200 Subject: [PATCH 131/279] updating avalon core --- repos/avalon-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repos/avalon-core b/repos/avalon-core index 4b80f81e66..7e5efd6885 160000 --- a/repos/avalon-core +++ b/repos/avalon-core @@ -1 +1 @@ -Subproject commit 4b80f81e66aca593784be8b299110a0b6541276f +Subproject commit 7e5efd6885330d84bb8495975bcab84df49bfa3d From 3810a2f8a83073959e4876c7077ab48f143d4065 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 15 Oct 2021 14:36:43 +0200 Subject: [PATCH 132/279] celaction is using host_tools --- openpype/hosts/celaction/api/cli.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/celaction/api/cli.py b/openpype/hosts/celaction/api/cli.py index acd9da8229..bc1e3eaf89 100644 --- a/openpype/hosts/celaction/api/cli.py +++ b/openpype/hosts/celaction/api/cli.py @@ -4,7 +4,6 @@ import copy import argparse from avalon import io -from avalon.tools import publish import pyblish.api import pyblish.util @@ -13,6 +12,7 @@ from openpype.api import Logger import openpype import openpype.hosts.celaction from openpype.hosts.celaction import api as celaction +from openpype.tools.utils import host_tools log = Logger().get_logger("Celaction_cli_publisher") @@ -82,7 +82,7 @@ def main(): pyblish.api.register_host(publish_host) - return publish.show() + return host_tools.show_publish() if __name__ == "__main__": From cca7b4c13d0d810f14c7c094867a95e1e5cb7909 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 15 Oct 2021 14:54:54 +0200 Subject: [PATCH 133/279] use host_tools in fusion --- openpype/hosts/fusion/api/__init__.py | 7 +------ openpype/hosts/fusion/api/menu.py | 30 ++++++++------------------- openpype/hosts/fusion/api/pipeline.py | 12 ----------- 3 files changed, 10 insertions(+), 39 deletions(-) diff --git a/openpype/hosts/fusion/api/__init__.py b/openpype/hosts/fusion/api/__init__.py index 5581a0a9cb..5aee978c15 100644 --- a/openpype/hosts/fusion/api/__init__.py +++ b/openpype/hosts/fusion/api/__init__.py @@ -1,8 +1,6 @@ from .pipeline import ( install, - uninstall, - publish, - launch_workfiles_app + uninstall ) from .utils import ( @@ -22,12 +20,9 @@ __all__ = [ # pipeline "install", "uninstall", - "publish", - "launch_workfiles_app", # utils "setup", - "get_resolve_module", # lib "get_additional_data", diff --git a/openpype/hosts/fusion/api/menu.py b/openpype/hosts/fusion/api/menu.py index 9093aa9e5e..5d2efb4911 100644 --- a/openpype/hosts/fusion/api/menu.py +++ b/openpype/hosts/fusion/api/menu.py @@ -3,19 +3,7 @@ import sys from Qt import QtWidgets, QtCore -from .pipeline import ( - publish, - launch_workfiles_app -) - -from avalon.tools import ( - creator, - sceneinventory, -) -from openpype.tools import ( - loader, - libraryloader -) +from openpype.tools.utils import host_tools from openpype.hosts.fusion.scripts import ( set_rendermode, @@ -36,7 +24,7 @@ def load_stylesheet(): class Spacer(QtWidgets.QWidget): def __init__(self, height, *args, **kwargs): - super(self.__class__, self).__init__(*args, **kwargs) + super(Spacer, self).__init__(*args, **kwargs) self.setFixedHeight(height) @@ -53,7 +41,7 @@ class Spacer(QtWidgets.QWidget): class OpenPypeMenu(QtWidgets.QWidget): def __init__(self, *args, **kwargs): - super(self.__class__, self).__init__(*args, **kwargs) + super(OpenPypeMenu, self).__init__(*args, **kwargs) self.setObjectName("OpenPypeMenu") @@ -117,27 +105,27 @@ class OpenPypeMenu(QtWidgets.QWidget): def on_workfile_clicked(self): print("Clicked Workfile") - launch_workfiles_app() + host_tools.show_workfiles() def on_create_clicked(self): print("Clicked Create") - creator.show() + host_tools.show_creator() def on_publish_clicked(self): print("Clicked Publish") - publish(None) + host_tools.show_publish() def on_load_clicked(self): print("Clicked Load") - loader.show(use_context=True) + host_tools.show_loader(use_context=True) def on_inventory_clicked(self): print("Clicked Inventory") - sceneinventory.show() + host_tools.show_scene_inventory() def on_libload_clicked(self): print("Clicked Library") - libraryloader.show() + host_tools.show_library_loader() def on_rendernode_clicked(self): from avalon import style diff --git a/openpype/hosts/fusion/api/pipeline.py b/openpype/hosts/fusion/api/pipeline.py index 688e75f6fe..c721146830 100644 --- a/openpype/hosts/fusion/api/pipeline.py +++ b/openpype/hosts/fusion/api/pipeline.py @@ -3,7 +3,6 @@ Basic avalon integration """ import os -from openpype.tools import workfiles from avalon import api as avalon from pyblish import api as pyblish from openpype.api import Logger @@ -98,14 +97,3 @@ def on_pyblish_instance_toggled(instance, new_value, old_value): current = attrs["TOOLB_PassThrough"] if current != passthrough: tool.SetAttrs({"TOOLB_PassThrough": passthrough}) - - -def launch_workfiles_app(*args): - workdir = os.environ["AVALON_WORKDIR"] - workfiles.show(workdir) - - -def publish(parent): - """Shorthand to publish from within host""" - from avalon.tools import publish - return publish.show(parent) From 6621b2c36e6d897b35421a7005857cbf51df053f Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 15 Oct 2021 15:00:44 +0200 Subject: [PATCH 134/279] OP-1063 - added validator for source files for Standalone Publisher --- .../plugins/publish/validate_sources.py | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 openpype/hosts/standalonepublisher/plugins/publish/validate_sources.py diff --git a/openpype/hosts/standalonepublisher/plugins/publish/validate_sources.py b/openpype/hosts/standalonepublisher/plugins/publish/validate_sources.py new file mode 100644 index 0000000000..da424cfb45 --- /dev/null +++ b/openpype/hosts/standalonepublisher/plugins/publish/validate_sources.py @@ -0,0 +1,37 @@ +import pyblish.api +import openpype.api + +import os + + +class ValidateSources(pyblish.api.InstancePlugin): + """Validates source files. + + Loops through all 'files' in 'stagingDir' if actually exist. They might + got deleted between starting of SP and now. + + """ + + order = openpype.api.ValidateContentsOrder + label = "Check source files" + + optional = True # only for unforeseeable cases + + hosts = ["standalonepublisher"] + + def process(self, instance): + self.log.info("instance {}".format(instance.data)) + + for repr in instance.data["representations"]: + files = [] + if isinstance(repr["files"], str): + files.append(repr["files"]) + else: + files = list(repr["files"]) + + for file_name in files: + source_file = os.path.join(repr["stagingDir"], + file_name) + + if not os.path.exists(source_file): + raise ValueError("File {} not found".format(source_file)) From ad1af83d5385a362f77c84eef6c84c4facee1b50 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 15 Oct 2021 15:03:40 +0200 Subject: [PATCH 135/279] maya is using host_tools --- openpype/hosts/maya/api/__init__.py | 8 +- openpype/hosts/maya/api/customize.py | 151 ++++++++++++--------------- openpype/hosts/maya/api/menu.py | 6 +- 3 files changed, 68 insertions(+), 97 deletions(-) diff --git a/openpype/hosts/maya/api/__init__.py b/openpype/hosts/maya/api/__init__.py index 725a075417..e4eb44fc27 100644 --- a/openpype/hosts/maya/api/__init__.py +++ b/openpype/hosts/maya/api/__init__.py @@ -8,7 +8,7 @@ from avalon import api as avalon from avalon import pipeline from avalon.maya import suspended_refresh from avalon.maya.pipeline import IS_HEADLESS -from openpype.tools import workfiles +from openpype.tools.utils import host_tools from pyblish import api as pyblish from openpype.lib import any_outdated import openpype.hosts.maya @@ -208,16 +208,12 @@ def on_init(_): launch_workfiles = os.environ.get("WORKFILES_STARTUP") if launch_workfiles: - safe_deferred(launch_workfiles_app) + safe_deferred(host_tools.show_workfiles) if not IS_HEADLESS: safe_deferred(override_toolbox_ui) -def launch_workfiles_app(): - workfiles.show(os.environ["AVALON_WORKDIR"]) - - def on_before_save(return_code, _): """Run validation for scene's FPS prior to saving""" return lib.validate_fps() diff --git a/openpype/hosts/maya/api/customize.py b/openpype/hosts/maya/api/customize.py index a84412963b..54c058aa75 100644 --- a/openpype/hosts/maya/api/customize.py +++ b/openpype/hosts/maya/api/customize.py @@ -1,10 +1,16 @@ """A set of commands that install overrides to Maya's UI""" +import os +import logging + +from functools import partial + import maya.cmds as mc import maya.mel as mel -from functools import partial -import os -import logging + +from avalon.maya import pipeline +from openpype.api import resources +from openpype.tools.utils import host_tools log = logging.getLogger(__name__) @@ -69,39 +75,8 @@ def override_component_mask_commands(): def override_toolbox_ui(): """Add custom buttons in Toolbox as replacement for Maya web help icon.""" - inventory = None - loader = None - launch_workfiles_app = None - mayalookassigner = None - try: - import avalon.tools.sceneinventory as inventory - except Exception: - log.warning("Could not import SceneInventory tool") - - try: - import openpype.tools.loader as loader - except Exception: - log.warning("Could not import Loader tool") - - try: - from avalon.maya.pipeline import launch_workfiles_app - except Exception: - log.warning("Could not import Workfiles tool") - - try: - from openpype.tools import mayalookassigner - except Exception: - log.warning("Could not import Maya Look assigner tool") - - from openpype.api import resources - icons = resources.get_resource("icons") - if not any(( - mayalookassigner, launch_workfiles_app, loader, inventory - )): - return - # Ensure the maya web icon on toolbox exists web_button = "ToolBox|MainToolboxLayout|mayaWebButton" if not mc.iconTextButton(web_button, query=True, exists=True): @@ -120,65 +95,69 @@ def override_toolbox_ui(): # Create our controls background_color = (0.267, 0.267, 0.267) controls = [] - if mayalookassigner: - controls.append( - mc.iconTextButton( - "pype_toolbox_lookmanager", - annotation="Look Manager", - label="Look Manager", - image=os.path.join(icons, "lookmanager.png"), - command=lambda: mayalookassigner.show(), - bgc=background_color, - width=icon_size, - height=icon_size, - parent=parent - ) + controls.append( + mc.iconTextButton( + "pype_toolbox_lookmanager", + annotation="Look Manager", + label="Look Manager", + image=os.path.join(icons, "lookmanager.png"), + command=lambda: host_tools.show_look_assigner( + parent=pipeline._parent + ), + bgc=background_color, + width=icon_size, + height=icon_size, + parent=parent ) + ) - if launch_workfiles_app: - controls.append( - mc.iconTextButton( - "pype_toolbox_workfiles", - annotation="Work Files", - label="Work Files", - image=os.path.join(icons, "workfiles.png"), - command=lambda: launch_workfiles_app(), - bgc=background_color, - width=icon_size, - height=icon_size, - parent=parent - ) + controls.append( + mc.iconTextButton( + "pype_toolbox_workfiles", + annotation="Work Files", + label="Work Files", + image=os.path.join(icons, "workfiles.png"), + command=lambda: host_tools.show_workfiles( + parent=pipeline._parent + ), + bgc=background_color, + width=icon_size, + height=icon_size, + parent=parent ) + ) - if loader: - controls.append( - mc.iconTextButton( - "pype_toolbox_loader", - annotation="Loader", - label="Loader", - image=os.path.join(icons, "loader.png"), - command=lambda: loader.show(use_context=True), - bgc=background_color, - width=icon_size, - height=icon_size, - parent=parent - ) + controls.append( + mc.iconTextButton( + "pype_toolbox_loader", + annotation="Loader", + label="Loader", + image=os.path.join(icons, "loader.png"), + command=lambda: host_tools.show_loader( + parent=pipeline._parent, use_context=True + ), + bgc=background_color, + width=icon_size, + height=icon_size, + parent=parent ) + ) - if inventory: - controls.append( - mc.iconTextButton( - "pype_toolbox_manager", - annotation="Inventory", - label="Inventory", - image=os.path.join(icons, "inventory.png"), - command=lambda: inventory.show(), - bgc=background_color, - width=icon_size, - height=icon_size, - parent=parent - ) + controls.append( + mc.iconTextButton( + "pype_toolbox_manager", + annotation="Inventory", + label="Inventory", + image=os.path.join(icons, "inventory.png"), + command=lambda: host_tools.show_scene_inventory( + parent=pipeline._parent + ), + bgc=background_color, + width=icon_size, + height=icon_size, + parent=parent ) + ) # Add the buttons on the bottom and stack # them above each other with side padding diff --git a/openpype/hosts/maya/api/menu.py b/openpype/hosts/maya/api/menu.py index 42cb76dca6..4f0966abfd 100644 --- a/openpype/hosts/maya/api/menu.py +++ b/openpype/hosts/maya/api/menu.py @@ -28,7 +28,6 @@ def _get_menu(menu_name=None): def deferred(): - def add_build_workfiles_item(): # Add build first workfile cmds.menuItem(divider=True, parent=pipeline._menu) @@ -48,9 +47,6 @@ def deferred(): ) def modify_workfiles(): - def launch_workfiles_app(*_args, **_kwargs): - host_tools.show_workfiles(pipeline._parent) - # Find the pipeline menu top_menu = _get_menu() @@ -71,7 +67,7 @@ def deferred(): cmds.menuItem( "Work Files", parent=pipeline._menu, - command=launch_workfiles_app, + command=lambda *args: host_tools.show_workfiles(pipeline._parent), insertAfter=after_action ) From 3356c21db245a14bd52af50825f32f75428bba4e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 15 Oct 2021 15:06:00 +0200 Subject: [PATCH 136/279] cleanup harmony imports --- openpype/hosts/harmony/api/__init__.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/openpype/hosts/harmony/api/__init__.py b/openpype/hosts/harmony/api/__init__.py index fd21725bd5..bcf7dffbe7 100644 --- a/openpype/hosts/harmony/api/__init__.py +++ b/openpype/hosts/harmony/api/__init__.py @@ -3,17 +3,14 @@ import os from pathlib import Path import logging -import re from openpype import lib -from openpype.api import (get_current_project_settings) import openpype.hosts.harmony import pyblish.api from avalon import io, harmony import avalon.api -import avalon.tools.sceneinventory log = logging.getLogger("openpype.hosts.harmony") From 296b7305072032dd20e76a41d63b67dbf52494bb Mon Sep 17 00:00:00 2001 From: karimmozlia Date: Fri, 15 Oct 2021 15:26:17 +0200 Subject: [PATCH 137/279] Root.format instead of data.width --- openpype/hosts/nuke/api/lib.py | 21 ------------------- .../plugins/create/create_write_render.py | 6 ++---- 2 files changed, 2 insertions(+), 25 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 87bf137d93..eb4eab2b22 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -1031,27 +1031,6 @@ class WorkfileSettings(object): log.error(msg) nuke.message(msg) - bbox = self._asset_entity.get('data', {}).get('reformat') - - if bbox: - try: - x, y, r, t = bbox.split(".") - data.update( - { - "x": int(x), - "y": int(y), - "r": int(r), - "t": int(t), - } - ) - except Exception as e: - bbox = None - msg = ("{}:{} \nFormat:Reformat need to be set with dots, " - "example: 0.0.1920.1080, " - "/nSetting to default").format(__name__, e) - log.error(msg) - nuke.message(msg) - existing_format = None for format in nuke.formats(): if data["name"] == format.name(): diff --git a/openpype/hosts/nuke/plugins/create/create_write_render.py b/openpype/hosts/nuke/plugins/create/create_write_render.py index acc1ee53ff..5f13fddf4e 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_render.py +++ b/openpype/hosts/nuke/plugins/create/create_write_render.py @@ -112,10 +112,8 @@ class CreateWriteRender(plugin.PypeCreator): "name": "Reformat01", "class": "Reformat", "knobs": [ - ("type", 1), - ("box_fixed", 1), - ("box_width", width), - ("box_height", height) + ("resize", 0), + ("black_outside", 1), ], "dependent": None } From 835a89e82a99cb6c8a313c93ec3c403e0081bd3c Mon Sep 17 00:00:00 2001 From: karimmozlia Date: Fri, 15 Oct 2021 16:03:14 +0200 Subject: [PATCH 138/279] del bbox --- openpype/hosts/nuke/api/lib.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index eb4eab2b22..6e46747d85 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -1042,12 +1042,6 @@ class WorkfileSettings(object): existing_format.setWidth(data["width"]) existing_format.setHeight(data["height"]) existing_format.setPixelAspect(data["pixel_aspect"]) - - if bbox: - existing_format.setX(data["x"]) - existing_format.setY(data["y"]) - existing_format.setR(data["r"]) - existing_format.setT(data["t"]) else: format_string = self.make_format_string(**data) log.info("Creating new format: {}".format(format_string)) From 7615d2c7af4eb1ec150f60ba6bf571ad5c3c8f06 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 15 Oct 2021 16:29:49 +0200 Subject: [PATCH 139/279] hiero is using host_tools --- openpype/hosts/hiero/api/menu.py | 9 ++++----- openpype/hosts/hiero/api/pipeline.py | 9 +++------ 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/hiero/api/menu.py b/openpype/hosts/hiero/api/menu.py index bcd78aa5bb..e3de220777 100644 --- a/openpype/hosts/hiero/api/menu.py +++ b/openpype/hosts/hiero/api/menu.py @@ -2,6 +2,7 @@ import os import sys import hiero.core from openpype.api import Logger +from openpype.tools.utils import host_tools from avalon.api import Session from hiero.ui import findMenuAction @@ -41,8 +42,6 @@ def menu_install(): apply_colorspace_project, apply_colorspace_clips ) # here is the best place to add menu - from avalon.tools import creator, sceneinventory - from openpype.tools import loader from avalon.vendor.Qt import QtGui menu_name = os.environ['AVALON_LABEL'] @@ -87,15 +86,15 @@ def menu_install(): creator_action = menu.addAction("Create ...") creator_action.setIcon(QtGui.QIcon("icons:CopyRectangle.png")) - creator_action.triggered.connect(creator.show) + creator_action.triggered.connect(host_tools.show_creator) loader_action = menu.addAction("Load ...") loader_action.setIcon(QtGui.QIcon("icons:CopyRectangle.png")) - loader_action.triggered.connect(loader.show) + loader_action.triggered.connect(host_tools.show_loader) sceneinventory_action = menu.addAction("Manage ...") sceneinventory_action.setIcon(QtGui.QIcon("icons:CopyRectangle.png")) - sceneinventory_action.triggered.connect(sceneinventory.show) + sceneinventory_action.triggered.connect(host_tools.show_scene_inventory) menu.addSeparator() if os.getenv("OPENPYPE_DEVELOP"): diff --git a/openpype/hosts/hiero/api/pipeline.py b/openpype/hosts/hiero/api/pipeline.py index 12f6923de7..6f6588e1be 100644 --- a/openpype/hosts/hiero/api/pipeline.py +++ b/openpype/hosts/hiero/api/pipeline.py @@ -4,13 +4,12 @@ Basic avalon integration import os import contextlib from collections import OrderedDict -from avalon.tools import publish as _publish -from openpype.tools import workfiles from avalon.pipeline import AVALON_CONTAINER_ID from avalon import api as avalon from avalon import schema from pyblish import api as pyblish from openpype.api import Logger +from openpype.tools.utils import host_tools from . import lib, menu, events log = Logger().get_logger(__name__) @@ -211,15 +210,13 @@ def update_container(track_item, data=None): def launch_workfiles_app(*args): ''' Wrapping function for workfiles launcher ''' - workdir = os.environ["AVALON_WORKDIR"] - # show workfile gui - workfiles.show(workdir) + host_tools.show_workfiles() def publish(parent): """Shorthand to publish from within host""" - return _publish.show(parent) + return host_tools.show_publish(parent) @contextlib.contextmanager From 72a645bd74efd26b2f7ce78ef51e50d46bd73831 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 15 Oct 2021 16:47:55 +0200 Subject: [PATCH 140/279] nuke is using host_tools --- openpype/hosts/nuke/api/lib.py | 5 ++--- openpype/hosts/nuke/api/menu.py | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 6fe9af744b..00ed4c6e78 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -7,7 +7,6 @@ from collections import OrderedDict from avalon import api, io, lib -from openpype.tools import workfiles import avalon.nuke from avalon.nuke import lib as anlib from avalon.nuke import ( @@ -24,7 +23,7 @@ from openpype.api import ( get_current_project_settings, ApplicationManager ) - +from openpype.tools.utils import host_tools import nuke from .utils import set_context_favorites @@ -1689,7 +1688,7 @@ def launch_workfiles_app(): if not opnl.workfiles_launched: opnl.workfiles_launched = True - workfiles.show(os.environ["AVALON_WORKDIR"]) + host_tools.show_workfiles() def process_workfile_builder(): diff --git a/openpype/hosts/nuke/api/menu.py b/openpype/hosts/nuke/api/menu.py index 021ea04159..87990c5e92 100644 --- a/openpype/hosts/nuke/api/menu.py +++ b/openpype/hosts/nuke/api/menu.py @@ -4,7 +4,7 @@ from avalon.api import Session from .lib import WorkfileSettings from openpype.api import Logger, BuildWorkfile, get_current_project_settings -from openpype.tools import workfiles +from openpype.tools.utils import host_tools log = Logger().get_logger(__name__) @@ -25,7 +25,7 @@ def install(): menu.removeItem(rm_item[1].name()) menu.addCommand( name, - workfiles.show, + host_tools.show_workfiles, index=2 ) menu.addSeparator(index=3) From 60f1c3cecd0f26758d69ccfa84e6601c0000b0a3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 15 Oct 2021 16:51:45 +0200 Subject: [PATCH 141/279] resolve is using host_tools --- openpype/hosts/resolve/api/menu.py | 24 ++++++++---------------- openpype/hosts/resolve/api/pipeline.py | 8 +++----- 2 files changed, 11 insertions(+), 21 deletions(-) diff --git a/openpype/hosts/resolve/api/menu.py b/openpype/hosts/resolve/api/menu.py index c639fd2db8..262ce739dd 100644 --- a/openpype/hosts/resolve/api/menu.py +++ b/openpype/hosts/resolve/api/menu.py @@ -8,15 +8,7 @@ from .pipeline import ( launch_workfiles_app ) -from avalon.tools import ( - creator, - sceneinventory, - subsetmanager -) -from openpype.tools import ( - loader, - libraryloader, -) +from openpype.tools.utils import host_tools def load_stylesheet(): @@ -32,7 +24,7 @@ def load_stylesheet(): class Spacer(QtWidgets.QWidget): def __init__(self, height, *args, **kwargs): - super(self.__class__, self).__init__(*args, **kwargs) + super(Spacer, self).__init__(*args, **kwargs) self.setFixedHeight(height) @@ -49,7 +41,7 @@ class Spacer(QtWidgets.QWidget): class OpenPypeMenu(QtWidgets.QWidget): def __init__(self, *args, **kwargs): - super(self.__class__, self).__init__(*args, **kwargs) + super(OpenPypeMenu, self).__init__(*args, **kwargs) self.setObjectName("OpenPypeMenu") @@ -119,7 +111,7 @@ class OpenPypeMenu(QtWidgets.QWidget): def on_create_clicked(self): print("Clicked Create") - creator.show() + host_tools.show_creator() def on_publish_clicked(self): print("Clicked Publish") @@ -127,19 +119,19 @@ class OpenPypeMenu(QtWidgets.QWidget): def on_load_clicked(self): print("Clicked Load") - loader.show(use_context=True) + host_tools.show_loader(use_context=True) def on_inventory_clicked(self): print("Clicked Inventory") - sceneinventory.show() + host_tools.show_scene_inventory() def on_subsetm_clicked(self): print("Clicked Subset Manager") - subsetmanager.show() + host_tools.show_subset_manager() def on_libload_clicked(self): print("Clicked Library") - libraryloader.show() + host_tools.show_library_loader() def on_rename_clicked(self): print("Clicked Rename") diff --git a/openpype/hosts/resolve/api/pipeline.py b/openpype/hosts/resolve/api/pipeline.py index 80249310e8..ce95cfe02a 100644 --- a/openpype/hosts/resolve/api/pipeline.py +++ b/openpype/hosts/resolve/api/pipeline.py @@ -4,7 +4,6 @@ Basic avalon integration import os import contextlib from collections import OrderedDict -from openpype.tools import workfiles from avalon import api as avalon from avalon import schema from avalon.pipeline import AVALON_CONTAINER_ID @@ -12,6 +11,7 @@ from pyblish import api as pyblish from openpype.api import Logger from . import lib from . import PLUGINS_DIR +from openpype.tools.utils import host_tools log = Logger().get_logger(__name__) PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") @@ -212,14 +212,12 @@ def update_container(timeline_item, data=None): def launch_workfiles_app(*args): - workdir = os.environ["AVALON_WORKDIR"] - workfiles.show(workdir) + host_tools.show_workfiles() def publish(parent): """Shorthand to publish from within host""" - from avalon.tools import publish - return publish.show(parent) + return host_tools.show_publish() @contextlib.contextmanager From 9d67911bd2f2d08ab3fb1e22221dc9bc07920b85 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 15 Oct 2021 17:30:14 +0200 Subject: [PATCH 142/279] removed unused import --- openpype/tools/utils/host_tools.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/tools/utils/host_tools.py b/openpype/tools/utils/host_tools.py index 431009ac27..ee184ccf2d 100644 --- a/openpype/tools/utils/host_tools.py +++ b/openpype/tools/utils/host_tools.py @@ -4,7 +4,6 @@ It is possible to create `HostToolsHelper` in host implementaion or use singleton approach with global functions (using helper anyway). """ -from Qt import QtCore import avalon.api From 97ed09c059bc269c6f4a5fd0c2e6e3b8cf5d3e99 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 15 Oct 2021 18:12:17 +0200 Subject: [PATCH 143/279] maya look assigner is not added if can't be initialized --- openpype/hosts/maya/api/customize.py | 36 +++++++++++++++++----------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/openpype/hosts/maya/api/customize.py b/openpype/hosts/maya/api/customize.py index 54c058aa75..2e10de1351 100644 --- a/openpype/hosts/maya/api/customize.py +++ b/openpype/hosts/maya/api/customize.py @@ -95,21 +95,29 @@ def override_toolbox_ui(): # Create our controls background_color = (0.267, 0.267, 0.267) controls = [] - controls.append( - mc.iconTextButton( - "pype_toolbox_lookmanager", - annotation="Look Manager", - label="Look Manager", - image=os.path.join(icons, "lookmanager.png"), - command=lambda: host_tools.show_look_assigner( - parent=pipeline._parent - ), - bgc=background_color, - width=icon_size, - height=icon_size, - parent=parent + look_assigner = None + try: + look_assigner = host_tools.get_tool_by_name( + "lookassigner", + parent=pipeline._parent + ) + except Exception as exc: + log.warning("Couldn't create Look assigner window.") + + if look_assigner is not None: + controls.append( + mc.iconTextButton( + "pype_toolbox_lookmanager", + annotation="Look Manager", + label="Look Manager", + image=os.path.join(icons, "lookmanager.png"), + command=host_tools.show_look_assigner, + bgc=background_color, + width=icon_size, + height=icon_size, + parent=parent + ) ) - ) controls.append( mc.iconTextButton( From 447add6b4a74273e17355856778ddae6a15346dd Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 15 Oct 2021 18:16:19 +0200 Subject: [PATCH 144/279] log traceback of lookassigner creation --- openpype/hosts/maya/api/customize.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/api/customize.py b/openpype/hosts/maya/api/customize.py index 2e10de1351..8474262626 100644 --- a/openpype/hosts/maya/api/customize.py +++ b/openpype/hosts/maya/api/customize.py @@ -101,8 +101,8 @@ def override_toolbox_ui(): "lookassigner", parent=pipeline._parent ) - except Exception as exc: - log.warning("Couldn't create Look assigner window.") + except Exception: + log.warning("Couldn't create Look assigner window.", exc_info=True) if look_assigner is not None: controls.append( From 9eaa0a5a380550203e55aaed36f9ed96ecf2946e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 15 Oct 2021 18:58:37 +0200 Subject: [PATCH 145/279] pass parenting of widget properly in main window --- openpype/tools/loader/app.py | 96 ++++++++++++++++++++---------------- 1 file changed, 54 insertions(+), 42 deletions(-) diff --git a/openpype/tools/loader/app.py b/openpype/tools/loader/app.py index c18b6e798a..e7a7d2c7ad 100644 --- a/openpype/tools/loader/app.py +++ b/openpype/tools/loader/app.py @@ -54,61 +54,72 @@ class LoaderWindow(QtWidgets.QDialog): self.setWindowFlags(QtCore.Qt.Window) self.setFocusPolicy(QtCore.Qt.StrongFocus) - body = QtWidgets.QWidget() - footer = QtWidgets.QWidget() - footer.setFixedHeight(20) + split = QtWidgets.QSplitter(self) - container = QtWidgets.QWidget() + asset_filter_splitter = QtWidgets.QSplitter(split) + asset_filter_splitter.setOrientation(QtCore.Qt.Vertical) - assets = AssetWidget(io, multiselection=True, parent=self) + # --- Left part --- + # Assets widget + assets = AssetWidget( + io, multiselection=True, parent=asset_filter_splitter + ) assets.set_current_asset_btn_visibility(True) - families = FamilyListView(io, self.family_config_cache, self) - subsets = SubsetWidget( - io, - self.groups_config, - self.family_config_cache, - tool_name=self.tool_name, - parent=self + # Families widget + families = FamilyListView( + io, self.family_config_cache, asset_filter_splitter ) - version = VersionWidget(io) - thumbnail = ThumbnailWidget(io) - representations = RepresentationWidget(io, self.tool_name) - - manager = ModulesManager() - sync_server = manager.modules_by_name["sync_server"] - - thumb_ver_splitter = QtWidgets.QSplitter() - thumb_ver_splitter.setOrientation(QtCore.Qt.Vertical) - thumb_ver_splitter.addWidget(thumbnail) - thumb_ver_splitter.addWidget(version) - if sync_server.enabled: - thumb_ver_splitter.addWidget(representations) - thumb_ver_splitter.setStretchFactor(0, 30) - thumb_ver_splitter.setStretchFactor(1, 35) - - # Create splitter to show / hide family filters - asset_filter_splitter = QtWidgets.QSplitter() - asset_filter_splitter.setOrientation(QtCore.Qt.Vertical) asset_filter_splitter.addWidget(assets) asset_filter_splitter.addWidget(families) asset_filter_splitter.setStretchFactor(0, 65) asset_filter_splitter.setStretchFactor(1, 35) - container_layout = QtWidgets.QHBoxLayout(container) - container_layout.setContentsMargins(0, 0, 0, 0) - split = QtWidgets.QSplitter() + # --- Middle part --- + # Subsets widget + subsets = SubsetWidget( + io, + self.groups_config, + self.family_config_cache, + tool_name=self.tool_name, + parent=split + ) + + # --- Right part --- + thumb_ver_splitter = QtWidgets.QSplitter(split) + thumb_ver_splitter.setOrientation(QtCore.Qt.Vertical) + version = VersionWidget(io, parent=thumb_ver_splitter) + thumbnail = ThumbnailWidget(io, parent=thumb_ver_splitter) + + manager = ModulesManager() + sync_server = manager.modules_by_name.get("sync_server") + sync_server_enabled = False + if sync_server is not None: + sync_server_enabled = sync_server.enabled + + thumb_ver_splitter.addWidget(thumbnail) + thumb_ver_splitter.addWidget(version) + + representations = None + if sync_server_enabled: + representations = RepresentationWidget( + io, self.tool_name, parent=thumb_ver_splitter + ) + thumb_ver_splitter.addWidget(representations) + + thumb_ver_splitter.setStretchFactor(0, 30) + thumb_ver_splitter.setStretchFactor(1, 35) + split.addWidget(asset_filter_splitter) split.addWidget(subsets) split.addWidget(thumb_ver_splitter) - container_layout.addWidget(split) + # TODO keep footer size by message size + footer = QtWidgets.QWidget(self) + footer.setFixedHeight(20) - body_layout = QtWidgets.QHBoxLayout(body) - body_layout.addWidget(container) - body_layout.setContentsMargins(0, 0, 0, 0) - - message = QtWidgets.QLabel() + # TODO Don't hide messsage just set label to empty string + message = QtWidgets.QLabel(footer) message.hide() footer_layout = QtWidgets.QVBoxLayout(footer) @@ -116,7 +127,7 @@ class LoaderWindow(QtWidgets.QDialog): footer_layout.setContentsMargins(0, 0, 0, 0) layout = QtWidgets.QVBoxLayout(self) - layout.addWidget(body) + layout.addWidget(split) layout.addWidget(footer) self.data = { @@ -152,6 +163,7 @@ class LoaderWindow(QtWidgets.QDialog): representations.load_started.connect(self._on_load_start) representations.load_ended.connect(self._on_load_end) + # TODO add overlay using stack widget self._overlay_frame = overlay_frame self.family_config_cache.refresh() @@ -161,7 +173,7 @@ class LoaderWindow(QtWidgets.QDialog): self._assetschanged() # Defaults - if sync_server.enabled: + if sync_server_enabled: split.setSizes([250, 1000, 550]) self.resize(1800, 900) else: From 514e49ff239927128b298e054af8ed58328e60b3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 15 Oct 2021 19:07:28 +0200 Subject: [PATCH 146/279] simplified message showing without scheduler --- openpype/tools/loader/app.py | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/openpype/tools/loader/app.py b/openpype/tools/loader/app.py index e7a7d2c7ad..a7f66bdccb 100644 --- a/openpype/tools/loader/app.py +++ b/openpype/tools/loader/app.py @@ -37,6 +37,7 @@ class LoaderWindow(QtWidgets.QDialog): """Asset loader interface""" tool_name = "loader" + message_timeout = 5000 def __init__(self, parent=None): super(LoaderWindow, self).__init__(parent) @@ -118,12 +119,10 @@ class LoaderWindow(QtWidgets.QDialog): footer = QtWidgets.QWidget(self) footer.setFixedHeight(20) - # TODO Don't hide messsage just set label to empty string - message = QtWidgets.QLabel(footer) - message.hide() + message_label = QtWidgets.QLabel(footer) footer_layout = QtWidgets.QVBoxLayout(footer) - footer_layout.addWidget(message) + footer_layout.addWidget(message_label) footer_layout.setContentsMargins(0, 0, 0, 0) layout = QtWidgets.QVBoxLayout(self) @@ -139,9 +138,6 @@ class LoaderWindow(QtWidgets.QDialog): "thumbnail": thumbnail, "representations": representations }, - "label": { - "message": message, - }, "state": { "assetIds": None } @@ -150,6 +146,12 @@ class LoaderWindow(QtWidgets.QDialog): overlay_frame = OverlayFrame("Loading...", self) overlay_frame.setVisible(False) + message_timer = QtCore.QTimer() + message_timer.setInterval(self.message_timeout) + message_timer.setSingleShot(True) + + message_timer.timeout.connect(self._on_message_timeout) + families.active_changed.connect(subsets.set_family_filters) assets.selection_changed.connect(self.on_assetschanged) assets.refresh_triggered.connect(self.on_assetschanged) @@ -164,7 +166,10 @@ class LoaderWindow(QtWidgets.QDialog): representations.load_ended.connect(self._on_load_end) # TODO add overlay using stack widget + self._message_label = message_label + self._overlay_frame = overlay_frame + self._message_timer = message_timer self.family_config_cache.refresh() self.groups_config.refresh() @@ -450,13 +455,13 @@ class LoaderWindow(QtWidgets.QDialog): asset_widget = self.data["widgets"]["assets"] asset_widget.select_assets(asset) - def echo(self, message): - widget = self.data["label"]["message"] - widget.setText(str(message)) - widget.show() - print(message) + def _on_message_timeout(self): + self._message_label.setText("") - lib.schedule(widget.hide, 5000, channel="message") + def echo(self, message): + self._message_label.setText(str(message)) + print(message) + self._message_timer.start() def closeEvent(self, event): # Kill on holding SHIFT From 893ae73437d11dbce05428a9cdf6afa7507a706c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 15 Oct 2021 19:13:51 +0200 Subject: [PATCH 147/279] fix repres widget --- openpype/tools/loader/app.py | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/openpype/tools/loader/app.py b/openpype/tools/loader/app.py index a7f66bdccb..1b316c9223 100644 --- a/openpype/tools/loader/app.py +++ b/openpype/tools/loader/app.py @@ -101,12 +101,12 @@ class LoaderWindow(QtWidgets.QDialog): thumb_ver_splitter.addWidget(thumbnail) thumb_ver_splitter.addWidget(version) - representations = None + repres_widget = None if sync_server_enabled: - representations = RepresentationWidget( + repres_widget = RepresentationWidget( io, self.tool_name, parent=thumb_ver_splitter ) - thumb_ver_splitter.addWidget(representations) + thumb_ver_splitter.addWidget(repres_widget) thumb_ver_splitter.setStretchFactor(0, 30) thumb_ver_splitter.setStretchFactor(1, 35) @@ -136,7 +136,6 @@ class LoaderWindow(QtWidgets.QDialog): "subsets": subsets, "version": version, "thumbnail": thumbnail, - "representations": representations }, "state": { "assetIds": None @@ -162,12 +161,14 @@ class LoaderWindow(QtWidgets.QDialog): subsets.load_started.connect(self._on_load_start) subsets.load_ended.connect(self._on_load_end) - representations.load_started.connect(self._on_load_start) - representations.load_ended.connect(self._on_load_end) + repres_widget.load_started.connect(self._on_load_start) + repres_widget.load_ended.connect(self._on_load_end) # TODO add overlay using stack widget self._message_label = message_label + self._repres_widget = repres_widget + self._overlay_frame = overlay_frame self._message_timer = message_timer @@ -321,9 +322,9 @@ class LoaderWindow(QtWidgets.QDialog): self.data["state"]["assetIds"] = asset_ids - representations = self.data["widgets"]["representations"] # reset repre list - representations.set_version_ids([]) + if self._repres_widget is not None: + self._repres_widget.set_version_ids([]) def _subsetschanged(self): asset_ids = self.data["state"]["assetIds"] @@ -414,12 +415,14 @@ class LoaderWindow(QtWidgets.QDialog): self.data["widgets"]["thumbnail"].set_thumbnail(thumbnail_docs) - representations = self.data["widgets"]["representations"] - version_ids = [doc["_id"] for doc in version_docs or []] - representations.set_version_ids(version_ids) + if self._repres_widget is not None: + version_ids = [doc["_id"] for doc in version_docs or []] + self._repres_widget.set_version_ids(version_ids) - # representations.change_visibility("subset", len(rows) > 1) - # representations.change_visibility("asset", len(asset_docs) > 1) + # self._repres_widget.change_visibility("subset", len(rows) > 1) + # self._repres_widget.change_visibility( + # "asset", len(asset_docs) > 1 + # ) def _set_context(self, context, refresh=True): """Set the selection in the interface using a context. From 6d49ebf3a85ef169bedf764e59b5915f10436ac4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 15 Oct 2021 19:16:16 +0200 Subject: [PATCH 148/279] change split to main_splitter --- openpype/tools/loader/app.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/openpype/tools/loader/app.py b/openpype/tools/loader/app.py index 1b316c9223..ea28304134 100644 --- a/openpype/tools/loader/app.py +++ b/openpype/tools/loader/app.py @@ -55,9 +55,9 @@ class LoaderWindow(QtWidgets.QDialog): self.setWindowFlags(QtCore.Qt.Window) self.setFocusPolicy(QtCore.Qt.StrongFocus) - split = QtWidgets.QSplitter(self) + main_splitter = QtWidgets.QSplitter(self) - asset_filter_splitter = QtWidgets.QSplitter(split) + asset_filter_splitter = QtWidgets.QSplitter(main_splitter) asset_filter_splitter.setOrientation(QtCore.Qt.Vertical) # --- Left part --- @@ -83,11 +83,11 @@ class LoaderWindow(QtWidgets.QDialog): self.groups_config, self.family_config_cache, tool_name=self.tool_name, - parent=split + parent=main_splitter ) # --- Right part --- - thumb_ver_splitter = QtWidgets.QSplitter(split) + thumb_ver_splitter = QtWidgets.QSplitter(main_splitter) thumb_ver_splitter.setOrientation(QtCore.Qt.Vertical) version = VersionWidget(io, parent=thumb_ver_splitter) thumbnail = ThumbnailWidget(io, parent=thumb_ver_splitter) @@ -111,9 +111,9 @@ class LoaderWindow(QtWidgets.QDialog): thumb_ver_splitter.setStretchFactor(0, 30) thumb_ver_splitter.setStretchFactor(1, 35) - split.addWidget(asset_filter_splitter) - split.addWidget(subsets) - split.addWidget(thumb_ver_splitter) + main_splitter.addWidget(asset_filter_splitter) + main_splitter.addWidget(subsets) + main_splitter.addWidget(thumb_ver_splitter) # TODO keep footer size by message size footer = QtWidgets.QWidget(self) @@ -126,7 +126,7 @@ class LoaderWindow(QtWidgets.QDialog): footer_layout.setContentsMargins(0, 0, 0, 0) layout = QtWidgets.QVBoxLayout(self) - layout.addWidget(split) + layout.addWidget(main_splitter) layout.addWidget(footer) self.data = { @@ -180,10 +180,10 @@ class LoaderWindow(QtWidgets.QDialog): # Defaults if sync_server_enabled: - split.setSizes([250, 1000, 550]) + main_splitter.setSizes([250, 1000, 550]) self.resize(1800, 900) else: - split.setSizes([250, 850, 200]) + main_splitter.setSizes([250, 850, 200]) self.resize(1300, 700) def resizeEvent(self, event): From 98a6225e5ffbba710d829b95ee2dd3cb2ca0c1e5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 15 Oct 2021 19:17:04 +0200 Subject: [PATCH 149/279] renamed 'footer' to 'footer_widget' --- openpype/tools/loader/app.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/tools/loader/app.py b/openpype/tools/loader/app.py index ea28304134..9569c87e35 100644 --- a/openpype/tools/loader/app.py +++ b/openpype/tools/loader/app.py @@ -116,18 +116,18 @@ class LoaderWindow(QtWidgets.QDialog): main_splitter.addWidget(thumb_ver_splitter) # TODO keep footer size by message size - footer = QtWidgets.QWidget(self) - footer.setFixedHeight(20) + footer_widget = QtWidgets.QWidget(self) + footer_widget.setFixedHeight(20) - message_label = QtWidgets.QLabel(footer) + message_label = QtWidgets.QLabel(footer_widget) - footer_layout = QtWidgets.QVBoxLayout(footer) - footer_layout.addWidget(message_label) + footer_layout = QtWidgets.QVBoxLayout(footer_widget) footer_layout.setContentsMargins(0, 0, 0, 0) + footer_layout.addWidget(message_label) layout = QtWidgets.QVBoxLayout(self) layout.addWidget(main_splitter) - layout.addWidget(footer) + layout.addWidget(footer_widget) self.data = { "widgets": { From 9f02356237614f15e96d7bebd715d31863137f27 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Sat, 16 Oct 2021 03:38:58 +0000 Subject: [PATCH 150/279] [Automated] Bump version --- CHANGELOG.md | 18 +++++++++--------- openpype/version.py | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f92fdc9f5..fe06b590f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [3.5.0-nightly.6](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.5.0-nightly.7](https://github.com/pypeclub/OpenPype/tree/HEAD) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.4.1...HEAD) @@ -10,12 +10,16 @@ **πŸ†• New features** +- Added project and task into context change message in Maya [\#2131](https://github.com/pypeclub/OpenPype/pull/2131) +- 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) - Maya: Validate setdress top group [\#2068](https://github.com/pypeclub/OpenPype/pull/2068) **πŸš€ Enhancements** +- 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) @@ -31,6 +35,7 @@ **πŸ› Bug fixes** +- 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) @@ -66,12 +71,11 @@ - General: Startup validations [\#2054](https://github.com/pypeclub/OpenPype/pull/2054) - Nuke: proxy mode validator [\#2052](https://github.com/pypeclub/OpenPype/pull/2052) -- Settings UI: Deffered set value on entity [\#2044](https://github.com/pypeclub/OpenPype/pull/2044) +- Ftrack: Removed ftrack interface [\#2049](https://github.com/pypeclub/OpenPype/pull/2049) - Loader: Families filtering [\#2043](https://github.com/pypeclub/OpenPype/pull/2043) - Settings UI: Project view enhancements [\#2042](https://github.com/pypeclub/OpenPype/pull/2042) - Settings for Nuke IncrementScriptVersion [\#2039](https://github.com/pypeclub/OpenPype/pull/2039) - Loader & Library loader: Use tools from OpenPype [\#2038](https://github.com/pypeclub/OpenPype/pull/2038) -- Adding predefined project folders creation in PM [\#2030](https://github.com/pypeclub/OpenPype/pull/2030) - WebserverModule: Removed interface of webserver module [\#2028](https://github.com/pypeclub/OpenPype/pull/2028) - TimersManager: Removed interface of timers manager [\#2024](https://github.com/pypeclub/OpenPype/pull/2024) - Feature Maya import asset from scene inventory [\#2018](https://github.com/pypeclub/OpenPype/pull/2018) @@ -102,9 +106,10 @@ **πŸš€ Enhancements** -- Ftrack: Removed ftrack interface [\#2049](https://github.com/pypeclub/OpenPype/pull/2049) +- Settings UI: Deffered set value on entity [\#2044](https://github.com/pypeclub/OpenPype/pull/2044) - Added possibility to configure of synchronization of workfile version… [\#2041](https://github.com/pypeclub/OpenPype/pull/2041) - General: Task types in profiles [\#2036](https://github.com/pypeclub/OpenPype/pull/2036) +- Adding predefined project folders creation in PM [\#2030](https://github.com/pypeclub/OpenPype/pull/2030) - Console interpreter: Handle invalid sizes on initialization [\#2022](https://github.com/pypeclub/OpenPype/pull/2022) - Ftrack: Show OpenPype versions in event server status [\#2019](https://github.com/pypeclub/OpenPype/pull/2019) - General: Staging icon [\#2017](https://github.com/pypeclub/OpenPype/pull/2017) @@ -112,9 +117,6 @@ - Modules: Connect method is not required [\#2009](https://github.com/pypeclub/OpenPype/pull/2009) - Settings UI: Number with configurable steps [\#2001](https://github.com/pypeclub/OpenPype/pull/2001) - Moving project folder structure creation out of ftrack module \#1989 [\#1996](https://github.com/pypeclub/OpenPype/pull/1996) -- Configurable items for providers without Settings [\#1987](https://github.com/pypeclub/OpenPype/pull/1987) -- Global: Example addons [\#1986](https://github.com/pypeclub/OpenPype/pull/1986) -- Standalone Publisher: Extract harmony zip handle workfile template [\#1982](https://github.com/pypeclub/OpenPype/pull/1982) **πŸ› Bug fixes** @@ -127,8 +129,6 @@ - Bugfix/webpublisher task type [\#2006](https://github.com/pypeclub/OpenPype/pull/2006) - Nuke thumbnails generated from middle of the sequence [\#1992](https://github.com/pypeclub/OpenPype/pull/1992) - Nuke: last version from path gets correct version [\#1990](https://github.com/pypeclub/OpenPype/pull/1990) -- nuke, resolve, hiero: precollector order lest then 0.5 [\#1984](https://github.com/pypeclub/OpenPype/pull/1984) -- Last workfile with multiple work templates [\#1981](https://github.com/pypeclub/OpenPype/pull/1981) ## [3.3.1](https://github.com/pypeclub/OpenPype/tree/3.3.1) (2021-08-20) diff --git a/openpype/version.py b/openpype/version.py index 3a589bac75..3bdad62e73 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.5.0-nightly.6" +__version__ = "3.5.0-nightly.7" From 81b271dd04020a0373c69d2046c8a65553984880 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Sun, 17 Oct 2021 19:03:37 +0000 Subject: [PATCH 151/279] [Automated] Bump version --- CHANGELOG.md | 13 +++++++------ openpype/version.py | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe06b590f5..c011c000fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [3.5.0-nightly.7](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.5.0-nightly.8](https://github.com/pypeclub/OpenPype/tree/HEAD) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.4.1...HEAD) @@ -18,6 +18,7 @@ **πŸš€ 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) @@ -50,10 +51,11 @@ - 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) - 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:** +- Maya: fix model publishing [\#2130](https://github.com/pypeclub/OpenPype/pull/2130) +- Add ExtractBurnin to photoshop review [\#2124](https://github.com/pypeclub/OpenPype/pull/2124) - Blender: Fix NoneType error when animation\_data is missing for a rig [\#2101](https://github.com/pypeclub/OpenPype/pull/2101) - Delivery Action Files Sequence fix [\#2096](https://github.com/pypeclub/OpenPype/pull/2096) - Bump pywin32 from 300 to 301 [\#2086](https://github.com/pypeclub/OpenPype/pull/2086) @@ -72,16 +74,19 @@ - General: Startup validations [\#2054](https://github.com/pypeclub/OpenPype/pull/2054) - Nuke: proxy mode validator [\#2052](https://github.com/pypeclub/OpenPype/pull/2052) - Ftrack: Removed ftrack interface [\#2049](https://github.com/pypeclub/OpenPype/pull/2049) +- Settings UI: Deffered set value on entity [\#2044](https://github.com/pypeclub/OpenPype/pull/2044) - Loader: Families filtering [\#2043](https://github.com/pypeclub/OpenPype/pull/2043) - Settings UI: Project view enhancements [\#2042](https://github.com/pypeclub/OpenPype/pull/2042) - Settings for Nuke IncrementScriptVersion [\#2039](https://github.com/pypeclub/OpenPype/pull/2039) - Loader & Library loader: Use tools from OpenPype [\#2038](https://github.com/pypeclub/OpenPype/pull/2038) +- Adding predefined project folders creation in PM [\#2030](https://github.com/pypeclub/OpenPype/pull/2030) - WebserverModule: Removed interface of webserver module [\#2028](https://github.com/pypeclub/OpenPype/pull/2028) - TimersManager: Removed interface of timers manager [\#2024](https://github.com/pypeclub/OpenPype/pull/2024) - Feature Maya import asset from scene inventory [\#2018](https://github.com/pypeclub/OpenPype/pull/2018) **πŸ› Bug fixes** +- Fix Sync Queue when project disabled [\#2063](https://github.com/pypeclub/OpenPype/pull/2063) - Timers manger: Typo fix [\#2058](https://github.com/pypeclub/OpenPype/pull/2058) - Hiero: Editorial fixes [\#2057](https://github.com/pypeclub/OpenPype/pull/2057) - Differentiate jpg sequences from thumbnail [\#2056](https://github.com/pypeclub/OpenPype/pull/2056) @@ -106,10 +111,8 @@ **πŸš€ Enhancements** -- Settings UI: Deffered set value on entity [\#2044](https://github.com/pypeclub/OpenPype/pull/2044) - Added possibility to configure of synchronization of workfile version… [\#2041](https://github.com/pypeclub/OpenPype/pull/2041) - General: Task types in profiles [\#2036](https://github.com/pypeclub/OpenPype/pull/2036) -- Adding predefined project folders creation in PM [\#2030](https://github.com/pypeclub/OpenPype/pull/2030) - Console interpreter: Handle invalid sizes on initialization [\#2022](https://github.com/pypeclub/OpenPype/pull/2022) - Ftrack: Show OpenPype versions in event server status [\#2019](https://github.com/pypeclub/OpenPype/pull/2019) - General: Staging icon [\#2017](https://github.com/pypeclub/OpenPype/pull/2017) @@ -127,8 +130,6 @@ - FFmpeg: Subprocess arguments as list [\#2032](https://github.com/pypeclub/OpenPype/pull/2032) - General: Fix Python 2 breaking line [\#2016](https://github.com/pypeclub/OpenPype/pull/2016) - Bugfix/webpublisher task type [\#2006](https://github.com/pypeclub/OpenPype/pull/2006) -- Nuke thumbnails generated from middle of the sequence [\#1992](https://github.com/pypeclub/OpenPype/pull/1992) -- Nuke: last version from path gets correct version [\#1990](https://github.com/pypeclub/OpenPype/pull/1990) ## [3.3.1](https://github.com/pypeclub/OpenPype/tree/3.3.1) (2021-08-20) diff --git a/openpype/version.py b/openpype/version.py index 3bdad62e73..5c8555c6a7 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.5.0-nightly.7" +__version__ = "3.5.0-nightly.8" From 8be1113edcb3e38333ce14842d530a57d2fbaaf6 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Sun, 17 Oct 2021 19:33:06 +0000 Subject: [PATCH 152/279] [Automated] Release --- CHANGELOG.md | 24 ++++++++++++------------ openpype/version.py | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c011c000fd..95792f8a7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # Changelog -## [3.5.0-nightly.8](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.5.0](https://github.com/pypeclub/OpenPype/tree/3.5.0) (2021-10-17) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.4.1...HEAD) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.4.1...3.5.0) **Deprecated:** @@ -11,6 +11,7 @@ **πŸ†• 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) @@ -36,30 +37,30 @@ **πŸ› Bug fixes** +- Maya: fix model publishing [\#2130](https://github.com/pypeclub/OpenPype/pull/2130) - 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:** -- Maya: fix model publishing [\#2130](https://github.com/pypeclub/OpenPype/pull/2130) -- Add ExtractBurnin to photoshop review [\#2124](https://github.com/pypeclub/OpenPype/pull/2124) -- Blender: Fix NoneType error when animation\_data is missing for a rig [\#2101](https://github.com/pypeclub/OpenPype/pull/2101) -- Delivery Action Files Sequence fix [\#2096](https://github.com/pypeclub/OpenPype/pull/2096) - Bump pywin32 from 300 to 301 [\#2086](https://github.com/pypeclub/OpenPype/pull/2086) -- Nuke UI scaling [\#2077](https://github.com/pypeclub/OpenPype/pull/2077) ## [3.4.1](https://github.com/pypeclub/OpenPype/tree/3.4.1) (2021-09-23) @@ -86,7 +87,6 @@ **πŸ› Bug fixes** -- Fix Sync Queue when project disabled [\#2063](https://github.com/pypeclub/OpenPype/pull/2063) - Timers manger: Typo fix [\#2058](https://github.com/pypeclub/OpenPype/pull/2058) - Hiero: Editorial fixes [\#2057](https://github.com/pypeclub/OpenPype/pull/2057) - Differentiate jpg sequences from thumbnail [\#2056](https://github.com/pypeclub/OpenPype/pull/2056) @@ -101,10 +101,6 @@ [Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.4.0-nightly.6...3.4.0) -### πŸ“– Documentation - -- Documentation: Ftrack launch argsuments update [\#2014](https://github.com/pypeclub/OpenPype/pull/2014) - **πŸ†• New features** - Nuke: Compatibility with Nuke 13 [\#2003](https://github.com/pypeclub/OpenPype/pull/2003) @@ -131,6 +127,10 @@ - General: Fix Python 2 breaking line [\#2016](https://github.com/pypeclub/OpenPype/pull/2016) - Bugfix/webpublisher task type [\#2006](https://github.com/pypeclub/OpenPype/pull/2006) +### πŸ“– Documentation + +- Documentation: Ftrack launch argsuments update [\#2014](https://github.com/pypeclub/OpenPype/pull/2014) + ## [3.3.1](https://github.com/pypeclub/OpenPype/tree/3.3.1) (2021-08-20) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.3.1-nightly.1...3.3.1) diff --git a/openpype/version.py b/openpype/version.py index 5c8555c6a7..d88d79b995 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.5.0-nightly.8" +__version__ = "3.5.0" From e144e3d9fd8b1cad9ac6c18ae4faf3169c0a9d58 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 18 Oct 2021 10:03:47 +0200 Subject: [PATCH 153/279] renamed version info widget and do not store it into data --- openpype/tools/loader/app.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/openpype/tools/loader/app.py b/openpype/tools/loader/app.py index 9569c87e35..0ec5a50f31 100644 --- a/openpype/tools/loader/app.py +++ b/openpype/tools/loader/app.py @@ -89,7 +89,8 @@ class LoaderWindow(QtWidgets.QDialog): # --- Right part --- thumb_ver_splitter = QtWidgets.QSplitter(main_splitter) thumb_ver_splitter.setOrientation(QtCore.Qt.Vertical) - version = VersionWidget(io, parent=thumb_ver_splitter) + + version_info_widget = VersionWidget(io, parent=thumb_ver_splitter) thumbnail = ThumbnailWidget(io, parent=thumb_ver_splitter) manager = ModulesManager() @@ -99,7 +100,7 @@ class LoaderWindow(QtWidgets.QDialog): sync_server_enabled = sync_server.enabled thumb_ver_splitter.addWidget(thumbnail) - thumb_ver_splitter.addWidget(version) + thumb_ver_splitter.addWidget(version_info_widget) repres_widget = None if sync_server_enabled: @@ -134,8 +135,7 @@ class LoaderWindow(QtWidgets.QDialog): "families": families, "assets": assets, "subsets": subsets, - "version": version, - "thumbnail": thumbnail, + "thumbnail": thumbnail }, "state": { "assetIds": None @@ -167,6 +167,7 @@ class LoaderWindow(QtWidgets.QDialog): # TODO add overlay using stack widget self._message_label = message_label + self._version_info_widget = version_info_widget self._repres_widget = repres_widget self._overlay_frame = overlay_frame @@ -317,7 +318,7 @@ class LoaderWindow(QtWidgets.QDialog): ) # Clear the version information on asset change - self.data["widgets"]["version"].set_version(None) + self._version_info_widget.set_version(None) self.data["widgets"]["thumbnail"].set_thumbnail(asset_docs) self.data["state"]["assetIds"] = asset_ids @@ -404,7 +405,7 @@ class LoaderWindow(QtWidgets.QDialog): else: version_docs.append(item["version_document"]) - self.data["widgets"]["version"].set_version(version_doc) + self._version_info_widget.set_version(version_doc) thumbnail_docs = version_docs assets_widget = self.data["widgets"]["assets"] From c90cf1765c04719c306ef8894eb4f545a8cb7d32 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 18 Oct 2021 10:06:05 +0200 Subject: [PATCH 154/279] renamed thumbnail widget and do not store it into data --- openpype/tools/loader/app.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/tools/loader/app.py b/openpype/tools/loader/app.py index 0ec5a50f31..43e3750428 100644 --- a/openpype/tools/loader/app.py +++ b/openpype/tools/loader/app.py @@ -90,8 +90,8 @@ class LoaderWindow(QtWidgets.QDialog): thumb_ver_splitter = QtWidgets.QSplitter(main_splitter) thumb_ver_splitter.setOrientation(QtCore.Qt.Vertical) + thumbnail_widget = ThumbnailWidget(io, parent=thumb_ver_splitter) version_info_widget = VersionWidget(io, parent=thumb_ver_splitter) - thumbnail = ThumbnailWidget(io, parent=thumb_ver_splitter) manager = ModulesManager() sync_server = manager.modules_by_name.get("sync_server") @@ -99,7 +99,7 @@ class LoaderWindow(QtWidgets.QDialog): if sync_server is not None: sync_server_enabled = sync_server.enabled - thumb_ver_splitter.addWidget(thumbnail) + thumb_ver_splitter.addWidget(thumbnail_widget) thumb_ver_splitter.addWidget(version_info_widget) repres_widget = None @@ -134,8 +134,7 @@ class LoaderWindow(QtWidgets.QDialog): "widgets": { "families": families, "assets": assets, - "subsets": subsets, - "thumbnail": thumbnail + "subsets": subsets }, "state": { "assetIds": None @@ -168,6 +167,7 @@ class LoaderWindow(QtWidgets.QDialog): self._message_label = message_label self._version_info_widget = version_info_widget + self._thumbnail_widget = thumbnail_widget self._repres_widget = repres_widget self._overlay_frame = overlay_frame @@ -318,8 +318,8 @@ class LoaderWindow(QtWidgets.QDialog): ) # Clear the version information on asset change + self._thumbnail_widget.set_thumbnail(asset_docs) self._version_info_widget.set_version(None) - self.data["widgets"]["thumbnail"].set_thumbnail(asset_docs) self.data["state"]["assetIds"] = asset_ids @@ -414,7 +414,7 @@ class LoaderWindow(QtWidgets.QDialog): if len(asset_docs) > 0: thumbnail_docs = asset_docs - self.data["widgets"]["thumbnail"].set_thumbnail(thumbnail_docs) + self._thumbnail_widget.set_thumbnail(thumbnail_docs) if self._repres_widget is not None: version_ids = [doc["_id"] for doc in version_docs or []] From a85c93cc0fa599441e8eee0e3276029469be3a31 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 18 Oct 2021 10:12:18 +0200 Subject: [PATCH 155/279] rename families filter view and do not store it into data --- openpype/tools/loader/app.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/openpype/tools/loader/app.py b/openpype/tools/loader/app.py index 43e3750428..f10348eced 100644 --- a/openpype/tools/loader/app.py +++ b/openpype/tools/loader/app.py @@ -68,11 +68,11 @@ class LoaderWindow(QtWidgets.QDialog): assets.set_current_asset_btn_visibility(True) # Families widget - families = FamilyListView( + families_filter_view = FamilyListView( io, self.family_config_cache, asset_filter_splitter ) asset_filter_splitter.addWidget(assets) - asset_filter_splitter.addWidget(families) + asset_filter_splitter.addWidget(families_filter_view) asset_filter_splitter.setStretchFactor(0, 65) asset_filter_splitter.setStretchFactor(1, 35) @@ -132,7 +132,6 @@ class LoaderWindow(QtWidgets.QDialog): self.data = { "widgets": { - "families": families, "assets": assets, "subsets": subsets }, @@ -150,7 +149,7 @@ class LoaderWindow(QtWidgets.QDialog): message_timer.timeout.connect(self._on_message_timeout) - families.active_changed.connect(subsets.set_family_filters) + families_filter_view.active_changed.connect(subsets.set_family_filters) assets.selection_changed.connect(self.on_assetschanged) assets.refresh_triggered.connect(self.on_assetschanged) assets.view.clicked.connect(self.on_assetview_click) @@ -163,15 +162,17 @@ class LoaderWindow(QtWidgets.QDialog): repres_widget.load_started.connect(self._on_load_start) repres_widget.load_ended.connect(self._on_load_end) - # TODO add overlay using stack widget self._message_label = message_label + self._message_timer = message_timer + + self._families_filter_view = families_filter_view self._version_info_widget = version_info_widget self._thumbnail_widget = thumbnail_widget self._repres_widget = repres_widget + # TODO add overlay using stack widget self._overlay_frame = overlay_frame - self._message_timer = message_timer self.family_config_cache.refresh() self.groups_config.refresh() @@ -236,11 +237,10 @@ class LoaderWindow(QtWidgets.QDialog): def _on_subset_refresh(self, has_item): subsets_widget = self.data["widgets"]["subsets"] - families_view = self.data["widgets"]["families"] subsets_widget.set_loading_state(loading=False, empty=not has_item) families = subsets_widget.get_subsets_families() - families_view.set_enabled_families(families) + self._families_filter_view.set_enabled_families(families) def _on_load_end(self): # Delay hiding as click events happened during loading should be @@ -251,9 +251,8 @@ class LoaderWindow(QtWidgets.QDialog): def on_context_task_change(self, *args, **kwargs): assets_widget = self.data["widgets"]["assets"] - families_view = self.data["widgets"]["families"] # Refresh families config - families_view.refresh() + self._families_filter_view.refresh() # Change to context asset on context change assets_widget.select_assets(io.Session["AVALON_ASSET"]) @@ -268,8 +267,7 @@ class LoaderWindow(QtWidgets.QDialog): assets_widget.refresh() assets_widget.setFocus() - families_view = self.data["widgets"]["families"] - families_view.refresh() + self._families_filter_view.refresh() def clear_assets_underlines(self): """Clear colors from asset data to remove colored underlines From 6947c68bfe63014ab17a3e622e170ef27a949d81 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 18 Oct 2021 10:13:27 +0200 Subject: [PATCH 156/279] renamed 'asset_filter_splitter' to 'left_side_splitter' --- openpype/tools/loader/app.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/openpype/tools/loader/app.py b/openpype/tools/loader/app.py index f10348eced..f81f54cc23 100644 --- a/openpype/tools/loader/app.py +++ b/openpype/tools/loader/app.py @@ -57,24 +57,24 @@ class LoaderWindow(QtWidgets.QDialog): main_splitter = QtWidgets.QSplitter(self) - asset_filter_splitter = QtWidgets.QSplitter(main_splitter) - asset_filter_splitter.setOrientation(QtCore.Qt.Vertical) - # --- Left part --- + left_side_splitter = QtWidgets.QSplitter(main_splitter) + left_side_splitter.setOrientation(QtCore.Qt.Vertical) + # Assets widget assets = AssetWidget( - io, multiselection=True, parent=asset_filter_splitter + io, multiselection=True, parent=left_side_splitter ) assets.set_current_asset_btn_visibility(True) # Families widget families_filter_view = FamilyListView( - io, self.family_config_cache, asset_filter_splitter + io, self.family_config_cache, left_side_splitter ) - asset_filter_splitter.addWidget(assets) - asset_filter_splitter.addWidget(families_filter_view) - asset_filter_splitter.setStretchFactor(0, 65) - asset_filter_splitter.setStretchFactor(1, 35) + left_side_splitter.addWidget(assets) + left_side_splitter.addWidget(families_filter_view) + left_side_splitter.setStretchFactor(0, 65) + left_side_splitter.setStretchFactor(1, 35) # --- Middle part --- # Subsets widget @@ -112,7 +112,7 @@ class LoaderWindow(QtWidgets.QDialog): thumb_ver_splitter.setStretchFactor(0, 30) thumb_ver_splitter.setStretchFactor(1, 35) - main_splitter.addWidget(asset_filter_splitter) + main_splitter.addWidget(left_side_splitter) main_splitter.addWidget(subsets) main_splitter.addWidget(thumb_ver_splitter) From 658c4a1b6f48fdb0cc96373032815bcd559432e5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 18 Oct 2021 10:21:44 +0200 Subject: [PATCH 157/279] rename asset widget and do not store it to data --- openpype/tools/loader/app.py | 45 +++++++++++++++++------------------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/openpype/tools/loader/app.py b/openpype/tools/loader/app.py index f81f54cc23..3f3f03867f 100644 --- a/openpype/tools/loader/app.py +++ b/openpype/tools/loader/app.py @@ -62,16 +62,16 @@ class LoaderWindow(QtWidgets.QDialog): left_side_splitter.setOrientation(QtCore.Qt.Vertical) # Assets widget - assets = AssetWidget( + assets_widget = AssetWidget( io, multiselection=True, parent=left_side_splitter ) - assets.set_current_asset_btn_visibility(True) + assets_widget.set_current_asset_btn_visibility(True) # Families widget families_filter_view = FamilyListView( io, self.family_config_cache, left_side_splitter ) - left_side_splitter.addWidget(assets) + left_side_splitter.addWidget(assets_widget) left_side_splitter.addWidget(families_filter_view) left_side_splitter.setStretchFactor(0, 65) left_side_splitter.setStretchFactor(1, 35) @@ -132,7 +132,6 @@ class LoaderWindow(QtWidgets.QDialog): self.data = { "widgets": { - "assets": assets, "subsets": subsets }, "state": { @@ -150,9 +149,9 @@ class LoaderWindow(QtWidgets.QDialog): message_timer.timeout.connect(self._on_message_timeout) families_filter_view.active_changed.connect(subsets.set_family_filters) - assets.selection_changed.connect(self.on_assetschanged) - assets.refresh_triggered.connect(self.on_assetschanged) - assets.view.clicked.connect(self.on_assetview_click) + assets_widget.selection_changed.connect(self.on_assetschanged) + assets_widget.refresh_triggered.connect(self.on_assetschanged) + assets_widget.view.clicked.connect(self.on_assetview_click) subsets.active_changed.connect(self.on_subsetschanged) subsets.version_changed.connect(self.on_versionschanged) subsets.refreshed.connect(self._on_subset_refresh) @@ -162,15 +161,16 @@ class LoaderWindow(QtWidgets.QDialog): repres_widget.load_started.connect(self._on_load_start) repres_widget.load_ended.connect(self._on_load_end) - self._message_label = message_label - self._message_timer = message_timer - + self._assets_widget = assets_widget self._families_filter_view = families_filter_view self._version_info_widget = version_info_widget self._thumbnail_widget = thumbnail_widget self._repres_widget = repres_widget + self._message_label = message_label + self._message_timer = message_timer + # TODO add overlay using stack widget self._overlay_frame = overlay_frame @@ -250,11 +250,10 @@ class LoaderWindow(QtWidgets.QDialog): # ------------------------------ def on_context_task_change(self, *args, **kwargs): - assets_widget = self.data["widgets"]["assets"] # Refresh families config self._families_filter_view.refresh() # Change to context asset on context change - assets_widget.select_assets(io.Session["AVALON_ASSET"]) + self._assets_widget.select_assets(io.Session["AVALON_ASSET"]) def _refresh(self): """Load assets from database""" @@ -263,9 +262,8 @@ class LoaderWindow(QtWidgets.QDialog): project = io.find_one({"type": "project"}, {"type": 1}) assert project, "Project was not found! This is a bug" - assets_widget = self.data["widgets"]["assets"] - assets_widget.refresh() - assets_widget.setFocus() + self._assets_widget.refresh() + self._assets_widget.setFocus() self._families_filter_view.refresh() @@ -275,11 +273,12 @@ class LoaderWindow(QtWidgets.QDialog): own selected subsets. These colors must be cleared from asset data on selection change so they match current selection. """ - last_asset_ids = self.data["state"]["assetIds"] + # TODO do not touch inner attributes of asset widget + last_asset_ids = self.data["state"]["assetIds"] or [] if not last_asset_ids: return - assets_widget = self.data["widgets"]["assets"] + assets_widget = self._assets_widget id_role = assets_widget.model.ObjectIdRole for index in lib.iter_model_rows(assets_widget.model, 0): @@ -292,7 +291,6 @@ class LoaderWindow(QtWidgets.QDialog): def _assetschanged(self): """Selected assets have changed""" - assets_widget = self.data["widgets"]["assets"] subsets_widget = self.data["widgets"]["subsets"] subsets_model = subsets_widget.model @@ -300,7 +298,7 @@ class LoaderWindow(QtWidgets.QDialog): self.clear_assets_underlines() # filter None docs they are silo - asset_docs = assets_widget.get_selected_assets() + asset_docs = self._assets_widget.get_selected_assets() asset_ids = [asset_doc["_id"] for asset_doc in asset_docs] # Start loading @@ -354,7 +352,8 @@ class LoaderWindow(QtWidgets.QDialog): self.clear_assets_underlines() - assets_widget = self.data["widgets"]["assets"] + # TODO do not use inner attributes of asset widget + assets_widget = self._assets_widget indexes = assets_widget.view.selectionModel().selectedRows() for index in indexes: @@ -406,8 +405,7 @@ class LoaderWindow(QtWidgets.QDialog): self._version_info_widget.set_version(version_doc) thumbnail_docs = version_docs - assets_widget = self.data["widgets"]["assets"] - asset_docs = assets_widget.get_selected_assets() + asset_docs = self._assets_widget.get_selected_assets() if not thumbnail_docs: if len(asset_docs) > 0: thumbnail_docs = asset_docs @@ -454,8 +452,7 @@ class LoaderWindow(QtWidgets.QDialog): # scheduled refresh and the silo tabs are not shown. self._refresh() - asset_widget = self.data["widgets"]["assets"] - asset_widget.select_assets(asset) + self._assets_widget.select_assets(asset) def _on_message_timeout(self): self._message_label.setText("") From 584fd49e7b02f5bc74c7173c9b1cce74a0f0765a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 18 Oct 2021 10:34:13 +0200 Subject: [PATCH 158/279] renamed subset widget and do not store it to data --- openpype/tools/loader/app.py | 46 ++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/openpype/tools/loader/app.py b/openpype/tools/loader/app.py index 3f3f03867f..a4b4b5eb28 100644 --- a/openpype/tools/loader/app.py +++ b/openpype/tools/loader/app.py @@ -78,7 +78,7 @@ class LoaderWindow(QtWidgets.QDialog): # --- Middle part --- # Subsets widget - subsets = SubsetWidget( + subsets_widget = SubsetWidget( io, self.groups_config, self.family_config_cache, @@ -113,7 +113,7 @@ class LoaderWindow(QtWidgets.QDialog): thumb_ver_splitter.setStretchFactor(1, 35) main_splitter.addWidget(left_side_splitter) - main_splitter.addWidget(subsets) + main_splitter.addWidget(subsets_widget) main_splitter.addWidget(thumb_ver_splitter) # TODO keep footer size by message size @@ -148,22 +148,27 @@ class LoaderWindow(QtWidgets.QDialog): message_timer.timeout.connect(self._on_message_timeout) - families_filter_view.active_changed.connect(subsets.set_family_filters) + families_filter_view.active_changed.connect( + self._on_family_filter_change + ) assets_widget.selection_changed.connect(self.on_assetschanged) assets_widget.refresh_triggered.connect(self.on_assetschanged) + # TODO do not touch view in asset widget assets_widget.view.clicked.connect(self.on_assetview_click) - subsets.active_changed.connect(self.on_subsetschanged) - subsets.version_changed.connect(self.on_versionschanged) - subsets.refreshed.connect(self._on_subset_refresh) + subsets_widget.active_changed.connect(self.on_subsetschanged) + subsets_widget.version_changed.connect(self.on_versionschanged) + subsets_widget.refreshed.connect(self._on_subset_refresh) - subsets.load_started.connect(self._on_load_start) - subsets.load_ended.connect(self._on_load_end) + subsets_widget.load_started.connect(self._on_load_start) + subsets_widget.load_ended.connect(self._on_load_end) repres_widget.load_started.connect(self._on_load_start) repres_widget.load_ended.connect(self._on_load_end) self._assets_widget = assets_widget self._families_filter_view = families_filter_view + self._subsets_widget = subsets_widget + self._version_info_widget = version_info_widget self._thumbnail_widget = thumbnail_widget self._repres_widget = repres_widget @@ -236,10 +241,10 @@ class LoaderWindow(QtWidgets.QDialog): self._overlay_frame.setVisible(False) def _on_subset_refresh(self, has_item): - subsets_widget = self.data["widgets"]["subsets"] - - subsets_widget.set_loading_state(loading=False, empty=not has_item) - families = subsets_widget.get_subsets_families() + self._subsets_widget.set_loading_state( + loading=False, empty=not has_item + ) + families = self._subsets_widget.get_subsets_families() self._families_filter_view.set_enabled_families(families) def _on_load_end(self): @@ -248,6 +253,8 @@ class LoaderWindow(QtWidgets.QDialog): QtCore.QTimer.singleShot(100, self._hide_overlay) # ------------------------------ + def _on_family_filter_change(self, families): + self._subsets_widget.set_family_filters(families) def on_context_task_change(self, *args, **kwargs): # Refresh families config @@ -291,7 +298,8 @@ class LoaderWindow(QtWidgets.QDialog): def _assetschanged(self): """Selected assets have changed""" - subsets_widget = self.data["widgets"]["subsets"] + subsets_widget = self._subsets_widget + # TODO do not touch subset widget inner attributes subsets_model = subsets_widget.model subsets_model.clear() @@ -330,8 +338,9 @@ class LoaderWindow(QtWidgets.QDialog): self._versionschanged() return - subsets = self.data["widgets"]["subsets"] - selected_subsets = subsets.selected_subsets(_merged=True, _other=False) + selected_subsets = self._subsets_widget.selected_subsets( + _merged=True, _other=False + ) asset_models = {} asset_ids = [] @@ -370,7 +379,7 @@ class LoaderWindow(QtWidgets.QDialog): self._versionschanged() def _versionschanged(self): - subsets = self.data["widgets"]["subsets"] + subsets = self._subsets_widget selection = subsets.view.selectionModel() # Active must be in the selected rows otherwise we @@ -488,7 +497,7 @@ class LoaderWindow(QtWidgets.QDialog): event.setAccepted(True) # Avoid interfering other widgets def show_grouping_dialog(self): - subsets = self.data["widgets"]["subsets"] + subsets = self._subsets_widget if not subsets.is_groupable(): self.echo("Grouping not enabled.") return @@ -527,7 +536,8 @@ class SubsetGroupingDialog(QtWidgets.QDialog): self.items = items self.groups_config = groups_config - self.subsets = parent.data["widgets"]["subsets"] + # TODO do not touch inner attributes + self.subsets = parent._subsets_widget self.asset_ids = parent.data["state"]["assetIds"] name = QtWidgets.QLineEdit() From 3fd25b14a770107d3282bf6116a0d72f37fbd163 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 18 Oct 2021 11:26:25 +0200 Subject: [PATCH 159/279] OP-1206 - added reference loader wip --- .../photoshop/plugins/load/load_image.py | 5 ++++- .../photoshop/plugins/load/load_reference.py | 22 +++++++++++++++++++ .../collect_default_deadline_server.py | 2 +- .../defaults/project_anatomy/imageio.json | 4 +--- 4 files changed, 28 insertions(+), 5 deletions(-) create mode 100644 openpype/hosts/photoshop/plugins/load/load_reference.py diff --git a/openpype/hosts/photoshop/plugins/load/load_image.py b/openpype/hosts/photoshop/plugins/load/load_image.py index d043323768..d97894b269 100644 --- a/openpype/hosts/photoshop/plugins/load/load_image.py +++ b/openpype/hosts/photoshop/plugins/load/load_image.py @@ -21,7 +21,7 @@ class ImageLoader(api.Loader): context["asset"]["name"], name) with photoshop.maintained_selection(): - layer = stub.import_smart_object(self.fname, layer_name) + layer = self.import_layer(self.fname, layer_name) self[:] = [layer] namespace = namespace or layer_name @@ -72,3 +72,6 @@ class ImageLoader(api.Loader): def switch(self, container, representation): self.update(container, representation) + + def import_layer(self, file_name, layer_name): + return stub.import_smart_object(file_name, layer_name) diff --git a/openpype/hosts/photoshop/plugins/load/load_reference.py b/openpype/hosts/photoshop/plugins/load/load_reference.py new file mode 100644 index 0000000000..306647c032 --- /dev/null +++ b/openpype/hosts/photoshop/plugins/load/load_reference.py @@ -0,0 +1,22 @@ +import re + +from avalon import api, photoshop + +from openpype.hosts.photoshop.plugins.lib import get_unique_layer_name +from openpype.hosts.photoshop.plugins.load.load_image import ImageLoader + +stub = photoshop.stub() + + +class ReferenceLoader(ImageLoader): + """Load reference images + + Stores the imported asset in a container named after the asset. + """ + + families = ["image", "render"] + representations = ["*"] + + def import_layer(self, file_name, layer_name): + return stub.import_smart_object(file_name, layer_name, + as_reference=True) diff --git a/openpype/modules/default_modules/deadline/plugins/publish/collect_default_deadline_server.py b/openpype/modules/default_modules/deadline/plugins/publish/collect_default_deadline_server.py index afb8583069..53231bd7e4 100644 --- a/openpype/modules/default_modules/deadline/plugins/publish/collect_default_deadline_server.py +++ b/openpype/modules/default_modules/deadline/plugins/publish/collect_default_deadline_server.py @@ -6,7 +6,7 @@ import pyblish.api class CollectDefaultDeadlineServer(pyblish.api.ContextPlugin): """Collect default Deadline Webservice URL.""" - order = pyblish.api.CollectorOrder + order = pyblish.api.CollectorOrder + 0.01 label = "Default Deadline Webservice" def process(self, context): diff --git a/openpype/settings/defaults/project_anatomy/imageio.json b/openpype/settings/defaults/project_anatomy/imageio.json index 38313a3d84..25608f67c6 100644 --- a/openpype/settings/defaults/project_anatomy/imageio.json +++ b/openpype/settings/defaults/project_anatomy/imageio.json @@ -162,9 +162,7 @@ ] } ], - "customNodes": [ - - ] + "customNodes": [] }, "regexInputs": { "inputs": [ From c2e22ba7d669421aee76e690c52a85f3e16b40cd Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 18 Oct 2021 13:15:38 +0200 Subject: [PATCH 160/279] Use dnxhd profile from input metadata --- openpype/scripts/otio_burnin.py | 34 ++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/openpype/scripts/otio_burnin.py b/openpype/scripts/otio_burnin.py index 184d7689e3..206abfc0b4 100644 --- a/openpype/scripts/otio_burnin.py +++ b/openpype/scripts/otio_burnin.py @@ -109,9 +109,7 @@ def _prores_codec_args(ffprobe_data, source_ffmpeg_cmd): def _h264_codec_args(ffprobe_data, source_ffmpeg_cmd): - output = [] - - output.extend(["-codec:v", "h264"]) + output = ["-codec:v", "h264"] # Use arguments from source if are available source arguments if source_ffmpeg_cmd: @@ -137,6 +135,32 @@ def _h264_codec_args(ffprobe_data, source_ffmpeg_cmd): return output +def _dnxhd_codec_args(ffprobe_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 "" + # Lower profile and replace space with underscore + cleaned_profile = profile.lower().replace(" ", "_") + dnx_profiles = { + "dnxhd", + "dnxhr_lb", + "dnxhr_sq", + "dnxhr_hq", + "dnxhr_hqx", + "dnxhr_444" + } + if cleaned_profile in dnx_profiles: + output.extend(["-profile:v", cleaned_profile]) + + pix_fmt = ffprobe_data.get("pix_fmt") + if pix_fmt: + output.extend(["-pix_fmt", pix_fmt]) + + output.extend(["-g", "1"]) + return output + + def get_codec_args(ffprobe_data, source_ffmpeg_cmd): codec_name = ffprobe_data.get("codec_name") # Codec "prores" @@ -147,6 +171,10 @@ def get_codec_args(ffprobe_data, source_ffmpeg_cmd): if codec_name == "h264": return _h264_codec_args(ffprobe_data, source_ffmpeg_cmd) + # Coded DNxHD + if codec_name == "dnxhd": + return _dnxhd_codec_args(ffprobe_data, source_ffmpeg_cmd) + output = [] if codec_name: output.extend(["-codec:v", codec_name]) From 86cd93c0acae803db01a48737924cfa292d038a5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 18 Oct 2021 16:10:16 +0200 Subject: [PATCH 161/279] fix lookassigner imports for python3 compatibility --- openpype/tools/mayalookassigner/app.py | 18 ++++++++++++------ openpype/tools/mayalookassigner/commands.py | 4 ++-- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/openpype/tools/mayalookassigner/app.py b/openpype/tools/mayalookassigner/app.py index 1fa3a3868a..83c779fde1 100644 --- a/openpype/tools/mayalookassigner/app.py +++ b/openpype/tools/mayalookassigner/app.py @@ -13,8 +13,14 @@ from maya import cmds import maya.OpenMaya import maya.api.OpenMaya as om -from . import widgets -from . import commands +from .widgets import ( + AssetOutliner, + LookOutliner +) +from .commands import ( + get_workfile, + remove_unused_looks +) from . vray_proxies import vrayproxy_assign_look @@ -32,7 +38,7 @@ class App(QtWidgets.QWidget): # Store callback references self._callbacks = [] - filename = commands.get_workfile() + filename = get_workfile() self.setObjectName("lookManager") self.setWindowTitle("Look Manager 1.3.0 - [{}]".format(filename)) @@ -57,13 +63,13 @@ class App(QtWidgets.QWidget): """Build the UI""" # Assets (left) - asset_outliner = widgets.AssetOutliner() + asset_outliner = AssetOutliner() # Looks (right) looks_widget = QtWidgets.QWidget() looks_layout = QtWidgets.QVBoxLayout(looks_widget) - look_outliner = widgets.LookOutliner() # Database look overview + look_outliner = LookOutliner() # Database look overview assign_selected = QtWidgets.QCheckBox("Assign to selected only") assign_selected.setToolTip("Whether to assign only to selected nodes " @@ -124,7 +130,7 @@ class App(QtWidgets.QWidget): lambda: self.echo("Loaded assets..")) self.look_outliner.menu_apply_action.connect(self.on_process_selected) - self.remove_unused.clicked.connect(commands.remove_unused_looks) + self.remove_unused.clicked.connect(remove_unused_looks) # Maya renderlayer switch callback callback = om.MEventMessage.addEventCallback( diff --git a/openpype/tools/mayalookassigner/commands.py b/openpype/tools/mayalookassigner/commands.py index a53251cdef..f7d26f9adb 100644 --- a/openpype/tools/mayalookassigner/commands.py +++ b/openpype/tools/mayalookassigner/commands.py @@ -9,7 +9,7 @@ from openpype.hosts.maya.api import lib from avalon import io, api -import vray_proxies +from .vray_proxies import get_alembic_ids_cache log = logging.getLogger(__name__) @@ -146,7 +146,7 @@ def create_items_from_nodes(nodes): vray_proxy_nodes = cmds.ls(nodes, type="VRayProxy") for vp in vray_proxy_nodes: path = cmds.getAttr("{}.fileName".format(vp)) - ids = vray_proxies.get_alembic_ids_cache(path) + ids = get_alembic_ids_cache(path) parent_id = {} for k, _ in ids.items(): pid = k.split(":")[0] From 0dea134364b43c30267d8c51e78582c4e099bc11 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 18 Oct 2021 16:51:05 +0200 Subject: [PATCH 162/279] fix more imports --- openpype/tools/mayalookassigner/app.py | 5 +++-- openpype/tools/mayalookassigner/models.py | 5 +++-- openpype/tools/mayalookassigner/views.py | 2 +- openpype/tools/mayalookassigner/widgets.py | 13 +++++++------ 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/openpype/tools/mayalookassigner/app.py b/openpype/tools/mayalookassigner/app.py index 83c779fde1..0f5a930902 100644 --- a/openpype/tools/mayalookassigner/app.py +++ b/openpype/tools/mayalookassigner/app.py @@ -2,11 +2,12 @@ import sys import time import logging +from Qt import QtWidgets, QtCore + from openpype.hosts.maya.api.lib import assign_look_by_version from avalon import style, io from avalon.tools import lib -from avalon.vendor.Qt import QtWidgets, QtCore from maya import cmds # old api for MFileIO @@ -21,7 +22,7 @@ from .commands import ( get_workfile, remove_unused_looks ) -from . vray_proxies import vrayproxy_assign_look +from .vray_proxies import vrayproxy_assign_look module = sys.modules[__name__] diff --git a/openpype/tools/mayalookassigner/models.py b/openpype/tools/mayalookassigner/models.py index 7c5133de82..80de6c1897 100644 --- a/openpype/tools/mayalookassigner/models.py +++ b/openpype/tools/mayalookassigner/models.py @@ -1,7 +1,8 @@ from collections import defaultdict -from avalon.tools import models -from avalon.vendor.Qt import QtCore +from Qt import QtCore + +from avalon.tools import models from avalon.vendor import qtawesome from avalon.style import colors diff --git a/openpype/tools/mayalookassigner/views.py b/openpype/tools/mayalookassigner/views.py index decf04ee57..993023bb45 100644 --- a/openpype/tools/mayalookassigner/views.py +++ b/openpype/tools/mayalookassigner/views.py @@ -1,4 +1,4 @@ -from avalon.vendor.Qt import QtWidgets, QtCore +from Qt import QtWidgets, QtCore DEFAULT_COLOR = "#fb9c15" diff --git a/openpype/tools/mayalookassigner/widgets.py b/openpype/tools/mayalookassigner/widgets.py index 2dab266af9..625e9ef8c6 100644 --- a/openpype/tools/mayalookassigner/widgets.py +++ b/openpype/tools/mayalookassigner/widgets.py @@ -1,13 +1,16 @@ import logging from collections import defaultdict -from avalon.vendor.Qt import QtWidgets, QtCore +from Qt import QtWidgets, QtCore # TODO: expose this better in avalon core from avalon.tools import lib from avalon.tools.models import TreeModel -from . import models +from .models import ( + AssetModel, + LookModel +) from . import commands from . import views @@ -30,7 +33,7 @@ class AssetOutliner(QtWidgets.QWidget): title.setAlignment(QtCore.Qt.AlignCenter) title.setStyleSheet("font-weight: bold; font-size: 12px") - model = models.AssetModel() + model = AssetModel() view = views.View() view.setModel(model) view.customContextMenuRequested.connect(self.right_mouse_menu) @@ -201,7 +204,7 @@ class LookOutliner(QtWidgets.QWidget): title.setStyleSheet("font-weight: bold; font-size: 12px") title.setAlignment(QtCore.Qt.AlignCenter) - model = models.LookModel() + model = LookModel() # Proxy for dynamic sorting proxy = QtCore.QSortFilterProxyModel() @@ -257,5 +260,3 @@ class LookOutliner(QtWidgets.QWidget): menu.addAction(apply_action) menu.exec_(globalpos) - - From ec6210a826e6e390a8349d1dda885e8008b0edac Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 18 Oct 2021 17:42:57 +0200 Subject: [PATCH 163/279] fix publish callback code in houdini --- openpype/hosts/houdini/startup/MainMenuCommon.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/startup/MainMenuCommon.xml b/openpype/hosts/houdini/startup/MainMenuCommon.xml index 2923cb9ef5..2b556a2e75 100644 --- a/openpype/hosts/houdini/startup/MainMenuCommon.xml +++ b/openpype/hosts/houdini/startup/MainMenuCommon.xml @@ -40,7 +40,7 @@ host_tools.show_scene_inventory(parent) import hou from openpype.tools.utils import host_tools parent = hou.qt.mainWindow() -publish.show(parent) +host_tools.show_publish(parent) ]]> From 62977a9b4f184fc3316a0073e0e317f3967b1632 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 19 Oct 2021 12:05:40 +0200 Subject: [PATCH 164/279] OP-1206 - added reference loader wip --- .../photoshop/plugins/load/load_image.py | 6 +- .../photoshop/plugins/load/load_reference.py | 65 ++++++++++++++++++- 2 files changed, 66 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/photoshop/plugins/load/load_image.py b/openpype/hosts/photoshop/plugins/load/load_image.py index d97894b269..981a1ed204 100644 --- a/openpype/hosts/photoshop/plugins/load/load_image.py +++ b/openpype/hosts/photoshop/plugins/load/load_image.py @@ -6,7 +6,6 @@ from openpype.hosts.photoshop.plugins.lib import get_unique_layer_name stub = photoshop.stub() - class ImageLoader(api.Loader): """Load images @@ -45,8 +44,9 @@ class ImageLoader(api.Loader): layer_name = "{}_{}".format(context["asset"], context["subset"]) # switching assets if namespace_from_container != layer_name: - layer_name = self._get_unique_layer_name(context["asset"], - context["subset"]) + layer_name = get_unique_layer_name(stub.get_layers(), + context["asset"], + context["subset"]) else: # switching version - keep same name layer_name = container["namespace"] diff --git a/openpype/hosts/photoshop/plugins/load/load_reference.py b/openpype/hosts/photoshop/plugins/load/load_reference.py index 306647c032..1b54bd97f1 100644 --- a/openpype/hosts/photoshop/plugins/load/load_reference.py +++ b/openpype/hosts/photoshop/plugins/load/load_reference.py @@ -8,15 +8,76 @@ from openpype.hosts.photoshop.plugins.load.load_image import ImageLoader stub = photoshop.stub() -class ReferenceLoader(ImageLoader): +class ReferenceLoader(api.Loader): """Load reference images - Stores the imported asset in a container named after the asset. + Stores the imported asset in a container named after the asset. + + Inheriting from 'load_image' didn't work because of + "Cannot write to closing transport", possible refactor. """ families = ["image", "render"] representations = ["*"] + def load(self, context, name=None, namespace=None, data=None): + layer_name = get_unique_layer_name(stub.get_layers(), + context["asset"]["name"], + name) + with photoshop.maintained_selection(): + layer = self.import_layer(self.fname, layer_name) + + self[:] = [layer] + namespace = namespace or layer_name + + return photoshop.containerise( + name, + namespace, + layer, + context, + self.__class__.__name__ + ) + + def update(self, container, representation): + """ Switch asset or change version """ + layer = container.pop("layer") + + context = representation.get("context", {}) + + namespace_from_container = re.sub(r'_\d{3}$', '', + container["namespace"]) + layer_name = "{}_{}".format(context["asset"], context["subset"]) + # switching assets + if namespace_from_container != layer_name: + layer_name = get_unique_layer_name(stub.get_layers(), + context["asset"], + context["subset"]) + else: # switching version - keep same name + layer_name = container["namespace"] + + path = api.get_representation_path(representation) + with photoshop.maintained_selection(): + stub.replace_smart_object( + layer, path, layer_name + ) + + stub.imprint( + layer, {"representation": str(representation["_id"])} + ) + + def remove(self, container): + """ + Removes element from scene: deletes layer + removes from Headline + Args: + container (dict): container to be removed - used to get layer_id + """ + layer = container.pop("layer") + stub.imprint(layer, {}) + stub.delete_layer(layer.id) + + def switch(self, container, representation): + self.update(container, representation) + def import_layer(self, file_name, layer_name): return stub.import_smart_object(file_name, layer_name, as_reference=True) From 9ed805f1a652db2feee2554ba4d31e2f63c36695 Mon Sep 17 00:00:00 2001 From: karimmozlia Date: Tue, 19 Oct 2021 12:24:50 +0200 Subject: [PATCH 165/279] update --- openpype/hosts/nuke/api/lib.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index eb4eab2b22..6e46747d85 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -1042,12 +1042,6 @@ class WorkfileSettings(object): existing_format.setWidth(data["width"]) existing_format.setHeight(data["height"]) existing_format.setPixelAspect(data["pixel_aspect"]) - - if bbox: - existing_format.setX(data["x"]) - existing_format.setY(data["y"]) - existing_format.setR(data["r"]) - existing_format.setT(data["t"]) else: format_string = self.make_format_string(**data) log.info("Creating new format: {}".format(format_string)) From 5b60f07f1373a416168dfbc9ccb3bafbb2e27f79 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Tue, 19 Oct 2021 12:35:37 +0200 Subject: [PATCH 166/279] add smooth transformation to loader thumbnail widget --- openpype/tools/loader/widgets.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index 6b94fc6e44..1ccbb5796d 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -786,7 +786,10 @@ class ThumbnailWidget(QtWidgets.QLabel): def scale_pixmap(self, pixmap): return pixmap.scaled( - self.width(), self.height(), QtCore.Qt.KeepAspectRatio + self.width(), + self.height(), + QtCore.Qt.KeepAspectRatio, + QtCore.Qt.SmoothTransformation ) def set_thumbnail(self, entity=None): From 0188febe4f17066249d34f18c020deafd869f64a Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 19 Oct 2021 13:01:28 +0200 Subject: [PATCH 167/279] PYPE-1901 - added check that no other batch is currently processed PS cannot be run twice on a host machine --- openpype/lib/remote_publish.py | 49 +++++++++++++++++++++++++++++++ openpype/pype_commands.py | 53 ++++++++++++++++++++-------------- 2 files changed, 81 insertions(+), 21 deletions(-) diff --git a/openpype/lib/remote_publish.py b/openpype/lib/remote_publish.py index 4946e1bd53..51007cfad2 100644 --- a/openpype/lib/remote_publish.py +++ b/openpype/lib/remote_publish.py @@ -100,6 +100,55 @@ def publish_and_log(dbcon, _id, log, close_plugin_name=None): ) +def fail_batch(_id, batches_in_progress, dbcon): + """Set current batch as failed as there are some stuck batches.""" + running_batches = [str(batch["_id"]) + for batch in batches_in_progress + if batch["_id"] != _id] + msg = "There are still running batches {}\n". \ + format("\n".join(running_batches)) + msg += "Ask admin to check them and reprocess current batch" + dbcon.update_one( + {"_id": _id}, + {"$set": + { + "finish_date": datetime.now(), + "status": "error", + "log": msg + + }} + ) + raise ValueError(msg) + + +def find_variant_key(application_manager, host): + """Searches for latest installed variant for 'host' + + Args: + application_manager (ApplicationManager) + host (str) + Returns + (string) (optional) + Raises: + (ValueError) if no variant found + """ + app_group = application_manager.app_groups.get(host) + if not app_group or not app_group.enabled: + raise ValueError("No application {} configured".format(host)) + + found_variant_key = None + # finds most up-to-date variant if any installed + for variant_key, variant in app_group.variants.items(): + for executable in variant.executables: + if executable.exists(): + found_variant_key = variant_key + + if not found_variant_key: + raise ValueError("No executable for {} found".format(host)) + + return found_variant_key + + def _get_close_plugin(close_plugin_name, log): if close_plugin_name: plugins = pyblish.api.discover() diff --git a/openpype/pype_commands.py b/openpype/pype_commands.py index e2869a956d..3071629ee5 100644 --- a/openpype/pype_commands.py +++ b/openpype/pype_commands.py @@ -3,7 +3,6 @@ import os import sys import json -from datetime import datetime import time from openpype.lib import PypeLogger @@ -12,7 +11,9 @@ from openpype.lib.plugin_tools import parse_json, get_batch_asset_task_info from openpype.lib.remote_publish import ( get_webpublish_conn, start_webpublish_log, - publish_and_log + publish_and_log, + fail_batch, + find_variant_key ) @@ -125,10 +126,17 @@ class PypeCommands: wants to process uploaded .psd file and publish collected layers from there. + Checks if no other batches are running (status =='in_progress). If + so, it sleeps for SLEEP (this is separate process), + waits for WAIT_FOR seconds altogether. + Requires installed host application on the machine. 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 from openpype.api import Logger @@ -141,25 +149,12 @@ class PypeCommands: from openpype.lib import ApplicationManager application_manager = ApplicationManager() - app_group = application_manager.app_groups.get(host) - if not app_group or not app_group.enabled: - raise ValueError("No application {} configured".format(host)) - - found_variant_key = None - # finds most up-to-date variant if any installed - for variant_key, variant in app_group.variants.items(): - for executable in variant.executables: - if executable.exists(): - found_variant_key = variant_key - - if not found_variant_key: - raise ValueError("No executable for {} found".format(host)) + 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): - # TODO check if batch manifest is same as tasks manifests batch_data = parse_json(os.path.join(batch_dir, "manifest.json")) if not batch_data: @@ -174,6 +169,27 @@ class PypeCommands: batch_data["files"][0]) print("workfile_path {}".format(workfile_path)) + _, batch_id = os.path.split(batch_dir) + 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) + + print("Another batch running, sleeping for a bit") + time.sleep(SLEEP) + slept_times += 1 + else: + in_progress = False + # must have for proper launch of app env = get_app_environments_for_context( project, @@ -183,11 +199,6 @@ class PypeCommands: ) os.environ.update(env) - _, batch_id = os.path.split(batch_dir) - dbcon = get_webpublish_conn() - # safer to start logging here, launch might be broken altogether - _id = start_webpublish_log(dbcon, batch_id, user) - os.environ["OPENPYPE_PUBLISH_DATA"] = batch_dir os.environ["IS_HEADLESS"] = "true" # must pass identifier to update log lines for a batch From 4e000a6cb425a56abd14a9d406f13099e8da3898 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 19 Oct 2021 13:22:58 +0200 Subject: [PATCH 168/279] PYPE-1901 - added proper selection of host based on studio_processing --- .../webserver_service/webpublish_routes.py | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py index 0014d1b344..3f67038fbf 100644 --- a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py +++ b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py @@ -175,6 +175,9 @@ 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() @@ -182,10 +185,28 @@ class WebpublisherBatchPublishEndpoint(_RestApiEndpoint): 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") + command = "remotepublishfromapp" + for host, extensions in host_map.items(): + for ext in extensions: + for file_name in content.get("files", []): + if ext in file_name: + add_args["host"] = host + break + openpype_app = self.resource.executable args = [ openpype_app, - 'remotepublish', + command, batch_path ] @@ -193,12 +214,6 @@ class WebpublisherBatchPublishEndpoint(_RestApiEndpoint): msg = "Non existent OpenPype executable {}".format(openpype_app) raise RuntimeError(msg) - add_args = { - "host": "webpublisher", - "project": content["project_name"], - "user": content["user"] - } - for key, value in add_args.items(): args.append("--{}".format(key)) args.append(value) From 2d687e5e6a923c4f34fac241f074867f6d85580b Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 19 Oct 2021 15:18:41 +0200 Subject: [PATCH 169/279] fix maya hotbox --- openpype/hosts/maya/api/menu.py | 66 ++++++++++++++++----------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/openpype/hosts/maya/api/menu.py b/openpype/hosts/maya/api/menu.py index ad225dcd28..18d3e1e896 100644 --- a/openpype/hosts/maya/api/menu.py +++ b/openpype/hosts/maya/api/menu.py @@ -19,10 +19,8 @@ def _get_menu(menu_name=None): if menu_name is None: menu_name = pipeline._menu - widgets = dict(( - w.objectName(), w) for w in QtWidgets.QApplication.allWidgets()) - menu = widgets.get(menu_name) - return menu + widgets = {w.objectName(): w for w in QtWidgets.QApplication.allWidgets()} + return widgets.get(menu_name) def deferred(): @@ -43,6 +41,34 @@ def deferred(): command=lambda *args: mayalookassigner.show() ) + def add_scripts_menu(): + try: + import scriptsmenu.launchformaya as launchformaya + except ImportError: + log.warning( + "Skipping studio.menu install, because " + "'scriptsmenu' module seems unavailable." + ) + return + + # load configuration of custom menu + project_settings = get_project_settings(os.getenv("AVALON_PROJECT")) + config = project_settings["maya"]["scriptsmenu"]["definition"] + _menu = project_settings["maya"]["scriptsmenu"]["name"] + + if not config: + log.warning("Skipping studio menu, no definition found.") + return + + # run the launcher for Maya menu + studio_menu = launchformaya.main( + title=_menu.title(), + objectName=_menu.title().lower().replace(" ", "_") + ) + + # apply configuration + studio_menu.build_from_configuration(studio_menu, config) + def modify_workfiles(): from openpype.tools import workfiles @@ -109,38 +135,12 @@ def deferred(): log.info("Attempting to install scripts menu ...") + # add_scripts_menu() add_build_workfiles_item() add_look_assigner_item() modify_workfiles() remove_project_manager() - - try: - import scriptsmenu.launchformaya as launchformaya - import scriptsmenu.scriptsmenu as scriptsmenu - except ImportError: - log.warning( - "Skipping studio.menu install, because " - "'scriptsmenu' module seems unavailable." - ) - return - - # load configuration of custom menu - project_settings = get_project_settings(os.getenv("AVALON_PROJECT")) - config = project_settings["maya"]["scriptsmenu"]["definition"] - _menu = project_settings["maya"]["scriptsmenu"]["name"] - - if not config: - log.warning("Skipping studio menu, no definition found.") - return - - # run the launcher for Maya menu - studio_menu = launchformaya.main( - title=_menu.title(), - objectName=_menu.title().lower().replace(" ", "_") - ) - - # apply configuration - studio_menu.build_from_configuration(studio_menu, config) + add_scripts_menu() def uninstall(): @@ -161,7 +161,7 @@ def install(): return # Allow time for uninstallation to finish. - cmds.evalDeferred(deferred) + cmds.evalDeferred(deferred, lowestPriority=True) def popup(): From 8b1952ca4be2d4a8a477fc0361913f259c88f83a Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 19 Oct 2021 15:22:38 +0200 Subject: [PATCH 170/279] Hound --- openpype/hosts/photoshop/plugins/load/load_reference.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/photoshop/plugins/load/load_reference.py b/openpype/hosts/photoshop/plugins/load/load_reference.py index 1b54bd97f1..0cb4e4a69f 100644 --- a/openpype/hosts/photoshop/plugins/load/load_reference.py +++ b/openpype/hosts/photoshop/plugins/load/load_reference.py @@ -3,7 +3,6 @@ import re from avalon import api, photoshop from openpype.hosts.photoshop.plugins.lib import get_unique_layer_name -from openpype.hosts.photoshop.plugins.load.load_image import ImageLoader stub = photoshop.stub() From 71d79a2dc91f707bf806f864367de92e54e3cd87 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 19 Oct 2021 15:25:16 +0200 Subject: [PATCH 171/279] ignore save warnings exception in prepare project --- .../event_handlers_server/action_prepare_project.py | 9 +++++++-- .../ftrack/event_handlers_user/action_prepare_project.py | 8 +++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/openpype/modules/default_modules/ftrack/event_handlers_server/action_prepare_project.py b/openpype/modules/default_modules/ftrack/event_handlers_server/action_prepare_project.py index 85317031b2..2e55be2743 100644 --- a/openpype/modules/default_modules/ftrack/event_handlers_server/action_prepare_project.py +++ b/openpype/modules/default_modules/ftrack/event_handlers_server/action_prepare_project.py @@ -3,6 +3,7 @@ import json from avalon.api import AvalonMongoDB from openpype.api import ProjectSettings from openpype.lib import create_project +from openpype.settings import SaveWarningExc from openpype_modules.ftrack.lib import ( ServerAction, @@ -312,7 +313,6 @@ class PrepareProjectServer(ServerAction): if not in_data: return - root_values = {} root_key = "__root__" for key in tuple(in_data.keys()): @@ -392,7 +392,12 @@ class PrepareProjectServer(ServerAction): else: attributes_entity[key] = value - project_settings.save() + try: + project_settings.save() + except SaveWarningExc as exc: + self.log.info("Few warnings happened during settings save:") + for warning in exc.warnings: + self.log.info(str(warning)) # Change custom attributes on project if custom_attribute_values: diff --git a/openpype/modules/default_modules/ftrack/event_handlers_user/action_prepare_project.py b/openpype/modules/default_modules/ftrack/event_handlers_user/action_prepare_project.py index 87d3329179..3759bc81ac 100644 --- a/openpype/modules/default_modules/ftrack/event_handlers_user/action_prepare_project.py +++ b/openpype/modules/default_modules/ftrack/event_handlers_user/action_prepare_project.py @@ -3,6 +3,7 @@ import json from avalon.api import AvalonMongoDB from openpype.api import ProjectSettings from openpype.lib import create_project +from openpype.settings import SaveWarningExc from openpype_modules.ftrack.lib import ( BaseAction, @@ -417,7 +418,12 @@ class PrepareProjectLocal(BaseAction): else: attributes_entity[key] = value - project_settings.save() + try: + project_settings.save() + except SaveWarningExc as exc: + self.log.info("Few warnings happened during settings save:") + for warning in exc.warnings: + self.log.info(str(warning)) # Change custom attributes on project if custom_attribute_values: From 9f35dd7763322a3c488cd827f372bae51080d800 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 19 Oct 2021 17:31:23 +0200 Subject: [PATCH 172/279] added basic of experimental tool definitions --- openpype/tools/experimental_tools/__init__.py | 9 ++ .../tools/experimental_tools/tools_def.py | 82 +++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 openpype/tools/experimental_tools/__init__.py create mode 100644 openpype/tools/experimental_tools/tools_def.py diff --git a/openpype/tools/experimental_tools/__init__.py b/openpype/tools/experimental_tools/__init__.py new file mode 100644 index 0000000000..d61c560886 --- /dev/null +++ b/openpype/tools/experimental_tools/__init__.py @@ -0,0 +1,9 @@ +from .tools_def import ( + ExperimentalTools, + LOCAL_EXPERIMENTAL_KEY +) + +__all__ = ( + "ExperimentalTools", + "LOCAL_EXPERIMENTAL_KEY" +) diff --git a/openpype/tools/experimental_tools/tools_def.py b/openpype/tools/experimental_tools/tools_def.py new file mode 100644 index 0000000000..ec4815c741 --- /dev/null +++ b/openpype/tools/experimental_tools/tools_def.py @@ -0,0 +1,82 @@ +from openpype.settings import get_local_settings + +# Constant key under which local settings are stored +LOCAL_EXPERIMENTAL_KEY = "experimental_tools" + + +class ExperimentalTool: + """Definition of experimental tool. + + Args: + identifier (str): String identifier of tool (unique). + label (str): Label shown in UI. + callback (function): Callback for UI button. + tooltip (str): Tooltip showed on button. + hosts_filter (list): List of host names for which is tool available. + Some tools may not be available in all hosts. + """ + def __init__(self, identifier, label, callback, tooltip, hosts_filter=None): + self.identifier = identifier + self.label = label + self.callback = callback + self.tooltip = tooltip + self.hosts_filter = hosts_filter + self._enabled = True + + def is_available_for_host(self, host_name): + if self.hosts_filter: + return host_name in self.hosts_filter + return True + + @property + def enabled(self): + """Is tool enabled and button is clickable.""" + return self._enabled + + def set_enabled(self, enabled=True): + """Change if tool is enabled.""" + self._enabled = enabled + + def execute(self): + """Trigger registerd callback.""" + self.callback() + + +class ExperimentalTools: + """Wrapper around experimental tools. + + To add/remove experimental tool just add/remove tool to + `experimental_tools` variable in __init__ function. + + """ + def __init__(self, parent=None, host_name=None, filter_hosts=None): + experimental_tools = [] + if filter_hosts is None: + filter_hosts = host_name is not None + + if filter_hosts and not host_name: + filter_hosts = False + + if filter_hosts: + experimental_tools = [ + tool + for tool in experimental_tools + if tool.is_available_for_host(host_name) + ] + + self.tools_by_identifier = { + tool.identifier: tool + for tool in experimental_tools + } + self.experimental_tools = experimental_tools + self._parent_widget = parent + + def refresh_availability(self): + local_settings = get_local_settings() + experimental_settings = ( + local_settings.get(LOCAL_EXPERIMENTAL_KEY) + ) or {} + + for identifier, eperimental_tool in self.tools_by_identifier.items(): + enabled = experimental_settings.get(identifier, False) + eperimental_tool.set_enabled(enabled) From e1e2f8e9ddf60fe8b1a48a21bce67e7e2d47a3d7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 19 Oct 2021 17:33:07 +0200 Subject: [PATCH 173/279] base of dialog for experimental tools --- openpype/tools/experimental_tools/dialog.py | 146 ++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 openpype/tools/experimental_tools/dialog.py diff --git a/openpype/tools/experimental_tools/dialog.py b/openpype/tools/experimental_tools/dialog.py new file mode 100644 index 0000000000..db868c572f --- /dev/null +++ b/openpype/tools/experimental_tools/dialog.py @@ -0,0 +1,146 @@ +from Qt import QtWidgets + +from openpype.style import ( + load_stylesheet, + app_icon_path +) + +from .tools_def import ExperimentalTools + + +class ToolButton(QtWidgets.QPushButton): + triggered = QtCore.Signal(str) + + def __init__(self, identifier, *args, **kwargs): + super(ExperimentalDialog, self).__init__(*args, **kwargs) + self._identifier = identifier + + self.clicked.connect(self._on_click) + + def _on_click(self): + self.triggered.emit(self._identifier) + + +class ExperimentalDialog(QtWidgets.QDialog): + refresh_interval = 3000 + + def __init__(self, parent=None): + super(ExperimentalDialog, self).__init__(parent) + self.setWindowTitle("OpenPype Experimental tools") + self.setWindowIcon(app_icon_path()) + + empty_label = QtWidgets.QLabel( + "There are no experimental tools available.", self + ) + content_widget = QtWidgets.QWidget(self) + content_widget.setVisible(False) + + content_layout = QtWidgets.QHBoxLayout(content_widget) + content_layout.setContentsMargins(0, 0, 0, 0) + + experimental_tools = ExperimentalTools() + buttons_by_tool_identifier = {} + + layout = QtWidgets.QHBoxLayout(self) + layout.addWidget(empty_label) + layout.addWidget(content_widget) + + refresh_timer = QtCore.QTimer() + refresh_timer.setInterval(self.refresh_interval) + refresh_timer.timeout.connect(self._on_refresh_timeout) + + self._empty_label = empty_label + self._content_widget = content_widget + self._content_layout = content_layout + + self._experimental_tools = experimental_tools + self._buttons_by_tool_identifier = buttons_by_tool_identifier + + self._is_refreshing = False + self._refresh_on_active = True + self._window_is_active = False + self._refresh_timer = refresh_timer + + def refresh(self): + if self._is_refreshing: + return + self._is_refreshing = True + + self._experimental_tools.refresh_availability() + + buttons_to_remove = set(self._buttons_by_tool_identifier.keys()) + for idx, tool in enumerate(self._experimental_tools.experimental_tools): + identifier = tool.identifier + if identifier in buttons_to_remove: + buttons_to_remove.remove(identifier) + is_new = False + button = self._buttons_by_tool_identifier[identifier] + else: + is_new = True + button = ToolButton(identifier, self) + button.triggered.connect(self._on_btn_trigger) + self._buttons_by_tool_identifier[identifier] = button + self._content_layout.insertWidget(idx, button) + + if button.text() != tool.label: + button.setText(tool.label) + + if tool.enabled: + button.setToolTip(tool.tooltip) + + elif is_new or button.isEnabled(): + button.setToolTip(( + "You can enable this tool in local settings. + "\n\nOpenPype Tray > Settings > Experimental Tools" + )) + + for identifier in buttons_to_remove: + button = self._buttons_by_tool_identifier.pop(identifier) + button.setVisible(False) + idx = self._content_layout.indexOf(button) + self._content_layout.takeAt(idx) + button.deleteLater() + + self._empty_label.setVisible(not self._buttons_by_tool_identifier) + + self._is_refreshing = False + + def _on_btn_trigger(self, identifier): + tool = self._experimental_tools.tools_by_identifier.get(identifier) + if tool is not None: + tool.execute() + + def showEvent(self, event): + super(LauncherWindow, self).showEvent(event) + + if self._refresh_on_active: + # Start/Restart timer + self._refresh_timer.start() + # Refresh + self.refresh() + + elif not self._refresh_timer.isActive(): + self._refresh_timer.start() + + def changeEvent(self, event): + if event.type() == QtCore.QEvent.ActivationChange: + self._window_is_active = self.isActiveWindow() + if self._window_is_active and self._refresh_on_active: + self._refresh_timer.start() + self.refresh() + + super(LauncherWindow, self).changeEvent(event) + + def _on_refresh_timeout(self): + # Stop timer if window is not visible + if not self.isVisible(): + self._refresh_on_active = True + self._refresh_timer.stop() + + # Skip refreshing if window is not active + elif not self._window_is_active: + self._refresh_on_active = True + + # Window is active and visible so we're refreshing buttons + else: + self.refresh() From 6a68cfd4c93f84cbd99fd1a250606debcc4e92f0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 19 Oct 2021 17:36:46 +0200 Subject: [PATCH 174/279] added Experimental tools category to local settings --- .../local_settings/experimental_widget.py | 63 +++++++++++++++++++ .../tools/settings/local_settings/window.py | 33 ++++++++++ 2 files changed, 96 insertions(+) create mode 100644 openpype/tools/settings/local_settings/experimental_widget.py diff --git a/openpype/tools/settings/local_settings/experimental_widget.py b/openpype/tools/settings/local_settings/experimental_widget.py new file mode 100644 index 0000000000..953f8f75a9 --- /dev/null +++ b/openpype/tools/settings/local_settings/experimental_widget.py @@ -0,0 +1,63 @@ +from Qt import QtWidgets +from openpype.tools.experimental_tools import ( + ExperimentalTools, + LOCAL_EXPERIMENTAL_KEY +) + + +__all__ = ( + "LocalExperimentalToolsWidgets", + "LOCAL_EXPERIMENTAL_KEY" +) + + +class LocalExperimentalToolsWidgets(QtWidgets.QWidget): + def __init__(self, parent): + super(LocalExperimentalToolsWidgets, self).__init__(parent) + + self._loading_local_settings = False + + layout = QtWidgets.QFormLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + + # Label that says there are no experimental tools available + empty_label = QtWidgets.QLabel(self) + empty_label.setText("There are no experimental tools available.") + + layout.addRow(empty_label) + + experimental_defs = ExperimentalTools() + checkboxes_by_identifier = {} + for tool in experimental_defs.experimental_tools: + checkbox = QtWidgets.QCheckBox(self) + label_widget = QtWidgets.QLabel(tool.label, self) + checkbox.setToolTip(tool.tooltip) + label_widget.setToolTip(tool.tooltip) + layout.addRow(label_widget, checkbox) + + checkboxes_by_identifier[tool.identifier] = checkbox + + empty_label.setVisible(len(checkboxes_by_identifier) == 0) + + self._empty_label = empty_label + self._checkboxes_by_identifier = checkboxes_by_identifier + self._experimental_defs = experimental_defs + + def update_local_settings(self, value): + self._loading_local_settings = True + value = value or {} + + for identifier, checkbox in self._checkboxes_by_identifier.items(): + checked = value.get(identifier, False) + checkbox.setChecked(checked) + + self._loading_local_settings = False + + def settings_value(self): + # Add changed + # If these have changed then + output = {} + for identifier, checkbox in self._checkboxes_by_identifier.items(): + if checkbox.isChecked(): + output[identifier] = True + return output diff --git a/openpype/tools/settings/local_settings/window.py b/openpype/tools/settings/local_settings/window.py index 9e8fd89b23..f22e397323 100644 --- a/openpype/tools/settings/local_settings/window.py +++ b/openpype/tools/settings/local_settings/window.py @@ -20,6 +20,10 @@ from .widgets import ( ) from .mongo_widget import OpenPypeMongoWidget from .general_widget import LocalGeneralWidgets +from .experimental_widget import ( + LocalExperimentalToolsWidgets, + LOCAL_EXPERIMENTAL_KEY +) from .apps_widget import LocalApplicationsWidgets from .projects_widget import ProjectSettingsWidget @@ -44,11 +48,13 @@ class LocalSettingsWidget(QtWidgets.QWidget): self.pype_mongo_widget = None self.general_widget = None + self.experimental_widget = None self.apps_widget = None self.projects_widget = None self._create_pype_mongo_ui() self._create_general_ui() + self._create_experimental_ui() self._create_app_ui() self._create_project_ui() @@ -85,6 +91,26 @@ class LocalSettingsWidget(QtWidgets.QWidget): self.general_widget = general_widget + def _create_experimental_ui(self): + # General + experimental_expand_widget = ExpandingWidget( + "Experimental tools", self + ) + + experimental_content = QtWidgets.QWidget(self) + experimental_layout = QtWidgets.QVBoxLayout(experimental_content) + experimental_layout.setContentsMargins(CHILD_OFFSET, 5, 0, 0) + experimental_expand_widget.set_content_widget(experimental_content) + + experimental_widget = LocalExperimentalToolsWidgets( + experimental_content + ) + experimental_layout.addWidget(experimental_widget) + + self.main_layout.addWidget(experimental_expand_widget) + + self.experimental_widget = experimental_widget + def _create_app_ui(self): # Applications app_expand_widget = ExpandingWidget("Applications", self) @@ -135,6 +161,9 @@ class LocalSettingsWidget(QtWidgets.QWidget): self.projects_widget.update_local_settings( value.get(LOCAL_PROJECTS_KEY) ) + self.experimental_widget.update_local_settings( + value.get(LOCAL_EXPERIMENTAL_KEY) + ) def settings_value(self): output = {} @@ -149,6 +178,10 @@ class LocalSettingsWidget(QtWidgets.QWidget): projects_value = self.projects_widget.settings_value() if projects_value: output[LOCAL_PROJECTS_KEY] = projects_value + + experimental_value = self.experimental_widget.settings_value() + if experimental_value: + output[LOCAL_EXPERIMENTAL_KEY] = experimental_value return output From 90de4cddb582ed50eea8ac65698d3517f6dd058c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 19 Oct 2021 17:50:10 +0200 Subject: [PATCH 175/279] store tools under 'tools' variable --- openpype/tools/experimental_tools/tools_def.py | 2 +- openpype/tools/settings/local_settings/experimental_widget.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/experimental_tools/tools_def.py b/openpype/tools/experimental_tools/tools_def.py index ec4815c741..353ec8e1d5 100644 --- a/openpype/tools/experimental_tools/tools_def.py +++ b/openpype/tools/experimental_tools/tools_def.py @@ -68,7 +68,7 @@ class ExperimentalTools: tool.identifier: tool for tool in experimental_tools } - self.experimental_tools = experimental_tools + self.tools = experimental_tools self._parent_widget = parent def refresh_availability(self): diff --git a/openpype/tools/settings/local_settings/experimental_widget.py b/openpype/tools/settings/local_settings/experimental_widget.py index 953f8f75a9..741c173415 100644 --- a/openpype/tools/settings/local_settings/experimental_widget.py +++ b/openpype/tools/settings/local_settings/experimental_widget.py @@ -28,7 +28,7 @@ class LocalExperimentalToolsWidgets(QtWidgets.QWidget): experimental_defs = ExperimentalTools() checkboxes_by_identifier = {} - for tool in experimental_defs.experimental_tools: + for tool in experimental_defs.tools: checkbox = QtWidgets.QCheckBox(self) label_widget = QtWidgets.QLabel(tool.label, self) checkbox.setToolTip(tool.tooltip) From 27e9f20c07cc3a9550e2fdae70d2b00560e6ede8 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 19 Oct 2021 17:58:10 +0200 Subject: [PATCH 176/279] fix dialog file --- openpype/tools/experimental_tools/dialog.py | 25 +++++++++++---------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/openpype/tools/experimental_tools/dialog.py b/openpype/tools/experimental_tools/dialog.py index db868c572f..14f3e6e48b 100644 --- a/openpype/tools/experimental_tools/dialog.py +++ b/openpype/tools/experimental_tools/dialog.py @@ -1,4 +1,4 @@ -from Qt import QtWidgets +from Qt import QtWidgets, QtCore, QtGui from openpype.style import ( load_stylesheet, @@ -12,7 +12,7 @@ class ToolButton(QtWidgets.QPushButton): triggered = QtCore.Signal(str) def __init__(self, identifier, *args, **kwargs): - super(ExperimentalDialog, self).__init__(*args, **kwargs) + super(ToolButton, self).__init__(*args, **kwargs) self._identifier = identifier self.clicked.connect(self._on_click) @@ -27,23 +27,22 @@ class ExperimentalDialog(QtWidgets.QDialog): def __init__(self, parent=None): super(ExperimentalDialog, self).__init__(parent) self.setWindowTitle("OpenPype Experimental tools") - self.setWindowIcon(app_icon_path()) + icon = QtGui.QIcon(app_icon_path()) + self.setWindowIcon(icon) empty_label = QtWidgets.QLabel( "There are no experimental tools available.", self ) content_widget = QtWidgets.QWidget(self) - content_widget.setVisible(False) content_layout = QtWidgets.QHBoxLayout(content_widget) content_layout.setContentsMargins(0, 0, 0, 0) experimental_tools = ExperimentalTools() - buttons_by_tool_identifier = {} layout = QtWidgets.QHBoxLayout(self) layout.addWidget(empty_label) - layout.addWidget(content_widget) + layout.addWidget(content_widget, 1) refresh_timer = QtCore.QTimer() refresh_timer.setInterval(self.refresh_interval) @@ -54,7 +53,7 @@ class ExperimentalDialog(QtWidgets.QDialog): self._content_layout = content_layout self._experimental_tools = experimental_tools - self._buttons_by_tool_identifier = buttons_by_tool_identifier + self._buttons_by_tool_identifier = {} self._is_refreshing = False self._refresh_on_active = True @@ -69,7 +68,7 @@ class ExperimentalDialog(QtWidgets.QDialog): self._experimental_tools.refresh_availability() buttons_to_remove = set(self._buttons_by_tool_identifier.keys()) - for idx, tool in enumerate(self._experimental_tools.experimental_tools): + for idx, tool in enumerate(self._experimental_tools.tools): identifier = tool.identifier if identifier in buttons_to_remove: buttons_to_remove.remove(identifier) @@ -90,7 +89,7 @@ class ExperimentalDialog(QtWidgets.QDialog): elif is_new or button.isEnabled(): button.setToolTip(( - "You can enable this tool in local settings. + "You can enable this tool in local settings." "\n\nOpenPype Tray > Settings > Experimental Tools" )) @@ -101,7 +100,9 @@ class ExperimentalDialog(QtWidgets.QDialog): self._content_layout.takeAt(idx) button.deleteLater() - self._empty_label.setVisible(not self._buttons_by_tool_identifier) + self._empty_label.setVisible( + len(self._buttons_by_tool_identifier) == 0 + ) self._is_refreshing = False @@ -111,7 +112,7 @@ class ExperimentalDialog(QtWidgets.QDialog): tool.execute() def showEvent(self, event): - super(LauncherWindow, self).showEvent(event) + super(ExperimentalDialog, self).showEvent(event) if self._refresh_on_active: # Start/Restart timer @@ -129,7 +130,7 @@ class ExperimentalDialog(QtWidgets.QDialog): self._refresh_timer.start() self.refresh() - super(LauncherWindow, self).changeEvent(event) + super(ExperimentalDialog, self).changeEvent(event) def _on_refresh_timeout(self): # Stop timer if window is not visible From 5026d300bbcae2eaa87dccf3ad2a0655a3ae4274 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 19 Oct 2021 17:58:22 +0200 Subject: [PATCH 177/279] import ExperimentalDialog to init file --- openpype/tools/experimental_tools/__init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/openpype/tools/experimental_tools/__init__.py b/openpype/tools/experimental_tools/__init__.py index d61c560886..75e3210aab 100644 --- a/openpype/tools/experimental_tools/__init__.py +++ b/openpype/tools/experimental_tools/__init__.py @@ -3,7 +3,12 @@ from .tools_def import ( LOCAL_EXPERIMENTAL_KEY ) +from .dialog import ExperimentalDialog + + __all__ = ( "ExperimentalTools", - "LOCAL_EXPERIMENTAL_KEY" + "LOCAL_EXPERIMENTAL_KEY", + + "ExperimentalDialog" ) From 7d3f4d315ff71f71403dddfd3f95d1ecd57e69a3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 19 Oct 2021 18:20:26 +0200 Subject: [PATCH 178/279] empty dialog has Ok btn --- openpype/tools/experimental_tools/dialog.py | 65 ++++++++++++++++----- 1 file changed, 49 insertions(+), 16 deletions(-) diff --git a/openpype/tools/experimental_tools/dialog.py b/openpype/tools/experimental_tools/dialog.py index 14f3e6e48b..237052c055 100644 --- a/openpype/tools/experimental_tools/dialog.py +++ b/openpype/tools/experimental_tools/dialog.py @@ -30,41 +30,59 @@ class ExperimentalDialog(QtWidgets.QDialog): icon = QtGui.QIcon(app_icon_path()) self.setWindowIcon(icon) + empty_widget = QtWidgets.QWidget(self) + empty_label = QtWidgets.QLabel( - "There are no experimental tools available.", self + "There are no experimental tools available...", empty_widget ) + + empty_btns_layout = QtWidgets.QHBoxLayout() + ok_btn = QtWidgets.QPushButton("OK", empty_widget) + + empty_btns_layout.setContentsMargins(0, 0, 0, 0) + empty_btns_layout.addStretch(1) + empty_btns_layout.addWidget(ok_btn, 0) + + empty_layout = QtWidgets.QVBoxLayout(empty_widget) + empty_layout.setContentsMargins(0, 0, 0, 0) + empty_layout.addWidget(empty_label) + empty_layout.addStretch(1) + empty_layout.addLayout(empty_btns_layout) + content_widget = QtWidgets.QWidget(self) - content_layout = QtWidgets.QHBoxLayout(content_widget) + content_layout = QtWidgets.QVBoxLayout(content_widget) content_layout.setContentsMargins(0, 0, 0, 0) experimental_tools = ExperimentalTools() - layout = QtWidgets.QHBoxLayout(self) - layout.addWidget(empty_label) + layout = QtWidgets.QVBoxLayout(self) + layout.addWidget(empty_widget, 1) layout.addWidget(content_widget, 1) refresh_timer = QtCore.QTimer() refresh_timer.setInterval(self.refresh_interval) refresh_timer.timeout.connect(self._on_refresh_timeout) - self._empty_label = empty_label + ok_btn.clicked.connect(self._on_ok_click) + + self._empty_widget = empty_widget self._content_widget = content_widget self._content_layout = content_layout self._experimental_tools = experimental_tools self._buttons_by_tool_identifier = {} - self._is_refreshing = False - self._refresh_on_active = True - self._window_is_active = False self._refresh_timer = refresh_timer - def refresh(self): - if self._is_refreshing: - return - self._is_refreshing = True + # Is dialog first shown + self._first_show = True + # Trigger refresh when window get's activity + self._refresh_on_active = True + # Is window active + self._window_is_active = False + def refresh(self): self._experimental_tools.refresh_availability() buttons_to_remove = set(self._buttons_by_tool_identifier.keys()) @@ -100,11 +118,18 @@ class ExperimentalDialog(QtWidgets.QDialog): self._content_layout.takeAt(idx) button.deleteLater() - self._empty_label.setVisible( - len(self._buttons_by_tool_identifier) == 0 - ) + self._set_visibility() - self._is_refreshing = False + def _is_content_visible(self): + return len(self._buttons_by_tool_identifier) > 0 + + def _set_visibility(self): + content_visible = self._is_content_visible() + self._content_widget.setVisible(content_visible) + self._empty_widget.setVisible(not content_visible) + + def _on_ok_click(self): + self.close() def _on_btn_trigger(self, identifier): tool = self._experimental_tools.tools_by_identifier.get(identifier) @@ -123,6 +148,14 @@ class ExperimentalDialog(QtWidgets.QDialog): elif not self._refresh_timer.isActive(): self._refresh_timer.start() + if self._first_show: + self._first_show = False + # Resize dialog if there is not content + if not self._is_content_visible(): + size = self.size() + size.setWidth(size.width() + size.width() / 3) + self.resize(size) + def changeEvent(self, event): if event.type() == QtCore.QEvent.ActivationChange: self._window_is_active = self.isActiveWindow() From c7df4e9edc7185a0d155a215a8822b3bed9dcbd7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 19 Oct 2021 20:12:23 +0200 Subject: [PATCH 179/279] changed label --- openpype/tools/settings/local_settings/experimental_widget.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/tools/settings/local_settings/experimental_widget.py b/openpype/tools/settings/local_settings/experimental_widget.py index 741c173415..72f999d886 100644 --- a/openpype/tools/settings/local_settings/experimental_widget.py +++ b/openpype/tools/settings/local_settings/experimental_widget.py @@ -22,7 +22,9 @@ class LocalExperimentalToolsWidgets(QtWidgets.QWidget): # Label that says there are no experimental tools available empty_label = QtWidgets.QLabel(self) - empty_label.setText("There are no experimental tools available.") + empty_label.setText( + "There are no experimental tools available..." + ) layout.addRow(empty_label) From 54a8b9d811e6d8602b2f2b729ae0db2701c678ef Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 20 Oct 2021 11:28:54 +0200 Subject: [PATCH 180/279] removed "widget" key from data --- openpype/tools/loader/app.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/openpype/tools/loader/app.py b/openpype/tools/loader/app.py index a4b4b5eb28..d7fa9640ac 100644 --- a/openpype/tools/loader/app.py +++ b/openpype/tools/loader/app.py @@ -131,9 +131,6 @@ class LoaderWindow(QtWidgets.QDialog): layout.addWidget(footer_widget) self.data = { - "widgets": { - "subsets": subsets - }, "state": { "assetIds": None } @@ -206,8 +203,8 @@ class LoaderWindow(QtWidgets.QDialog): # ------------------------------- def on_assetview_click(self, *args): - subsets_widget = self.data["widgets"]["subsets"] - selection_model = subsets_widget.view.selectionModel() + # TODO do not touch inner attributes of subset widget + selection_model = self._subsets_widget.view.selectionModel() if selection_model.selectedIndexes(): selection_model.clearSelection() From 3a56ac5e5e1920dcffea9d54a0f043cc92c5e628 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 20 Oct 2021 11:05:36 +0200 Subject: [PATCH 181/279] validate tool identifier keys --- openpype/tools/experimental_tools/tools_def.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/openpype/tools/experimental_tools/tools_def.py b/openpype/tools/experimental_tools/tools_def.py index 353ec8e1d5..2fa42bcc6e 100644 --- a/openpype/tools/experimental_tools/tools_def.py +++ b/openpype/tools/experimental_tools/tools_def.py @@ -64,10 +64,16 @@ class ExperimentalTools: if tool.is_available_for_host(host_name) ] - self.tools_by_identifier = { - tool.identifier: tool - for tool in experimental_tools - } + # Store tools by identifier + tools_by_identifier = {} + for tool in experimental_tools: + if tool.identifier in tools_by_identifier: + raise KeyError(( + "Duplicated experimental tool identifier \"{}\"" + ).format(tool.identifier)) + tools_by_identifier[tool.identifier] = tool + + self.tools_by_identifier = tools_by_identifier self.tools = experimental_tools self._parent_widget = parent From 630299e340ab41af673dde31803288bb1587d93f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 20 Oct 2021 11:05:46 +0200 Subject: [PATCH 182/279] fix 80char line --- openpype/tools/experimental_tools/tools_def.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/tools/experimental_tools/tools_def.py b/openpype/tools/experimental_tools/tools_def.py index 2fa42bcc6e..13283c157a 100644 --- a/openpype/tools/experimental_tools/tools_def.py +++ b/openpype/tools/experimental_tools/tools_def.py @@ -15,7 +15,9 @@ class ExperimentalTool: hosts_filter (list): List of host names for which is tool available. Some tools may not be available in all hosts. """ - def __init__(self, identifier, label, callback, tooltip, hosts_filter=None): + def __init__( + self, identifier, label, callback, tooltip, hosts_filter=None + ): self.identifier = identifier self.label = label self.callback = callback From b6bb7a9d09fb8e5922bfcaf40ceeaea5d5344d70 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 20 Oct 2021 11:06:21 +0200 Subject: [PATCH 183/279] get host name from environment if not passed # Conflicts: # openpype/tools/experimental_tools/tools_def.py --- openpype/tools/experimental_tools/tools_def.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/tools/experimental_tools/tools_def.py b/openpype/tools/experimental_tools/tools_def.py index 13283c157a..0dcec7a871 100644 --- a/openpype/tools/experimental_tools/tools_def.py +++ b/openpype/tools/experimental_tools/tools_def.py @@ -1,3 +1,4 @@ +import os from openpype.settings import get_local_settings # Constant key under which local settings are stored @@ -53,6 +54,10 @@ class ExperimentalTools: """ def __init__(self, parent=None, host_name=None, filter_hosts=None): experimental_tools = [] + + if not host_name: + host_name = os.environ.get("AVALON_APP") + if filter_hosts is None: filter_hosts = host_name is not None From c9860711512a48d18fe9423a0b510e508c59eeb2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 20 Oct 2021 11:06:29 +0200 Subject: [PATCH 184/279] added few docstrings # Conflicts: # openpype/tools/experimental_tools/tools_def.py --- openpype/tools/experimental_tools/tools_def.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/openpype/tools/experimental_tools/tools_def.py b/openpype/tools/experimental_tools/tools_def.py index 0dcec7a871..5dd92151ca 100644 --- a/openpype/tools/experimental_tools/tools_def.py +++ b/openpype/tools/experimental_tools/tools_def.py @@ -51,19 +51,30 @@ class ExperimentalTools: To add/remove experimental tool just add/remove tool to `experimental_tools` variable in __init__ function. + Args: + parent (QtWidgets.QWidget): Parent widget for tools. + host_name (str): Name of host in which context we're now. Environment + value 'AVALON_APP' is used when not passed. + filter_hosts (bool): Should filter tools. By default is set to 'True' + when 'host_name' is passed. Is always set to 'False' if 'host_name' + is not defined. """ def __init__(self, parent=None, host_name=None, filter_hosts=None): + # Definition of experimental tools experimental_tools = [] + # Try to get host name from env variable `AVALON_APP` if not host_name: host_name = os.environ.get("AVALON_APP") + # Decide if filtering by host name should happen if filter_hosts is None: filter_hosts = host_name is not None if filter_hosts and not host_name: filter_hosts = False + # Filter tools by host name if filter_hosts: experimental_tools = [ tool From dc9f901c7a83331f4495ac8230f195b371f43a24 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 20 Oct 2021 11:18:13 +0200 Subject: [PATCH 185/279] use OpenPype stylesheet in experimental dialog --- openpype/tools/experimental_tools/dialog.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/tools/experimental_tools/dialog.py b/openpype/tools/experimental_tools/dialog.py index 237052c055..a611416efc 100644 --- a/openpype/tools/experimental_tools/dialog.py +++ b/openpype/tools/experimental_tools/dialog.py @@ -29,6 +29,7 @@ class ExperimentalDialog(QtWidgets.QDialog): self.setWindowTitle("OpenPype Experimental tools") icon = QtGui.QIcon(app_icon_path()) self.setWindowIcon(icon) + self.setStyleSheet(load_stylesheet()) empty_widget = QtWidgets.QWidget(self) From 661ba6090967d87edf639fa197ad14e420392196 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 20 Oct 2021 12:21:47 +0200 Subject: [PATCH 186/279] fix parenting in subset widget --- openpype/tools/loader/widgets.py | 70 +++++++++++++++----------------- 1 file changed, 33 insertions(+), 37 deletions(-) diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index 6b94fc6e44..d2b0a6b730 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -159,20 +159,25 @@ class SubsetWidget(QtWidgets.QWidget): grouping=enable_grouping ) proxy = SubsetFilterProxyModel() + proxy.setSourceModel(model) + proxy.setDynamicSortFilter(True) + proxy.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive) + family_proxy = FamiliesFilterProxyModel() family_proxy.setSourceModel(proxy) - subset_filter = QtWidgets.QLineEdit() + subset_filter = QtWidgets.QLineEdit(self) subset_filter.setPlaceholderText("Filter subsets..") - groupable = QtWidgets.QCheckBox("Enable Grouping") - groupable.setChecked(enable_grouping) + group_checkbox = QtWidgets.QCheckBox("Enable Grouping", self) + group_checkbox.setChecked(enable_grouping) top_bar_layout = QtWidgets.QHBoxLayout() top_bar_layout.addWidget(subset_filter) - top_bar_layout.addWidget(groupable) + top_bar_layout.addWidget(group_checkbox) - view = TreeViewSpinner() + view = TreeViewSpinner(self) + view.setModel(family_proxy) view.setObjectName("SubsetView") view.setIndentation(20) view.setStyleSheet(""" @@ -192,59 +197,50 @@ class SubsetWidget(QtWidgets.QWidget): column = model.Columns.index("time") view.setItemDelegateForColumn(column, time_delegate) - layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.addLayout(top_bar_layout) - layout.addWidget(view) - view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) view.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) view.setSortingEnabled(True) view.sortByColumn(1, QtCore.Qt.AscendingOrder) view.setAlternatingRowColors(True) - self.data = { - "delegates": { - "version": version_delegate, - "time": time_delegate - }, - "state": { - "groupable": groupable - } - } - - self.proxy = proxy - self.model = model - self.view = view - self.filter = subset_filter - self.family_proxy = family_proxy + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.addLayout(top_bar_layout) + layout.addWidget(view) # settings and connections - self.proxy.setSourceModel(self.model) - self.proxy.setDynamicSortFilter(True) - self.proxy.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive) - - self.view.setModel(self.family_proxy) - self.view.customContextMenuRequested.connect(self.on_context_menu) - for column_name, width in self.default_widths: idx = model.Columns.index(column_name) view.setColumnWidth(idx, width) + self.model = model + self.view = view + actual_project = dbcon.Session["AVALON_PROJECT"] self.on_project_change(actual_project) + view.customContextMenuRequested.connect(self.on_context_menu) + selection = view.selectionModel() selection.selectionChanged.connect(self.active_changed) version_delegate.version_changed.connect(self.version_changed) - groupable.stateChanged.connect(self.set_grouping) + group_checkbox.stateChanged.connect(self.set_grouping) - self.filter.textChanged.connect(self.proxy.setFilterRegExp) - self.filter.textChanged.connect(self.view.expandAll) + subset_filter.textChanged.connect(proxy.setFilterRegExp) + subset_filter.textChanged.connect(view.expandAll) model.refreshed.connect(self.refreshed) + self.proxy = proxy + self.family_proxy = family_proxy + + self._subset_filter = subset_filter + self._group_checkbox = group_checkbox + + self._version_delegate = version_delegate + self._time_delegate = time_delegate + self.model.refresh() def get_subsets_families(self): @@ -254,7 +250,7 @@ class SubsetWidget(QtWidgets.QWidget): self.family_proxy.setFamiliesFilter(families) def is_groupable(self): - return self.data["state"]["groupable"].checkState() + return self._group_checkbox.isChecked() def set_grouping(self, state): with tools_lib.preserve_selection(tree_view=self.view, @@ -1128,7 +1124,7 @@ class RepresentationWidget(QtWidgets.QWidget): label = QtWidgets.QLabel("Representations", self) - tree_view = DeselectableTreeView() + tree_view = DeselectableTreeView(parent=self) tree_view.setModel(proxy_model) tree_view.setAllColumnsShowFocus(True) tree_view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) From 6d0f9069e8f326bc8c40159e83117c5aabb42a1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Wed, 20 Oct 2021 12:24:20 +0200 Subject: [PATCH 187/279] fix UNC path support --- openpype/hosts/maya/plugins/publish/collect_render.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_render.py b/openpype/hosts/maya/plugins/publish/collect_render.py index 575cc2456b..d2f277329a 100644 --- a/openpype/hosts/maya/plugins/publish/collect_render.py +++ b/openpype/hosts/maya/plugins/publish/collect_render.py @@ -244,17 +244,17 @@ class CollectMayaRender(pyblish.api.ContextPlugin): # metadata file will be located in top-most common # directory. # TODO: use `os.path.commonpath()` after switch to Python 3 + publish_meta_path = os.path.normpath(publish_meta_path) common_publish_meta_path = os.path.splitdrive( publish_meta_path)[0] if common_publish_meta_path: common_publish_meta_path += os.path.sep - for part in publish_meta_path.split("/"): + for part in publish_meta_path.replace( + common_publish_meta_path, "").split(os.path.sep): common_publish_meta_path = os.path.join( common_publish_meta_path, part) if part == expected_layer_name: break - common_publish_meta_path = common_publish_meta_path.replace( - "\\", "/") self.log.info( "Publish meta path: {}".format(common_publish_meta_path)) From c0ca4ea5893b6fa267aad72011e457d93de3722b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 20 Oct 2021 12:29:19 +0200 Subject: [PATCH 188/279] add parenting in util widgets --- openpype/tools/utils/widgets.py | 79 ++++++++++++++++----------------- 1 file changed, 38 insertions(+), 41 deletions(-) diff --git a/openpype/tools/utils/widgets.py b/openpype/tools/utils/widgets.py index b9b542c123..878a9b7c86 100644 --- a/openpype/tools/utils/widgets.py +++ b/openpype/tools/utils/widgets.py @@ -35,28 +35,19 @@ class AssetWidget(QtWidgets.QWidget): self.dbcon = dbcon - self.setContentsMargins(0, 0, 0, 0) - - layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(4) - # Tree View model = AssetModel(dbcon=self.dbcon, parent=self) proxy = RecursiveSortFilterProxyModel() proxy.setSourceModel(model) proxy.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive) - view = AssetsView() + view = AssetsView(self) view.setModel(proxy) if multiselection: asset_delegate = AssetDelegate() view.setSelectionMode(view.ExtendedSelection) view.setItemDelegate(asset_delegate) - # Header - header = QtWidgets.QHBoxLayout() - icon = qtawesome.icon("fa.arrow-down", color=style.colors.light) set_current_asset_btn = QtWidgets.QPushButton(icon, "") set_current_asset_btn.setToolTip("Go to Asset from current Session") @@ -64,22 +55,28 @@ class AssetWidget(QtWidgets.QWidget): set_current_asset_btn.setVisible(False) icon = qtawesome.icon("fa.refresh", color=style.colors.light) - refresh = QtWidgets.QPushButton(icon, "") + refresh = QtWidgets.QPushButton(icon, "", parent=self) refresh.setToolTip("Refresh items") - filter = QtWidgets.QLineEdit() - filter.textChanged.connect(proxy.setFilterFixedString) - filter.setPlaceholderText("Filter assets..") + filter_input = QtWidgets.QLineEdit(self) + filter_input.setPlaceholderText("Filter assets..") - header.addWidget(filter) - header.addWidget(set_current_asset_btn) - header.addWidget(refresh) + # Header + header_layout = QtWidgets.QHBoxLayout() + header_layout.addWidget(filter_input) + header_layout.addWidget(set_current_asset_btn) + header_layout.addWidget(refresh) # Layout - layout.addLayout(header) + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(4) + layout.addLayout(header_layout) layout.addWidget(view) # Signals/Slots + filter_input.textChanged.connect(proxy.setFilterFixedString) + selection = view.selectionModel() selection.selectionChanged.connect(self.selection_changed) selection.currentChanged.connect(self.current_changed) @@ -399,30 +396,30 @@ class OptionalActionWidget(QtWidgets.QWidget): def __init__(self, label, parent=None): super(OptionalActionWidget, self).__init__(parent) - body = QtWidgets.QWidget() - body.setStyleSheet("background: transparent;") + body_widget = QtWidgets.QWidget(self) + body_widget.setStyleSheet("background: transparent;") - icon = QtWidgets.QLabel() - label = QtWidgets.QLabel(label) - option = OptionBox(body) + icon = QtWidgets.QLabel(body_widget) + label = QtWidgets.QLabel(label, body_widget) + option = OptionBox(body_widget) icon.setFixedSize(24, 16) option.setFixedSize(30, 30) - layout = QtWidgets.QHBoxLayout(body) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(2) - layout.addWidget(icon) - layout.addWidget(label) - layout.addSpacing(6) + body_layout = QtWidgets.QHBoxLayout(body_widget) + body_layout.setContentsMargins(0, 0, 0, 0) + body_layout.setSpacing(2) + body_layout.addWidget(icon) + body_layout.addWidget(label) + body_layout.addSpacing(6) layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(6, 1, 2, 1) layout.setSpacing(0) - layout.addWidget(body) + layout.addWidget(body_widget) layout.addWidget(option) - body.setMouseTracking(True) + body_widget.setMouseTracking(True) label.setMouseTracking(True) option.setMouseTracking(True) self.setMouseTracking(True) @@ -431,7 +428,7 @@ class OptionalActionWidget(QtWidgets.QWidget): self.icon = icon self.label = label self.option = option - self.body = body + self.body = body_widget # (NOTE) For removing ugly QLable shadow FX when highlighted in Nuke. # See https://stackoverflow.com/q/52838690/4145300 @@ -476,20 +473,20 @@ class OptionDialog(QtWidgets.QDialog): def create(self, options): parser = qargparse.QArgumentParser(arguments=options) - decision = QtWidgets.QWidget() - accept = QtWidgets.QPushButton("Accept") - cancel = QtWidgets.QPushButton("Cancel") + decision_widget = QtWidgets.QWidget(self) + accept_btn = QtWidgets.QPushButton("Accept", decision_widget) + cancel_btn = QtWidgets.QPushButton("Cancel", decision_widget) - layout = QtWidgets.QHBoxLayout(decision) - layout.addWidget(accept) - layout.addWidget(cancel) + decision_layout = QtWidgets.QHBoxLayout(decision_widget) + decision_layout.addWidget(accept_btn) + decision_layout.addWidget(cancel_btn) layout = QtWidgets.QVBoxLayout(self) layout.addWidget(parser) - layout.addWidget(decision) + layout.addWidget(decision_widget) - accept.clicked.connect(self.accept) - cancel.clicked.connect(self.reject) + accept_btn.clicked.connect(self.accept) + cancel_btn.clicked.connect(self.reject) parser.changed.connect(self.on_changed) def on_changed(self, argument): From 95838d120a2f834fe38031b4d3fa97b2919f0f4f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 20 Oct 2021 12:32:23 +0200 Subject: [PATCH 189/279] use openpype stylesheet --- openpype/tools/loader/app.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/tools/loader/app.py b/openpype/tools/loader/app.py index d7fa9640ac..74e121896e 100644 --- a/openpype/tools/loader/app.py +++ b/openpype/tools/loader/app.py @@ -1,10 +1,10 @@ import sys from Qt import QtWidgets, QtCore -from avalon import api, io, style, pipeline +from avalon import api, io, pipeline +from openpype.style import load_stylesheet from openpype.tools.utils.widgets import AssetWidget - from openpype.tools.utils import lib from .widgets import ( @@ -46,6 +46,7 @@ class LoaderWindow(QtWidgets.QDialog): if project_name: title += " - {}".format(project_name) self.setWindowTitle(title) + self.setStyleSheet(load_stylesheet()) # Groups config self.groups_config = lib.GroupsConfig(io) @@ -653,7 +654,6 @@ def show(debug=False, parent=None, use_context=False): with lib.application(): window = LoaderWindow(parent) - window.setStyleSheet(style.load_stylesheet()) window.show() if use_context: From a2485eb7bf581273ec0c33b7182dd19e3a33e329 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 20 Oct 2021 12:32:35 +0200 Subject: [PATCH 190/279] add parenting to asset view --- openpype/tools/utils/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/utils/views.py b/openpype/tools/utils/views.py index bed5655647..89e49fe142 100644 --- a/openpype/tools/utils/views.py +++ b/openpype/tools/utils/views.py @@ -68,8 +68,8 @@ class AssetsView(TreeViewSpinner, DeselectableTreeView): This implements a context menu. """ - def __init__(self): - super(AssetsView, self).__init__() + def __init__(self, parent=None): + super(AssetsView, self).__init__(parent) self.setIndentation(15) self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.setHeaderHidden(True) From ce755730d2a84684cf319d203754982aae8b585a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 20 Oct 2021 12:50:30 +0200 Subject: [PATCH 191/279] use WA_TranslucentBackground --- openpype/tools/loader/widgets.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index d2b0a6b730..0946826dc4 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -37,12 +37,13 @@ class OverlayFrame(QtWidgets.QFrame): super(OverlayFrame, self).__init__(parent) label_widget = QtWidgets.QLabel(label, self) + label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) + main_layout = QtWidgets.QVBoxLayout(self) main_layout.addWidget(label_widget, 1, QtCore.Qt.AlignCenter) self.label_widget = label_widget - label_widget.setStyleSheet("background: transparent;") self.setStyleSheet(( "background: rgba(0, 0, 0, 127);" "font-size: 60pt;" From 4b1f739f211d550276019bbc19d53364b22ca1e3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 20 Oct 2021 12:50:51 +0200 Subject: [PATCH 192/279] move stylesheet of SubsetView to style.css --- openpype/style/style.css | 5 +++++ openpype/tools/loader/widgets.py | 6 ------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/openpype/style/style.css b/openpype/style/style.css index 830ed85f9b..a7e82be567 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -629,3 +629,8 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { #PythonInterpreterOutput, #PythonCodeEditor { font-family: "Roboto Mono"; } + +#SubsetView::item { + padding: 5px 1px; + border: 0px; +} diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index 0946826dc4..a55cfb6e43 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -181,12 +181,6 @@ class SubsetWidget(QtWidgets.QWidget): view.setModel(family_proxy) view.setObjectName("SubsetView") view.setIndentation(20) - view.setStyleSheet(""" - QTreeView::item{ - padding: 5px 1px; - border: 0px; - } - """) view.setAllColumnsShowFocus(True) # Set view delegates From e5e502502d35c6f692224bbb32564362d383634e Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 20 Oct 2021 13:28:12 +0200 Subject: [PATCH 193/279] PYPE-1901 - updated parsing of host Payload doesn't contain all necessary data, manifests must be parsed --- .../webserver_service/webpublish_routes.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py index 3f67038fbf..920ed042dc 100644 --- a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py +++ b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py @@ -11,6 +11,7 @@ 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 import PypeLogger @@ -195,14 +196,30 @@ class WebpublisherBatchPublishEndpoint(_RestApiEndpoint): 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 content.get("files", []): + 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"])) + openpype_app = self.resource.executable args = [ openpype_app, From 524844f98b78abf3d78cfc65526c8d124c1e3f6a Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 20 Oct 2021 13:28:43 +0200 Subject: [PATCH 194/279] PYPE-1901 - updated parsing of workfile Payload doesn't contain all necessary data, manifests must be parsed --- openpype/pype_commands.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/openpype/pype_commands.py b/openpype/pype_commands.py index 3071629ee5..4bef3b7a15 100644 --- a/openpype/pype_commands.py +++ b/openpype/pype_commands.py @@ -164,9 +164,15 @@ class PypeCommands: 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")) + workfile_path = os.path.join(batch_dir, - batch_data["task"], - batch_data["files"][0]) + task_dir_name, + task_data["files"][0]) + print("workfile_path {}".format(workfile_path)) _, batch_id = os.path.split(batch_dir) From 1c179510f7d402f69949faf84b4f6abbb4192e2f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 20 Oct 2021 14:36:03 +0200 Subject: [PATCH 195/279] copied color definitions from new publisher PR --- openpype/style/__init__.py | 40 ++++- openpype/style/color_defs.py | 285 +++++++++++++++++++++++++++++++++++ 2 files changed, 322 insertions(+), 3 deletions(-) create mode 100644 openpype/style/color_defs.py diff --git a/openpype/style/__init__.py b/openpype/style/__init__.py index 0d7904d133..d763bfdc3c 100644 --- a/openpype/style/__init__.py +++ b/openpype/style/__init__.py @@ -2,6 +2,8 @@ import os import json import collections from openpype import resources +import six +from .color_defs import parse_color _STYLESHEET_CACHE = None @@ -10,6 +12,40 @@ _FONT_IDS = None current_dir = os.path.dirname(os.path.abspath(__file__)) +def get_colors_data(): + data = _get_colors_raw_data() + return data.get("color") or {} + + +def _convert_color_values_to_objects(value): + if isinstance(value, dict): + output = {} + for _key, _value in value.items(): + output[_key] = _convert_color_values_to_objects(_value) + return output + + if not isinstance(value, six.string_types): + raise TypeError(( + "Unexpected type in colors data '{}'. Expected 'str' or 'dict'." + ).format(str(type(value)))) + return parse_color(value) + + +def get_objected_colors(): + colors_data = get_colors_data() + output = {} + for key, value in colors_data.items(): + output[key] = _convert_color_values_to_objects(value) + return output + + +def _get_colors_raw_data(): + data_path = os.path.join(current_dir, "data.json") + with open(data_path, "r") as data_stream: + data = json.load(data_stream) + return data + + def _load_stylesheet(): from . import qrc_resources @@ -19,9 +55,7 @@ def _load_stylesheet(): with open(style_path, "r") as style_file: stylesheet = style_file.read() - data_path = os.path.join(current_dir, "data.json") - with open(data_path, "r") as data_stream: - data = json.load(data_stream) + data = _get_colors_raw_data() data_deque = collections.deque() for item in data.items(): diff --git a/openpype/style/color_defs.py b/openpype/style/color_defs.py new file mode 100644 index 0000000000..4d726cc3f3 --- /dev/null +++ b/openpype/style/color_defs.py @@ -0,0 +1,285 @@ +import re + + +def parse_color(value): + modified_value = value.strip().lower() + if modified_value.startswith("hsla"): + return HSLAColor(value) + + if modified_value.startswith("hsl"): + return HSLColor(value) + + if modified_value.startswith("#"): + return HEXColor(value) + + if modified_value.startswith("rgba"): + return RGBAColor(value) + + if modified_value.startswith("rgb"): + return RGBColor(value) + return UnknownColor(value) + + +def create_qcolor(*args): + from Qt import QtGui + + return QtGui.QColor(*args) + + +def min_max_check(value, min_value, max_value): + if min_value is not None and value < min_value: + raise ValueError("Minimum expected value is '{}' got '{}'".format( + min_value, value + )) + + if max_value is not None and value > max_value: + raise ValueError("Maximum expected value is '{}' got '{}'".format( + min_value, value + )) + + +def int_validation(value, min_value=None, max_value=None): + if not isinstance(value, int): + raise TypeError(( + "Invalid type of hue expected 'int' got {}" + ).format(str(type(value)))) + + min_max_check(value, min_value, max_value) + + +def float_validation(value, min_value=None, max_value=None): + if not isinstance(value, float): + raise TypeError(( + "Invalid type of hue expected 'int' got {}" + ).format(str(type(value)))) + + min_max_check(value, min_value, max_value) + + +class UnknownColor: + def __init__(self, value): + self.value = value + + def get_qcolor(self): + return create_qcolor(self.value) + + +class HEXColor: + regex = re.compile(r"[a-fA-F0-9]{3}(?:[a-fA-F0-9]{3})?$") + + def __init__(self, color_string): + red, green, blue = self.hex_to_rgb(color_string) + + self._color_string = color_string + self._red = red + self._green = green + self._blue = blue + + @property + def red(self): + return self._red + + @property + def green(self): + return self._green + + @property + def blue(self): + return self._blue + + def to_stylesheet_str(self): + return self._color_string + + @classmethod + def hex_to_rgb(cls, value): + hex_value = value.lstrip("#") + if not cls.regex.match(hex_value): + raise ValueError("\"{}\" is not a valid HEX code.".format(value)) + + output = [] + if len(hex_value) == 3: + for char in hex_value: + output.append(int(char * 2, 16)) + else: + for idx in range(3): + start_idx = idx * 2 + output.append(int(hex_value[start_idx:start_idx + 2], 16)) + return output + + def get_qcolor(self): + return create_qcolor(self.red, self.green, self.blue) + + +class RGBColor: + def __init__(self, value): + modified_color = value.lower().strip() + content = modified_color.rstrip(")").lstrip("rgb(") + red_str, green_str, blue_str = ( + item.strip() for item in content.split(",") + ) + red = int(red_str) + green = int(green_str) + blue = int(blue_str) + + int_validation(red, 0, 255) + int_validation(green, 0, 255) + int_validation(blue, 0, 255) + + self._red = red + self._green = green + self._blue = blue + + @property + def red(self): + return self._red + + @property + def green(self): + return self._green + + @property + def blue(self): + return self._blue + + def get_qcolor(self): + return create_qcolor(self.red, self.green, self.blue) + + +class RGBAColor: + def __init__(self, value): + modified_color = value.lower().strip() + content = modified_color.rstrip(")").lstrip("rgba(") + red_str, green_str, blue_str, alpha_str = ( + item.strip() for item in content.split(",") + ) + red = int(red_str) + green = int(green_str) + blue = int(blue_str) + alpha = int(alpha_str) + + int_validation(red, 0, 255) + int_validation(green, 0, 255) + int_validation(blue, 0, 255) + int_validation(alpha, 0, 255) + + self._red = red + self._green = green + self._blue = blue + self._alpha = alpha + + @property + def red(self): + return self._red + + @property + def green(self): + return self._green + + @property + def blue(self): + return self._blue + + @property + def alpha(self): + return self._alpha + + def get_qcolor(self): + return create_qcolor(self.red, self.green, self.blue, self.alpha) + + +class HSLColor: + def __init__(self, value): + modified_color = value.lower().strip() + content = modified_color.rstrip(")").lstrip("hsl(") + hue_str, sat_str, light_str = ( + item.strip() for item in content.split(",") + ) + hue = int(hue_str) % 360 + if "%" in sat_str: + sat = float(sat_str.rstrip("%")) / 100 + else: + sat = float(sat) + + if "%" in light_str: + light = float(light_str.rstrip("%")) / 100 + else: + light = float(light_str) + + int_validation(hue, 0, 360) + float_validation(sat, 0, 1) + float_validation(light, 0, 1) + + self._hue = hue + self._saturation = sat + self._light = light + + @property + def hue(self): + return self._hue + + @property + def saturation(self): + return self._saturation + + @property + def light(self): + return self._light + + def get_qcolor(self): + color = create_qcolor() + color.setHslF(self.hue / 360, self.saturation, self.light) + return color + + +class HSLAColor: + def __init__(self, value): + modified_color = value.lower().strip() + content = modified_color.rstrip(")").lstrip("hsla(") + hue_str, sat_str, light_str, alpha_str = ( + item.strip() for item in content.split(",") + ) + hue = int(hue_str) % 360 + if "%" in sat_str: + sat = float(sat_str.rstrip("%")) / 100 + else: + sat = float(sat) + + if "%" in light_str: + light = float(light_str.rstrip("%")) / 100 + else: + light = float(light_str) + alpha = float(alpha_str) + + if isinstance(alpha, int): + alpha = float(alpha) + + int_validation(hue, 0, 360) + float_validation(sat, 0, 1) + float_validation(light, 0, 1) + float_validation(alpha, 0, 1) + + self._hue = hue + self._saturation = sat + self._light = light + self._alpha = alpha + + @property + def hue(self): + return self._hue + + @property + def saturation(self): + return self._saturation + + @property + def light(self): + return self._light + + @property + def alpha(self): + return self._alpha + + def get_qcolor(self): + color = create_qcolor() + color.setHslF(self.hue / 360, self.saturation, self.light, self.alpha) + return color From 35435fedba310293bc3d0f5c2c7979b5caccc152 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 20 Oct 2021 15:23:37 +0200 Subject: [PATCH 196/279] use rgba instead of hsla --- openpype/style/data.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/openpype/style/data.json b/openpype/style/data.json index a58829d946..5cac7e07db 100644 --- a/openpype/style/data.json +++ b/openpype/style/data.json @@ -28,7 +28,7 @@ "bg": "#2C313A", "bg-inputs": "#21252B", "bg-buttons": "#434a56", - "bg-button-hover": "hsla(220, 14%, 70%, .3)", + "bg-button-hover": "rgba(168, 175, 189, 0.3)", "bg-inputs-disabled": "#2C313A", "bg-buttons-disabled": "#434a56", @@ -38,15 +38,15 @@ "bg-view": "#21252B", "bg-view-header": "#373D48", - "bg-view-hover": "hsla(220, 14%, 70%, .3)", + "bg-view-hover": "rgba(168, 175, 189, .3)", "bg-view-alternate": "rgb(36, 42, 50)", "bg-view-disabled": "#434a56", "bg-view-alternate-disabled": "#2C313A", - "bg-view-selection": "hsla(200, 60%, 60%, .4)", - "bg-view-selection-hover": "hsla(200, 60%, 60%, .8)", + "bg-view-selection": "rgba(92, 173, 214, .4)", + "bg-view-selection-hover": "rgba(92, 173, 214, .8)", "border": "#373D48", - "border-hover": "hsla(220, 14%, 70%, .3)", + "border-hover": "rgba(168, 175, 189, .3)", "border-focus": "hsl(200, 60%, 60%)" } } From 66a1ba4033deb115db6627ec60fafaa0a9470bad Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 20 Oct 2021 15:24:03 +0200 Subject: [PATCH 197/279] added RepresentationView to stylesheet --- openpype/style/style.css | 2 +- openpype/tools/loader/widgets.py | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/openpype/style/style.css b/openpype/style/style.css index a7e82be567..6507cbe63b 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -630,7 +630,7 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { font-family: "Roboto Mono"; } -#SubsetView::item { +#SubsetView::item, #RepresentationView:item { padding: 5px 1px; border: 0px; } diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index a55cfb6e43..f6ba200eff 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -1120,6 +1120,7 @@ class RepresentationWidget(QtWidgets.QWidget): label = QtWidgets.QLabel("Representations", self) tree_view = DeselectableTreeView(parent=self) + tree_view.setObjectName("RepresentationView") tree_view.setModel(proxy_model) tree_view.setAllColumnsShowFocus(True) tree_view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) @@ -1129,12 +1130,6 @@ class RepresentationWidget(QtWidgets.QWidget): tree_view.sortByColumn(1, QtCore.Qt.AscendingOrder) tree_view.setAlternatingRowColors(True) tree_view.setIndentation(20) - tree_view.setStyleSheet(""" - QTreeView::item{ - padding: 5px 1px; - border: 0px; - } - """) tree_view.collapseAll() for column_name, width in self.default_widths: From dd0ed45d53efc57de2b7fabb3b08be495d70257f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 20 Oct 2021 15:36:33 +0200 Subject: [PATCH 198/279] changed order of setting stylesheet --- openpype/tools/loader/app.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/tools/loader/app.py b/openpype/tools/loader/app.py index 74e121896e..b29f0970ca 100644 --- a/openpype/tools/loader/app.py +++ b/openpype/tools/loader/app.py @@ -46,7 +46,6 @@ class LoaderWindow(QtWidgets.QDialog): if project_name: title += " - {}".format(project_name) self.setWindowTitle(title) - self.setStyleSheet(load_stylesheet()) # Groups config self.groups_config = lib.GroupsConfig(io) @@ -191,6 +190,8 @@ class LoaderWindow(QtWidgets.QDialog): main_splitter.setSizes([250, 850, 200]) self.resize(1300, 700) + self.setStyleSheet(load_stylesheet()) + def resizeEvent(self, event): super(LoaderWindow, self).resizeEvent(event) self._overlay_frame.resize(self.size()) From 740328ef851cd5cc8aa51ec752256b586e27631e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 20 Oct 2021 15:37:08 +0200 Subject: [PATCH 199/279] added parents to delegates --- openpype/tools/loader/widgets.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index f6ba200eff..9a639c3b85 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -182,22 +182,21 @@ class SubsetWidget(QtWidgets.QWidget): view.setObjectName("SubsetView") view.setIndentation(20) view.setAllColumnsShowFocus(True) - - # Set view delegates - version_delegate = VersionDelegate(self.dbcon) - column = model.Columns.index("version") - view.setItemDelegateForColumn(column, version_delegate) - - time_delegate = PrettyTimeDelegate() - column = model.Columns.index("time") - view.setItemDelegateForColumn(column, time_delegate) - view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) view.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) view.setSortingEnabled(True) view.sortByColumn(1, QtCore.Qt.AscendingOrder) view.setAlternatingRowColors(True) + # Set view delegates + version_delegate = VersionDelegate(self.dbcon, view) + column = model.Columns.index("version") + view.setItemDelegateForColumn(column, version_delegate) + + time_delegate = PrettyTimeDelegate(view) + column = model.Columns.index("time") + view.setItemDelegateForColumn(column, time_delegate) + layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.addLayout(top_bar_layout) From b888240bdb9b66e224e09bf445ba8b815f3e0837 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 20 Oct 2021 15:37:42 +0200 Subject: [PATCH 200/279] use better margins for optional items --- openpype/tools/utils/widgets.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/tools/utils/widgets.py b/openpype/tools/utils/widgets.py index 878a9b7c86..de75de705b 100644 --- a/openpype/tools/utils/widgets.py +++ b/openpype/tools/utils/widgets.py @@ -407,14 +407,13 @@ class OptionalActionWidget(QtWidgets.QWidget): option.setFixedSize(30, 30) body_layout = QtWidgets.QHBoxLayout(body_widget) - body_layout.setContentsMargins(0, 0, 0, 0) + body_layout.setContentsMargins(4, 0, 4, 0) body_layout.setSpacing(2) body_layout.addWidget(icon) body_layout.addWidget(label) - body_layout.addSpacing(6) layout = QtWidgets.QHBoxLayout(self) - layout.setContentsMargins(6, 1, 2, 1) + layout.setContentsMargins(2, 1, 2, 1) layout.setSpacing(0) layout.addWidget(body_widget) layout.addWidget(option) From 7d70d22e2d76bdb0a7aa6c936e0c8284b7c63839 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 20 Oct 2021 17:31:21 +0200 Subject: [PATCH 201/279] moved set of stylesheet after show of tool --- openpype/tools/utils/host_tools.py | 48 ++++++++++++++++++------------ 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/openpype/tools/utils/host_tools.py b/openpype/tools/utils/host_tools.py index ee184ccf2d..599c25d6c8 100644 --- a/openpype/tools/utils/host_tools.py +++ b/openpype/tools/utils/host_tools.py @@ -40,7 +40,6 @@ class HostToolsHelper: def get_workfiles_tool(self, parent): """Create, cache and return workfiles tool window.""" if self._workfiles_tool is None: - from avalon import style from openpype.tools.workfiles.app import ( Window, validate_host_requirements ) @@ -49,13 +48,14 @@ class HostToolsHelper: validate_host_requirements(host) workfiles_window = Window(parent=parent) - workfiles_window.setStyleSheet(style.load_stylesheet()) self._workfiles_tool = workfiles_window return self._workfiles_tool def show_workfiles(self, parent=None, use_context=None, save=None): """Workfiles tool for changing context and saving workfiles.""" + from avalon import style + if use_context is None: use_context = True @@ -79,24 +79,30 @@ class HostToolsHelper: # Pull window to the front. workfiles_tool.raise_() workfiles_tool.activateWindow() + workfiles_tool.setStyleSheet(style.load_stylesheet()) def get_loader_tool(self, parent): """Create, cache and return loader tool window.""" if self._loader_tool is None: - from avalon import style from openpype.tools.loader import LoaderWindow loader_window = LoaderWindow(parent=parent or self._parent) - loader_window.setStyleSheet(style.load_stylesheet()) self._loader_tool = loader_window return self._loader_tool def show_loader(self, parent=None, use_context=None): """Loader tool for loading representations.""" + from avalon import style + + loader_tool = self.get_loader_tool(parent) + + loader_tool.show() + loader_tool.raise_() + loader_tool.activateWindow() + if use_context is None: use_context = False - loader_tool = self.get_loader_tool(parent) if use_context: context = {"asset": avalon.api.Session["AVALON_ASSET"]} @@ -104,29 +110,28 @@ class HostToolsHelper: else: loader_tool.refresh() - loader_tool.show() - loader_tool.raise_() - loader_tool.activateWindow() - loader_tool.refresh() + loader_tool.setStyleSheet(style.load_stylesheet()) def get_creator_tool(self, parent): """Create, cache and return creator tool window.""" if self._creator_tool is None: - from avalon import style from avalon.tools.creator.app import Window creator_window = Window(parent=parent or self._parent) - creator_window.setStyleSheet(style.load_stylesheet()) self._creator_tool = creator_window return self._creator_tool def show_creator(self, parent=None): """Show tool to create new instantes for publishing.""" + from avalon import style + creator_tool = self.get_creator_tool(parent) creator_tool.refresh() creator_tool.show() + creator_tool.setStyleSheet(style.load_stylesheet()) + # Pull window to the front. creator_tool.raise_() creator_tool.activateWindow() @@ -134,20 +139,22 @@ 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 import style from avalon.tools.subsetmanager import Window subset_manager_window = Window(parent=parent or self._parent) - subset_manager_window.setStyleSheet(style.load_stylesheet()) 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() @@ -155,20 +162,21 @@ 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 import style from avalon.tools.sceneinventory.app import Window scene_inventory_window = Window(parent=parent or self._parent) - scene_inventory_window.setStyleSheet(style.load_stylesheet()) 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_() @@ -177,24 +185,25 @@ class HostToolsHelper: def get_library_loader_tool(self, parent): """Create, cache and return library loader tool window.""" if self._library_loader_tool is None: - from avalon import style from openpype.tools.libraryloader import LibraryLoaderWindow library_window = LibraryLoaderWindow( parent=parent or self._parent ) - library_window.setStyleSheet(style.load_stylesheet()) self._library_loader_tool = library_window return self._library_loader_tool def show_library_loader(self, parent=None): """Loader tool for loading representations from library project.""" + from avalon import style + library_loader_tool = self.get_library_loader_tool(parent) library_loader_tool.show() library_loader_tool.raise_() library_loader_tool.activateWindow() library_loader_tool.refresh() + library_loader_tool.setStyleSheet(style.load_stylesheet()) def show_publish(self, parent=None): """Publish UI.""" @@ -205,18 +214,19 @@ class HostToolsHelper: def get_look_assigner_tool(self, parent): """Create, cache and return look assigner tool window.""" if self._look_assigner_tool is None: - from avalon import style import mayalookassigner mayalookassigner_window = mayalookassigner.App(parent) - mayalookassigner_window.setStyleSheet(style.load_stylesheet()) self._look_assigner_tool = mayalookassigner_window return self._look_assigner_tool def show_look_assigner(self, parent=None): """Look manager is Maya specific tool for look management.""" + from avalon import style + look_assigner_tool = self.get_look_assigner_tool(parent) look_assigner_tool.show() + look_assigner_tool.setStyleSheet(style.load_stylesheet()) def get_tool_by_name(self, tool_name, parent=None, *args, **kwargs): """Show tool by it's name. From 961a602e1c0d2bdd33c2b7b8e3fbe2ee762db5b5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 20 Oct 2021 17:56:42 +0200 Subject: [PATCH 202/279] fix hovering stylesheet of optional action --- openpype/style/style.css | 8 ++++++ openpype/tools/utils/widgets.py | 47 ++++++++++++++++++--------------- 2 files changed, 34 insertions(+), 21 deletions(-) diff --git a/openpype/style/style.css b/openpype/style/style.css index 6507cbe63b..948ee8c7b7 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -634,3 +634,11 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { padding: 5px 1px; border: 0px; } + +#OptionalActionBody, #OptionalActionOption { + background: transparent; +} + +#OptionalActionBody[state="hover"], #OptionalActionOption[state="hover"] { + background: {color:bg-view-hover}; +} diff --git a/openpype/tools/utils/widgets.py b/openpype/tools/utils/widgets.py index de75de705b..15bcbeff90 100644 --- a/openpype/tools/utils/widgets.py +++ b/openpype/tools/utils/widgets.py @@ -310,7 +310,6 @@ class OptionalMenu(QtWidgets.QMenu): actions that were instances of `QtWidgets.QWidgetAction`. """ - def mouseReleaseEvent(self, event): """Emit option clicked signal if mouse released on it""" active = self.actionAt(event.pos()) @@ -349,6 +348,7 @@ class OptionalAction(QtWidgets.QWidgetAction): self.use_option = use_option self.option_tip = "" self.optioned = False + self.widget = None def createWidget(self, parent): widget = OptionalActionWidget(self.label, parent) @@ -374,20 +374,10 @@ class OptionalAction(QtWidgets.QWidgetAction): self.optioned = True def set_highlight(self, state, global_pos=None): - body = self.widget.body - option = self.widget.option - - role = QtGui.QPalette.Highlight if state else QtGui.QPalette.Window - body.setBackgroundRole(role) - body.setAutoFillBackground(state) - - if not self.use_option: - return - - state = option.is_hovered(global_pos) - role = QtGui.QPalette.Highlight if state else QtGui.QPalette.Window - option.setBackgroundRole(role) - option.setAutoFillBackground(state) + option_state = False + if self.use_option: + option_state = self.widget.option.is_hovered(global_pos) + self.widget.set_hover_properties(state, option_state) class OptionalActionWidget(QtWidgets.QWidget): @@ -397,11 +387,15 @@ class OptionalActionWidget(QtWidgets.QWidget): super(OptionalActionWidget, self).__init__(parent) body_widget = QtWidgets.QWidget(self) - body_widget.setStyleSheet("background: transparent;") + body_widget.setObjectName("OptionalActionBody") icon = QtWidgets.QLabel(body_widget) label = QtWidgets.QLabel(label, body_widget) + # (NOTE) For removing ugly QLable shadow FX when highlighted in Nuke. + # See https://stackoverflow.com/q/52838690/4145300 + label.setStyle(QtWidgets.QStyleFactory.create("Plastique")) option = OptionBox(body_widget) + option.setObjectName("OptionalActionOption") icon.setFixedSize(24, 16) option.setFixedSize(30, 30) @@ -429,9 +423,22 @@ class OptionalActionWidget(QtWidgets.QWidget): self.option = option self.body = body_widget - # (NOTE) For removing ugly QLable shadow FX when highlighted in Nuke. - # See https://stackoverflow.com/q/52838690/4145300 - label.setStyle(QtWidgets.QStyleFactory.create("Plastique")) + def set_hover_properties(self, hovered, option_hovered): + body_state = "" + option_state = "" + if hovered: + body_state = "hover" + + if option_hovered: + option_state = "hover" + + if self.body.property("state") != body_state: + self.body.setProperty("state", body_state) + self.body.style().polish(self.body) + + if self.option.property("state") != option_state: + self.option.setProperty("state", option_state) + self.option.style().polish(self.option) def setIcon(self, icon): pixmap = icon.pixmap(16, 16) @@ -452,8 +459,6 @@ class OptionBox(QtWidgets.QLabel): pixmap = icon.pixmap(18, 18) self.setPixmap(pixmap) - self.setStyleSheet("background: transparent;") - def is_hovered(self, global_pos): if global_pos is None: return False From bcb27d5aebe6ccc8d5cae84dd4177f8d0c4aa7de Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 20 Oct 2021 18:24:18 +0200 Subject: [PATCH 203/279] changed new thumbnail --- .../tools/loader/images/default_thumbnail.png | Bin 4018 -> 5118 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/openpype/tools/loader/images/default_thumbnail.png b/openpype/tools/loader/images/default_thumbnail.png index 97bd958e0daf01e740db9f426e1f9b3e22484c87..adea862e5b7169f8279bd398226a2fe6283e4925 100644 GIT binary patch literal 5118 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 aQ8TBK`_#VgnlokXr%hW!U;VAB9rQ1Cc_|D4 From acf88c7f16488da71ec5187025382a80c946919a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 20 Oct 2021 18:35:09 +0200 Subject: [PATCH 204/279] alpha can have float number in rgba --- openpype/style/color_defs.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/style/color_defs.py b/openpype/style/color_defs.py index 4d726cc3f3..3f504a9d3b 100644 --- a/openpype/style/color_defs.py +++ b/openpype/style/color_defs.py @@ -155,7 +155,10 @@ class RGBAColor: red = int(red_str) green = int(green_str) blue = int(blue_str) - alpha = int(alpha_str) + if "." in alpha_str: + alpha = int(float(alpha_str) * 100) + else: + alpha = int(alpha_str) int_validation(red, 0, 255) int_validation(green, 0, 255) From 0f62d59d5d89ecc19375bbc3dfa1c410c6e0efdb Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 20 Oct 2021 18:35:33 +0200 Subject: [PATCH 205/279] use colors in asset delegate from openpype style --- openpype/style/data.json | 10 +++++++++- openpype/tools/utils/delegates.py | 23 ++++++++++++++++++++--- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/openpype/style/data.json b/openpype/style/data.json index 5cac7e07db..143c6695af 100644 --- a/openpype/style/data.json +++ b/openpype/style/data.json @@ -47,6 +47,14 @@ "border": "#373D48", "border-hover": "rgba(168, 175, 189, .3)", - "border-focus": "hsl(200, 60%, 60%)" + "border-focus": "hsl(200, 60%, 60%)", + + "loader": { + "asset-view": { + "selected": "rgba(168, 175, 189, 0.6)", + "hover": "rgba(168, 175, 189, 0.3)", + "selected-hover": "rgba(168, 175, 189, 0.7)" + } + } } } diff --git a/openpype/tools/utils/delegates.py b/openpype/tools/utils/delegates.py index 1827bc7e9b..96353c44c6 100644 --- a/openpype/tools/utils/delegates.py +++ b/openpype/tools/utils/delegates.py @@ -7,6 +7,7 @@ import Qt from Qt import QtWidgets, QtGui, QtCore from avalon.lib import HeroVersionType +from openpype.style import get_objected_colors from .models import ( AssetModel, TreeModel @@ -24,6 +25,19 @@ log = logging.getLogger(__name__) class AssetDelegate(QtWidgets.QItemDelegate): bar_height = 3 + def __init__(self, *args, **kwargs): + super(AssetDelegate, self).__init__(*args, **kwargs) + asset_view_colors = get_objected_colors()["loader"]["asset-view"] + self._selected_color = ( + asset_view_colors["selected"].get_qcolor() + ) + self._hover_color = ( + asset_view_colors["hover"].get_qcolor() + ) + self._selected_hover_color = ( + asset_view_colors["selected-hover"].get_qcolor() + ) + def sizeHint(self, option, index): result = super(AssetDelegate, self).sizeHint(option, index) height = result.height() @@ -66,17 +80,20 @@ class AssetDelegate(QtWidgets.QItemDelegate): counter += 1 # Background - bg_color = QtGui.QColor(60, 60, 60) if option.state & QtWidgets.QStyle.State_Selected: if len(subset_colors) == 0: item_rect.setTop(item_rect.top() + (self.bar_height / 2)) + if option.state & QtWidgets.QStyle.State_MouseOver: - bg_color.setRgb(70, 70, 70) + bg_color = self._selected_hover_color + else: + bg_color = self._selected_color else: item_rect.setTop(item_rect.top() + (self.bar_height / 2)) if option.state & QtWidgets.QStyle.State_MouseOver: - bg_color.setAlpha(100) + bg_color = self._hover_color else: + bg_color = QtGui.QColor() bg_color.setAlpha(0) # When not needed to do a rounded corners (easier and without From ba97477f1102551bb32cecbf0747a9d064238d5c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 20 Oct 2021 19:26:19 +0200 Subject: [PATCH 206/279] fix resources for maya --- openpype/style/pyside2_resources.py | 929 ++++++++++++++-------------- 1 file changed, 453 insertions(+), 476 deletions(-) diff --git a/openpype/style/pyside2_resources.py b/openpype/style/pyside2_resources.py index ee68a74b8e..c7328e7c91 100644 --- a/openpype/style/pyside2_resources.py +++ b/openpype/style/pyside2_resources.py @@ -1,24 +1,15 @@ -# Resource object code (Python 3) -# Created by: object code -# Created by: The Resource Compiler for Qt version 5.15.2 +# -*- coding: utf-8 -*- + +# Resource object code +# +# Created: Wed Oct 20 19:25:24 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\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\xa6\ \x89\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ @@ -45,31 +36,6 @@ 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\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\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\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\ @@ -83,158 +49,43 @@ HYs\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ 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\x9e\ +\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\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\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\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\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\ +\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\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\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\x07\xdd\ \x89\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ @@ -363,6 +214,289 @@ 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\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\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\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\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\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\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\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\x070\ +\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\x07\xad\ \x89\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ @@ -488,6 +622,55 @@ 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\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\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\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\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\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\xa6\ \x89\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ @@ -501,186 +684,6 @@ HYs\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ 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\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\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\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\x070\ -\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\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\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\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\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\ " qt_resource_name = b"\ @@ -693,61 +696,15 @@ qt_resource_name = b"\ \x00i\ \x00m\x00a\x00g\x00e\x00s\ \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\x12\ -\x01.\x03'\ +\x03'rg\ \x00c\ -\x00o\x00m\x00b\x00o\x00b\x00o\x00x\x00_\x00a\x00r\x00r\x00o\x00w\x00.\x00p\x00n\ -\x00g\ -\x00\x0e\ -\x04\xa2\xfc\xa7\ -\x00d\ -\x00o\x00w\x00n\x00_\x00a\x00r\x00r\x00o\x00w\x00.\x00p\x00n\x00g\ +\x00o\x00m\x00b\x00o\x00b\x00o\x00x\x00_\x00a\x00r\x00r\x00o\x00w\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\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\ -\x00\xb8\x8c\x07\ -\x00l\ -\x00e\x00f\x00t\x00_\x00a\x00r\x00r\x00o\x00w\x00_\x00o\x00n\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\x0c\ -\x06\xe6\xe6g\ -\x00u\ -\x00p\x00_\x00a\x00r\x00r\x00o\x00w\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\x14\ -\x04^-\xa7\ -\x00b\ -\x00r\x00a\x00n\x00c\x00h\x00_\x00c\x00l\x00o\x00s\x00e\x00d\x00_\x00o\x00n\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\x0e\ \x0e\xde\xfa\xc7\ \x00l\ @@ -757,87 +714,107 @@ qt_resource_name = b"\ \x00d\ \x00o\x00w\x00n\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\x12\ -\x05\x8f\x9d\x07\ -\x00b\ -\x00r\x00a\x00n\x00c\x00h\x00_\x00o\x00p\x00e\x00n\x00_\x00o\x00n\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\x12\ \x03\x8d\x04G\ \x00r\ \x00i\x00g\x00h\x00t\x00_\x00a\x00r\x00r\x00o\x00w\x00_\x00o\x00n\x00.\x00p\x00n\ \x00g\ -\x00\x15\ -\x03'rg\ +\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\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\x0f\ +\x02\x9f\x05\x87\ +\x00r\ +\x00i\x00g\x00h\x00t\x00_\x00a\x00r\x00r\x00o\x00w\x00.\x00p\x00n\x00g\ +\x00\x0f\ +\x06S%\xa7\ +\x00b\ +\x00r\x00a\x00n\x00c\x00h\x00_\x00o\x00p\x00e\x00n\x00.\x00p\x00n\x00g\ +\x00\x12\ +\x01.\x03'\ \x00c\ -\x00o\x00m\x00b\x00o\x00b\x00o\x00x\x00_\x00a\x00r\x00r\x00o\x00w\x00_\x00o\x00n\ -\x00.\x00p\x00n\x00g\ +\x00o\x00m\x00b\x00o\x00b\x00o\x00x\x00_\x00a\x00r\x00r\x00o\x00w\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\x12\ +\x05\x8f\x9d\x07\ +\x00b\ +\x00r\x00a\x00n\x00c\x00h\x00_\x00o\x00p\x00e\x00n\x00_\x00o\x00n\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\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\x0f\ +\x01s\x8b\x07\ +\x00u\ +\x00p\x00_\x00a\x00r\x00r\x00o\x00w\x00_\x00o\x00n\x00.\x00p\x00n\x00g\ +\x00\x0c\ +\x06\xe6\xe6g\ +\x00u\ +\x00p\x00_\x00a\x00r\x00r\x00o\x00w\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\x11\ +\x00\xb8\x8c\x07\ +\x00l\ +\x00e\x00f\x00t\x00_\x00a\x00r\x00r\x00o\x00w\x00_\x00o\x00n\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\x00\x00\x00\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ -\x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x00\x16\x00\x02\x00\x00\x00\x13\x00\x00\x00\x03\ -\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x01\x16\x00\x00\x00\x00\x00\x01\x00\x00\x03C\ -\x00\x00\x01vA\x9d\xa25\ -\x00\x00\x02P\x00\x00\x00\x00\x00\x01\x00\x00\x1d!\ -\x00\x00\x01vA\x9d\xa25\ -\x00\x00\x00X\x00\x00\x00\x00\x00\x01\x00\x00\x00\xa3\ -\x00\x00\x01y\xb4r\xcc\x9c\ -\x00\x00\x01>\x00\x00\x00\x00\x00\x01\x00\x00\x03\xed\ -\x00\x00\x01vA\x9d\xa29\ -\x00\x00\x02x\x00\x00\x00\x00\x00\x01\x00\x00\x1d\xca\ -\x00\x00\x01vA\x9d\xa27\ -\x00\x00\x03$\x00\x00\x00\x00\x00\x01\x00\x00&\xf0\ -\x00\x00\x01y\xb4r\xcc\x9c\ -\x00\x00\x00\xa4\x00\x00\x00\x00\x00\x01\x00\x00\x01\xf6\ -\x00\x00\x01y\xb4r\xcc\x9c\ -\x00\x00\x02\xfa\x00\x00\x00\x00\x00\x01\x00\x00&L\ -\x00\x00\x01vA\x9d\xa27\ -\x00\x00\x00\xe0\x00\x00\x00\x00\x00\x01\x00\x00\x02\x9f\ -\x00\x00\x01vA\x9d\xa27\ -\x00\x00\x01\xd8\x00\x00\x00\x00\x00\x01\x00\x00\x0c\xe5\ -\x00\x00\x01y\xc2\x05+`\ -\x00\x00\x00\x82\x00\x00\x00\x00\x00\x01\x00\x00\x01M\ -\x00\x00\x01vA\x9d\xa25\ -\x00\x00\x02\x9c\x00\x00\x00\x00\x00\x01\x00\x00\x1en\ -\x00\x00\x01y\xc1\xfc\x16\x91\ -\x00\x00\x01\x80\x00\x00\x00\x00\x00\x01\x00\x00\x051\ -\x00\x00\x01y\xc1\xf9Kx\ -\x00\x00\x01b\x00\x00\x00\x00\x00\x01\x00\x00\x04\x8f\ -\x00\x00\x01vA\x9d\xa29\ -\x00\x00\x02\x06\x00\x00\x00\x00\x00\x01\x00\x00\x14\xc6\ -\x00\x00\x01y\xc2\x05\x91*\ -\x00\x00\x01\xa4\x00\x00\x00\x00\x00\x01\x00\x00\x0c;\ -\x00\x00\x01vA\x9d\xa25\ -\x00\x00\x02\xc6\x00\x00\x00\x00\x00\x01\x00\x00%\xa2\ -\x00\x00\x01vA\x9d\xa25\ -\x00\x00\x02.\x00\x00\x00\x00\x00\x01\x00\x00\x1cw\ -\x00\x00\x01vA\x9d\xa25\ +\x00\x00\x03,\x00\x00\x00\x00\x00\x01\x00\x00&\xf0\ +\x00\x00\x00\xb6\x00\x00\x00\x00\x00\x01\x00\x00\x01\xfd\ +\x00\x00\x01\xe2\x00\x00\x00\x00\x00\x01\x00\x00\x14&\ +\x00\x00\x02\xb6\x00\x00\x00\x00\x00\x01\x00\x00%\x02\ +\x00\x00\x01\x9a\x00\x00\x00\x00\x00\x01\x00\x00\x0cx\ \x00\x00\x00(\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ -\x00\x00\x01vA\x9d\xa29\ +\x00\x00\x00X\x00\x00\x00\x00\x00\x01\x00\x00\x00\xaa\ +\x00\x00\x01\x0e\x00\x00\x00\x00\x00\x01\x00\x00\x03I\ +\x00\x00\x02\x80\x00\x00\x00\x00\x00\x01\x00\x00$^\ +\x00\x00\x018\x00\x00\x00\x00\x00\x01\x00\x00\x03\xed\ +\x00\x00\x02\x0c\x00\x00\x00\x00\x00\x01\x00\x00\x14\xd0\ +\x00\x00\x02.\x00\x00\x00\x00\x00\x01\x00\x00\x15y\ +\x00\x00\x01\xbe\x00\x00\x00\x00\x00\x01\x00\x00\x0d\x1c\ +\x00\x00\x02\xda\x00\x00\x00\x00\x00\x01\x00\x00%\xa4\ +\x00\x00\x02X\x00\x00\x00\x00\x00\x01\x00\x00\x1c\xad\ +\x00\x00\x01f\x00\x00\x00\x00\x00\x01\x00\x00\x0b\xce\ +\x00\x00\x02\xf8\x00\x00\x00\x00\x00\x01\x00\x00&F\ +\x00\x00\x00\x94\x00\x00\x00\x00\x00\x01\x00\x00\x01S\ +\x00\x00\x00\xde\x00\x00\x00\x00\x00\x01\x00\x00\x02\xa6\ " - def qInitResources(): - QtCore.qRegisterResourceData( - 0x03, qt_resource_struct, qt_resource_name, qt_resource_data - ) - + QtCore.qRegisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) def qCleanupResources(): - QtCore.qUnregisterResourceData( - 0x03, qt_resource_struct, qt_resource_name, qt_resource_data - ) + QtCore.qUnregisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) + +qInitResources() From 8d441da917e6601cb6a249937e3693e436a7c6b4 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Thu, 21 Oct 2021 09:59:43 +0200 Subject: [PATCH 207/279] update google analytics --- website/docusaurus.config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js index 3ce1cde060..ddbcfd9ce7 100644 --- a/website/docusaurus.config.js +++ b/website/docusaurus.config.js @@ -116,8 +116,8 @@ module.exports = { // Optional: Algolia search parameters searchParameters: {}, }, - googleAnalytics: { - trackingID: 'G-HHJZ9VF0FG', + gtag: { + trackingID: 'G-DTKXMFENFY', // Optional fields. anonymizeIP: false, // Should IPs be anonymized? }, From 62c411f585ee59c5fc53c22f7a38bde825b56e52 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Thu, 21 Oct 2021 10:06:32 +0200 Subject: [PATCH 208/279] fix typo in ci --- tools/ci_tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/ci_tools.py b/tools/ci_tools.py index 337b19a346..e5ca0c2c28 100644 --- a/tools/ci_tools.py +++ b/tools/ci_tools.py @@ -27,7 +27,7 @@ def get_release_type_github(Log, github_token): return "minor" if any(label in labels for label in patch_labels): - return "path" + return "patch" return None From 08971bb73ccd34e2f787d6c3c1042e2a9fbc9ca7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Oct 2021 08:23:02 +0000 Subject: [PATCH 209/279] Bump pillow from 8.2.0 to 8.3.2 Bumps [pillow](https://github.com/python-pillow/Pillow) from 8.2.0 to 8.3.2. - [Release notes](https://github.com/python-pillow/Pillow/releases) - [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst) - [Commits](https://github.com/python-pillow/Pillow/compare/8.2.0...8.3.2) --- updated-dependencies: - dependency-name: pillow dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- poetry.lock | 94 +++++++++++++++++++++++++++++++------------------- pyproject.toml | 2 +- 2 files changed, 59 insertions(+), 37 deletions(-) diff --git a/poetry.lock b/poetry.lock index e5f5919a01..36105f4213 100644 --- a/poetry.lock +++ b/poetry.lock @@ -782,7 +782,7 @@ six = "*" [[package]] name = "pillow" -version = "8.2.0" +version = "8.3.2" description = "Python Imaging Library (Fork)" category = "main" optional = false @@ -1538,7 +1538,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pyt [metadata] lock-version = "1.1" python-versions = "3.7.*" -content-hash = "ff2bfa35a7304378917a0c25d7d7af9f81a130288d95789bdf7429f071e80b69" +content-hash = "fb6db80d126fe7ef2d1d06d0381b6d11445d6d3e54b33585f6b0a0b6b0b9d372" [metadata.files] acre = [] @@ -2058,40 +2058,59 @@ pathlib2 = [ {file = "pathlib2-2.3.5.tar.gz", hash = "sha256:6cd9a47b597b37cc57de1c05e56fb1a1c9cc9fab04fe78c29acd090418529868"}, ] pillow = [ - {file = "Pillow-8.2.0-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:dc38f57d8f20f06dd7c3161c59ca2c86893632623f33a42d592f097b00f720a9"}, - {file = "Pillow-8.2.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a013cbe25d20c2e0c4e85a9daf438f85121a4d0344ddc76e33fd7e3965d9af4b"}, - {file = "Pillow-8.2.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8bb1e155a74e1bfbacd84555ea62fa21c58e0b4e7e6b20e4447b8d07990ac78b"}, - {file = "Pillow-8.2.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c5236606e8570542ed424849f7852a0ff0bce2c4c8d0ba05cc202a5a9c97dee9"}, - {file = "Pillow-8.2.0-cp36-cp36m-win32.whl", hash = "sha256:12e5e7471f9b637762453da74e390e56cc43e486a88289995c1f4c1dc0bfe727"}, - {file = "Pillow-8.2.0-cp36-cp36m-win_amd64.whl", hash = "sha256:5afe6b237a0b81bd54b53f835a153770802f164c5570bab5e005aad693dab87f"}, - {file = "Pillow-8.2.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:cb7a09e173903541fa888ba010c345893cd9fc1b5891aaf060f6ca77b6a3722d"}, - {file = "Pillow-8.2.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0d19d70ee7c2ba97631bae1e7d4725cdb2ecf238178096e8c82ee481e189168a"}, - {file = "Pillow-8.2.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:083781abd261bdabf090ad07bb69f8f5599943ddb539d64497ed021b2a67e5a9"}, - {file = "Pillow-8.2.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:c6b39294464b03457f9064e98c124e09008b35a62e3189d3513e5148611c9388"}, - {file = "Pillow-8.2.0-cp37-cp37m-win32.whl", hash = "sha256:01425106e4e8cee195a411f729cff2a7d61813b0b11737c12bd5991f5f14bcd5"}, - {file = "Pillow-8.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3b570f84a6161cf8865c4e08adf629441f56e32f180f7aa4ccbd2e0a5a02cba2"}, - {file = "Pillow-8.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:031a6c88c77d08aab84fecc05c3cde8414cd6f8406f4d2b16fed1e97634cc8a4"}, - {file = "Pillow-8.2.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:66cc56579fd91f517290ab02c51e3a80f581aba45fd924fcdee01fa06e635812"}, - {file = "Pillow-8.2.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6c32cc3145928c4305d142ebec682419a6c0a8ce9e33db900027ddca1ec39178"}, - {file = "Pillow-8.2.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:624b977355cde8b065f6d51b98497d6cd5fbdd4f36405f7a8790e3376125e2bb"}, - {file = "Pillow-8.2.0-cp38-cp38-win32.whl", hash = "sha256:5cbf3e3b1014dddc45496e8cf38b9f099c95a326275885199f427825c6522232"}, - {file = "Pillow-8.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:463822e2f0d81459e113372a168f2ff59723e78528f91f0bd25680ac185cf797"}, - {file = "Pillow-8.2.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:95d5ef984eff897850f3a83883363da64aae1000e79cb3c321915468e8c6add5"}, - {file = "Pillow-8.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b91c36492a4bbb1ee855b7d16fe51379e5f96b85692dc8210831fbb24c43e484"}, - {file = "Pillow-8.2.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d68cb92c408261f806b15923834203f024110a2e2872ecb0bd2a110f89d3c602"}, - {file = "Pillow-8.2.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f217c3954ce5fd88303fc0c317af55d5e0204106d86dea17eb8205700d47dec2"}, - {file = "Pillow-8.2.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:5b70110acb39f3aff6b74cf09bb4169b167e2660dabc304c1e25b6555fa781ef"}, - {file = "Pillow-8.2.0-cp39-cp39-win32.whl", hash = "sha256:a7d5e9fad90eff8f6f6106d3b98b553a88b6f976e51fce287192a5d2d5363713"}, - {file = "Pillow-8.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:238c197fc275b475e87c1453b05b467d2d02c2915fdfdd4af126145ff2e4610c"}, - {file = "Pillow-8.2.0-pp36-pypy36_pp73-macosx_10_10_x86_64.whl", hash = "sha256:0e04d61f0064b545b989126197930807c86bcbd4534d39168f4aa5fda39bb8f9"}, - {file = "Pillow-8.2.0-pp36-pypy36_pp73-manylinux2010_i686.whl", hash = "sha256:63728564c1410d99e6d1ae8e3b810fe012bc440952168af0a2877e8ff5ab96b9"}, - {file = "Pillow-8.2.0-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:c03c07ed32c5324939b19e36ae5f75c660c81461e312a41aea30acdd46f93a7c"}, - {file = "Pillow-8.2.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:4d98abdd6b1e3bf1a1cbb14c3895226816e666749ac040c4e2554231068c639b"}, - {file = "Pillow-8.2.0-pp37-pypy37_pp73-manylinux2010_i686.whl", hash = "sha256:aac00e4bc94d1b7813fe882c28990c1bc2f9d0e1aa765a5f2b516e8a6a16a9e4"}, - {file = "Pillow-8.2.0-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:22fd0f42ad15dfdde6c581347eaa4adb9a6fc4b865f90b23378aa7914895e120"}, - {file = "Pillow-8.2.0-pp37-pypy37_pp73-win32.whl", hash = "sha256:e98eca29a05913e82177b3ba3d198b1728e164869c613d76d0de4bde6768a50e"}, - {file = "Pillow-8.2.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:8b56553c0345ad6dcb2e9b433ae47d67f95fc23fe28a0bde15a120f25257e291"}, - {file = "Pillow-8.2.0.tar.gz", hash = "sha256:a787ab10d7bb5494e5f76536ac460741788f1fbce851068d73a87ca7c35fc3e1"}, + {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"}, ] pluggy = [ {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, @@ -2294,6 +2313,7 @@ pynput = [ 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"}, @@ -2303,6 +2323,7 @@ pyobjc-core = [ 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"}, @@ -2312,6 +2333,7 @@ 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"}, diff --git a/pyproject.toml b/pyproject.toml index 085538d306..dade0a2f57 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,7 +45,7 @@ jsonschema = "^3.2.0" keyring = "^22.0.1" log4mongo = "^1.7" pathlib2= "^2.3.5" # deadline submit publish job only (single place, maybe not needed?) -Pillow = "^8.1" # only used for slates prototype +Pillow = "^8.3" # only used for slates prototype pyblish-base = "^1.8.8" pynput = "^1.7.2" # idle manager in tray pymongo = "^3.11.2" From e47ed5e77713a970c3332cbc0ecf14be9e14cece Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 21 Oct 2021 10:41:48 +0200 Subject: [PATCH 210/279] safer library loader action handling --- openpype/modules/default_modules/avalon_apps/avalon_app.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openpype/modules/default_modules/avalon_apps/avalon_app.py b/openpype/modules/default_modules/avalon_apps/avalon_app.py index d21b37e520..a8e102f447 100644 --- a/openpype/modules/default_modules/avalon_apps/avalon_app.py +++ b/openpype/modules/default_modules/avalon_apps/avalon_app.py @@ -70,6 +70,9 @@ class AvalonModule(OpenPypeModule, ITrayModule): # Definition of Tray menu def tray_menu(self, tray_menu): + if self.libraryloader is None: + return + from Qt import QtWidgets # Actions action_library_loader = QtWidgets.QAction( @@ -87,6 +90,9 @@ class AvalonModule(OpenPypeModule, ITrayModule): return def show_library_loader(self): + if self.libraryloader is None: + return + self.libraryloader.show() # Raise and activate the window From 68ff9ea9c963df63a42594e0578c8bd7a210c88b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 21 Oct 2021 10:43:02 +0200 Subject: [PATCH 211/279] converted library loader to be created same way as loader --- openpype/tools/libraryloader/app.py | 335 ++++++++++++++-------------- 1 file changed, 172 insertions(+), 163 deletions(-) diff --git a/openpype/tools/libraryloader/app.py b/openpype/tools/libraryloader/app.py index 3f11157418..69c5cb61e7 100644 --- a/openpype/tools/libraryloader/app.py +++ b/openpype/tools/libraryloader/app.py @@ -2,8 +2,8 @@ import sys from Qt import QtWidgets, QtCore, QtGui -from avalon import style from avalon.api import AvalonMongoDB +from openpype import style from openpype.tools.utils import lib as tools_lib from openpype.tools.loader.widgets import ( ThumbnailWidget, @@ -28,6 +28,8 @@ class LibraryLoaderWindow(QtWidgets.QDialog): tool_title = "Library Loader 0.5" tool_name = "library_loader" + message_timeout = 5000 + def __init__( self, parent=None, icon=None, show_projects=False, show_libraries=True ): @@ -36,6 +38,20 @@ class LibraryLoaderWindow(QtWidgets.QDialog): self._initial_refresh = False self._ignore_project_change = False + dbcon = AvalonMongoDB() + dbcon.install() + dbcon.Session["AVALON_PROJECT"] = None + + self.dbcon = dbcon + + self.show_projects = show_projects + self.show_libraries = show_libraries + + # Groups config + self.groups_config = tools_lib.GroupsConfig(dbcon) + self.family_config_cache = tools_lib.FamilyConfigCache(dbcon) + + # UI initialization # Enable minimize and maximize for app self.setWindowTitle(self.tool_title) window_flags = QtCore.Qt.Window @@ -43,140 +59,149 @@ class LibraryLoaderWindow(QtWidgets.QDialog): window_flags |= QtCore.Qt.WindowStaysOnTopHint self.setWindowFlags(window_flags) self.setFocusPolicy(QtCore.Qt.StrongFocus) - if icon is not None: - self.setWindowIcon(icon) - # self.setAttribute(QtCore.Qt.WA_DeleteOnClose) - body = QtWidgets.QWidget() - footer = QtWidgets.QWidget() - footer.setFixedHeight(20) + icon = QtGui.QIcon(style.app_icon_path()) + self.setWindowIcon(icon) - container = QtWidgets.QWidget() + main_splitter = QtWidgets.QSplitter(self) - self.dbcon = AvalonMongoDB() - self.dbcon.install() - self.dbcon.Session["AVALON_PROJECT"] = None + # --- Left part --- + left_side_splitter = QtWidgets.QSplitter(main_splitter) + left_side_splitter.setOrientation(QtCore.Qt.Vertical) - self.show_projects = show_projects - self.show_libraries = show_libraries + # Project combobox + projects_combobox = QtWidgets.QComboBox(left_side_splitter) + combobox_delegate = QtWidgets.QStyledItemDelegate(self) + projects_combobox.setItemDelegate(combobox_delegate) - # Groups config - self.groups_config = tools_lib.GroupsConfig(self.dbcon) - self.family_config_cache = tools_lib.FamilyConfigCache(self.dbcon) - - assets = AssetWidget( - self.dbcon, multiselection=True, parent=self + # Assets widget + assets_widget = AssetWidget( + dbcon, multiselection=True, parent=left_side_splitter ) - families = FamilyListView( - self.dbcon, self.family_config_cache, parent=self + + # Families widget + families_filter_view = FamilyListView( + dbcon, self.family_config_cache, left_side_splitter ) - subsets = LibrarySubsetWidget( - self.dbcon, + left_side_splitter.addWidget(projects_combobox) + left_side_splitter.addWidget(assets_widget) + left_side_splitter.addWidget(families_filter_view) + left_side_splitter.setStretchFactor(1, 65) + left_side_splitter.setStretchFactor(2, 35) + + # --- Middle part --- + # Subsets widget + subsets_widget = LibrarySubsetWidget( + dbcon, self.groups_config, self.family_config_cache, tool_name=self.tool_name, parent=self ) - version = VersionWidget(self.dbcon) - thumbnail = ThumbnailWidget(self.dbcon) - - # Project - self.combo_projects = QtWidgets.QComboBox() - - # Create splitter to show / hide family filters - asset_filter_splitter = QtWidgets.QSplitter() - asset_filter_splitter.setOrientation(QtCore.Qt.Vertical) - asset_filter_splitter.addWidget(self.combo_projects) - asset_filter_splitter.addWidget(assets) - asset_filter_splitter.addWidget(families) - asset_filter_splitter.setStretchFactor(1, 65) - asset_filter_splitter.setStretchFactor(2, 35) - - manager = ModulesManager() - sync_server = manager.modules_by_name["sync_server"] - - representations = RepresentationWidget(self.dbcon) - thumb_ver_splitter = QtWidgets.QSplitter() + # --- Right part --- + thumb_ver_splitter = QtWidgets.QSplitter(main_splitter) thumb_ver_splitter.setOrientation(QtCore.Qt.Vertical) - thumb_ver_splitter.addWidget(thumbnail) - thumb_ver_splitter.addWidget(version) - if sync_server.enabled: - thumb_ver_splitter.addWidget(representations) + + thumbnail_widget = ThumbnailWidget(dbcon, parent=thumb_ver_splitter) + version_info_widget = VersionWidget(dbcon, parent=thumb_ver_splitter) + + thumb_ver_splitter.addWidget(thumbnail_widget) + thumb_ver_splitter.addWidget(version_info_widget) + thumb_ver_splitter.setStretchFactor(0, 30) thumb_ver_splitter.setStretchFactor(1, 35) - container_layout = QtWidgets.QHBoxLayout(container) - container_layout.setContentsMargins(0, 0, 0, 0) - split = QtWidgets.QSplitter() - split.addWidget(asset_filter_splitter) - split.addWidget(subsets) - split.addWidget(thumb_ver_splitter) - split.setSizes([180, 950, 200]) - container_layout.addWidget(split) + manager = ModulesManager() + sync_server = manager.modules_by_name.get("sync_server") + sync_server_enabled = False + if sync_server is not None: + sync_server_enabled = sync_server.enabled - body_layout = QtWidgets.QHBoxLayout(body) - body_layout.addWidget(container) - body_layout.setContentsMargins(0, 0, 0, 0) + repres_widget = None + if sync_server_enabled: + repres_widget = RepresentationWidget( + dbcon, self.tool_name, parent=thumb_ver_splitter + ) + thumb_ver_splitter.addWidget(repres_widget) - message = QtWidgets.QLabel() - message.hide() + main_splitter.addWidget(left_side_splitter) + main_splitter.addWidget(subsets_widget) + main_splitter.addWidget(thumb_ver_splitter) - footer_layout = QtWidgets.QVBoxLayout(footer) - footer_layout.addWidget(message) + # --- Footer --- + footer_widget = QtWidgets.QWidget(self) + footer_widget.setFixedHeight(20) + + message_label = QtWidgets.QLabel(footer_widget) + + footer_layout = QtWidgets.QVBoxLayout(footer_widget) footer_layout.setContentsMargins(0, 0, 0, 0) + footer_layout.addWidget(message_label) layout = QtWidgets.QVBoxLayout(self) - layout.addWidget(body) - layout.addWidget(footer) + layout.addWidget(main_splitter) + layout.addWidget(footer_widget) self.data = { - "widgets": { - "families": families, - "assets": assets, - "subsets": subsets, - "version": version, - "thumbnail": thumbnail, - "representations": representations - }, - "label": { - "message": message, - }, "state": { "assetIds": None } } - families.active_changed.connect(subsets.set_family_filters) - assets.selection_changed.connect(self.on_assetschanged) - assets.refresh_triggered.connect(self.on_assetschanged) - assets.view.clicked.connect(self.on_assetview_click) - subsets.active_changed.connect(self.on_subsetschanged) - subsets.version_changed.connect(self.on_versionschanged) - subsets.refreshed.connect(self._on_subset_refresh) - self.combo_projects.currentTextChanged.connect(self.on_project_change) + message_timer = QtCore.QTimer() + message_timer.setInterval(self.message_timeout) + message_timer.setSingleShot(True) + + message_timer.timeout.connect(self._on_message_timeout) + + families_filter_view.active_changed.connect( + self._on_family_filter_change + ) + assets_widget.selection_changed.connect(self.on_assetschanged) + assets_widget.refresh_triggered.connect(self.on_assetschanged) + assets_widget.view.clicked.connect(self.on_assetview_click) + subsets_widget.active_changed.connect(self.on_subsetschanged) + subsets_widget.version_changed.connect(self.on_versionschanged) + subsets_widget.refreshed.connect(self._on_subset_refresh) + projects_combobox.currentTextChanged.connect(self.on_project_change) self.sync_server = sync_server + self._combobox_delegate = combobox_delegate + self._projects_combobox = projects_combobox + self._assets_widget = assets_widget + self._families_filter_view = families_filter_view + + self._subsets_widget = subsets_widget + + self._version_info_widget = version_info_widget + self._thumbnail_widget = thumbnail_widget + self._repres_widget = repres_widget + + self._message_label = message_label + self._message_timer = message_timer + # Set default thumbnail on start - thumbnail.set_thumbnail(None) + thumbnail_widget.set_thumbnail(None) # Defaults - if sync_server.enabled: - split.setSizes([250, 1000, 550]) + if sync_server_enabled: + main_splitter.setSizes([250, 1000, 550]) self.resize(1800, 900) else: - split.setSizes([250, 850, 200]) + main_splitter.setSizes([250, 850, 200]) self.resize(1300, 700) + self.setStyleSheet(style.load_stylesheet()) + def showEvent(self, event): super(LibraryLoaderWindow, self).showEvent(event) if not self._initial_refresh: self.refresh() def on_assetview_click(self, *args): - subsets_widget = self.data["widgets"]["subsets"] - selection_model = subsets_widget.view.selectionModel() + selection_model = self._subsets_widget.view.selectionModel() if selection_model.selectedIndexes(): selection_model.clearSelection() @@ -187,7 +212,7 @@ class LibraryLoaderWindow(QtWidgets.QDialog): self._ignore_project_change = True # Cleanup - self.combo_projects.clear() + self._projects_combobox.clear() # Fill combobox with projects select_project_item = QtGui.QStandardItem("< Select project >") @@ -202,18 +227,18 @@ class LibraryLoaderWindow(QtWidgets.QDialog): item.setData(project_name, QtCore.Qt.UserRole + 1) combobox_items.append(item) - root_item = self.combo_projects.model().invisibleRootItem() + root_item = self._projects_combobox.model().invisibleRootItem() root_item.appendRows(combobox_items) index = 0 self._ignore_project_change = False if old_project_name: - index = self.combo_projects.findText( + index = self._projects_combobox.findText( old_project_name, QtCore.Qt.MatchFixedString ) - self.combo_projects.setCurrentIndex(index) + self._projects_combobox.setCurrentIndex(index) def get_filtered_projects(self): projects = list() @@ -231,8 +256,8 @@ class LibraryLoaderWindow(QtWidgets.QDialog): if self._ignore_project_change: return - row = self.combo_projects.currentIndex() - index = self.combo_projects.model().index(row, 0) + row = self._projects_combobox.currentIndex() + index = self._projects_combobox.model().index(row, 0) project_name = index.data(QtCore.Qt.UserRole + 1) self.dbcon.Session["AVALON_PROJECT"] = project_name @@ -245,11 +270,9 @@ class LibraryLoaderWindow(QtWidgets.QDialog): "Config `%s` has no function `install`" % _config.__name__ ) - subsets = self.data["widgets"]["subsets"] - representations = self.data["widgets"]["representations"] - - subsets.on_project_change(self.dbcon.Session["AVALON_PROJECT"]) - representations.on_project_change(self.dbcon.Session["AVALON_PROJECT"]) + self._subsets_widget.on_project_change(project_name) + if self._repres_widget: + self._repres_widget.on_project_change(project_name) self.family_config_cache.refresh() self.groups_config.refresh() @@ -263,13 +286,7 @@ class LibraryLoaderWindow(QtWidgets.QDialog): @property def current_project(self): - if ( - not self.dbcon.active_project() or - self.dbcon.active_project() == "" - ): - return None - - return self.dbcon.active_project() + return self.dbcon.active_project() or None # ------------------------------- # Delay calling blocking methods @@ -292,12 +309,11 @@ class LibraryLoaderWindow(QtWidgets.QDialog): tools_lib.schedule(self._versionschanged, 150, channel="mongo") def _on_subset_refresh(self, has_item): - subsets_widget = self.data["widgets"]["subsets"] - families_view = self.data["widgets"]["families"] - - subsets_widget.set_loading_state(loading=False, empty=not has_item) - families = subsets_widget.get_subsets_families() - families_view.set_enabled_families(families) + self._subsets_widget.set_loading_state( + loading=False, empty=not has_item + ) + families = self._subsets_widget.get_subsets_families() + self._families_filter_view.set_enabled_families(families) def set_context(self, context, refresh=True): self.echo("Setting context: {}".format(context)) @@ -307,6 +323,9 @@ class LibraryLoaderWindow(QtWidgets.QDialog): ) # ------------------------------ + def _on_family_filter_change(self, families): + self._subsets_widget.set_family_filters(families) + def _refresh(self): if not self._initial_refresh: self._initial_refresh = True @@ -322,74 +341,69 @@ class LibraryLoaderWindow(QtWidgets.QDialog): ) assert project_doc, "This is a bug" - assets_widget = self.data["widgets"]["assets"] - families_view = self.data["widgets"]["families"] - families_view.set_enabled_families(set()) - families_view.refresh() + self._families_filter_view.set_enabled_families(set()) + self._families_filter_view.refresh() - assets_widget.model.stop_fetch_thread() - assets_widget.refresh() - assets_widget.setFocus() + self._assets_widget.model.stop_fetch_thread() + self._assets_widget.refresh() + self._assets_widget.setFocus() def clear_assets_underlines(self): last_asset_ids = self.data["state"]["assetIds"] if not last_asset_ids: return - assets_widget = self.data["widgets"]["assets"] - id_role = assets_widget.model.ObjectIdRole + assets_model = self._assets_widget.model + id_role = assets_model.ObjectIdRole - for index in tools_lib.iter_model_rows(assets_widget.model, 0): + for index in tools_lib.iter_model_rows(assets_model, 0): if index.data(id_role) not in last_asset_ids: continue - assets_widget.model.setData( - index, [], assets_widget.model.subsetColorsRole + assets_model.setData( + index, [], assets_model.subsetColorsRole ) def _assetschanged(self): """Selected assets have changed""" - assets_widget = self.data["widgets"]["assets"] - subsets_widget = self.data["widgets"]["subsets"] - subsets_model = subsets_widget.model + subsets_model = self._subsets_widget.model subsets_model.clear() self.clear_assets_underlines() if not self.dbcon.Session.get("AVALON_PROJECT"): - subsets_widget.set_loading_state( + self._subsets_widget.set_loading_state( loading=False, empty=True ) return # filter None docs they are silo - asset_docs = assets_widget.get_selected_assets() + asset_docs = self._assets_widget.get_selected_assets() if len(asset_docs) == 0: return asset_ids = [asset_doc["_id"] for asset_doc in asset_docs] # Start loading - subsets_widget.set_loading_state( + self._subsets_widget.set_loading_state( loading=bool(asset_ids), empty=True ) subsets_model.set_assets(asset_ids) - subsets_widget.view.setColumnHidden( + self._subsets_widget.view.setColumnHidden( subsets_model.Columns.index("asset"), len(asset_ids) < 2 ) # Clear the version information on asset change - self.data["widgets"]["version"].set_version(None) - self.data["widgets"]["thumbnail"].set_thumbnail(asset_docs) + self._version_info_widget.set_version(None) + self._thumbnail_widget.set_thumbnail(asset_docs) self.data["state"]["assetIds"] = asset_ids - representations = self.data["widgets"]["representations"] # reset repre list - representations.set_version_ids([]) + self._repres_widget.set_version_ids([]) def _subsetschanged(self): asset_ids = self.data["state"]["assetIds"] @@ -398,8 +412,9 @@ class LibraryLoaderWindow(QtWidgets.QDialog): self._versionschanged() return - subsets = self.data["widgets"]["subsets"] - selected_subsets = subsets.selected_subsets(_merged=True, _other=False) + selected_subsets = self._subsets_widget.selected_subsets( + _merged=True, _other=False + ) asset_models = {} asset_ids = [] @@ -420,26 +435,24 @@ class LibraryLoaderWindow(QtWidgets.QDialog): self.clear_assets_underlines() - assets_widget = self.data["widgets"]["assets"] - indexes = assets_widget.view.selectionModel().selectedRows() + indexes = self._assets_widget.view.selectionModel().selectedRows() + assets_model = self._assets_widget.model for index in indexes: - id = index.data(assets_widget.model.ObjectIdRole) + id = index.data(assets_model.ObjectIdRole) if id not in asset_models: continue - assets_widget.model.setData( - index, asset_models[id], assets_widget.model.subsetColorsRole + assets_model.setData( + index, asset_models[id], assets_model.subsetColorsRole ) # Trigger repaint - assets_widget.view.updateGeometries() + self._assets_widget.view.updateGeometries() # Set version in Version Widget self._versionschanged() def _versionschanged(self): - - subsets = self.data["widgets"]["subsets"] - selection = subsets.view.selectionModel() + selection = self._subsets_widget.view.selectionModel() # Active must be in the selected rows otherwise we # assume it's not actually an "active" current index. @@ -448,7 +461,7 @@ class LibraryLoaderWindow(QtWidgets.QDialog): active = selection.currentIndex() rows = selection.selectedRows(column=active.column()) if active and active in rows: - item = active.data(subsets.model.ItemRole) + item = active.data(self._subsets_widget.model.ItemRole) if ( item is not None and not (item.get("isGroup") or item.get("isMerged")) @@ -460,7 +473,7 @@ class LibraryLoaderWindow(QtWidgets.QDialog): for index in rows: if not index or not index.isValid(): continue - item = index.data(subsets.model.ItemRole) + item = index.data(self._subsets_widget.model.ItemRole) if ( item is None or item.get("isGroup") @@ -469,20 +482,18 @@ class LibraryLoaderWindow(QtWidgets.QDialog): continue version_docs.append(item["version_document"]) - self.data["widgets"]["version"].set_version(version_doc) + self._version_info_widget.set_version(version_doc) thumbnail_docs = version_docs if not thumbnail_docs: - assets_widget = self.data["widgets"]["assets"] - asset_docs = assets_widget.get_selected_assets() + asset_docs = self._assets_widget.get_selected_assets() if len(asset_docs) > 0: thumbnail_docs = asset_docs - self.data["widgets"]["thumbnail"].set_thumbnail(thumbnail_docs) + self._thumbnail_widget.set_thumbnail(thumbnail_docs) - representations = self.data["widgets"]["representations"] version_ids = [doc["_id"] for doc in version_docs or []] - representations.set_version_ids(version_ids) + self._repres_widget.set_version_ids(version_ids) def _set_context(self, context, refresh=True): """Set the selection in the interface using a context. @@ -510,16 +521,15 @@ class LibraryLoaderWindow(QtWidgets.QDialog): # scheduled refresh and the silo tabs are not shown. self._refresh_assets() - asset_widget = self.data["widgets"]["assets"] - asset_widget.select_assets(asset) + self._assets_widget.select_assets(asset) + + def _on_message_timeout(self): + self._message_label.setText("") def echo(self, message): - widget = self.data["label"]["message"] - widget.setText(str(message)) - widget.show() + self._message_label.setText(str(message)) print(message) - - tools_lib.schedule(widget.hide, 5000, channel="message") + self._message_timer.start() def closeEvent(self, event): # Kill on holding SHIFT @@ -576,7 +586,6 @@ def show( window = LibraryLoaderWindow( parent, icon, show_projects, show_libraries ) - window.setStyleSheet(style.load_stylesheet()) window.show() module.window = window From b642d770e58643af253e96e739aabe7b8a9d230b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 21 Oct 2021 10:44:28 +0200 Subject: [PATCH 212/279] reorganize initialization --- openpype/tools/loader/app.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/openpype/tools/loader/app.py b/openpype/tools/loader/app.py index 955242e551..54eafd8f6d 100644 --- a/openpype/tools/loader/app.py +++ b/openpype/tools/loader/app.py @@ -3,7 +3,7 @@ import sys from Qt import QtWidgets, QtCore from avalon import api, io, pipeline -from openpype.style import load_stylesheet +from openpype import style from openpype.tools.utils.widgets import AssetWidget from openpype.tools.utils import lib @@ -96,15 +96,18 @@ class LoaderWindow(QtWidgets.QDialog): thumbnail_widget = ThumbnailWidget(io, parent=thumb_ver_splitter) version_info_widget = VersionWidget(io, parent=thumb_ver_splitter) + thumb_ver_splitter.addWidget(thumbnail_widget) + thumb_ver_splitter.addWidget(version_info_widget) + + thumb_ver_splitter.setStretchFactor(0, 30) + thumb_ver_splitter.setStretchFactor(1, 35) + manager = ModulesManager() sync_server = manager.modules_by_name.get("sync_server") sync_server_enabled = False if sync_server is not None: sync_server_enabled = sync_server.enabled - thumb_ver_splitter.addWidget(thumbnail_widget) - thumb_ver_splitter.addWidget(version_info_widget) - repres_widget = None if sync_server_enabled: repres_widget = RepresentationWidget( @@ -112,9 +115,6 @@ class LoaderWindow(QtWidgets.QDialog): ) thumb_ver_splitter.addWidget(repres_widget) - thumb_ver_splitter.setStretchFactor(0, 30) - thumb_ver_splitter.setStretchFactor(1, 35) - main_splitter.addWidget(left_side_splitter) main_splitter.addWidget(subsets_widget) main_splitter.addWidget(thumb_ver_splitter) @@ -193,7 +193,7 @@ class LoaderWindow(QtWidgets.QDialog): main_splitter.setSizes([250, 850, 200]) self.resize(1300, 700) - self.setStyleSheet(load_stylesheet()) + self.setStyleSheet(style.load_stylesheet()) def resizeEvent(self, event): super(LoaderWindow, self).resizeEvent(event) From 7cedb5af316288abbb9481a13f6e0951170a11e1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 21 Oct 2021 10:45:32 +0200 Subject: [PATCH 213/279] do not set stylesheets of library loader --- openpype/modules/default_modules/avalon_apps/avalon_app.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/openpype/modules/default_modules/avalon_apps/avalon_app.py b/openpype/modules/default_modules/avalon_apps/avalon_app.py index a8e102f447..9e650a097e 100644 --- a/openpype/modules/default_modules/avalon_apps/avalon_app.py +++ b/openpype/modules/default_modules/avalon_apps/avalon_app.py @@ -1,6 +1,5 @@ import os import openpype -from openpype import resources from openpype.modules import OpenPypeModule from openpype_interfaces import ITrayModule @@ -52,16 +51,12 @@ class AvalonModule(OpenPypeModule, ITrayModule): def tray_init(self): # Add library tool try: - from Qt import QtGui - from avalon import style from openpype.tools.libraryloader import LibraryLoaderWindow self.libraryloader = LibraryLoaderWindow( - icon=QtGui.QIcon(resources.get_openpype_icon_filepath()), show_projects=True, show_libraries=True ) - self.libraryloader.setStyleSheet(style.load_stylesheet()) except Exception: self.log.warning( "Couldn't load Library loader tool for tray.", From 80b0b590b8c824123b687702c5de5180f038de1b Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 21 Oct 2021 11:27:35 +0100 Subject: [PATCH 214/279] Implemented deselect function to handle objects not in 'object mode' --- openpype/hosts/blender/api/plugin.py | 24 +++++++++++++++++++ .../hosts/blender/plugins/load/load_abc.py | 4 ++-- .../hosts/blender/plugins/load/load_fbx.py | 4 ++-- .../blender/plugins/load/load_layout_blend.py | 2 +- .../blender/plugins/load/load_layout_json.py | 2 +- .../hosts/blender/plugins/load/load_model.py | 6 ++--- .../hosts/blender/plugins/load/load_rig.py | 8 +++---- .../blender/plugins/publish/extract_abc.py | 4 ++-- .../blender/plugins/publish/extract_fbx.py | 6 +++-- 9 files changed, 43 insertions(+), 17 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index 50b73ade2b..181e5972a8 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -95,6 +95,30 @@ def get_local_collection_with_name(name): return None +def deselect_all(): + """Deselect all objects in the scene. + + Blender gives context error if trying to deselect object that it isn't + in object mode. + """ + modes = [] + active = bpy.context.view_layer.objects.active + + for obj in bpy.data.objects: + if obj.mode != 'OBJECT': + modes.append((obj, obj.mode)) + bpy.context.view_layer.objects.active = obj + bpy.ops.object.mode_set(mode = 'OBJECT') + + bpy.ops.object.select_all(action='DESELECT') + + for p in modes: + bpy.context.view_layer.objects.active = p[0] + bpy.ops.object.mode_set(mode = p[1]) + + bpy.context.view_layer.objects.active = active + + class Creator(PypeCreatorMixin, blender.Creator): pass diff --git a/openpype/hosts/blender/plugins/load/load_abc.py b/openpype/hosts/blender/plugins/load/load_abc.py index 92656fac9e..5969432c36 100644 --- a/openpype/hosts/blender/plugins/load/load_abc.py +++ b/openpype/hosts/blender/plugins/load/load_abc.py @@ -47,7 +47,7 @@ class CacheModelLoader(plugin.AssetLoader): bpy.data.objects.remove(empty) def _process(self, libpath, asset_group, group_name): - bpy.ops.object.select_all(action='DESELECT') + plugin.deselect_all() collection = bpy.context.view_layer.active_layer_collection.collection @@ -109,7 +109,7 @@ class CacheModelLoader(plugin.AssetLoader): avalon_info = obj[AVALON_PROPERTY] avalon_info.update({"container_name": group_name}) - bpy.ops.object.select_all(action='DESELECT') + plugin.deselect_all() return objects diff --git a/openpype/hosts/blender/plugins/load/load_fbx.py b/openpype/hosts/blender/plugins/load/load_fbx.py index b80dc69adc..5f69aecb1a 100644 --- a/openpype/hosts/blender/plugins/load/load_fbx.py +++ b/openpype/hosts/blender/plugins/load/load_fbx.py @@ -46,7 +46,7 @@ class FbxModelLoader(plugin.AssetLoader): bpy.data.objects.remove(obj) def _process(self, libpath, asset_group, group_name, action): - bpy.ops.object.select_all(action='DESELECT') + plugin.deselect_all() collection = bpy.context.view_layer.active_layer_collection.collection @@ -112,7 +112,7 @@ class FbxModelLoader(plugin.AssetLoader): avalon_info = obj[AVALON_PROPERTY] avalon_info.update({"container_name": group_name}) - bpy.ops.object.select_all(action='DESELECT') + plugin.deselect_all() return objects diff --git a/openpype/hosts/blender/plugins/load/load_layout_blend.py b/openpype/hosts/blender/plugins/load/load_layout_blend.py index 85cb4dfbd3..4c1f751a77 100644 --- a/openpype/hosts/blender/plugins/load/load_layout_blend.py +++ b/openpype/hosts/blender/plugins/load/load_layout_blend.py @@ -150,7 +150,7 @@ class BlendLayoutLoader(plugin.AssetLoader): bpy.data.orphans_purge(do_local_ids=False) - bpy.ops.object.select_all(action='DESELECT') + plugin.deselect_all() return objects diff --git a/openpype/hosts/blender/plugins/load/load_layout_json.py b/openpype/hosts/blender/plugins/load/load_layout_json.py index 1a4dbbb5cb..38718fd9b2 100644 --- a/openpype/hosts/blender/plugins/load/load_layout_json.py +++ b/openpype/hosts/blender/plugins/load/load_layout_json.py @@ -59,7 +59,7 @@ class JsonLayoutLoader(plugin.AssetLoader): return None def _process(self, libpath, asset, asset_group, actions): - bpy.ops.object.select_all(action='DESELECT') + plugin.deselect_all() with open(libpath, "r") as fp: data = json.load(fp) diff --git a/openpype/hosts/blender/plugins/load/load_model.py b/openpype/hosts/blender/plugins/load/load_model.py index af5591c299..c33c656dec 100644 --- a/openpype/hosts/blender/plugins/load/load_model.py +++ b/openpype/hosts/blender/plugins/load/load_model.py @@ -93,7 +93,7 @@ class BlendModelLoader(plugin.AssetLoader): bpy.data.orphans_purge(do_local_ids=False) - bpy.ops.object.select_all(action='DESELECT') + plugin.deselect_all() return objects @@ -126,7 +126,7 @@ class BlendModelLoader(plugin.AssetLoader): asset_group.empty_display_type = 'SINGLE_ARROW' avalon_container.objects.link(asset_group) - bpy.ops.object.select_all(action='DESELECT') + plugin.deselect_all() if options is not None: parent = options.get('parent') @@ -158,7 +158,7 @@ class BlendModelLoader(plugin.AssetLoader): bpy.ops.object.parent_set(keep_transform=True) - bpy.ops.object.select_all(action='DESELECT') + plugin.deselect_all() objects = self._process(libpath, asset_group, group_name) diff --git a/openpype/hosts/blender/plugins/load/load_rig.py b/openpype/hosts/blender/plugins/load/load_rig.py index 6062c293df..e80da8af45 100644 --- a/openpype/hosts/blender/plugins/load/load_rig.py +++ b/openpype/hosts/blender/plugins/load/load_rig.py @@ -156,7 +156,7 @@ class BlendRigLoader(plugin.AssetLoader): while bpy.data.orphans_purge(do_local_ids=False): pass - bpy.ops.object.select_all(action='DESELECT') + plugin.deselect_all() return objects @@ -191,7 +191,7 @@ class BlendRigLoader(plugin.AssetLoader): action = None - bpy.ops.object.select_all(action='DESELECT') + plugin.deselect_all() create_animation = False @@ -227,7 +227,7 @@ class BlendRigLoader(plugin.AssetLoader): bpy.ops.object.parent_set(keep_transform=True) - bpy.ops.object.select_all(action='DESELECT') + plugin.deselect_all() objects = self._process(libpath, asset_group, group_name, action) @@ -250,7 +250,7 @@ class BlendRigLoader(plugin.AssetLoader): data={"dependencies": str(context["representation"]["_id"])} ) - bpy.ops.object.select_all(action='DESELECT') + plugin.deselect_all() bpy.context.scene.collection.objects.link(asset_group) diff --git a/openpype/hosts/blender/plugins/publish/extract_abc.py b/openpype/hosts/blender/plugins/publish/extract_abc.py index 4696da3db4..b75bec4e28 100644 --- a/openpype/hosts/blender/plugins/publish/extract_abc.py +++ b/openpype/hosts/blender/plugins/publish/extract_abc.py @@ -28,7 +28,7 @@ class ExtractABC(api.Extractor): # Perform extraction self.log.info("Performing extraction..") - bpy.ops.object.select_all(action='DESELECT') + plugin.deselect_all() selected = [] asset_group = None @@ -50,7 +50,7 @@ class ExtractABC(api.Extractor): flatten=False ) - bpy.ops.object.select_all(action='DESELECT') + plugin.deselect_all() if "representations" not in instance.data: instance.data["representations"] = [] diff --git a/openpype/hosts/blender/plugins/publish/extract_fbx.py b/openpype/hosts/blender/plugins/publish/extract_fbx.py index b91f2a75ef..31d37da8e0 100644 --- a/openpype/hosts/blender/plugins/publish/extract_fbx.py +++ b/openpype/hosts/blender/plugins/publish/extract_fbx.py @@ -24,7 +24,7 @@ class ExtractFBX(api.Extractor): # Perform extraction self.log.info("Performing extraction..") - bpy.ops.object.select_all(action='DESELECT') + plugin.deselect_all() selected = [] asset_group = None @@ -60,7 +60,9 @@ class ExtractFBX(api.Extractor): add_leaf_bones=False ) - bpy.ops.object.select_all(action='DESELECT') + bpy.context.scene.unit_settings.scale_length = scale_length + + plugin.deselect_all() for mat in new_materials: bpy.data.materials.remove(mat) From a2397f48f032389184f8cee414c674cd8ae4d6c8 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 21 Oct 2021 12:40:33 +0200 Subject: [PATCH 215/279] set stylesheet on first show --- openpype/tools/libraryloader/app.py | 30 ++++++++++++++++------------- openpype/tools/loader/app.py | 8 +++++++- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/openpype/tools/libraryloader/app.py b/openpype/tools/libraryloader/app.py index 69c5cb61e7..700d3c05bd 100644 --- a/openpype/tools/libraryloader/app.py +++ b/openpype/tools/libraryloader/app.py @@ -35,6 +35,18 @@ class LibraryLoaderWindow(QtWidgets.QDialog): ): super(LibraryLoaderWindow, self).__init__(parent) + # Window modifications + self.setWindowTitle(self.tool_title) + window_flags = QtCore.Qt.Window + if not parent: + window_flags |= QtCore.Qt.WindowStaysOnTopHint + self.setWindowFlags(window_flags) + self.setFocusPolicy(QtCore.Qt.StrongFocus) + + icon = QtGui.QIcon(style.app_icon_path()) + self.setWindowIcon(icon) + + self._first_show = True self._initial_refresh = False self._ignore_project_change = False @@ -52,17 +64,6 @@ class LibraryLoaderWindow(QtWidgets.QDialog): self.family_config_cache = tools_lib.FamilyConfigCache(dbcon) # UI initialization - # Enable minimize and maximize for app - self.setWindowTitle(self.tool_title) - window_flags = QtCore.Qt.Window - if not parent: - window_flags |= QtCore.Qt.WindowStaysOnTopHint - self.setWindowFlags(window_flags) - self.setFocusPolicy(QtCore.Qt.StrongFocus) - - icon = QtGui.QIcon(style.app_icon_path()) - self.setWindowIcon(icon) - main_splitter = QtWidgets.QSplitter(self) # --- Left part --- @@ -193,11 +194,14 @@ class LibraryLoaderWindow(QtWidgets.QDialog): main_splitter.setSizes([250, 850, 200]) self.resize(1300, 700) - self.setStyleSheet(style.load_stylesheet()) - def showEvent(self, event): super(LibraryLoaderWindow, self).showEvent(event) + if self._first_show: + self._first_show = False + self.setStyleSheet(style.load_stylesheet()) + if not self._initial_refresh: + self._initial_refresh = True self.refresh() def on_assetview_click(self, *args): diff --git a/openpype/tools/loader/app.py b/openpype/tools/loader/app.py index 54eafd8f6d..a98c7e2f2f 100644 --- a/openpype/tools/loader/app.py +++ b/openpype/tools/loader/app.py @@ -193,7 +193,7 @@ class LoaderWindow(QtWidgets.QDialog): main_splitter.setSizes([250, 850, 200]) self.resize(1300, 700) - self.setStyleSheet(style.load_stylesheet()) + self._first_show = True def resizeEvent(self, event): super(LoaderWindow, self).resizeEvent(event) @@ -203,6 +203,12 @@ class LoaderWindow(QtWidgets.QDialog): super(LoaderWindow, self).moveEvent(event) self._overlay_frame.move(0, 0) + def showEvent(self, event): + super(LoaderWindow, self).showEvent(event) + if self._first_show: + self._first_show = False + self.setStyleSheet(style.load_stylesheet()) + # ------------------------------- # Delay calling blocking methods # ------------------------------- From e545d431da9a8283b5a957c048fa298bd4ca848b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 21 Oct 2021 12:40:42 +0200 Subject: [PATCH 216/279] modified splitter style --- openpype/style/style.css | 40 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/openpype/style/style.css b/openpype/style/style.css index 948ee8c7b7..8013f38bea 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -200,12 +200,44 @@ QComboBox::down-arrow, QComboBox::down-arrow:on, QComboBox::down-arrow:hover, QC } /* Splitter */ -QSplitter { - border: none; +QSplitter::handle { + border: 3px solid transparent; } -QSplitter::handle { - border: 1px dotted {color:bg-menu-separator}; +QSplitter::handle:horizontal { + background: qlineargradient( + x1:0, y1:0, x2:1, y2:0, + stop:0.3 rgba(0, 0, 0, 0), + stop:0.5 {color:bg-buttons}, + stop:0.7 rgba(0, 0, 0, 0) + ); +} + +QSplitter::handle:vertical { + background: qlineargradient( + x1:0, y1:0, x2:0, y2:1, + stop:0.3 rgba(0, 0, 0, 0), + stop:0.5 {color:bg-buttons}, + stop:0.7 rgba(0, 0, 0, 0) + ); +} + +QSplitter::handle:horizontal:hover { + background: qlineargradient( + x1:0, y1:0, x2:1, y2:0, + stop:0.3 rgba(0, 0, 0, 0), + stop:0.5 {color:bg-button-hover}, + stop:0.7 rgba(0, 0, 0, 0) + ); +} + +QSplitter::handle:vertical:hover { + background: qlineargradient( + x1:0, y1:0, x2:0, y2:1, + stop:0.3 rgba(0, 0, 0, 0), + stop:0.5 {color:bg-button-hover}, + stop:0.7 rgba(0, 0, 0, 0) + ); } /* SLider */ From 52f474d62d1bfb22b0ada0908a768d90728841b7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 21 Oct 2021 12:42:01 +0200 Subject: [PATCH 217/279] splitter has it's own key in data --- openpype/style/data.json | 3 +++ openpype/style/style.css | 8 ++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/openpype/style/data.json b/openpype/style/data.json index 143c6695af..c33c2eaa5e 100644 --- a/openpype/style/data.json +++ b/openpype/style/data.json @@ -32,6 +32,9 @@ "bg-inputs-disabled": "#2C313A", "bg-buttons-disabled": "#434a56", + "bg-splitter": "#434a56", + "bg-splitter-hover": "rgba(168, 175, 189, 0.3)", + "bg-menu-separator": "rgba(75, 83, 98, 127)", "bg-scroll-handle": "#4B5362", diff --git a/openpype/style/style.css b/openpype/style/style.css index 8013f38bea..3f006fb845 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -208,7 +208,7 @@ QSplitter::handle:horizontal { background: qlineargradient( x1:0, y1:0, x2:1, y2:0, stop:0.3 rgba(0, 0, 0, 0), - stop:0.5 {color:bg-buttons}, + stop:0.5 {color:bg-splitter}, stop:0.7 rgba(0, 0, 0, 0) ); } @@ -217,7 +217,7 @@ QSplitter::handle:vertical { background: qlineargradient( x1:0, y1:0, x2:0, y2:1, stop:0.3 rgba(0, 0, 0, 0), - stop:0.5 {color:bg-buttons}, + stop:0.5 {color:bg-splitter}, stop:0.7 rgba(0, 0, 0, 0) ); } @@ -226,7 +226,7 @@ QSplitter::handle:horizontal:hover { background: qlineargradient( x1:0, y1:0, x2:1, y2:0, stop:0.3 rgba(0, 0, 0, 0), - stop:0.5 {color:bg-button-hover}, + stop:0.5 {color:bg-splitter-hover}, stop:0.7 rgba(0, 0, 0, 0) ); } @@ -235,7 +235,7 @@ QSplitter::handle:vertical:hover { background: qlineargradient( x1:0, y1:0, x2:0, y2:1, stop:0.3 rgba(0, 0, 0, 0), - stop:0.5 {color:bg-button-hover}, + stop:0.5 {color:bg-splitter-hover}, stop:0.7 rgba(0, 0, 0, 0) ); } From 36f49122416af0138f8acae4fd72792f6a1c673f Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 21 Oct 2021 11:51:12 +0100 Subject: [PATCH 218/279] Hound fixes --- openpype/hosts/blender/api/plugin.py | 4 ++-- openpype/hosts/blender/plugins/publish/extract_fbx.py | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index 181e5972a8..6d437059b8 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -108,13 +108,13 @@ def deselect_all(): if obj.mode != 'OBJECT': modes.append((obj, obj.mode)) bpy.context.view_layer.objects.active = obj - bpy.ops.object.mode_set(mode = 'OBJECT') + bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.select_all(action='DESELECT') for p in modes: bpy.context.view_layer.objects.active = p[0] - bpy.ops.object.mode_set(mode = p[1]) + bpy.ops.object.mode_set(mode=p[1]) bpy.context.view_layer.objects.active = active diff --git a/openpype/hosts/blender/plugins/publish/extract_fbx.py b/openpype/hosts/blender/plugins/publish/extract_fbx.py index 31d37da8e0..f9ffdea1d1 100644 --- a/openpype/hosts/blender/plugins/publish/extract_fbx.py +++ b/openpype/hosts/blender/plugins/publish/extract_fbx.py @@ -60,8 +60,6 @@ class ExtractFBX(api.Extractor): add_leaf_bones=False ) - bpy.context.scene.unit_settings.scale_length = scale_length - plugin.deselect_all() for mat in new_materials: From 196779d9b3a5f96f71ab7096724b042019b33160 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 21 Oct 2021 14:30:30 +0200 Subject: [PATCH 219/279] Flame: creating host folder with basic methods --- openpype/hosts/flame/__init__.py | 105 +++++++++ openpype/hosts/flame/api/__init__.py | 3 + openpype/hosts/flame/api/lib.py | 310 +++++++++++++++++++++++++++ openpype/hosts/flame/api/menu.py | 199 +++++++++++++++++ openpype/hosts/flame/api/pipeline.py | 162 ++++++++++++++ openpype/hosts/flame/api/plugin.py | 13 ++ openpype/hosts/flame/api/utils.py | 91 ++++++++ openpype/hosts/flame/api/workio.py | 37 ++++ 8 files changed, 920 insertions(+) create mode 100644 openpype/hosts/flame/__init__.py create mode 100644 openpype/hosts/flame/api/__init__.py create mode 100644 openpype/hosts/flame/api/lib.py create mode 100644 openpype/hosts/flame/api/menu.py create mode 100644 openpype/hosts/flame/api/pipeline.py create mode 100644 openpype/hosts/flame/api/plugin.py create mode 100644 openpype/hosts/flame/api/utils.py create mode 100644 openpype/hosts/flame/api/workio.py diff --git a/openpype/hosts/flame/__init__.py b/openpype/hosts/flame/__init__.py new file mode 100644 index 0000000000..dc3d3e7cba --- /dev/null +++ b/openpype/hosts/flame/__init__.py @@ -0,0 +1,105 @@ +from .api.utils import ( + setup +) + +from .api.pipeline import ( + install, + uninstall, + ls, + containerise, + update_container, + maintained_selection, + remove_instance, + list_instances, + imprint +) + +from .api.lib import ( + FlameAppFramework, + maintain_current_timeline, + get_project_manager, + get_current_project, + get_current_timeline, + create_bin, +) + +from .api.menu import ( + FlameMenuProjectconnect, + FlameMenuTimeline +) + +from .api.workio import ( + open_file, + save_file, + current_file, + has_unsaved_changes, + file_extensions, + work_root +) + +import os + +HOST_DIR = os.path.dirname( + os.path.abspath(__file__) +) +API_DIR = os.path.join(HOST_DIR, "api") +PLUGINS_DIR = os.path.join(HOST_DIR, "plugins") +PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") +LOAD_PATH = os.path.join(PLUGINS_DIR, "load") +CREATE_PATH = os.path.join(PLUGINS_DIR, "create") +INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory") + +app_framework = None +apps = [] + + +__all__ = [ + "HOST_DIR", + "API_DIR", + "PLUGINS_DIR", + "PUBLISH_PATH", + "LOAD_PATH", + "CREATE_PATH", + "INVENTORY_PATH", + "INVENTORY_PATH", + + "app_framework", + "apps", + + # pipeline + "install", + "uninstall", + "ls", + "containerise", + "update_container", + "reload_pipeline", + "maintained_selection", + "remove_instance", + "list_instances", + "imprint", + + # utils + "setup", + + # lib + "FlameAppFramework", + "maintain_current_timeline", + "get_project_manager", + "get_current_project", + "get_current_timeline", + "create_bin", + + # menu + "FlameMenuProjectconnect", + "FlameMenuTimeline", + + # plugin + + # workio + "open_file", + "save_file", + "current_file", + "has_unsaved_changes", + "file_extensions", + "work_root" +] diff --git a/openpype/hosts/flame/api/__init__.py b/openpype/hosts/flame/api/__init__.py new file mode 100644 index 0000000000..50a6b3f098 --- /dev/null +++ b/openpype/hosts/flame/api/__init__.py @@ -0,0 +1,3 @@ +""" +OpenPype Autodesk Flame api +""" diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py new file mode 100644 index 0000000000..9d24e94df8 --- /dev/null +++ b/openpype/hosts/flame/api/lib.py @@ -0,0 +1,310 @@ +import sys +import json +import re +import os +import pickle +import contextlib +from pprint import pprint, pformat +from opentimelineio import opentime +import openpype + + +# from ..otio import davinci_export as otio_export + +from openpype.api import Logger + +log = Logger().get_logger(__name__) + +self = sys.modules[__name__] +self.project_manager = None +self.media_storage = None + +# OpenPype sequencial rename variables +self.rename_index = 0 +self.rename_add = 0 + +self.publish_clip_color = "Pink" +self.pype_marker_workflow = True + +# OpenPype compound clip workflow variable +self.pype_tag_name = "VFX Notes" + +# OpenPype marker workflow variables +self.pype_marker_name = "OpenPypeData" +self.pype_marker_duration = 1 +self.pype_marker_color = "Mint" +self.temp_marker_frame = None + +# OpenPype default timeline +self.pype_timeline_name = "OpenPypeTimeline" + + +class FlameAppFramework(object): + # flameAppFramework class takes care of preferences + + class prefs_dict(dict): + # subclass of a dict() in order to directly link it + # to main framework prefs dictionaries + # when accessed directly it will operate on a dictionary under a "name" + # key in master dictionary. + # master = {} + # p = prefs(master, "app_name") + # p["key"] = "value" + # master - {"app_name": {"key", "value"}} + + def __init__(self, master, name, **kwargs): + self.name = name + self.master = master + if not self.master.get(self.name): + self.master[self.name] = {} + self.master[self.name].__init__() + + def __getitem__(self, k): + return self.master[self.name].__getitem__(k) + + def __setitem__(self, k, v): + return self.master[self.name].__setitem__(k, v) + + def __delitem__(self, k): + return self.master[self.name].__delitem__(k) + + def get(self, k, default=None): + return self.master[self.name].get(k, default) + + def setdefault(self, k, default=None): + return self.master[self.name].setdefault(k, default) + + def pop(self, k, v=object()): + if v is object(): + return self.master[self.name].pop(k) + return self.master[self.name].pop(k, v) + + def update(self, mapping=(), **kwargs): + self.master[self.name].update(mapping, **kwargs) + + def __contains__(self, k): + return self.master[self.name].__contains__(k) + + def copy(self): # don"t delegate w/ super - dict.copy() -> dict :( + return type(self)(self) + + def keys(self): + return self.master[self.name].keys() + + @classmethod + def fromkeys(cls, keys, v=None): + return cls.master[cls.name].fromkeys(keys, v) + + def __repr__(self): + return "{0}({1})".format(type(self).__name__, self.master[self.name].__repr__()) + + def master_keys(self): + return self.master.keys() + + def __init__(self): + self.name = self.__class__.__name__ + self.bundle_name = "OpenPypeFlame" + # self.prefs scope is limited to flame project and user + self.prefs = {} + self.prefs_user = {} + self.prefs_global = {} + self.log = log + + + try: + import flame + self.flame = flame + self.flame_project_name = self.flame.project.current_project.name + self.flame_user_name = flame.users.current_user.name + except: + self.flame = None + self.flame_project_name = None + self.flame_user_name = None + + import socket + self.hostname = socket.gethostname() + + if sys.platform == "darwin": + self.prefs_folder = os.path.join( + os.path.expanduser("~"), + "Library", + "Caches", + "OpenPype", + self.bundle_name) + elif sys.platform.startswith("linux"): + self.prefs_folder = os.path.join( + os.path.expanduser("~"), + ".OpenPype", + self.bundle_name) + + self.prefs_folder = os.path.join( + self.prefs_folder, + self.hostname, + ) + + self.log.info("[{}] waking up".format(self.__class__.__name__)) + self.load_prefs() + + # menu auto-refresh defaults + + if not self.prefs_global.get("menu_auto_refresh"): + self.prefs_global["menu_auto_refresh"] = { + "media_panel": True, + "batch": True, + "main_menu": True, + "timeline_menu": True + } + + self.apps = [] + + def load_prefs(self): + prefix = self.prefs_folder + os.path.sep + self.bundle_name + prefs_file_path = (prefix + "." + self.flame_user_name + "." + + self.flame_project_name + ".prefs") + prefs_user_file_path = (prefix + "." + self.flame_user_name + + ".prefs") + prefs_global_file_path = prefix + ".prefs" + + try: + with open(prefs_file_path, "r") as prefs_file: + self.prefs = pickle.load(prefs_file) + + self.log.info("preferences loaded from {}".format(prefs_file_path)) + self.log.info("preferences contents:\n" + pformat(self.prefs)) + except: + self.log.info("unable to load preferences from {}".format( + prefs_file_path)) + + try: + with open(prefs_user_file_path, "r") as prefs_file: + self.prefs_user = pickle.load(prefs_file) + self.log.info("preferences loaded from {}".format( + prefs_user_file_path)) + self.log.info("preferences contents:\n" + pformat(self.prefs_user)) + except: + self.log.info("unable to load preferences from {}".format( + prefs_user_file_path)) + + try: + with open(prefs_global_file_path, "r") as prefs_file: + self.prefs_global = pickle.load(prefs_file) + self.log.info("preferences loaded from {}".format( + prefs_global_file_path)) + self.log.info("preferences contents:\n" + pformat(self.prefs_global)) + + except: + self.log.info("unable to load preferences from {}".format( + prefs_global_file_path)) + + return True + + def save_prefs(self): + import pickle + + if not os.path.isdir(self.prefs_folder): + try: + os.makedirs(self.prefs_folder) + except: + self.log.info("unable to create folder {}".format( + self.prefs_folder)) + return False + + prefix = self.prefs_folder + os.path.sep + self.bundle_name + prefs_file_path = prefix + "." + self.flame_user_name + "." + self.flame_project_name + ".prefs" + prefs_user_file_path = prefix + "." + self.flame_user_name + ".prefs" + prefs_global_file_path = prefix + ".prefs" + + try: + prefs_file = open(prefs_file_path, "w") + pickle.dump(self.prefs, prefs_file) + prefs_file.close() + self.log.info("preferences saved to {}".format(prefs_file_path)) + self.log.info("preferences contents:\n" + pformat(self.prefs)) + except: + self.log.info("unable to save preferences to {}".format(prefs_file_path)) + + try: + prefs_file = open(prefs_user_file_path, "w") + pickle.dump(self.prefs_user, prefs_file) + prefs_file.close() + self.log.info("preferences saved to {}".format(prefs_user_file_path)) + self.log.info("preferences contents:\n" + pformat(self.prefs_user)) + except: + self.log.info("unable to save preferences to {}".format(prefs_user_file_path)) + + try: + prefs_file = open(prefs_global_file_path, "w") + pickle.dump(self.prefs_global, prefs_file) + prefs_file.close() + self.log.info("preferences saved to {}".format(prefs_global_file_path)) + self.log.info("preferences contents:\n" + pformat(self.prefs_global)) + except: + self.log.info("unable to save preferences to {}".format(prefs_global_file_path)) + + return True + + +@contextlib.contextmanager +def maintain_current_timeline(to_timeline, from_timeline=None): + """Maintain current timeline selection during context + + Attributes: + from_timeline (resolve.Timeline)[optional]: + Example: + >>> print(from_timeline.GetName()) + timeline1 + >>> print(to_timeline.GetName()) + timeline2 + + >>> with maintain_current_timeline(to_timeline): + ... print(get_current_timeline().GetName()) + timeline2 + + >>> print(get_current_timeline().GetName()) + timeline1 + """ + project = get_current_project() + working_timeline = from_timeline or project.GetCurrentTimeline() + + # swith to the input timeline + project.SetCurrentTimeline(to_timeline) + + try: + # do a work + yield + finally: + # put the original working timeline to context + project.SetCurrentTimeline(working_timeline) + + +def get_project_manager(): + # TODO: get_project_manager + return + + +def get_media_storage(): + # TODO: get_media_storage + return + + +def get_current_project(): + # TODO: get_current_project + return + + +def get_current_timeline(new=False): + # TODO: get_current_timeline + return + + +def create_bin(name, root=None): + # TODO: create_bin + return + + +def rescan_hooks(): + import flame + try: + flame.execute_shortcut('Rescan Python Hooks') + except: + pass diff --git a/openpype/hosts/flame/api/menu.py b/openpype/hosts/flame/api/menu.py new file mode 100644 index 0000000000..65d1535beb --- /dev/null +++ b/openpype/hosts/flame/api/menu.py @@ -0,0 +1,199 @@ +import os +import sys +from Qt import QtWidgets, QtCore +from pprint import pprint, pformat +from copy import deepcopy + +from .lib import rescan_hooks +from openpype.tools.utils.host_tools import HostToolsHelper + + +menu_group_name = 'OpenPype' + +default_flame_export_presets = { + 'Publish': {'PresetVisibility': 2, 'PresetType': 0, 'PresetFile': 'OpenEXR/OpenEXR (16-bit fp PIZ).xml'}, + 'Preview': {'PresetVisibility': 3, 'PresetType': 2, 'PresetFile': 'Generate Preview.xml'}, + 'Thumbnail': {'PresetVisibility': 3, 'PresetType': 0, 'PresetFile': 'Generate Thumbnail.xml'} +} + + +class _FlameMenuApp(object): + def __init__(self, framework): + self.name = self.__class__.__name__ + self.framework = framework + self.log = framework.log + self.menu_group_name = menu_group_name + self.dynamic_menu_data = {} + + # flame module is only avaliable when a + # flame project is loaded and initialized + self.flame = None + try: + import flame + self.flame = flame + except: + self.flame = None + + self.flame_project_name = flame.project.current_project.name + self.prefs = self.framework.prefs_dict(self.framework.prefs, self.name) + self.prefs_user = self.framework.prefs_dict( + self.framework.prefs_user, self.name) + self.prefs_global = self.framework.prefs_dict( + self.framework.prefs_global, self.name) + + self.mbox = QtWidgets.QMessageBox() + + self.menu = { + "actions": [{ + 'name': os.getenv("AVALON_PROJECT", "project"), + 'isEnabled': False + }], + "name": self.menu_group_name + } + self.tools_helper = HostToolsHelper() + + def __getattr__(self, name): + def method(*args, **kwargs): + print('calling %s' % name) + return method + + def rescan(self, *args, **kwargs): + if not self.flame: + try: + import flame + self.flame = flame + except: + self.flame = None + + if self.flame: + self.flame.execute_shortcut('Rescan Python Hooks') + self.log.info('Rescan Python Hooks') + + +class FlameMenuProjectconnect(_FlameMenuApp): + + # flameMenuProjectconnect app takes care of the preferences dialog as well + + def __init__(self, framework): + _FlameMenuApp.__init__(self, framework) + + def __getattr__(self, name): + def method(*args, **kwargs): + project = self.dynamic_menu_data.get(name) + if project: + self.link_project(project) + return method + + def build_menu(self): + if not self.flame: + return [] + + flame_project_name = self.flame_project_name + self.log.info("______ {} ______".format(flame_project_name)) + + menu = deepcopy(self.menu) + + menu['actions'].append({ + "name": "Workfiles ...", + "execute": lambda x: self.tools_helper.show_workfiles() + }) + menu['actions'].append({ + "name": "Create ...", + "execute": lambda x: self.tools_helper.show_creator() + }) + menu['actions'].append({ + "name": "Publish ...", + "execute": lambda x: self.tools_helper.show_publish() + }) + menu['actions'].append({ + "name": "Load ...", + "execute": lambda x: self.tools_helper.show_loader() + }) + menu['actions'].append({ + "name": "Manage ...", + "execute": lambda x: self.tools_helper.show_scene_inventory() + }) + menu['actions'].append({ + "name": "Library ...", + "execute": lambda x: self.tools_helper.show_library_loader() + }) + return menu + + def get_projects(self, *args, **kwargs): + pass + + def refresh(self, *args, **kwargs): + self.rescan() + + def rescan(self, *args, **kwargs): + if not self.flame: + try: + import flame + self.flame = flame + except: + self.flame = None + + if self.flame: + self.flame.execute_shortcut('Rescan Python Hooks') + self.log.info('Rescan Python Hooks') + + +class FlameMenuTimeline(_FlameMenuApp): + + # flameMenuProjectconnect app takes care of the preferences dialog as well + + def __init__(self, framework): + _FlameMenuApp.__init__(self, framework) + + def __getattr__(self, name): + def method(*args, **kwargs): + project = self.dynamic_menu_data.get(name) + if project: + self.link_project(project) + return method + + def build_menu(self): + if not self.flame: + return [] + + flame_project_name = self.flame_project_name + self.log.info("______ {} ______".format(flame_project_name)) + + menu = deepcopy(self.menu) + + menu['actions'].append({ + "name": "Create ...", + "execute": lambda x: self.tools_helper.show_creator() + }) + menu['actions'].append({ + "name": "Publish ...", + "execute": lambda x: self.tools_helper.show_publish() + }) + menu['actions'].append({ + "name": "Load ...", + "execute": lambda x: self.tools_helper.show_loader() + }) + menu['actions'].append({ + "name": "Manage ...", + "execute": lambda x: self.tools_helper.show_scene_inventory() + }) + + return menu + + def get_projects(self, *args, **kwargs): + pass + + def refresh(self, *args, **kwargs): + self.rescan() + + def rescan(self, *args, **kwargs): + if not self.flame: + try: + import flame + self.flame = flame + except: + self.flame = None + + if self.flame: + self.flame.execute_shortcut('Rescan Python Hooks') + self.log.info('Rescan Python Hooks') diff --git a/openpype/hosts/flame/api/pipeline.py b/openpype/hosts/flame/api/pipeline.py new file mode 100644 index 0000000000..297ab0e44c --- /dev/null +++ b/openpype/hosts/flame/api/pipeline.py @@ -0,0 +1,162 @@ +""" +Basic avalon integration +""" +import os +import contextlib +from collections import OrderedDict +from avalon.tools import workfiles +from avalon import api as avalon +from avalon import schema +from avalon.pipeline import AVALON_CONTAINER_ID +from pyblish import api as pyblish +from openpype.api import Logger +from . import lib + +AVALON_CONTAINERS = "AVALON_CONTAINERS" + +log = Logger().get_logger(__name__) + + + +def install(): + from .. import ( + PUBLISH_PATH, + LOAD_PATH, + CREATE_PATH, + INVENTORY_PATH + ) + # TODO: install + + # Disable all families except for the ones we explicitly want to see + family_states = [ + "imagesequence", + "render2d", + "plate", + "render", + "mov", + "clip" + ] + avalon.data["familiesStateDefault"] = False + avalon.data["familiesStateToggled"] = family_states + + log.info("openpype.hosts.flame installed") + + pyblish.register_host("flame") + pyblish.register_plugin_path(PUBLISH_PATH) + log.info("Registering DaVinci Resovle plug-ins..") + + avalon.register_plugin_path(avalon.Loader, LOAD_PATH) + avalon.register_plugin_path(avalon.Creator, CREATE_PATH) + avalon.register_plugin_path(avalon.InventoryAction, INVENTORY_PATH) + + # register callback for switching publishable + pyblish.register_callback("instanceToggled", on_pyblish_instance_toggled) + + +def uninstall(): + from .. import ( + PUBLISH_PATH, + LOAD_PATH, + CREATE_PATH, + INVENTORY_PATH + ) + + # TODO: uninstall + pyblish.deregister_host("flame") + pyblish.deregister_plugin_path(PUBLISH_PATH) + log.info("Deregistering DaVinci Resovle plug-ins..") + + avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH) + avalon.deregister_plugin_path(avalon.Creator, CREATE_PATH) + avalon.deregister_plugin_path(avalon.InventoryAction, INVENTORY_PATH) + + # register callback for switching publishable + pyblish.deregister_callback("instanceToggled", on_pyblish_instance_toggled) + + +def containerise(tl_segment, + name, + namespace, + context, + loader=None, + data=None): + # TODO: containerise + pass + + +def ls(): + """List available containers. + """ + # TODO: ls + pass + + +def parse_container(tl_segment, validate=True): + """Return container data from timeline_item's openpype tag. + """ + # TODO: parse_container + pass + + +def update_container(tl_segment, data=None): + """Update container data to input timeline_item's openpype tag. + """ + # TODO: update_container + pass + + +@contextlib.contextmanager +def maintained_selection(): + """Maintain selection during context + + Example: + >>> with maintained_selection(): + ... node['selected'].setValue(True) + >>> print(node['selected'].value()) + False + """ + # TODO: maintained_selection + remove undo steps + + try: + # do the operation + yield + finally: + pass + + +def reset_selection(): + """Deselect all selected nodes + """ + pass + + +def on_pyblish_instance_toggled(instance, old_value, new_value): + """Toggle node passthrough states on instance toggles.""" + + log.info("instance toggle: {}, old_value: {}, new_value:{} ".format( + instance, old_value, new_value)) + + from openpype.hosts.resolve import ( + set_publish_attribute + ) + + # Whether instances should be passthrough based on new value + timeline_item = instance.data["item"] + set_publish_attribute(timeline_item, new_value) + + +def remove_instance(instance): + """Remove instance marker from track item.""" + # TODO: remove_instance + pass + + +def list_instances(): + """List all created instances from current workfile.""" + # TODO: list_instances + pass + + +def imprint(item, data=None): + # TODO: imprint + pass diff --git a/openpype/hosts/flame/api/plugin.py b/openpype/hosts/flame/api/plugin.py new file mode 100644 index 0000000000..ac86c7c224 --- /dev/null +++ b/openpype/hosts/flame/api/plugin.py @@ -0,0 +1,13 @@ +import re +import uuid +from avalon import api +import openpype.api as pype +from openpype.hosts import resolve +from avalon.vendor import qargparse +from . import lib + +from Qt import QtWidgets, QtCore + +# Creator plugin functions +# Publishing plugin functions +# Loader plugin functions diff --git a/openpype/hosts/flame/api/utils.py b/openpype/hosts/flame/api/utils.py new file mode 100644 index 0000000000..489b51e37c --- /dev/null +++ b/openpype/hosts/flame/api/utils.py @@ -0,0 +1,91 @@ +#! python3 + +""" +Resolve's tools for setting environment +""" + +import os +import shutil +from openpype.api import Logger +log = Logger().get_logger(__name__) + + +def _sync_utility_scripts(env=None): + """ Synchronizing basic utlility scripts for resolve. + + To be able to run start OpenPype within Flame we have to copy + all utility_scripts and additional FLAME_SCRIPT_DIR into + `/opt/Autodesk/shared/python`. This will be always synchronizing those + folders. + """ + from .. import HOST_DIR + + if not env: + env = os.environ + + # initiate inputs + scripts = {} + fsd_env = env.get("FLAME_SCRIPT_DIR", "") + flame_shared_dir = "/opt/Autodesk/shared/python" + + fsd_paths = [os.path.join( + HOST_DIR, + "utility_scripts" + )] + + # collect script dirs + log.info("FLAME_SCRIPT_DIR: `{fsd_env}`".format(**locals())) + log.info("fsd_paths: `{fsd_paths}`".format(**locals())) + + # add application environment setting for FLAME_SCRIPT_DIR + # to script path search + for _dirpath in fsd_env.split(os.pathsep): + if not os.path.isdir(_dirpath): + log.warning("Path is not a valid dir: `{_dirpath}`".format(**locals())) + continue + fsd_paths.append(_dirpath) + + # collect scripts from dirs + for path in fsd_paths: + scripts.update({path: os.listdir(path)}) + + log.info("Additional Flame script paths: `{fsd_paths}`".format(**locals())) + log.info("Flame Scripts: `{scripts}`".format(**locals())) + + # make sure no script file is in folder + if next(iter(os.listdir(flame_shared_dir)), None): + for s in os.listdir(flame_shared_dir): + path = os.path.join(flame_shared_dir, s) + log.info("Removing `{path}`...".format(**locals())) + if os.path.isdir(path): + shutil.rmtree(path, onerror=None) + else: + os.remove(path) + + # copy scripts into Resolve's utility scripts dir + for dirpath, scriptlist in scripts.items(): + # directory and scripts list + for _script in scriptlist: + # script in script list + src = os.path.join(dirpath, _script) + dst = os.path.join(flame_shared_dir, _script) + log.info("Copying `{src}` to `{dst}`...".format(**locals())) + if os.path.isdir(src): + shutil.copytree( + src, dst, symlinks=False, + ignore=None, ignore_dangling_symlinks=False + ) + else: + shutil.copy2(src, dst) + + +def setup(env=None): + """ Wrapper installer started from pype.hooks.resolve.FlamePrelaunch() + """ + if not env: + env = os.environ + + # synchronize resolve utility scripts + _sync_utility_scripts(env) + + log.info("Flame OpenPype wrapper has been installed") diff --git a/openpype/hosts/flame/api/workio.py b/openpype/hosts/flame/api/workio.py new file mode 100644 index 0000000000..00fcdb9405 --- /dev/null +++ b/openpype/hosts/flame/api/workio.py @@ -0,0 +1,37 @@ +"""Host API required Work Files tool""" + +import os +from openpype.api import Logger +from .. import ( + get_project_manager, + get_current_project +) + + +log = Logger().get_logger(__name__) + +exported_projet_ext = ".otoc" + + +def file_extensions(): + return [exported_projet_ext] + + +def has_unsaved_changes(): + pass + + +def save_file(filepath): + pass + + +def open_file(filepath): + pass + + +def current_file(): + pass + + +def work_root(session): + return os.path.normpath(session["AVALON_WORKDIR"]).replace("\\", "/") From 48cbd28deb0444b53e207123b92f11848c5ec632 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 21 Oct 2021 14:30:54 +0200 Subject: [PATCH 220/279] Flame: adding prelauch hook --- openpype/hosts/flame/hooks/pre_flame_setup.py | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 openpype/hosts/flame/hooks/pre_flame_setup.py diff --git a/openpype/hosts/flame/hooks/pre_flame_setup.py b/openpype/hosts/flame/hooks/pre_flame_setup.py new file mode 100644 index 0000000000..aec9a15e30 --- /dev/null +++ b/openpype/hosts/flame/hooks/pre_flame_setup.py @@ -0,0 +1,116 @@ +import os +import json +import tempfile +from openpype.lib import ( + PreLaunchHook, get_openpype_username) +from openpype.hosts import flame as opflame +import openpype +from pprint import pformat + + +class FlamePrelaunch(PreLaunchHook): + """ Flame prelaunch hook + + Will make sure flame_script_dirs are coppied to user's folder defined + in environment var FLAME_SCRIPT_DIR. + """ + app_groups = ["flame"] + + # todo: replace version number with avalon launch app version + flame_python_exe = "/opt/Autodesk/python/2021/bin/python2.7" + + wtc_script_path = os.path.join( + opflame.HOST_DIR, "scripts", "wiretap_com.py") + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.signature = "( {} )".format(self.__class__.__name__) + + def execute(self): + """Hook entry method.""" + project_doc = self.data["project_doc"] + user_name = get_openpype_username() + + self.log.debug("Collected user \"{}\"".format(user_name)) + self.log.info(pformat(project_doc)) + _db_p_data = project_doc["data"] + width = _db_p_data["resolutionWidth"] + height = _db_p_data["resolutionHeight"] + fps = int(_db_p_data["fps"]) + + project_data = { + "Name": project_doc["name"], + "Nickname": _db_p_data["code"], + "Description": "Created by OpenPype", + "SetupDir": project_doc["name"], + "FrameWidth": int(width), + "FrameHeight": int(height), + "AspectRatio": float((width / height) * _db_p_data["pixelAspect"]), + "FrameRate": "{} fps".format(fps), + "FrameDepth": "16-bit fp", + "FieldDominance": "PROGRESSIVE" + } + + data_to_script = { + # from settings + "host_name": "localhost", + "volume_name": "stonefs", + "group_name": "staff", + "color_policy": "ACES 1.1", + + # from project + "project_name": project_doc["name"], + "user_name": user_name, + "project_data": project_data + } + app_arguments = self._get_launch_arguments(data_to_script) + + self.log.info(pformat(dict(self.launch_context.env))) + + opflame.setup(self.launch_context.env) + + self.launch_context.launch_args.extend(app_arguments) + + def _get_launch_arguments(self, script_data): + # Dump data to string + dumped_script_data = json.dumps(script_data) + + # Store dumped json to temporary file + temporary_json_file = tempfile.NamedTemporaryFile( + mode="w", suffix=".json", delete=False + ) + temporary_json_file.write(dumped_script_data) + temporary_json_file.close() + temporary_json_filepath = temporary_json_file.name.replace( + "\\", "/" + ) + + # Prepare subprocess arguments + args = [ + self.flame_python_exe, + self.wtc_script_path, + temporary_json_filepath + ] + self.log.info("Executing: {}".format(" ".join(args))) + + process_kwargs = { + "logger": self.log, + "env": {} + } + + openpype.api.run_subprocess(args, **process_kwargs) + + # process returned json file to pass launch args + return_json_data = open(temporary_json_filepath).read() + returned_data = json.loads(return_json_data) + app_args = returned_data.get("app_args") + self.log.info("____ app_args: `{}`".format(app_args)) + + if not app_args: + RuntimeError("App arguments were not solved") + + # Remove the temporary json + os.remove(temporary_json_filepath) + + return app_args From 0ec42fca3fbecfca25b53959af0949cb1feb94f0 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 21 Oct 2021 14:31:16 +0200 Subject: [PATCH 221/279] Flame: adding WireTap communication script --- openpype/hosts/flame/scripts/wiretap_com.py | 481 ++++++++++++++++++++ 1 file changed, 481 insertions(+) create mode 100644 openpype/hosts/flame/scripts/wiretap_com.py diff --git a/openpype/hosts/flame/scripts/wiretap_com.py b/openpype/hosts/flame/scripts/wiretap_com.py new file mode 100644 index 0000000000..a5925d0546 --- /dev/null +++ b/openpype/hosts/flame/scripts/wiretap_com.py @@ -0,0 +1,481 @@ +#!/usr/bin/env python2.7 +# -*- coding: utf-8 -*- + +from __future__ import absolute_import +import os +import sys +import subprocess +import json +import xml.dom.minidom as minidom +from copy import deepcopy +import datetime + +# Todo: this has to be replaced with somehting more dynamic +flame_python_path = "/opt/Autodesk/flame_2021/python" +flame_exe_path = "/opt/Autodesk/flame_2021/bin/flame.app/Contents/MacOS/startApp" + +sys.path.append(flame_python_path) + +from libwiretapPythonClientAPI import ( + WireTapClientInit, + WireTapClientUninit, + WireTapNodeHandle, + WireTapServerHandle, + WireTapInt, + WireTapStr +) + +class WireTapCom(object): + """ + Comunicator class wrapper for talking to WireTap db. + + This way we are able to set new project with settings and + correct colorspace policy. Also we are able to create new user + or get actuall user with similar name (users are usually cloning + their profiles and adding date stamp into suffix). + """ + + def __init__(self, host_name=None, volume_name=None, group_name=None): + """Initialisation of WireTap communication class + + Args: + host_name (str, optional): Name of host server. Defaults to None. + volume_name (str, optional): Name of volume. Defaults to None. + group_name (str, optional): Name of user group. Defaults to None. + """ + # set main attributes of server + # if there are none set the default installation + self.host_name = host_name or "localhost" + self.volume_name = volume_name or "stonefs" + self.group_name = group_name or "staff" + + # initialize WireTap client + WireTapClientInit() + + # add the server to shared variable + self._server = WireTapServerHandle("{}:IFFFS".format(self.host_name)) + print("WireTap connected at '{}'...".format( + self.host_name)) + + def close(self): + self._server = None + WireTapClientUninit() + print("WireTap closed...") + + def get_launch_args( + self, project_name, project_data, user_name, *args, **kwargs): + """Forming launch arguments for OpenPype launcher. + + Args: + project_name (str): name of project + project_data (dict): Flame compatible project data + user_name (str): name of user + + Returns: + list: arguments + """ + + workspace_name = kwargs.get("workspace_name") + color_policy = kwargs.get("color_policy") + + self._project_prep(project_name) + self._set_project_settings(project_name, project_data) + self._set_project_colorspace(project_name, color_policy) + user_name = self._user_prep(user_name) + + if workspace_name is None: + # default workspace + print("Using a default workspace") + return [ + "--start-project={}".format(project_name), + "--start-user={}".format(user_name), + "--create-workspace" + ] + + else: + print( + "Using a custom workspace '{}'".format(workspace_name)) + + self._workspace_prep(project_name, workspace_name) + return [ + "--start-project={}".format(project_name), + "--start-user={}".format(user_name), + "--create-workspace", + "--start-workspace={}".format(workspace_name) + ] + + def _workspace_prep(self, project_name, workspace_name): + """Preparing a workspace + + In case it doesn not exists it will create one + + Args: + project_name (str): project name + workspace_name (str): workspace name + + Raises: + AttributeError: unable to create workspace + """ + workspace_exists = self._child_is_in_parent_path( + "/projects/{}".format(project_name), workspace_name, "WORKSPACE" + ) + if not workspace_exists: + project = WireTapNodeHandle( + self._server, "/projects/{}".format(project_name)) + + workspace_node = WireTapNodeHandle() + created_workspace = project.createNode( + workspace_name, "WORKSPACE", workspace_node) + + if not created_workspace: + raise AttributeError( + "Cannot create workspace `{}` in " + "project `{}`: `{}`".format( + workspace_name, project_name, project.lastError()) + ) + + print( + "Workspace `{}` is successfully created".format(workspace_name)) + + def _project_prep(self, project_name): + """Preparing a project + + In case it doesn not exists it will create one + + Args: + project_name (str): project name + + Raises: + AttributeError: unable to create project + """ + # test if projeft exists + project_exists = self._child_is_in_parent_path( + "/projects", project_name, "PROJECT") + + if not project_exists: + volumes = self._get_all_volumes() + + if len(volumes) == 0: + raise AttributeError( + "Not able to create new project. No Volumes existing" + ) + + # check if volumes exists + if self.volume_name not in volumes: + raise AttributeError( + ("Volume '{}' does not exist '{}'").format( + self.volume_name, volumes) + ) + + # form cmd arguments + project_create_cmd = [ + os.path.join( + "/opt/Autodesk/", + "wiretap", + "tools", + "2021", + "wiretap_create_node", + ), + '-n', + os.path.join("/volumes", self.volume_name), + '-d', + project_name, + '-g', + ] + + project_create_cmd.append(self.group_name) + + print(project_create_cmd) + + exit_code = subprocess.call( + project_create_cmd, + cwd=os.path.expanduser('~')) + + if exit_code != 0: + RuntimeError("Cannot create project in flame db") + + print( + "A new project '{}' is created.".format(project_name)) + + def _get_all_volumes(self): + """Request all available volumens from WireTap + + Returns: + list: all available volumes in server + + Rises: + AttributeError: unable to get any volumes childs from server + """ + root = WireTapNodeHandle(self._server, "/volumes") + children_num = WireTapInt(0) + + get_children_num = root.getNumChildren(children_num) + if not get_children_num: + raise AttributeError( + "Cannot get number of volumes: {}".format(root.lastError()) + ) + + volumes = [] + + # go trough all children and get volume names + child_obj = WireTapNodeHandle() + for child_idx in range(children_num): + + # get a child + if not root.getChild(child_idx, child_obj): + raise AttributeError( + "Unable to get child: {}".format(root.lastError())) + + node_name = WireTapStr() + get_children_name = child_obj.getDisplayName(node_name) + + if not get_children_name: + raise AttributeError( + "Unable to get child name: {}".format(child_obj.lastError()) + ) + + volumes.append(node_name.c_str()) + + return volumes + + def _user_prep(self, user_name): + """Ensuring user does exists in user's stack + + Args: + user_name (str): name of a user + + Raises: + AttributeError: unable to create user + """ + + # get all used usernames in db + used_names = self._get_usernames() + print(">> used_names: {}".format(used_names)) + + # filter only those which are sharing input user name + filtered_users = [user for user in used_names if user_name in user] + + if filtered_users: + # todo: need to find lastly created following regex patern for date used in name + return filtered_users.pop() + + # create new user name with date in suffix + now = datetime.datetime.now() # current date and time + date = now.strftime("%Y%m%d") + new_user_name = "{}_{}".format(user_name, date) + print(new_user_name) + + if not self._child_is_in_parent_path("/users", new_user_name, "USER"): + # Create the new user + users = WireTapNodeHandle(self._server, "/users") + + user_node = WireTapNodeHandle() + created_user = users.createNode(new_user_name, "USER", user_node) + if not created_user: + raise AttributeError( + "User {} cannot be created: {}".format( + new_user_name, users.lastError()) + ) + + print("User `{}` is created".format(new_user_name)) + return new_user_name + + def _get_usernames(self): + """Requesting all available users from WireTap + + Returns: + list: all available user names + + Raises: + AttributeError: there are no users in server + """ + root = WireTapNodeHandle(self._server, "/users") + children_num = WireTapInt(0) + + get_children_num = root.getNumChildren(children_num) + if not get_children_num: + raise AttributeError( + "Cannot get number of volumes: {}".format(root.lastError()) + ) + + usernames = [] + + # go trough all children and get volume names + child_obj = WireTapNodeHandle() + for child_idx in range(children_num): + + # get a child + if not root.getChild(child_idx, child_obj): + raise AttributeError( + "Unable to get child: {}".format(root.lastError())) + + node_name = WireTapStr() + get_children_name = child_obj.getDisplayName(node_name) + + if not get_children_name: + raise AttributeError( + "Unable to get child name: {}".format(child_obj.lastError()) + ) + + usernames.append(node_name.c_str()) + + return usernames + + def _child_is_in_parent_path(self, parent_path, child_name, child_type): + """Checking if a given child is in parent path. + + Args: + parent_path (str): db path to parent + child_name (str): name of child + child_type (str): type of child + + Raises: + AttributeError: Not able to get number of children + AttributeError: Not able to get children form parent + AttributeError: Not able to get children name + AttributeError: Not able to get children type + + Returns: + bool: True if child is in parent path + """ + parent = WireTapNodeHandle(self._server, parent_path) + + # iterate number of children + children_num = WireTapInt(0) + requested = parent.getNumChildren(children_num) + if not requested: + raise AttributeError(( + "Error: Cannot request number of " + "childrens from the node {}. Make sure your " + "wiretap service is running: {}").format( + parent_path, parent.lastError()) + ) + + # iterate children + child_obj = WireTapNodeHandle() + for child_idx in range(children_num): + if not parent.getChild(child_idx, child_obj): + raise AttributeError( + "Cannot get child: {}".format( + parent.lastError())) + + node_name = WireTapStr() + node_type = WireTapStr() + + if not child_obj.getDisplayName(node_name): + raise AttributeError( + "Unable to get child name: %s" % child_obj.lastError() + ) + if not child_obj.getNodeTypeStr(node_type): + raise AttributeError( + "Unable to obtain child type: %s" % child_obj.lastError() + ) + + if (node_name.c_str() == child_name) and ( + node_type.c_str() == child_type): + return True + + return False + + def _set_project_settings(self, project_name, project_data): + """Setting project attributes. + + Args: + project_name (str): name of project + project_data (dict): data with project attributes + (flame compatible) + + Raises: + AttributeError: Not able to set project attributes + """ + # generated xml from project_data dict + _xml = "" + for key, value in project_data.items(): + _xml += "<{}>{}".format(key, value, key) + _xml += "" + + pretty_xml = minidom.parseString(_xml).toprettyxml() + print("__ xml: {}".format(pretty_xml)) + + # set project data to wiretap + project_node = WireTapNodeHandle( + self._server, "/projects/{}".format(project_name)) + + if not project_node.setMetaData("XML", _xml): + raise AttributeError( + "Not able to set project attributes {}. Error: {}".format( + project_name, project_node.lastError()) + ) + + print("Project settings successfully set.") + + def _set_project_colorspace(self, project_name, color_policy): + """Set project's colorspace policy. + + Args: + project_name (str): name of project + color_policy (str): name of policy + + Raises: + RuntimeError: Not able to set colorspace policy + """ + color_policy = color_policy or "Legacy" + project_colorspace_cmd = [ + os.path.join( + "/opt/Autodesk/", + "wiretap", + "tools", + "2021", + "wiretap_duplicate_node", + ), + "-s", + "/syncolor/policies/Autodesk/{}".format(color_policy), + "-n", + "/projects/{}/syncolor".format(project_name) + ] + + print(project_colorspace_cmd) + + exit_code = subprocess.call( + project_colorspace_cmd, + cwd=os.path.expanduser('~')) + + if exit_code != 0: + RuntimeError("Cannot set colorspace {} on project {}".format( + color_policy, project_name + )) + + +if __name__ == "__main__": + # get json exchange data + json_path = sys.argv[-1] + json_data = open(json_path).read() + in_data = json.loads(json_data) + out_data = deepcopy(in_data) + + # get main server attributes + host_name = in_data.pop("host_name") + volume_name = in_data.pop("volume_name") + group_name = in_data.pop("group_name") + + # initialize class + wiretap_handler = WireTapCom(host_name, volume_name, group_name) + + try: + app_args = wiretap_handler.get_launch_args( + project_name=in_data.pop("project_name"), + project_data=in_data.pop("project_data"), + user_name=in_data.pop("user_name"), + **in_data + ) + finally: + wiretap_handler.close() + + # set returned args back to out data + out_data.update({ + "app_args": app_args + }) + + # write it out back to the exchange json file + with open(json_path, "w") as file_stream: + json.dump(out_data, file_stream, indent=4) From 8fac3175a0a23dbd6361bb6cc698a6eeb499406b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 21 Oct 2021 14:33:14 +0200 Subject: [PATCH 222/279] Flame: adding flame_hook utility script for launching withing flame --- .../hosts/flame/utility_scripts/flame_hook.py | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 openpype/hosts/flame/utility_scripts/flame_hook.py diff --git a/openpype/hosts/flame/utility_scripts/flame_hook.py b/openpype/hosts/flame/utility_scripts/flame_hook.py new file mode 100644 index 0000000000..b46109a609 --- /dev/null +++ b/openpype/hosts/flame/utility_scripts/flame_hook.py @@ -0,0 +1,131 @@ +import sys +from Qt import QtWidgets, QtCore +from pprint import pprint, pformat +import atexit +import openpype +import avalon +import openpype.hosts.flame as opflame + +flh = sys.modules[__name__] +flh._project = None + + +def openpype_install(): + openpype.install() + avalon.api.install(opflame) + print("<<<<<<<<<<< Avalon registred hosts: {} >>>>>>>>>>>>>>>".format( + avalon.api.registered_host())) + + +# Exception handler +def exeption_handler(exctype, value, tb): + import traceback + msg = "OpenPype: Python exception {} in {}".format(value, exctype) + mbox = QtWidgets.QMessageBox() + mbox.setText(msg) + mbox.setDetailedText( + pformat(traceback.format_exception(exctype, value, tb))) + mbox.setStyleSheet('QLabel{min-width: 800px;}') + mbox.exec_() + sys.__excepthook__(exctype, value, tb) + + +# add exception handler into sys module +sys.excepthook = exeption_handler + + +# register clean up logic to be called at Flame exit +def cleanup(): + if opflame.apps: + print('<<<< `{}` cleaning up apps:\n {}\n'.format( + __file__, pformat(opflame.apps))) + while len(opflame.apps): + app = opflame.apps.pop() + print('<<<< `{}` removing : {}'.format(__file__, app.name)) + del app + opflame.apps = [] + + if opflame.app_framework: + print('PYTHON\t: %s cleaning up' % opflame.app_framework.bundle_name) + opflame.app_framework.save_prefs() + opflame.app_framework = None + + +atexit.register(cleanup) + + +def load_apps(): + opflame.apps.append(opflame.FlameMenuProjectconnect(opflame.app_framework)) + opflame.apps.append(opflame.FlameMenuTimeline(opflame.app_framework)) + opflame.app_framework.log.info("Apps are loaded") + + +def project_changed_dict(info): + cleanup() + + +def app_initialized(parent=None): + opflame.app_framework = opflame.FlameAppFramework() + + print(">> flame_hook.py: {} initializing".format( + opflame.app_framework.bundle_name)) + + load_apps() + + +try: + import flame + app_initialized(parent=None) +except ImportError: + print("!!!! not able to import flame module !!!!") + + +def rescan_hooks(): + import flame + try: + flame.execute_shortcut('Rescan Python Hooks') + except: + pass + + +def _build_app_menu(app_name): + menu = [] + app = None + for _app in opflame.apps: + if _app.__class__.__name__ == app_name: + app = _app + + if app: + menu.append(app.build_menu()) + + print(">>_> `{}` was build: {}".format(app_name, pformat(menu))) + + if opflame.app_framework: + menu_auto_refresh = opflame.app_framework.prefs_global.get( + 'menu_auto_refresh', {}) + if menu_auto_refresh.get('timeline_menu', True): + try: + import flame + flame.schedule_idle_event(rescan_hooks) + except ImportError: + print("!-!!! not able to import flame module !!!!") + + return menu + +def project_saved(project_name, save_time, is_auto_save): + if opflame.app_framework: + opflame.app_framework.save_prefs() + + +def get_main_menu_custom_ui_actions(): + # install openpype and the host + openpype_install() + + return _build_app_menu("FlameMenuProjectconnect") + + +def get_timeline_custom_ui_actions(): + # install openpype and the host + openpype_install() + + return _build_app_menu("FlameMenuTimeline") From 1a1f6e7039e690678af4f31854fb9903e5b82b29 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 21 Oct 2021 14:34:02 +0200 Subject: [PATCH 223/279] Flame: adding settings and icon --- openpype/resources/app_icons/flame.png | Bin 0 -> 74845 bytes .../system_settings/applications.json | 36 ++++++++++++++++ openpype/settings/entities/enum_entity.py | 1 + .../host_settings/schema_flame.json | 39 ++++++++++++++++++ .../system_schema/schema_applications.json | 4 ++ 5 files changed, 80 insertions(+) create mode 100644 openpype/resources/app_icons/flame.png create mode 100644 openpype/settings/entities/schemas/system_schema/host_settings/schema_flame.json diff --git a/openpype/resources/app_icons/flame.png b/openpype/resources/app_icons/flame.png new file mode 100644 index 0000000000000000000000000000000000000000..ba9b69e45fa73d3f9298e77ec4a167a38f7ecf7b GIT binary patch literal 74845 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 mB{bSPYzgNTjDR9V!~XzB%rJgMHNZar0000 Date: Thu, 21 Oct 2021 14:44:39 +0200 Subject: [PATCH 224/279] added few docstrings --- openpype/style/__init__.py | 47 ++++++++++++--- openpype/style/color_defs.py | 109 ++++++++++++++++++++++++++++++++++- 2 files changed, 146 insertions(+), 10 deletions(-) diff --git a/openpype/style/__init__.py b/openpype/style/__init__.py index d763bfdc3c..fd39e93b5d 100644 --- a/openpype/style/__init__.py +++ b/openpype/style/__init__.py @@ -12,12 +12,36 @@ _FONT_IDS = None current_dir = os.path.dirname(os.path.abspath(__file__)) +def _get_colors_raw_data(): + """Read data file with stylesheet fill values. + + Returns: + dict: Loaded data for stylesheet. + """ + data_path = os.path.join(current_dir, "data.json") + with open(data_path, "r") as data_stream: + data = json.load(data_stream) + return data + + def get_colors_data(): + """Only color data from stylesheet data.""" data = _get_colors_raw_data() return data.get("color") or {} def _convert_color_values_to_objects(value): + """Parse all string values in dictionary to Color definitions. + + Recursive function calling itself if value is dictionary. + + Args: + value (dict, str): String is parsed into color definition object and + dictionary is passed into this function. + + Raises: + TypeError: If value in color data do not contain string of dictionary. + """ if isinstance(value, dict): output = {} for _key, _value in value.items(): @@ -32,6 +56,11 @@ def _convert_color_values_to_objects(value): def get_objected_colors(): + """Colors parsed from stylesheet data into color definitions. + + Returns: + dict: Parsed color objects by keys in data. + """ colors_data = get_colors_data() output = {} for key, value in colors_data.items(): @@ -39,14 +68,15 @@ def get_objected_colors(): return output -def _get_colors_raw_data(): - data_path = os.path.join(current_dir, "data.json") - with open(data_path, "r") as data_stream: - data = json.load(data_stream) - return data - - def _load_stylesheet(): + """Load strylesheet and trigger all related callbacks. + + Style require more than a stylesheet string. Stylesheet string + contains paths to resources which must be registered into Qt application + and load fonts used in stylesheets. + + Also replace values from stylesheet data into stylesheet text. + """ from . import qrc_resources qrc_resources.qInitResources() @@ -78,6 +108,7 @@ def _load_stylesheet(): def _load_font(): + """Load and register fonts into Qt application.""" from Qt import QtGui global _FONT_IDS @@ -117,6 +148,7 @@ def _load_font(): def load_stylesheet(): + """Load and return OpenPype Qt stylesheet.""" global _STYLESHEET_CACHE if _STYLESHEET_CACHE is None: _STYLESHEET_CACHE = _load_stylesheet() @@ -125,4 +157,5 @@ def load_stylesheet(): def app_icon_path(): + """Path to OpenPype icon.""" return resources.get_openpype_icon_filepath() diff --git a/openpype/style/color_defs.py b/openpype/style/color_defs.py index 3f504a9d3b..0f4e145ca0 100644 --- a/openpype/style/color_defs.py +++ b/openpype/style/color_defs.py @@ -1,7 +1,27 @@ +"""Color definitions that can be used to parse strings for stylesheet. + +Each definition must have available method `get_qcolor` which should return +`QtGui.QColor` representation of the color. + +# TODO create abstract class to force this method implementation + +Usage: Some colors may be not be used only in stylesheet but is required to +use them in code too. To not hardcode these color values into code it is better +to use same colors that are available fro stylesheets. + +It is possible that some colors may not be used in stylesheet at all and thei +definition is used only in code. +""" + import re def parse_color(value): + """Parse string value of color to one of objected representation. + + Args: + value(str): Color definition usable in stylesheet. + """ modified_value = value.strip().lower() if modified_value.startswith("hsla"): return HSLAColor(value) @@ -21,12 +41,30 @@ def parse_color(value): def create_qcolor(*args): + """Create QtGui.QColor object. + + Args: + *args (tuple): It is possible to pass initialization arguments for + Qcolor. + """ from Qt import QtGui return QtGui.QColor(*args) def min_max_check(value, min_value, max_value): + """Validate number value if is in passed range. + + Args: + value (int, float): Value which is validated. + min_value (int, float): Minimum possible value. Validation is skipped + if passed value is None. + max_value (int, float): Maximum possible value. Validation is skipped + if passed value is None. + + Raises: + ValueError: When 'value' is out of specified range. + """ if min_value is not None and value < min_value: raise ValueError("Minimum expected value is '{}' got '{}'".format( min_value, value @@ -39,6 +77,16 @@ def min_max_check(value, min_value, max_value): def int_validation(value, min_value=None, max_value=None): + """Validation of integer value within range. + + Args: + value (int): Validated value. + min_value (int): Minimum possible value. + max_value (int): Maximum possible value. + + Raises: + TypeError: If 'value' is not 'int' type. + """ if not isinstance(value, int): raise TypeError(( "Invalid type of hue expected 'int' got {}" @@ -48,6 +96,16 @@ def int_validation(value, min_value=None, max_value=None): def float_validation(value, min_value=None, max_value=None): + """Validation of float value within range. + + Args: + value (float): Validated value. + min_value (float): Minimum possible value. + max_value (float): Maximum possible value. + + Raises: + TypeError: If 'value' is not 'float' type. + """ if not isinstance(value, float): raise TypeError(( "Invalid type of hue expected 'int' got {}" @@ -57,6 +115,11 @@ def float_validation(value, min_value=None, max_value=None): class UnknownColor: + """Color from stylesheet data without known color definition. + + This is backup for unknown color definitions which may be for example + constants or definition not yet defined by class. + """ def __init__(self, value): self.value = value @@ -65,6 +128,14 @@ class UnknownColor: class HEXColor: + """Hex color definition. + + Hex color is defined by '#' and 3 or 6 hex values (0-F). + + Examples: + "#fff" + "#f3f3f3" + """ regex = re.compile(r"[a-fA-F0-9]{3}(?:[a-fA-F0-9]{3})?$") def __init__(self, color_string): @@ -92,6 +163,7 @@ class HEXColor: @classmethod def hex_to_rgb(cls, value): + """Convert hex value to rgb.""" hex_value = value.lstrip("#") if not cls.regex.match(hex_value): raise ValueError("\"{}\" is not a valid HEX code.".format(value)) @@ -111,6 +183,13 @@ class HEXColor: class RGBColor: + """Color defined by red green and blue values. + + Each color has possible integer range 0-255. + + Examples: + "rgb(255, 127, 0)" + """ def __init__(self, value): modified_color = value.lower().strip() content = modified_color.rstrip(")").lstrip("rgb(") @@ -146,6 +225,13 @@ class RGBColor: class RGBAColor: + """Color defined by red green, blue and alpha values. + + Each color has possible integer range 0-255. + + Examples: + "rgba(255, 127, 0, 127)" + """ def __init__(self, value): modified_color = value.lower().strip() content = modified_color.rstrip(")").lstrip("rgba(") @@ -191,6 +277,15 @@ class RGBAColor: class HSLColor: + """Color defined by hue, saturation and light values. + + Hue is defined as integer in rage 0-360. Saturation and light can be + defined as float or percent value. + + Examples: + "hsl(27, 0.7, 0.3)" + "hsl(27, 70%, 30%)" + """ def __init__(self, value): modified_color = value.lower().strip() content = modified_color.rstrip(")").lstrip("hsl(") @@ -235,6 +330,16 @@ class HSLColor: class HSLAColor: + """Color defined by hue, saturation, light and alpha values. + + Hue is defined as integer in rage 0-360. Saturation and light can be + defined as float (0-1 range) or percent value(0-100%). And alpha + as float (0-1 range). + + Examples: + "hsl(27, 0.7, 0.3)" + "hsl(27, 70%, 30%)" + """ def __init__(self, value): modified_color = value.lower().strip() content = modified_color.rstrip(")").lstrip("hsla(") @@ -251,10 +356,8 @@ class HSLAColor: light = float(light_str.rstrip("%")) / 100 else: light = float(light_str) - alpha = float(alpha_str) - if isinstance(alpha, int): - alpha = float(alpha) + alpha = float(alpha_str) int_validation(hue, 0, 360) float_validation(sat, 0, 1) From 426c996b71e673ee39edbc84c86170241b9f38b3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 21 Oct 2021 14:50:06 +0200 Subject: [PATCH 225/279] hound fixes in pyside2 resources --- openpype/style/pyside2_resources.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/openpype/style/pyside2_resources.py b/openpype/style/pyside2_resources.py index c7328e7c91..80f9b904fd 100644 --- a/openpype/style/pyside2_resources.py +++ b/openpype/style/pyside2_resources.py @@ -811,10 +811,14 @@ qt_resource_struct = b"\ \x00\x00\x00\xde\x00\x00\x00\x00\x00\x01\x00\x00\x02\xa6\ " + def qInitResources(): - QtCore.qRegisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) + QtCore.qRegisterResourceData( + 0x01, qt_resource_struct, qt_resource_name, qt_resource_data + ) + def qCleanupResources(): - QtCore.qUnregisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) - -qInitResources() + QtCore.qUnregisterResourceData( + 0x01, qt_resource_struct, qt_resource_name, qt_resource_data + ) From 610477961025d7dd7b149668f084baee857aa903 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 21 Oct 2021 15:00:05 +0200 Subject: [PATCH 226/279] renamed 'ExperimentalDialog' to 'ExperimentalToolsDialog' --- openpype/tools/experimental_tools/__init__.py | 4 ++-- openpype/tools/experimental_tools/dialog.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/tools/experimental_tools/__init__.py b/openpype/tools/experimental_tools/__init__.py index 75e3210aab..d6315e4655 100644 --- a/openpype/tools/experimental_tools/__init__.py +++ b/openpype/tools/experimental_tools/__init__.py @@ -3,12 +3,12 @@ from .tools_def import ( LOCAL_EXPERIMENTAL_KEY ) -from .dialog import ExperimentalDialog +from .dialog import ExperimentalToolsDialog __all__ = ( "ExperimentalTools", "LOCAL_EXPERIMENTAL_KEY", - "ExperimentalDialog" + "ExperimentalToolsDialog" ) diff --git a/openpype/tools/experimental_tools/dialog.py b/openpype/tools/experimental_tools/dialog.py index a611416efc..6173deb693 100644 --- a/openpype/tools/experimental_tools/dialog.py +++ b/openpype/tools/experimental_tools/dialog.py @@ -21,11 +21,11 @@ class ToolButton(QtWidgets.QPushButton): self.triggered.emit(self._identifier) -class ExperimentalDialog(QtWidgets.QDialog): +class ExperimentalToolsDialog(QtWidgets.QDialog): refresh_interval = 3000 def __init__(self, parent=None): - super(ExperimentalDialog, self).__init__(parent) + super(ExperimentalToolsDialog, self).__init__(parent) self.setWindowTitle("OpenPype Experimental tools") icon = QtGui.QIcon(app_icon_path()) self.setWindowIcon(icon) @@ -138,7 +138,7 @@ class ExperimentalDialog(QtWidgets.QDialog): tool.execute() def showEvent(self, event): - super(ExperimentalDialog, self).showEvent(event) + super(ExperimentalToolsDialog, self).showEvent(event) if self._refresh_on_active: # Start/Restart timer @@ -164,7 +164,7 @@ class ExperimentalDialog(QtWidgets.QDialog): self._refresh_timer.start() self.refresh() - super(ExperimentalDialog, self).changeEvent(event) + super(ExperimentalToolsDialog, self).changeEvent(event) def _on_refresh_timeout(self): # Stop timer if window is not visible From 0c0809469cddf15bc5412856ed6f478f001cc716 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 21 Oct 2021 15:00:16 +0200 Subject: [PATCH 227/279] added experimental dialog to host tools --- openpype/tools/utils/host_tools.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/openpype/tools/utils/host_tools.py b/openpype/tools/utils/host_tools.py index ee184ccf2d..c0e6d71b73 100644 --- a/openpype/tools/utils/host_tools.py +++ b/openpype/tools/utils/host_tools.py @@ -28,6 +28,7 @@ class HostToolsHelper: self._scene_inventory_tool = None self._library_loader_tool = None self._look_assigner_tool = None + self._experimental_tools_dialog = None @property def log(self): @@ -218,6 +219,22 @@ class HostToolsHelper: look_assigner_tool = self.get_look_assigner_tool(parent) look_assigner_tool.show() + def get_experimental_tools_dialog(self, parent=None): + if self._experimental_tools_dialog is None: + from openpype.tools.experimental_tools import ( + ExperimentalToolsDialog + ) + + self._experimental_tools_dialog = ExperimentalToolsDialog(parent) + return self._experimental_tools_dialog + + def show_experimental_tools_dialog(self, parent=None): + dialog = self.get_experimental_tools_dialog(parent) + + dialog.show() + dialog.raise_() + dialog.activateWindow() + def get_tool_by_name(self, tool_name, parent=None, *args, **kwargs): """Show tool by it's name. @@ -247,6 +264,9 @@ class HostToolsHelper: elif tool_name == "publish": self.log.info("Can't return publish tool window.") + elif tool_name == "experimental_tools": + return self.get_experimental_tools_dialog(parent, *args, **kwargs) + else: self.log.warning( "Can't show unknown tool name: \"{}\"".format(tool_name) @@ -281,6 +301,9 @@ class HostToolsHelper: elif tool_name == "publish": self.show_publish(parent, *args, **kwargs) + elif tool_name == "experimental_tools": + self.show_experimental_tools_dialog(parent, *args, **kwargs) + else: self.log.warning( "Can't show unknown tool name: \"{}\"".format(tool_name) @@ -355,3 +378,7 @@ def show_look_assigner(parent=None): def show_publish(parent=None): _SingletonPoint.show_tool_by_name("publish", parent) + + +def show_experimental_tools_dialog(parent=None): + _SingletonPoint.show_tool_by_name("experimental_tools", parent) From d4729ab0695ac59673583f02efb9bb9fdfb136ce Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 21 Oct 2021 15:05:11 +0200 Subject: [PATCH 228/279] disable filtering by host name when used in local settings --- openpype/tools/settings/local_settings/experimental_widget.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/settings/local_settings/experimental_widget.py b/openpype/tools/settings/local_settings/experimental_widget.py index 72f999d886..e863d9afb0 100644 --- a/openpype/tools/settings/local_settings/experimental_widget.py +++ b/openpype/tools/settings/local_settings/experimental_widget.py @@ -28,7 +28,7 @@ class LocalExperimentalToolsWidgets(QtWidgets.QWidget): layout.addRow(empty_label) - experimental_defs = ExperimentalTools() + experimental_defs = ExperimentalTools(filter_hosts=False) checkboxes_by_identifier = {} for tool in experimental_defs.tools: checkbox = QtWidgets.QCheckBox(self) From 7a50a106e333ed472dcb8fa6dfd7acc7e71d5efb Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 21 Oct 2021 15:05:20 +0200 Subject: [PATCH 229/279] added few docstrings --- .../tools/experimental_tools/tools_def.py | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/openpype/tools/experimental_tools/tools_def.py b/openpype/tools/experimental_tools/tools_def.py index 5dd92151ca..6ae4637039 100644 --- a/openpype/tools/experimental_tools/tools_def.py +++ b/openpype/tools/experimental_tools/tools_def.py @@ -8,6 +8,8 @@ LOCAL_EXPERIMENTAL_KEY = "experimental_tools" class ExperimentalTool: """Definition of experimental tool. + Definition is used in local settings and in experimental tools dialog. + Args: identifier (str): String identifier of tool (unique). label (str): Label shown in UI. @@ -91,11 +93,32 @@ class ExperimentalTools: ).format(tool.identifier)) tools_by_identifier[tool.identifier] = tool - self.tools_by_identifier = tools_by_identifier - self.tools = experimental_tools + self._tools_by_identifier = tools_by_identifier + self._tools = experimental_tools self._parent_widget = parent + @property + def tools(self): + """Tools in list. + + Returns: + list: Tools filtered by host name if filtering was enabled + on initialization. + """ + return self._tools + + @property + def tools_by_identifier(self): + """Tools by their identifier. + + Returns: + dict: Tools by identifier filtered by host name if filtering + was enabled on initialization. + """ + return self._tools_by_identifier + def refresh_availability(self): + """Reload local settings and check if any tool changed ability.""" local_settings = get_local_settings() experimental_settings = ( local_settings.get(LOCAL_EXPERIMENTAL_KEY) From 33764f6e16c3cc0e46bc75d7945db592acf0623f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 21 Oct 2021 15:08:39 +0200 Subject: [PATCH 230/279] added docstrings to host tools --- openpype/tools/utils/host_tools.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/openpype/tools/utils/host_tools.py b/openpype/tools/utils/host_tools.py index c0e6d71b73..2ac9d0c48b 100644 --- a/openpype/tools/utils/host_tools.py +++ b/openpype/tools/utils/host_tools.py @@ -220,6 +220,16 @@ class HostToolsHelper: look_assigner_tool.show() def get_experimental_tools_dialog(self, parent=None): + """Dialog of experimental tools. + + For some hosts it is not easy to modify menu of tools. For + those cases was addded experimental tools dialog which is Qt based + and can dynamically filled by experimental tools so + host need only single "Experimental tools" button to see them. + + Dialog can be also empty with a message that there are not available + experimental tools. + """ if self._experimental_tools_dialog is None: from openpype.tools.experimental_tools import ( ExperimentalToolsDialog @@ -229,6 +239,7 @@ class HostToolsHelper: return self._experimental_tools_dialog def show_experimental_tools_dialog(self, parent=None): + """Show dialog with experimental tools.""" dialog = self.get_experimental_tools_dialog(parent) dialog.show() From 29d1c47a58751ba16f9a5f71ff5d35cf7839ac61 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 21 Oct 2021 15:12:12 +0200 Subject: [PATCH 231/279] disable buttons of tools that are not turned on --- openpype/tools/experimental_tools/dialog.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/tools/experimental_tools/dialog.py b/openpype/tools/experimental_tools/dialog.py index 6173deb693..4923759249 100644 --- a/openpype/tools/experimental_tools/dialog.py +++ b/openpype/tools/experimental_tools/dialog.py @@ -112,6 +112,9 @@ class ExperimentalToolsDialog(QtWidgets.QDialog): "\n\nOpenPype Tray > Settings > Experimental Tools" )) + if tool.enabled != button.isEnabled(): + button.setEnabled(tool.enabled) + for identifier in buttons_to_remove: button = self._buttons_by_tool_identifier.pop(identifier) button.setVisible(False) From de9f0f7fa46fa757f1b6ce6d6b985360de98410d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 21 Oct 2021 15:28:25 +0200 Subject: [PATCH 232/279] adde commented example tool --- openpype/tools/experimental_tools/tools_def.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/openpype/tools/experimental_tools/tools_def.py b/openpype/tools/experimental_tools/tools_def.py index 6ae4637039..3657c2385b 100644 --- a/openpype/tools/experimental_tools/tools_def.py +++ b/openpype/tools/experimental_tools/tools_def.py @@ -65,6 +65,19 @@ class ExperimentalTools: # Definition of experimental tools experimental_tools = [] + # --- Example tool (callback will just print on click) --- + # def example_callback(*args): + # print("Triggered tool") + # + # experimental_tools = [ + # ExperimentalTool( + # "example", + # "Exmaple experimental tool", + # example_callback, + # "Example tool tooltip." + # ) + # ] + # Try to get host name from env variable `AVALON_APP` if not host_name: host_name = os.environ.get("AVALON_APP") From ee443cb735637b56be5367164309481f7defb35a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 21 Oct 2021 15:29:28 +0200 Subject: [PATCH 233/279] added example tool into experimental tools --- openpype/hosts/maya/api/menu.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/openpype/hosts/maya/api/menu.py b/openpype/hosts/maya/api/menu.py index 4f0966abfd..5eb8882030 100644 --- a/openpype/hosts/maya/api/menu.py +++ b/openpype/hosts/maya/api/menu.py @@ -46,6 +46,15 @@ def deferred(): ) ) + def add_experimental_item(): + cmds.menuItem( + "Experimental tools...", + parent=pipeline._menu, + command=lambda *args: host_tools.show_experimental_tools_dialog( + pipeline._parent + ) + ) + def modify_workfiles(): # Find the pipeline menu top_menu = _get_menu() @@ -103,6 +112,7 @@ def deferred(): add_build_workfiles_item() add_look_assigner_item() + add_experimental_item() modify_workfiles() remove_project_manager() From 501f0ed550e76ebbff0340b6bdf2bb14a75e06ce Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 21 Oct 2021 15:50:10 +0200 Subject: [PATCH 234/279] added experimental tools into nuke menu --- openpype/hosts/nuke/api/menu.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openpype/hosts/nuke/api/menu.py b/openpype/hosts/nuke/api/menu.py index 87990c5e92..3e74893589 100644 --- a/openpype/hosts/nuke/api/menu.py +++ b/openpype/hosts/nuke/api/menu.py @@ -84,6 +84,12 @@ def install(): ) log.debug("Adding menu item: {}".format(name)) + # Add experimental tools action + menu.addSeparator() + menu.addCommand( + "Experimental tools...", + host_tools.show_experimental_tools_dialog + ) # adding shortcuts add_shortcuts_from_presets() From 6da7c65e295be27cab7fd1c594f78945247ec989 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 21 Oct 2021 16:19:43 +0200 Subject: [PATCH 235/279] set stylesheet after show --- openpype/tools/experimental_tools/dialog.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/tools/experimental_tools/dialog.py b/openpype/tools/experimental_tools/dialog.py index 4923759249..c7c8ce83fc 100644 --- a/openpype/tools/experimental_tools/dialog.py +++ b/openpype/tools/experimental_tools/dialog.py @@ -29,7 +29,6 @@ class ExperimentalToolsDialog(QtWidgets.QDialog): self.setWindowTitle("OpenPype Experimental tools") icon = QtGui.QIcon(app_icon_path()) self.setWindowIcon(icon) - self.setStyleSheet(load_stylesheet()) empty_widget = QtWidgets.QWidget(self) @@ -154,6 +153,8 @@ class ExperimentalToolsDialog(QtWidgets.QDialog): if self._first_show: self._first_show = False + # Set stylesheet + self.setStyleSheet(load_stylesheet()) # Resize dialog if there is not content if not self._is_content_visible(): size = self.size() From 3bb391f8701be3ed803794267eac6e95213f68ea Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 21 Oct 2021 15:35:46 +0100 Subject: [PATCH 236/279] Implemented fix for deselecting all objects --- openpype/hosts/blender/plugins/create/create_camera.py | 2 +- openpype/hosts/blender/plugins/load/load_camera_blend.py | 2 +- openpype/hosts/blender/plugins/load/load_camera_fbx.py | 4 ++-- openpype/hosts/blender/plugins/publish/extract_camera.py | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/blender/plugins/create/create_camera.py b/openpype/hosts/blender/plugins/create/create_camera.py index fad827d85a..98ccca313c 100644 --- a/openpype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -53,7 +53,7 @@ class CreateCamera(plugin.Creator): selected.append(asset_group) bpy.ops.object.parent_set(keep_transform=True) else: - bpy.ops.object.select_all(action='DESELECT') + plugin.deselect_all() camera_obj.select_set(True) asset_group.select_set(True) bpy.context.view_layer.objects.active = asset_group diff --git a/openpype/hosts/blender/plugins/load/load_camera_blend.py b/openpype/hosts/blender/plugins/load/load_camera_blend.py index 1173e26d7b..834eb467d8 100644 --- a/openpype/hosts/blender/plugins/load/load_camera_blend.py +++ b/openpype/hosts/blender/plugins/load/load_camera_blend.py @@ -91,7 +91,7 @@ class BlendCameraLoader(plugin.AssetLoader): bpy.data.orphans_purge(do_local_ids=False) - bpy.ops.object.select_all(action='DESELECT') + plugin.deselect_all() return objects diff --git a/openpype/hosts/blender/plugins/load/load_camera_fbx.py b/openpype/hosts/blender/plugins/load/load_camera_fbx.py index c6d491870d..5edba7ec0c 100644 --- a/openpype/hosts/blender/plugins/load/load_camera_fbx.py +++ b/openpype/hosts/blender/plugins/load/load_camera_fbx.py @@ -38,7 +38,7 @@ class FbxCameraLoader(plugin.AssetLoader): bpy.data.objects.remove(obj) def _process(self, libpath, asset_group, group_name): - bpy.ops.object.select_all(action='DESELECT') + plugin.deselect_all() collection = bpy.context.view_layer.active_layer_collection.collection @@ -68,7 +68,7 @@ class FbxCameraLoader(plugin.AssetLoader): avalon_info = obj[AVALON_PROPERTY] avalon_info.update({"container_name": group_name}) - bpy.ops.object.select_all(action='DESELECT') + plugin.deselect_all() return objects diff --git a/openpype/hosts/blender/plugins/publish/extract_camera.py b/openpype/hosts/blender/plugins/publish/extract_camera.py index 7888ddad6a..a0e78178c8 100644 --- a/openpype/hosts/blender/plugins/publish/extract_camera.py +++ b/openpype/hosts/blender/plugins/publish/extract_camera.py @@ -23,7 +23,7 @@ class ExtractCamera(api.Extractor): # Perform extraction self.log.info("Performing extraction..") - bpy.ops.object.select_all(action='DESELECT') + plugin.deselect_all() selected = [] @@ -56,7 +56,7 @@ class ExtractCamera(api.Extractor): bpy.context.scene.unit_settings.scale_length = scale_length - bpy.ops.object.select_all(action='DESELECT') + plugin.deselect_all() if "representations" not in instance.data: instance.data["representations"] = [] From 28bf0a23996dd5538b9843dfaa271904b41a416e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 21 Oct 2021 17:14:17 +0200 Subject: [PATCH 237/279] resize after showing --- openpype/tools/libraryloader/app.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/openpype/tools/libraryloader/app.py b/openpype/tools/libraryloader/app.py index 700d3c05bd..3e4c5d5850 100644 --- a/openpype/tools/libraryloader/app.py +++ b/openpype/tools/libraryloader/app.py @@ -129,6 +129,10 @@ class LibraryLoaderWindow(QtWidgets.QDialog): main_splitter.addWidget(left_side_splitter) main_splitter.addWidget(subsets_widget) main_splitter.addWidget(thumb_ver_splitter) + if sync_server_enabled: + main_splitter.setSizes([250, 1000, 550]) + else: + main_splitter.setSizes([250, 850, 200]) # --- Footer --- footer_widget = QtWidgets.QWidget(self) @@ -168,6 +172,7 @@ class LibraryLoaderWindow(QtWidgets.QDialog): projects_combobox.currentTextChanged.connect(self.on_project_change) self.sync_server = sync_server + self._sync_server_enabled = sync_server_enabled self._combobox_delegate = combobox_delegate self._projects_combobox = projects_combobox @@ -186,19 +191,15 @@ class LibraryLoaderWindow(QtWidgets.QDialog): # Set default thumbnail on start thumbnail_widget.set_thumbnail(None) - # Defaults - if sync_server_enabled: - main_splitter.setSizes([250, 1000, 550]) - self.resize(1800, 900) - else: - main_splitter.setSizes([250, 850, 200]) - self.resize(1300, 700) - def showEvent(self, event): super(LibraryLoaderWindow, self).showEvent(event) if self._first_show: self._first_show = False self.setStyleSheet(style.load_stylesheet()) + if self._sync_server_enabled: + self.resize(1800, 900) + else: + self.resize(1300, 700) if not self._initial_refresh: self._initial_refresh = True From 2b2f27b74e557a1b34e15bc30ffb8fc9ca23d489 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 21 Oct 2021 17:15:40 +0200 Subject: [PATCH 238/279] set default thumbnail on initialization of ThumbnailWidget --- openpype/tools/libraryloader/app.py | 3 --- openpype/tools/loader/widgets.py | 1 + 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/openpype/tools/libraryloader/app.py b/openpype/tools/libraryloader/app.py index 3e4c5d5850..d7c6c162e6 100644 --- a/openpype/tools/libraryloader/app.py +++ b/openpype/tools/libraryloader/app.py @@ -188,9 +188,6 @@ class LibraryLoaderWindow(QtWidgets.QDialog): self._message_label = message_label self._message_timer = message_timer - # Set default thumbnail on start - thumbnail_widget.set_thumbnail(None) - def showEvent(self, event): super(LibraryLoaderWindow, self).showEvent(event) if self._first_show: diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index b068dd95d1..4c075382ac 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -745,6 +745,7 @@ class ThumbnailWidget(QtWidgets.QLabel): "default_thumbnail.png" ) self.default_pix = QtGui.QPixmap(default_pix_path) + self.set_pixmap() def height(self): width = self.width() From 1b10ef39fd43e65a8ddcfcabb185066d88a578b9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 21 Oct 2021 17:17:24 +0200 Subject: [PATCH 239/279] resize loader after showing --- openpype/tools/loader/app.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/openpype/tools/loader/app.py b/openpype/tools/loader/app.py index a98c7e2f2f..bbf6719af5 100644 --- a/openpype/tools/loader/app.py +++ b/openpype/tools/loader/app.py @@ -119,6 +119,11 @@ class LoaderWindow(QtWidgets.QDialog): main_splitter.addWidget(subsets_widget) main_splitter.addWidget(thumb_ver_splitter) + if sync_server_enabled: + main_splitter.setSizes([250, 1000, 550]) + else: + main_splitter.setSizes([250, 850, 200]) + # TODO keep footer size by message size footer_widget = QtWidgets.QWidget(self) footer_widget.setFixedHeight(20) @@ -164,6 +169,8 @@ class LoaderWindow(QtWidgets.QDialog): repres_widget.load_started.connect(self._on_load_start) repres_widget.load_ended.connect(self._on_load_end) + self._sync_server_enabled = sync_server_enabled + self._assets_widget = assets_widget self._families_filter_view = families_filter_view @@ -185,14 +192,6 @@ class LoaderWindow(QtWidgets.QDialog): self._refresh() self._assetschanged() - # Defaults - if sync_server_enabled: - main_splitter.setSizes([250, 1000, 550]) - self.resize(1800, 900) - else: - main_splitter.setSizes([250, 850, 200]) - self.resize(1300, 700) - self._first_show = True def resizeEvent(self, event): @@ -208,6 +207,10 @@ class LoaderWindow(QtWidgets.QDialog): if self._first_show: self._first_show = False self.setStyleSheet(style.load_stylesheet()) + if self._sync_server_enabled: + self.resize(1800, 900) + else: + self.resize(1300, 700) # ------------------------------- # Delay calling blocking methods From 5b95e21c6f4da5683043895ae97222c10fafcae6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 22 Oct 2021 10:53:03 +0200 Subject: [PATCH 240/279] do not apply avalon style on loader and library loader --- openpype/tools/utils/host_tools.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/openpype/tools/utils/host_tools.py b/openpype/tools/utils/host_tools.py index 599c25d6c8..2a64e23883 100644 --- a/openpype/tools/utils/host_tools.py +++ b/openpype/tools/utils/host_tools.py @@ -93,8 +93,6 @@ class HostToolsHelper: def show_loader(self, parent=None, use_context=None): """Loader tool for loading representations.""" - from avalon import style - loader_tool = self.get_loader_tool(parent) loader_tool.show() @@ -110,8 +108,6 @@ class HostToolsHelper: else: loader_tool.refresh() - loader_tool.setStyleSheet(style.load_stylesheet()) - def get_creator_tool(self, parent): """Create, cache and return creator tool window.""" if self._creator_tool is None: @@ -196,14 +192,11 @@ class HostToolsHelper: def show_library_loader(self, parent=None): """Loader tool for loading representations from library project.""" - from avalon import style - library_loader_tool = self.get_library_loader_tool(parent) library_loader_tool.show() library_loader_tool.raise_() library_loader_tool.activateWindow() library_loader_tool.refresh() - library_loader_tool.setStyleSheet(style.load_stylesheet()) def show_publish(self, parent=None): """Publish UI.""" From 600c94f464d93edded5927873b86379838708a12 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 22 Oct 2021 11:49:15 +0200 Subject: [PATCH 241/279] fix double slashes --- openpype/style/style.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/style/style.css b/openpype/style/style.css index 3f006fb845..6921a786f3 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -425,20 +425,20 @@ QAbstractItemView::branch:open:has-children:has-siblings { QAbstractItemView::branch:open:has-children:!has-siblings:hover, QAbstractItemView::branch:open:has-children:has-siblings:hover { border-image: none; - image: url(:/openpype/images//branch_open_on.png); + image: url(:/openpype/images/branch_open_on.png); background: transparent; } QAbstractItemView::branch:has-children:!has-siblings:closed, QAbstractItemView::branch:closed:has-children:has-siblings { border-image: none; - image: url(:/openpype/images//branch_closed.png); + image: url(:/openpype/images/branch_closed.png); background: transparent; } QAbstractItemView::branch:has-children:!has-siblings:closed:hover, QAbstractItemView::branch:closed:has-children:has-siblings:hover { border-image: none; - image: url(:/openpype/images//branch_closed_on.png); + image: url(:/openpype/images/branch_closed_on.png); background: transparent; } From c85fb71103c22a2be372a1e6ea1c55ed4522c1d1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 22 Oct 2021 12:26:12 +0200 Subject: [PATCH 242/279] qlineargradient are single line --- openpype/style/style.css | 69 +++++++++++----------------------------- 1 file changed, 19 insertions(+), 50 deletions(-) diff --git a/openpype/style/style.css b/openpype/style/style.css index 6921a786f3..8e9827084e 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -205,39 +205,23 @@ QSplitter::handle { } QSplitter::handle:horizontal { - background: qlineargradient( - x1:0, y1:0, x2:1, y2:0, - stop:0.3 rgba(0, 0, 0, 0), - stop:0.5 {color:bg-splitter}, - stop:0.7 rgba(0, 0, 0, 0) - ); + /* must be single like because of Nuke*/ + background: qlineargradient(x1:0, y1:0, x2:1, y2:0,stop:0.3 rgba(0, 0, 0, 0),stop:0.5 {color:bg-splitter},stop:0.7 rgba(0, 0, 0, 0)); } QSplitter::handle:vertical { - background: qlineargradient( - x1:0, y1:0, x2:0, y2:1, - stop:0.3 rgba(0, 0, 0, 0), - stop:0.5 {color:bg-splitter}, - stop:0.7 rgba(0, 0, 0, 0) - ); + /* must be single like because of Nuke*/ + background: qlineargradient(x1:0, y1:0, x2:0, y2:1,stop:0.3 rgba(0, 0, 0, 0),stop:0.5 {color:bg-splitter},stop:0.7 rgba(0, 0, 0, 0)); } QSplitter::handle:horizontal:hover { - background: qlineargradient( - x1:0, y1:0, x2:1, y2:0, - stop:0.3 rgba(0, 0, 0, 0), - stop:0.5 {color:bg-splitter-hover}, - stop:0.7 rgba(0, 0, 0, 0) - ); + /* must be single like because of Nuke*/ + background: qlineargradient(x1:0, y1:0, x2:1, y2:0,stop:0.3 rgba(0, 0, 0, 0),stop:0.5 {color:bg-splitter-hover},stop:0.7 rgba(0, 0, 0, 0)); } QSplitter::handle:vertical:hover { - background: qlineargradient( - x1:0, y1:0, x2:0, y2:1, - stop:0.3 rgba(0, 0, 0, 0), - stop:0.5 {color:bg-splitter-hover}, - stop:0.7 rgba(0, 0, 0, 0) - ); + /* must be single like because of Nuke*/ + background: qlineargradient(x1:0, y1:0, x2:0, y2:1,stop:0.3 rgba(0, 0, 0, 0),stop:0.5 {color:bg-splitter-hover},stop:0.7 rgba(0, 0, 0, 0)); } /* SLider */ @@ -264,18 +248,15 @@ QSlider::groove:focus { border-color: {color:border-focus}; } QSlider::handle { - background: qlineargradient( - x1: 0, y1: 0.5, - x2: 1, y2: 0.5, - stop: 0 {palette:blue-base}, - stop: 1 {palette:green-base} - ); + /* must be single like because of Nuke*/ + background: qlineargradient(x1: 0, y1: 0.5, x2: 1, y2: 0.5,stop: 0 {palette:blue-base},stop: 1 {palette:green-base}); border: 1px solid #5c5c5c; width: 10px; height: 10px; border-radius: 5px; } + QSlider::handle:horizontal { margin: -2px 0; } @@ -284,12 +265,8 @@ QSlider::handle:vertical { } QSlider::handle:disabled { - background: qlineargradient( - x1:0, y1:0, - x2:1, y2:1, - stop:0 {color:bg-buttons}, - stop:1 {color:bg-buttons-disabled} - ); + /* must be single like because of Nuke*/ + background: qlineargradient(x1:0, y1:0,x2:1, y2:1,stop:0 {color:bg-buttons},stop:1 {color:bg-buttons-disabled}); } /* Tab widget*/ @@ -307,19 +284,15 @@ QTabBar::tab { border-left: 3px solid transparent; border-top: 1px solid {color:border}; border-right: 1px solid {color:border}; - background: qlineargradient( - x1: 0, y1: 1, x2: 0, y2: 0, - stop: 0.5 {color:bg}, stop: 1.0 {color:bg-inputs} - ); + /* must be single like because of Nuke*/ + background: qlineargradient(x1: 0, y1: 1, x2: 0, y2: 0,stop: 0.5 {color:bg}, stop: 1.0 {color:bg-inputs}); } QTabBar::tab:selected { background: {color:grey-lighter}; border-left: 3px solid {color:border-focus}; - background: qlineargradient( - x1: 0, y1: 1, x2: 0, y2: 0, - stop: 0.5 {color:bg}, stop: 1.0 {color:border} - ); + /* must be single like because of Nuke*/ + background: qlineargradient(x1: 0, y1: 1, x2: 0, y2: 0,stop: 0.5 {color:bg}, stop: 1.0 {color:border}); } QTabBar::tab:!selected { @@ -457,12 +430,8 @@ QProgressBar:vertical { } QProgressBar::chunk { - background: qlineargradient( - x1: 0, y1: 0.5, - x2: 1, y2: 0.5, - stop: 0 {palette:blue-base}, - stop: 1 {palette:green-base} - ); + /* must be single like because of Nuke*/ + background: qlineargradient(x1: 0, y1: 0.5,x2: 1, y2: 0.5,stop: 0 {palette:blue-base},stop: 1 {palette:green-base}); } /* Scroll bars */ From 3158710d43f06fd5e2793d84483ba7770c2a2753 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 22 Oct 2021 12:28:58 +0200 Subject: [PATCH 243/279] removed fixed height of footer widget --- openpype/tools/loader/app.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/openpype/tools/loader/app.py b/openpype/tools/loader/app.py index bbf6719af5..dac5e11d4c 100644 --- a/openpype/tools/loader/app.py +++ b/openpype/tools/loader/app.py @@ -124,19 +124,17 @@ class LoaderWindow(QtWidgets.QDialog): else: main_splitter.setSizes([250, 850, 200]) - # TODO keep footer size by message size footer_widget = QtWidgets.QWidget(self) - footer_widget.setFixedHeight(20) message_label = QtWidgets.QLabel(footer_widget) - footer_layout = QtWidgets.QVBoxLayout(footer_widget) + footer_layout = QtWidgets.QHBoxLayout(footer_widget) footer_layout.setContentsMargins(0, 0, 0, 0) - footer_layout.addWidget(message_label) + footer_layout.addWidget(message_label, 1) layout = QtWidgets.QVBoxLayout(self) - layout.addWidget(main_splitter) - layout.addWidget(footer_widget) + layout.addWidget(main_splitter, 1) + layout.addWidget(footer_widget, 0) self.data = { "state": { From 01d059a993af1e486bedb8ae051299f1cbcab07c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 22 Oct 2021 12:31:52 +0200 Subject: [PATCH 244/279] added transparent image to resources --- openpype/style/images/transparent.png | Bin 0 -> 69 bytes openpype/style/pyqt5_resources.py | 745 +++++++++++++------------- openpype/style/pyside2_resources.py | 602 +++++++++++---------- openpype/style/resources.qrc | 1 + 4 files changed, 679 insertions(+), 669 deletions(-) create mode 100644 openpype/style/images/transparent.png diff --git a/openpype/style/images/transparent.png b/openpype/style/images/transparent.png new file mode 100644 index 0000000000000000000000000000000000000000..0f2e143b39a2e37e52841ff55d410a2000125eca GIT binary patch literal 69 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1SBVv2j2ryJf1F&AsjQ46A}`DJQfDV#ayC~ Pfh-13S3j3^P6 \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\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\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\ @@ -62,18 +36,136 @@ 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\x00\x9f\ +\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\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\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\x070\ +\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\xa0\ \x89\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ @@ -86,6 +178,31 @@ 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\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\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\x07\xdd\ \x89\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ @@ -214,19 +331,57 @@ 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\x00\xa6\ +\x00\x00\x00\x9e\ \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\ 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\ +\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\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\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\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\x00\xa0\ \x89\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ @@ -234,11 +389,23 @@ 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\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\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\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\x06\ \x89\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ @@ -357,146 +524,28 @@ 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\x00\xa5\ +\x00\x00\x00\xa0\ \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\x070\ -\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\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\x07\xad\ \x89\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ @@ -622,55 +671,6 @@ 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\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\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\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\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\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\xa6\ \x89\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ @@ -695,16 +695,6 @@ qt_resource_name = b"\ \x07\x03}\xc3\ \x00i\ \x00m\x00a\x00g\x00e\x00s\ -\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\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\x0e\ \x0e\xde\xfa\xc7\ \x00l\ @@ -714,62 +704,35 @@ qt_resource_name = b"\ \x00d\ \x00o\x00w\x00n\x00_\x00a\x00r\x00r\x00o\x00w\x00_\x00o\x00n\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\x12\ -\x03\x8d\x04G\ -\x00r\ -\x00i\x00g\x00h\x00t\x00_\x00a\x00r\x00r\x00o\x00w\x00_\x00o\x00n\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\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\x0f\ -\x02\x9f\x05\x87\ -\x00r\ -\x00i\x00g\x00h\x00t\x00_\x00a\x00r\x00r\x00o\x00w\x00.\x00p\x00n\x00g\ -\x00\x0f\ -\x06S%\xa7\ -\x00b\ -\x00r\x00a\x00n\x00c\x00h\x00_\x00o\x00p\x00e\x00n\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\x0e\ -\x04\xa2\xfc\xa7\ -\x00d\ -\x00o\x00w\x00n\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\x11\ -\x0b\xda0\xa7\ -\x00b\ -\x00r\x00a\x00n\x00c\x00h\x00_\x00c\x00l\x00o\x00s\x00e\x00d\x00.\x00p\x00n\x00g\ -\ -\x00\x18\ -\x03\x8e\xdeg\ +\x00\x12\ +\x03\x8d\x04G\ \x00r\ -\x00i\x00g\x00h\x00t\x00_\x00a\x00r\x00r\x00o\x00w\x00_\x00d\x00i\x00s\x00a\x00b\ -\x00l\x00e\x00d\x00.\x00p\x00n\x00g\ +\x00i\x00g\x00h\x00t\x00_\x00a\x00r\x00r\x00o\x00w\x00_\x00o\x00n\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\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\ +\x04^-\xa7\ +\x00b\ +\x00r\x00a\x00n\x00c\x00h\x00_\x00c\x00l\x00o\x00s\x00e\x00d\x00_\x00o\x00n\x00.\ +\x00p\x00n\x00g\ \x00\x0c\ \x06\xe6\xe6g\ \x00u\ @@ -779,6 +742,43 @@ qt_resource_name = b"\ \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\ +\x03'rg\ +\x00c\ +\x00o\x00m\x00b\x00o\x00b\x00o\x00x\x00_\x00a\x00r\x00r\x00o\x00w\x00_\x00o\x00n\ +\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\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\ +\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\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\x0f\ +\x02\x9f\x05\x87\ +\x00r\ +\x00i\x00g\x00h\x00t\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\x11\ \x00\xb8\x8c\x07\ \x00l\ @@ -791,34 +791,30 @@ qt_resource_struct = b"\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ \x00\x00\x00\x16\x00\x02\x00\x00\x00\x13\x00\x00\x00\x03\ \x00\x00\x03,\x00\x00\x00\x00\x00\x01\x00\x00&\xf0\ -\x00\x00\x00\xb6\x00\x00\x00\x00\x00\x01\x00\x00\x01\xfd\ -\x00\x00\x01\xe2\x00\x00\x00\x00\x00\x01\x00\x00\x14&\ -\x00\x00\x02\xb6\x00\x00\x00\x00\x00\x01\x00\x00%\x02\ -\x00\x00\x01\x9a\x00\x00\x00\x00\x00\x01\x00\x00\x0cx\ +\x00\x00\x00J\x00\x00\x00\x00\x00\x01\x00\x00\x00\xaa\ +\x00\x00\x00r\x00\x00\x00\x00\x00\x01\x00\x00\x01S\ +\x00\x00\x00\xf0\x00\x00\x00\x00\x00\x01\x00\x00\x09\xd5\ +\x00\x00\x02\xe0\x00\x00\x00\x00\x00\x01\x00\x00\x1e\x9b\ +\x00\x00\x01\xd0\x00\x00\x00\x00\x00\x01\x00\x00\x14M\ +\x00\x00\x01\x14\x00\x00\x00\x00\x00\x01\x00\x00\x0aw\ +\x00\x00\x00\xc6\x00\x00\x00\x00\x00\x01\x00\x00\x091\ +\x00\x00\x02\x22\x00\x00\x00\x00\x00\x01\x00\x00\x15\xa0\ +\x00\x00\x01P\x00\x00\x00\x00\x00\x01\x00\x00\x0b \ +\x00\x00\x02\x00\x00\x00\x00\x00\x00\x01\x00\x00\x14\xf7\ +\x00\x00\x00\x9c\x00\x00\x00\x00\x00\x01\x00\x00\x01\xfd\ +\x00\x00\x02\x88\x00\x00\x00\x00\x00\x01\x00\x00\x16\xe7\ +\x00\x00\x01~\x00\x00\x00\x00\x00\x01\x00\x00\x13\x01\ +\x00\x00\x03\x04\x00\x00\x00\x00\x00\x01\x00\x00\x1f?\ +\x00\x00\x02\xac\x00\x00\x00\x00\x00\x01\x00\x00\x1d\xf1\ +\x00\x00\x01\x9c\x00\x00\x00\x00\x00\x01\x00\x00\x13\xa3\ \x00\x00\x00(\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ -\x00\x00\x00X\x00\x00\x00\x00\x00\x01\x00\x00\x00\xaa\ -\x00\x00\x01\x0e\x00\x00\x00\x00\x00\x01\x00\x00\x03I\ -\x00\x00\x02\x80\x00\x00\x00\x00\x00\x01\x00\x00$^\ -\x00\x00\x018\x00\x00\x00\x00\x00\x01\x00\x00\x03\xed\ -\x00\x00\x02\x0c\x00\x00\x00\x00\x00\x01\x00\x00\x14\xd0\ -\x00\x00\x02.\x00\x00\x00\x00\x00\x01\x00\x00\x15y\ -\x00\x00\x01\xbe\x00\x00\x00\x00\x00\x01\x00\x00\x0d\x1c\ -\x00\x00\x02\xda\x00\x00\x00\x00\x00\x01\x00\x00%\xa4\ -\x00\x00\x02X\x00\x00\x00\x00\x00\x01\x00\x00\x1c\xad\ -\x00\x00\x01f\x00\x00\x00\x00\x00\x01\x00\x00\x0b\xce\ -\x00\x00\x02\xf8\x00\x00\x00\x00\x00\x01\x00\x00&F\ -\x00\x00\x00\x94\x00\x00\x00\x00\x00\x01\x00\x00\x01S\ -\x00\x00\x00\xde\x00\x00\x00\x00\x00\x01\x00\x00\x02\xa6\ +\x00\x00\x02X\x00\x00\x00\x00\x00\x01\x00\x00\x16D\ " - def qInitResources(): - QtCore.qRegisterResourceData( - 0x01, qt_resource_struct, qt_resource_name, qt_resource_data - ) - + QtCore.qRegisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) def qCleanupResources(): - QtCore.qUnregisterResourceData( - 0x01, qt_resource_struct, qt_resource_name, qt_resource_data - ) + QtCore.qUnregisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) + +qInitResources() diff --git a/openpype/style/resources.qrc b/openpype/style/resources.qrc index a583d9458e..e2e69711f4 100644 --- a/openpype/style/resources.qrc +++ b/openpype/style/resources.qrc @@ -19,5 +19,6 @@ images/up_arrow.png images/up_arrow_disabled.png images/up_arrow_on.png + images/transparent.png From 536c6383715627a7778927f58840db3f71d84d69 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 22 Oct 2021 12:32:09 +0200 Subject: [PATCH 245/279] use transparent image to hide branches in tree view --- openpype/style/style.css | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/openpype/style/style.css b/openpype/style/style.css index 8e9827084e..f8a61cbbd3 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -415,6 +415,25 @@ QAbstractItemView::branch:closed:has-children:has-siblings:hover { background: transparent; } +QAbstractItemView::branch:has-siblings:!adjoins-item { + border-image: none; + image: url(:/openpype/images/transparent.png); + background: transparent; +} + +QAbstractItemView::branch:has-siblings:adjoins-item { + border-image: none; + image: url(:/openpype/images/transparent.png); + background: transparent; +} + +QAbstractItemView::branch:!has-children:!has-siblings:adjoins-item { + border-image: none; + image: url(:/openpype/images/transparent.png); + background: transparent; +} + + /* Progress bar */ QProgressBar { border: 1px solid {color:border}; From 140c290c607ad0dd87cce1e5f44afce38091c0ef Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 22 Oct 2021 12:32:19 +0200 Subject: [PATCH 246/279] set down/up arrow for header --- openpype/style/style.css | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/openpype/style/style.css b/openpype/style/style.css index f8a61cbbd3..d6f2460a27 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -340,6 +340,15 @@ QHeaderView::section:first { QHeaderView::section:last { border-right: none; } + +QHeaderView::down-arrow { + image: url(:/openpype/images/down_arrow.png); +} + +QHeaderView::up-arrow { + image: url(:/openpype/images/up_arrow.png); +} + /* Views QListView QTreeView QTableView */ QAbstractItemView { border: 0px solid {color:border}; From d4b4d2e8417e267a6381cf0fd883910b3b4bf5ad Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 22 Oct 2021 12:40:12 +0200 Subject: [PATCH 247/279] fix formatting --- openpype/style/pyside2_resources.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openpype/style/pyside2_resources.py b/openpype/style/pyside2_resources.py index e4bbc50533..97ee781c5d 100644 --- a/openpype/style/pyside2_resources.py +++ b/openpype/style/pyside2_resources.py @@ -812,9 +812,13 @@ qt_resource_struct = b"\ " def qInitResources(): - QtCore.qRegisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) + QtCore.qRegisterResourceData( + 0x01, qt_resource_struct, qt_resource_name, qt_resource_data + ) def qCleanupResources(): - QtCore.qUnregisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) + QtCore.qUnregisterResourceData( + 0x01, qt_resource_struct, qt_resource_name, qt_resource_data + ) qInitResources() From c39c3ecb045629160d809fd3e25bdb3bad173ea1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 22 Oct 2021 12:40:38 +0200 Subject: [PATCH 248/279] do not call pyside initialization on import --- openpype/style/pyside2_resources.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/style/pyside2_resources.py b/openpype/style/pyside2_resources.py index 97ee781c5d..dff01eec49 100644 --- a/openpype/style/pyside2_resources.py +++ b/openpype/style/pyside2_resources.py @@ -811,14 +811,14 @@ qt_resource_struct = b"\ \x00\x00\x02X\x00\x00\x00\x00\x00\x01\x00\x00\x16D\ " + def qInitResources(): QtCore.qRegisterResourceData( 0x01, qt_resource_struct, qt_resource_name, qt_resource_data ) + def qCleanupResources(): QtCore.qUnregisterResourceData( 0x01, qt_resource_struct, qt_resource_name, qt_resource_data ) - -qInitResources() From 765fcf701434a19c3ff4e0401a0bb904e6e8f1f5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 22 Oct 2021 14:24:25 +0200 Subject: [PATCH 249/279] fix typo in label --- openpype/tools/experimental_tools/tools_def.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/experimental_tools/tools_def.py b/openpype/tools/experimental_tools/tools_def.py index 3657c2385b..254f542c4d 100644 --- a/openpype/tools/experimental_tools/tools_def.py +++ b/openpype/tools/experimental_tools/tools_def.py @@ -72,7 +72,7 @@ class ExperimentalTools: # experimental_tools = [ # ExperimentalTool( # "example", - # "Exmaple experimental tool", + # "Example experimental tool", # example_callback, # "Example tool tooltip." # ) From dc5f2221b9c6d838ef383c648bea66e8fd0e2b69 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 22 Oct 2021 14:34:43 +0200 Subject: [PATCH 250/279] added label describing how to turn onoff experimental tools --- openpype/tools/experimental_tools/dialog.py | 39 +++++++++++++++++---- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/openpype/tools/experimental_tools/dialog.py b/openpype/tools/experimental_tools/dialog.py index c7c8ce83fc..0fd170b31e 100644 --- a/openpype/tools/experimental_tools/dialog.py +++ b/openpype/tools/experimental_tools/dialog.py @@ -30,6 +30,7 @@ class ExperimentalToolsDialog(QtWidgets.QDialog): icon = QtGui.QIcon(app_icon_path()) self.setWindowIcon(icon) + # Widgets for cases there are not available experimental tools empty_widget = QtWidgets.QWidget(self) empty_label = QtWidgets.QLabel( @@ -49,16 +50,42 @@ class ExperimentalToolsDialog(QtWidgets.QDialog): empty_layout.addStretch(1) empty_layout.addLayout(empty_btns_layout) - content_widget = QtWidgets.QWidget(self) + # Content of Experimental tools - content_layout = QtWidgets.QVBoxLayout(content_widget) + # Layout where buttons are added + content_layout = QtWidgets.QVBoxLayout() content_layout.setContentsMargins(0, 0, 0, 0) + # Separator line + separator_widget = QtWidgets.QWidget(self) + separator_widget.setObjectName("Separator") + separator_widget.setMinimumHeight(2) + separator_widget.setMaximumHeight(2) + + # Label describing how to turn off tools + tool_btns_widget = QtWidgets.QWidget(self) + tool_btns_label = QtWidgets.QLabel( + ( + "You can enable these features in" + "
OpenPype tray -> Settings -> Experimental tools" + ), + tool_btns_widget + ) + tool_btns_label.setAlignment(QtCore.Qt.AlignCenter) + + tool_btns_layout = QtWidgets.QVBoxLayout(tool_btns_widget) + tool_btns_layout.setContentsMargins(0, 0, 0, 0) + tool_btns_layout.addLayout(content_layout) + tool_btns_layout.addStretch(1) + tool_btns_layout.addWidget(separator_widget, 0) + tool_btns_layout.addWidget(tool_btns_label, 0) + experimental_tools = ExperimentalTools() + # Main layout layout = QtWidgets.QVBoxLayout(self) layout.addWidget(empty_widget, 1) - layout.addWidget(content_widget, 1) + layout.addWidget(tool_btns_widget, 1) refresh_timer = QtCore.QTimer() refresh_timer.setInterval(self.refresh_interval) @@ -67,7 +94,7 @@ class ExperimentalToolsDialog(QtWidgets.QDialog): ok_btn.clicked.connect(self._on_ok_click) self._empty_widget = empty_widget - self._content_widget = content_widget + self._tool_btns_widget = tool_btns_widget self._content_layout = content_layout self._experimental_tools = experimental_tools @@ -94,7 +121,7 @@ class ExperimentalToolsDialog(QtWidgets.QDialog): button = self._buttons_by_tool_identifier[identifier] else: is_new = True - button = ToolButton(identifier, self) + button = ToolButton(identifier, self._tool_btns_widget) button.triggered.connect(self._on_btn_trigger) self._buttons_by_tool_identifier[identifier] = button self._content_layout.insertWidget(idx, button) @@ -128,7 +155,7 @@ class ExperimentalToolsDialog(QtWidgets.QDialog): def _set_visibility(self): content_visible = self._is_content_visible() - self._content_widget.setVisible(content_visible) + self._tool_btns_widget.setVisible(content_visible) self._empty_widget.setVisible(not content_visible) def _on_ok_click(self): From e320065986f25b5b8a814b59a56363edef1548b2 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Sat, 23 Oct 2021 03:40:10 +0000 Subject: [PATCH 251/279] [Automated] Bump version --- CHANGELOG.md | 51 +++++++++++++++++++++++++-------------------- openpype/version.py | 2 +- 2 files changed, 29 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95792f8a7a..eca2a8b423 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,34 @@ # Changelog +## [3.5.1-nightly.1](https://github.com/pypeclub/OpenPype/tree/HEAD) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.5.0...HEAD) + +**πŸš€ Enhancements** + +- 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) +- 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) + +**πŸ› Bug fixes** + +- 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) +- Loader thumbnails with smooth edges [\#2147](https://github.com/pypeclub/OpenPype/pull/2147) +- Added validator for source files for Standalone Publisher [\#2138](https://github.com/pypeclub/OpenPype/pull/2138) + +**Merged pull requests:** + +- 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/3.4.1...3.5.0) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.5.0-nightly.8...3.5.0) **Deprecated:** @@ -66,10 +92,6 @@ [Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.4.1-nightly.1...3.4.1) -**πŸ†• New features** - -- Settings: Flag project as deactivated and hide from tools' view [\#2008](https://github.com/pypeclub/OpenPype/pull/2008) - **πŸš€ Enhancements** - General: Startup validations [\#2054](https://github.com/pypeclub/OpenPype/pull/2054) @@ -81,9 +103,7 @@ - Settings for Nuke IncrementScriptVersion [\#2039](https://github.com/pypeclub/OpenPype/pull/2039) - Loader & Library loader: Use tools from OpenPype [\#2038](https://github.com/pypeclub/OpenPype/pull/2038) - Adding predefined project folders creation in PM [\#2030](https://github.com/pypeclub/OpenPype/pull/2030) -- WebserverModule: Removed interface of webserver module [\#2028](https://github.com/pypeclub/OpenPype/pull/2028) - TimersManager: Removed interface of timers manager [\#2024](https://github.com/pypeclub/OpenPype/pull/2024) -- Feature Maya import asset from scene inventory [\#2018](https://github.com/pypeclub/OpenPype/pull/2018) **πŸ› Bug fixes** @@ -101,21 +121,12 @@ [Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.4.0-nightly.6...3.4.0) -**πŸ†• New features** - -- Nuke: Compatibility with Nuke 13 [\#2003](https://github.com/pypeclub/OpenPype/pull/2003) - **πŸš€ Enhancements** - Added possibility to configure of synchronization of workfile version… [\#2041](https://github.com/pypeclub/OpenPype/pull/2041) - General: Task types in profiles [\#2036](https://github.com/pypeclub/OpenPype/pull/2036) +- WebserverModule: Removed interface of webserver module [\#2028](https://github.com/pypeclub/OpenPype/pull/2028) - Console interpreter: Handle invalid sizes on initialization [\#2022](https://github.com/pypeclub/OpenPype/pull/2022) -- Ftrack: Show OpenPype versions in event server status [\#2019](https://github.com/pypeclub/OpenPype/pull/2019) -- General: Staging icon [\#2017](https://github.com/pypeclub/OpenPype/pull/2017) -- Ftrack: Sync to avalon actions have jobs [\#2015](https://github.com/pypeclub/OpenPype/pull/2015) -- Modules: Connect method is not required [\#2009](https://github.com/pypeclub/OpenPype/pull/2009) -- Settings UI: Number with configurable steps [\#2001](https://github.com/pypeclub/OpenPype/pull/2001) -- Moving project folder structure creation out of ftrack module \#1989 [\#1996](https://github.com/pypeclub/OpenPype/pull/1996) **πŸ› Bug fixes** @@ -124,12 +135,6 @@ - Nuke: typo on a button [\#2034](https://github.com/pypeclub/OpenPype/pull/2034) - Hiero: Fix "none" named tags [\#2033](https://github.com/pypeclub/OpenPype/pull/2033) - FFmpeg: Subprocess arguments as list [\#2032](https://github.com/pypeclub/OpenPype/pull/2032) -- General: Fix Python 2 breaking line [\#2016](https://github.com/pypeclub/OpenPype/pull/2016) -- Bugfix/webpublisher task type [\#2006](https://github.com/pypeclub/OpenPype/pull/2006) - -### πŸ“– Documentation - -- Documentation: Ftrack launch argsuments update [\#2014](https://github.com/pypeclub/OpenPype/pull/2014) ## [3.3.1](https://github.com/pypeclub/OpenPype/tree/3.3.1) (2021-08-20) diff --git a/openpype/version.py b/openpype/version.py index d88d79b995..49b61c755e 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.5.0" +__version__ = "3.5.1-nightly.1" From db7c8b69909472e2a8de435c2518731e9a53cc25 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Mon, 25 Oct 2021 09:22:38 +0200 Subject: [PATCH 252/279] fix Ci expected format in pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index dade0a2f57..c49ecabdab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.0.0" +version = "3.5.1-nightly.1" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From 9b529ce589c4b5e4cc80ce949677372146dcf1ee Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 25 Oct 2021 17:56:40 +0200 Subject: [PATCH 253/279] OP-1206 - fix no repres_widget issue without sync server --- openpype/tools/loader/app.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/tools/loader/app.py b/openpype/tools/loader/app.py index dac5e11d4c..04da08326f 100644 --- a/openpype/tools/loader/app.py +++ b/openpype/tools/loader/app.py @@ -164,8 +164,9 @@ class LoaderWindow(QtWidgets.QDialog): subsets_widget.load_started.connect(self._on_load_start) subsets_widget.load_ended.connect(self._on_load_end) - repres_widget.load_started.connect(self._on_load_start) - repres_widget.load_ended.connect(self._on_load_end) + if repres_widget: + repres_widget.load_started.connect(self._on_load_start) + repres_widget.load_ended.connect(self._on_load_end) self._sync_server_enabled = sync_server_enabled From db26055d563af7a92670aabd1c26121928527b80 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 25 Oct 2021 19:17:40 +0200 Subject: [PATCH 254/279] use plist info to get full path to executable --- openpype/lib/applications.py | 48 ++++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/openpype/lib/applications.py b/openpype/lib/applications.py index cc8cb8e7be..b9bcecd3a0 100644 --- a/openpype/lib/applications.py +++ b/openpype/lib/applications.py @@ -461,13 +461,8 @@ class ApplicationExecutable: # On MacOS check if exists path to executable when ends with `.app` # - it is common that path will lead to "/Applications/Blender" but # real path is "/Applications/Blender.app" - if ( - platform.system().lower() == "darwin" - and not os.path.exists(executable) - ): - _executable = executable + ".app" - if os.path.exists(_executable): - executable = _executable + if platform.system().lower() == "darwin": + executable = self.macos_executable_prep(executable) self.executable_path = executable @@ -477,6 +472,45 @@ class ApplicationExecutable: def __repr__(self): return "<{}> {}".format(self.__class__.__name__, self.executable_path) + @staticmethod + def macos_executable_prep(executable): + """Try to find full path to executable file. + + Real executable is stored in '*.app/Contents/MacOS/'. + + Having path to '*.app' gives ability to read it's plist info and + use "CFBundleExecutable" key from plist to know what is "executable." + + Plist is stored in '*.app/Contents/Info.plist'. + + This is because some '*.app' directories don't have same permissions + as real executable. + """ + # Try to find if there is `.app` file + if not os.path.exists(executable): + _executable = executable + ".app" + if os.path.exists(_executable): + executable = _executable + + # Try to find real executable if executable has `Contents` subfolder + contents_dir = os.path.join(executable, "Contents") + if os.path.exists(contents_dir): + executable_filename = None + # Load plist file and check for bundle executable + plist_filepath = os.path.join(contents_dir, "Info.plist") + if os.path.exists(plist_filepath): + import plistlib + + parsed_plist = plistlib.readPlist(plist_filepath) + executable_filename = parsed_plist.get("CFBundleExecutable") + + if executable_filename: + executable = os.path.join( + contents_dir, "MacOS", executable_filename + ) + + return executable + def as_args(self): return [self.executable_path] From 5107689f64122f67f41368eec666b1737780675f Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 26 Oct 2021 10:40:24 +0200 Subject: [PATCH 255/279] PYPE-1901 - fix multiple mapping configuration --- .../photoshop/plugins/publish/collect_remote_instances.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/photoshop/plugins/publish/collect_remote_instances.py b/openpype/hosts/photoshop/plugins/publish/collect_remote_instances.py index 9bb8e90350..12f9fa5ab5 100644 --- a/openpype/hosts/photoshop/plugins/publish/collect_remote_instances.py +++ b/openpype/hosts/photoshop/plugins/publish/collect_remote_instances.py @@ -106,12 +106,12 @@ class CollectRemoteInstances(pyblish.api.ContextPlugin): for mapping in self.color_code_mapping: if mapping["color_code"] and \ layer.color_code not in mapping["color_code"]: - break + continue if mapping["layer_name_regex"] and \ not any(re.search(pattern, layer.name) for pattern in mapping["layer_name_regex"]): - break + continue family_list.append(mapping["family"]) subset_name_list.append(mapping["subset_template_name"]) @@ -127,7 +127,6 @@ class CollectRemoteInstances(pyblish.api.ContextPlugin): format(layer.name)) self.log.warning("Only first family used!") family_list[:] = family_list[0] - if subset_name_list: resolved_subset_template = subset_name_list.pop() if family_list: From 5b4f266eb07bf57355e408bf34a35ad405087c29 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 26 Oct 2021 16:17:34 +0200 Subject: [PATCH 256/279] Hound and Review comments --- openpype/hosts/flame/__init__.py | 4 +- openpype/hosts/flame/api/lib.py | 192 ++++++++---------- openpype/hosts/flame/api/menu.py | 2 +- openpype/hosts/flame/api/utils.py | 12 +- openpype/hosts/flame/hooks/pre_flame_setup.py | 66 +++--- .../hosts/flame/utility_scripts/flame_hook.py | 4 +- 6 files changed, 137 insertions(+), 143 deletions(-) diff --git a/openpype/hosts/flame/__init__.py b/openpype/hosts/flame/__init__.py index dc3d3e7cba..48e8dc86c9 100644 --- a/openpype/hosts/flame/__init__.py +++ b/openpype/hosts/flame/__init__.py @@ -24,7 +24,7 @@ from .api.lib import ( ) from .api.menu import ( - FlameMenuProjectconnect, + FlameMenuProjectConnect, FlameMenuTimeline ) @@ -90,7 +90,7 @@ __all__ = [ "create_bin", # menu - "FlameMenuProjectconnect", + "FlameMenuProjectConnect", "FlameMenuTimeline", # plugin diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index 9d24e94df8..a58b67d54a 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -1,56 +1,51 @@ import sys -import json -import re import os import pickle import contextlib -from pprint import pprint, pformat -from opentimelineio import opentime -import openpype - - -# from ..otio import davinci_export as otio_export +from pprint import pformat from openpype.api import Logger log = Logger().get_logger(__name__) -self = sys.modules[__name__] -self.project_manager = None -self.media_storage = None -# OpenPype sequencial rename variables -self.rename_index = 0 -self.rename_add = 0 +@contextlib.contextmanager +def load_preferences_file(klass, filepath, attribute): + try: + with open(filepath, "r") as prefs_file: + setattr(klass, attribute, pickle.load(prefs_file)) -self.publish_clip_color = "Pink" -self.pype_marker_workflow = True + yield -# OpenPype compound clip workflow variable -self.pype_tag_name = "VFX Notes" + except IOError: + klass.log.info("Unable to load preferences from {}".format( + filepath)) -# OpenPype marker workflow variables -self.pype_marker_name = "OpenPypeData" -self.pype_marker_duration = 1 -self.pype_marker_color = "Mint" -self.temp_marker_frame = None + finally: + klass.log.info("Preferences loaded from {}".format(filepath)) -# OpenPype default timeline -self.pype_timeline_name = "OpenPypeTimeline" + +@contextlib.contextmanager +def save_preferences_file(klass, filepath, attribute): + try: + with open(filepath, "w") as prefs_file: + attr = getattr(klass, attribute) + pickle.dump(attr, prefs_file) + + yield + + except IOError: + klass.log.info("Unable to save preferences to {}".format( + filepath)) + + finally: + klass.log.info("Preferences saved to {}".format(filepath)) class FlameAppFramework(object): # flameAppFramework class takes care of preferences class prefs_dict(dict): - # subclass of a dict() in order to directly link it - # to main framework prefs dictionaries - # when accessed directly it will operate on a dictionary under a "name" - # key in master dictionary. - # master = {} - # p = prefs(master, "app_name") - # p["key"] = "value" - # master - {"app_name": {"key", "value"}} def __init__(self, master, name, **kwargs): self.name = name @@ -85,7 +80,7 @@ class FlameAppFramework(object): def __contains__(self, k): return self.master[self.name].__contains__(k) - def copy(self): # don"t delegate w/ super - dict.copy() -> dict :( + def copy(self): # don"t delegate w/ super - dict.copy() -> dict :( return type(self)(self) def keys(self): @@ -96,7 +91,8 @@ class FlameAppFramework(object): return cls.master[cls.name].fromkeys(keys, v) def __repr__(self): - return "{0}({1})".format(type(self).__name__, self.master[self.name].__repr__()) + return "{0}({1})".format( + type(self).__name__, self.master[self.name].__repr__()) def master_keys(self): return self.master.keys() @@ -110,13 +106,12 @@ class FlameAppFramework(object): self.prefs_global = {} self.log = log - try: import flame self.flame = flame self.flame_project_name = self.flame.project.current_project.name self.flame_user_name = flame.users.current_user.name - except: + except Exception: self.flame = None self.flame_project_name = None self.flame_user_name = None @@ -127,10 +122,11 @@ class FlameAppFramework(object): if sys.platform == "darwin": self.prefs_folder = os.path.join( os.path.expanduser("~"), - "Library", - "Caches", - "OpenPype", - self.bundle_name) + "Library", + "Caches", + "OpenPype", + self.bundle_name + ) elif sys.platform.startswith("linux"): self.prefs_folder = os.path.join( os.path.expanduser("~"), @@ -157,89 +153,74 @@ class FlameAppFramework(object): self.apps = [] - def load_prefs(self): + def get_pref_file_paths(self): + prefix = self.prefs_folder + os.path.sep + self.bundle_name - prefs_file_path = (prefix + "." + self.flame_user_name + "." - + self.flame_project_name + ".prefs") - prefs_user_file_path = (prefix + "." + self.flame_user_name - + ".prefs") + prefs_file_path = "_".join([ + prefix, self.flame_user_name, + self.flame_project_name]) + ".prefs" + prefs_user_file_path = "_".join([ + prefix, self.flame_user_name]) + ".prefs" prefs_global_file_path = prefix + ".prefs" - try: - with open(prefs_file_path, "r") as prefs_file: - self.prefs = pickle.load(prefs_file) + return (prefs_file_path, prefs_user_file_path, prefs_global_file_path) - self.log.info("preferences loaded from {}".format(prefs_file_path)) - self.log.info("preferences contents:\n" + pformat(self.prefs)) - except: - self.log.info("unable to load preferences from {}".format( - prefs_file_path)) + def load_prefs(self): - try: - with open(prefs_user_file_path, "r") as prefs_file: - self.prefs_user = pickle.load(prefs_file) - self.log.info("preferences loaded from {}".format( - prefs_user_file_path)) - self.log.info("preferences contents:\n" + pformat(self.prefs_user)) - except: - self.log.info("unable to load preferences from {}".format( - prefs_user_file_path)) + (proj_pref_path, user_pref_path, + glob_pref_path) = self.get_pref_file_paths() - try: - with open(prefs_global_file_path, "r") as prefs_file: - self.prefs_global = pickle.load(prefs_file) - self.log.info("preferences loaded from {}".format( - prefs_global_file_path)) - self.log.info("preferences contents:\n" + pformat(self.prefs_global)) + with load_preferences_file(self, proj_pref_path, "prefs"): + self.log.info( + "Project - preferences contents:\n{}".format( + pformat(self.prefs) + )) - except: - self.log.info("unable to load preferences from {}".format( - prefs_global_file_path)) + with load_preferences_file(self, user_pref_path, "prefs_user"): + self.log.info( + "User - preferences contents:\n{}".format( + pformat(self.prefs_user) + )) + + with load_preferences_file(self, glob_pref_path, "prefs_global"): + self.log.info( + "Global - preferences contents:\n{}".format( + pformat(self.prefs_global) + )) return True def save_prefs(self): - import pickle - + # make sure the preference folder is available if not os.path.isdir(self.prefs_folder): try: os.makedirs(self.prefs_folder) - except: - self.log.info("unable to create folder {}".format( + except Exception: + self.log.info("Unable to create folder {}".format( self.prefs_folder)) return False - prefix = self.prefs_folder + os.path.sep + self.bundle_name - prefs_file_path = prefix + "." + self.flame_user_name + "." + self.flame_project_name + ".prefs" - prefs_user_file_path = prefix + "." + self.flame_user_name + ".prefs" - prefs_global_file_path = prefix + ".prefs" + # get all pref file paths + (proj_pref_path, user_pref_path, + glob_pref_path) = self.get_pref_file_paths() - try: - prefs_file = open(prefs_file_path, "w") - pickle.dump(self.prefs, prefs_file) - prefs_file.close() - self.log.info("preferences saved to {}".format(prefs_file_path)) - self.log.info("preferences contents:\n" + pformat(self.prefs)) - except: - self.log.info("unable to save preferences to {}".format(prefs_file_path)) + with save_preferences_file(self, proj_pref_path, "prefs"): + self.log.info( + "Project - preferences contents:\n{}".format( + pformat(self.prefs) + )) - try: - prefs_file = open(prefs_user_file_path, "w") - pickle.dump(self.prefs_user, prefs_file) - prefs_file.close() - self.log.info("preferences saved to {}".format(prefs_user_file_path)) - self.log.info("preferences contents:\n" + pformat(self.prefs_user)) - except: - self.log.info("unable to save preferences to {}".format(prefs_user_file_path)) + with save_preferences_file(self, user_pref_path, "prefs_user"): + self.log.info( + "User - preferences contents:\n{}".format( + pformat(self.prefs_user) + )) - try: - prefs_file = open(prefs_global_file_path, "w") - pickle.dump(self.prefs_global, prefs_file) - prefs_file.close() - self.log.info("preferences saved to {}".format(prefs_global_file_path)) - self.log.info("preferences contents:\n" + pformat(self.prefs_global)) - except: - self.log.info("unable to save preferences to {}".format(prefs_global_file_path)) + with save_preferences_file(self, glob_pref_path, "prefs_global"): + self.log.info( + "Global - preferences contents:\n{}".format( + pformat(self.prefs_global) + )) return True @@ -263,6 +244,7 @@ def maintain_current_timeline(to_timeline, from_timeline=None): >>> print(get_current_timeline().GetName()) timeline1 """ + # todo: this is still Resolve's implementation project = get_current_project() working_timeline = from_timeline or project.GetCurrentTimeline() @@ -306,5 +288,5 @@ def rescan_hooks(): import flame try: flame.execute_shortcut('Rescan Python Hooks') - except: + except Exception: pass diff --git a/openpype/hosts/flame/api/menu.py b/openpype/hosts/flame/api/menu.py index 65d1535beb..184881c6a7 100644 --- a/openpype/hosts/flame/api/menu.py +++ b/openpype/hosts/flame/api/menu.py @@ -70,7 +70,7 @@ class _FlameMenuApp(object): self.log.info('Rescan Python Hooks') -class FlameMenuProjectconnect(_FlameMenuApp): +class FlameMenuProjectConnect(_FlameMenuApp): # flameMenuProjectconnect app takes care of the preferences dialog as well diff --git a/openpype/hosts/flame/api/utils.py b/openpype/hosts/flame/api/utils.py index 489b51e37c..bd321e194c 100644 --- a/openpype/hosts/flame/api/utils.py +++ b/openpype/hosts/flame/api/utils.py @@ -1,5 +1,3 @@ -#! python3 - """ Resolve's tools for setting environment """ @@ -9,7 +7,6 @@ import shutil from openpype.api import Logger log = Logger().get_logger(__name__) - def _sync_utility_scripts(env=None): """ Synchronizing basic utlility scripts for resolve. @@ -20,8 +17,7 @@ def _sync_utility_scripts(env=None): """ from .. import HOST_DIR - if not env: - env = os.environ + env = env or os.environ # initiate inputs scripts = {} @@ -41,7 +37,8 @@ def _sync_utility_scripts(env=None): # to script path search for _dirpath in fsd_env.split(os.pathsep): if not os.path.isdir(_dirpath): - log.warning("Path is not a valid dir: `{_dirpath}`".format(**locals())) + log.warning("Path is not a valid dir: `{_dirpath}`".format( + **locals())) continue fsd_paths.append(_dirpath) @@ -82,8 +79,7 @@ def _sync_utility_scripts(env=None): def setup(env=None): """ Wrapper installer started from pype.hooks.resolve.FlamePrelaunch() """ - if not env: - env = os.environ + env = env or os.environ # synchronize resolve utility scripts _sync_utility_scripts(env) diff --git a/openpype/hosts/flame/hooks/pre_flame_setup.py b/openpype/hosts/flame/hooks/pre_flame_setup.py index aec9a15e30..368a70f395 100644 --- a/openpype/hosts/flame/hooks/pre_flame_setup.py +++ b/openpype/hosts/flame/hooks/pre_flame_setup.py @@ -1,6 +1,7 @@ import os import json import tempfile +import contextlib from openpype.lib import ( PreLaunchHook, get_openpype_username) from openpype.hosts import flame as opflame @@ -76,41 +77,56 @@ class FlamePrelaunch(PreLaunchHook): # Dump data to string dumped_script_data = json.dumps(script_data) + with make_temp_file(dumped_script_data) as tmp_json_path: + # Prepare subprocess arguments + args = [ + self.flame_python_exe, + self.wtc_script_path, + tmp_json_path + ] + self.log.info("Executing: {}".format(" ".join(args))) + + process_kwargs = { + "logger": self.log, + "env": {} + } + + openpype.api.run_subprocess(args, **process_kwargs) + + # process returned json file to pass launch args + return_json_data = open(tmp_json_path).read() + returned_data = json.loads(return_json_data) + app_args = returned_data.get("app_args") + self.log.info("____ app_args: `{}`".format(app_args)) + + if not app_args: + RuntimeError("App arguments were not solved") + + return app_args + + +@contextlib.contextmanager +def make_temp_file(data): + try: # Store dumped json to temporary file temporary_json_file = tempfile.NamedTemporaryFile( mode="w", suffix=".json", delete=False ) - temporary_json_file.write(dumped_script_data) + temporary_json_file.write(data) temporary_json_file.close() temporary_json_filepath = temporary_json_file.name.replace( "\\", "/" ) - # Prepare subprocess arguments - args = [ - self.flame_python_exe, - self.wtc_script_path, - temporary_json_filepath - ] - self.log.info("Executing: {}".format(" ".join(args))) + yield temporary_json_filepath - process_kwargs = { - "logger": self.log, - "env": {} - } - - openpype.api.run_subprocess(args, **process_kwargs) - - # process returned json file to pass launch args - return_json_data = open(temporary_json_filepath).read() - returned_data = json.loads(return_json_data) - app_args = returned_data.get("app_args") - self.log.info("____ app_args: `{}`".format(app_args)) - - if not app_args: - RuntimeError("App arguments were not solved") + except IOError as _error: + raise IOError( + "Not able to create temp json file: {}".format( + _error + ) + ) + finally: # Remove the temporary json os.remove(temporary_json_filepath) - - return app_args diff --git a/openpype/hosts/flame/utility_scripts/flame_hook.py b/openpype/hosts/flame/utility_scripts/flame_hook.py index b46109a609..2233b97d32 100644 --- a/openpype/hosts/flame/utility_scripts/flame_hook.py +++ b/openpype/hosts/flame/utility_scripts/flame_hook.py @@ -55,7 +55,7 @@ atexit.register(cleanup) def load_apps(): - opflame.apps.append(opflame.FlameMenuProjectconnect(opflame.app_framework)) + opflame.apps.append(opflame.FlameMenuProjectConnect(opflame.app_framework)) opflame.apps.append(opflame.FlameMenuTimeline(opflame.app_framework)) opflame.app_framework.log.info("Apps are loaded") @@ -121,7 +121,7 @@ def get_main_menu_custom_ui_actions(): # install openpype and the host openpype_install() - return _build_app_menu("FlameMenuProjectconnect") + return _build_app_menu("FlameMenuProjectConnect") def get_timeline_custom_ui_actions(): From f6076af2ad5e513eb1fa7f6fd22c00d1e3161810 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 26 Oct 2021 16:26:34 +0200 Subject: [PATCH 257/279] Hound and review comments --- openpype/hosts/flame/api/menu.py | 31 ++++++++++++++++++---------- openpype/hosts/flame/api/pipeline.py | 7 ------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/openpype/hosts/flame/api/menu.py b/openpype/hosts/flame/api/menu.py index 184881c6a7..b4f1728acf 100644 --- a/openpype/hosts/flame/api/menu.py +++ b/openpype/hosts/flame/api/menu.py @@ -1,19 +1,28 @@ import os -import sys -from Qt import QtWidgets, QtCore -from pprint import pprint, pformat +from Qt import QtWidgets from copy import deepcopy -from .lib import rescan_hooks from openpype.tools.utils.host_tools import HostToolsHelper menu_group_name = 'OpenPype' default_flame_export_presets = { - 'Publish': {'PresetVisibility': 2, 'PresetType': 0, 'PresetFile': 'OpenEXR/OpenEXR (16-bit fp PIZ).xml'}, - 'Preview': {'PresetVisibility': 3, 'PresetType': 2, 'PresetFile': 'Generate Preview.xml'}, - 'Thumbnail': {'PresetVisibility': 3, 'PresetType': 0, 'PresetFile': 'Generate Thumbnail.xml'} + 'Publish': { + 'PresetVisibility': 2, + 'PresetType': 0, + 'PresetFile': 'OpenEXR/OpenEXR (16-bit fp PIZ).xml' + }, + 'Preview': { + 'PresetVisibility': 3, + 'PresetType': 2, + 'PresetFile': 'Generate Preview.xml' + }, + 'Thumbnail': { + 'PresetVisibility': 3, + 'PresetType': 0, + 'PresetFile': 'Generate Thumbnail.xml' + } } @@ -31,7 +40,7 @@ class _FlameMenuApp(object): try: import flame self.flame = flame - except: + except ImportError: self.flame = None self.flame_project_name = flame.project.current_project.name @@ -62,7 +71,7 @@ class _FlameMenuApp(object): try: import flame self.flame = flame - except: + except ImportError: self.flame = None if self.flame: @@ -130,7 +139,7 @@ class FlameMenuProjectConnect(_FlameMenuApp): try: import flame self.flame = flame - except: + except ImportError: self.flame = None if self.flame: @@ -191,7 +200,7 @@ class FlameMenuTimeline(_FlameMenuApp): try: import flame self.flame = flame - except: + except ImportError: self.flame = None if self.flame: diff --git a/openpype/hosts/flame/api/pipeline.py b/openpype/hosts/flame/api/pipeline.py index 297ab0e44c..85b9f7e24a 100644 --- a/openpype/hosts/flame/api/pipeline.py +++ b/openpype/hosts/flame/api/pipeline.py @@ -1,23 +1,16 @@ """ Basic avalon integration """ -import os import contextlib -from collections import OrderedDict -from avalon.tools import workfiles from avalon import api as avalon -from avalon import schema -from avalon.pipeline import AVALON_CONTAINER_ID from pyblish import api as pyblish from openpype.api import Logger -from . import lib AVALON_CONTAINERS = "AVALON_CONTAINERS" log = Logger().get_logger(__name__) - def install(): from .. import ( PUBLISH_PATH, From 97a405b5a119ba7822c062cb69111ba43e4a0342 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 26 Oct 2021 20:13:12 +0200 Subject: [PATCH 258/279] Hound suggestions --- openpype/hosts/flame/api/plugin.py | 10 ---------- openpype/hosts/flame/utility_scripts/flame_hook.py | 11 ++++------- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/openpype/hosts/flame/api/plugin.py b/openpype/hosts/flame/api/plugin.py index ac86c7c224..2a28a20a75 100644 --- a/openpype/hosts/flame/api/plugin.py +++ b/openpype/hosts/flame/api/plugin.py @@ -1,13 +1,3 @@ -import re -import uuid -from avalon import api -import openpype.api as pype -from openpype.hosts import resolve -from avalon.vendor import qargparse -from . import lib - -from Qt import QtWidgets, QtCore - # Creator plugin functions # Publishing plugin functions # Loader plugin functions diff --git a/openpype/hosts/flame/utility_scripts/flame_hook.py b/openpype/hosts/flame/utility_scripts/flame_hook.py index 2233b97d32..f482126624 100644 --- a/openpype/hosts/flame/utility_scripts/flame_hook.py +++ b/openpype/hosts/flame/utility_scripts/flame_hook.py @@ -1,6 +1,6 @@ import sys -from Qt import QtWidgets, QtCore -from pprint import pprint, pformat +from Qt import QtWidgets +from pprint import pformat import atexit import openpype import avalon @@ -81,11 +81,7 @@ except ImportError: def rescan_hooks(): - import flame - try: - flame.execute_shortcut('Rescan Python Hooks') - except: - pass + flame.execute_shortcut('Rescan Python Hooks') def _build_app_menu(app_name): @@ -112,6 +108,7 @@ def _build_app_menu(app_name): return menu + def project_saved(project_name, save_time, is_auto_save): if opflame.app_framework: opflame.app_framework.save_prefs() From deada2422e39182151285cfc03f032ea298842ad Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 26 Oct 2021 20:36:04 +0200 Subject: [PATCH 259/279] flame hook adding docstrings --- .../hosts/flame/utility_scripts/flame_hook.py | 77 ++++++++++++++++--- 1 file changed, 68 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/flame/utility_scripts/flame_hook.py b/openpype/hosts/flame/utility_scripts/flame_hook.py index f482126624..bce668a389 100644 --- a/openpype/hosts/flame/utility_scripts/flame_hook.py +++ b/openpype/hosts/flame/utility_scripts/flame_hook.py @@ -1,3 +1,4 @@ +from __future__ import print_function import sys from Qt import QtWidgets from pprint import pformat @@ -11,23 +12,32 @@ flh._project = None def openpype_install(): + """Registering OpenPype in context + """ openpype.install() avalon.api.install(opflame) - print("<<<<<<<<<<< Avalon registred hosts: {} >>>>>>>>>>>>>>>".format( + print("Avalon registred hosts: {}".format( avalon.api.registered_host())) # Exception handler -def exeption_handler(exctype, value, tb): +def exeption_handler(exctype, value, _traceback): + """Exception handler for improving UX + + Args: + exctype (str): type of exception + value (str): exception value + tb (str): traceback to show + """ import traceback msg = "OpenPype: Python exception {} in {}".format(value, exctype) mbox = QtWidgets.QMessageBox() mbox.setText(msg) mbox.setDetailedText( - pformat(traceback.format_exception(exctype, value, tb))) + pformat(traceback.format_exception(exctype, value, _traceback))) mbox.setStyleSheet('QLabel{min-width: 800px;}') mbox.exec_() - sys.__excepthook__(exctype, value, tb) + sys.__excepthook__(exctype, value, _traceback) # add exception handler into sys module @@ -36,12 +46,14 @@ sys.excepthook = exeption_handler # register clean up logic to be called at Flame exit def cleanup(): + """Cleaning up Flame framework context + """ if opflame.apps: - print('<<<< `{}` cleaning up apps:\n {}\n'.format( + print('`{}` cleaning up apps:\n {}\n'.format( __file__, pformat(opflame.apps))) while len(opflame.apps): app = opflame.apps.pop() - print('<<<< `{}` removing : {}'.format(__file__, app.name)) + print('`{}` removing : {}'.format(__file__, app.name)) del app opflame.apps = [] @@ -55,24 +67,44 @@ atexit.register(cleanup) def load_apps(): + """Load available apps into Flame framework + """ opflame.apps.append(opflame.FlameMenuProjectConnect(opflame.app_framework)) opflame.apps.append(opflame.FlameMenuTimeline(opflame.app_framework)) opflame.app_framework.log.info("Apps are loaded") def project_changed_dict(info): + """Hook for project change action + + Args: + info (str): info text + """ cleanup() def app_initialized(parent=None): + """Inicialization of Framework + + Args: + parent (obj, optional): Parent object. Defaults to None. + """ opflame.app_framework = opflame.FlameAppFramework() - print(">> flame_hook.py: {} initializing".format( + print("{} initializing".format( opflame.app_framework.bundle_name)) load_apps() +""" +Initialisation of the hook is starting from here + +First it needs to test if it can import the flame modul. +This will happen only in case a project has been loaded. +Then `app_initialized` will load main Framework which will load +all menu objects as apps. +""" try: import flame app_initialized(parent=None) @@ -85,7 +117,17 @@ def rescan_hooks(): def _build_app_menu(app_name): + """Flame menu object generator + + Args: + app_name (str): name of menu object app + + Returns: + list: menu object + """ menu = [] + + # first find the relative appname app = None for _app in opflame.apps: if _app.__class__.__name__ == app_name: @@ -94,8 +136,6 @@ def _build_app_menu(app_name): if app: menu.append(app.build_menu()) - print(">>_> `{}` was build: {}".format(app_name, pformat(menu))) - if opflame.app_framework: menu_auto_refresh = opflame.app_framework.prefs_global.get( 'menu_auto_refresh', {}) @@ -109,12 +149,26 @@ def _build_app_menu(app_name): return menu +""" Flame hooks are starting here +""" def project_saved(project_name, save_time, is_auto_save): + """Hook to activate when project is saved + + Args: + project_name (str): name of project + save_time (str): time when it was saved + is_auto_save (bool): autosave is on or off + """ if opflame.app_framework: opflame.app_framework.save_prefs() def get_main_menu_custom_ui_actions(): + """Hook to create submenu in start menu + + Returns: + list: menu object + """ # install openpype and the host openpype_install() @@ -122,6 +176,11 @@ def get_main_menu_custom_ui_actions(): def get_timeline_custom_ui_actions(): + """Hook to create submenu in timeline + + Returns: + list: menu object + """ # install openpype and the host openpype_install() From a2330d218c0e069d2ec4f3074c0a3e9bc2b415fa Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 26 Oct 2021 20:56:47 +0200 Subject: [PATCH 260/279] Hound suggestions --- openpype/hosts/flame/scripts/wiretap_com.py | 41 +++++++++++++-------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/openpype/hosts/flame/scripts/wiretap_com.py b/openpype/hosts/flame/scripts/wiretap_com.py index a5925d0546..d8dc1884cf 100644 --- a/openpype/hosts/flame/scripts/wiretap_com.py +++ b/openpype/hosts/flame/scripts/wiretap_com.py @@ -10,20 +10,26 @@ import xml.dom.minidom as minidom from copy import deepcopy import datetime -# Todo: this has to be replaced with somehting more dynamic -flame_python_path = "/opt/Autodesk/flame_2021/python" -flame_exe_path = "/opt/Autodesk/flame_2021/bin/flame.app/Contents/MacOS/startApp" +try: + from libwiretapPythonClientAPI import ( + WireTapClientInit) +except ImportError: + flame_python_path = "/opt/Autodesk/flame_2021/python" + flame_exe_path = ( + "/opt/Autodesk/flame_2021/bin/flame.app" + "/Contents/MacOS/startApp") -sys.path.append(flame_python_path) + sys.path.append(flame_python_path) + + from libwiretapPythonClientAPI import ( + WireTapClientInit, + WireTapClientUninit, + WireTapNodeHandle, + WireTapServerHandle, + WireTapInt, + WireTapStr + ) -from libwiretapPythonClientAPI import ( - WireTapClientInit, - WireTapClientUninit, - WireTapNodeHandle, - WireTapServerHandle, - WireTapInt, - WireTapStr -) class WireTapCom(object): """ @@ -231,7 +237,8 @@ class WireTapCom(object): if not get_children_name: raise AttributeError( - "Unable to get child name: {}".format(child_obj.lastError()) + "Unable to get child name: {}".format( + child_obj.lastError()) ) volumes.append(node_name.c_str()) @@ -256,11 +263,12 @@ class WireTapCom(object): filtered_users = [user for user in used_names if user_name in user] if filtered_users: - # todo: need to find lastly created following regex patern for date used in name + # todo: need to find lastly created following regex patern for + # date used in name return filtered_users.pop() # create new user name with date in suffix - now = datetime.datetime.now() # current date and time + now = datetime.datetime.now() # current date and time date = now.strftime("%Y%m%d") new_user_name = "{}_{}".format(user_name, date) print(new_user_name) @@ -314,7 +322,8 @@ class WireTapCom(object): if not get_children_name: raise AttributeError( - "Unable to get child name: {}".format(child_obj.lastError()) + "Unable to get child name: {}".format( + child_obj.lastError()) ) usernames.append(node_name.c_str()) From 3b67b7d4849c5910eceaa0f521a7fb92d43634b3 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 26 Oct 2021 20:57:11 +0200 Subject: [PATCH 261/279] flame hook: renaming to something more appropriate --- .../utility_scripts/{flame_hook.py => openpype_in_flame.py} | 3 +++ 1 file changed, 3 insertions(+) rename openpype/hosts/flame/utility_scripts/{flame_hook.py => openpype_in_flame.py} (99%) diff --git a/openpype/hosts/flame/utility_scripts/flame_hook.py b/openpype/hosts/flame/utility_scripts/openpype_in_flame.py similarity index 99% rename from openpype/hosts/flame/utility_scripts/flame_hook.py rename to openpype/hosts/flame/utility_scripts/openpype_in_flame.py index bce668a389..afbd44afc9 100644 --- a/openpype/hosts/flame/utility_scripts/flame_hook.py +++ b/openpype/hosts/flame/utility_scripts/openpype_in_flame.py @@ -105,6 +105,7 @@ This will happen only in case a project has been loaded. Then `app_initialized` will load main Framework which will load all menu objects as apps. """ + try: import flame app_initialized(parent=None) @@ -151,6 +152,8 @@ def _build_app_menu(app_name): """ Flame hooks are starting here """ + + def project_saved(project_name, save_time, is_auto_save): """Hook to activate when project is saved From 65d860d3f8cc54d86ca8c9f5acb63a68571866a8 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 26 Oct 2021 21:06:55 +0200 Subject: [PATCH 262/279] Flame: custom script dirs variable changed to plural --- openpype/hosts/flame/api/utils.py | 10 ++++++---- .../defaults/system_settings/applications.json | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/flame/api/utils.py b/openpype/hosts/flame/api/utils.py index bd321e194c..daebea32a7 100644 --- a/openpype/hosts/flame/api/utils.py +++ b/openpype/hosts/flame/api/utils.py @@ -1,5 +1,5 @@ """ -Resolve's tools for setting environment +Flame utils for syncing scripts """ import os @@ -7,8 +7,9 @@ import shutil from openpype.api import Logger log = Logger().get_logger(__name__) + def _sync_utility_scripts(env=None): - """ Synchronizing basic utlility scripts for resolve. + """ Synchronizing basic utlility scripts for flame. To be able to run start OpenPype within Flame we have to copy all utility_scripts and additional FLAME_SCRIPT_DIR into @@ -21,7 +22,7 @@ def _sync_utility_scripts(env=None): # initiate inputs scripts = {} - fsd_env = env.get("FLAME_SCRIPT_DIR", "") + fsd_env = env.get("FLAME_SCRIPT_DIRS", "") flame_shared_dir = "/opt/Autodesk/shared/python" fsd_paths = [os.path.join( @@ -77,7 +78,8 @@ def _sync_utility_scripts(env=None): def setup(env=None): - """ Wrapper installer started from pype.hooks.resolve.FlamePrelaunch() + """ Wrapper installer started from + `flame/hooks/pre_flame_setup.py` """ env = env or os.environ diff --git a/openpype/settings/defaults/system_settings/applications.json b/openpype/settings/defaults/system_settings/applications.json index 8aa7f3c1a3..2866673f4b 100644 --- a/openpype/settings/defaults/system_settings/applications.json +++ b/openpype/settings/defaults/system_settings/applications.json @@ -103,7 +103,7 @@ "icon": "{}/app_icons/flame.png", "host_name": "flame", "environment": { - "FLAME_SCRIPT_DIR": { + "FLAME_SCRIPT_DIRS": { "windows": "", "darvin": "", "linux": "" From 25557e7be8a7058f727acc4cdb89bb48a897b234 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 26 Oct 2021 21:07:06 +0200 Subject: [PATCH 263/279] hound suggestion --- openpype/hosts/flame/api/workio.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/flame/api/workio.py b/openpype/hosts/flame/api/workio.py index 00fcdb9405..d2e2408798 100644 --- a/openpype/hosts/flame/api/workio.py +++ b/openpype/hosts/flame/api/workio.py @@ -2,10 +2,10 @@ import os from openpype.api import Logger -from .. import ( - get_project_manager, - get_current_project -) +# from .. import ( +# get_project_manager, +# get_current_project +# ) log = Logger().get_logger(__name__) From d01ffd9373a0cc2f5196804da9e53f4c660e80ec Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 26 Oct 2021 21:24:11 +0200 Subject: [PATCH 264/279] flame: fixing contextmanager for save and load preferences file --- openpype/hosts/flame/api/lib.py | 48 +++++++++++---------------------- 1 file changed, 16 insertions(+), 32 deletions(-) diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index a58b67d54a..2b3396c420 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -10,37 +10,15 @@ log = Logger().get_logger(__name__) @contextlib.contextmanager -def load_preferences_file(klass, filepath, attribute): +def io_preferences_file(klass, filepath, write=False): try: - with open(filepath, "r") as prefs_file: - setattr(klass, attribute, pickle.load(prefs_file)) - - yield + flag = "w" if write else "r" + yield open(filepath, flag) except IOError: - klass.log.info("Unable to load preferences from {}".format( + klass.log.info("Unable to work with preferences `{}`".format( filepath)) - finally: - klass.log.info("Preferences loaded from {}".format(filepath)) - - -@contextlib.contextmanager -def save_preferences_file(klass, filepath, attribute): - try: - with open(filepath, "w") as prefs_file: - attr = getattr(klass, attribute) - pickle.dump(attr, prefs_file) - - yield - - except IOError: - klass.log.info("Unable to save preferences to {}".format( - filepath)) - - finally: - klass.log.info("Preferences saved to {}".format(filepath)) - class FlameAppFramework(object): # flameAppFramework class takes care of preferences @@ -170,19 +148,22 @@ class FlameAppFramework(object): (proj_pref_path, user_pref_path, glob_pref_path) = self.get_pref_file_paths() - with load_preferences_file(self, proj_pref_path, "prefs"): + with io_preferences_file(self, proj_pref_path) as prefs_file: + self.prefs = pickle.load(prefs_file) self.log.info( "Project - preferences contents:\n{}".format( pformat(self.prefs) )) - with load_preferences_file(self, user_pref_path, "prefs_user"): + with io_preferences_file(self, user_pref_path) as prefs_file: + self.prefs_user = pickle.load(prefs_file) self.log.info( "User - preferences contents:\n{}".format( pformat(self.prefs_user) )) - with load_preferences_file(self, glob_pref_path, "prefs_global"): + with io_preferences_file(self, glob_pref_path) as prefs_file: + self.prefs_global = pickle.load(prefs_file) self.log.info( "Global - preferences contents:\n{}".format( pformat(self.prefs_global) @@ -204,19 +185,22 @@ class FlameAppFramework(object): (proj_pref_path, user_pref_path, glob_pref_path) = self.get_pref_file_paths() - with save_preferences_file(self, proj_pref_path, "prefs"): + with io_preferences_file(self, proj_pref_path, True) as prefs_file: + pickle.dump(self.prefs, prefs_file) self.log.info( "Project - preferences contents:\n{}".format( pformat(self.prefs) )) - with save_preferences_file(self, user_pref_path, "prefs_user"): + with io_preferences_file(self, user_pref_path, True) as prefs_file: + pickle.dump(self.prefs_user, prefs_file) self.log.info( "User - preferences contents:\n{}".format( pformat(self.prefs_user) )) - with save_preferences_file(self, glob_pref_path, "prefs_global"): + with io_preferences_file(self, glob_pref_path, True) as prefs_file: + pickle.dump(self.prefs_global, prefs_file) self.log.info( "Global - preferences contents:\n{}".format( pformat(self.prefs_global) From 1d8acaa45dbd01d2dff4c014958ccfd1fc96a877 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 26 Oct 2021 21:36:40 +0200 Subject: [PATCH 265/279] Flame debugging `io_preferences_file` --- openpype/hosts/flame/api/lib.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index 2b3396c420..48331dcbc2 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -15,9 +15,9 @@ def io_preferences_file(klass, filepath, write=False): flag = "w" if write else "r" yield open(filepath, flag) - except IOError: - klass.log.info("Unable to work with preferences `{}`".format( - filepath)) + except IOError as _error: + klass.log.info("Unable to work with preferences `{}`: {}".format( + filepath, _error)) class FlameAppFramework(object): From 211fc22e090253d0be7b971dccd9a87f369f485d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 26 Oct 2021 21:36:57 +0200 Subject: [PATCH 266/279] Flame: cleaning Resolve mentioning --- openpype/hosts/flame/api/pipeline.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/flame/api/pipeline.py b/openpype/hosts/flame/api/pipeline.py index 85b9f7e24a..26dfe7c032 100644 --- a/openpype/hosts/flame/api/pipeline.py +++ b/openpype/hosts/flame/api/pipeline.py @@ -36,7 +36,7 @@ def install(): pyblish.register_host("flame") pyblish.register_plugin_path(PUBLISH_PATH) - log.info("Registering DaVinci Resovle plug-ins..") + log.info("Registering Flame plug-ins..") avalon.register_plugin_path(avalon.Loader, LOAD_PATH) avalon.register_plugin_path(avalon.Creator, CREATE_PATH) @@ -129,13 +129,13 @@ def on_pyblish_instance_toggled(instance, old_value, new_value): log.info("instance toggle: {}, old_value: {}, new_value:{} ".format( instance, old_value, new_value)) - from openpype.hosts.resolve import ( - set_publish_attribute - ) + # from openpype.hosts.resolve import ( + # set_publish_attribute + # ) - # Whether instances should be passthrough based on new value - timeline_item = instance.data["item"] - set_publish_attribute(timeline_item, new_value) + # # Whether instances should be passthrough based on new value + # timeline_item = instance.data["item"] + # set_publish_attribute(timeline_item, new_value) def remove_instance(instance): From 86c528dd4a836f444d28f2a63fe84314660eec6f Mon Sep 17 00:00:00 2001 From: OpenPype Date: Wed, 27 Oct 2021 03:40:31 +0000 Subject: [PATCH 267/279] [Automated] Bump version --- CHANGELOG.md | 8 ++++---- openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eca2a8b423..68409c4db8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [3.5.1-nightly.1](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.6.0-nightly.1](https://github.com/pypeclub/OpenPype/tree/HEAD) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.5.0...HEAD) @@ -8,11 +8,13 @@ - 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) **πŸ› Bug fixes** +- MacOS: Launching of applications may cause Permissions error [\#2175](https://github.com/pypeclub/OpenPype/pull/2175) - 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) @@ -103,7 +105,7 @@ - Settings for Nuke IncrementScriptVersion [\#2039](https://github.com/pypeclub/OpenPype/pull/2039) - Loader & Library loader: Use tools from OpenPype [\#2038](https://github.com/pypeclub/OpenPype/pull/2038) - Adding predefined project folders creation in PM [\#2030](https://github.com/pypeclub/OpenPype/pull/2030) -- TimersManager: Removed interface of timers manager [\#2024](https://github.com/pypeclub/OpenPype/pull/2024) +- WebserverModule: Removed interface of webserver module [\#2028](https://github.com/pypeclub/OpenPype/pull/2028) **πŸ› Bug fixes** @@ -125,8 +127,6 @@ - Added possibility to configure of synchronization of workfile version… [\#2041](https://github.com/pypeclub/OpenPype/pull/2041) - General: Task types in profiles [\#2036](https://github.com/pypeclub/OpenPype/pull/2036) -- WebserverModule: Removed interface of webserver module [\#2028](https://github.com/pypeclub/OpenPype/pull/2028) -- Console interpreter: Handle invalid sizes on initialization [\#2022](https://github.com/pypeclub/OpenPype/pull/2022) **πŸ› Bug fixes** diff --git a/openpype/version.py b/openpype/version.py index 49b61c755e..6eb58f6fcc 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.5.1-nightly.1" +__version__ = "3.6.0-nightly.1" diff --git a/pyproject.toml b/pyproject.toml index c49ecabdab..1a112d2071 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.5.1-nightly.1" # OpenPype +version = "3.6.0-nightly.1" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From e8b1467800fcda662e83fce6a987d374b00ecaf1 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 27 Oct 2021 12:28:53 +0200 Subject: [PATCH 268/279] PYPE-1901 - currently only image family is created in PS --- .../schemas/projects_schema/schema_project_photoshop.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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 cd457ee21d..cec3f58460 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json @@ -81,7 +81,12 @@ { "key": "family", "label": "Resulting family", - "type": "text" + "type": "enum", + "enum_items": [ + { + "image": "image" + } + ] }, { "type": "text", From 82f1cb9a0f5716d8e6547eb95b4b42a52daba39d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 27 Oct 2021 14:42:03 +0200 Subject: [PATCH 269/279] flame settings fixing darvin to darwin --- openpype/hosts/flame/api/utils.py | 2 +- .../defaults/system_settings/applications.json | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/flame/api/utils.py b/openpype/hosts/flame/api/utils.py index daebea32a7..4ae7157812 100644 --- a/openpype/hosts/flame/api/utils.py +++ b/openpype/hosts/flame/api/utils.py @@ -31,7 +31,7 @@ def _sync_utility_scripts(env=None): )] # collect script dirs - log.info("FLAME_SCRIPT_DIR: `{fsd_env}`".format(**locals())) + log.info("FLAME_SCRIPT_DIRS: `{fsd_env}`".format(**locals())) log.info("fsd_paths: `{fsd_paths}`".format(**locals())) # add application environment setting for FLAME_SCRIPT_DIR diff --git a/openpype/settings/defaults/system_settings/applications.json b/openpype/settings/defaults/system_settings/applications.json index 2866673f4b..79711f3067 100644 --- a/openpype/settings/defaults/system_settings/applications.json +++ b/openpype/settings/defaults/system_settings/applications.json @@ -105,7 +105,7 @@ "environment": { "FLAME_SCRIPT_DIRS": { "windows": "", - "darvin": "", + "darwin": "", "linux": "" } }, @@ -656,12 +656,12 @@ "FUSION_UTILITY_SCRIPTS_SOURCE_DIR": [], "FUSION_UTILITY_SCRIPTS_DIR": { "windows": "{PROGRAMDATA}/Blackmagic Design/Fusion/Scripts/Comp", - "darvin": "/Library/Application Support/Blackmagic Design/Fusion/Scripts/Comp", + "darwin": "/Library/Application Support/Blackmagic Design/Fusion/Scripts/Comp", "linux": "/opt/Fusion/Scripts/Comp" }, "PYTHON36": { "windows": "{LOCALAPPDATA}/Programs/Python/Python36", - "darvin": "~/Library/Python/3.6/bin", + "darwin": "~/Library/Python/3.6/bin", "linux": "/opt/Python/3.6/bin" }, "PYTHONPATH": [ @@ -722,22 +722,22 @@ "RESOLVE_UTILITY_SCRIPTS_SOURCE_DIR": [], "RESOLVE_SCRIPT_API": { "windows": "{PROGRAMDATA}/Blackmagic Design/DaVinci Resolve/Support/Developer/Scripting", - "darvin": "/Library/Application Support/Blackmagic Design/DaVinci Resolve/Developer/Scripting", + "darwin": "/Library/Application Support/Blackmagic Design/DaVinci Resolve/Developer/Scripting", "linux": "/opt/resolve/Developer/Scripting" }, "RESOLVE_SCRIPT_LIB": { "windows": "C:/Program Files/Blackmagic Design/DaVinci Resolve/fusionscript.dll", - "darvin": "/Applications/DaVinci Resolve/DaVinci Resolve.app/Contents/Libraries/Fusion/fusionscript.so", + "darwin": "/Applications/DaVinci Resolve/DaVinci Resolve.app/Contents/Libraries/Fusion/fusionscript.so", "linux": "/opt/resolve/libs/Fusion/fusionscript.so" }, "RESOLVE_UTILITY_SCRIPTS_DIR": { "windows": "{PROGRAMDATA}/Blackmagic Design/DaVinci Resolve/Fusion/Scripts/Comp", - "darvin": "/Library/Application Support/Blackmagic Design/DaVinci Resolve/Fusion/Scripts/Comp", + "darwin": "/Library/Application Support/Blackmagic Design/DaVinci Resolve/Fusion/Scripts/Comp", "linux": "/opt/resolve/Fusion/Scripts/Comp" }, "PYTHON36_RESOLVE": { "windows": "{LOCALAPPDATA}/Programs/Python/Python36", - "darvin": "~/Library/Python/3.6/bin", + "darwin": "~/Library/Python/3.6/bin", "linux": "/opt/Python/3.6/bin" }, "PYTHONPATH": [ From c04a86ddbff933d6604672307265b221d9c68e30 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 27 Oct 2021 14:54:17 +0200 Subject: [PATCH 270/279] Flame: adding filter to sync utility script --- openpype/hosts/flame/api/utils.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openpype/hosts/flame/api/utils.py b/openpype/hosts/flame/api/utils.py index 4ae7157812..3a36b30784 100644 --- a/openpype/hosts/flame/api/utils.py +++ b/openpype/hosts/flame/api/utils.py @@ -47,12 +47,20 @@ def _sync_utility_scripts(env=None): for path in fsd_paths: scripts.update({path: os.listdir(path)}) + remove_black_list = [] + for _k, s_list in scripts.items(): + remove_black_list += s_list + log.info("Additional Flame script paths: `{fsd_paths}`".format(**locals())) log.info("Flame Scripts: `{scripts}`".format(**locals())) # make sure no script file is in folder if next(iter(os.listdir(flame_shared_dir)), None): for s in os.listdir(flame_shared_dir): + # skip all scripts and folders which are not maintained + if s not in remove_black_list: + continue + path = os.path.join(flame_shared_dir, s) log.info("Removing `{path}`...".format(**locals())) if os.path.isdir(path): From 432fa380f9ac15f6c7fb1d1a6dba99d9da1838dd Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 27 Oct 2021 15:38:06 +0200 Subject: [PATCH 271/279] Flame: adding exception for not maintained files during sync --- openpype/hosts/flame/api/utils.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/flame/api/utils.py b/openpype/hosts/flame/api/utils.py index 3a36b30784..0a3ee68815 100644 --- a/openpype/hosts/flame/api/utils.py +++ b/openpype/hosts/flame/api/utils.py @@ -51,17 +51,28 @@ def _sync_utility_scripts(env=None): for _k, s_list in scripts.items(): remove_black_list += s_list + log.info("remove_black_list: `{remove_black_list}`".format(**locals())) log.info("Additional Flame script paths: `{fsd_paths}`".format(**locals())) log.info("Flame Scripts: `{scripts}`".format(**locals())) # make sure no script file is in folder if next(iter(os.listdir(flame_shared_dir)), None): - for s in os.listdir(flame_shared_dir): + for _itm in os.listdir(flame_shared_dir): + skip = False + # skip all scripts and folders which are not maintained - if s not in remove_black_list: + if _itm not in remove_black_list: + skip = True + + # do not skyp if pyc in extension + if not os.path.isdir(_itm) and "pyc" in os.path.splitext(_itm)[-1]: + skip = False + + # continue if skip in true + if skip: continue - path = os.path.join(flame_shared_dir, s) + path = os.path.join(flame_shared_dir, _itm) log.info("Removing `{path}`...".format(**locals())) if os.path.isdir(path): shutil.rmtree(path, onerror=None) From 40142effb6f353367110db4b82a24a6c6bb3ff11 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 27 Oct 2021 15:38:17 +0200 Subject: [PATCH 272/279] Flame: missing import --- openpype/hosts/flame/utility_scripts/openpype_in_flame.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/flame/utility_scripts/openpype_in_flame.py b/openpype/hosts/flame/utility_scripts/openpype_in_flame.py index afbd44afc9..50ccbb521c 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_in_flame.py +++ b/openpype/hosts/flame/utility_scripts/openpype_in_flame.py @@ -114,6 +114,7 @@ except ImportError: def rescan_hooks(): + import flame flame.execute_shortcut('Rescan Python Hooks') From 1f5b8132edb7370afa7fa022ea51a4183ecb85eb Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 27 Oct 2021 15:44:49 +0200 Subject: [PATCH 273/279] hound suggestions --- openpype/hosts/flame/api/utils.py | 6 +++--- openpype/hosts/flame/utility_scripts/openpype_in_flame.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/flame/api/utils.py b/openpype/hosts/flame/api/utils.py index 0a3ee68815..a750046362 100644 --- a/openpype/hosts/flame/api/utils.py +++ b/openpype/hosts/flame/api/utils.py @@ -59,15 +59,15 @@ def _sync_utility_scripts(env=None): if next(iter(os.listdir(flame_shared_dir)), None): for _itm in os.listdir(flame_shared_dir): skip = False - + # skip all scripts and folders which are not maintained if _itm not in remove_black_list: skip = True - + # do not skyp if pyc in extension if not os.path.isdir(_itm) and "pyc" in os.path.splitext(_itm)[-1]: skip = False - + # continue if skip in true if skip: continue diff --git a/openpype/hosts/flame/utility_scripts/openpype_in_flame.py b/openpype/hosts/flame/utility_scripts/openpype_in_flame.py index 50ccbb521c..c5fa881f3c 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_in_flame.py +++ b/openpype/hosts/flame/utility_scripts/openpype_in_flame.py @@ -107,14 +107,14 @@ all menu objects as apps. """ try: - import flame + import flame # noqa app_initialized(parent=None) except ImportError: print("!!!! not able to import flame module !!!!") def rescan_hooks(): - import flame + import flame # noqa flame.execute_shortcut('Rescan Python Hooks') @@ -143,7 +143,7 @@ def _build_app_menu(app_name): 'menu_auto_refresh', {}) if menu_auto_refresh.get('timeline_menu', True): try: - import flame + import flame # noqa flame.schedule_idle_event(rescan_hooks) except ImportError: print("!-!!! not able to import flame module !!!!") From 71181342c72a598fd1cb368e4f93211d706232f6 Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Fri, 29 Oct 2021 14:08:20 +0200 Subject: [PATCH 274/279] add photoshop2022 --- .../system_settings/applications.json | 19 ++++++++--- .../host_settings/schema_photoshop.json | 32 ++++++++----------- 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/openpype/settings/defaults/system_settings/applications.json b/openpype/settings/defaults/system_settings/applications.json index cfdeca4b87..17e2d4c551 100644 --- a/openpype/settings/defaults/system_settings/applications.json +++ b/openpype/settings/defaults/system_settings/applications.json @@ -973,8 +973,6 @@ }, "variants": { "2020": { - "enabled": true, - "variant_label": "2020", "executables": { "windows": [ "C:\\Program Files\\Adobe\\Adobe Photoshop 2020\\Photoshop.exe" @@ -990,8 +988,6 @@ "environment": {} }, "2021": { - "enabled": true, - "variant_label": "2021", "executables": { "windows": [ "C:\\Program Files\\Adobe\\Adobe Photoshop 2021\\Photoshop.exe" @@ -1005,6 +1001,21 @@ "linux": [] }, "environment": {} + }, + "2022": { + "executables": { + "windows": [ + "C:\\Program Files\\Adobe\\Adobe Photoshop 2022\\Photoshop.exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": {} } } }, diff --git a/openpype/settings/entities/schemas/system_schema/host_settings/schema_photoshop.json b/openpype/settings/entities/schemas/system_schema/host_settings/schema_photoshop.json index 7bcd89c650..7055232ae7 100644 --- a/openpype/settings/entities/schemas/system_schema/host_settings/schema_photoshop.json +++ b/openpype/settings/entities/schemas/system_schema/host_settings/schema_photoshop.json @@ -20,26 +20,20 @@ "type": "raw-json" }, { - "type": "dict", + "type": "dict-modifiable", "key": "variants", - "children": [ - { - "type": "schema_template", - "name": "template_host_variant", - "template_data": [ - { - "app_variant_label": "2020", - "app_variant": "2020", - "variant_skip_paths": ["use_python_2"] - }, - { - "app_variant_label": "2021", - "app_variant": "2021", - "variant_skip_paths": ["use_python_2"] - } - ] - } - ] + "collapsible_key": true, + "use_label_wrap": false, + "object_type": { + "type": "dict", + "collapsible": true, + "children": [ + { + "type": "schema_template", + "name": "template_host_variant_items" + } + ] + } } ] } From 6bbdd3c6b66b2c130c173e7adfdb8798738233e3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 29 Oct 2021 15:22:54 +0200 Subject: [PATCH 275/279] make representaions key optional --- .../standalonepublisher/plugins/publish/validate_sources.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/standalonepublisher/plugins/publish/validate_sources.py b/openpype/hosts/standalonepublisher/plugins/publish/validate_sources.py index da424cfb45..8baebcfc82 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/validate_sources.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/validate_sources.py @@ -22,7 +22,7 @@ class ValidateSources(pyblish.api.InstancePlugin): def process(self, instance): self.log.info("instance {}".format(instance.data)) - for repr in instance.data["representations"]: + for repr in instance.data.get("representations") or []: files = [] if isinstance(repr["files"], str): files.append(repr["files"]) From 1ca9edac7cdb59c9f4edbe3612bb08d8ee074f22 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 29 Oct 2021 15:23:05 +0200 Subject: [PATCH 276/279] changed 'repr' variable to 'repre' --- .../plugins/publish/validate_sources.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/standalonepublisher/plugins/publish/validate_sources.py b/openpype/hosts/standalonepublisher/plugins/publish/validate_sources.py index 8baebcfc82..eec675e97f 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/validate_sources.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/validate_sources.py @@ -22,15 +22,15 @@ class ValidateSources(pyblish.api.InstancePlugin): def process(self, instance): self.log.info("instance {}".format(instance.data)) - for repr in instance.data.get("representations") or []: + for repre in instance.data.get("representations") or []: files = [] - if isinstance(repr["files"], str): - files.append(repr["files"]) + if isinstance(repre["files"], str): + files.append(repre["files"]) else: - files = list(repr["files"]) + files = list(repre["files"]) for file_name in files: - source_file = os.path.join(repr["stagingDir"], + source_file = os.path.join(repre["stagingDir"], file_name) if not os.path.exists(source_file): From 9e08d20e25627e7acaaa870a1abb4bcce9957aa4 Mon Sep 17 00:00:00 2001 From: clement Date: Fri, 29 Oct 2021 15:40:35 +0200 Subject: [PATCH 277/279] Remove use_python_2 Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../schemas/system_schema/host_settings/schema_photoshop.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/settings/entities/schemas/system_schema/host_settings/schema_photoshop.json b/openpype/settings/entities/schemas/system_schema/host_settings/schema_photoshop.json index 7055232ae7..0687b9699b 100644 --- a/openpype/settings/entities/schemas/system_schema/host_settings/schema_photoshop.json +++ b/openpype/settings/entities/schemas/system_schema/host_settings/schema_photoshop.json @@ -30,7 +30,8 @@ "children": [ { "type": "schema_template", - "name": "template_host_variant_items" + "name": "template_host_variant_items", + "skip_paths": ["use_python_2"] } ] } From d83adaf1849871836a43bac20b453c7ce16f0788 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Sat, 30 Oct 2021 03:40:03 +0000 Subject: [PATCH 278/279] [Automated] Bump version --- CHANGELOG.md | 16 ++++++++-------- openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 68409c4db8..e08a4cf9e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,13 @@ # Changelog -## [3.6.0-nightly.1](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.6.0-nightly.2](https://github.com/pypeclub/OpenPype/tree/HEAD) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.5.0...HEAD) +**πŸ†• New features** + +- Flame: a host basic integration [\#2165](https://github.com/pypeclub/OpenPype/pull/2165) + **πŸš€ Enhancements** - Tools: Experimental tools [\#2167](https://github.com/pypeclub/OpenPype/pull/2167) @@ -14,6 +18,7 @@ **πŸ› Bug fixes** +- StandalonePublisher: Source validator don't expect representations [\#2190](https://github.com/pypeclub/OpenPype/pull/2190) - MacOS: Launching of applications may cause Permissions error [\#2175](https://github.com/pypeclub/OpenPype/pull/2175) - 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) @@ -21,7 +26,6 @@ - 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) - Loader thumbnails with smooth edges [\#2147](https://github.com/pypeclub/OpenPype/pull/2147) -- Added validator for source files for Standalone Publisher [\#2138](https://github.com/pypeclub/OpenPype/pull/2138) **Merged pull requests:** @@ -65,6 +69,7 @@ **πŸ› Bug fixes** +- Added validator for source files for Standalone Publisher [\#2138](https://github.com/pypeclub/OpenPype/pull/2138) - Maya: fix model publishing [\#2130](https://github.com/pypeclub/OpenPype/pull/2130) - 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) @@ -103,9 +108,6 @@ - Loader: Families filtering [\#2043](https://github.com/pypeclub/OpenPype/pull/2043) - Settings UI: Project view enhancements [\#2042](https://github.com/pypeclub/OpenPype/pull/2042) - Settings for Nuke IncrementScriptVersion [\#2039](https://github.com/pypeclub/OpenPype/pull/2039) -- Loader & Library loader: Use tools from OpenPype [\#2038](https://github.com/pypeclub/OpenPype/pull/2038) -- Adding predefined project folders creation in PM [\#2030](https://github.com/pypeclub/OpenPype/pull/2030) -- WebserverModule: Removed interface of webserver module [\#2028](https://github.com/pypeclub/OpenPype/pull/2028) **πŸ› Bug fixes** @@ -126,15 +128,13 @@ **πŸš€ Enhancements** - Added possibility to configure of synchronization of workfile version… [\#2041](https://github.com/pypeclub/OpenPype/pull/2041) +- Loader & Library loader: Use tools from OpenPype [\#2038](https://github.com/pypeclub/OpenPype/pull/2038) - General: Task types in profiles [\#2036](https://github.com/pypeclub/OpenPype/pull/2036) **πŸ› Bug fixes** - Workfiles tool: Task selection [\#2040](https://github.com/pypeclub/OpenPype/pull/2040) - Ftrack: Delete old versions missing settings key [\#2037](https://github.com/pypeclub/OpenPype/pull/2037) -- Nuke: typo on a button [\#2034](https://github.com/pypeclub/OpenPype/pull/2034) -- Hiero: Fix "none" named tags [\#2033](https://github.com/pypeclub/OpenPype/pull/2033) -- FFmpeg: Subprocess arguments as list [\#2032](https://github.com/pypeclub/OpenPype/pull/2032) ## [3.3.1](https://github.com/pypeclub/OpenPype/tree/3.3.1) (2021-08-20) diff --git a/openpype/version.py b/openpype/version.py index 6eb58f6fcc..5906507813 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.1" +__version__ = "3.6.0-nightly.2" diff --git a/pyproject.toml b/pyproject.toml index 1a112d2071..46c6723098 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.6.0-nightly.1" # OpenPype +version = "3.6.0-nightly.2" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From 6410966ef51f3516d09b5b9a3be6f57259fa16da Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sun, 31 Oct 2021 11:23:01 +0100 Subject: [PATCH 279/279] keep raw data as QtCore.QByteArray if already are raw --- openpype/tools/project_manager/project_manager/model.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 5b6ed78b50..b7ab9e40d0 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -1456,7 +1456,11 @@ class HierarchyModel(QtCore.QAbstractItemModel): return raw_data = mime_data.data("application/copy_task") - encoded_data = QtCore.QByteArray.fromRawData(raw_data) + if isinstance(raw_data, QtCore.QByteArray): + # Raw data are already QByteArrat and we don't have to load them + encoded_data = raw_data + else: + encoded_data = QtCore.QByteArray.fromRawData(raw_data) stream = QtCore.QDataStream(encoded_data, QtCore.QIODevice.ReadOnly) text = stream.readQString() try: